Commit d7e65dae3909b14afc9e6dfdc81a1ced23573c1a
Merge branch '20230710' into 'master_dev'
refactor: 命令下发逻辑调整 See merge request yunteng/thingskit!209
Showing
33 changed files
with
1829 additions
and
1422 deletions
@@ -72,6 +72,7 @@ import org.thingsboard.server.dao.tenant.TenantProfileService; | @@ -72,6 +72,7 @@ import org.thingsboard.server.dao.tenant.TenantProfileService; | ||
72 | import org.thingsboard.server.dao.tenant.TenantService; | 72 | import org.thingsboard.server.dao.tenant.TenantService; |
73 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 73 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
74 | import org.thingsboard.server.dao.user.UserService; | 74 | import org.thingsboard.server.dao.user.UserService; |
75 | +import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | ||
75 | import org.thingsboard.server.queue.discovery.PartitionService; | 76 | import org.thingsboard.server.queue.discovery.PartitionService; |
76 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; | 77 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
77 | import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | 78 | import org.thingsboard.server.queue.usagestats.TbApiUsageClient; |
@@ -645,4 +646,10 @@ public class ActorSystemContext { | @@ -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 | * Copyright © 2016-2022 The Thingsboard Authors | 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 | * limitations under the License. | 12 | * limitations under the License. |
15 | */ | 13 | */ |
16 | package org.thingsboard.server.actors.device; | 14 | package org.thingsboard.server.actors.device; |
@@ -22,6 +20,21 @@ import com.google.common.util.concurrent.Futures; | @@ -22,6 +20,21 @@ import com.google.common.util.concurrent.Futures; | ||
22 | import com.google.common.util.concurrent.ListenableFuture; | 20 | import com.google.common.util.concurrent.ListenableFuture; |
23 | import com.google.common.util.concurrent.MoreExecutors; | 21 | import com.google.common.util.concurrent.MoreExecutors; |
24 | import com.google.protobuf.InvalidProtocolBufferException; | 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 | import lombok.extern.slf4j.Slf4j; | 38 | import lombok.extern.slf4j.Slf4j; |
26 | import org.apache.commons.collections.CollectionUtils; | 39 | import org.apache.commons.collections.CollectionUtils; |
27 | import org.thingsboard.common.util.JacksonUtil; | 40 | import org.thingsboard.common.util.JacksonUtil; |
@@ -35,7 +48,6 @@ import org.thingsboard.server.actors.TbActorCtx; | @@ -35,7 +48,6 @@ import org.thingsboard.server.actors.TbActorCtx; | ||
35 | import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; | 48 | import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
36 | import org.thingsboard.server.common.data.DataConstants; | 49 | import org.thingsboard.server.common.data.DataConstants; |
37 | import org.thingsboard.server.common.data.Device; | 50 | import org.thingsboard.server.common.data.Device; |
38 | -import org.thingsboard.server.common.data.DeviceTransportType; | ||
39 | import org.thingsboard.server.common.data.StringUtils; | 51 | import org.thingsboard.server.common.data.StringUtils; |
40 | import org.thingsboard.server.common.data.edge.EdgeEvent; | 52 | import org.thingsboard.server.common.data.edge.EdgeEvent; |
41 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; | 53 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
@@ -59,8 +71,6 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; | @@ -59,8 +71,6 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; | ||
59 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 71 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
60 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; | 72 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
61 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | 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 | import org.thingsboard.server.common.msg.TbActorMsg; | 74 | import org.thingsboard.server.common.msg.TbActorMsg; |
65 | import org.thingsboard.server.common.msg.TbMsgMetaData; | 75 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
66 | import org.thingsboard.server.common.msg.queue.TbCallback; | 76 | import org.thingsboard.server.common.msg.queue.TbCallback; |
@@ -97,921 +107,1109 @@ import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; | @@ -97,921 +107,1109 @@ import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; | ||
97 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; | 107 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; |
98 | import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; | 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 | * @author Andrew Shvayka | 111 | * @author Andrew Shvayka |
120 | */ | 112 | */ |
121 | @Slf4j | 113 | @Slf4j |
122 | class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | 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 | } else { | 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 | } else { | 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 | } else { | 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 | return; | 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 | long timeout = rpc.getExpirationTime() - System.currentTimeMillis(); | 1165 | long timeout = rpc.getExpirationTime() - System.currentTimeMillis(); |
972 | if (timeout <= 0) { | 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 | } else { | 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,6 +78,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; | ||
78 | import org.thingsboard.server.dao.tenant.TenantService; | 78 | import org.thingsboard.server.dao.tenant.TenantService; |
79 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 79 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
80 | import org.thingsboard.server.dao.user.UserService; | 80 | import org.thingsboard.server.dao.user.UserService; |
81 | +import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | ||
81 | import org.thingsboard.server.gen.transport.TransportProtos; | 82 | import org.thingsboard.server.gen.transport.TransportProtos; |
82 | import org.thingsboard.server.queue.TbQueueCallback; | 83 | import org.thingsboard.server.queue.TbQueueCallback; |
83 | import org.thingsboard.server.queue.TbQueueMsgMetadata; | 84 | import org.thingsboard.server.queue.TbQueueMsgMetadata; |
@@ -689,4 +690,10 @@ class DefaultTbContext implements TbContext { | @@ -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,8 +17,8 @@ import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; | ||
17 | import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; | 17 | import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; |
18 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; | 18 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; |
19 | import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; | 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 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | 22 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
23 | import org.thingsboard.server.common.data.yunteng.id.SceneId; | 23 | import org.thingsboard.server.common.data.yunteng.id.SceneId; |
24 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; | 24 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
@@ -21,27 +21,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode; | @@ -21,27 +21,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode; | ||
21 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
22 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | import org.springframework.stereotype.Service; | 23 | import org.springframework.stereotype.Service; |
24 | -import org.thingsboard.common.util.JacksonUtil; | ||
25 | import org.thingsboard.common.util.ThingsBoardThreadFactory; | 24 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
26 | import org.thingsboard.server.actors.ActorSystemContext; | 25 | import org.thingsboard.server.actors.ActorSystemContext; |
27 | import org.thingsboard.server.cluster.TbClusterService; | 26 | import org.thingsboard.server.cluster.TbClusterService; |
28 | import org.thingsboard.server.common.data.DataConstants; | 27 | import org.thingsboard.server.common.data.DataConstants; |
29 | import org.thingsboard.server.common.data.Device; | 28 | import org.thingsboard.server.common.data.Device; |
30 | -import org.thingsboard.server.common.data.StringUtils; | ||
31 | import org.thingsboard.server.common.data.User; | 29 | import org.thingsboard.server.common.data.User; |
32 | -import org.thingsboard.server.common.data.id.DeviceId; | ||
33 | import org.thingsboard.server.common.data.rpc.RpcError; | 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 | import org.thingsboard.server.common.msg.TbMsg; | 31 | import org.thingsboard.server.common.msg.TbMsg; |
39 | import org.thingsboard.server.common.msg.TbMsgDataType; | 32 | import org.thingsboard.server.common.msg.TbMsgDataType; |
40 | import org.thingsboard.server.common.msg.TbMsgMetaData; | 33 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
41 | import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; | 34 | import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; |
42 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; | 35 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
43 | import org.thingsboard.server.dao.device.DeviceService; | 36 | import org.thingsboard.server.dao.device.DeviceService; |
44 | -import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | ||
45 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; | 37 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
46 | import org.thingsboard.server.queue.util.TbCoreComponent; | 38 | import org.thingsboard.server.queue.util.TbCoreComponent; |
47 | import org.thingsboard.server.service.security.model.SecurityUser; | 39 | import org.thingsboard.server.service.security.model.SecurityUser; |
@@ -79,8 +71,6 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { | @@ -79,8 +71,6 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { | ||
79 | private ScheduledExecutorService scheduler; | 71 | private ScheduledExecutorService scheduler; |
80 | private String serviceId; | 72 | private String serviceId; |
81 | 73 | ||
82 | - @Autowired | ||
83 | - protected TkDeviceService tkDeviceService; | ||
84 | public DefaultTbCoreDeviceRpcService(DeviceService deviceService, TbClusterService clusterService, TbServiceInfoProvider serviceInfoProvider, | 74 | public DefaultTbCoreDeviceRpcService(DeviceService deviceService, TbClusterService clusterService, TbServiceInfoProvider serviceInfoProvider, |
85 | ActorSystemContext actorContext) { | 75 | ActorSystemContext actorContext) { |
86 | this.deviceService = deviceService; | 76 | this.deviceService = deviceService; |
@@ -110,32 +100,6 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { | @@ -110,32 +100,6 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { | ||
110 | @Override | 100 | @Override |
111 | public void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer, SecurityUser currentUser) { | 101 | public void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer, SecurityUser currentUser) { |
112 | log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); | 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 | UUID requestId = request.getId(); | 103 | UUID requestId = request.getId(); |
140 | localToRuleEngineRpcRequests.put(requestId, responseConsumer); | 104 | localToRuleEngineRpcRequests.put(requestId, responseConsumer); |
141 | sendRpcRequestToRuleEngine(request, currentUser); | 105 | sendRpcRequestToRuleEngine(request, currentUser); |
@@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.yunteng.dto; | @@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.yunteng.dto; | ||
3 | import io.swagger.annotations.ApiModelProperty; | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | import lombok.Data; | 4 | import lombok.Data; |
5 | import org.thingsboard.server.common.data.alarm.AlarmSeverity; | 5 | import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
6 | +import org.thingsboard.server.common.data.yunteng.dto.scene.TriggerDTO; | ||
6 | 7 | ||
7 | import java.util.List; | 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 | import com.fasterxml.jackson.databind.JsonNode; | 3 | import com.fasterxml.jackson.databind.JsonNode; |
4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
5 | import lombok.Data; | 5 | import lombok.Data; |
6 | import lombok.EqualsAndHashCode; | 6 | import lombok.EqualsAndHashCode; |
7 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | ||
7 | import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; | 8 | import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; |
8 | import org.thingsboard.server.common.data.yunteng.enums.CallTypeEnum; | 9 | import org.thingsboard.server.common.data.yunteng.enums.CallTypeEnum; |
9 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | 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 | import io.swagger.annotations.ApiModelProperty; | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | +import java.util.List; | ||
4 | import lombok.Data; | 5 | import lombok.Data; |
5 | import lombok.EqualsAndHashCode; | 6 | import lombok.EqualsAndHashCode; |
6 | -import org.thingsboard.server.common.data.device.profile.AlarmCondition; | ||
7 | import org.thingsboard.server.common.data.device.profile.AlarmRule; | 7 | import org.thingsboard.server.common.data.device.profile.AlarmRule; |
8 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | ||
8 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | 9 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
9 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; | 10 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
10 | import org.thingsboard.server.common.data.yunteng.enums.TriggerTypeEnum; | 11 | import org.thingsboard.server.common.data.yunteng.enums.TriggerTypeEnum; |
11 | 12 | ||
12 | -import java.util.List; | ||
13 | - | ||
14 | /** | 13 | /** |
15 | * @Description 场景联动执行条件数据传输表 @Author cxy @Date 2021/11/24 17:33 | 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 | import io.swagger.annotations.ApiModelProperty; | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | import lombok.Data; | 4 | import lombok.Data; |
5 | import lombok.EqualsAndHashCode; | 5 | import lombok.EqualsAndHashCode; |
6 | import org.thingsboard.server.common.data.HasName; | 6 | import org.thingsboard.server.common.data.HasName; |
7 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; | 7 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; |
8 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | ||
8 | 9 | ||
9 | import javax.validation.constraints.NotEmpty; | 10 | import javax.validation.constraints.NotEmpty; |
10 | import java.util.List; | 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 | import io.swagger.annotations.ApiModelProperty; | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | import lombok.Data; | 4 | import lombok.Data; |
5 | import lombok.EqualsAndHashCode; | 5 | import lombok.EqualsAndHashCode; |
6 | import org.thingsboard.server.common.data.device.profile.AlarmRule; | 6 | import org.thingsboard.server.common.data.device.profile.AlarmRule; |
7 | +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO; | ||
7 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | 8 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
8 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; | 9 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
9 | import org.thingsboard.server.common.data.yunteng.enums.TriggerTypeEnum; | 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,6 +13,7 @@ import org.thingsboard.server.common.data.yunteng.core.exception.NoneTenantAsset | ||
13 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; | 13 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; |
14 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; | 14 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
15 | import org.thingsboard.server.common.data.yunteng.dto.*; | 15 | import org.thingsboard.server.common.data.yunteng.dto.*; |
16 | +import org.thingsboard.server.common.data.yunteng.dto.scene.DoActionDTO; | ||
16 | import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils; | 17 | import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils; |
17 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; | 18 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
18 | import org.thingsboard.server.dao.yunteng.entities.TkAlarmProfileEntity; | 19 | import org.thingsboard.server.dao.yunteng.entities.TkAlarmProfileEntity; |
@@ -7,7 +7,7 @@ import java.util.Set; | @@ -7,7 +7,7 @@ import java.util.Set; | ||
7 | import java.util.stream.Collectors; | 7 | import java.util.stream.Collectors; |
8 | import lombok.RequiredArgsConstructor; | 8 | import lombok.RequiredArgsConstructor; |
9 | import org.springframework.stereotype.Service; | 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 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; | 11 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
12 | import org.thingsboard.server.dao.yunteng.entities.TenantBaseEntity; | 12 | import org.thingsboard.server.dao.yunteng.entities.TenantBaseEntity; |
13 | import org.thingsboard.server.dao.yunteng.entities.TkDeviceEntity; | 13 | import org.thingsboard.server.dao.yunteng.entities.TkDeviceEntity; |
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | @@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||
5 | import com.baomidou.mybatisplus.core.metadata.IPage; | 5 | import com.baomidou.mybatisplus.core.metadata.IPage; |
6 | import com.fasterxml.jackson.databind.JsonNode; | 6 | import com.fasterxml.jackson.databind.JsonNode; |
7 | import com.fasterxml.jackson.databind.node.ObjectNode; | 7 | import com.fasterxml.jackson.databind.node.ObjectNode; |
8 | +import java.util.*; | ||
9 | +import java.util.stream.Collectors; | ||
8 | import lombok.RequiredArgsConstructor; | 10 | import lombok.RequiredArgsConstructor; |
9 | import org.apache.commons.lang3.StringUtils; | 11 | import org.apache.commons.lang3.StringUtils; |
10 | import org.springframework.stereotype.Service; | 12 | import org.springframework.stereotype.Service; |
@@ -16,6 +18,7 @@ import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | @@ -16,6 +18,7 @@ import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | ||
16 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; | 18 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; |
17 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; | 19 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
18 | import org.thingsboard.server.common.data.yunteng.dto.*; | 20 | import org.thingsboard.server.common.data.yunteng.dto.*; |
21 | +import org.thingsboard.server.common.data.yunteng.dto.scene.*; | ||
19 | import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; | 22 | import org.thingsboard.server.common.data.yunteng.enums.ActionTypeEnum; |
20 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | 23 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
21 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; | 24 | import org.thingsboard.server.common.data.yunteng.enums.ScopeEnum; |
@@ -27,13 +30,13 @@ import org.thingsboard.server.dao.yunteng.entities.*; | @@ -27,13 +30,13 @@ import org.thingsboard.server.dao.yunteng.entities.*; | ||
27 | import org.thingsboard.server.dao.yunteng.mapper.*; | 30 | import org.thingsboard.server.dao.yunteng.mapper.*; |
28 | import org.thingsboard.server.dao.yunteng.service.*; | 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 | @Service | 36 | @Service |
35 | @RequiredArgsConstructor | 37 | @RequiredArgsConstructor |
36 | -public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageMapper, TkSceneLinkageEntity> | 38 | +public class TkSceneLinkageServiceImpl |
39 | + extends AbstractBaseService<SceneLinkageMapper, TkSceneLinkageEntity> | ||
37 | implements SceneLinkageService { | 40 | implements SceneLinkageService { |
38 | 41 | ||
39 | private final SceneLinkageMapper sceneLinkageMapper; | 42 | private final SceneLinkageMapper sceneLinkageMapper; |
@@ -74,14 +77,20 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -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 | * @param customerId 客户ID | 83 | * @param customerId 客户ID |
80 | * @param sceneLinkage 场景联动主表实体 | 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 | String organizationId = sceneLinkage.getOrganizationId(); | 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 | List<String> tbDeviceIds = new ArrayList<>(); | 95 | List<String> tbDeviceIds = new ArrayList<>(); |
87 | for (DeviceDTO item : organizationDevices) { | 96 | for (DeviceDTO item : organizationDevices) { |
@@ -105,7 +114,8 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -105,7 +114,8 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
105 | new QueryWrapper<TkSceneLinkageEntity>() | 114 | new QueryWrapper<TkSceneLinkageEntity>() |
106 | .lambda() | 115 | .lambda() |
107 | .eq(TkSceneLinkageEntity::getTenantId, tenantId) | 116 | .eq(TkSceneLinkageEntity::getTenantId, tenantId) |
108 | -// .eq(StringUtils.isNotBlank(currentUserId),SceneLinkage::getCreator, currentUserId) | 117 | + // .eq(StringUtils.isNotBlank(currentUserId),SceneLinkage::getCreator, |
118 | + // currentUserId) | ||
109 | .in(TkSceneLinkageEntity::getId, ids); | 119 | .in(TkSceneLinkageEntity::getId, ids); |
110 | int result = sceneLinkageMapper.delete(Wrapper); | 120 | int result = sceneLinkageMapper.delete(Wrapper); |
111 | if (result != ids.size()) { | 121 | if (result != ids.size()) { |
@@ -239,19 +249,22 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -239,19 +249,22 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
239 | doActionDTO.setTenantId(sceneLinkageDTO.getTenantId()); | 249 | doActionDTO.setTenantId(sceneLinkageDTO.getTenantId()); |
240 | doActionDTO.setSceneLinkageId(sceneLinkageDTO.getId()); | 250 | doActionDTO.setSceneLinkageId(sceneLinkageDTO.getId()); |
241 | if (ActionTypeEnum.DEVICE_OUT.equals(doActionDTO.getOutTarget())) { | 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 | ObjectNode outputContext = JacksonUtil.newObjectNode(); | 254 | ObjectNode outputContext = JacksonUtil.newObjectNode(); |
244 | outputContext.put(FastIotConstants.Rpc.METHOD_NAME, "methodThingskit"); | 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 | outputContext.set(FastIotConstants.Rpc.PARAMS_NAME, inputContext); | 261 | outputContext.set(FastIotConstants.Rpc.PARAMS_NAME, inputContext); |
251 | } | 262 | } |
252 | 263 | ||
253 | ObjectNode addtionalInfo = JacksonUtil.newObjectNode(); | 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 | outputContext.set(DataConstants.ADDITIONAL_INFO, addtionalInfo); | 268 | outputContext.set(DataConstants.ADDITIONAL_INFO, addtionalInfo); |
256 | doActionDTO.setDoContext(outputContext); | 269 | doActionDTO.setDoContext(outputContext); |
257 | } | 270 | } |
@@ -264,11 +277,13 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -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 | if (ScopeEnum.PART.equals(entityType)) { | 287 | if (ScopeEnum.PART.equals(entityType)) { |
273 | if (deviceIds == null || deviceIds.isEmpty()) { | 288 | if (deviceIds == null || deviceIds.isEmpty()) { |
274 | throw new TkDataValidationException(ErrorMessage.DEVICE_LOSED.getMessage()); | 289 | throw new TkDataValidationException(ErrorMessage.DEVICE_LOSED.getMessage()); |
@@ -338,13 +353,13 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -338,13 +353,13 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
338 | String organizationId = (String) queryMap.get("organizationId"); | 353 | String organizationId = (String) queryMap.get("organizationId"); |
339 | // 不为空 | 354 | // 不为空 |
340 | if (null != organizationId && !StringUtils.isEmpty(organizationId)) { | 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 | if (!isCustomerUser) { | 358 | if (!isCustomerUser) { |
345 | queryMap.remove("currentUser"); | 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 | IPage<SceneLinkageDTO> scenePage = baseMapper.getScenePage(page, queryMap); | 363 | IPage<SceneLinkageDTO> scenePage = baseMapper.getScenePage(page, queryMap); |
349 | return getPageData(scenePage, SceneLinkageDTO.class); | 364 | return getPageData(scenePage, SceneLinkageDTO.class); |
350 | } | 365 | } |
@@ -392,7 +407,8 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -392,7 +407,8 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
392 | * @return 设备集合 | 407 | * @return 设备集合 |
393 | */ | 408 | */ |
394 | @Override | 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 | List<String> organizationFilter = new ArrayList<>(); | 412 | List<String> organizationFilter = new ArrayList<>(); |
397 | organizationFilter.add(organizationId); | 413 | organizationFilter.add(organizationId); |
398 | // 查询该组织的所有子类 | 414 | // 查询该组织的所有子类 |
@@ -405,7 +421,6 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -405,7 +421,6 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
405 | throw new TkDataValidationException(ErrorMessage.ORGANIZATION_NOT_EXTIED.getMessage()); | 421 | throw new TkDataValidationException(ErrorMessage.ORGANIZATION_NOT_EXTIED.getMessage()); |
406 | } | 422 | } |
407 | 423 | ||
408 | - | ||
409 | List<String> customerDevices = null; | 424 | List<String> customerDevices = null; |
410 | // 不等于默认的UUID就是客户用户 | 425 | // 不等于默认的UUID就是客户用户 |
411 | if (!EntityId.NULL_UUID.toString().equals(customerId)) { | 426 | if (!EntityId.NULL_UUID.toString().equals(customerId)) { |
@@ -416,18 +431,19 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -416,18 +431,19 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
416 | } | 431 | } |
417 | 432 | ||
418 | List<TkDeviceEntity> orgDevices = | 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 | List<DeviceDTO> result = | 438 | List<DeviceDTO> result = |
422 | orgDevices.stream() | 439 | orgDevices.stream() |
423 | - .filter(t -> typeFilter.isEmpty() || typeFilter.contains(t.getDeviceType())) | 440 | + .filter(t -> typeFilter.isEmpty() || typeFilter.contains(t.getDeviceType())) |
424 | .map(device -> device.getDTO(DeviceDTO.class)) | 441 | .map(device -> device.getDTO(DeviceDTO.class)) |
425 | .collect(Collectors.toList()); | 442 | .collect(Collectors.toList()); |
426 | 443 | ||
427 | - | ||
428 | if (null != customerDevices && customerDevices.size() > 0) { | 444 | if (null != customerDevices && customerDevices.size() > 0) { |
429 | List<DeviceDTO> customerAllDevice = new ArrayList<>(); | 445 | List<DeviceDTO> customerAllDevice = new ArrayList<>(); |
430 | - for(DeviceDTO item: result){ | 446 | + for (DeviceDTO item : result) { |
431 | if (customerDevices.contains(item.getTbDeviceId())) { | 447 | if (customerDevices.contains(item.getTbDeviceId())) { |
432 | customerAllDevice.add(item); | 448 | customerAllDevice.add(item); |
433 | } | 449 | } |
@@ -480,12 +496,10 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -480,12 +496,10 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
480 | return null; | 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 | List<TkTriggerEntity> triggers = | 503 | List<TkTriggerEntity> triggers = |
490 | triggerMapper.selectList( | 504 | triggerMapper.selectList( |
491 | new QueryWrapper<TkTriggerEntity>() | 505 | new QueryWrapper<TkTriggerEntity>() |
@@ -493,27 +507,51 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | @@ -493,27 +507,51 @@ public class TkSceneLinkageServiceImpl extends AbstractBaseService<SceneLinkageM | ||
493 | .eq(TkTriggerEntity::getTenantId, tenantId) | 507 | .eq(TkTriggerEntity::getTenantId, tenantId) |
494 | .eq(TkTriggerEntity::getTriggerType, TriggerTypeEnum.DEVICE_TRIGGER) | 508 | .eq(TkTriggerEntity::getTriggerType, TriggerTypeEnum.DEVICE_TRIGGER) |
495 | .in(TkTriggerEntity::getSceneLinkageId, enableIds)); | 509 | .in(TkTriggerEntity::getSceneLinkageId, enableIds)); |
496 | - | ||
497 | triggers.forEach( | 510 | triggers.forEach( |
498 | trigger -> { | 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 | return null; | 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 | Map<String, String> projects = new HashMap<>(); | 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 | Map<String, Map> engineConfig = new HashMap<>(); | 556 | Map<String, Map> engineConfig = new HashMap<>(); |
519 | engineConfig.put("scenes", matchedDevices); | 557 | engineConfig.put("scenes", matchedDevices); |
@@ -525,21 +563,38 @@ List<TkDeviceProfileEntity> profiles = profileMapper.selectList(new LambdaQueryW | @@ -525,21 +563,38 @@ List<TkDeviceProfileEntity> profiles = profileMapper.selectList(new LambdaQueryW | ||
525 | return JacksonUtil.convertValue(engineConfig, JsonNode.class); | 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 | * @param resultMap 缓存设备和场景联动映射结果的集合 | 585 | * @param resultMap 缓存设备和场景联动映射结果的集合 |
534 | - * @param devices 设备主键集合 | 586 | + * @param postfixIds 设备或产品主键集合 |
535 | * @param scenId 场景联动主键 | 587 | * @param scenId 场景联动主键 |
536 | */ | 588 | */ |
537 | private void deviceSceneMap( | 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 | resultMap.put(deviceId, scenes); | 599 | resultMap.put(deviceId, scenes); |
545 | } | 600 | } |
@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
5 | import lombok.RequiredArgsConstructor; | 5 | import lombok.RequiredArgsConstructor; |
6 | import org.springframework.stereotype.Service; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; | 8 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; |
9 | import org.thingsboard.server.dao.yunteng.mapper.TriggerMapper; | 9 | import org.thingsboard.server.dao.yunteng.mapper.TriggerMapper; |
10 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; | 10 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; |
@@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; | @@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; | ||
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | import org.apache.ibatis.annotations.Mapper; | 4 | import org.apache.ibatis.annotations.Mapper; |
5 | import org.apache.ibatis.annotations.Param; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkDoActionEntity; | 7 | import org.thingsboard.server.dao.yunteng.entities.TkDoActionEntity; |
8 | 8 | ||
9 | import java.util.List; | 9 | import java.util.List; |
@@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; | @@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; | ||
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | import org.apache.ibatis.annotations.Mapper; | 4 | import org.apache.ibatis.annotations.Mapper; |
5 | import org.apache.ibatis.annotations.Param; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkDoConditionEntity; | 7 | import org.thingsboard.server.dao.yunteng.entities.TkDoConditionEntity; |
8 | 8 | ||
9 | import java.util.List; | 9 | import java.util.List; |
@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||
4 | import com.baomidou.mybatisplus.core.metadata.IPage; | 4 | import com.baomidou.mybatisplus.core.metadata.IPage; |
5 | import org.apache.ibatis.annotations.Mapper; | 5 | import org.apache.ibatis.annotations.Mapper; |
6 | import org.apache.ibatis.annotations.Param; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkSceneLinkageEntity; | 8 | import org.thingsboard.server.dao.yunteng.entities.TkSceneLinkageEntity; |
9 | 9 | ||
10 | import java.util.Map; | 10 | import java.util.Map; |
@@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; | @@ -3,7 +3,7 @@ package org.thingsboard.server.dao.yunteng.mapper; | ||
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
4 | import org.apache.ibatis.annotations.Mapper; | 4 | import org.apache.ibatis.annotations.Mapper; |
5 | import org.apache.ibatis.annotations.Param; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; | 7 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; |
8 | 8 | ||
9 | import java.util.List; | 9 | import java.util.List; |
1 | package org.thingsboard.server.dao.yunteng.service; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkDoActionEntity; | 4 | import org.thingsboard.server.dao.yunteng.entities.TkDoActionEntity; |
5 | 5 | ||
6 | import java.util.List; | 6 | import java.util.List; |
1 | package org.thingsboard.server.dao.yunteng.service; | 1 | package org.thingsboard.server.dao.yunteng.service; |
2 | import com.fasterxml.jackson.databind.JsonNode; | 2 | import com.fasterxml.jackson.databind.JsonNode; |
3 | import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; | 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 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | 5 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
6 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; | 6 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
7 | import org.thingsboard.server.dao.yunteng.entities.TkSceneLinkageEntity; | 7 | import org.thingsboard.server.dao.yunteng.entities.TkSceneLinkageEntity; |
1 | package org.thingsboard.server.dao.yunteng.service; | 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 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; | 4 | import org.thingsboard.server.dao.yunteng.entities.TkTriggerEntity; |
5 | 5 | ||
6 | import java.util.List; | 6 | import java.util.List; |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | ||
4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.DoActionMapper"> | 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 | <result property="id" column="id"/> | 6 | <result property="id" column="id"/> |
7 | <result property="deviceId" column="device_id" | 7 | <result property="deviceId" column="device_id" |
8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> | 8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | ||
4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.DoConditionMapper"> | 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 | <result property="id" column="id"/> | 6 | <result property="id" column="id"/> |
7 | <result property="entityId" column="entity_id" | 7 | <result property="entityId" column="entity_id" |
8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> | 8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | ||
4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.SceneLinkageMapper"> | 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 | <result property="id" column="id"/> | 6 | <result property="id" column="id"/> |
7 | <result property="name" column="name"/> | 7 | <result property="name" column="name"/> |
8 | <result property="organizationId" column="organization_id"/> | 8 | <result property="organizationId" column="organization_id"/> |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
3 | 3 | ||
4 | <mapper namespace="org.thingsboard.server.dao.yunteng.mapper.TriggerMapper"> | 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 | <result property="id" column="id"/> | 6 | <result property="id" column="id"/> |
7 | <result property="entityId" column="entity_id" | 7 | <result property="entityId" column="entity_id" |
8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> | 8 | typeHandler="org.thingsboard.server.dao.yunteng.mapper.ListStringTypeHandler"/> |
@@ -44,8 +44,8 @@ | @@ -44,8 +44,8 @@ | ||
44 | <scope>provided</scope> | 44 | <scope>provided</scope> |
45 | </dependency> | 45 | </dependency> |
46 | <dependency> | 46 | <dependency> |
47 | - <groupId>org.thingsboard.common</groupId> | ||
48 | - <artifactId>dao-api</artifactId> | 47 | + <groupId>org.thingsboard</groupId> |
48 | + <artifactId>dao</artifactId> | ||
49 | <scope>provided</scope> | 49 | <scope>provided</scope> |
50 | </dependency> | 50 | </dependency> |
51 | <dependency> | 51 | <dependency> |
@@ -56,6 +56,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; | @@ -56,6 +56,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; | ||
56 | import org.thingsboard.server.dao.tenant.TenantService; | 56 | import org.thingsboard.server.dao.tenant.TenantService; |
57 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 57 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
58 | import org.thingsboard.server.dao.user.UserService; | 58 | import org.thingsboard.server.dao.user.UserService; |
59 | +import org.thingsboard.server.dao.yunteng.service.TkDeviceService; | ||
59 | 60 | ||
60 | import java.util.Set; | 61 | import java.util.Set; |
61 | import java.util.function.BiConsumer; | 62 | import java.util.function.BiConsumer; |
@@ -285,4 +286,8 @@ public interface TbContext { | @@ -285,4 +286,8 @@ public interface TbContext { | ||
285 | void removeListeners(); | 286 | void removeListeners(); |
286 | 287 | ||
287 | TenantProfile getTenantProfile(); | 288 | TenantProfile getTenantProfile(); |
289 | + | ||
290 | + | ||
291 | + //Thingskit function | ||
292 | + TkDeviceService getTkDeviceService(); | ||
288 | } | 293 | } |
@@ -16,12 +16,13 @@ | @@ -16,12 +16,13 @@ | ||
16 | package org.thingsboard.rule.engine.rpc; | 16 | package org.thingsboard.rule.engine.rpc; |
17 | 17 | ||
18 | import com.datastax.oss.driver.api.core.uuid.Uuids; | 18 | import com.datastax.oss.driver.api.core.uuid.Uuids; |
19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
19 | import com.google.gson.Gson; | 20 | import com.google.gson.Gson; |
20 | import com.google.gson.JsonElement; | 21 | import com.google.gson.JsonElement; |
21 | import com.google.gson.JsonObject; | 22 | import com.google.gson.JsonObject; |
22 | import com.google.gson.JsonParser; | 23 | import com.google.gson.JsonParser; |
23 | import lombok.extern.slf4j.Slf4j; | 24 | import lombok.extern.slf4j.Slf4j; |
24 | -import org.springframework.util.StringUtils; | 25 | +import org.thingsboard.common.util.JacksonUtil; |
25 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; | 26 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; |
26 | import org.thingsboard.rule.engine.api.RuleNode; | 27 | import org.thingsboard.rule.engine.api.RuleNode; |
27 | import org.thingsboard.rule.engine.api.TbContext; | 28 | import org.thingsboard.rule.engine.api.TbContext; |
@@ -31,9 +32,17 @@ import org.thingsboard.rule.engine.api.TbNodeException; | @@ -31,9 +32,17 @@ import org.thingsboard.rule.engine.api.TbNodeException; | ||
31 | import org.thingsboard.rule.engine.api.TbRelationTypes; | 32 | import org.thingsboard.rule.engine.api.TbRelationTypes; |
32 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; | 33 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
33 | import org.thingsboard.server.common.data.DataConstants; | 34 | import org.thingsboard.server.common.data.DataConstants; |
35 | +import org.thingsboard.server.common.data.Device; | ||
34 | import org.thingsboard.server.common.data.EntityType; | 36 | import org.thingsboard.server.common.data.EntityType; |
37 | +import org.thingsboard.server.common.data.StringUtils; | ||
35 | import org.thingsboard.server.common.data.id.DeviceId; | 38 | import org.thingsboard.server.common.data.id.DeviceId; |
36 | import org.thingsboard.server.common.data.plugin.ComponentType; | 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 | import org.thingsboard.server.common.msg.TbMsg; | 46 | import org.thingsboard.server.common.msg.TbMsg; |
38 | 47 | ||
39 | import java.util.Random; | 48 | import java.util.Random; |
@@ -98,12 +107,41 @@ public class TbSendRPCRequestNode implements TbNode { | @@ -98,12 +107,41 @@ public class TbSendRPCRequestNode implements TbNode { | ||
98 | String params = parseJsonData(json.get("params")); | 107 | String params = parseJsonData(json.get("params")); |
99 | String additionalInfo = parseJsonData(json.get(DataConstants.ADDITIONAL_INFO)); | 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 | RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder() | 139 | RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder() |
102 | .oneway(oneway) | 140 | .oneway(oneway) |
103 | .method(json.get("method").getAsString()) | 141 | .method(json.get("method").getAsString()) |
104 | .body(params) | 142 | .body(params) |
105 | .tenantId(ctx.getTenantId()) | 143 | .tenantId(ctx.getTenantId()) |
106 | - .deviceId(new DeviceId(msg.getOriginator().getId())) | 144 | + .deviceId(netEnableId) |
107 | .requestId(requestId) | 145 | .requestId(requestId) |
108 | .requestUUID(requestUUID) | 146 | .requestUUID(requestUUID) |
109 | .originServiceId(originServiceId) | 147 | .originServiceId(originServiceId) |
@@ -3,12 +3,10 @@ package org.thingsboard.rule.engine.yunteng.scene; | @@ -3,12 +3,10 @@ package org.thingsboard.rule.engine.yunteng.scene; | ||
3 | 3 | ||
4 | import com.fasterxml.jackson.databind.JsonNode; | 4 | import com.fasterxml.jackson.databind.JsonNode; |
5 | import com.fasterxml.jackson.databind.node.ObjectNode; | 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 | import java.util.concurrent.ConcurrentHashMap; | 7 | import java.util.concurrent.ConcurrentHashMap; |
11 | import java.util.concurrent.ExecutionException; | 8 | import java.util.concurrent.ExecutionException; |
9 | +import java.util.concurrent.atomic.AtomicBoolean; | ||
12 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
13 | import org.apache.commons.lang3.StringUtils; | 11 | import org.apache.commons.lang3.StringUtils; |
14 | import org.jetbrains.annotations.NotNull; | 12 | import org.jetbrains.annotations.NotNull; |
@@ -23,7 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceId; | @@ -23,7 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceId; | ||
23 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | 21 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
24 | import org.thingsboard.server.common.data.yunteng.dto.ActionAlarmDTO; | 22 | import org.thingsboard.server.common.data.yunteng.dto.ActionAlarmDTO; |
25 | import org.thingsboard.server.common.data.yunteng.dto.AlarmInfoDTO; | 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 | import org.thingsboard.server.common.data.yunteng.enums.*; | 25 | import org.thingsboard.server.common.data.yunteng.enums.*; |
28 | import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil; | 26 | import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil; |
29 | import org.thingsboard.server.common.data.yunteng.utils.SpringBeanUtils; | 27 | import org.thingsboard.server.common.data.yunteng.utils.SpringBeanUtils; |
@@ -86,7 +84,16 @@ class ReactState { | @@ -86,7 +84,16 @@ class ReactState { | ||
86 | this.ytDeviceService = SpringBeanUtils.getBean(TkDeviceService.class); | 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 | throws ExecutionException, InterruptedException { | 97 | throws ExecutionException, InterruptedException { |
91 | 98 | ||
92 | StringBuilder detail = new StringBuilder(); | 99 | StringBuilder detail = new StringBuilder(); |
@@ -94,74 +101,120 @@ class ReactState { | @@ -94,74 +101,120 @@ class ReactState { | ||
94 | ctx.tellSuccess(msg); | 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 | for (TkDoActionEntity item : actions) { | 198 | for (TkDoActionEntity item : actions) { |
155 | if (ActionTypeEnum.MSG_NOTIFY.equals(item.getOutTarget())) { | 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 | } else { | 205 | } else { |
161 | ctx.tellSuccess(msg); | 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 | protected TriggerState getOrCreateTriggerState( | 218 | protected TriggerState getOrCreateTriggerState( |
166 | TriggerDTO trigger, String profileId, String deviceId) { | 219 | TriggerDTO trigger, String profileId, String deviceId) { |
167 | String triggerId = trigger.getId(); | 220 | String triggerId = trigger.getId(); |
@@ -173,6 +226,7 @@ class ReactState { | @@ -173,6 +226,7 @@ class ReactState { | ||
173 | || (trigger.getEntityType().equals(ScopeEnum.ALL) | 226 | || (trigger.getEntityType().equals(ScopeEnum.ALL) |
174 | && trigger.getDeviceProfileId().equals(profileId))) { | 227 | && trigger.getDeviceProfileId().equals(profileId))) { |
175 | TriggerState state = createTriggerState(deviceId, trigger.getTriggerCondition()); | 228 | TriggerState state = createTriggerState(deviceId, trigger.getTriggerCondition()); |
229 | + log.error(String.format("新建设备【%s】的触发器",deviceId)); | ||
176 | triggerState.put(cacheKey, state); | 230 | triggerState.put(cacheKey, state); |
177 | return state; | 231 | return state; |
178 | } | 232 | } |
@@ -224,7 +278,7 @@ class ReactState { | @@ -224,7 +278,7 @@ class ReactState { | ||
224 | || (condition.getEntityType().equals(ScopeEnum.ALL) | 278 | || (condition.getEntityType().equals(ScopeEnum.ALL) |
225 | && condition.getDeviceProfileId().equals(profileId))) { | 279 | && condition.getDeviceProfileId().equals(profileId))) { |
226 | TriggerState state = createTriggerState(deviceId, condition.getTriggerCondition()); | 280 | TriggerState state = createTriggerState(deviceId, condition.getTriggerCondition()); |
227 | - triggerState.put(cacheKey, state); | 281 | + conditionState.put(cacheKey, state); |
228 | return state; | 282 | return state; |
229 | } | 283 | } |
230 | return null; | 284 | return null; |
@@ -242,10 +296,6 @@ class ReactState { | @@ -242,10 +296,6 @@ class ReactState { | ||
242 | } | 296 | } |
243 | 297 | ||
244 | private void pushMsg(TbContext ctx, TbMsg msg, TkDoActionEntity action, String detail) { | 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 | switch (action.getOutTarget()) { | 299 | switch (action.getOutTarget()) { |
250 | case DEVICE_OUT: | 300 | case DEVICE_OUT: |
251 | List<String> rpcDevices = action.getDeviceId(); | 301 | List<String> rpcDevices = action.getDeviceId(); |
@@ -358,7 +408,7 @@ class ReactState { | @@ -358,7 +408,7 @@ class ReactState { | ||
358 | private void clearAlarm(TbContext ctx, TbMsg msg, String deviceId, String key) | 408 | private void clearAlarm(TbContext ctx, TbMsg msg, String deviceId, String key) |
359 | throws ExecutionException, InterruptedException { | 409 | throws ExecutionException, InterruptedException { |
360 | TriggerState clearState = getOrCreateClearState(deviceId, key); | 410 | TriggerState clearState = getOrCreateClearState(deviceId, key); |
361 | - if (clearState != null && clearState.process(ctx, msg)) { | 411 | + if (clearState != null && clearState.process(ctx, msg,true)) { |
362 | ctx.getAlarmService() | 412 | ctx.getAlarmService() |
363 | .clearAlarmForResult( | 413 | .clearAlarmForResult( |
364 | ctx.getTenantId(), | 414 | ctx.getTenantId(), |
1 | /** | 1 | /** |
2 | * Copyright © 2016-2022 The Thingsboard Authors | 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 | * limitations under the License. | 12 | * limitations under the License. |
15 | */ | 13 | */ |
16 | package org.thingsboard.rule.engine.yunteng.scene; | 14 | package org.thingsboard.rule.engine.yunteng.scene; |
17 | 15 | ||
18 | import com.fasterxml.jackson.databind.ObjectMapper; | 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 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
21 | import org.thingsboard.rule.engine.api.*; | 21 | import org.thingsboard.rule.engine.api.*; |
22 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; | 22 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
@@ -24,129 +24,116 @@ import org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNodeConfig; | @@ -24,129 +24,116 @@ import org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNodeConfig; | ||
24 | import org.thingsboard.rule.engine.profile.SnapshotUpdate; | 24 | import org.thingsboard.rule.engine.profile.SnapshotUpdate; |
25 | import org.thingsboard.rule.engine.yunteng.utils.TriggerRuleState; | 25 | import org.thingsboard.rule.engine.yunteng.utils.TriggerRuleState; |
26 | import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; | 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 | import org.thingsboard.server.common.data.plugin.ComponentType; | 27 | import org.thingsboard.server.common.data.plugin.ComponentType; |
28 | +import org.thingsboard.server.common.data.yunteng.dto.scene.RuleFilterDTO; | ||
30 | import org.thingsboard.server.common.msg.TbMsg; | 29 | import org.thingsboard.server.common.msg.TbMsg; |
31 | import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; | 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 | @Slf4j | 32 | @Slf4j |
43 | @RuleNode( | 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 | public class TbSceneReactNode implements TbNode { | 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 | package org.thingsboard.rule.engine.yunteng.scene; | 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 | import java.util.HashMap; | 4 | import java.util.HashMap; |
12 | import java.util.List; | 5 | import java.util.List; |
13 | import java.util.Map; | 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 | @Data | 11 | @Data |
16 | public class TbSceneReactNodeConfig implements NodeConfiguration<TbSceneReactNodeConfig> { | 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 | * Copyright © 2016-2022 The Thingsboard Authors | 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 | * limitations under the License. | 12 | * limitations under the License. |
15 | */ | 13 | */ |
16 | package org.thingsboard.rule.engine.yunteng.scene; | 14 | package org.thingsboard.rule.engine.yunteng.scene; |
17 | 15 | ||
18 | import com.google.gson.JsonParser; | 16 | import com.google.gson.JsonParser; |
17 | +import java.util.*; | ||
18 | +import java.util.concurrent.ExecutionException; | ||
19 | +import java.util.function.BiFunction; | ||
19 | import lombok.Data; | 20 | import lombok.Data; |
21 | +import lombok.Getter; | ||
20 | import lombok.extern.slf4j.Slf4j; | 22 | import lombok.extern.slf4j.Slf4j; |
21 | import org.thingsboard.rule.engine.api.TbContext; | 23 | import org.thingsboard.rule.engine.api.TbContext; |
22 | import org.thingsboard.rule.engine.profile.*; | 24 | import org.thingsboard.rule.engine.profile.*; |
@@ -36,243 +38,312 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; | @@ -36,243 +38,312 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; | ||
36 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; | 38 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
37 | import org.thingsboard.server.dao.sql.query.EntityKeyMapping; | 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 | @Data | 41 | @Data |
44 | @Slf4j | 42 | @Slf4j |
45 | class TriggerState { | 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 | } |