Commit 45c5f87b16288c3eff29a3f9d7fb61e8990d37ce
Merge branch 'master_dev' into 'master'
refactor: 命令下发逻辑调整 See merge request yunteng/thingskit!210
Showing
36 changed files
with
1875 additions
and
1426 deletions
... | ... | @@ -72,6 +72,7 @@ import org.thingsboard.server.dao.tenant.TenantProfileService; |
72 | 72 | import org.thingsboard.server.dao.tenant.TenantService; |
73 | 73 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
74 | 74 | import org.thingsboard.server.dao.user.UserService; |
75 | +import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | |
75 | 76 | import org.thingsboard.server.queue.discovery.PartitionService; |
76 | 77 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
77 | 78 | import org.thingsboard.server.queue.usagestats.TbApiUsageClient; |
... | ... | @@ -645,4 +646,10 @@ public class ActorSystemContext { |
645 | 646 | } |
646 | 647 | } |
647 | 648 | |
649 | + | |
650 | + //Thingskit function | |
651 | + @Autowired | |
652 | + @Getter | |
653 | + private TkDeviceService tkDeviceService; | |
654 | + | |
648 | 655 | } | ... | ... |
1 | 1 | /** |
2 | 2 | * Copyright © 2016-2022 The Thingsboard Authors |
3 | 3 | * |
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | - * you may not use this file except in compliance with the License. | |
6 | - * You may obtain a copy of the License at | |
4 | + * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file | |
5 | + * except in compliance with the License. You may obtain a copy of the License at | |
7 | 6 | * |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
7 | + * <p>http://www.apache.org/licenses/LICENSE-2.0 | |
9 | 8 | * |
10 | - * Unless required by applicable law or agreed to in writing, software | |
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | - * See the License for the specific language governing permissions and | |
9 | + * <p>Unless required by applicable law or agreed to in writing, software distributed under the | |
10 | + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | |
11 | + * express or implied. See the License for the specific language governing permissions and | |
14 | 12 | * limitations under the License. |
15 | 13 | */ |
16 | 14 | package org.thingsboard.server.actors.device; |
... | ... | @@ -22,6 +20,21 @@ import com.google.common.util.concurrent.Futures; |
22 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
23 | 21 | import com.google.common.util.concurrent.MoreExecutors; |
24 | 22 | import com.google.protobuf.InvalidProtocolBufferException; |
23 | +import java.util.ArrayList; | |
24 | +import java.util.Arrays; | |
25 | +import java.util.Collections; | |
26 | +import java.util.HashMap; | |
27 | +import java.util.HashSet; | |
28 | +import java.util.LinkedHashMap; | |
29 | +import java.util.List; | |
30 | +import java.util.Map; | |
31 | +import java.util.Objects; | |
32 | +import java.util.Optional; | |
33 | +import java.util.Set; | |
34 | +import java.util.UUID; | |
35 | +import java.util.function.Consumer; | |
36 | +import java.util.stream.Collectors; | |
37 | +import javax.annotation.Nullable; | |
25 | 38 | import lombok.extern.slf4j.Slf4j; |
26 | 39 | import org.apache.commons.collections.CollectionUtils; |
27 | 40 | import org.thingsboard.common.util.JacksonUtil; |
... | ... | @@ -35,7 +48,6 @@ import org.thingsboard.server.actors.TbActorCtx; |
35 | 48 | import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
36 | 49 | import org.thingsboard.server.common.data.DataConstants; |
37 | 50 | import org.thingsboard.server.common.data.Device; |
38 | -import org.thingsboard.server.common.data.DeviceTransportType; | |
39 | 51 | import org.thingsboard.server.common.data.StringUtils; |
40 | 52 | import org.thingsboard.server.common.data.edge.EdgeEvent; |
41 | 53 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
... | ... | @@ -59,8 +71,6 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; |
59 | 71 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
60 | 72 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
61 | 73 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
62 | -import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | |
63 | -import org.thingsboard.server.common.data.yunteng.enums.CmdTypeEnum; | |
64 | 74 | import org.thingsboard.server.common.msg.TbActorMsg; |
65 | 75 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
66 | 76 | import org.thingsboard.server.common.msg.queue.TbCallback; |
... | ... | @@ -97,921 +107,1109 @@ import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; |
97 | 107 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; |
98 | 108 | import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; |
99 | 109 | |
100 | -import javax.annotation.Nullable; | |
101 | -import java.util.ArrayList; | |
102 | -import java.util.Arrays; | |
103 | -import java.util.Collections; | |
104 | -import java.util.ConcurrentModificationException; | |
105 | -import java.util.HashMap; | |
106 | -import java.util.HashSet; | |
107 | -import java.util.LinkedHashMap; | |
108 | -import java.util.List; | |
109 | -import java.util.Map; | |
110 | -import java.util.Objects; | |
111 | -import java.util.Optional; | |
112 | -import java.util.Set; | |
113 | -import java.util.UUID; | |
114 | -import java.util.function.Consumer; | |
115 | -import java.util.stream.Collectors; | |
116 | - | |
117 | - | |
118 | 110 | /** |
119 | 111 | * @author Andrew Shvayka |
120 | 112 | */ |
121 | 113 | @Slf4j |
122 | 114 | class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
123 | 115 | |
124 | - static final String SESSION_TIMEOUT_MESSAGE = "session timeout!"; | |
125 | - final TenantId tenantId; | |
126 | - final DeviceId deviceId; | |
127 | - final LinkedHashMapRemoveEldest<UUID, SessionInfoMetaData> sessions; | |
128 | - private final Map<UUID, SessionInfo> attributeSubscriptions; | |
129 | - private final Map<UUID, SessionInfo> rpcSubscriptions; | |
130 | - private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap; | |
131 | - private final boolean rpcSequential; | |
132 | - | |
133 | - private int rpcSeq = 0; | |
134 | - private String deviceName; | |
135 | - private String deviceType; | |
136 | - private TbMsgMetaData defaultMetaData; | |
137 | - private EdgeId edgeId; | |
138 | - | |
139 | - DeviceActorMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { | |
140 | - super(systemContext); | |
141 | - this.tenantId = tenantId; | |
142 | - this.deviceId = deviceId; | |
143 | - this.rpcSequential = systemContext.isRpcSequential(); | |
144 | - this.attributeSubscriptions = new HashMap<>(); | |
145 | - this.rpcSubscriptions = new HashMap<>(); | |
146 | - this.toDeviceRpcPendingMap = new LinkedHashMap<>(); | |
147 | - this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSessionMaxSessionsLimit); | |
148 | - if (initAttributes()) { | |
149 | - restoreSessions(); | |
150 | - } | |
116 | + static final String SESSION_TIMEOUT_MESSAGE = "session timeout!"; | |
117 | + final TenantId tenantId; | |
118 | + final DeviceId deviceId; | |
119 | + final LinkedHashMapRemoveEldest<UUID, SessionInfoMetaData> sessions; | |
120 | + private final Map<UUID, SessionInfo> attributeSubscriptions; | |
121 | + private final Map<UUID, SessionInfo> rpcSubscriptions; | |
122 | + private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap; | |
123 | + private final boolean rpcSequential; | |
124 | + | |
125 | + private int rpcSeq = 0; | |
126 | + private String deviceName; | |
127 | + private String deviceType; | |
128 | + private TbMsgMetaData defaultMetaData; | |
129 | + private EdgeId edgeId; | |
130 | + | |
131 | + DeviceActorMessageProcessor( | |
132 | + ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { | |
133 | + super(systemContext); | |
134 | + this.tenantId = tenantId; | |
135 | + this.deviceId = deviceId; | |
136 | + this.rpcSequential = systemContext.isRpcSequential(); | |
137 | + this.attributeSubscriptions = new HashMap<>(); | |
138 | + this.rpcSubscriptions = new HashMap<>(); | |
139 | + this.toDeviceRpcPendingMap = new LinkedHashMap<>(); | |
140 | + this.sessions = | |
141 | + new LinkedHashMapRemoveEldest<>( | |
142 | + systemContext.getMaxConcurrentSessionsPerDevice(), | |
143 | + this::notifyTransportAboutClosedSessionMaxSessionsLimit); | |
144 | + if (initAttributes()) { | |
145 | + restoreSessions(); | |
151 | 146 | } |
152 | - | |
153 | - boolean initAttributes() { | |
154 | - Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId); | |
155 | - if (device != null) { | |
156 | - this.deviceName = device.getName(); | |
157 | - this.deviceType = device.getType(); | |
158 | - this.defaultMetaData = new TbMsgMetaData(); | |
159 | - this.defaultMetaData.putValue("deviceName", deviceName); | |
160 | - this.defaultMetaData.putValue("deviceType", deviceType); | |
161 | - if (systemContext.isEdgesEnabled()) { | |
162 | - this.edgeId = findRelatedEdgeId(); | |
163 | - } | |
164 | - return true; | |
165 | - } else { | |
166 | - return false; | |
167 | - } | |
147 | + } | |
148 | + | |
149 | + boolean initAttributes() { | |
150 | + Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId); | |
151 | + if (device != null) { | |
152 | + this.deviceName = device.getName(); | |
153 | + this.deviceType = device.getType(); | |
154 | + this.defaultMetaData = new TbMsgMetaData(); | |
155 | + this.defaultMetaData.putValue("deviceName", deviceName); | |
156 | + this.defaultMetaData.putValue("deviceType", deviceType); | |
157 | + if (systemContext.isEdgesEnabled()) { | |
158 | + this.edgeId = findRelatedEdgeId(); | |
159 | + } | |
160 | + return true; | |
161 | + } else { | |
162 | + return false; | |
168 | 163 | } |
169 | - | |
170 | - private EdgeId findRelatedEdgeId() { | |
171 | - List<EntityRelation> result = | |
172 | - systemContext.getRelationService().findByToAndType(tenantId, deviceId, EntityRelation.EDGE_TYPE, RelationTypeGroup.COMMON); | |
173 | - if (result != null && result.size() > 0) { | |
174 | - EntityRelation relationToEdge = result.get(0); | |
175 | - if (relationToEdge.getFrom() != null && relationToEdge.getFrom().getId() != null) { | |
176 | - log.trace("[{}][{}] found edge [{}] for device", tenantId, deviceId, relationToEdge.getFrom().getId()); | |
177 | - return new EdgeId(relationToEdge.getFrom().getId()); | |
178 | - } else { | |
179 | - log.trace("[{}][{}] edge relation is empty {}", tenantId, deviceId, relationToEdge); | |
180 | - } | |
181 | - } else { | |
182 | - log.trace("[{}][{}] device doesn't have any related edge", tenantId, deviceId); | |
183 | - } | |
184 | - return null; | |
164 | + } | |
165 | + | |
166 | + private EdgeId findRelatedEdgeId() { | |
167 | + List<EntityRelation> result = | |
168 | + systemContext | |
169 | + .getRelationService() | |
170 | + .findByToAndType( | |
171 | + tenantId, deviceId, EntityRelation.EDGE_TYPE, RelationTypeGroup.COMMON); | |
172 | + if (result != null && result.size() > 0) { | |
173 | + EntityRelation relationToEdge = result.get(0); | |
174 | + if (relationToEdge.getFrom() != null && relationToEdge.getFrom().getId() != null) { | |
175 | + log.trace( | |
176 | + "[{}][{}] found edge [{}] for device", | |
177 | + tenantId, | |
178 | + deviceId, | |
179 | + relationToEdge.getFrom().getId()); | |
180 | + return new EdgeId(relationToEdge.getFrom().getId()); | |
181 | + } else { | |
182 | + log.trace("[{}][{}] edge relation is empty {}", tenantId, deviceId, relationToEdge); | |
183 | + } | |
184 | + } else { | |
185 | + log.trace("[{}][{}] device doesn't have any related edge", tenantId, deviceId); | |
186 | + } | |
187 | + return null; | |
188 | + } | |
189 | + | |
190 | + void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) { | |
191 | + ToDeviceRpcRequest request = msg.getMsg(); | |
192 | + ToDeviceRpcRequestMsg rpcRequest = creteToDeviceRpcRequestMsg(request); | |
193 | + | |
194 | + long timeout = request.getExpirationTime() - System.currentTimeMillis(); | |
195 | + boolean persisted = request.isPersisted(); | |
196 | + | |
197 | + if (timeout <= 0) { | |
198 | + log.debug( | |
199 | + "[{}][{}] Ignoring message due to exp time reached, {}", | |
200 | + deviceId, | |
201 | + request.getId(), | |
202 | + request.getExpirationTime()); | |
203 | + if (persisted) { | |
204 | + createRpc(request, RpcStatus.EXPIRED); | |
205 | + } | |
206 | + return; | |
207 | + } else if (persisted) { | |
208 | + createRpc(request, RpcStatus.QUEUED); | |
185 | 209 | } |
186 | 210 | |
187 | - void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) { | |
188 | - ToDeviceRpcRequest request = msg.getMsg(); | |
189 | - ToDeviceRpcRequestMsg rpcRequest = creteToDeviceRpcRequestMsg(request); | |
190 | - | |
191 | - long timeout = request.getExpirationTime() - System.currentTimeMillis(); | |
192 | - boolean persisted = request.isPersisted(); | |
193 | - | |
194 | - if (timeout <= 0) { | |
195 | - log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime()); | |
196 | - if (persisted) { | |
197 | - createRpc(request, RpcStatus.EXPIRED); | |
211 | + boolean sent = false; | |
212 | + if (systemContext.isEdgesEnabled() && edgeId != null) { | |
213 | + log.debug( | |
214 | + "[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", | |
215 | + tenantId, | |
216 | + deviceId, | |
217 | + edgeId.getId()); | |
218 | + saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()); | |
219 | + sent = true; | |
220 | + } else if (isSendNewRpcAvailable()) { | |
221 | + sent = rpcSubscriptions.size() > 0; | |
222 | + Set<UUID> syncSessionSet = new HashSet<>(); | |
223 | + rpcSubscriptions.forEach( | |
224 | + (key, value) -> { | |
225 | + sendToTransport(rpcRequest, key, value.getNodeId()); | |
226 | + if (SessionType.SYNC == value.getType()) { | |
227 | + syncSessionSet.add(key); | |
198 | 228 | } |
199 | - return; | |
200 | - } else if (persisted) { | |
201 | - createRpc(request, RpcStatus.QUEUED); | |
202 | - } | |
203 | - | |
204 | - boolean sent = false; | |
205 | - if (systemContext.isEdgesEnabled() && edgeId != null) { | |
206 | - log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId()); | |
207 | - saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()); | |
208 | - sent = true; | |
209 | - } else if (isSendNewRpcAvailable()) { | |
210 | - sent = rpcSubscriptions.size() > 0; | |
211 | - Set<UUID> syncSessionSet = new HashSet<>(); | |
212 | - rpcSubscriptions.forEach((key, value) -> { | |
213 | - sendToTransport(rpcRequest, key, value.getNodeId()); | |
214 | - if (SessionType.SYNC == value.getType()) { | |
215 | - syncSessionSet.add(key); | |
216 | - } | |
217 | - }); | |
218 | - log.trace("Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions); | |
219 | - syncSessionSet.forEach(rpcSubscriptions::remove); | |
220 | - } | |
221 | - | |
222 | - if (persisted) { | |
223 | - ObjectNode response = JacksonUtil.newObjectNode(); | |
224 | - response.put("rpcId", request.getId().toString()); | |
225 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null)); | |
226 | - } | |
227 | - | |
228 | - if (!persisted && request.isOneway() && sent) { | |
229 | - log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); | |
230 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); | |
231 | - } else { | |
232 | - registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); | |
233 | - } | |
234 | - if (sent) { | |
235 | - log.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); | |
236 | - } else { | |
237 | - log.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); | |
238 | - } | |
229 | + }); | |
230 | + log.trace( | |
231 | + "Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions); | |
232 | + syncSessionSet.forEach(rpcSubscriptions::remove); | |
239 | 233 | } |
240 | 234 | |
241 | - private boolean isSendNewRpcAvailable() { | |
242 | - return !rpcSequential || toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty(); | |
235 | + if (persisted) { | |
236 | + ObjectNode response = JacksonUtil.newObjectNode(); | |
237 | + response.put("rpcId", request.getId().toString()); | |
238 | + systemContext | |
239 | + .getTbCoreDeviceRpcService() | |
240 | + .processRpcResponseFromDeviceActor( | |
241 | + new FromDeviceRpcResponse( | |
242 | + msg.getMsg().getId(), JacksonUtil.toString(response), null)); | |
243 | 243 | } |
244 | 244 | |
245 | - private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) { | |
246 | - //Thingskit function | |
247 | - JsonNode old = JacksonUtil.toJsonNode(request.getAdditionalInfo()); | |
248 | - ObjectNode additional = (old == null || old.isEmpty()) ?mapper.createObjectNode():(ObjectNode)old; | |
249 | - if(!additional.has(ModelConstants.TablePropertyMapping.COMMAND_TYPE)){ | |
250 | - additional.put(ModelConstants.TablePropertyMapping.COMMAND_TYPE, CmdTypeEnum.DIY.ordinal()); | |
251 | - } | |
252 | - DeviceId saveDeviceId = deviceId; | |
253 | - if(additional.has(FastIotConstants.Rpc.TARGET_ID)){ | |
254 | - saveDeviceId = new DeviceId(UUID.fromString(additional.get(FastIotConstants.Rpc.TARGET_ID).asText())); | |
255 | - } | |
256 | - | |
257 | - Rpc rpc = new Rpc(new RpcId(request.getId())); | |
258 | - rpc.setCreatedTime(System.currentTimeMillis()); | |
259 | - rpc.setTenantId(tenantId); | |
260 | - rpc.setDeviceId(saveDeviceId); | |
261 | - rpc.setExpirationTime(request.getExpirationTime()); | |
262 | - rpc.setRequest(JacksonUtil.valueToTree(request)); | |
263 | - rpc.setStatus(status); | |
264 | - rpc.setAdditionalInfo(JacksonUtil.toJsonNode(request.getAdditionalInfo())); | |
265 | - | |
266 | - rpc.setAdditionalInfo(additional); | |
267 | - | |
268 | - return systemContext.getTbRpcService().save(tenantId, rpc); | |
269 | - } | |
270 | - | |
271 | - private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) { | |
272 | - ToDeviceRpcRequestBody body = request.getBody(); | |
273 | - return ToDeviceRpcRequestMsg.newBuilder() | |
274 | - .setRequestId(rpcSeq++) | |
275 | - .setMethodName(body.getMethod()) | |
276 | - .setParams(body.getParams()) | |
277 | - .setExpirationTime(request.getExpirationTime()) | |
278 | - .setRequestIdMSB(request.getId().getMostSignificantBits()) | |
279 | - .setRequestIdLSB(request.getId().getLeastSignificantBits()) | |
280 | - .setOneway(request.isOneway()) | |
281 | - .setPersisted(request.isPersisted()) | |
282 | - .build(); | |
283 | - } | |
284 | - | |
285 | - void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) { | |
286 | - log.debug("[{}] Processing rpc command response from edge session", deviceId); | |
287 | - ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
288 | - boolean success = requestMd != null; | |
289 | - if (success) { | |
290 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(responseMsg.getMsg()); | |
291 | - } else { | |
292 | - log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); | |
293 | - } | |
245 | + if (!persisted && request.isOneway() && sent) { | |
246 | + log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); | |
247 | + systemContext | |
248 | + .getTbCoreDeviceRpcService() | |
249 | + .processRpcResponseFromDeviceActor( | |
250 | + new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); | |
251 | + } else { | |
252 | + registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); | |
294 | 253 | } |
295 | - | |
296 | - void processRemoveRpc(TbActorCtx context, RemoveRpcActorMsg msg) { | |
297 | - log.debug("[{}] Processing remove rpc command", msg.getRequestId()); | |
298 | - Map.Entry<Integer, ToDeviceRpcRequestMetadata> entry = null; | |
299 | - for (Map.Entry<Integer, ToDeviceRpcRequestMetadata> e : toDeviceRpcPendingMap.entrySet()) { | |
300 | - if (e.getValue().getMsg().getMsg().getId().equals(msg.getRequestId())) { | |
301 | - entry = e; | |
302 | - break; | |
303 | - } | |
304 | - } | |
305 | - | |
306 | - if (entry != null) { | |
307 | - if (entry.getValue().isDelivered()) { | |
308 | - toDeviceRpcPendingMap.remove(entry.getKey()); | |
309 | - } else { | |
310 | - Optional<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> firstRpc = getFirstRpc(); | |
311 | - if (firstRpc.isPresent() && entry.getKey().equals(firstRpc.get().getKey())) { | |
312 | - toDeviceRpcPendingMap.remove(entry.getKey()); | |
313 | - sendNextPendingRequest(context); | |
314 | - } else { | |
315 | - toDeviceRpcPendingMap.remove(entry.getKey()); | |
316 | - } | |
317 | - } | |
318 | - } | |
254 | + if (sent) { | |
255 | + log.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); | |
256 | + } else { | |
257 | + log.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); | |
319 | 258 | } |
320 | - | |
321 | - private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { | |
322 | - toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); | |
323 | - DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout); | |
324 | - scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout()); | |
259 | + } | |
260 | + | |
261 | + private boolean isSendNewRpcAvailable() { | |
262 | + return !rpcSequential | |
263 | + || toDeviceRpcPendingMap.values().stream() | |
264 | + .filter(md -> !md.isDelivered()) | |
265 | + .findAny() | |
266 | + .isEmpty(); | |
267 | + } | |
268 | + | |
269 | + private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) { | |
270 | + // Thingskit function | |
271 | + ObjectNode additional = (ObjectNode) JacksonUtil.toJsonNode(request.getAdditionalInfo()); | |
272 | + DeviceId saveDeviceId = deviceId; | |
273 | + if (additional.has(FastIotConstants.Rpc.TARGET_ID)) { | |
274 | + saveDeviceId = | |
275 | + new DeviceId(UUID.fromString(additional.get(FastIotConstants.Rpc.TARGET_ID).asText())); | |
325 | 276 | } |
326 | 277 | |
327 | - void processServerSideRpcTimeout(TbActorCtx context, DeviceActorServerSideRpcTimeoutMsg msg) { | |
328 | - ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); | |
329 | - if (requestMd != null) { | |
330 | - log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); | |
331 | - if (requestMd.getMsg().getMsg().isPersisted()) { | |
332 | - systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.EXPIRED, null); | |
333 | - } | |
334 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), | |
335 | - null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); | |
336 | - if (!requestMd.isDelivered()) { | |
337 | - sendNextPendingRequest(context); | |
338 | - } | |
339 | - } | |
278 | + Rpc rpc = new Rpc(new RpcId(request.getId())); | |
279 | + rpc.setCreatedTime(System.currentTimeMillis()); | |
280 | + rpc.setTenantId(tenantId); | |
281 | + rpc.setDeviceId(saveDeviceId); | |
282 | + rpc.setExpirationTime(request.getExpirationTime()); | |
283 | + rpc.setRequest(JacksonUtil.valueToTree(request)); | |
284 | + rpc.setStatus(status); | |
285 | + rpc.setAdditionalInfo(JacksonUtil.toJsonNode(request.getAdditionalInfo())); | |
286 | + | |
287 | + rpc.setAdditionalInfo(additional); | |
288 | + | |
289 | + return systemContext.getTbRpcService().save(tenantId, rpc); | |
290 | + } | |
291 | + | |
292 | + private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) { | |
293 | + ToDeviceRpcRequestBody body = request.getBody(); | |
294 | + return ToDeviceRpcRequestMsg.newBuilder() | |
295 | + .setRequestId(rpcSeq++) | |
296 | + .setMethodName(body.getMethod()) | |
297 | + .setParams(body.getParams()) | |
298 | + .setExpirationTime(request.getExpirationTime()) | |
299 | + .setRequestIdMSB(request.getId().getMostSignificantBits()) | |
300 | + .setRequestIdLSB(request.getId().getLeastSignificantBits()) | |
301 | + .setOneway(request.isOneway()) | |
302 | + .setPersisted(request.isPersisted()) | |
303 | + .build(); | |
304 | + } | |
305 | + | |
306 | + void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) { | |
307 | + log.debug("[{}] Processing rpc command response from edge session", deviceId); | |
308 | + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
309 | + boolean success = requestMd != null; | |
310 | + if (success) { | |
311 | + systemContext | |
312 | + .getTbCoreDeviceRpcService() | |
313 | + .processRpcResponseFromDeviceActor(responseMsg.getMsg()); | |
314 | + } else { | |
315 | + log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); | |
316 | + } | |
317 | + } | |
318 | + | |
319 | + void processRemoveRpc(TbActorCtx context, RemoveRpcActorMsg msg) { | |
320 | + log.debug("[{}] Processing remove rpc command", msg.getRequestId()); | |
321 | + Map.Entry<Integer, ToDeviceRpcRequestMetadata> entry = null; | |
322 | + for (Map.Entry<Integer, ToDeviceRpcRequestMetadata> e : toDeviceRpcPendingMap.entrySet()) { | |
323 | + if (e.getValue().getMsg().getMsg().getId().equals(msg.getRequestId())) { | |
324 | + entry = e; | |
325 | + break; | |
326 | + } | |
340 | 327 | } |
341 | 328 | |
342 | - private void sendPendingRequests(TbActorCtx context, UUID sessionId, String nodeId) { | |
343 | - SessionType sessionType = getSessionType(sessionId); | |
344 | - if (!toDeviceRpcPendingMap.isEmpty()) { | |
345 | - log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); | |
346 | - if (sessionType == SessionType.SYNC) { | |
347 | - log.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); | |
348 | - rpcSubscriptions.remove(sessionId); | |
349 | - } | |
350 | - } else { | |
351 | - log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); | |
352 | - } | |
353 | - Set<Integer> sentOneWayIds = new HashSet<>(); | |
354 | - | |
355 | - if (rpcSequential) { | |
356 | - getFirstRpc().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds)); | |
357 | - } else if (sessionType == SessionType.ASYNC) { | |
358 | - toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, nodeId, sentOneWayIds)); | |
329 | + if (entry != null) { | |
330 | + if (entry.getValue().isDelivered()) { | |
331 | + toDeviceRpcPendingMap.remove(entry.getKey()); | |
332 | + } else { | |
333 | + Optional<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> firstRpc = getFirstRpc(); | |
334 | + if (firstRpc.isPresent() && entry.getKey().equals(firstRpc.get().getKey())) { | |
335 | + toDeviceRpcPendingMap.remove(entry.getKey()); | |
336 | + sendNextPendingRequest(context); | |
359 | 337 | } else { |
360 | - toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds)); | |
338 | + toDeviceRpcPendingMap.remove(entry.getKey()); | |
361 | 339 | } |
362 | - | |
363 | - sentOneWayIds.stream().filter(id -> !toDeviceRpcPendingMap.get(id).getMsg().getMsg().isPersisted()).forEach(toDeviceRpcPendingMap::remove); | |
340 | + } | |
364 | 341 | } |
365 | - | |
366 | - private Optional<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> getFirstRpc() { | |
367 | - return toDeviceRpcPendingMap.entrySet().stream().filter(e -> !e.getValue().isDelivered()).findFirst(); | |
342 | + } | |
343 | + | |
344 | + private void registerPendingRpcRequest( | |
345 | + TbActorCtx context, | |
346 | + ToDeviceRpcRequestActorMsg msg, | |
347 | + boolean sent, | |
348 | + ToDeviceRpcRequestMsg rpcRequest, | |
349 | + long timeout) { | |
350 | + toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); | |
351 | + DeviceActorServerSideRpcTimeoutMsg timeoutMsg = | |
352 | + new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout); | |
353 | + scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout()); | |
354 | + } | |
355 | + | |
356 | + void processServerSideRpcTimeout(TbActorCtx context, DeviceActorServerSideRpcTimeoutMsg msg) { | |
357 | + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); | |
358 | + if (requestMd != null) { | |
359 | + log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); | |
360 | + if (requestMd.getMsg().getMsg().isPersisted()) { | |
361 | + systemContext | |
362 | + .getTbRpcService() | |
363 | + .save( | |
364 | + tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.EXPIRED, null); | |
365 | + } | |
366 | + systemContext | |
367 | + .getTbCoreDeviceRpcService() | |
368 | + .processRpcResponseFromDeviceActor( | |
369 | + new FromDeviceRpcResponse( | |
370 | + requestMd.getMsg().getMsg().getId(), | |
371 | + null, | |
372 | + requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); | |
373 | + if (!requestMd.isDelivered()) { | |
374 | + sendNextPendingRequest(context); | |
375 | + } | |
368 | 376 | } |
369 | - | |
370 | - private void sendNextPendingRequest(TbActorCtx context) { | |
371 | - if (rpcSequential) { | |
372 | - rpcSubscriptions.forEach((id, s) -> sendPendingRequests(context, id, s.getNodeId())); | |
373 | - } | |
377 | + } | |
378 | + | |
379 | + private void sendPendingRequests(TbActorCtx context, UUID sessionId, String nodeId) { | |
380 | + SessionType sessionType = getSessionType(sessionId); | |
381 | + if (!toDeviceRpcPendingMap.isEmpty()) { | |
382 | + log.debug( | |
383 | + "[{}] Pushing {} pending RPC messages to new async session [{}]", | |
384 | + deviceId, | |
385 | + toDeviceRpcPendingMap.size(), | |
386 | + sessionId); | |
387 | + if (sessionType == SessionType.SYNC) { | |
388 | + log.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); | |
389 | + rpcSubscriptions.remove(sessionId); | |
390 | + } | |
391 | + } else { | |
392 | + log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); | |
374 | 393 | } |
375 | - | |
376 | - private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(TbActorCtx context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) { | |
377 | - return entry -> { | |
378 | - ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg(); | |
379 | - ToDeviceRpcRequestBody body = request.getBody(); | |
380 | - if (request.isOneway() && !rpcSequential) { | |
381 | - sentOneWayIds.add(entry.getKey()); | |
382 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null)); | |
383 | - } | |
384 | - ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder() | |
385 | - .setRequestId(entry.getKey()) | |
386 | - .setMethodName(body.getMethod()) | |
387 | - .setParams(body.getParams()) | |
388 | - .setExpirationTime(request.getExpirationTime()) | |
389 | - .setRequestIdMSB(request.getId().getMostSignificantBits()) | |
390 | - .setRequestIdLSB(request.getId().getLeastSignificantBits()) | |
391 | - .setOneway(request.isOneway()) | |
392 | - .setPersisted(request.isPersisted()) | |
393 | - .build(); | |
394 | - sendToTransport(rpcRequest, sessionId, nodeId); | |
395 | - }; | |
396 | - } | |
397 | - | |
398 | - void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) { | |
399 | - TransportToDeviceActorMsg msg = wrapper.getMsg(); | |
400 | - TbCallback callback = wrapper.getCallback(); | |
401 | - var sessionInfo = msg.getSessionInfo(); | |
402 | - | |
403 | - if (msg.hasSessionEvent()) { | |
404 | - processSessionStateMsgs(sessionInfo, msg.getSessionEvent()); | |
405 | - } | |
406 | - if (msg.hasSubscribeToAttributes()) { | |
407 | - processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToAttributes()); | |
408 | - } | |
409 | - if (msg.hasSubscribeToRPC()) { | |
410 | - processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC()); | |
411 | - } | |
412 | - if (msg.hasSendPendingRPC()) { | |
413 | - sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo.getNodeId()); | |
414 | - } | |
415 | - if (msg.hasGetAttributes()) { | |
416 | - handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes()); | |
417 | - } | |
418 | - if (msg.hasToDeviceRPCCallResponse()) { | |
419 | - processRpcResponses(context, sessionInfo, msg.getToDeviceRPCCallResponse()); | |
420 | - } | |
421 | - if (msg.hasSubscriptionInfo()) { | |
422 | - handleSessionActivity(context, sessionInfo, msg.getSubscriptionInfo()); | |
423 | - } | |
424 | - if (msg.hasClaimDevice()) { | |
425 | - handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice()); | |
426 | - } | |
427 | - if (msg.hasRpcResponseStatusMsg()) { | |
428 | - processRpcResponseStatus(context, sessionInfo, msg.getRpcResponseStatusMsg()); | |
429 | - } | |
430 | - if (msg.hasUplinkNotificationMsg()) { | |
431 | - processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg()); | |
432 | - } | |
433 | - callback.onSuccess(); | |
394 | + Set<Integer> sentOneWayIds = new HashSet<>(); | |
395 | + | |
396 | + if (rpcSequential) { | |
397 | + getFirstRpc().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds)); | |
398 | + } else if (sessionType == SessionType.ASYNC) { | |
399 | + toDeviceRpcPendingMap | |
400 | + .entrySet() | |
401 | + .forEach(processPendingRpc(context, sessionId, nodeId, sentOneWayIds)); | |
402 | + } else { | |
403 | + toDeviceRpcPendingMap.entrySet().stream() | |
404 | + .findFirst() | |
405 | + .ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds)); | |
434 | 406 | } |
435 | 407 | |
436 | - private void processUplinkNotificationMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) { | |
437 | - String nodeId = sessionInfo.getNodeId(); | |
438 | - sessions.entrySet().stream() | |
439 | - .filter(kv -> kv.getValue().getSessionInfo().getNodeId().equals(nodeId) && (kv.getValue().isSubscribedToAttributes() || kv.getValue().isSubscribedToRPC())) | |
440 | - .forEach(kv -> { | |
441 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
442 | - .setSessionIdMSB(kv.getKey().getMostSignificantBits()) | |
443 | - .setSessionIdLSB(kv.getKey().getLeastSignificantBits()) | |
444 | - .setUplinkNotificationMsg(uplinkNotificationMsg) | |
445 | - .build(); | |
446 | - systemContext.getTbCoreToTransportService().process(kv.getValue().getSessionInfo().getNodeId(), msg); | |
447 | - }); | |
448 | - } | |
408 | + sentOneWayIds.stream() | |
409 | + .filter(id -> !toDeviceRpcPendingMap.get(id).getMsg().getMsg().isPersisted()) | |
410 | + .forEach(toDeviceRpcPendingMap::remove); | |
411 | + } | |
449 | 412 | |
450 | - private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) { | |
451 | - DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); | |
452 | - systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs()); | |
453 | - } | |
413 | + private Optional<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> getFirstRpc() { | |
414 | + return toDeviceRpcPendingMap.entrySet().stream() | |
415 | + .filter(e -> !e.getValue().isDelivered()) | |
416 | + .findFirst(); | |
417 | + } | |
454 | 418 | |
455 | - private void reportSessionOpen() { | |
456 | - systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId); | |
419 | + private void sendNextPendingRequest(TbActorCtx context) { | |
420 | + if (rpcSequential) { | |
421 | + rpcSubscriptions.forEach((id, s) -> sendPendingRequests(context, id, s.getNodeId())); | |
457 | 422 | } |
458 | - | |
459 | - private void reportSessionClose() { | |
460 | - systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId); | |
423 | + } | |
424 | + | |
425 | + private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc( | |
426 | + TbActorCtx context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) { | |
427 | + return entry -> { | |
428 | + ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg(); | |
429 | + ToDeviceRpcRequestBody body = request.getBody(); | |
430 | + if (request.isOneway() && !rpcSequential) { | |
431 | + sentOneWayIds.add(entry.getKey()); | |
432 | + systemContext | |
433 | + .getTbCoreDeviceRpcService() | |
434 | + .processRpcResponseFromDeviceActor( | |
435 | + new FromDeviceRpcResponse(request.getId(), null, null)); | |
436 | + } | |
437 | + ToDeviceRpcRequestMsg rpcRequest = | |
438 | + ToDeviceRpcRequestMsg.newBuilder() | |
439 | + .setRequestId(entry.getKey()) | |
440 | + .setMethodName(body.getMethod()) | |
441 | + .setParams(body.getParams()) | |
442 | + .setExpirationTime(request.getExpirationTime()) | |
443 | + .setRequestIdMSB(request.getId().getMostSignificantBits()) | |
444 | + .setRequestIdLSB(request.getId().getLeastSignificantBits()) | |
445 | + .setOneway(request.isOneway()) | |
446 | + .setPersisted(request.isPersisted()) | |
447 | + .build(); | |
448 | + sendToTransport(rpcRequest, sessionId, nodeId); | |
449 | + }; | |
450 | + } | |
451 | + | |
452 | + void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) { | |
453 | + TransportToDeviceActorMsg msg = wrapper.getMsg(); | |
454 | + TbCallback callback = wrapper.getCallback(); | |
455 | + var sessionInfo = msg.getSessionInfo(); | |
456 | + | |
457 | + if (msg.hasSessionEvent()) { | |
458 | + processSessionStateMsgs(sessionInfo, msg.getSessionEvent()); | |
461 | 459 | } |
462 | - | |
463 | - private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { | |
464 | - int requestId = request.getRequestId(); | |
465 | - if (request.getOnlyShared()) { | |
466 | - Futures.addCallback(findAllAttributesByScope(DataConstants.SHARED_SCOPE), new FutureCallback<>() { | |
467 | - @Override | |
468 | - public void onSuccess(@Nullable List<AttributeKvEntry> result) { | |
469 | - GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() | |
470 | - .setRequestId(requestId) | |
471 | - .setSharedStateMsg(true) | |
472 | - .addAllSharedAttributeList(toTsKvProtos(result)) | |
473 | - .setIsMultipleAttributesRequest(request.getSharedAttributeNamesCount() > 1) | |
474 | - .build(); | |
475 | - sendToTransport(responseMsg, sessionInfo); | |
476 | - } | |
477 | - | |
478 | - @Override | |
479 | - public void onFailure(Throwable t) { | |
480 | - GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() | |
481 | - .setError(t.getMessage()) | |
482 | - .setSharedStateMsg(true) | |
483 | - .build(); | |
484 | - sendToTransport(responseMsg, sessionInfo); | |
485 | - } | |
486 | - }, MoreExecutors.directExecutor()); | |
487 | - } else { | |
488 | - Futures.addCallback(getAttributesKvEntries(request), new FutureCallback<>() { | |
489 | - @Override | |
490 | - public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) { | |
491 | - GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() | |
492 | - .setRequestId(requestId) | |
493 | - .addAllClientAttributeList(toTsKvProtos(result.get(0))) | |
494 | - .addAllSharedAttributeList(toTsKvProtos(result.get(1))) | |
495 | - .setIsMultipleAttributesRequest( | |
496 | - request.getSharedAttributeNamesCount() + request.getClientAttributeNamesCount() > 1) | |
497 | - .build(); | |
498 | - sendToTransport(responseMsg, sessionInfo); | |
499 | - } | |
500 | - | |
501 | - @Override | |
502 | - public void onFailure(Throwable t) { | |
503 | - GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() | |
504 | - .setError(t.getMessage()) | |
505 | - .build(); | |
506 | - sendToTransport(responseMsg, sessionInfo); | |
507 | - } | |
508 | - }, MoreExecutors.directExecutor()); | |
509 | - } | |
460 | + if (msg.hasSubscribeToAttributes()) { | |
461 | + processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToAttributes()); | |
510 | 462 | } |
511 | - | |
512 | - private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) { | |
513 | - ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture; | |
514 | - ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture; | |
515 | - if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { | |
516 | - clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE); | |
517 | - sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE); | |
518 | - } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { | |
519 | - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); | |
520 | - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); | |
521 | - } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { | |
522 | - clientAttributesFuture = Futures.immediateFuture(Collections.emptyList()); | |
523 | - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); | |
524 | - } else { | |
525 | - sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList()); | |
526 | - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); | |
527 | - } | |
528 | - return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)); | |
463 | + if (msg.hasSubscribeToRPC()) { | |
464 | + processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC()); | |
529 | 465 | } |
530 | - | |
531 | - private ListenableFuture<List<AttributeKvEntry>> findAllAttributesByScope(String scope) { | |
532 | - return systemContext.getAttributesService().findAll(tenantId, deviceId, scope); | |
466 | + if (msg.hasSendPendingRPC()) { | |
467 | + sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo.getNodeId()); | |
533 | 468 | } |
534 | - | |
535 | - private ListenableFuture<List<AttributeKvEntry>> findAttributesByScope(Set<String> attributesSet, String scope) { | |
536 | - return systemContext.getAttributesService().find(tenantId, deviceId, scope, attributesSet); | |
469 | + if (msg.hasGetAttributes()) { | |
470 | + handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes()); | |
537 | 471 | } |
538 | - | |
539 | - private Set<String> toSet(List<String> strings) { | |
540 | - return new HashSet<>(strings); | |
472 | + if (msg.hasToDeviceRPCCallResponse()) { | |
473 | + processRpcResponses(context, sessionInfo, msg.getToDeviceRPCCallResponse()); | |
541 | 474 | } |
542 | - | |
543 | - private SessionType getSessionType(UUID sessionId) { | |
544 | - return sessions.containsKey(sessionId) ? SessionType.ASYNC : SessionType.SYNC; | |
475 | + if (msg.hasSubscriptionInfo()) { | |
476 | + handleSessionActivity(context, sessionInfo, msg.getSubscriptionInfo()); | |
545 | 477 | } |
546 | - | |
547 | - void processAttributesUpdate(TbActorCtx context, DeviceAttributesEventNotificationMsg msg) { | |
548 | - if (attributeSubscriptions.size() > 0) { | |
549 | - boolean hasNotificationData = false; | |
550 | - AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder(); | |
551 | - if (msg.isDeleted()) { | |
552 | - List<String> sharedKeys = msg.getDeletedKeys().stream() | |
553 | - .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) | |
554 | - .map(AttributeKey::getAttributeKey) | |
555 | - .collect(Collectors.toList()); | |
556 | - if (!sharedKeys.isEmpty()) { | |
557 | - notification.addAllSharedDeleted(sharedKeys); | |
558 | - hasNotificationData = true; | |
559 | - } | |
560 | - } else { | |
561 | - if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) { | |
562 | - List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues()); | |
563 | - if (attributes.size() > 0) { | |
564 | - List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto) | |
565 | - .collect(Collectors.toList()); | |
566 | - if (!sharedUpdated.isEmpty()) { | |
567 | - notification.addAllSharedUpdated(sharedUpdated); | |
568 | - hasNotificationData = true; | |
569 | - } | |
570 | - } else { | |
571 | - log.debug("[{}] No public shared side attributes changed!", deviceId); | |
572 | - } | |
573 | - } | |
574 | - } | |
575 | - if (hasNotificationData) { | |
576 | - AttributeUpdateNotificationMsg finalNotification = notification.build(); | |
577 | - attributeSubscriptions.forEach((key, value) -> sendToTransport(finalNotification, key, value.getNodeId())); | |
578 | - } | |
579 | - } else { | |
580 | - log.debug("[{}] No registered attributes subscriptions to process!", deviceId); | |
581 | - } | |
478 | + if (msg.hasClaimDevice()) { | |
479 | + handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice()); | |
582 | 480 | } |
583 | - | |
584 | - private void processRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) { | |
585 | - UUID sessionId = getSessionId(sessionInfo); | |
586 | - log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); | |
587 | - ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
588 | - boolean success = requestMd != null; | |
589 | - if (success) { | |
590 | - boolean hasError = StringUtils.isNotEmpty(responseMsg.getError()); | |
591 | - try { | |
592 | - String payload = hasError ? responseMsg.getError() : responseMsg.getPayload(); | |
593 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor( | |
594 | - new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), | |
595 | - payload, null)); | |
596 | - if (requestMd.getMsg().getMsg().isPersisted()) { | |
597 | - RpcStatus status = hasError ? RpcStatus.FAILED : RpcStatus.SUCCESSFUL; | |
598 | - JsonNode response; | |
599 | - try { | |
600 | - response = JacksonUtil.toJsonNode(payload); | |
601 | - } catch (IllegalArgumentException e) { | |
602 | - response = JacksonUtil.newObjectNode().put("error", payload); | |
603 | - } | |
604 | - systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), status, response); | |
605 | - } | |
606 | - } finally { | |
607 | - if (hasError && !requestMd.isDelivered()) { | |
608 | - sendNextPendingRequest(context); | |
609 | - } | |
610 | - } | |
611 | - } else { | |
612 | - log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); | |
613 | - } | |
481 | + if (msg.hasRpcResponseStatusMsg()) { | |
482 | + processRpcResponseStatus(context, sessionInfo, msg.getRpcResponseStatusMsg()); | |
614 | 483 | } |
615 | - | |
616 | - private void processRpcResponseStatus(TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseStatusMsg responseMsg) { | |
617 | - UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB()); | |
618 | - RpcStatus status = RpcStatus.valueOf(responseMsg.getStatus()); | |
619 | - ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(responseMsg.getRequestId()); | |
620 | - | |
621 | - if (md != null) { | |
622 | - JsonNode response = null; | |
623 | - if (status.equals(RpcStatus.DELIVERED)) { | |
624 | - if (md.getMsg().getMsg().isOneway()) { | |
625 | - toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
626 | - if (rpcSequential) { | |
627 | - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(rpcId, null, null)); | |
628 | - } | |
629 | - } else { | |
630 | - md.setDelivered(true); | |
631 | - } | |
632 | - } else if (status.equals(RpcStatus.TIMEOUT)) { | |
633 | - Integer maxRpcRetries = md.getMsg().getMsg().getRetries(); | |
634 | - maxRpcRetries = maxRpcRetries == null ? systemContext.getMaxRpcRetries() : Math.min(maxRpcRetries, systemContext.getMaxRpcRetries()); | |
635 | - if (maxRpcRetries <= md.getRetries()) { | |
636 | - toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
637 | - status = RpcStatus.FAILED; | |
638 | - response = JacksonUtil.newObjectNode().put("error", "There was a Timeout and all retry attempts have been exhausted. Retry attempts set: " + maxRpcRetries); | |
639 | - } else { | |
640 | - md.setRetries(md.getRetries() + 1); | |
641 | - } | |
484 | + if (msg.hasUplinkNotificationMsg()) { | |
485 | + processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg()); | |
486 | + } | |
487 | + callback.onSuccess(); | |
488 | + } | |
489 | + | |
490 | + private void processUplinkNotificationMsg( | |
491 | + TbActorCtx context, | |
492 | + SessionInfoProto sessionInfo, | |
493 | + TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) { | |
494 | + String nodeId = sessionInfo.getNodeId(); | |
495 | + sessions.entrySet().stream() | |
496 | + .filter( | |
497 | + kv -> | |
498 | + kv.getValue().getSessionInfo().getNodeId().equals(nodeId) | |
499 | + && (kv.getValue().isSubscribedToAttributes() | |
500 | + || kv.getValue().isSubscribedToRPC())) | |
501 | + .forEach( | |
502 | + kv -> { | |
503 | + ToTransportMsg msg = | |
504 | + ToTransportMsg.newBuilder() | |
505 | + .setSessionIdMSB(kv.getKey().getMostSignificantBits()) | |
506 | + .setSessionIdLSB(kv.getKey().getLeastSignificantBits()) | |
507 | + .setUplinkNotificationMsg(uplinkNotificationMsg) | |
508 | + .build(); | |
509 | + systemContext | |
510 | + .getTbCoreToTransportService() | |
511 | + .process(kv.getValue().getSessionInfo().getNodeId(), msg); | |
512 | + }); | |
513 | + } | |
514 | + | |
515 | + private void handleClaimDeviceMsg( | |
516 | + TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) { | |
517 | + DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); | |
518 | + systemContext | |
519 | + .getClaimDevicesService() | |
520 | + .registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs()); | |
521 | + } | |
522 | + | |
523 | + private void reportSessionOpen() { | |
524 | + systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId); | |
525 | + } | |
526 | + | |
527 | + private void reportSessionClose() { | |
528 | + systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId); | |
529 | + } | |
530 | + | |
531 | + private void handleGetAttributesRequest( | |
532 | + TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { | |
533 | + int requestId = request.getRequestId(); | |
534 | + if (request.getOnlyShared()) { | |
535 | + Futures.addCallback( | |
536 | + findAllAttributesByScope(DataConstants.SHARED_SCOPE), | |
537 | + new FutureCallback<>() { | |
538 | + @Override | |
539 | + public void onSuccess(@Nullable List<AttributeKvEntry> result) { | |
540 | + GetAttributeResponseMsg responseMsg = | |
541 | + GetAttributeResponseMsg.newBuilder() | |
542 | + .setRequestId(requestId) | |
543 | + .setSharedStateMsg(true) | |
544 | + .addAllSharedAttributeList(toTsKvProtos(result)) | |
545 | + .setIsMultipleAttributesRequest(request.getSharedAttributeNamesCount() > 1) | |
546 | + .build(); | |
547 | + sendToTransport(responseMsg, sessionInfo); | |
642 | 548 | } |
643 | 549 | |
644 | - if (md.getMsg().getMsg().isPersisted()) { | |
645 | - systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), status, response); | |
550 | + @Override | |
551 | + public void onFailure(Throwable t) { | |
552 | + GetAttributeResponseMsg responseMsg = | |
553 | + GetAttributeResponseMsg.newBuilder() | |
554 | + .setError(t.getMessage()) | |
555 | + .setSharedStateMsg(true) | |
556 | + .build(); | |
557 | + sendToTransport(responseMsg, sessionInfo); | |
646 | 558 | } |
647 | - if (status != RpcStatus.SENT) { | |
648 | - sendNextPendingRequest(context); | |
559 | + }, | |
560 | + MoreExecutors.directExecutor()); | |
561 | + } else { | |
562 | + Futures.addCallback( | |
563 | + getAttributesKvEntries(request), | |
564 | + new FutureCallback<>() { | |
565 | + @Override | |
566 | + public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) { | |
567 | + GetAttributeResponseMsg responseMsg = | |
568 | + GetAttributeResponseMsg.newBuilder() | |
569 | + .setRequestId(requestId) | |
570 | + .addAllClientAttributeList(toTsKvProtos(result.get(0))) | |
571 | + .addAllSharedAttributeList(toTsKvProtos(result.get(1))) | |
572 | + .setIsMultipleAttributesRequest( | |
573 | + request.getSharedAttributeNamesCount() | |
574 | + + request.getClientAttributeNamesCount() | |
575 | + > 1) | |
576 | + .build(); | |
577 | + sendToTransport(responseMsg, sessionInfo); | |
649 | 578 | } |
650 | - } else { | |
651 | - log.info("[{}][{}] Rpc has already removed from pending map.", deviceId, rpcId); | |
652 | - } | |
653 | - } | |
654 | 579 | |
655 | - private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) { | |
656 | - UUID sessionId = getSessionId(sessionInfo); | |
657 | - if (subscribeCmd.getUnsubscribe()) { | |
658 | - log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); | |
659 | - attributeSubscriptions.remove(sessionId); | |
660 | - } else { | |
661 | - SessionInfoMetaData sessionMD = sessions.get(sessionId); | |
662 | - if (sessionMD == null) { | |
663 | - sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); | |
580 | + @Override | |
581 | + public void onFailure(Throwable t) { | |
582 | + GetAttributeResponseMsg responseMsg = | |
583 | + GetAttributeResponseMsg.newBuilder().setError(t.getMessage()).build(); | |
584 | + sendToTransport(responseMsg, sessionInfo); | |
664 | 585 | } |
665 | - sessionMD.setSubscribedToAttributes(true); | |
666 | - log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); | |
667 | - attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo()); | |
668 | - dumpSessions(); | |
669 | - } | |
586 | + }, | |
587 | + MoreExecutors.directExecutor()); | |
670 | 588 | } |
671 | - | |
672 | - private UUID getSessionId(SessionInfoProto sessionInfo) { | |
673 | - return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); | |
589 | + } | |
590 | + | |
591 | + private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries( | |
592 | + GetAttributeRequestMsg request) { | |
593 | + ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture; | |
594 | + ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture; | |
595 | + if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) | |
596 | + && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { | |
597 | + clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE); | |
598 | + sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE); | |
599 | + } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) | |
600 | + && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { | |
601 | + clientAttributesFuture = | |
602 | + findAttributesByScope( | |
603 | + toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); | |
604 | + sharedAttributesFuture = | |
605 | + findAttributesByScope( | |
606 | + toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); | |
607 | + } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) | |
608 | + && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { | |
609 | + clientAttributesFuture = Futures.immediateFuture(Collections.emptyList()); | |
610 | + sharedAttributesFuture = | |
611 | + findAttributesByScope( | |
612 | + toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); | |
613 | + } else { | |
614 | + sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList()); | |
615 | + clientAttributesFuture = | |
616 | + findAttributesByScope( | |
617 | + toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); | |
674 | 618 | } |
675 | - | |
676 | - private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) { | |
677 | - UUID sessionId = getSessionId(sessionInfo); | |
678 | - if (subscribeCmd.getUnsubscribe()) { | |
679 | - log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); | |
680 | - rpcSubscriptions.remove(sessionId); | |
681 | - } else { | |
682 | - SessionInfoMetaData sessionMD = sessions.get(sessionId); | |
683 | - if (sessionMD == null) { | |
684 | - sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); | |
685 | - } | |
686 | - sessionMD.setSubscribedToRPC(true); | |
687 | - log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); | |
688 | - rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo()); | |
689 | - sendPendingRequests(context, sessionId, sessionInfo.getNodeId()); | |
690 | - dumpSessions(); | |
619 | + return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)); | |
620 | + } | |
621 | + | |
622 | + private ListenableFuture<List<AttributeKvEntry>> findAllAttributesByScope(String scope) { | |
623 | + return systemContext.getAttributesService().findAll(tenantId, deviceId, scope); | |
624 | + } | |
625 | + | |
626 | + private ListenableFuture<List<AttributeKvEntry>> findAttributesByScope( | |
627 | + Set<String> attributesSet, String scope) { | |
628 | + return systemContext.getAttributesService().find(tenantId, deviceId, scope, attributesSet); | |
629 | + } | |
630 | + | |
631 | + private Set<String> toSet(List<String> strings) { | |
632 | + return new HashSet<>(strings); | |
633 | + } | |
634 | + | |
635 | + private SessionType getSessionType(UUID sessionId) { | |
636 | + return sessions.containsKey(sessionId) ? SessionType.ASYNC : SessionType.SYNC; | |
637 | + } | |
638 | + | |
639 | + void processAttributesUpdate(TbActorCtx context, DeviceAttributesEventNotificationMsg msg) { | |
640 | + if (attributeSubscriptions.size() > 0) { | |
641 | + boolean hasNotificationData = false; | |
642 | + AttributeUpdateNotificationMsg.Builder notification = | |
643 | + AttributeUpdateNotificationMsg.newBuilder(); | |
644 | + if (msg.isDeleted()) { | |
645 | + List<String> sharedKeys = | |
646 | + msg.getDeletedKeys().stream() | |
647 | + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) | |
648 | + .map(AttributeKey::getAttributeKey) | |
649 | + .collect(Collectors.toList()); | |
650 | + if (!sharedKeys.isEmpty()) { | |
651 | + notification.addAllSharedDeleted(sharedKeys); | |
652 | + hasNotificationData = true; | |
691 | 653 | } |
692 | - } | |
693 | - | |
694 | - private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) { | |
695 | - UUID sessionId = getSessionId(sessionInfo); | |
696 | - Objects.requireNonNull(sessionId); | |
697 | - if (msg.getEvent() == SessionEvent.OPEN) { | |
698 | - if (sessions.containsKey(sessionId)) { | |
699 | - log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); | |
700 | - return; | |
701 | - } | |
702 | - log.debug("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size()); | |
703 | - | |
704 | - sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); | |
705 | - if (sessions.size() == 1) { | |
706 | - reportSessionOpen(); | |
654 | + } else { | |
655 | + if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) { | |
656 | + List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues()); | |
657 | + if (attributes.size() > 0) { | |
658 | + List<TsKvProto> sharedUpdated = | |
659 | + msg.getValues().stream().map(this::toTsKvProto).collect(Collectors.toList()); | |
660 | + if (!sharedUpdated.isEmpty()) { | |
661 | + notification.addAllSharedUpdated(sharedUpdated); | |
662 | + hasNotificationData = true; | |
707 | 663 | } |
708 | - systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); | |
709 | - dumpSessions(); | |
710 | - } else if (msg.getEvent() == SessionEvent.CLOSED) { | |
711 | - log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); | |
712 | - sessions.remove(sessionId); | |
713 | - attributeSubscriptions.remove(sessionId); | |
714 | - rpcSubscriptions.remove(sessionId); | |
715 | - if (sessions.isEmpty()) { | |
716 | - reportSessionClose(); | |
717 | - } | |
718 | - dumpSessions(); | |
664 | + } else { | |
665 | + log.debug("[{}] No public shared side attributes changed!", deviceId); | |
666 | + } | |
719 | 667 | } |
668 | + } | |
669 | + if (hasNotificationData) { | |
670 | + AttributeUpdateNotificationMsg finalNotification = notification.build(); | |
671 | + attributeSubscriptions.forEach( | |
672 | + (key, value) -> sendToTransport(finalNotification, key, value.getNodeId())); | |
673 | + } | |
674 | + } else { | |
675 | + log.debug("[{}] No registered attributes subscriptions to process!", deviceId); | |
720 | 676 | } |
721 | - | |
722 | - private void handleSessionActivity(TbActorCtx context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) { | |
723 | - UUID sessionId = getSessionId(sessionInfoProto); | |
724 | - Objects.requireNonNull(sessionId); | |
725 | - | |
726 | - SessionInfoMetaData sessionMD = sessions.get(sessionId); | |
727 | - if (sessionMD != null) { | |
728 | - sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime()); | |
729 | - sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription()); | |
730 | - sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription()); | |
731 | - if (subscriptionInfo.getAttributeSubscription()) { | |
732 | - attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | |
733 | - } | |
734 | - if (subscriptionInfo.getRpcSubscription()) { | |
735 | - rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | |
736 | - } | |
677 | + } | |
678 | + | |
679 | + private void processRpcResponses( | |
680 | + TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) { | |
681 | + UUID sessionId = getSessionId(sessionInfo); | |
682 | + log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); | |
683 | + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
684 | + boolean success = requestMd != null; | |
685 | + if (success) { | |
686 | + boolean hasError = StringUtils.isNotEmpty(responseMsg.getError()); | |
687 | + try { | |
688 | + String payload = hasError ? responseMsg.getError() : responseMsg.getPayload(); | |
689 | + systemContext | |
690 | + .getTbCoreDeviceRpcService() | |
691 | + .processRpcResponseFromDeviceActor( | |
692 | + new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), payload, null)); | |
693 | + if (requestMd.getMsg().getMsg().isPersisted()) { | |
694 | + RpcStatus status = hasError ? RpcStatus.FAILED : RpcStatus.SUCCESSFUL; | |
695 | + JsonNode response; | |
696 | + try { | |
697 | + response = JacksonUtil.toJsonNode(payload); | |
698 | + } catch (IllegalArgumentException e) { | |
699 | + response = JacksonUtil.newObjectNode().put("error", payload); | |
700 | + } | |
701 | + systemContext | |
702 | + .getTbRpcService() | |
703 | + .save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), status, response); | |
737 | 704 | } |
738 | - systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, subscriptionInfo.getLastActivityTime()); | |
739 | - if (sessionMD != null) { | |
740 | - dumpSessions(); | |
705 | + } finally { | |
706 | + if (hasError && !requestMd.isDelivered()) { | |
707 | + sendNextPendingRequest(context); | |
741 | 708 | } |
709 | + } | |
710 | + } else { | |
711 | + log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); | |
742 | 712 | } |
743 | - | |
744 | - void processCredentialsUpdate(TbActorMsg msg) { | |
745 | - if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { | |
746 | - sessions.forEach((k, v) -> { | |
747 | - notifyTransportAboutDeviceCredentialsUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); | |
748 | - }); | |
713 | + } | |
714 | + | |
715 | + private void processRpcResponseStatus( | |
716 | + TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseStatusMsg responseMsg) { | |
717 | + UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB()); | |
718 | + RpcStatus status = RpcStatus.valueOf(responseMsg.getStatus()); | |
719 | + ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(responseMsg.getRequestId()); | |
720 | + | |
721 | + if (md != null) { | |
722 | + JsonNode response = null; | |
723 | + if (status.equals(RpcStatus.DELIVERED)) { | |
724 | + if (md.getMsg().getMsg().isOneway()) { | |
725 | + toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
726 | + if (rpcSequential) { | |
727 | + systemContext | |
728 | + .getTbCoreDeviceRpcService() | |
729 | + .processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(rpcId, null, null)); | |
730 | + } | |
749 | 731 | } else { |
750 | - sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!")); | |
751 | - attributeSubscriptions.clear(); | |
752 | - rpcSubscriptions.clear(); | |
753 | - dumpSessions(); | |
754 | - | |
732 | + md.setDelivered(true); | |
755 | 733 | } |
756 | - } | |
757 | - | |
758 | - private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) { | |
759 | - log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId [{}] sessionMd [{}]", sessionId, sessionMd); | |
760 | - notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); | |
761 | - } | |
762 | - | |
763 | - private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) { | |
764 | - SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto | |
765 | - .newBuilder() | |
766 | - .setMessage(message).build(); | |
767 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
768 | - .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
769 | - .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
770 | - .setSessionCloseNotification(sessionCloseNotificationProto) | |
771 | - .build(); | |
772 | - systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg); | |
773 | - } | |
774 | - | |
775 | - void notifyTransportAboutDeviceCredentialsUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) { | |
776 | - ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder(); | |
777 | - notification.addCredentialsId(deviceCredentials.getCredentialsId()); | |
778 | - notification.addCredentialsValue(deviceCredentials.getCredentialsValue()); | |
779 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
780 | - .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
781 | - .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
782 | - .setToTransportUpdateCredentialsNotification(notification).build(); | |
783 | - systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg); | |
784 | - } | |
785 | - | |
786 | - void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { | |
787 | - this.deviceName = msg.getDeviceName(); | |
788 | - this.deviceType = msg.getDeviceType(); | |
789 | - this.defaultMetaData = new TbMsgMetaData(); | |
790 | - this.defaultMetaData.putValue("deviceName", deviceName); | |
791 | - this.defaultMetaData.putValue("deviceType", deviceType); | |
792 | - } | |
793 | - | |
794 | - void processEdgeUpdate(DeviceEdgeUpdateMsg msg) { | |
795 | - log.trace("[{}] Processing edge update {}", deviceId, msg); | |
796 | - this.edgeId = msg.getEdgeId(); | |
797 | - } | |
798 | - | |
799 | - private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) { | |
800 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
801 | - .setSessionIdMSB(sessionInfo.getSessionIdMSB()) | |
802 | - .setSessionIdLSB(sessionInfo.getSessionIdLSB()) | |
803 | - .setGetAttributesResponse(responseMsg).build(); | |
804 | - systemContext.getTbCoreToTransportService().process(sessionInfo.getNodeId(), msg); | |
805 | - } | |
806 | - | |
807 | - private void sendToTransport(AttributeUpdateNotificationMsg notificationMsg, UUID sessionId, String nodeId) { | |
808 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
809 | - .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
810 | - .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
811 | - .setAttributeUpdateNotification(notificationMsg).build(); | |
812 | - systemContext.getTbCoreToTransportService().process(nodeId, msg); | |
813 | - } | |
814 | - | |
815 | - private void sendToTransport(ToDeviceRpcRequestMsg rpcMsg, UUID sessionId, String nodeId) { | |
816 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
817 | - .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
818 | - .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
819 | - .setToDeviceRequest(rpcMsg).build(); | |
820 | - systemContext.getTbCoreToTransportService().process(nodeId, msg); | |
821 | - } | |
822 | - | |
823 | - private void sendToTransport(ToServerRpcResponseMsg rpcMsg, UUID sessionId, String nodeId) { | |
824 | - ToTransportMsg msg = ToTransportMsg.newBuilder() | |
825 | - .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
826 | - .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
827 | - .setToServerResponse(rpcMsg).build(); | |
828 | - systemContext.getTbCoreToTransportService().process(nodeId, msg); | |
829 | - } | |
830 | - | |
831 | - private void saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) { | |
832 | - EdgeEvent edgeEvent = new EdgeEvent(); | |
833 | - edgeEvent.setTenantId(tenantId); | |
834 | - edgeEvent.setAction(EdgeEventActionType.RPC_CALL); | |
835 | - edgeEvent.setEntityId(deviceId.getId()); | |
836 | - edgeEvent.setType(EdgeEventType.DEVICE); | |
837 | - | |
838 | - ObjectNode body = mapper.createObjectNode(); | |
839 | - body.put("requestId", requestId); | |
840 | - body.put("requestUUID", msg.getId().toString()); | |
841 | - body.put("oneway", msg.isOneway()); | |
842 | - body.put("expirationTime", msg.getExpirationTime()); | |
843 | - body.put("method", msg.getBody().getMethod()); | |
844 | - body.put("params", msg.getBody().getParams()); | |
845 | - edgeEvent.setBody(body); | |
846 | - | |
847 | - edgeEvent.setEdgeId(edgeId); | |
848 | - systemContext.getEdgeEventService().save(edgeEvent); | |
849 | - systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); | |
850 | - } | |
851 | - | |
852 | - private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) { | |
853 | - List<TsKvProto> clientAttributes; | |
854 | - if (result == null || result.isEmpty()) { | |
855 | - clientAttributes = Collections.emptyList(); | |
734 | + } else if (status.equals(RpcStatus.TIMEOUT)) { | |
735 | + Integer maxRpcRetries = md.getMsg().getMsg().getRetries(); | |
736 | + maxRpcRetries = | |
737 | + maxRpcRetries == null | |
738 | + ? systemContext.getMaxRpcRetries() | |
739 | + : Math.min(maxRpcRetries, systemContext.getMaxRpcRetries()); | |
740 | + if (maxRpcRetries <= md.getRetries()) { | |
741 | + toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | |
742 | + status = RpcStatus.FAILED; | |
743 | + response = | |
744 | + JacksonUtil.newObjectNode() | |
745 | + .put( | |
746 | + "error", | |
747 | + "There was a Timeout and all retry attempts have been exhausted. Retry attempts set: " | |
748 | + + maxRpcRetries); | |
856 | 749 | } else { |
857 | - clientAttributes = new ArrayList<>(result.size()); | |
858 | - for (AttributeKvEntry attrEntry : result) { | |
859 | - clientAttributes.add(toTsKvProto(attrEntry)); | |
860 | - } | |
861 | - } | |
862 | - return clientAttributes; | |
863 | - } | |
864 | - | |
865 | - private TsKvProto toTsKvProto(AttributeKvEntry attrEntry) { | |
866 | - return TsKvProto.newBuilder().setTs(attrEntry.getLastUpdateTs()) | |
867 | - .setKv(toKeyValueProto(attrEntry)).build(); | |
868 | - } | |
869 | - | |
870 | - private KeyValueProto toKeyValueProto(KvEntry kvEntry) { | |
871 | - KeyValueProto.Builder builder = KeyValueProto.newBuilder(); | |
872 | - builder.setKey(kvEntry.getKey()); | |
873 | - switch (kvEntry.getDataType()) { | |
874 | - case BOOLEAN: | |
875 | - builder.setType(KeyValueType.BOOLEAN_V); | |
876 | - builder.setBoolV(kvEntry.getBooleanValue().get()); | |
877 | - break; | |
878 | - case DOUBLE: | |
879 | - builder.setType(KeyValueType.DOUBLE_V); | |
880 | - builder.setDoubleV(kvEntry.getDoubleValue().get()); | |
881 | - break; | |
882 | - case LONG: | |
883 | - builder.setType(KeyValueType.LONG_V); | |
884 | - builder.setLongV(kvEntry.getLongValue().get()); | |
885 | - break; | |
886 | - case STRING: | |
887 | - builder.setType(KeyValueType.STRING_V); | |
888 | - builder.setStringV(kvEntry.getStrValue().get()); | |
889 | - break; | |
890 | - case JSON: | |
891 | - builder.setType(KeyValueType.JSON_V); | |
892 | - builder.setJsonV(kvEntry.getJsonValue().get()); | |
893 | - break; | |
750 | + md.setRetries(md.getRetries() + 1); | |
894 | 751 | } |
895 | - return builder.build(); | |
752 | + } | |
753 | + | |
754 | + if (md.getMsg().getMsg().isPersisted()) { | |
755 | + systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), status, response); | |
756 | + } | |
757 | + if (status != RpcStatus.SENT) { | |
758 | + sendNextPendingRequest(context); | |
759 | + } | |
760 | + } else { | |
761 | + log.info("[{}][{}] Rpc has already removed from pending map.", deviceId, rpcId); | |
896 | 762 | } |
897 | - | |
898 | - void restoreSessions() { | |
899 | - if (systemContext.isLocalCacheType()) { | |
900 | - return; | |
901 | - } | |
902 | - log.debug("[{}] Restoring sessions from cache", deviceId); | |
903 | - DeviceSessionsCacheEntry sessionsDump = null; | |
904 | - try { | |
905 | - sessionsDump = DeviceSessionsCacheEntry.parseFrom(systemContext.getDeviceSessionCacheService().get(deviceId)); | |
906 | - } catch (InvalidProtocolBufferException e) { | |
907 | - log.warn("[{}] Failed to decode device sessions from cache", deviceId); | |
908 | - return; | |
909 | - } | |
910 | - if (sessionsDump.getSessionsCount() == 0) { | |
911 | - log.debug("[{}] No session information found", deviceId); | |
912 | - return; | |
913 | - } | |
914 | - // TODO: Take latest max allowed sessions size from cache | |
915 | - for (SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) { | |
916 | - SessionInfoProto sessionInfoProto = sessionSubscriptionInfoProto.getSessionInfo(); | |
917 | - UUID sessionId = getSessionId(sessionInfoProto); | |
918 | - SessionInfo sessionInfo = new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()); | |
919 | - SubscriptionInfoProto subInfo = sessionSubscriptionInfoProto.getSubscriptionInfo(); | |
920 | - SessionInfoMetaData sessionMD = new SessionInfoMetaData(sessionInfo, subInfo.getLastActivityTime()); | |
921 | - sessions.put(sessionId, sessionMD); | |
922 | - if (subInfo.getAttributeSubscription()) { | |
923 | - attributeSubscriptions.put(sessionId, sessionInfo); | |
924 | - sessionMD.setSubscribedToAttributes(true); | |
925 | - } | |
926 | - if (subInfo.getRpcSubscription()) { | |
927 | - rpcSubscriptions.put(sessionId, sessionInfo); | |
928 | - sessionMD.setSubscribedToRPC(true); | |
929 | - } | |
930 | - log.debug("[{}] Restored session: {}", deviceId, sessionMD); | |
931 | - } | |
932 | - log.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); | |
763 | + } | |
764 | + | |
765 | + private void processSubscriptionCommands( | |
766 | + TbActorCtx context, | |
767 | + SessionInfoProto sessionInfo, | |
768 | + SubscribeToAttributeUpdatesMsg subscribeCmd) { | |
769 | + UUID sessionId = getSessionId(sessionInfo); | |
770 | + if (subscribeCmd.getUnsubscribe()) { | |
771 | + log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); | |
772 | + attributeSubscriptions.remove(sessionId); | |
773 | + } else { | |
774 | + SessionInfoMetaData sessionMD = sessions.get(sessionId); | |
775 | + if (sessionMD == null) { | |
776 | + sessionMD = | |
777 | + new SessionInfoMetaData( | |
778 | + new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); | |
779 | + } | |
780 | + sessionMD.setSubscribedToAttributes(true); | |
781 | + log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); | |
782 | + attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo()); | |
783 | + dumpSessions(); | |
784 | + } | |
785 | + } | |
786 | + | |
787 | + private UUID getSessionId(SessionInfoProto sessionInfo) { | |
788 | + return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); | |
789 | + } | |
790 | + | |
791 | + private void processSubscriptionCommands( | |
792 | + TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) { | |
793 | + UUID sessionId = getSessionId(sessionInfo); | |
794 | + if (subscribeCmd.getUnsubscribe()) { | |
795 | + log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); | |
796 | + rpcSubscriptions.remove(sessionId); | |
797 | + } else { | |
798 | + SessionInfoMetaData sessionMD = sessions.get(sessionId); | |
799 | + if (sessionMD == null) { | |
800 | + sessionMD = | |
801 | + new SessionInfoMetaData( | |
802 | + new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); | |
803 | + } | |
804 | + sessionMD.setSubscribedToRPC(true); | |
805 | + log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); | |
806 | + rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo()); | |
807 | + sendPendingRequests(context, sessionId, sessionInfo.getNodeId()); | |
808 | + dumpSessions(); | |
809 | + } | |
810 | + } | |
811 | + | |
812 | + private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) { | |
813 | + UUID sessionId = getSessionId(sessionInfo); | |
814 | + Objects.requireNonNull(sessionId); | |
815 | + if (msg.getEvent() == SessionEvent.OPEN) { | |
816 | + if (sessions.containsKey(sessionId)) { | |
817 | + log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); | |
818 | + return; | |
819 | + } | |
820 | + log.debug( | |
821 | + "[{}] Processing new session [{}]. Current sessions size {}", | |
822 | + deviceId, | |
823 | + sessionId, | |
824 | + sessions.size()); | |
825 | + | |
826 | + sessions.put( | |
827 | + sessionId, | |
828 | + new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); | |
829 | + if (sessions.size() == 1) { | |
830 | + reportSessionOpen(); | |
831 | + } | |
832 | + systemContext | |
833 | + .getDeviceStateService() | |
834 | + .onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); | |
835 | + dumpSessions(); | |
836 | + } else if (msg.getEvent() == SessionEvent.CLOSED) { | |
837 | + log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); | |
838 | + sessions.remove(sessionId); | |
839 | + attributeSubscriptions.remove(sessionId); | |
840 | + rpcSubscriptions.remove(sessionId); | |
841 | + if (sessions.isEmpty()) { | |
842 | + reportSessionClose(); | |
843 | + } | |
844 | + dumpSessions(); | |
845 | + } | |
846 | + } | |
847 | + | |
848 | + private void handleSessionActivity( | |
849 | + TbActorCtx context, | |
850 | + SessionInfoProto sessionInfoProto, | |
851 | + SubscriptionInfoProto subscriptionInfo) { | |
852 | + UUID sessionId = getSessionId(sessionInfoProto); | |
853 | + Objects.requireNonNull(sessionId); | |
854 | + | |
855 | + SessionInfoMetaData sessionMD = sessions.get(sessionId); | |
856 | + if (sessionMD != null) { | |
857 | + sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime()); | |
858 | + sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription()); | |
859 | + sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription()); | |
860 | + if (subscriptionInfo.getAttributeSubscription()) { | |
861 | + attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | |
862 | + } | |
863 | + if (subscriptionInfo.getRpcSubscription()) { | |
864 | + rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | |
865 | + } | |
933 | 866 | } |
867 | + systemContext | |
868 | + .getDeviceStateService() | |
869 | + .onDeviceActivity(tenantId, deviceId, subscriptionInfo.getLastActivityTime()); | |
870 | + if (sessionMD != null) { | |
871 | + dumpSessions(); | |
872 | + } | |
873 | + } | |
874 | + | |
875 | + void processCredentialsUpdate(TbActorMsg msg) { | |
876 | + if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() | |
877 | + == DeviceCredentialsType.LWM2M_CREDENTIALS) { | |
878 | + sessions.forEach( | |
879 | + (k, v) -> { | |
880 | + notifyTransportAboutDeviceCredentialsUpdate( | |
881 | + k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); | |
882 | + }); | |
883 | + } else { | |
884 | + sessions.forEach( | |
885 | + (sessionId, sessionMd) -> | |
886 | + notifyTransportAboutClosedSession( | |
887 | + sessionId, sessionMd, "device credentials updated!")); | |
888 | + attributeSubscriptions.clear(); | |
889 | + rpcSubscriptions.clear(); | |
890 | + dumpSessions(); | |
891 | + } | |
892 | + } | |
893 | + | |
894 | + private void notifyTransportAboutClosedSessionMaxSessionsLimit( | |
895 | + UUID sessionId, SessionInfoMetaData sessionMd) { | |
896 | + log.debug( | |
897 | + "remove eldest session (max concurrent sessions limit reached per device) sessionId [{}] sessionMd [{}]", | |
898 | + sessionId, | |
899 | + sessionMd); | |
900 | + notifyTransportAboutClosedSession( | |
901 | + sessionId, sessionMd, "max concurrent sessions limit reached per device!"); | |
902 | + } | |
903 | + | |
904 | + private void notifyTransportAboutClosedSession( | |
905 | + UUID sessionId, SessionInfoMetaData sessionMd, String message) { | |
906 | + SessionCloseNotificationProto sessionCloseNotificationProto = | |
907 | + SessionCloseNotificationProto.newBuilder().setMessage(message).build(); | |
908 | + ToTransportMsg msg = | |
909 | + ToTransportMsg.newBuilder() | |
910 | + .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
911 | + .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
912 | + .setSessionCloseNotification(sessionCloseNotificationProto) | |
913 | + .build(); | |
914 | + systemContext | |
915 | + .getTbCoreToTransportService() | |
916 | + .process(sessionMd.getSessionInfo().getNodeId(), msg); | |
917 | + } | |
918 | + | |
919 | + void notifyTransportAboutDeviceCredentialsUpdate( | |
920 | + UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) { | |
921 | + ToTransportUpdateCredentialsProto.Builder notification = | |
922 | + ToTransportUpdateCredentialsProto.newBuilder(); | |
923 | + notification.addCredentialsId(deviceCredentials.getCredentialsId()); | |
924 | + notification.addCredentialsValue(deviceCredentials.getCredentialsValue()); | |
925 | + ToTransportMsg msg = | |
926 | + ToTransportMsg.newBuilder() | |
927 | + .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
928 | + .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
929 | + .setToTransportUpdateCredentialsNotification(notification) | |
930 | + .build(); | |
931 | + systemContext | |
932 | + .getTbCoreToTransportService() | |
933 | + .process(sessionMd.getSessionInfo().getNodeId(), msg); | |
934 | + } | |
935 | + | |
936 | + void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { | |
937 | + this.deviceName = msg.getDeviceName(); | |
938 | + this.deviceType = msg.getDeviceType(); | |
939 | + this.defaultMetaData = new TbMsgMetaData(); | |
940 | + this.defaultMetaData.putValue("deviceName", deviceName); | |
941 | + this.defaultMetaData.putValue("deviceType", deviceType); | |
942 | + } | |
943 | + | |
944 | + void processEdgeUpdate(DeviceEdgeUpdateMsg msg) { | |
945 | + log.trace("[{}] Processing edge update {}", deviceId, msg); | |
946 | + this.edgeId = msg.getEdgeId(); | |
947 | + } | |
948 | + | |
949 | + private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) { | |
950 | + ToTransportMsg msg = | |
951 | + ToTransportMsg.newBuilder() | |
952 | + .setSessionIdMSB(sessionInfo.getSessionIdMSB()) | |
953 | + .setSessionIdLSB(sessionInfo.getSessionIdLSB()) | |
954 | + .setGetAttributesResponse(responseMsg) | |
955 | + .build(); | |
956 | + systemContext.getTbCoreToTransportService().process(sessionInfo.getNodeId(), msg); | |
957 | + } | |
958 | + | |
959 | + private void sendToTransport( | |
960 | + AttributeUpdateNotificationMsg notificationMsg, UUID sessionId, String nodeId) { | |
961 | + ToTransportMsg msg = | |
962 | + ToTransportMsg.newBuilder() | |
963 | + .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
964 | + .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
965 | + .setAttributeUpdateNotification(notificationMsg) | |
966 | + .build(); | |
967 | + systemContext.getTbCoreToTransportService().process(nodeId, msg); | |
968 | + } | |
969 | + | |
970 | + private void sendToTransport(ToDeviceRpcRequestMsg rpcMsg, UUID sessionId, String nodeId) { | |
971 | + ToTransportMsg msg = | |
972 | + ToTransportMsg.newBuilder() | |
973 | + .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
974 | + .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
975 | + .setToDeviceRequest(rpcMsg) | |
976 | + .build(); | |
977 | + systemContext.getTbCoreToTransportService().process(nodeId, msg); | |
978 | + } | |
979 | + | |
980 | + private void sendToTransport(ToServerRpcResponseMsg rpcMsg, UUID sessionId, String nodeId) { | |
981 | + ToTransportMsg msg = | |
982 | + ToTransportMsg.newBuilder() | |
983 | + .setSessionIdMSB(sessionId.getMostSignificantBits()) | |
984 | + .setSessionIdLSB(sessionId.getLeastSignificantBits()) | |
985 | + .setToServerResponse(rpcMsg) | |
986 | + .build(); | |
987 | + systemContext.getTbCoreToTransportService().process(nodeId, msg); | |
988 | + } | |
989 | + | |
990 | + private void saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) { | |
991 | + EdgeEvent edgeEvent = new EdgeEvent(); | |
992 | + edgeEvent.setTenantId(tenantId); | |
993 | + edgeEvent.setAction(EdgeEventActionType.RPC_CALL); | |
994 | + edgeEvent.setEntityId(deviceId.getId()); | |
995 | + edgeEvent.setType(EdgeEventType.DEVICE); | |
996 | + | |
997 | + ObjectNode body = mapper.createObjectNode(); | |
998 | + body.put("requestId", requestId); | |
999 | + body.put("requestUUID", msg.getId().toString()); | |
1000 | + body.put("oneway", msg.isOneway()); | |
1001 | + body.put("expirationTime", msg.getExpirationTime()); | |
1002 | + body.put("method", msg.getBody().getMethod()); | |
1003 | + body.put("params", msg.getBody().getParams()); | |
1004 | + edgeEvent.setBody(body); | |
1005 | + | |
1006 | + edgeEvent.setEdgeId(edgeId); | |
1007 | + systemContext.getEdgeEventService().save(edgeEvent); | |
1008 | + systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); | |
1009 | + } | |
1010 | + | |
1011 | + private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) { | |
1012 | + List<TsKvProto> clientAttributes; | |
1013 | + if (result == null || result.isEmpty()) { | |
1014 | + clientAttributes = Collections.emptyList(); | |
1015 | + } else { | |
1016 | + clientAttributes = new ArrayList<>(result.size()); | |
1017 | + for (AttributeKvEntry attrEntry : result) { | |
1018 | + clientAttributes.add(toTsKvProto(attrEntry)); | |
1019 | + } | |
1020 | + } | |
1021 | + return clientAttributes; | |
1022 | + } | |
1023 | + | |
1024 | + private TsKvProto toTsKvProto(AttributeKvEntry attrEntry) { | |
1025 | + return TsKvProto.newBuilder() | |
1026 | + .setTs(attrEntry.getLastUpdateTs()) | |
1027 | + .setKv(toKeyValueProto(attrEntry)) | |
1028 | + .build(); | |
1029 | + } | |
1030 | + | |
1031 | + private KeyValueProto toKeyValueProto(KvEntry kvEntry) { | |
1032 | + KeyValueProto.Builder builder = KeyValueProto.newBuilder(); | |
1033 | + builder.setKey(kvEntry.getKey()); | |
1034 | + switch (kvEntry.getDataType()) { | |
1035 | + case BOOLEAN: | |
1036 | + builder.setType(KeyValueType.BOOLEAN_V); | |
1037 | + builder.setBoolV(kvEntry.getBooleanValue().get()); | |
1038 | + break; | |
1039 | + case DOUBLE: | |
1040 | + builder.setType(KeyValueType.DOUBLE_V); | |
1041 | + builder.setDoubleV(kvEntry.getDoubleValue().get()); | |
1042 | + break; | |
1043 | + case LONG: | |
1044 | + builder.setType(KeyValueType.LONG_V); | |
1045 | + builder.setLongV(kvEntry.getLongValue().get()); | |
1046 | + break; | |
1047 | + case STRING: | |
1048 | + builder.setType(KeyValueType.STRING_V); | |
1049 | + builder.setStringV(kvEntry.getStrValue().get()); | |
1050 | + break; | |
1051 | + case JSON: | |
1052 | + builder.setType(KeyValueType.JSON_V); | |
1053 | + builder.setJsonV(kvEntry.getJsonValue().get()); | |
1054 | + break; | |
1055 | + } | |
1056 | + return builder.build(); | |
1057 | + } | |
934 | 1058 | |
935 | - private void dumpSessions() { | |
936 | - if (systemContext.isLocalCacheType()) { | |
1059 | + void restoreSessions() { | |
1060 | + if (systemContext.isLocalCacheType()) { | |
1061 | + return; | |
1062 | + } | |
1063 | + log.debug("[{}] Restoring sessions from cache", deviceId); | |
1064 | + DeviceSessionsCacheEntry sessionsDump = null; | |
1065 | + try { | |
1066 | + sessionsDump = | |
1067 | + DeviceSessionsCacheEntry.parseFrom( | |
1068 | + systemContext.getDeviceSessionCacheService().get(deviceId)); | |
1069 | + } catch (InvalidProtocolBufferException e) { | |
1070 | + log.warn("[{}] Failed to decode device sessions from cache", deviceId); | |
1071 | + return; | |
1072 | + } | |
1073 | + if (sessionsDump.getSessionsCount() == 0) { | |
1074 | + log.debug("[{}] No session information found", deviceId); | |
1075 | + return; | |
1076 | + } | |
1077 | + // TODO: Take latest max allowed sessions size from cache | |
1078 | + for (SessionSubscriptionInfoProto sessionSubscriptionInfoProto : | |
1079 | + sessionsDump.getSessionsList()) { | |
1080 | + SessionInfoProto sessionInfoProto = sessionSubscriptionInfoProto.getSessionInfo(); | |
1081 | + UUID sessionId = getSessionId(sessionInfoProto); | |
1082 | + SessionInfo sessionInfo = new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()); | |
1083 | + SubscriptionInfoProto subInfo = sessionSubscriptionInfoProto.getSubscriptionInfo(); | |
1084 | + SessionInfoMetaData sessionMD = | |
1085 | + new SessionInfoMetaData(sessionInfo, subInfo.getLastActivityTime()); | |
1086 | + sessions.put(sessionId, sessionMD); | |
1087 | + if (subInfo.getAttributeSubscription()) { | |
1088 | + attributeSubscriptions.put(sessionId, sessionInfo); | |
1089 | + sessionMD.setSubscribedToAttributes(true); | |
1090 | + } | |
1091 | + if (subInfo.getRpcSubscription()) { | |
1092 | + rpcSubscriptions.put(sessionId, sessionInfo); | |
1093 | + sessionMD.setSubscribedToRPC(true); | |
1094 | + } | |
1095 | + log.debug("[{}] Restored session: {}", deviceId, sessionMD); | |
1096 | + } | |
1097 | + log.debug( | |
1098 | + "[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", | |
1099 | + deviceId, | |
1100 | + sessions.size(), | |
1101 | + rpcSubscriptions.size(), | |
1102 | + attributeSubscriptions.size()); | |
1103 | + } | |
1104 | + | |
1105 | + private void dumpSessions() { | |
1106 | + if (systemContext.isLocalCacheType()) { | |
1107 | + return; | |
1108 | + } | |
1109 | + log.debug( | |
1110 | + "[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", | |
1111 | + deviceId, | |
1112 | + sessions.size(), | |
1113 | + rpcSubscriptions.size(), | |
1114 | + attributeSubscriptions.size()); | |
1115 | + List<SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size()); | |
1116 | + sessions.forEach( | |
1117 | + (uuid, sessionMD) -> { | |
1118 | + if (sessionMD.getSessionInfo().getType() == SessionType.SYNC) { | |
937 | 1119 | return; |
938 | - } | |
939 | - log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); | |
940 | - List<SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size()); | |
941 | - sessions.forEach((uuid, sessionMD) -> { | |
942 | - if (sessionMD.getSessionInfo().getType() == SessionType.SYNC) { | |
943 | - return; | |
944 | - } | |
945 | - SessionInfo sessionInfo = sessionMD.getSessionInfo(); | |
946 | - SubscriptionInfoProto subscriptionInfoProto = SubscriptionInfoProto.newBuilder() | |
947 | - .setLastActivityTime(sessionMD.getLastActivityTime()) | |
948 | - .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) | |
949 | - .setRpcSubscription(sessionMD.isSubscribedToRPC()).build(); | |
950 | - SessionInfoProto sessionInfoProto = SessionInfoProto.newBuilder() | |
951 | - .setSessionIdMSB(uuid.getMostSignificantBits()) | |
952 | - .setSessionIdLSB(uuid.getLeastSignificantBits()) | |
953 | - .setNodeId(sessionInfo.getNodeId()).build(); | |
954 | - sessionsList.add(SessionSubscriptionInfoProto.newBuilder() | |
955 | - .setSessionInfo(sessionInfoProto) | |
956 | - .setSubscriptionInfo(subscriptionInfoProto).build()); | |
957 | - log.debug("[{}] Dumping session: {}", deviceId, sessionMD); | |
1120 | + } | |
1121 | + SessionInfo sessionInfo = sessionMD.getSessionInfo(); | |
1122 | + SubscriptionInfoProto subscriptionInfoProto = | |
1123 | + SubscriptionInfoProto.newBuilder() | |
1124 | + .setLastActivityTime(sessionMD.getLastActivityTime()) | |
1125 | + .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) | |
1126 | + .setRpcSubscription(sessionMD.isSubscribedToRPC()) | |
1127 | + .build(); | |
1128 | + SessionInfoProto sessionInfoProto = | |
1129 | + SessionInfoProto.newBuilder() | |
1130 | + .setSessionIdMSB(uuid.getMostSignificantBits()) | |
1131 | + .setSessionIdLSB(uuid.getLeastSignificantBits()) | |
1132 | + .setNodeId(sessionInfo.getNodeId()) | |
1133 | + .build(); | |
1134 | + sessionsList.add( | |
1135 | + SessionSubscriptionInfoProto.newBuilder() | |
1136 | + .setSessionInfo(sessionInfoProto) | |
1137 | + .setSubscriptionInfo(subscriptionInfoProto) | |
1138 | + .build()); | |
1139 | + log.debug("[{}] Dumping session: {}", deviceId, sessionMD); | |
958 | 1140 | }); |
959 | - systemContext.getDeviceSessionCacheService() | |
960 | - .put(deviceId, DeviceSessionsCacheEntry.newBuilder() | |
961 | - .addAllSessions(sessionsList).build().toByteArray()); | |
962 | - } | |
963 | - | |
964 | - void init(TbActorCtx ctx) { | |
965 | - PageLink pageLink = new PageLink(1024, 0, null, new SortOrder("createdTime")); | |
966 | - PageData<Rpc> pageData; | |
967 | - do { | |
968 | - pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink); | |
969 | - pageData.getData().forEach(rpc -> { | |
970 | - ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class); | |
1141 | + systemContext | |
1142 | + .getDeviceSessionCacheService() | |
1143 | + .put( | |
1144 | + deviceId, | |
1145 | + DeviceSessionsCacheEntry.newBuilder() | |
1146 | + .addAllSessions(sessionsList) | |
1147 | + .build() | |
1148 | + .toByteArray()); | |
1149 | + } | |
1150 | + | |
1151 | + void init(TbActorCtx ctx) { | |
1152 | + PageLink pageLink = new PageLink(1024, 0, null, new SortOrder("createdTime")); | |
1153 | + PageData<Rpc> pageData; | |
1154 | + do { | |
1155 | + pageData = | |
1156 | + systemContext | |
1157 | + .getTbRpcService() | |
1158 | + .findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink); | |
1159 | + pageData | |
1160 | + .getData() | |
1161 | + .forEach( | |
1162 | + rpc -> { | |
1163 | + ToDeviceRpcRequest msg = | |
1164 | + JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class); | |
971 | 1165 | long timeout = rpc.getExpirationTime() - System.currentTimeMillis(); |
972 | 1166 | if (timeout <= 0) { |
973 | - rpc.setStatus(RpcStatus.EXPIRED); | |
974 | - systemContext.getTbRpcService().save(tenantId, rpc); | |
1167 | + rpc.setStatus(RpcStatus.EXPIRED); | |
1168 | + systemContext.getTbRpcService().save(tenantId, rpc); | |
975 | 1169 | } else { |
976 | - registerPendingRpcRequest(ctx, new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), false, creteToDeviceRpcRequestMsg(msg), timeout); | |
977 | - } | |
978 | - }); | |
979 | - if (pageData.hasNext()) { | |
980 | - pageLink = pageLink.nextPageLink(); | |
981 | - } | |
982 | - } while (pageData.hasNext()); | |
983 | - } | |
984 | - | |
985 | - void checkSessionsTimeout() { | |
986 | - final long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout(); | |
987 | - List<UUID> expiredIds = null; | |
988 | - | |
989 | - for (Map.Entry<UUID, SessionInfoMetaData> kv : sessions.entrySet()) { //entry set are cached for stable sessions | |
990 | - if (kv.getValue().getLastActivityTime() < expTime) { | |
991 | - final UUID id = kv.getKey(); | |
992 | - if (expiredIds == null) { | |
993 | - expiredIds = new ArrayList<>(1); //most of the expired sessions is a single event | |
1170 | + registerPendingRpcRequest( | |
1171 | + ctx, | |
1172 | + new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), | |
1173 | + false, | |
1174 | + creteToDeviceRpcRequestMsg(msg), | |
1175 | + timeout); | |
994 | 1176 | } |
995 | - expiredIds.add(id); | |
996 | - } | |
1177 | + }); | |
1178 | + if (pageData.hasNext()) { | |
1179 | + pageLink = pageLink.nextPageLink(); | |
1180 | + } | |
1181 | + } while (pageData.hasNext()); | |
1182 | + } | |
1183 | + | |
1184 | + void checkSessionsTimeout() { | |
1185 | + final long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout(); | |
1186 | + List<UUID> expiredIds = null; | |
1187 | + | |
1188 | + for (Map.Entry<UUID, SessionInfoMetaData> kv : | |
1189 | + sessions.entrySet()) { // entry set are cached for stable sessions | |
1190 | + if (kv.getValue().getLastActivityTime() < expTime) { | |
1191 | + final UUID id = kv.getKey(); | |
1192 | + if (expiredIds == null) { | |
1193 | + expiredIds = new ArrayList<>(1); // most of the expired sessions is a single event | |
997 | 1194 | } |
1195 | + expiredIds.add(id); | |
1196 | + } | |
1197 | + } | |
998 | 1198 | |
999 | - if (expiredIds != null) { | |
1000 | - int removed = 0; | |
1001 | - for (UUID id : expiredIds) { | |
1002 | - final SessionInfoMetaData session = sessions.remove(id); | |
1003 | - rpcSubscriptions.remove(id); | |
1004 | - attributeSubscriptions.remove(id); | |
1005 | - if (session != null) { | |
1006 | - removed++; | |
1007 | - notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE); | |
1008 | - } | |
1009 | - } | |
1010 | - if (removed != 0) { | |
1011 | - dumpSessions(); | |
1012 | - } | |
1199 | + if (expiredIds != null) { | |
1200 | + int removed = 0; | |
1201 | + for (UUID id : expiredIds) { | |
1202 | + final SessionInfoMetaData session = sessions.remove(id); | |
1203 | + rpcSubscriptions.remove(id); | |
1204 | + attributeSubscriptions.remove(id); | |
1205 | + if (session != null) { | |
1206 | + removed++; | |
1207 | + notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE); | |
1013 | 1208 | } |
1014 | - | |
1209 | + } | |
1210 | + if (removed != 0) { | |
1211 | + dumpSessions(); | |
1212 | + } | |
1015 | 1213 | } |
1016 | - | |
1214 | + } | |
1017 | 1215 | } | ... | ... |
... | ... | @@ -78,6 +78,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; |
78 | 78 | import org.thingsboard.server.dao.tenant.TenantService; |
79 | 79 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
80 | 80 | import org.thingsboard.server.dao.user.UserService; |
81 | +import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | |
81 | 82 | import org.thingsboard.server.gen.transport.TransportProtos; |
82 | 83 | import org.thingsboard.server.queue.TbQueueCallback; |
83 | 84 | import org.thingsboard.server.queue.TbQueueMsgMetadata; |
... | ... | @@ -689,4 +690,10 @@ class DefaultTbContext implements TbContext { |
689 | 690 | } |
690 | 691 | } |
691 | 692 | } |
693 | + | |
694 | + //Thingskit function | |
695 | + @Override | |
696 | + public TkDeviceService getTkDeviceService() { | |
697 | + return mainCtx.getTkDeviceService(); | |
698 | + } | |
692 | 699 | } | ... | ... |
... | ... | @@ -17,8 +17,8 @@ import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; |
17 | 17 | import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; |
18 | 18 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; |
19 | 19 | import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; |
20 | -import org.thingsboard.server.common.data.yunteng.dto.SceneLinkageDTO; | |
21 | -import org.thingsboard.server.common.data.yunteng.dto.TriggerDTO; | |
20 | +import org.thingsboard.server.common.data.yunteng.dto.scene.SceneLinkageDTO; | |
21 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | |
22 | 22 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
23 | 23 | import org.thingsboard.server.common.data.yunteng.id.SceneId; |
24 | 24 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; | ... | ... |
... | ... | @@ -16,18 +16,17 @@ import org.thingsboard.server.common.data.id.UserId; |
16 | 16 | import org.thingsboard.server.common.data.security.Authority; |
17 | 17 | import org.thingsboard.server.common.data.security.UserCredentials; |
18 | 18 | import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; |
19 | +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; | |
19 | 20 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
20 | 21 | import org.thingsboard.server.common.data.yunteng.core.utils.AccountProperties; |
21 | -import org.thingsboard.server.common.data.yunteng.dto.AuthorizeDTO; | |
22 | -import org.thingsboard.server.common.data.yunteng.dto.UserDTO; | |
23 | -import org.thingsboard.server.common.data.yunteng.dto.UserDetailsDTO; | |
24 | -import org.thingsboard.server.common.data.yunteng.dto.TkThirdUserDTO; | |
22 | +import org.thingsboard.server.common.data.yunteng.dto.*; | |
25 | 23 | import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; |
26 | 24 | import org.thingsboard.server.common.data.yunteng.enums.ThirdPlatformEnum; |
27 | 25 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
28 | 26 | import org.thingsboard.server.controller.BaseController; |
29 | 27 | import org.thingsboard.server.dao.exception.DataValidationException; |
30 | 28 | import org.thingsboard.server.dao.yunteng.entities.TkThirdUserEntity; |
29 | +import org.thingsboard.server.dao.yunteng.service.TkTenantService; | |
31 | 30 | import org.thingsboard.server.dao.yunteng.service.TkThirdPlatformService; |
32 | 31 | import org.thingsboard.server.dao.yunteng.service.TkUserService; |
33 | 32 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
... | ... | @@ -36,7 +35,9 @@ import org.thingsboard.server.service.security.model.SecurityUser; |
36 | 35 | import org.thingsboard.server.service.security.model.UserPrincipal; |
37 | 36 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
38 | 37 | |
38 | +import java.time.LocalDateTime; | |
39 | 39 | import java.util.List; |
40 | +import java.util.Optional; | |
40 | 41 | import java.util.UUID; |
41 | 42 | |
42 | 43 | import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*; |
... | ... | @@ -55,6 +56,7 @@ public class TkThirdPlatformController extends BaseController { |
55 | 56 | private final TkThirdPlatformService thirdService; |
56 | 57 | private final TkUserService tkUserService; |
57 | 58 | private final AccountProperties accountProperties; |
59 | + private final TkTenantService tenantService; | |
58 | 60 | @GetMapping(params = {PAGE_SIZE, PAGE}) |
59 | 61 | @ApiOperation("分页") |
60 | 62 | public TkPageData<TkThirdUserDTO> pageAlarmProfile( |
... | ... | @@ -130,6 +132,7 @@ public class TkThirdPlatformController extends BaseController { |
130 | 132 | |
131 | 133 | @NotNull |
132 | 134 | private TkLoginResponse buildJwtToken(UserDTO userDto, String thirdUserId) { |
135 | + checkTenantExpireTime(userDto); | |
133 | 136 | String accessToken = ""; |
134 | 137 | String refreshToken = ""; |
135 | 138 | if (userDto != null) { |
... | ... | @@ -168,4 +171,31 @@ public class TkThirdPlatformController extends BaseController { |
168 | 171 | result.setRefreshToken(refreshToken); |
169 | 172 | return result.setThirdUserId(thirdUserId); |
170 | 173 | } |
174 | + | |
175 | + private void checkTenantExpireTime(UserDTO user) | |
176 | + { | |
177 | + LocalDateTime nowDateTime = LocalDateTime.now(); | |
178 | + //租户和客户检查租户是否有效 | |
179 | + if(user.getLevel()>=2){ | |
180 | + TenantDTO tenant = tenantService.findTenantByTenantId(user.getTenantId()); | |
181 | + Optional.ofNullable(tenant).map(obj->{ | |
182 | + LocalDateTime tenantExpireTime = obj.getTenantExpireTime(); | |
183 | + if(null !=tenantExpireTime && nowDateTime.isAfter(tenantExpireTime)){ | |
184 | + throw new TkDataValidationException(ErrorMessage.ACCOUNT_HAS_EXPIRED.getMessage()); | |
185 | + } | |
186 | + return obj; | |
187 | + }).orElseThrow(()->{ | |
188 | + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()); | |
189 | + }); | |
190 | + } | |
191 | + //检查账号是否过期 | |
192 | + if(!user.isEnabled()){ | |
193 | + throw new TkDataValidationException(ErrorMessage.ACCOUNT_DISABLED.getMessage()); | |
194 | + } | |
195 | + if(null != user.getAccountExpireTime()){ | |
196 | + if(nowDateTime.isAfter(user.getAccountExpireTime())){ | |
197 | + throw new TkDataValidationException(ErrorMessage.ACCOUNT_HAS_EXPIRED.getMessage()); | |
198 | + } | |
199 | + } | |
200 | + } | |
171 | 201 | } | ... | ... |
... | ... | @@ -21,27 +21,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | 21 | import lombok.extern.slf4j.Slf4j; |
22 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | 23 | import org.springframework.stereotype.Service; |
24 | -import org.thingsboard.common.util.JacksonUtil; | |
25 | 24 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
26 | 25 | import org.thingsboard.server.actors.ActorSystemContext; |
27 | 26 | import org.thingsboard.server.cluster.TbClusterService; |
28 | 27 | import org.thingsboard.server.common.data.DataConstants; |
29 | 28 | import org.thingsboard.server.common.data.Device; |
30 | -import org.thingsboard.server.common.data.StringUtils; | |
31 | 29 | import org.thingsboard.server.common.data.User; |
32 | -import org.thingsboard.server.common.data.id.DeviceId; | |
33 | 30 | import org.thingsboard.server.common.data.rpc.RpcError; |
34 | -import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; | |
35 | -import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | |
36 | -import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; | |
37 | -import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
38 | 31 | import org.thingsboard.server.common.msg.TbMsg; |
39 | 32 | import org.thingsboard.server.common.msg.TbMsgDataType; |
40 | 33 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
41 | 34 | import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; |
42 | 35 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
43 | 36 | import org.thingsboard.server.dao.device.DeviceService; |
44 | -import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | |
45 | 37 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
46 | 38 | import org.thingsboard.server.queue.util.TbCoreComponent; |
47 | 39 | import org.thingsboard.server.service.security.model.SecurityUser; |
... | ... | @@ -79,8 +71,6 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { |
79 | 71 | private ScheduledExecutorService scheduler; |
80 | 72 | private String serviceId; |
81 | 73 | |
82 | - @Autowired | |
83 | - protected TkDeviceService tkDeviceService; | |
84 | 74 | public DefaultTbCoreDeviceRpcService(DeviceService deviceService, TbClusterService clusterService, TbServiceInfoProvider serviceInfoProvider, |
85 | 75 | ActorSystemContext actorContext) { |
86 | 76 | this.deviceService = deviceService; |
... | ... | @@ -110,32 +100,6 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { |
110 | 100 | @Override |
111 | 101 | public void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer, SecurityUser currentUser) { |
112 | 102 | log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); |
113 | - | |
114 | - //Thingskit function 修改RPC三要素:设备ID、参数、附加信息 | |
115 | - //只有命令下发的时候走此逻辑,其他method的时候不走以下逻辑 | |
116 | - ToDeviceRpcRequestBody body = request.getBody(); | |
117 | - if(body.getMethod().contains("method")) | |
118 | - { | |
119 | - DeviceId targetId = request.getDeviceId(); | |
120 | - DeviceDTO targetDevice = tkDeviceService.findDeviceInfoByTbDeviceId(currentUser.getCurrentTenantId(), targetId.getId().toString()); | |
121 | - DeviceId netEnableId = DeviceTypeEnum.SENSOR == targetDevice.getDeviceType()?new DeviceId(UUID.fromString(targetDevice.getGatewayId())):targetId; | |
122 | - String paramStr = body.getParams(); | |
123 | - if(DeviceTypeEnum.SENSOR == targetDevice.getDeviceType() && paramStr.contains("}")){ | |
124 | - ObjectNode methodParams = (ObjectNode) JacksonUtil.toJsonNode(paramStr); | |
125 | - methodParams.put(FastIotConstants.Rpc.TARGET_NAME,targetDevice.getName()); | |
126 | - body = new ToDeviceRpcRequestBody(body.getMethod(),JacksonUtil.toString(methodParams)); | |
127 | - } | |
128 | - | |
129 | - ObjectNode additional = JacksonUtil.newObjectNode(); | |
130 | - if(StringUtils.isNotBlank(request.getAdditionalInfo())){ | |
131 | - additional = (ObjectNode) JacksonUtil.toJsonNode(request.getAdditionalInfo()); | |
132 | - } | |
133 | - if(!additional.has(FastIotConstants.Rpc.TARGET_ID)){ | |
134 | - additional.put(FastIotConstants.Rpc.TARGET_ID, targetId.getId().toString()); | |
135 | - } | |
136 | - request = new ToDeviceRpcRequest(request.getId(),request.getTenantId(),netEnableId,request.isOneway(),request.getExpirationTime(),body,request.isPersisted(),request.getRetries(),JacksonUtil.toString(additional)); | |
137 | - | |
138 | - } | |
139 | 103 | UUID requestId = request.getId(); |
140 | 104 | localToRuleEngineRpcRequests.put(requestId, responseConsumer); |
141 | 105 | sendRpcRequestToRuleEngine(request, currentUser); | ... | ... |
... | ... | @@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.yunteng.dto; |
3 | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | 4 | import lombok.Data; |
5 | 5 | import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
6 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | |
6 | 7 | |
7 | 8 | import java.util.List; |
8 | 9 | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/scene/DoActionDTO.java
renamed from
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/DoActionDTO.java
1 | -package org.thingsboard.server.common.data.yunteng.dto; | |
1 | +package org.thingsboard.server.common.data.yunteng.dto.scene; | |
2 | 2 | |
3 | 3 | import com.fasterxml.jackson.databind.JsonNode; |
4 | 4 | import io.swagger.annotations.ApiModelProperty; |
5 | 5 | import lombok.Data; |
6 | 6 | import lombok.EqualsAndHashCode; |
7 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | |
7 | 8 | import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; |
8 | 9 | import org.thingsboard.server.common.data.yunteng.enums.CallTypeEnum; |
9 | 10 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/scene/DoConditionDTO.java
renamed from
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/DoConditionDTO.java
1 | -package org.thingsboard.server.common.data.yunteng.dto; | |
1 | +package org.thingsboard.server.common.data.yunteng.dto.scene; | |
2 | 2 | |
3 | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | +import java.util.List; | |
4 | 5 | import lombok.Data; |
5 | 6 | import lombok.EqualsAndHashCode; |
6 | -import org.thingsboard.server.common.data.device.profile.AlarmCondition; | |
7 | 7 | import org.thingsboard.server.common.data.device.profile.AlarmRule; |
8 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | |
8 | 9 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
9 | 10 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
10 | 11 | import org.thingsboard.server.common.data.yunteng.enums.TriggerTypeEnum; |
11 | 12 | |
12 | -import java.util.List; | |
13 | - | |
14 | 13 | /** |
15 | 14 | * @Description 场景联动执行条件数据传输表 @Author cxy @Date 2021/11/24 17:33 |
16 | 15 | */ | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/scene/RuleFilterDTO.java
0 → 100644
1 | +package org.thingsboard.server.common.data.yunteng.dto.scene; | |
2 | + | |
3 | +import com.fasterxml.jackson.databind.JsonNode; | |
4 | +import io.swagger.annotations.ApiModelProperty; | |
5 | + | |
6 | +import java.io.Serializable; | |
7 | +import java.util.List; | |
8 | +import lombok.Data; | |
9 | +import lombok.EqualsAndHashCode; | |
10 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | |
11 | +import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; | |
12 | +import org.thingsboard.server.common.data.yunteng.enums.CallTypeEnum; | |
13 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
14 | +import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; | |
15 | + | |
16 | +/** | |
17 | + * @Description 场景联动执行动作数据传输表 @Author cxy @Date 2021/11/24 17:32 | |
18 | + */ | |
19 | +@Data | |
20 | +public class RuleFilterDTO implements Serializable { | |
21 | + | |
22 | + @ApiModelProperty(value = "触发条件ID") | |
23 | + private String ruleId; | |
24 | + | |
25 | + @ApiModelProperty(value = "场景联动ID") | |
26 | + private String scenId; | |
27 | + | |
28 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/scene/SceneLinkageDTO.java
renamed from
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/SceneLinkageDTO.java
1 | -package org.thingsboard.server.common.data.yunteng.dto; | |
1 | +package org.thingsboard.server.common.data.yunteng.dto.scene; | |
2 | 2 | |
3 | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | 4 | import lombok.Data; |
5 | 5 | import lombok.EqualsAndHashCode; |
6 | 6 | import org.thingsboard.server.common.data.HasName; |
7 | 7 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; |
8 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | |
8 | 9 | |
9 | 10 | import javax.validation.constraints.NotEmpty; |
10 | 11 | import java.util.List; | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/scene/TriggerDTO.java
renamed from
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/TriggerDTO.java
1 | -package org.thingsboard.server.common.data.yunteng.dto; | |
1 | +package org.thingsboard.server.common.data.yunteng.dto.scene; | |
2 | 2 | |
3 | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | 4 | import lombok.Data; |
5 | 5 | import lombok.EqualsAndHashCode; |
6 | 6 | import org.thingsboard.server.common.data.device.profile.AlarmRule; |
7 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | |
7 | 8 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
8 | 9 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
9 | 10 | import org.thingsboard.server.common.data.yunteng.enums.TriggerTypeEnum; | ... | ... |
... | ... | @@ -13,6 +13,7 @@ import org.thingsboard.server.common.data.yunteng.core.exception.NoneTenantAsset |
13 | 13 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; |
14 | 14 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
15 | 15 | import org.thingsboard.server.common.data.yunteng.dto.*; |
16 | +import org.thingsboard.server.common.data.yunteng.dto.scene.DoActionDTO; | |
16 | 17 | import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils; |
17 | 18 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
18 | 19 | import org.thingsboard.server.dao.yunteng.entities.TkAlarmProfileEntity; | ... | ... |
... | ... | @@ -7,7 +7,7 @@ import java.util.Set; |
7 | 7 | import java.util.stream.Collectors; |
8 | 8 | import lombok.RequiredArgsConstructor; |
9 | 9 | import org.springframework.stereotype.Service; |
10 | -import org.thingsboard.server.common.data.yunteng.dto.DoActionDTO; | |
10 | +import org.thingsboard.server.common.data.yunteng.dto.scene.DoActionDTO; | |
11 | 11 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
12 | 12 | import org.thingsboard.server.dao.yunteng.entities.TenantBaseEntity; |
13 | 13 | import org.thingsboard.server.dao.yunteng.entities.TkDeviceEntity; | ... | ... |
... | ... | @@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
5 | 5 | import com.baomidou.mybatisplus.core.metadata.IPage; |
6 | 6 | import com.fasterxml.jackson.databind.JsonNode; |
7 | 7 | import com.fasterxml.jackson.databind.node.ObjectNode; |
8 | +import java.util.*; | |
9 | +import java.util.stream.Collectors; | |
8 | 10 | import lombok.RequiredArgsConstructor; |
9 | 11 | import org.apache.commons.lang3.StringUtils; |
10 | 12 | import org.springframework.stereotype.Service; |
... | ... | @@ -16,6 +18,7 @@ import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; |
16 | 18 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; |
17 | 19 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
18 | 20 | import org.thingsboard.server.common.data.yunteng.dto.*; |
21 | +import org.thingsboard.server.common.data.yunteng.dto.scene.*; | |
19 | 22 | import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; |
20 | 23 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
21 | 24 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
... | ... | @@ -27,13 +30,13 @@ import org.thingsboard.server.dao.yunteng.entities.*; |
27 | 30 | import org.thingsboard.server.dao.yunteng.mapper.*; |
28 | 31 | import org.thingsboard.server.dao.yunteng.service.*; |
29 | 32 | |
30 | -import java.util.*; | |
31 | -import java.util.stream.Collectors; | |
32 | - | |
33 | -/** @Description 场景联动业务实现层 @Author cxy @Date 2021/11/25 11:22 */ | |
33 | +/** | |
34 | + * @Description 场景联动业务实现层 @Author cxy @Date 2021/11/25 11:22 | |
35 | + */ | |
34 | 36 | @Service |
35 | 37 | @RequiredArgsConstructor |
36 | -public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageMapper, TkSceneLinkageEntity> | |
38 | +public class TkSceneLinkageServiceImpl | |
39 | + extends AbstractBaseService<SceneLinkageMapper, TkSceneLinkageEntity> | |
37 | 40 | implements SceneLinkageService { |
38 | 41 | |
39 | 42 | private final SceneLinkageMapper sceneLinkageMapper; |
... | ... | @@ -74,14 +77,20 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
74 | 77 | |
75 | 78 | /** |
76 | 79 | * 更新场景联动附加信息,触发器、执行条件、动作 |
77 | - * @param sceneLinkageDTO 场景联动表单数据 | |
78 | - * @param tenantId 租户ID | |
80 | + * | |
81 | + * @param sceneLinkageDTO 场景联动表单数据 | |
82 | + * @param tenantId 租户ID | |
79 | 83 | * @param customerId 客户ID |
80 | 84 | * @param sceneLinkage 场景联动主表实体 |
81 | 85 | */ |
82 | - private void updateScene(SceneLinkageDTO sceneLinkageDTO, String tenantId, String customerId, TkSceneLinkageEntity sceneLinkage) { | |
86 | + private void updateScene( | |
87 | + SceneLinkageDTO sceneLinkageDTO, | |
88 | + String tenantId, | |
89 | + String customerId, | |
90 | + TkSceneLinkageEntity sceneLinkage) { | |
83 | 91 | String organizationId = sceneLinkage.getOrganizationId(); |
84 | - List<DeviceDTO> organizationDevices = findDeviceList(organizationId, tenantId, customerId,new ArrayList<>()); | |
92 | + List<DeviceDTO> organizationDevices = | |
93 | + findDeviceList(organizationId, tenantId, customerId, new ArrayList<>()); | |
85 | 94 | |
86 | 95 | List<String> tbDeviceIds = new ArrayList<>(); |
87 | 96 | for (DeviceDTO item : organizationDevices) { |
... | ... | @@ -105,7 +114,8 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
105 | 114 | new QueryWrapper<TkSceneLinkageEntity>() |
106 | 115 | .lambda() |
107 | 116 | .eq(TkSceneLinkageEntity::getTenantId, tenantId) |
108 | -// .eq(StringUtils.isNotBlank(currentUserId),SceneLinkage::getCreator, currentUserId) | |
117 | + // .eq(StringUtils.isNotBlank(currentUserId),SceneLinkage::getCreator, | |
118 | + // currentUserId) | |
109 | 119 | .in(TkSceneLinkageEntity::getId, ids); |
110 | 120 | int result = sceneLinkageMapper.delete(Wrapper); |
111 | 121 | if (result != ids.size()) { |
... | ... | @@ -239,19 +249,22 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
239 | 249 | doActionDTO.setTenantId(sceneLinkageDTO.getTenantId()); |
240 | 250 | doActionDTO.setSceneLinkageId(sceneLinkageDTO.getId()); |
241 | 251 | if (ActionTypeEnum.DEVICE_OUT.equals(doActionDTO.getOutTarget())) { |
242 | - JsonNode inputContext = doActionDTO.getDoContext().get(FastIotConstants.Rpc.PARAMS_NAME); | |
252 | + JsonNode inputContext = | |
253 | + doActionDTO.getDoContext().get(FastIotConstants.Rpc.PARAMS_NAME); | |
243 | 254 | ObjectNode outputContext = JacksonUtil.newObjectNode(); |
244 | 255 | outputContext.put(FastIotConstants.Rpc.METHOD_NAME, "methodThingskit"); |
245 | - if(inputContext == null){ | |
246 | - outputContext.put(FastIotConstants.Rpc.PARAMS_NAME,""); | |
247 | - }else if(inputContext.isTextual()){ | |
248 | - outputContext.put(FastIotConstants.Rpc.PARAMS_NAME,inputContext.asText()); | |
249 | - }else{ | |
256 | + if (inputContext == null) { | |
257 | + outputContext.put(FastIotConstants.Rpc.PARAMS_NAME, ""); | |
258 | + } else if (inputContext.isTextual()) { | |
259 | + outputContext.put(FastIotConstants.Rpc.PARAMS_NAME, inputContext.asText()); | |
260 | + } else { | |
250 | 261 | outputContext.set(FastIotConstants.Rpc.PARAMS_NAME, inputContext); |
251 | 262 | } |
252 | 263 | |
253 | 264 | ObjectNode addtionalInfo = JacksonUtil.newObjectNode(); |
254 | - addtionalInfo.put(ModelConstants.TablePropertyMapping.COMMAND_TYPE, doActionDTO.getCommandType()); | |
265 | + addtionalInfo.put( | |
266 | + ModelConstants.TablePropertyMapping.COMMAND_TYPE, | |
267 | + doActionDTO.getCommandType()); | |
255 | 268 | outputContext.set(DataConstants.ADDITIONAL_INFO, addtionalInfo); |
256 | 269 | doActionDTO.setDoContext(outputContext); |
257 | 270 | } |
... | ... | @@ -264,11 +277,13 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
264 | 277 | |
265 | 278 | /** |
266 | 279 | * 验证设备输出的目标设备是否合法有效 |
267 | - * @param tbDeviceIds 场景联动所属组织的全部设备ID | |
268 | - * @param deviceIds 场景联动选择的设备ID | |
269 | - * @param entityType 设备输出类型 | |
280 | + * | |
281 | + * @param tbDeviceIds 场景联动所属组织的全部设备ID | |
282 | + * @param deviceIds 场景联动选择的设备ID | |
283 | + * @param entityType 设备输出类型 | |
270 | 284 | */ |
271 | - private void validateRpcDevice(List<String> tbDeviceIds, List<String> deviceIds, ScopeEnum entityType) { | |
285 | + private void validateRpcDevice( | |
286 | + List<String> tbDeviceIds, List<String> deviceIds, ScopeEnum entityType) { | |
272 | 287 | if (ScopeEnum.PART.equals(entityType)) { |
273 | 288 | if (deviceIds == null || deviceIds.isEmpty()) { |
274 | 289 | throw new TkDataValidationException(ErrorMessage.DEVICE_LOSED.getMessage()); |
... | ... | @@ -338,13 +353,13 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
338 | 353 | String organizationId = (String) queryMap.get("organizationId"); |
339 | 354 | // 不为空 |
340 | 355 | if (null != organizationId && !StringUtils.isEmpty(organizationId)) { |
341 | - queryMap.put( | |
342 | - "organizationIds", getQueryOrganizationIds(tenantId, List.of(organizationId))); | |
356 | + queryMap.put("organizationIds", getQueryOrganizationIds(tenantId, List.of(organizationId))); | |
343 | 357 | } |
344 | 358 | if (!isCustomerUser) { |
345 | 359 | queryMap.remove("currentUser"); |
346 | 360 | } |
347 | - IPage<TkSceneLinkageEntity> page = getPage(queryMap, FastIotConstants.DefaultOrder.CREATE_TIME, false); | |
361 | + IPage<TkSceneLinkageEntity> page = | |
362 | + getPage(queryMap, FastIotConstants.DefaultOrder.CREATE_TIME, false); | |
348 | 363 | IPage<SceneLinkageDTO> scenePage = baseMapper.getScenePage(page, queryMap); |
349 | 364 | return getPageData(scenePage, SceneLinkageDTO.class); |
350 | 365 | } |
... | ... | @@ -392,7 +407,8 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
392 | 407 | * @return 设备集合 |
393 | 408 | */ |
394 | 409 | @Override |
395 | - public List<DeviceDTO> findDeviceList(String organizationId, String tenantId, String customerId,List<DeviceTypeEnum> typeFilter) { | |
410 | + public List<DeviceDTO> findDeviceList( | |
411 | + String organizationId, String tenantId, String customerId, List<DeviceTypeEnum> typeFilter) { | |
396 | 412 | List<String> organizationFilter = new ArrayList<>(); |
397 | 413 | organizationFilter.add(organizationId); |
398 | 414 | // 查询该组织的所有子类 |
... | ... | @@ -405,7 +421,6 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
405 | 421 | throw new TkDataValidationException(ErrorMessage.ORGANIZATION_NOT_EXTIED.getMessage()); |
406 | 422 | } |
407 | 423 | |
408 | - | |
409 | 424 | List<String> customerDevices = null; |
410 | 425 | // 不等于默认的UUID就是客户用户 |
411 | 426 | if (!EntityId.NULL_UUID.toString().equals(customerId)) { |
... | ... | @@ -416,18 +431,19 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
416 | 431 | } |
417 | 432 | |
418 | 433 | List<TkDeviceEntity> orgDevices = |
419 | - deviceMapper.selectList( | |
420 | - new QueryWrapper<TkDeviceEntity>().lambda().in(TkDeviceEntity::getOrganizationId, orgIds)); | |
434 | + deviceMapper.selectList( | |
435 | + new QueryWrapper<TkDeviceEntity>() | |
436 | + .lambda() | |
437 | + .in(TkDeviceEntity::getOrganizationId, orgIds)); | |
421 | 438 | List<DeviceDTO> result = |
422 | 439 | orgDevices.stream() |
423 | - .filter(t -> typeFilter.isEmpty() || typeFilter.contains(t.getDeviceType())) | |
440 | + .filter(t -> typeFilter.isEmpty() || typeFilter.contains(t.getDeviceType())) | |
424 | 441 | .map(device -> device.getDTO(DeviceDTO.class)) |
425 | 442 | .collect(Collectors.toList()); |
426 | 443 | |
427 | - | |
428 | 444 | if (null != customerDevices && customerDevices.size() > 0) { |
429 | 445 | List<DeviceDTO> customerAllDevice = new ArrayList<>(); |
430 | - for(DeviceDTO item: result){ | |
446 | + for (DeviceDTO item : result) { | |
431 | 447 | if (customerDevices.contains(item.getTbDeviceId())) { |
432 | 448 | customerAllDevice.add(item); |
433 | 449 | } |
... | ... | @@ -480,12 +496,10 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
480 | 496 | return null; |
481 | 497 | } |
482 | 498 | |
499 | + Map<String, List<RuleFilterDTO>> matchedDevices = new HashMap<>(); | |
500 | + Map<String, List<RuleFilterDTO>> matchedProjectes = new HashMap<>(); | |
483 | 501 | |
484 | - | |
485 | - | |
486 | - Map<String, List<String>> matchedDevices = new HashMap<>(); | |
487 | - Map<String, List<String>> matchedProjectes = new HashMap<>(); | |
488 | - | |
502 | + Set<String> projectFilter = new HashSet<>(); | |
489 | 503 | List<TkTriggerEntity> triggers = |
490 | 504 | triggerMapper.selectList( |
491 | 505 | new QueryWrapper<TkTriggerEntity>() |
... | ... | @@ -493,27 +507,51 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM |
493 | 507 | .eq(TkTriggerEntity::getTenantId, tenantId) |
494 | 508 | .eq(TkTriggerEntity::getTriggerType, TriggerTypeEnum.DEVICE_TRIGGER) |
495 | 509 | .in(TkTriggerEntity::getSceneLinkageId, enableIds)); |
496 | - | |
497 | 510 | triggers.forEach( |
498 | 511 | trigger -> { |
499 | - String scenId = trigger.getSceneLinkageId(); | |
500 | - if (ScopeEnum.ALL.equals(trigger.getEntityType()) ) { | |
501 | - List<String> triggerIds = new ArrayList<>(); | |
502 | - triggerIds.add(trigger.getDeviceProfileId()); | |
503 | - deviceSceneMap(matchedProjectes, triggerIds, scenId); | |
504 | - }else{ | |
505 | - deviceSceneMap(matchedDevices, trigger.getEntityId(), scenId); | |
506 | - } | |
512 | + projectFilter.add(trigger.getDeviceProfileId()); | |
513 | + deviceSceneMap( | |
514 | + matchedProjectes, | |
515 | + matchedDevices, | |
516 | + trigger.getId(), | |
517 | + trigger.getSceneLinkageId(), | |
518 | + trigger.getEntityType(), | |
519 | + trigger.getDeviceProfileId(), | |
520 | + trigger.getEntityId()); | |
521 | + }); | |
507 | 522 | |
523 | + List<TkDoConditionEntity> conditions = | |
524 | + doConditionMapper.selectList( | |
525 | + new QueryWrapper<TkDoConditionEntity>() | |
526 | + .lambda() | |
527 | + .eq(TkDoConditionEntity::getTenantId, tenantId) | |
528 | + .eq(TkDoConditionEntity::getTriggerType, TriggerTypeEnum.DEVICE_TRIGGER) | |
529 | + .in(TkDoConditionEntity::getSceneLinkageId, enableIds)); | |
530 | + conditions.forEach( | |
531 | + condition -> { | |
532 | + projectFilter.add(condition.getDeviceProfileId()); | |
533 | + deviceSceneMap( | |
534 | + matchedProjectes, | |
535 | + matchedDevices, | |
536 | + condition.getId(), | |
537 | + condition.getSceneLinkageId(), | |
538 | + condition.getEntityType(), | |
539 | + condition.getDeviceProfileId(), | |
540 | + condition.getEntityId()); | |
508 | 541 | }); |
509 | 542 | |
510 | - if (matchedDevices.isEmpty()&&matchedProjectes.isEmpty()) { | |
543 | + if (matchedDevices.isEmpty() && matchedProjectes.isEmpty()) { | |
511 | 544 | return null; |
512 | 545 | } |
513 | 546 | |
514 | -List<TkDeviceProfileEntity> profiles = profileMapper.selectList(new LambdaQueryWrapper<TkDeviceProfileEntity>().eq(TkDeviceProfileEntity::getTenantId,tenantId)); | |
547 | + | |
548 | + | |
549 | + List<TkDeviceProfileEntity> profiles = | |
550 | + profileMapper.selectList( | |
551 | + new LambdaQueryWrapper<TkDeviceProfileEntity>() | |
552 | + .eq(TkDeviceProfileEntity::getTenantId, tenantId).in(TkDeviceProfileEntity::getId, projectFilter)); | |
515 | 553 | Map<String, String> projects = new HashMap<>(); |
516 | - profiles.forEach(f->projects.put(f.getTbProfileId(),f.getId())); | |
554 | + profiles.forEach(f -> projects.put(f.getTbProfileId(), f.getId())); | |
517 | 555 | |
518 | 556 | Map<String, Map> engineConfig = new HashMap<>(); |
519 | 557 | engineConfig.put("scenes", matchedDevices); |
... | ... | @@ -525,21 +563,38 @@ List<TkDeviceProfileEntity> profiles = profileMapper.selectList(new LambdaQueryW |
525 | 563 | return JacksonUtil.convertValue(engineConfig, JsonNode.class); |
526 | 564 | } |
527 | 565 | |
528 | - | |
566 | + private void deviceSceneMap( | |
567 | + Map<String, List<RuleFilterDTO>> matchedProjectes, | |
568 | + Map<String, List<RuleFilterDTO>> matchedDevices,String ruleId, | |
569 | + String scenId, | |
570 | + ScopeEnum scope, | |
571 | + String profileId, | |
572 | + List<String> deviceIds) { | |
573 | + if (ScopeEnum.ALL.equals(scope)) { | |
574 | + List<String> profileIds = new ArrayList<>(); | |
575 | + profileIds.add(profileId); | |
576 | + deviceSceneMap(matchedProjectes, profileIds,ruleId, scenId); | |
577 | + } else { | |
578 | + deviceSceneMap(matchedDevices, deviceIds,ruleId, scenId); | |
579 | + } | |
580 | + } | |
529 | 581 | |
530 | 582 | /** |
531 | 583 | * 设备与场景联动的映射集合 |
532 | 584 | * |
533 | 585 | * @param resultMap 缓存设备和场景联动映射结果的集合 |
534 | - * @param devices 设备主键集合 | |
586 | + * @param postfixIds 设备或产品主键集合 | |
535 | 587 | * @param scenId 场景联动主键 |
536 | 588 | */ |
537 | 589 | private void deviceSceneMap( |
538 | - Map<String, List<String>> resultMap, List<String> devices, String scenId) { | |
539 | - for (String deviceId : devices) { | |
540 | - List<String> scenes = resultMap.computeIfAbsent(deviceId, k -> new ArrayList<String>()); | |
541 | - if (!scenes.contains(scenId)) { | |
542 | - scenes.add(scenId); | |
590 | + Map<String, List<RuleFilterDTO>> resultMap, List<String> postfixIds, String prefixId, String scenId) { | |
591 | + for (String deviceId : postfixIds) { | |
592 | + List<RuleFilterDTO> scenes = resultMap.computeIfAbsent(deviceId, k -> new ArrayList<RuleFilterDTO>()); | |
593 | + RuleFilterDTO filter = new RuleFilterDTO(); | |
594 | + filter.setRuleId(prefixId); | |
595 | + filter.setScenId(scenId); | |
596 | + if (!scenes.contains(filter)) { | |
597 | + scenes.add(filter); | |
543 | 598 | } |
544 | 599 | resultMap.put(deviceId, scenes); |
545 | 600 | } | ... | ... |
... | ... | @@ -349,6 +349,16 @@ public class TkTenantServiceImpl extends AbstractBaseService<TenantMapper, SysTe |
349 | 349 | .orElse(null); |
350 | 350 | } |
351 | 351 | |
352 | + @Override | |
353 | + public TenantDTO findTenantByTenantId(String id) { | |
354 | + if(StringUtils.isEmpty(id)){ | |
355 | + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()); | |
356 | + } | |
357 | + SysTenantEntity entity = baseMapper.selectOne(new LambdaQueryWrapper<SysTenantEntity>() | |
358 | + .eq(SysTenantEntity::getTenantId,id)); | |
359 | + return Optional.ofNullable(entity).map(obj->obj.getDTO(TenantDTO.class)).orElse(null); | |
360 | + } | |
361 | + | |
352 | 362 | /** |
353 | 363 | * 保存租户与菜单、角色的映射关系 |
354 | 364 | * | ... | ... |
... | ... | @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
4 | 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
5 | 5 | import lombok.RequiredArgsConstructor; |
6 | 6 | import org.springframework.stereotype.Service; |
7 | -import org.thingsboard.server.common.data.yunteng.dto.TriggerDTO; | |
7 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | |
8 | 8 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; |
9 | 9 | import org.thingsboard.server.dao.yunteng.mapper.TriggerMapper; |
10 | 10 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; | ... | ... |
... | ... | @@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; |
3 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | 4 | import org.apache.ibatis.annotations.Mapper; |
5 | 5 | import org.apache.ibatis.annotations.Param; |
6 | -import org.thingsboard.server.common.data.yunteng.dto.DoActionDTO; | |
6 | +import org.thingsboard.server.common.data.yunteng.dto.scene.DoActionDTO; | |
7 | 7 | import org.thingsboard.server.dao.yunteng.entities.TkDoActionEntity; |
8 | 8 | |
9 | 9 | import java.util.List; | ... | ... |
... | ... | @@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; |
3 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | 4 | import org.apache.ibatis.annotations.Mapper; |
5 | 5 | import org.apache.ibatis.annotations.Param; |
6 | -import org.thingsboard.server.common.data.yunteng.dto.DoConditionDTO; | |
6 | +import org.thingsboard.server.common.data.yunteng.dto.scene.DoConditionDTO; | |
7 | 7 | import org.thingsboard.server.dao.yunteng.entities.TkDoConditionEntity; |
8 | 8 | |
9 | 9 | import java.util.List; | ... | ... |
... | ... | @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | 4 | import com.baomidou.mybatisplus.core.metadata.IPage; |
5 | 5 | import org.apache.ibatis.annotations.Mapper; |
6 | 6 | import org.apache.ibatis.annotations.Param; |
7 | -import org.thingsboard.server.common.data.yunteng.dto.SceneLinkageDTO; | |
7 | +import org.thingsboard.server.common.data.yunteng.dto.scene.SceneLinkageDTO; | |
8 | 8 | import org.thingsboard.server.dao.yunteng.entities.TkSceneLinkageEntity; |
9 | 9 | |
10 | 10 | import java.util.Map; | ... | ... |
... | ... | @@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; |
3 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | 4 | import org.apache.ibatis.annotations.Mapper; |
5 | 5 | import org.apache.ibatis.annotations.Param; |
6 | -import org.thingsboard.server.common.data.yunteng.dto.TriggerDTO; | |
6 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | |
7 | 7 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; |
8 | 8 | |
9 | 9 | import java.util.List; | ... | ... |
1 | 1 | package org.thingsboard.server.dao.yunteng.service; |
2 | 2 | |
3 | -import org.thingsboard.server.common.data.yunteng.dto.DoActionDTO; | |
3 | +import org.thingsboard.server.common.data.yunteng.dto.scene.DoActionDTO; | |
4 | 4 | import org.thingsboard.server.dao.yunteng.entities.TkDoActionEntity; |
5 | 5 | |
6 | 6 | import java.util.List; | ... | ... |
1 | 1 | package org.thingsboard.server.dao.yunteng.service; |
2 | 2 | import com.fasterxml.jackson.databind.JsonNode; |
3 | 3 | import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; |
4 | -import org.thingsboard.server.common.data.yunteng.dto.SceneLinkageDTO; | |
4 | +import org.thingsboard.server.common.data.yunteng.dto.scene.SceneLinkageDTO; | |
5 | 5 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
6 | 6 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
7 | 7 | import org.thingsboard.server.dao.yunteng.entities.TkSceneLinkageEntity; | ... | ... |
... | ... | @@ -36,4 +36,6 @@ public interface TkTenantService { |
36 | 36 | CompletableFuture<TsValue> findTenantsByTs(LocalDateTime startTs, LocalDateTime endTs,long ts); |
37 | 37 | |
38 | 38 | List<TenantDTO> checkTenantProfileIdUsedByTenants(String tenantProfileId); |
39 | + | |
40 | + TenantDTO findTenantByTenantId(String id); | |
39 | 41 | } | ... | ... |
1 | 1 | package org.thingsboard.server.dao.yunteng.service; |
2 | 2 | |
3 | -import org.thingsboard.server.common.data.yunteng.dto.TriggerDTO; | |
3 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | |
4 | 4 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; |
5 | 5 | |
6 | 6 | import java.util.List; | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | |
4 | 4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.DoActionMapper"> |
5 | - <resultMap id="actionDTO" type="org.thingsboard.server.common.data.yunteng.dto.DoActionDTO"> | |
5 | + <resultMap id="actionDTO" type="org.thingsboard.server.common.data.yunteng.dto.scene.DoActionDTO"> | |
6 | 6 | <result property="id" column="id"/> |
7 | 7 | <result property="deviceId" column="device_id" |
8 | 8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | |
4 | 4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.DoConditionMapper"> |
5 | - <resultMap id="conditionDTO" type="org.thingsboard.server.common.data.yunteng.dto.DoConditionDTO"> | |
5 | + <resultMap id="conditionDTO" type="org.thingsboard.server.common.data.yunteng.dto.scene.DoConditionDTO"> | |
6 | 6 | <result property="id" column="id"/> |
7 | 7 | <result property="entityId" column="entity_id" |
8 | 8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | |
4 | 4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.SceneLinkageMapper"> |
5 | - <resultMap id="sceneLinkageMap" type="org.thingsboard.server.common.data.yunteng.dto.SceneLinkageDTO"> | |
5 | + <resultMap id="sceneLinkageMap" type="org.thingsboard.server.common.data.yunteng.dto.scene.SceneLinkageDTO"> | |
6 | 6 | <result property="id" column="id"/> |
7 | 7 | <result property="name" column="name"/> |
8 | 8 | <result property="organizationId" column="organization_id"/> | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | |
4 | 4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.TriggerMapper"> |
5 | - <resultMap id="triggerDTO" type="org.thingsboard.server.common.data.yunteng.dto.TriggerDTO"> | |
5 | + <resultMap id="triggerDTO" type="org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO"> | |
6 | 6 | <result property="id" column="id"/> |
7 | 7 | <result property="entityId" column="entity_id" |
8 | 8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> | ... | ... |
... | ... | @@ -44,8 +44,8 @@ |
44 | 44 | <scope>provided</scope> |
45 | 45 | </dependency> |
46 | 46 | <dependency> |
47 | - <groupId>org.thingsboard.common</groupId> | |
48 | - <artifactId>dao-api</artifactId> | |
47 | + <groupId>org.thingsboard</groupId> | |
48 | + <artifactId>dao</artifactId> | |
49 | 49 | <scope>provided</scope> |
50 | 50 | </dependency> |
51 | 51 | <dependency> | ... | ... |
... | ... | @@ -56,6 +56,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; |
56 | 56 | import org.thingsboard.server.dao.tenant.TenantService; |
57 | 57 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
58 | 58 | import org.thingsboard.server.dao.user.UserService; |
59 | +import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | |
59 | 60 | |
60 | 61 | import java.util.Set; |
61 | 62 | import java.util.function.BiConsumer; |
... | ... | @@ -285,4 +286,8 @@ public interface TbContext { |
285 | 286 | void removeListeners(); |
286 | 287 | |
287 | 288 | TenantProfile getTenantProfile(); |
289 | + | |
290 | + | |
291 | + //Thingskit function | |
292 | + TkDeviceService getTkDeviceService(); | |
288 | 293 | } | ... | ... |
... | ... | @@ -16,12 +16,13 @@ |
16 | 16 | package org.thingsboard.rule.engine.rpc; |
17 | 17 | |
18 | 18 | import com.datastax.oss.driver.api.core.uuid.Uuids; |
19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
19 | 20 | import com.google.gson.Gson; |
20 | 21 | import com.google.gson.JsonElement; |
21 | 22 | import com.google.gson.JsonObject; |
22 | 23 | import com.google.gson.JsonParser; |
23 | 24 | import lombok.extern.slf4j.Slf4j; |
24 | -import org.springframework.util.StringUtils; | |
25 | +import org.thingsboard.common.util.JacksonUtil; | |
25 | 26 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; |
26 | 27 | import org.thingsboard.rule.engine.api.RuleNode; |
27 | 28 | import org.thingsboard.rule.engine.api.TbContext; |
... | ... | @@ -31,9 +32,17 @@ import org.thingsboard.rule.engine.api.TbNodeException; |
31 | 32 | import org.thingsboard.rule.engine.api.TbRelationTypes; |
32 | 33 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
33 | 34 | import org.thingsboard.server.common.data.DataConstants; |
35 | +import org.thingsboard.server.common.data.Device; | |
34 | 36 | import org.thingsboard.server.common.data.EntityType; |
37 | +import org.thingsboard.server.common.data.StringUtils; | |
35 | 38 | import org.thingsboard.server.common.data.id.DeviceId; |
36 | 39 | import org.thingsboard.server.common.data.plugin.ComponentType; |
40 | +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; | |
41 | +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | |
42 | +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | |
43 | +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; | |
44 | +import org.thingsboard.server.common.data.yunteng.enums.CmdTypeEnum; | |
45 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
37 | 46 | import org.thingsboard.server.common.msg.TbMsg; |
38 | 47 | |
39 | 48 | import java.util.Random; |
... | ... | @@ -98,12 +107,41 @@ public class TbSendRPCRequestNode implements TbNode { |
98 | 107 | String params = parseJsonData(json.get("params")); |
99 | 108 | String additionalInfo = parseJsonData(json.get(DataConstants.ADDITIONAL_INFO)); |
100 | 109 | |
110 | + | |
111 | + /** Thingskit function | |
112 | + * 1.1、目标设备为网关子设备时,表单数据增加属性deviceName。 | |
113 | + * 1.2、目标设备为网关子设备时,附加信息增加属性target,便于命令下发记录挂靠。 | |
114 | + * 1.3、目标设备为网关子设备时,将RuleEngineDeviceRpcRequest对象的deviceId改为网关设备。 | |
115 | + * 2、附加信息增加属性cmdType,记录命令下发类型,例如:定时任务。 | |
116 | + */ | |
117 | + UUID orginatorId = msg.getOriginator().getId(); | |
118 | + DeviceId targetId = new DeviceId(orginatorId); | |
119 | + DeviceDTO targetDevice = ctx.getTkDeviceService().findDeviceInfoByTbDeviceId(ctx.getTenantId().getId().toString(), orginatorId.toString()); | |
120 | + DeviceId netEnableId = DeviceTypeEnum.SENSOR == targetDevice.getDeviceType()?new DeviceId(UUID.fromString(targetDevice.getGatewayId())):targetId; | |
121 | + if(DeviceTypeEnum.SENSOR == targetDevice.getDeviceType() && params.contains("}")){ | |
122 | + ObjectNode methodParams = (ObjectNode) JacksonUtil.toJsonNode(params); | |
123 | + methodParams.put(FastIotConstants.Rpc.TARGET_NAME,targetDevice.getName()); | |
124 | + params = JacksonUtil.toString(methodParams); | |
125 | + } | |
126 | + ObjectNode additional = JacksonUtil.newObjectNode(); | |
127 | + if(StringUtils.isNotBlank(additionalInfo)){ | |
128 | + additional = (ObjectNode) JacksonUtil.toJsonNode(additionalInfo); | |
129 | + } | |
130 | + if(!additional.has(FastIotConstants.Rpc.TARGET_ID)){ | |
131 | + additional.put(FastIotConstants.Rpc.TARGET_ID, orginatorId.toString()); | |
132 | + } | |
133 | + if(!additional.has(ModelConstants.TablePropertyMapping.COMMAND_TYPE)){ | |
134 | + additional.put(ModelConstants.TablePropertyMapping.COMMAND_TYPE, CmdTypeEnum.DIY.ordinal()); | |
135 | + } | |
136 | + additionalInfo = JacksonUtil.toString(additional); | |
137 | + | |
138 | + | |
101 | 139 | RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder() |
102 | 140 | .oneway(oneway) |
103 | 141 | .method(json.get("method").getAsString()) |
104 | 142 | .body(params) |
105 | 143 | .tenantId(ctx.getTenantId()) |
106 | - .deviceId(new DeviceId(msg.getOriginator().getId())) | |
144 | + .deviceId(netEnableId) | |
107 | 145 | .requestId(requestId) |
108 | 146 | .requestUUID(requestUUID) |
109 | 147 | .originServiceId(originServiceId) | ... | ... |
... | ... | @@ -3,12 +3,10 @@ package org.thingsboard.rule.engine.yunteng.scene; |
3 | 3 | |
4 | 4 | import com.fasterxml.jackson.databind.JsonNode; |
5 | 5 | import com.fasterxml.jackson.databind.node.ObjectNode; |
6 | -import java.util.HashSet; | |
7 | -import java.util.List; | |
8 | -import java.util.Set; | |
9 | -import java.util.UUID; | |
6 | +import java.util.*; | |
10 | 7 | import java.util.concurrent.ConcurrentHashMap; |
11 | 8 | import java.util.concurrent.ExecutionException; |
9 | +import java.util.concurrent.atomic.AtomicBoolean; | |
12 | 10 | import lombok.extern.slf4j.Slf4j; |
13 | 11 | import org.apache.commons.lang3.StringUtils; |
14 | 12 | import org.jetbrains.annotations.NotNull; |
... | ... | @@ -23,7 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceId; |
23 | 21 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
24 | 22 | import org.thingsboard.server.common.data.yunteng.dto.ActionAlarmDTO; |
25 | 23 | import org.thingsboard.server.common.data.yunteng.dto.AlarmInfoDTO; |
26 | -import org.thingsboard.server.common.data.yunteng.dto.TriggerDTO; | |
24 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | |
27 | 25 | import org.thingsboard.server.common.data.yunteng.enums.*; |
28 | 26 | import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil; |
29 | 27 | import org.thingsboard.server.common.data.yunteng.utils.SpringBeanUtils; |
... | ... | @@ -86,7 +84,16 @@ class ReactState { |
86 | 84 | this.ytDeviceService = SpringBeanUtils.getBean(TkDeviceService.class); |
87 | 85 | } |
88 | 86 | |
89 | - public void process(TbContext ctx, TbMsg msg, String profileId, String deviceId) | |
87 | + /** | |
88 | + * 触发器内部逻辑:或 执行条件内部逻辑:且 触发器和执行条件之间的逻辑:且 | |
89 | + * | |
90 | + * @param ctx | |
91 | + * @param msg 设备推送的遥测数据 | |
92 | + * @param deviceId 遥测数据的来源设备的TB设备ID | |
93 | + * @throws ExecutionException | |
94 | + * @throws InterruptedException | |
95 | + */ | |
96 | + public void process(TbContext ctx, TbMsg msg, String prefixId,String deviceId) | |
90 | 97 | throws ExecutionException, InterruptedException { |
91 | 98 | |
92 | 99 | StringBuilder detail = new StringBuilder(); |
... | ... | @@ -94,74 +101,120 @@ class ReactState { |
94 | 101 | ctx.tellSuccess(msg); |
95 | 102 | } |
96 | 103 | |
97 | - boolean matched = false; | |
98 | - if (triggers == null || triggers.isEmpty()) { | |
99 | - matched = true; | |
100 | - } else { | |
101 | - boolean itemMatched = false; | |
102 | - for (TriggerDTO trigger : triggers) { | |
103 | - TriggerState triggerState = getOrCreateTriggerState(trigger, profileId, deviceId); | |
104 | - if (triggerState == null) { | |
105 | - continue; | |
106 | - } | |
107 | - itemMatched = triggerState.process(ctx, msg); | |
108 | - if (itemMatched) { | |
109 | - matched = true; | |
110 | - detail.append( | |
111 | - triggerState.getAlarmDetails() == null ? "" : triggerState.getAlarmDetails()); | |
112 | - if (this.alarmAction != null) { | |
113 | - noticeMsg( | |
114 | - ctx, | |
115 | - msg, | |
116 | - alarmAction, | |
117 | - deviceId, | |
118 | - triggerState.getAlarmDetails(), | |
119 | - triggerState.getLatestValues().getTs()); | |
120 | - } | |
121 | - } else if (currentAlarms.containsKey(deviceId) && this.alarmAction != null) { | |
122 | - // 清除设备告警 | |
123 | - for (AlarmConditionFilterKey entityKey : triggerState.getEntityKeys()) { | |
124 | - clearAlarm(ctx, msg, deviceId, entityKey.getKey()); | |
125 | - } | |
126 | - } | |
127 | - } | |
128 | - } | |
104 | + AtomicBoolean triggerMatched = new AtomicBoolean(true); | |
105 | + Optional.ofNullable(triggers) | |
106 | + .ifPresent( | |
107 | + t -> { | |
108 | + triggerMatched.set(false); | |
109 | + t.forEach( | |
110 | + trigger -> { | |
111 | + ScopeEnum entityType = trigger.getEntityType(); | |
112 | + List<String> trifggerDevices = trigger.getEntityId(); | |
113 | + String tkProjectId = trigger.getDeviceProfileId(); | |
114 | + if (ScopeEnum.ALL.equals(entityType)) { | |
115 | + trifggerDevices = | |
116 | + ytDeviceService.findTbDeviceIdsByDeviceProfileId( | |
117 | + tkProjectId, trigger.getTenantId()); | |
118 | + } | |
119 | + triggerMatched.set( | |
120 | + trifggerDevices.stream() | |
121 | + .anyMatch( | |
122 | + id -> { | |
123 | + TriggerState triggerState = | |
124 | + getOrCreateTriggerState(trigger, tkProjectId, id); | |
125 | + if (triggerState == null) { | |
126 | + return false; | |
127 | + } | |
128 | + try { | |
129 | + boolean fresh = false; | |
130 | + if(trigger.getId().equals(prefixId)&&msg.getOriginator().getId().toString().equals(id)){ | |
131 | + fresh=true; | |
132 | + } | |
133 | + boolean result = triggerState.process(ctx, msg,fresh); | |
134 | + log.error(String.format("触发器【%s】刷新【%s】结果【%s】触发器设备【%s】数据设备【%s】数据内容【%s】",trigger.getId(),fresh,result,id,msg.getOriginator(),msg.getData())); | |
135 | + if (result) { | |
136 | + detail.append( | |
137 | + triggerState.getAlarmDetails() == null | |
138 | + ? "" | |
139 | + : triggerState.getAlarmDetails()); | |
140 | + return true; | |
141 | + } else if (currentAlarms.containsKey(deviceId)) { | |
142 | + // 清除设备告警 | |
143 | + for (AlarmConditionFilterKey entityKey : | |
144 | + triggerState.getEntityKeys()) { | |
145 | + clearAlarm(ctx, msg, deviceId, entityKey.getKey()); | |
146 | + } | |
147 | + } | |
148 | + } catch (ExecutionException e) { | |
149 | + throw new RuntimeException(e); | |
150 | + } catch (InterruptedException e) { | |
151 | + throw new RuntimeException(e); | |
152 | + } | |
153 | + return false; | |
154 | + })); | |
155 | + }); | |
156 | + }); | |
129 | 157 | |
130 | - if (matched && conditions.size() > 0) { | |
131 | - matched = false; | |
132 | - for (TkDoConditionEntity item : conditions) { | |
133 | - List<String> entityIds = item.getEntityId(); | |
134 | - if (entityIds == null || entityIds.isEmpty()) { | |
135 | - matched = true; | |
136 | - break; | |
137 | - } | |
138 | - for (String id : entityIds) { | |
139 | - TriggerState conditionState = getOrCreateConditionState(item, profileId, id); | |
140 | - if (conditionState == null || conditionState.process(ctx, msg)) { | |
141 | - detail.append(";"); | |
142 | - detail.append(conditionState.getAlarmDetails()); | |
143 | - matched = true; | |
144 | - break; | |
145 | - } | |
146 | - } | |
147 | - if (matched) { | |
148 | - break; | |
149 | - } | |
150 | - } | |
151 | - } | |
158 | + /** 执行条件的所有设备都满足才为true */ | |
159 | + AtomicBoolean conditionMatched = new AtomicBoolean(true); | |
160 | + Optional.ofNullable(conditions) | |
161 | + .ifPresent( | |
162 | + t -> { | |
163 | + t.forEach( | |
164 | + condition -> { | |
165 | + ScopeEnum entityType = condition.getEntityType(); | |
166 | + List<String> conditionDevices = condition.getEntityId(); | |
167 | + String tkProjectId = condition.getDeviceProfileId(); | |
168 | + if (ScopeEnum.ALL.equals(entityType)) { | |
169 | + conditionDevices = | |
170 | + ytDeviceService.findTbDeviceIdsByDeviceProfileId( | |
171 | + tkProjectId, condition.getTenantId()); | |
172 | + } | |
173 | + conditionMatched.set( | |
174 | + !conditionDevices.stream() | |
175 | + .anyMatch( | |
176 | + id -> { | |
177 | + TriggerState conditionState = | |
178 | + getOrCreateConditionState(condition, tkProjectId, id); | |
179 | + try { | |
180 | + boolean fresh = false; | |
181 | + if(msg.getOriginator().getId().toString().equals(id)){ | |
182 | + fresh=true; | |
183 | + } | |
184 | + boolean result = conditionState.process(ctx, msg,fresh); | |
185 | + log.warn(String.format("执行器【%s】刷新【%s】结果【%s】执行器设备【%s】数据设备【%s】数据内容【%s】",condition.getId(),fresh,result,id,msg.getOriginator(),msg.getData())); | |
186 | + return !result; | |
187 | + } catch (ExecutionException e) { | |
188 | + throw new RuntimeException(e); | |
189 | + } catch (InterruptedException e) { | |
190 | + throw new RuntimeException(e); | |
191 | + } | |
192 | + })); | |
193 | + }); | |
194 | + }); | |
152 | 195 | |
153 | - if (matched) { | |
196 | + if (triggerMatched.get() && conditionMatched.get()) { | |
197 | + log.error(String.format("设备【%s】的消息内容【%s】触发动作",deviceId,msg.getData())); | |
154 | 198 | for (TkDoActionEntity item : actions) { |
155 | 199 | if (ActionTypeEnum.MSG_NOTIFY.equals(item.getOutTarget())) { |
156 | - continue; | |
200 | + noticeMsg(ctx, msg, item, deviceId, detail.toString(), msg.getTs()); | |
201 | + } else { | |
202 | + pushMsg(ctx, msg, item, detail.toString()); | |
157 | 203 | } |
158 | - pushMsg(ctx, msg, item, detail.toString()); | |
159 | 204 | } |
160 | 205 | } else { |
161 | 206 | ctx.tellSuccess(msg); |
162 | 207 | } |
163 | 208 | } |
164 | 209 | |
210 | + /** | |
211 | + * 获取触发器状态 | |
212 | + * | |
213 | + * @param trigger 触发器数据 | |
214 | + * @param profileId 遥测数据的来源设备的TK产品ID | |
215 | + * @param deviceId 遥测数据的来源设备的TB设备ID | |
216 | + * @return | |
217 | + */ | |
165 | 218 | protected TriggerState getOrCreateTriggerState( |
166 | 219 | TriggerDTO trigger, String profileId, String deviceId) { |
167 | 220 | String triggerId = trigger.getId(); |
... | ... | @@ -173,6 +226,7 @@ class ReactState { |
173 | 226 | || (trigger.getEntityType().equals(ScopeEnum.ALL) |
174 | 227 | && trigger.getDeviceProfileId().equals(profileId))) { |
175 | 228 | TriggerState state = createTriggerState(deviceId, trigger.getTriggerCondition()); |
229 | + log.error(String.format("新建设备【%s】的触发器",deviceId)); | |
176 | 230 | triggerState.put(cacheKey, state); |
177 | 231 | return state; |
178 | 232 | } |
... | ... | @@ -224,7 +278,7 @@ class ReactState { |
224 | 278 | || (condition.getEntityType().equals(ScopeEnum.ALL) |
225 | 279 | && condition.getDeviceProfileId().equals(profileId))) { |
226 | 280 | TriggerState state = createTriggerState(deviceId, condition.getTriggerCondition()); |
227 | - triggerState.put(cacheKey, state); | |
281 | + conditionState.put(cacheKey, state); | |
228 | 282 | return state; |
229 | 283 | } |
230 | 284 | return null; |
... | ... | @@ -242,10 +296,6 @@ class ReactState { |
242 | 296 | } |
243 | 297 | |
244 | 298 | private void pushMsg(TbContext ctx, TbMsg msg, TkDoActionEntity action, String detail) { |
245 | - TbMsgMetaData metaData = // lastMsgMetaData != null ? lastMsgMetaData.copy() : | |
246 | - new TbMsgMetaData(); | |
247 | - String relationType = ""; | |
248 | - TbMsg newMsg = null; | |
249 | 299 | switch (action.getOutTarget()) { |
250 | 300 | case DEVICE_OUT: |
251 | 301 | List<String> rpcDevices = action.getDeviceId(); |
... | ... | @@ -358,7 +408,7 @@ class ReactState { |
358 | 408 | private void clearAlarm(TbContext ctx, TbMsg msg, String deviceId, String key) |
359 | 409 | throws ExecutionException, InterruptedException { |
360 | 410 | TriggerState clearState = getOrCreateClearState(deviceId, key); |
361 | - if (clearState != null && clearState.process(ctx, msg)) { | |
411 | + if (clearState != null && clearState.process(ctx, msg,true)) { | |
362 | 412 | ctx.getAlarmService() |
363 | 413 | .clearAlarmForResult( |
364 | 414 | ctx.getTenantId(), | ... | ... |
1 | 1 | /** |
2 | 2 | * Copyright © 2016-2022 The Thingsboard Authors |
3 | 3 | * |
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | - * you may not use this file except in compliance with the License. | |
6 | - * You may obtain a copy of the License at | |
4 | + * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file | |
5 | + * except in compliance with the License. You may obtain a copy of the License at | |
7 | 6 | * |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
7 | + * <p>http://www.apache.org/licenses/LICENSE-2.0 | |
9 | 8 | * |
10 | - * Unless required by applicable law or agreed to in writing, software | |
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | - * See the License for the specific language governing permissions and | |
9 | + * <p>Unless required by applicable law or agreed to in writing, software distributed under the | |
10 | + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | |
11 | + * express or implied. See the License for the specific language governing permissions and | |
14 | 12 | * limitations under the License. |
15 | 13 | */ |
16 | 14 | package org.thingsboard.rule.engine.yunteng.scene; |
17 | 15 | |
18 | 16 | import com.fasterxml.jackson.databind.ObjectMapper; |
19 | -import com.google.gson.JsonParser; | |
17 | +import java.util.List; | |
18 | +import java.util.Map; | |
19 | +import java.util.concurrent.ConcurrentHashMap; | |
20 | 20 | import lombok.extern.slf4j.Slf4j; |
21 | 21 | import org.thingsboard.rule.engine.api.*; |
22 | 22 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
... | ... | @@ -24,129 +24,116 @@ import org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNodeConfig; |
24 | 24 | import org.thingsboard.rule.engine.profile.SnapshotUpdate; |
25 | 25 | import org.thingsboard.rule.engine.yunteng.utils.TriggerRuleState; |
26 | 26 | import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; |
27 | -import org.thingsboard.server.common.data.id.DeviceId; | |
28 | -import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
29 | 27 | import org.thingsboard.server.common.data.plugin.ComponentType; |
28 | +import org.thingsboard.server.common.data.yunteng.dto.scene.RuleFilterDTO; | |
30 | 29 | import org.thingsboard.server.common.msg.TbMsg; |
31 | 30 | import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; |
32 | -import org.thingsboard.server.common.msg.session.SessionMsgType; | |
33 | -import org.thingsboard.server.common.transport.adaptor.JsonConverter; | |
34 | - | |
35 | -import java.util.List; | |
36 | -import java.util.Map; | |
37 | -import java.util.Optional; | |
38 | -import java.util.Set; | |
39 | -import java.util.concurrent.ConcurrentHashMap; | |
40 | -import java.util.concurrent.ExecutionException; | |
41 | 31 | |
42 | 32 | @Slf4j |
43 | 33 | @RuleNode( |
44 | - type = ComponentType.FILTER, | |
45 | - name = "scene react", | |
46 | - configClazz = TbCheckAlarmStatusNodeConfig.class, | |
47 | - relationTypes = {"Message","Alarm Created", "Alarm Updated", "RPC Request"}, | |
48 | - nodeDescription = "基于业务场景,实现设备的交互控制。", | |
49 | - nodeDetails = "基于业务场景,实现设备的交互控制。", | |
50 | - uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
51 | - configDirective = "tbSceneReactNodeConfig") | |
34 | + type = ComponentType.FILTER, | |
35 | + name = "scene react", | |
36 | + configClazz = TbCheckAlarmStatusNodeConfig.class, | |
37 | + relationTypes = {"Message", "Alarm Created", "Alarm Updated", "RPC Request"}, | |
38 | + nodeDescription = "基于业务场景,实现设备的交互控制。", | |
39 | + nodeDetails = "基于业务场景,实现设备的交互控制。", | |
40 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
41 | + configDirective = "tbSceneReactNodeConfig") | |
52 | 42 | public class TbSceneReactNode implements TbNode { |
53 | - private final ObjectMapper mapper = new ObjectMapper(); | |
54 | - private TbSceneReactNodeConfig config; | |
55 | - | |
56 | - | |
57 | - | |
58 | - | |
59 | - /**场景联动状态 | |
60 | - * 键:场景联动主键 | |
61 | - * 值:场景联动状态 | |
62 | - * */ | |
63 | - private final Map<String, ReactState> reactStates = new ConcurrentHashMap<>(); | |
64 | - | |
65 | - | |
66 | - | |
67 | - private TbContext ctx; | |
68 | - | |
69 | - @Override | |
70 | - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
71 | - this.config = TbNodeUtils.convert(configuration, TbSceneReactNodeConfig.class); | |
72 | - this.ctx = ctx; | |
43 | + private final ObjectMapper mapper = new ObjectMapper(); | |
44 | + private TbSceneReactNodeConfig config; | |
45 | + | |
46 | + /** 场景联动状态 键:场景联动主键 值:场景联动状态 */ | |
47 | + private final Map<String, ReactState> reactStates = new ConcurrentHashMap<>(); | |
48 | + | |
49 | + private TbContext ctx; | |
50 | + | |
51 | + @Override | |
52 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
53 | + this.config = TbNodeUtils.convert(configuration, TbSceneReactNodeConfig.class); | |
54 | + this.ctx = ctx; | |
55 | + } | |
56 | + | |
57 | + @Override | |
58 | + public void onMsg(TbContext ctx, TbMsg msg) { | |
59 | + String deviceId = msg.getOriginator().getId().toString(); | |
60 | + String tbProfileId = msg.getMetaData().getValue("deviceProfileId"); | |
61 | + String projectId = config.getProfile().get(tbProfileId); | |
62 | + boolean deviceHas = config.getScenes().containsKey(deviceId); | |
63 | + boolean profileHas = config.getProject().containsKey(projectId); | |
64 | + if (!deviceHas && !profileHas) { | |
65 | + ctx.tellSuccess(msg); | |
66 | + return; | |
73 | 67 | } |
74 | - | |
75 | - @Override | |
76 | - public void onMsg(TbContext ctx, TbMsg msg) { | |
77 | - String deviceId = msg.getOriginator().getId().toString(); | |
78 | - String tbProfileId = msg.getMetaData().getValue("deviceProfileId"); | |
79 | - String projectId = config.getProfile().get(tbProfileId); | |
80 | - boolean deviceHas = config.getScenes().containsKey(deviceId); | |
81 | - boolean profileHas = config.getProject().containsKey(projectId); | |
82 | - if(!deviceHas && !profileHas){ | |
83 | - ctx.tellSuccess(msg); | |
84 | - return; | |
85 | - } | |
86 | - List<String> devScence = config.getScenes().get(deviceId); | |
87 | - if(devScence !=null && !devScence.isEmpty()){ | |
88 | - devScence.stream().forEach(t ->{ | |
89 | - ReactState react = getOrCreateReactState(ctx,config,t); | |
90 | - if(react != null){ | |
91 | - try { | |
92 | - react.process(ctx, msg,projectId,deviceId); | |
93 | - } catch (Exception e) { | |
94 | - ctx.tellFailure(msg,e); | |
95 | - } | |
96 | - } | |
97 | - }); | |
98 | - } | |
99 | - List<String> projectScence = config.getProject().get(projectId); | |
100 | - if(projectScence !=null && !projectScence.isEmpty()){ | |
101 | - projectScence.stream().forEach(t ->{ | |
102 | - ReactState react = getOrCreateReactState(ctx,config,t); | |
103 | - if(react != null){ | |
104 | - try { | |
105 | - react.process(ctx, msg,projectId,deviceId); | |
106 | - } catch (Exception e) { | |
107 | - ctx.tellFailure(msg,e); | |
108 | - } | |
68 | + List<RuleFilterDTO> devScence = config.getScenes().get(deviceId); | |
69 | + if (devScence != null && !devScence.isEmpty()) { | |
70 | + devScence.stream() | |
71 | + .forEach( | |
72 | + t -> { | |
73 | + ReactState react = getOrCreateReactState(ctx, config, t.getScenId()); | |
74 | + if (react != null) { | |
75 | + try { | |
76 | + react.process(ctx, msg,t.getRuleId(), deviceId); | |
77 | + } catch (Exception e) { | |
78 | + ctx.tellFailure(msg, e); | |
79 | + } | |
109 | 80 | } |
110 | - }); | |
111 | - } | |
112 | - } | |
113 | - | |
114 | - /** | |
115 | - * 设备数据是否包含触发器的指标 | |
116 | - * @param update 最新数据 | |
117 | - * @param condition 触发条件工具类 | |
118 | - * @return | |
119 | - */ | |
120 | - public boolean validateUpdate(SnapshotUpdate update, TriggerRuleState condition) { | |
121 | - if (update != null) { | |
122 | - //Check that the update type and that keys match. | |
123 | - if (update.getType().equals(AlarmConditionKeyType.TIME_SERIES)) { | |
124 | - return condition.validateTsUpdate(update.getKeys()); | |
125 | - } else if (update.getType().equals(AlarmConditionKeyType.ATTRIBUTE)) { | |
126 | - return condition.validateAttrUpdate(update.getKeys()); | |
127 | - } | |
128 | - } | |
129 | - return true; | |
81 | + }); | |
130 | 82 | } |
131 | - @Override | |
132 | - public void destroy() { | |
133 | - reactStates.clear(); | |
83 | + List<RuleFilterDTO> projectScence = config.getProject().get(projectId); | |
84 | + if (projectScence != null && !projectScence.isEmpty()) { | |
85 | + projectScence.stream() | |
86 | + .forEach( | |
87 | + t -> { | |
88 | + ReactState react = getOrCreateReactState(ctx, config, t.getScenId()); | |
89 | + if (react != null) { | |
90 | + try { | |
91 | + react.process(ctx, msg,t.getRuleId(), deviceId); | |
92 | + } catch (Exception e) { | |
93 | + ctx.tellFailure(msg, e); | |
94 | + } | |
95 | + } | |
96 | + }); | |
134 | 97 | } |
135 | - | |
136 | - @Override | |
137 | - public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { | |
138 | - // Cleanup the cache for all entities that are no longer assigned to current server partitions | |
139 | - reactStates.clear(); | |
98 | + } | |
99 | + | |
100 | + /** | |
101 | + * 设备数据是否包含触发器的指标 | |
102 | + * | |
103 | + * @param update 最新数据 | |
104 | + * @param condition 触发条件工具类 | |
105 | + * @return | |
106 | + */ | |
107 | + public boolean validateUpdate(SnapshotUpdate update, TriggerRuleState condition) { | |
108 | + if (update != null) { | |
109 | + // Check that the update type and that keys match. | |
110 | + if (update.getType().equals(AlarmConditionKeyType.TIME_SERIES)) { | |
111 | + return condition.validateTsUpdate(update.getKeys()); | |
112 | + } else if (update.getType().equals(AlarmConditionKeyType.ATTRIBUTE)) { | |
113 | + return condition.validateAttrUpdate(update.getKeys()); | |
114 | + } | |
140 | 115 | } |
141 | - | |
142 | - protected ReactState getOrCreateReactState(TbContext ctx, TbSceneReactNodeConfig config, String sceneId) { | |
143 | - ReactState reactState = reactStates.get(sceneId); | |
144 | - if (reactState == null) { | |
145 | - reactState = new ReactState(sceneId,ctx, config); | |
146 | - reactStates.put(sceneId, reactState); | |
147 | - } | |
148 | - return reactState; | |
116 | + return true; | |
117 | + } | |
118 | + | |
119 | + @Override | |
120 | + public void destroy() { | |
121 | + reactStates.clear(); | |
122 | + } | |
123 | + | |
124 | + @Override | |
125 | + public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { | |
126 | + // Cleanup the cache for all entities that are no longer assigned to current server partitions | |
127 | + reactStates.clear(); | |
128 | + } | |
129 | + | |
130 | + protected ReactState getOrCreateReactState( | |
131 | + TbContext ctx, TbSceneReactNodeConfig config, String sceneId) { | |
132 | + ReactState reactState = reactStates.get(sceneId); | |
133 | + if (reactState == null) { | |
134 | + reactState = new ReactState(sceneId, ctx, config); | |
135 | + reactStates.put(sceneId, reactState); | |
149 | 136 | } |
150 | - | |
151 | - | |
137 | + return reactState; | |
138 | + } | |
152 | 139 | } | ... | ... |
1 | -/** | |
2 | - * 场景联动配置文件 | |
3 | - */ | |
1 | +/** 场景联动配置文件 */ | |
4 | 2 | package org.thingsboard.rule.engine.yunteng.scene; |
5 | 3 | |
6 | -import lombok.Data; | |
7 | -import org.thingsboard.rule.engine.api.NodeConfiguration; | |
8 | -import org.thingsboard.server.common.data.id.EntityId; | |
9 | -import org.thingsboard.server.common.data.id.TenantId; | |
10 | - | |
11 | 4 | import java.util.HashMap; |
12 | 5 | import java.util.List; |
13 | 6 | import java.util.Map; |
7 | +import lombok.Data; | |
8 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
9 | +import org.thingsboard.server.common.data.yunteng.dto.scene.RuleFilterDTO; | |
14 | 10 | |
15 | 11 | @Data |
16 | 12 | public class TbSceneReactNodeConfig implements NodeConfiguration<TbSceneReactNodeConfig> { |
17 | 13 | |
18 | - /**【设备ID,场景】设备的哪些指标会触发场景联动*/ | |
19 | - private Map<String, List<String>> scenes; | |
20 | - /**【产品ID,场景】设备的哪些指标会触发场景联动*/ | |
21 | - private Map<String, List<String>> project; | |
22 | - /**【场景ID,场景名称】场景联动信息*/ | |
23 | - private Map<String, String> names; | |
24 | - /**【场景ID,组织ID】场景联动所属组织信息*/ | |
25 | - private Map<String, String> orgs; | |
26 | - private Map<String, String> profile; | |
27 | - | |
28 | - | |
29 | - | |
30 | - | |
31 | - | |
32 | - @Override | |
33 | - public TbSceneReactNodeConfig defaultConfiguration() { | |
34 | - TbSceneReactNodeConfig config = new TbSceneReactNodeConfig(); | |
35 | - Map<String, List<String>> sceneMap = new HashMap<>(); | |
36 | - Map<String, List<String>> projectScenes = new HashMap<>(); | |
37 | - | |
38 | - config.setScenes(sceneMap); | |
39 | - config.setProject(projectScenes); | |
40 | - config.setNames(new HashMap<>()); | |
41 | - config.setOrgs(new HashMap<>()); | |
42 | - config.setProfile(new HashMap<>()); | |
43 | - return config; | |
44 | - } | |
14 | + /** 【TB设备配置ID,TK产品ID】产品信息 */ | |
15 | + private Map<String, String> profile; | |
16 | + /** 【TK产品ID,场景】哪些产品会触发场景联动 */ | |
17 | + private Map<String, List<RuleFilterDTO>> project; | |
18 | + /** 【TB设备ID,场景】哪些设备会触发场景联动 */ | |
19 | + private Map<String, List<RuleFilterDTO>> scenes; | |
20 | + /** 【TK场景ID,场景名称】场景联动信息 */ | |
21 | + private Map<String, String> names; | |
22 | + /** 【TK场景ID,组织ID】场景联动所属组织信息 */ | |
23 | + private Map<String, String> orgs; | |
24 | + | |
25 | + @Override | |
26 | + public TbSceneReactNodeConfig defaultConfiguration() { | |
27 | + TbSceneReactNodeConfig config = new TbSceneReactNodeConfig(); | |
28 | + Map<String, List<RuleFilterDTO>> sceneMap = new HashMap<>(); | |
29 | + Map<String, List<RuleFilterDTO>> projectScenes = new HashMap<>(); | |
30 | + | |
31 | + config.setScenes(sceneMap); | |
32 | + config.setProject(projectScenes); | |
33 | + config.setNames(new HashMap<>()); | |
34 | + config.setOrgs(new HashMap<>()); | |
35 | + config.setProfile(new HashMap<>()); | |
36 | + return config; | |
37 | + } | |
45 | 38 | } | ... | ... |
1 | 1 | /** |
2 | 2 | * Copyright © 2016-2022 The Thingsboard Authors |
3 | - * <p> | |
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | - * you may not use this file except in compliance with the License. | |
6 | - * You may obtain a copy of the License at | |
7 | - * <p> | |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | - * <p> | |
10 | - * Unless required by applicable law or agreed to in writing, software | |
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | - * See the License for the specific language governing permissions and | |
3 | + * | |
4 | + * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file | |
5 | + * except in compliance with the License. You may obtain a copy of the License at | |
6 | + * | |
7 | + * <p>http://www.apache.org/licenses/LICENSE-2.0 | |
8 | + * | |
9 | + * <p>Unless required by applicable law or agreed to in writing, software distributed under the | |
10 | + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | |
11 | + * express or implied. See the License for the specific language governing permissions and | |
14 | 12 | * limitations under the License. |
15 | 13 | */ |
16 | 14 | package org.thingsboard.rule.engine.yunteng.scene; |
17 | 15 | |
18 | 16 | import com.google.gson.JsonParser; |
17 | +import java.util.*; | |
18 | +import java.util.concurrent.ExecutionException; | |
19 | +import java.util.function.BiFunction; | |
19 | 20 | import lombok.Data; |
21 | +import lombok.Getter; | |
20 | 22 | import lombok.extern.slf4j.Slf4j; |
21 | 23 | import org.thingsboard.rule.engine.api.TbContext; |
22 | 24 | import org.thingsboard.rule.engine.profile.*; |
... | ... | @@ -36,243 +38,312 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; |
36 | 38 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
37 | 39 | import org.thingsboard.server.dao.sql.query.EntityKeyMapping; |
38 | 40 | |
39 | -import java.util.*; | |
40 | -import java.util.concurrent.ExecutionException; | |
41 | -import java.util.function.BiFunction; | |
42 | - | |
43 | 41 | @Data |
44 | 42 | @Slf4j |
45 | 43 | class TriggerState { |
46 | - private final String originator; | |
47 | - private volatile TriggerRuleState ruleState; | |
48 | - private volatile Alarm currentAlarm; | |
49 | - private volatile boolean initialFetchDone; | |
50 | - private volatile TbMsgMetaData lastMsgMetaData; | |
51 | - | |
52 | - private final DynamicPredicateValueCtx dynamicPredicateValueCtx; | |
53 | - private DataSnapshot latestValues; | |
54 | - | |
55 | - private final Set<AlarmConditionFilterKey> entityKeys; | |
56 | - private final String alarmDetails; | |
57 | - | |
58 | - TriggerState(String originator, AlarmRule rule, Set<AlarmConditionFilterKey> filterKeys, String alarmDetails, DynamicPredicateValueCtx dynamicPredicateValueCtx) { | |
59 | - | |
60 | - this.originator = originator; | |
61 | - this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; | |
62 | - ruleState = new TriggerRuleState(rule.getCondition(), filterKeys, new PersistedAlarmRuleState(), rule.getSchedule()); | |
63 | - this.entityKeys = filterKeys; | |
64 | - this.alarmDetails = alarmDetails; | |
44 | + private final String originator; | |
45 | + private volatile TriggerRuleState ruleState; | |
46 | + private volatile Alarm currentAlarm; | |
47 | + private volatile boolean initialFetchDone; | |
48 | + private volatile TbMsgMetaData lastMsgMetaData; | |
49 | + | |
50 | + private final DynamicPredicateValueCtx dynamicPredicateValueCtx; | |
51 | + private DataSnapshot latestValues; | |
52 | + | |
53 | + private final Set<AlarmConditionFilterKey> entityKeys; | |
54 | + private final String alarmDetails; | |
55 | + | |
56 | + @Getter | |
57 | + /** 触发条件执行结果 */ | |
58 | + private boolean ruleMatched = false; | |
59 | + | |
60 | + TriggerState( | |
61 | + String originator, | |
62 | + AlarmRule rule, | |
63 | + Set<AlarmConditionFilterKey> filterKeys, | |
64 | + String alarmDetails, | |
65 | + DynamicPredicateValueCtx dynamicPredicateValueCtx) { | |
66 | + | |
67 | + this.originator = originator; | |
68 | + this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; | |
69 | + ruleState = | |
70 | + new TriggerRuleState( | |
71 | + rule.getCondition(), filterKeys, new PersistedAlarmRuleState(), rule.getSchedule()); | |
72 | + this.entityKeys = filterKeys; | |
73 | + this.alarmDetails = alarmDetails; | |
74 | + } | |
75 | + | |
76 | + public boolean process(TbContext ctx, TbMsg msg,boolean needFresh) throws ExecutionException, InterruptedException { | |
77 | + if (!needFresh) { | |
78 | + return ruleMatched; | |
65 | 79 | } |
66 | - | |
67 | - | |
68 | - public boolean process(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { | |
69 | - if (latestValues == null) { | |
70 | - latestValues = fetchLatestValues(ctx, originator); | |
71 | - } | |
72 | - lastMsgMetaData = msg.getMetaData(); | |
73 | - SnapshotUpdate update = null; | |
74 | - if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { | |
75 | - update = processTelemetry(ctx, msg); | |
76 | - } else { | |
77 | - update = processAttributes(ctx, msg); | |
78 | - } | |
79 | - | |
80 | - if (update != null && update.hasUpdate()) { | |
81 | - return createOrClearAlarms(ctx, msg, latestValues, update, TriggerRuleState::eval); | |
82 | - } | |
83 | - return false; | |
80 | + if (latestValues == null) { | |
81 | + latestValues = fetchLatestValues(ctx, originator); | |
84 | 82 | } |
85 | - | |
86 | - | |
87 | - public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { | |
88 | - return createOrClearAlarms(ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, latestValues)); | |
83 | + lastMsgMetaData = msg.getMetaData(); | |
84 | + SnapshotUpdate update; | |
85 | + if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { | |
86 | + update = processTelemetry(ctx, msg); | |
87 | + } else { | |
88 | + update = processAttributes(ctx, msg); | |
89 | 89 | } |
90 | 90 | |
91 | - public <T> boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction<TriggerRuleState, T, AlarmEvalResult> evalFunction) { | |
92 | - boolean stateUpdate = false; | |
93 | - if (!validateUpdate(update, ruleState)) { | |
94 | - return false; | |
95 | - } | |
96 | - AlarmEvalResult evalResult = evalFunction.apply(ruleState, data); | |
97 | - if (AlarmEvalResult.TRUE.equals(evalResult)) { | |
98 | - stateUpdate = true; | |
99 | - clearAlarmState(stateUpdate, ruleState); | |
100 | - } else if (AlarmEvalResult.FALSE.equals(evalResult)) { | |
101 | - clearAlarmState(stateUpdate, ruleState); | |
102 | - } | |
103 | - return stateUpdate; | |
91 | + if (update != null && update.hasUpdate()) { | |
92 | + ruleMatched = createOrClearAlarms(ctx, msg, latestValues, update, TriggerRuleState::eval); | |
93 | + return ruleMatched; | |
104 | 94 | } |
105 | - | |
106 | - | |
107 | - public boolean clearAlarmState(boolean stateUpdate, TriggerRuleState state) { | |
108 | - if (state != null) { | |
109 | - state.clear(); | |
110 | - stateUpdate |= state.checkUpdate(); | |
111 | - } | |
112 | - return stateUpdate; | |
95 | + return false; | |
96 | + } | |
97 | + | |
98 | + public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { | |
99 | + return createOrClearAlarms( | |
100 | + ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, latestValues)); | |
101 | + } | |
102 | + | |
103 | + public <T> boolean createOrClearAlarms( | |
104 | + TbContext ctx, | |
105 | + TbMsg msg, | |
106 | + T data, | |
107 | + SnapshotUpdate update, | |
108 | + BiFunction<TriggerRuleState, T, AlarmEvalResult> evalFunction) { | |
109 | + boolean stateUpdate = false; | |
110 | + if (!validateUpdate(update, ruleState)) { | |
111 | + return false; | |
113 | 112 | } |
114 | - | |
115 | - public boolean validateUpdate(SnapshotUpdate update, TriggerRuleState state) { | |
116 | - if (update != null) { | |
117 | - //Check that the update type and that keys match. | |
118 | - if (update.getType().equals(AlarmConditionKeyType.TIME_SERIES)) { | |
119 | - return state.validateTsUpdate(update.getKeys()); | |
120 | - } else if (update.getType().equals(AlarmConditionKeyType.ATTRIBUTE)) { | |
121 | - return state.validateAttrUpdate(update.getKeys()); | |
122 | - } | |
123 | - } | |
124 | - return true; | |
113 | + AlarmEvalResult evalResult = evalFunction.apply(ruleState, data); | |
114 | + if (AlarmEvalResult.TRUE.equals(evalResult)) { | |
115 | + stateUpdate = true; | |
116 | + clearAlarmState(stateUpdate, ruleState); | |
117 | + } else if (AlarmEvalResult.FALSE.equals(evalResult)) { | |
118 | + clearAlarmState(stateUpdate, ruleState); | |
125 | 119 | } |
120 | + return stateUpdate; | |
121 | + } | |
126 | 122 | |
127 | - | |
128 | - protected void setAlarmConditionMetadata(TriggerRuleState ruleState, TbMsgMetaData metaData) { | |
129 | - if (ruleState.getSpec().getType() == AlarmConditionSpecType.REPEATING) { | |
130 | - metaData.putValue(DataConstants.ALARM_CONDITION_REPEATS, String.valueOf(ruleState.getState().getEventCount())); | |
131 | - } | |
132 | - if (ruleState.getSpec().getType() == AlarmConditionSpecType.DURATION) { | |
133 | - metaData.putValue(DataConstants.ALARM_CONDITION_DURATION, String.valueOf(ruleState.getState().getDuration())); | |
134 | - } | |
123 | + public boolean clearAlarmState(boolean stateUpdate, TriggerRuleState state) { | |
124 | + if (state != null) { | |
125 | + state.clear(); | |
126 | + stateUpdate |= state.checkUpdate(); | |
135 | 127 | } |
136 | - | |
137 | - | |
138 | - public void processAckAlarm(Alarm alarm) { | |
139 | - if (currentAlarm != null && currentAlarm.getId().equals(alarm.getId())) { | |
140 | - currentAlarm.setStatus(alarm.getStatus()); | |
141 | - currentAlarm.setAckTs(alarm.getAckTs()); | |
142 | - } | |
128 | + return stateUpdate; | |
129 | + } | |
130 | + | |
131 | + public boolean validateUpdate(SnapshotUpdate update, TriggerRuleState state) { | |
132 | + if (update != null) { | |
133 | + // Check that the update type and that keys match. | |
134 | + if (update.getType().equals(AlarmConditionKeyType.TIME_SERIES)) { | |
135 | + return state.validateTsUpdate(update.getKeys()); | |
136 | + } else if (update.getType().equals(AlarmConditionKeyType.ATTRIBUTE)) { | |
137 | + return state.validateAttrUpdate(update.getKeys()); | |
138 | + } | |
143 | 139 | } |
144 | - | |
145 | - | |
146 | - protected SnapshotUpdate processAttributes(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { | |
147 | - Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); | |
148 | - if (!attributes.isEmpty()) { | |
149 | - return merge(attributes); | |
150 | - } | |
151 | - return null; | |
140 | + return true; | |
141 | + } | |
142 | + | |
143 | + protected void setAlarmConditionMetadata(TriggerRuleState ruleState, TbMsgMetaData metaData) { | |
144 | + if (ruleState.getSpec().getType() == AlarmConditionSpecType.REPEATING) { | |
145 | + metaData.putValue( | |
146 | + DataConstants.ALARM_CONDITION_REPEATS, | |
147 | + String.valueOf(ruleState.getState().getEventCount())); | |
152 | 148 | } |
153 | - | |
154 | - protected SnapshotUpdate processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { | |
155 | - Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), msg.getMetaDataTs()); | |
156 | - for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) { | |
157 | - Long ts = entry.getKey(); | |
158 | - List<KvEntry> data = entry.getValue(); | |
159 | - return merge(ts, data); | |
160 | - } | |
161 | - | |
162 | - return null; | |
149 | + if (ruleState.getSpec().getType() == AlarmConditionSpecType.DURATION) { | |
150 | + metaData.putValue( | |
151 | + DataConstants.ALARM_CONDITION_DURATION, | |
152 | + String.valueOf(ruleState.getState().getDuration())); | |
163 | 153 | } |
154 | + } | |
164 | 155 | |
165 | - | |
166 | - private DataSnapshot fetchLatestValues(TbContext ctx, String originator) throws ExecutionException, InterruptedException { | |
167 | - DataSnapshot result = new DataSnapshot(entityKeys); | |
168 | - addEntityKeysToSnapshot(ctx, originator, result); | |
169 | - return result; | |
156 | + public void processAckAlarm(Alarm alarm) { | |
157 | + if (currentAlarm != null && currentAlarm.getId().equals(alarm.getId())) { | |
158 | + currentAlarm.setStatus(alarm.getStatus()); | |
159 | + currentAlarm.setAckTs(alarm.getAckTs()); | |
170 | 160 | } |
171 | - | |
172 | - private void addEntityKeysToSnapshot(TbContext ctx, String originator, DataSnapshot result) throws InterruptedException, ExecutionException { | |
173 | - Set<String> attributeKeys = new HashSet<>(); | |
174 | - Set<String> latestTsKeys = new HashSet<>(); | |
175 | - | |
176 | - Device device = null; | |
177 | - for (AlarmConditionFilterKey entityKey : entityKeys) { | |
178 | - String key = entityKey.getKey(); | |
179 | - switch (entityKey.getType()) { | |
180 | - case ATTRIBUTE: | |
181 | - attributeKeys.add(key); | |
182 | - break; | |
183 | - case TIME_SERIES: | |
184 | - latestTsKeys.add(key); | |
185 | - break; | |
186 | - case ENTITY_FIELD: | |
187 | - if (device == null) { | |
188 | - device = ctx.getDeviceService().findDeviceById(ctx.getTenantId(), new DeviceId(UUID.fromString(originator))); | |
189 | - } | |
190 | - if (device != null) { | |
191 | - switch (key) { | |
192 | - case EntityKeyMapping.NAME: | |
193 | - result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getName())); | |
194 | - break; | |
195 | - case EntityKeyMapping.TYPE: | |
196 | - result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getType())); | |
197 | - break; | |
198 | - case EntityKeyMapping.CREATED_TIME: | |
199 | - result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromLong(device.getCreatedTime())); | |
200 | - break; | |
201 | - case EntityKeyMapping.LABEL: | |
202 | - result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getLabel())); | |
203 | - break; | |
204 | - } | |
205 | - } | |
206 | - break; | |
207 | - } | |
208 | - } | |
209 | - | |
210 | - if (!latestTsKeys.isEmpty()) { | |
211 | - List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), new DeviceId(UUID.fromString(originator)), latestTsKeys).get(); | |
212 | - for (TsKvEntry entry : data) { | |
213 | - if (entry.getValue() != null) { | |
214 | - result.putValue(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, entry.getKey()), entry.getTs(), toEntityValue(entry)); | |
215 | - } | |
216 | - } | |
217 | - } | |
218 | - if (!attributeKeys.isEmpty()) { | |
219 | - addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), new DeviceId(UUID.fromString(originator)), DataConstants.CLIENT_SCOPE, attributeKeys).get()); | |
220 | - addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), new DeviceId(UUID.fromString(originator)), DataConstants.SHARED_SCOPE, attributeKeys).get()); | |
221 | - addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), new DeviceId(UUID.fromString(originator)), DataConstants.SERVER_SCOPE, attributeKeys).get()); | |
222 | - } | |
161 | + } | |
162 | + | |
163 | + protected SnapshotUpdate processAttributes(TbContext ctx, TbMsg msg) | |
164 | + throws ExecutionException, InterruptedException { | |
165 | + Set<AttributeKvEntry> attributes = | |
166 | + JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); | |
167 | + if (!attributes.isEmpty()) { | |
168 | + return merge(attributes); | |
169 | + } | |
170 | + return null; | |
171 | + } | |
172 | + | |
173 | + protected SnapshotUpdate processTelemetry(TbContext ctx, TbMsg msg) | |
174 | + throws ExecutionException, InterruptedException { | |
175 | + Map<Long, List<KvEntry>> tsKvMap = | |
176 | + JsonConverter.convertToSortedTelemetry( | |
177 | + new JsonParser().parse(msg.getData()), msg.getMetaDataTs()); | |
178 | + for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) { | |
179 | + Long ts = entry.getKey(); | |
180 | + List<KvEntry> data = entry.getValue(); | |
181 | + return merge(ts, data); | |
223 | 182 | } |
224 | 183 | |
225 | - private void addToSnapshot(DataSnapshot snapshot, List<AttributeKvEntry> data) { | |
226 | - for (AttributeKvEntry entry : data) { | |
227 | - if (entry.getValue() != null) { | |
228 | - EntityKeyValue value = toEntityValue(entry); | |
229 | - snapshot.putValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value); | |
184 | + return null; | |
185 | + } | |
186 | + | |
187 | + private DataSnapshot fetchLatestValues(TbContext ctx, String originator) | |
188 | + throws ExecutionException, InterruptedException { | |
189 | + DataSnapshot result = new DataSnapshot(entityKeys); | |
190 | + addEntityKeysToSnapshot(ctx, originator, result); | |
191 | + return result; | |
192 | + } | |
193 | + | |
194 | + private void addEntityKeysToSnapshot(TbContext ctx, String originator, DataSnapshot result) | |
195 | + throws InterruptedException, ExecutionException { | |
196 | + Set<String> attributeKeys = new HashSet<>(); | |
197 | + Set<String> latestTsKeys = new HashSet<>(); | |
198 | + | |
199 | + Device device = null; | |
200 | + for (AlarmConditionFilterKey entityKey : entityKeys) { | |
201 | + String key = entityKey.getKey(); | |
202 | + switch (entityKey.getType()) { | |
203 | + case ATTRIBUTE: | |
204 | + attributeKeys.add(key); | |
205 | + break; | |
206 | + case TIME_SERIES: | |
207 | + latestTsKeys.add(key); | |
208 | + break; | |
209 | + case ENTITY_FIELD: | |
210 | + if (device == null) { | |
211 | + device = | |
212 | + ctx.getDeviceService() | |
213 | + .findDeviceById(ctx.getTenantId(), new DeviceId(UUID.fromString(originator))); | |
214 | + } | |
215 | + if (device != null) { | |
216 | + switch (key) { | |
217 | + case EntityKeyMapping.NAME: | |
218 | + result.putValue( | |
219 | + entityKey, | |
220 | + device.getCreatedTime(), | |
221 | + EntityKeyValue.fromString(device.getName())); | |
222 | + break; | |
223 | + case EntityKeyMapping.TYPE: | |
224 | + result.putValue( | |
225 | + entityKey, | |
226 | + device.getCreatedTime(), | |
227 | + EntityKeyValue.fromString(device.getType())); | |
228 | + break; | |
229 | + case EntityKeyMapping.CREATED_TIME: | |
230 | + result.putValue( | |
231 | + entityKey, | |
232 | + device.getCreatedTime(), | |
233 | + EntityKeyValue.fromLong(device.getCreatedTime())); | |
234 | + break; | |
235 | + case EntityKeyMapping.LABEL: | |
236 | + result.putValue( | |
237 | + entityKey, | |
238 | + device.getCreatedTime(), | |
239 | + EntityKeyValue.fromString(device.getLabel())); | |
240 | + break; | |
230 | 241 | } |
231 | - } | |
242 | + } | |
243 | + break; | |
244 | + } | |
232 | 245 | } |
233 | 246 | |
234 | - public static EntityKeyValue toEntityValue(KvEntry entry) { | |
235 | - switch (entry.getDataType()) { | |
236 | - case STRING: | |
237 | - return EntityKeyValue.fromString(entry.getStrValue().get()); | |
238 | - case LONG: | |
239 | - return EntityKeyValue.fromLong(entry.getLongValue().get()); | |
240 | - case DOUBLE: | |
241 | - return EntityKeyValue.fromDouble(entry.getDoubleValue().get()); | |
242 | - case BOOLEAN: | |
243 | - return EntityKeyValue.fromBool(entry.getBooleanValue().get()); | |
244 | - case JSON: | |
245 | - return EntityKeyValue.fromJson(entry.getJsonValue().get()); | |
246 | - default: | |
247 | - throw new RuntimeException("Can't parse entry: " + entry.getDataType()); | |
247 | + if (!latestTsKeys.isEmpty()) { | |
248 | + List<TsKvEntry> data = | |
249 | + ctx.getTimeseriesService() | |
250 | + .findLatest( | |
251 | + ctx.getTenantId(), new DeviceId(UUID.fromString(originator)), latestTsKeys) | |
252 | + .get(); | |
253 | + for (TsKvEntry entry : data) { | |
254 | + if (entry.getValue() != null) { | |
255 | + result.putValue( | |
256 | + new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, entry.getKey()), | |
257 | + entry.getTs(), | |
258 | + toEntityValue(entry)); | |
248 | 259 | } |
260 | + } | |
249 | 261 | } |
250 | - | |
251 | - private SnapshotUpdate merge(Long newTs, List<KvEntry> data) { | |
252 | - Set<AlarmConditionFilterKey> keys = new HashSet<>(); | |
253 | - for (KvEntry entry : data) { | |
254 | - AlarmConditionFilterKey entityKey = new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, entry.getKey()); | |
255 | - if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { | |
256 | - keys.add(entityKey); | |
257 | - } | |
258 | - } | |
259 | - latestValues.setTs(newTs); | |
260 | - return new SnapshotUpdate(AlarmConditionKeyType.TIME_SERIES, keys); | |
262 | + if (!attributeKeys.isEmpty()) { | |
263 | + addToSnapshot( | |
264 | + result, | |
265 | + ctx.getAttributesService() | |
266 | + .find( | |
267 | + ctx.getTenantId(), | |
268 | + new DeviceId(UUID.fromString(originator)), | |
269 | + DataConstants.CLIENT_SCOPE, | |
270 | + attributeKeys) | |
271 | + .get()); | |
272 | + addToSnapshot( | |
273 | + result, | |
274 | + ctx.getAttributesService() | |
275 | + .find( | |
276 | + ctx.getTenantId(), | |
277 | + new DeviceId(UUID.fromString(originator)), | |
278 | + DataConstants.SHARED_SCOPE, | |
279 | + attributeKeys) | |
280 | + .get()); | |
281 | + addToSnapshot( | |
282 | + result, | |
283 | + ctx.getAttributesService() | |
284 | + .find( | |
285 | + ctx.getTenantId(), | |
286 | + new DeviceId(UUID.fromString(originator)), | |
287 | + DataConstants.SERVER_SCOPE, | |
288 | + attributeKeys) | |
289 | + .get()); | |
261 | 290 | } |
262 | - | |
263 | - private SnapshotUpdate merge(Set<AttributeKvEntry> attributes) { | |
264 | - long newTs = 0; | |
265 | - Set<AlarmConditionFilterKey> keys = new HashSet<>(); | |
266 | - for (AttributeKvEntry entry : attributes) { | |
267 | - newTs = Math.max(newTs, entry.getLastUpdateTs()); | |
268 | - AlarmConditionFilterKey entityKey = new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, entry.getKey()); | |
269 | - if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { | |
270 | - keys.add(entityKey); | |
271 | - } | |
272 | - } | |
273 | - latestValues.setTs(newTs); | |
274 | - return new SnapshotUpdate(AlarmConditionKeyType.ATTRIBUTE, keys); | |
291 | + } | |
292 | + | |
293 | + private void addToSnapshot(DataSnapshot snapshot, List<AttributeKvEntry> data) { | |
294 | + for (AttributeKvEntry entry : data) { | |
295 | + if (entry.getValue() != null) { | |
296 | + EntityKeyValue value = toEntityValue(entry); | |
297 | + snapshot.putValue( | |
298 | + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, entry.getKey()), | |
299 | + entry.getLastUpdateTs(), | |
300 | + value); | |
301 | + } | |
275 | 302 | } |
276 | - | |
277 | - | |
303 | + } | |
304 | + | |
305 | + public static EntityKeyValue toEntityValue(KvEntry entry) { | |
306 | + switch (entry.getDataType()) { | |
307 | + case STRING: | |
308 | + return EntityKeyValue.fromString(entry.getStrValue().get()); | |
309 | + case LONG: | |
310 | + return EntityKeyValue.fromLong(entry.getLongValue().get()); | |
311 | + case DOUBLE: | |
312 | + return EntityKeyValue.fromDouble(entry.getDoubleValue().get()); | |
313 | + case BOOLEAN: | |
314 | + return EntityKeyValue.fromBool(entry.getBooleanValue().get()); | |
315 | + case JSON: | |
316 | + return EntityKeyValue.fromJson(entry.getJsonValue().get()); | |
317 | + default: | |
318 | + throw new RuntimeException("Can't parse entry: " + entry.getDataType()); | |
319 | + } | |
320 | + } | |
321 | + | |
322 | + private SnapshotUpdate merge(Long newTs, List<KvEntry> data) { | |
323 | + Set<AlarmConditionFilterKey> keys = new HashSet<>(); | |
324 | + for (KvEntry entry : data) { | |
325 | + AlarmConditionFilterKey entityKey = | |
326 | + new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, entry.getKey()); | |
327 | + if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { | |
328 | + keys.add(entityKey); | |
329 | + } | |
330 | + } | |
331 | + latestValues.setTs(newTs); | |
332 | + return new SnapshotUpdate(AlarmConditionKeyType.TIME_SERIES, keys); | |
333 | + } | |
334 | + | |
335 | + private SnapshotUpdate merge(Set<AttributeKvEntry> attributes) { | |
336 | + long newTs = 0; | |
337 | + Set<AlarmConditionFilterKey> keys = new HashSet<>(); | |
338 | + for (AttributeKvEntry entry : attributes) { | |
339 | + newTs = Math.max(newTs, entry.getLastUpdateTs()); | |
340 | + AlarmConditionFilterKey entityKey = | |
341 | + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, entry.getKey()); | |
342 | + if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { | |
343 | + keys.add(entityKey); | |
344 | + } | |
345 | + } | |
346 | + latestValues.setTs(newTs); | |
347 | + return new SnapshotUpdate(AlarmConditionKeyType.ATTRIBUTE, keys); | |
348 | + } | |
278 | 349 | } | ... | ... |