Commit 45c5f87b16288c3eff29a3f9d7fb61e8990d37ce

Authored by xp.Huang
2 parents 6305eb57 d7e65dae

Merge branch 'master_dev' into 'master'

refactor: 命令下发逻辑调整

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