Browse Source

Merge remote-tracking branch 'upstream/master' into handle-null-values-in-proto

pull/5007/head
Volodymyr Babak 5 years ago
parent
commit
eced5a49fa
  1. 3
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 26
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  3. 99
      application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
  4. 55
      application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java
  5. 108
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  6. 3
      application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java
  7. 48
      application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
  8. 11
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  10. 67
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  11. 9
      application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
  12. 8
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  13. 3
      application/src/main/resources/logback.xml
  14. 8
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java
  15. 6
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java
  16. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  17. 104
      application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java
  18. 8
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java
  19. 8
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  20. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java
  21. 41
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java
  22. 1
      common/queue/src/main/proto/queue.proto
  23. 20
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java
  24. 2
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  25. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  26. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java
  27. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java
  28. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java
  29. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  30. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  31. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  32. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  33. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java
  34. 164
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  35. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcResponseBody.java
  36. 23
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java
  37. 9
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  38. 5
      common/transport/mqtt/pom.xml
  39. 17
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  40. 11
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
  41. 35
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
  42. 61
      common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandlerTest.java
  43. 2
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java
  44. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  45. 12
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  46. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java
  47. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java
  48. 2
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  49. 7
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  50. 2
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
  51. 4
      dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java
  52. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
  53. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
  54. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
  55. 6
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  56. 34
      rest-client/src/main/resources/logback.xml
  57. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  58. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
  59. 8
      ui-ngx/src/app/core/api/widget-subscription.ts
  60. 4
      ui-ngx/src/app/core/http/device.service.ts
  61. 4
      ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
  62. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts
  63. 5
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html
  64. 19
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts
  65. 2
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts
  66. 6
      ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts
  67. 6
      ui-ngx/src/app/modules/home/components/profile/device/common/device-profile-common.module.ts
  68. 55
      ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html
  69. 13
      ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.ts
  70. 40
      ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html
  71. 194
      ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts
  72. 2
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html
  73. 28
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts
  74. 11
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html
  75. 8
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts
  76. 20
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts
  77. 4
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts
  78. 5
      ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html
  79. 20
      ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts
  80. 6
      ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts
  81. 6
      ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts
  82. 2
      ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts
  83. 9
      ui-ngx/src/app/shared/models/time/time.models.ts
  84. 6
      ui-ngx/src/assets/locale/locale.constant-cs_CZ.json
  85. 2
      ui-ngx/src/assets/locale/locale.constant-de_DE.json
  86. 2
      ui-ngx/src/assets/locale/locale.constant-el_GR.json
  87. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  88. 2
      ui-ngx/src/assets/locale/locale.constant-es_ES.json
  89. 2
      ui-ngx/src/assets/locale/locale.constant-fa_IR.json
  90. 2
      ui-ngx/src/assets/locale/locale.constant-fr_FR.json
  91. 2
      ui-ngx/src/assets/locale/locale.constant-it_IT.json
  92. 2
      ui-ngx/src/assets/locale/locale.constant-ja_JA.json
  93. 2
      ui-ngx/src/assets/locale/locale.constant-ka_GE.json
  94. 2
      ui-ngx/src/assets/locale/locale.constant-ko_KR.json
  95. 2
      ui-ngx/src/assets/locale/locale.constant-lv_LV.json
  96. 2
      ui-ngx/src/assets/locale/locale.constant-pt_BR.json
  97. 4
      ui-ngx/src/assets/locale/locale.constant-ro_RO.json
  98. 2
      ui-ngx/src/assets/locale/locale.constant-ru_RU.json
  99. 2
      ui-ngx/src/assets/locale/locale.constant-sl_SI.json
  100. 2
      ui-ngx/src/assets/locale/locale.constant-tr_TR.json

3
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -265,7 +265,8 @@ public class ActorSystemContext {
@Getter @Getter
private SmsSenderFactory smsSenderFactory; private SmsSenderFactory smsSenderFactory;
@Autowired @Lazy
@Autowired(required = false)
@Getter @Getter
private ClaimDevicesService claimDevicesService; private ClaimDevicesService claimDevicesService;

26
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.actors.device; package org.thingsboard.server.actors.device;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
@ -35,6 +36,7 @@ import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -379,11 +381,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
private void reportSessionOpen() { private void reportSessionOpen() {
systemContext.getDeviceStateService().onDeviceConnect(deviceId); systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId);
} }
private void reportSessionClose() { private void reportSessionClose() {
systemContext.getDeviceStateService().onDeviceDisconnect(deviceId); systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId);
} }
private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
@ -510,10 +512,20 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null; boolean success = requestMd != null;
if (success) { if (success) {
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), boolean hasError = StringUtils.isNotEmpty(responseMsg.getError());
responseMsg.getPayload(), null)); String payload = hasError ? responseMsg.getError() : responseMsg.getPayload();
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(
new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
payload, null));
if (requestMd.getMsg().getMsg().isPersisted()) { if (requestMd.getMsg().getMsg().isPersisted()) {
systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.SUCCESSFUL, JacksonUtil.toJsonNode(responseMsg.getPayload())); RpcStatus status = hasError ? RpcStatus.FAILED : RpcStatus.SUCCESSFUL;
JsonNode response;
try {
response = JacksonUtil.toJsonNode(payload);
} catch (IllegalArgumentException e) {
response = JacksonUtil.newObjectNode().put("error", payload);
}
systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), status, response);
} }
} else { } else {
log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
@ -578,7 +590,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (sessions.size() == 1) { if (sessions.size() == 1) {
reportSessionOpen(); reportSessionOpen();
} }
systemContext.getDeviceStateService().onDeviceActivity(deviceId, System.currentTimeMillis()); systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, System.currentTimeMillis());
dumpSessions(); dumpSessions();
} else if (msg.getEvent() == SessionEvent.CLOSED) { } else if (msg.getEvent() == SessionEvent.CLOSED) {
log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
@ -608,7 +620,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (subscriptionInfo.getRpcSubscription()) { if (subscriptionInfo.getRpcSubscription()) {
rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
} }
systemContext.getDeviceStateService().onDeviceActivity(deviceId, subscriptionInfo.getLastActivityTime()); systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, subscriptionInfo.getLastActivityTime());
dumpSessions(); dumpSessions();
} }

99
application/src/main/java/org/thingsboard/server/controller/RpcController.java → application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java

@ -16,7 +16,6 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -26,13 +25,12 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
@ -59,20 +57,15 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
/** /**
* Created by ashvayka on 22.03.18. * Created by ashvayka on 22.03.18.
*/ */
@RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping(TbUrlConstants.RPC_URL_PREFIX)
@Slf4j @Slf4j
public class RpcController extends BaseController { public abstract class AbstractRpcController extends BaseController {
protected final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired @Autowired
private TbCoreDeviceRpcService deviceRpcService; private TbCoreDeviceRpcService deviceRpcService;
@ -81,75 +74,15 @@ public class RpcController extends BaseController {
private AccessValidator accessValidator; private AccessValidator accessValidator;
@Value("${server.rest.server_side_rpc.min_timeout:5000}") @Value("${server.rest.server_side_rpc.min_timeout:5000}")
private long minTimeout; protected long minTimeout;
@Value("${server.rest.server_side_rpc.default_timeout:10000}") @Value("${server.rest.server_side_rpc.default_timeout:10000}")
private long defaultTimeout; protected long defaultTimeout;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
@ResponseBody
public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
RpcId rpcId = new RpcId(UUID.fromString(strRpc));
return checkRpcId(rpcId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET)
@ResponseBody
public PageData<Rpc> getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam RpcStatus rpcStatus,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("DeviceId", strDeviceId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc)));
} catch (Exception e) {
throw handleException(e);
}
}
private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { protected DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody, HttpStatus timeoutStatus, HttpStatus noActiveConnectionStatus) throws ThingsboardException {
try { try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); JsonNode rpcRequestBody = JacksonUtil.toJsonNode(requestBody);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), JacksonUtil.toString(rpcRequestBody.get("params")));
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId(); TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>(); final DeferredResult<ResponseEntity> response = new DeferredResult<>();
@ -157,7 +90,7 @@ public class RpcController extends BaseController {
long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout); long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID(); UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID();
boolean persisted = rpcRequestBody.has(DataConstants.PERSISTENT) && rpcRequestBody.get(DataConstants.PERSISTENT).asBoolean(); boolean persisted = rpcRequestBody.has(DataConstants.PERSISTENT) && rpcRequestBody.get(DataConstants.PERSISTENT).asBoolean();
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() { accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) { public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID, ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID,
@ -168,7 +101,7 @@ public class RpcController extends BaseController {
body, body,
persisted persisted
); );
deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser); deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse, timeoutStatus, noActiveConnectionStatus), currentUser);
} }
@Override @Override
@ -184,12 +117,12 @@ public class RpcController extends BaseController {
} }
})); }));
return response; return response;
} catch (IOException ioe) { } catch (IllegalArgumentException ioe) {
throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} }
} }
public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) { public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response, HttpStatus timeoutStatus, HttpStatus noActiveConnectionStatus) {
Optional<RpcError> rpcError = response.getError(); Optional<RpcError> rpcError = response.getError();
DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter(); DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter();
if (rpcError.isPresent()) { if (rpcError.isPresent()) {
@ -197,13 +130,13 @@ public class RpcController extends BaseController {
RpcError error = rpcError.get(); RpcError error = rpcError.get();
switch (error) { switch (error) {
case TIMEOUT: case TIMEOUT:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
break; break;
case NO_ACTIVE_CONNECTION: case NO_ACTIVE_CONNECTION:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT)); responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus));
break; break;
default: default:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
break; break;
} }
} else { } else {
@ -212,8 +145,8 @@ public class RpcController extends BaseController {
String data = responseData.get(); String data = responseData.get();
try { try {
logRpcCall(rpcRequest, rpcError, null); logRpcCall(rpcRequest, rpcError, null);
responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); responseWriter.setResult(new ResponseEntity<>(JacksonUtil.toJsonNode(data), HttpStatus.OK));
} catch (IOException e) { } catch (IllegalArgumentException e) {
log.debug("Failed to decode device response: {}", data, e); log.debug("Failed to decode device response: {}", data, e);
logRpcCall(rpcRequest, rpcError, e); logRpcCall(rpcRequest, rpcError, e);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));

55
application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping(TbUrlConstants.RPC_V1_URL_PREFIX)
@Slf4j
public class RpcV1Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT);
}
}

108
application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java

@ -0,0 +1,108 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping(TbUrlConstants.RPC_V2_URL_PREFIX)
@Slf4j
public class RpcV2Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT);
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
@ResponseBody
public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
RpcId rpcId = new RpcId(UUID.fromString(strRpc));
return checkRpcId(rpcId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET)
@ResponseBody
public PageData<Rpc> getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam RpcStatus rpcStatus,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("DeviceId", strDeviceId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc)));
} catch (Exception e) {
throw handleException(e);
}
}
}

3
application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java

@ -20,5 +20,6 @@ package org.thingsboard.server.controller;
*/ */
public class TbUrlConstants { public class TbUrlConstants {
public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry"; public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry";
public static final String RPC_URL_PREFIX = "/api/plugins/rpc"; public static final String RPC_V1_URL_PREFIX = "/api/plugins/rpc";
public static final String RPC_V2_URL_PREFIX = "/api/rpc";
} }

48
dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java → application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.device; package org.thingsboard.server.service.device;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -26,6 +28,7 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -37,12 +40,17 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.ClaimDataInfo;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.claim.ClaimData; import org.thingsboard.server.dao.device.claim.ClaimData;
import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.device.claim.ReclaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -54,6 +62,7 @@ import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CA
@Service @Service
@Slf4j @Slf4j
@TbCoreComponent
public class ClaimDevicesServiceImpl implements ClaimDevicesService { public class ClaimDevicesServiceImpl implements ClaimDevicesService {
private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed"; private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed";
@ -65,6 +74,8 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
@Autowired @Autowired
private AttributesService attributesService; private AttributesService attributesService;
@Autowired @Autowired
private RuleEngineTelemetryService telemetryService;
@Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@ -172,10 +183,23 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
if (isAllowedClaimingByDefault) { if (isAllowedClaimingByDefault) {
return Futures.immediateFuture(new ReclaimResult(unassignedCustomer)); return Futures.immediateFuture(new ReclaimResult(unassignedCustomer));
} }
return Futures.transform(attributesService.save( SettableFuture<ReclaimResult> result = SettableFuture.create();
telemetryService.saveAndNotify(
tenantId, device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList( tenantId, device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(
new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis()) new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis())
)), result -> new ReclaimResult(unassignedCustomer), MoreExecutors.directExecutor()); ),
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.set(new ReclaimResult(unassignedCustomer));
}
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
return result;
} }
cacheEviction(device.getId()); cacheEviction(device.getId());
return Futures.immediateFuture(new ReclaimResult(null)); return Futures.immediateFuture(new ReclaimResult(null));
@ -198,12 +222,24 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return systemDurationMs; return systemDurationMs;
} }
private ListenableFuture<List<Void>> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) { private ListenableFuture<Void> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) {
if (data.isFromCache()) { if (data.isFromCache()) {
cache.evict(data.getKey()); cache.evict(data.getKey());
} }
return attributesService.removeAll(device.getTenantId(), SettableFuture<Void> result = SettableFuture.create();
device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME)); telemetryService.deleteAndNotify(device.getTenantId(),
device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.set(tmp);
}
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
return result;
} }
private void cacheEviction(DeviceId deviceId) { private void cacheEviction(DeviceId deviceId) {

11
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@Service(value = "customOAuth2ClientMapper") @Service(value = "customOAuth2ClientMapper")
@ -40,6 +42,15 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@PostConstruct
public void init() {
// Register time module to parse Instant objects.
// com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
// Java 8 date/time type `java.time.Instant` not supported by default:
// add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
json.registerModule(new JavaTimeModule());
}
@Override @Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig(); OAuth2MapperConfig config = registration.getMapperConfig();

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@ -42,6 +43,7 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
@Slf4j
@Component(value = "oauth2AuthenticationSuccessHandler") @Component(value = "oauth2AuthenticationSuccessHandler")
public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@ -99,6 +101,8 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
clearAuthenticationAttributes(request, response); clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
} catch (Exception e) { } catch (Exception e) {
log.debug("Error occurred during processing authentication success result. " +
"request [{}], response [{}], authentication [{}]", request, response, authentication, e);
clearAuthenticationAttributes(request, response); clearAuthenticationAttributes(request, response);
String errorPrefix; String errorPrefix;
if (!StringUtils.isEmpty(callbackUrlScheme)) { if (!StringUtils.isEmpty(callbackUrlScheme)) {

67
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -137,7 +137,6 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
private ExecutorService deviceStateExecutor; private ExecutorService deviceStateExecutor;
private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>(); private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>();
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, Long> deviceLastSavedActivity = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>(); final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
@ -192,7 +191,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
@Override @Override
public void onDeviceConnect(DeviceId deviceId) { public void onDeviceConnect(TenantId tenantId, DeviceId deviceId) {
log.trace("on Device Connect [{}]", deviceId.getId()); log.trace("on Device Connect [{}]", deviceId.getId());
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
@ -200,23 +199,23 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
save(deviceId, LAST_CONNECT_TIME, ts); save(deviceId, LAST_CONNECT_TIME, ts);
pushRuleEngineMessage(stateData, CONNECT_EVENT); pushRuleEngineMessage(stateData, CONNECT_EVENT);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
public void onDeviceActivity(DeviceId deviceId, long lastReportedActivity) { public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivity) {
log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity); log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity);
long lastSavedActivity = deviceLastSavedActivity.getOrDefault(deviceId, 0L); final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) { if (lastReportedActivity > 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) {
final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
updateActivityState(deviceId, stateData, lastReportedActivity); updateActivityState(deviceId, stateData, lastReportedActivity);
} }
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) { void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) {
log.trace("updateActivityState - fetched state {} for device {}, lastReportedActivity {}", stateData, deviceId, lastReportedActivity); log.trace("updateActivityState - fetched state {} for device {}, lastReportedActivity {}", stateData, deviceId, lastReportedActivity);
if (stateData != null) { if (stateData != null) {
save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity); save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity);
deviceLastSavedActivity.put(deviceId, lastReportedActivity);
DeviceState state = stateData.getState(); DeviceState state = stateData.getState();
state.setLastActivityTime(lastReportedActivity); state.setLastActivityTime(lastReportedActivity);
if (!state.isActive()) { if (!state.isActive()) {
@ -225,21 +224,23 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
pushRuleEngineMessage(stateData, ACTIVITY_EVENT); pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
} }
} else { } else {
log.warn("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity); log.debug("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity);
cleanUpDeviceStateMap(deviceId);
} }
} }
@Override @Override
public void onDeviceDisconnect(DeviceId deviceId) { public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId) {
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
stateData.getState().setLastDisconnectTime(ts); stateData.getState().setLastDisconnectTime(ts);
save(deviceId, LAST_DISCONNECT_TIME, ts); save(deviceId, LAST_DISCONNECT_TIME, ts);
pushRuleEngineMessage(stateData, DISCONNECT_EVENT); pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
public void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) { public void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout) {
if (inactivityTimeout <= 0L) { if (inactivityTimeout <= 0L) {
return; return;
} }
@ -247,6 +248,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
stateData.getState().setInactivityTimeout(inactivityTimeout); stateData.getState().setInactivityTimeout(inactivityTimeout);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
@ -283,12 +285,10 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}, deviceStateExecutor); }, deviceStateExecutor);
} else if (proto.getUpdated()) { } else if (proto.getUpdated()) {
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId()); DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
if (stateData != null) { TbMsgMetaData md = new TbMsgMetaData();
TbMsgMetaData md = new TbMsgMetaData(); md.putValue("deviceName", device.getName());
md.putValue("deviceName", device.getName()); md.putValue("deviceType", device.getType());
md.putValue("deviceType", device.getType()); stateData.setMetaData(md);
stateData.setMetaData(md);
}
} }
} else { } else {
//Device was probably deleted while message was in queue; //Device was probably deleted while message was in queue;
@ -356,10 +356,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
// We no longer manage current partition of devices; // We no longer manage current partition of devices;
removedPartitions.forEach(partition -> { removedPartitions.forEach(partition -> {
Set<DeviceId> devices = partitionedDevices.remove(partition); Set<DeviceId> devices = partitionedDevices.remove(partition);
devices.forEach(deviceId -> { devices.forEach(this::cleanUpDeviceStateMap);
deviceStates.remove(deviceId);
deviceLastSavedActivity.remove(deviceId);
});
}); });
addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
@ -463,11 +460,12 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
void updateInactivityStateIfExpired() { void updateInactivityStateIfExpired() {
final long ts = System.currentTimeMillis(); final long ts = System.currentTimeMillis();
log.debug("Calculating state updates for {} devices", deviceStates.size()); partitionedDevices.forEach((tpi, deviceIds) -> {
Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet()); log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size());
for (DeviceId deviceId : deviceIds) { for (DeviceId deviceId : deviceIds) {
updateInactivityStateIfExpired(ts, deviceId); updateInactivityStateIfExpired(ts, deviceId);
} }
});
} }
void updateInactivityStateIfExpired(long ts, DeviceId deviceId) { void updateInactivityStateIfExpired(long ts, DeviceId deviceId) {
@ -488,8 +486,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} else { } else {
log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId); log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId);
deviceStates.remove(deviceId); cleanUpDeviceStateMap(deviceId);
deviceLastSavedActivity.remove(deviceId);
} }
} }
@ -522,6 +519,15 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} }
private void cleanDeviceStateIfBelongsExternalPartition(TenantId tenantId, final DeviceId deviceId) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
if (!partitionedDevices.containsKey(tpi)) {
cleanUpDeviceStateMap(deviceId);
log.debug("[{}][{}] device belongs to external partition. Probably rebalancing is in progress. Topic: {}"
, tenantId, deviceId, tpi.getFullTopicName());
}
}
private void sendDeviceEvent(TenantId tenantId, DeviceId deviceId, boolean added, boolean updated, boolean deleted) { private void sendDeviceEvent(TenantId tenantId, DeviceId deviceId, boolean added, boolean updated, boolean deleted) {
TransportProtos.DeviceStateServiceMsgProto.Builder builder = TransportProtos.DeviceStateServiceMsgProto.newBuilder(); TransportProtos.DeviceStateServiceMsgProto.Builder builder = TransportProtos.DeviceStateServiceMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
@ -536,13 +542,16 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) { private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
deviceStates.remove(deviceId); cleanUpDeviceStateMap(deviceId);
deviceLastSavedActivity.remove(deviceId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
Set<DeviceId> deviceIdSet = partitionedDevices.get(tpi); Set<DeviceId> deviceIdSet = partitionedDevices.get(tpi);
deviceIdSet.remove(deviceId); deviceIdSet.remove(deviceId);
} }
private void cleanUpDeviceStateMap(DeviceId deviceId) {
deviceStates.remove(deviceId);
}
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) { private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
ListenableFuture<DeviceStateData> future; ListenableFuture<DeviceStateData> future;
if (persistToTelemetry) { if (persistToTelemetry) {

9
application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.state;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -33,13 +34,13 @@ public interface DeviceStateService extends ApplicationListener<PartitionChangeE
void onDeviceDeleted(Device device); void onDeviceDeleted(Device device);
void onDeviceConnect(DeviceId deviceId); void onDeviceConnect(TenantId tenantId, DeviceId deviceId);
void onDeviceActivity(DeviceId deviceId, long lastReportedActivityTime); void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivityTime);
void onDeviceDisconnect(DeviceId deviceId); void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId);
void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout); void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout);
void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback bytes); void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback bytes);

8
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -224,7 +224,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return subscriptionUpdate; return subscriptionUpdate;
}); });
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
updateDeviceInactivityTimeout(entityId, ts); updateDeviceInactivityTimeout(tenantId, entityId, ts);
} }
callback.onSuccess(); callback.onSuccess();
} }
@ -259,7 +259,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}); });
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) { if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) {
updateDeviceInactivityTimeout(entityId, attributes); updateDeviceInactivityTimeout(tenantId, entityId, attributes);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
@ -269,10 +269,10 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
callback.onSuccess(); callback.onSuccess();
} }
private void updateDeviceInactivityTimeout(EntityId entityId, List<? extends KvEntry> kvEntries) { private void updateDeviceInactivityTimeout(TenantId tenantId, EntityId entityId, List<? extends KvEntry> kvEntries) {
for (KvEntry kvEntry : kvEntries) { for (KvEntry kvEntry : kvEntries) {
if (kvEntry.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) { if (kvEntry.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), kvEntry.getLongValue().orElse(0L)); deviceStateService.onDeviceInactivityTimeoutUpdate(tenantId, new DeviceId(entityId.getId()), kvEntry.getLongValue().orElse(0L));
} }
} }
} }

3
application/src/main/resources/logback.xml

@ -41,9 +41,6 @@
<!-- <logger name="org.thingsboard.server.service.queue.TbMsgPackProcessingContext" level="DEBUG" /> --> <!-- <logger name="org.thingsboard.server.service.queue.TbMsgPackProcessingContext" level="DEBUG" /> -->
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
<logger name="org.thingsboard.server.transport" level="TRACE" />
<logger name="org.thingsboard.server.coap.transport" level="TRACE" />
<logger name="org.eclipse" level="TRACE" />
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>

8
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java

@ -43,7 +43,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409), doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(504),
asyncContextTimeoutToUseRpcPlugin); asyncContextTimeoutToUseRpcPlugin);
} }
@ -52,7 +52,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString(); String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, String result = doPostAsync("/api/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound()); status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }
@ -62,7 +62,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409), doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(504),
asyncContextTimeoutToUseRpcPlugin); asyncContextTimeoutToUseRpcPlugin);
} }
@ -71,7 +71,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString(); String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, String result = doPostAsync("/api/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound()); status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }

6
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java

@ -71,7 +71,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
@ -99,14 +99,14 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
validateTwoWayStateChangedNotification(callback, 1, expectedResponseResult, actualResult); validateTwoWayStateChangedNotification(callback, 1, expectedResponseResult, actualResult);
latch = new CountDownLatch(1); latch = new CountDownLatch(1);
actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
validateTwoWayStateChangedNotification(callback, 2, expectedResponseResult, actualResult); validateTwoWayStateChangedNotification(callback, 2, expectedResponseResult, actualResult);

3
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -27,6 +27,7 @@ import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@ -261,7 +262,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
executor = Executors.newScheduledThreadPool(10); executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled"));
loginTenantAdmin(); loginTenantAdmin();
String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};

104
application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java

@ -16,6 +16,8 @@
package org.thingsboard.server.transport.lwm2m; package org.thingsboard.server.transport.lwm2m;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -23,16 +25,18 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCr
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.is;
import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries; import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING;
@ -43,8 +47,10 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDA
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
@Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
public static final int TIMEOUT = 30;
private final String OTA_TRANSPORT_CONFIGURATION = "{\n" + private final String OTA_TRANSPORT_CONFIGURATION = "{\n" +
" \"observeAttr\": {\n" + " \"observeAttr\": {\n" +
" \"keyName\": {\n" + " \"keyName\": {\n" +
@ -122,6 +128,15 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
" \"type\": \"LWM2M\"\n" + " \"type\": \"LWM2M\"\n" +
"}"; "}";
LwM2MTestClient client = null;
@After
public void tearDown() {
if (client != null) {
client.destroy();
}
}
@Test @Test
public void testConnectAndObserveTelemetry() throws Exception { public void testConnectAndObserveTelemetry() throws Exception {
NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
@ -196,37 +211,68 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
} }
} }
/**
* This is the example how to use the AWAITILITY instead Thread.sleep()
* Test will finish as fast as possible, but will await until TIMEOUT if a build machine is busy or slow
* Check the detailed log output to learn how Awaitility polling the API and when exactly expected result appears
* */
@Test @Test
public void testSoftwareUpdateByObject9() throws Exception { public void testSoftwareUpdateByObject9() throws Exception {
LwM2MTestClient client = null; //given
try { final List<OtaPackageUpdateStatus> expectedStatuses = Collections.unmodifiableList(Arrays.asList(
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED));
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint("OTA_" + ENDPOINT);
Device device = createDevice(clientCredentials);
device.setSoftwareId(createSoftware().getId());
device = doPost("/api/device", device, Device.class);
Thread.sleep(1000); createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); clientCredentials.setEndpoint("OTA_" + ENDPOINT);
client.init(SECURITY, COAP_CONFIG); final Device device = createDevice(clientCredentials);
device.setSoftwareId(createSoftware().getId());
Thread.sleep(3000);
log.warn("Saving by API " + device);
List<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { final Device savedDevice = doPost("/api/device", device, Device.class);
})); Assert.assertNotNull(savedDevice);
log.warn("Device saved by API {}", savedDevice);
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
log.warn("AWAIT atMost {} SECONDS on get device by API...", TIMEOUT);
await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> getDeviceFromAPI(device.getId().getId()), is(savedDevice));
log.warn("Got device by API.");
//when
log.warn("Init the client...");
client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT);
client.init(SECURITY, COAP_CONFIG);
log.warn("Init done");
log.warn("AWAIT atMost {} SECONDS on timeseries List<TsKvEntry> by API with list size {}...", TIMEOUT, expectedStatuses.size());
await()
.atMost(30, TimeUnit.SECONDS)
.until(() -> getSwStateTelemetryFromAPI(device.getId().getId())
.size(), is(expectedStatuses.size()));
log.warn("Got an expected await condition!");
//then
log.warn("Fetching ts for the final asserts");
List<TsKvEntry> ts = getSwStateTelemetryFromAPI(device.getId().getId());
log.warn("Got an ts {}", ts);
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
log.warn("Converted ts to statuses {}", statuses);
Assert.assertEquals(expectedStatuses, statuses);
}
List<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); private Device getDeviceFromAPI(UUID deviceId) throws Exception {
final Device device = doGet("/api/device/" + deviceId, Device.class);
log.warn("Fetched device by API for deviceId {}, device is {}", deviceId, device);
return device;
}
Assert.assertEquals(expectedStatuses, statuses); private List<TsKvEntry> getSwStateTelemetryFromAPI(UUID deviceId) throws Exception {
} finally { final List<TsKvEntry> tsKvEntries = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() {
if (client != null) { }));
client.destroy(); log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries);
} return tsKvEntries;
}
} }
} }

8
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java

@ -46,7 +46,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409), doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(504),
asyncContextTimeoutToUseRpcPlugin); asyncContextTimeoutToUseRpcPlugin);
} }
@ -55,7 +55,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString(); String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, String result = doPostAsync("/api/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound()); status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }
@ -65,7 +65,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409), doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(504),
asyncContextTimeoutToUseRpcPlugin); asyncContextTimeoutToUseRpcPlugin);
} }
@ -74,7 +74,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString(); String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, String result = doPostAsync("/api/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound()); status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }

8
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -69,7 +69,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertTrue(StringUtils.isEmpty(result)); Assert.assertTrue(StringUtils.isEmpty(result));
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
@ -95,7 +95,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"value1\":\"A\",\"value2\":\"B\"}"; String expected = "{\"value1\":\"A\",\"value2\":\"B\"}";
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result); Assert.assertEquals(expected, result);
@ -130,7 +130,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertTrue(StringUtils.isEmpty(result)); Assert.assertTrue(StringUtils.isEmpty(result));
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
@ -156,7 +156,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
String expected = "{\"success\":true}"; String expected = "{\"success\":true}";
assertEquals(expected, result); assertEquals(expected, result);

2
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java

@ -131,7 +131,7 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}"; String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}";
latch.await(3, TimeUnit.SECONDS); latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result); Assert.assertEquals(expected, result);

41
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java

@ -77,25 +77,34 @@ public class TbKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
@Override @Override
public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) { public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) {
createTopicIfNotExist(tpi); try {
String key = msg.getKey().toString(); createTopicIfNotExist(tpi);
byte[] data = msg.getData(); String key = msg.getKey().toString();
ProducerRecord<String, byte[]> record; byte[] data = msg.getData();
Iterable<Header> headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); ProducerRecord<String, byte[]> record;
record = new ProducerRecord<>(tpi.getFullTopicName(), null, key, data, headers); Iterable<Header> headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList());
producer.send(record, (metadata, exception) -> { record = new ProducerRecord<>(tpi.getFullTopicName(), null, key, data, headers);
if (exception == null) { producer.send(record, (metadata, exception) -> {
if (callback != null) { if (exception == null) {
callback.onSuccess(new KafkaTbQueueMsgMetadata(metadata)); if (callback != null) {
} callback.onSuccess(new KafkaTbQueueMsgMetadata(metadata));
} else { }
if (callback != null) {
callback.onFailure(exception);
} else { } else {
log.warn("Producer template failure: {}", exception.getMessage(), exception); if (callback != null) {
callback.onFailure(exception);
} else {
log.warn("Producer template failure: {}", exception.getMessage(), exception);
}
} }
});
} catch (Exception e) {
if (callback != null) {
callback.onFailure(e);
} else {
log.warn("Producer template failure (send method wrapper): {}", e.getMessage(), e);
} }
}); throw e;
}
} }
private void createTopicIfNotExist(TopicPartitionInfo tpi) { private void createTopicIfNotExist(TopicPartitionInfo tpi) {

1
common/queue/src/main/proto/queue.proto

@ -339,6 +339,7 @@ message ToDeviceRpcRequestMsg {
message ToDeviceRpcResponseMsg { message ToDeviceRpcResponseMsg {
int32 requestId = 1; int32 requestId = 1;
string payload = 2; string payload = 2;
string error = 3;
} }
message UplinkNotificationMsg { message UplinkNotificationMsg {

20
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
@ -507,6 +508,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
return; return;
} }
boolean sent = false; boolean sent = false;
String error = null;
boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getRpc()); boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getRpc());
try { try {
Response response = state.getAdaptor().convertToPublish(conRequest, msg, state.getConfiguration().getRpcRequestDynamicMessageBuilder()); Response response = state.getAdaptor().convertToPublish(conRequest, msg, state.getConfiguration().getRpcRequestDynamicMessageBuilder());
@ -515,15 +517,12 @@ public class DefaultCoapClientContext implements CoapClientContext {
if (msg.getPersisted() && conRequest) { if (msg.getPersisted() && conRequest) {
transportContext.getRpcAwaitingAck().put(requestId, msg); transportContext.getRpcAwaitingAck().put(requestId, msg);
transportContext.getScheduler().schedule(() -> { transportContext.getScheduler().schedule(() -> {
TransportProtos.ToDeviceRpcRequestMsg awaitingAckMsg = transportContext.getRpcAwaitingAck().remove(requestId); transportContext.getRpcAwaitingAck().remove(requestId);
if (awaitingAckMsg != null) {
transportService.process(state.getSession(), msg, true, TransportServiceCallback.EMPTY);
}
}, Math.max(0, msg.getExpirationTime() - System.currentTimeMillis()), TimeUnit.MILLISECONDS); }, Math.max(0, msg.getExpirationTime() - System.currentTimeMillis()), TimeUnit.MILLISECONDS);
response.addMessageObserver(new TbCoapMessageObserver(requestId, id -> { response.addMessageObserver(new TbCoapMessageObserver(requestId, id -> {
TransportProtos.ToDeviceRpcRequestMsg rpcRequestMsg = transportContext.getRpcAwaitingAck().remove(id); TransportProtos.ToDeviceRpcRequestMsg rpcRequestMsg = transportContext.getRpcAwaitingAck().remove(id);
if (rpcRequestMsg != null) { if (rpcRequestMsg != null) {
transportService.process(state.getSession(), rpcRequestMsg, false, TransportServiceCallback.EMPTY); transportService.process(state.getSession(), rpcRequestMsg, TransportServiceCallback.EMPTY);
} }
}, null)); }, null));
} }
@ -536,9 +535,16 @@ public class DefaultCoapClientContext implements CoapClientContext {
log.trace("Failed to reply due to error", e); log.trace("Failed to reply due to error", e);
cancelObserveRelation(state.getRpc()); cancelObserveRelation(state.getRpc());
cancelRpcSubscription(state); cancelRpcSubscription(state);
error = "Failed to convert device RPC command to CoAP msg";
} catch (Exception e) {
error = "Internal error: " + e.getMessage();
} finally { } finally {
if (msg.getPersisted() && !conRequest) { if (StringUtils.isNotEmpty(error)) {
transportService.process(state.getSession(), msg, sent, TransportServiceCallback.EMPTY); transportService.process(state.getSession(),
TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(msg.getRequestId()).setError(error).build(), TransportServiceCallback.EMPTY);
} else if (msg.getPersisted() && !conRequest && sent) {
transportService.process(state.getSession(), msg, TransportServiceCallback.EMPTY);
} }
} }
} }

2
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -409,7 +409,7 @@ public class DeviceApiController implements TbTransportService {
public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg msg) { public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg msg) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to device", sessionId);
responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK));
transportService.process(sessionInfo, msg, false, TransportServiceCallback.EMPTY); transportService.process(sessionInfo, msg, TransportServiceCallback.EMPTY);
} }
@Override @Override

11
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -19,6 +19,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
@ -26,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -38,6 +42,7 @@ import java.security.KeyStoreException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
@ -79,12 +84,14 @@ public class LwM2MTransportBootstrapService {
builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
/* Define model provider (Create Models )*/ /* Define model provider (Create Models )*/
List<ObjectModel> models = ObjectLoader.loadDefault();
builder.setModel(new StaticModel(models));
/* Create credentials */ /* Create credentials */
this.setServerWithCredentials(builder); this.setServerWithCredentials(builder);
// /** Set securityStore with new ConfigStore */ /* Set securityStore with new ConfigStore */
// builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore));
/* SecurityStore */ /* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore); builder.setSecurityStore(lwM2MBootstrapSecurityStore);

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java

@ -74,15 +74,19 @@ public class LwM2MBootstrapConfig implements Serializable {
configBs.servers.put(0, server0); configBs.servers.put(0, server0);
/* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */
this.bootstrapServer.setBootstrapServerIs(true); this.bootstrapServer.setBootstrapServerIs(true);
configBs.security.put(0, setServerSecurity(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); configBs.security.put(0, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId()));
/* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */ /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */
configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId()));
return configBs; return configBs;
} }
private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, String securityHost, Integer securityPort, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) {
BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity(); BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity();
serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port); if (securityMode.equals(SecurityMode.NO_SEC)) {
serverSecurity.uri = "coap://" + host + ":" + Integer.toString(port);
} else {
serverSecurity.uri = "coaps://" + securityHost + ":" + Integer.toString(securityPort);
}
serverSecurity.bootstrapServer = bootstrapServer; serverSecurity.bootstrapServer = bootstrapServer;
serverSecurity.securityMode = securityMode; serverSecurity.securityMode = securityMode;
serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode); serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode);

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter;
public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter {
public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) {
super(store);
}
}

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java

@ -31,24 +31,31 @@ public class LwM2MServerBootstrap {
String host = "0.0.0.0"; String host = "0.0.0.0";
Integer port = 0; Integer port = 0;
String securityHost = "0.0.0.0";
Integer securityPort = 0;
SecurityMode securityMode = SecurityMode.NO_SEC; SecurityMode securityMode = SecurityMode.NO_SEC;
Integer serverId = 123; Integer serverId = 123;
boolean bootstrapServerIs = false; boolean bootstrapServerIs = false;
public LwM2MServerBootstrap(){}; public LwM2MServerBootstrap() {
}
;
public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) { public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) {
this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId();
this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); this.clientSecretKey = bootstrapFromCredential.getClientSecretKey();
this.serverPublicKey = profileServerBootstrap.getServerPublicKey(); this.serverPublicKey = profileServerBootstrap.getServerPublicKey();
this.clientHoldOffTime = profileServerBootstrap.getClientHoldOffTime(); this.clientHoldOffTime = profileServerBootstrap.getClientHoldOffTime();
this.bootstrapServerAccountTimeout = profileServerBootstrap.getBootstrapServerAccountTimeout(); this.bootstrapServerAccountTimeout = profileServerBootstrap.getBootstrapServerAccountTimeout();
this.host = (profileServerBootstrap.getHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getHost(); this.host = (profileServerBootstrap.getHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getHost();
this.port = profileServerBootstrap.getPort(); this.port = profileServerBootstrap.getPort();
this.securityMode = profileServerBootstrap.getSecurityMode(); this.securityHost = (profileServerBootstrap.getSecurityHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getSecurityHost();
this.serverId = profileServerBootstrap.getServerId(); this.securityPort = profileServerBootstrap.getSecurityPort();
this.bootstrapServerIs = profileServerBootstrap.bootstrapServerIs; this.securityMode = profileServerBootstrap.getSecurityMode();
this.serverId = profileServerBootstrap.getServerId();
this.bootstrapServerIs = profileServerBootstrap.bootstrapServerIs;
} }
} }

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java

@ -84,7 +84,6 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to device", sessionId);
this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo); this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo);
transportService.process(sessionInfo, toDeviceRequest, false, TransportServiceCallback.EMPTY);
} }
@Override @Override

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -51,6 +51,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -113,6 +114,9 @@ public class LwM2mClient implements Serializable {
private boolean firstEdrxDownlink = true; private boolean firstEdrxDownlink = true;
@Getter
private final AtomicInteger retryAttempts;
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
return super.clone(); return super.clone();
} }
@ -124,6 +128,7 @@ public class LwM2mClient implements Serializable {
this.resources = new ConcurrentHashMap<>(); this.resources = new ConcurrentHashMap<>();
this.state = LwM2MClientState.CREATED; this.state = LwM2MClientState.CREATED;
this.lock = new ReentrantLock(); this.lock = new ReentrantLock();
this.retryAttempts = new AtomicInteger(0);
} }
public void init(ValidateDeviceCredentialsResponse credentials, UUID sessionId) { public void init(ValidateDeviceCredentialsResponse credentials, UUID sessionId) {

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -93,7 +93,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
log.debug("Fetched clients from store: {}", fetchedClients); log.debug("Fetched clients from store: {}", fetchedClients);
fetchedClients.forEach(client -> { fetchedClients.forEach(client -> {
lwM2mClientsByEndpoint.put(client.getEndpoint(), client); lwM2mClientsByEndpoint.put(client.getEndpoint(), client);
updateFetchedClient(nodeId, client); try {
client.lock();
updateFetchedClient(nodeId, client);
} finally {
client.unlock();
}
}); });
} }

38
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java

@ -45,11 +45,11 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequ
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult;
@ -178,7 +178,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
attributesToFetch.add(SOFTWARE_TAG); attributesToFetch.add(SOFTWARE_TAG);
attributesToFetch.add(SOFTWARE_URL); attributesToFetch.add(SOFTWARE_URL);
} }
var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings(); var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings();
onFirmwareStrategyUpdate(client, clientSettings); onFirmwareStrategyUpdate(client, clientSettings);
@ -283,8 +283,17 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
} }
fwInfo.setUpdateState(state); fwInfo.setUpdateState(state);
Optional<OtaPackageUpdateStatus> status = toOtaPackageUpdateStatus(state); Optional<OtaPackageUpdateStatus> status = toOtaPackageUpdateStatus(state);
status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo,
otaStatus, "Firmware Update State: " + state.name())); if (FirmwareUpdateState.IDLE.equals(state) && DOWNLOADING.equals(fwInfo.getStatus())) {
fwInfo.setFailedPackageId(fwInfo.getTargetPackageId());
status = Optional.of(FAILED);
}
status.ifPresent(otaStatus -> {
fwInfo.setStatus(otaStatus);
sendStateUpdateToTelemetry(client, fwInfo,
otaStatus, "Firmware Update State: " + state.name());
});
update(fwInfo); update(fwInfo);
} }
@ -294,8 +303,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue()); FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue());
Optional<OtaPackageUpdateStatus> status = toOtaPackageUpdateStatus(result); Optional<OtaPackageUpdateStatus> status = toOtaPackageUpdateStatus(result);
status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo,
otaStatus, "Firmware Update Result: " + result.name())); if (FirmwareUpdateResult.INITIAL.equals(result) && OtaPackageUpdateStatus.UPDATING.equals(fwInfo.getStatus())) {
status = Optional.of(UPDATED);
fwInfo.setRetryAttempts(0);
}
status.ifPresent(otaStatus -> {
fwInfo.setStatus(otaStatus);
sendStateUpdateToTelemetry(client, fwInfo,
otaStatus, "Firmware Update Result: " + result.name());
}
);
if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) { if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) {
fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1); fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1);
startFirmwareUpdateIfNeeded(client, fwInfo); startFirmwareUpdateIfNeeded(client, fwInfo);
@ -369,7 +389,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientFwOtaInfo fwInfo) { private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientFwOtaInfo fwInfo) {
try { try {
if (!fwInfo.isSupported()) { if (!fwInfo.isSupported() && fwInfo.isAssigned()) {
log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo); log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo);
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!"); sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!");
} else if (fwInfo.isUpdateRequired()) { } else if (fwInfo.isUpdateRequired()) {
@ -389,7 +409,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientSwOtaInfo swInfo) { private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientSwOtaInfo swInfo) {
try { try {
if (!swInfo.isSupported()) { if (!swInfo.isSupported() && swInfo.isAssigned()) {
log.debug("[{}] Sw update is not supported: {}", client.getEndpoint(), swInfo); log.debug("[{}] Sw update is not supported: {}", client.getEndpoint(), swInfo);
sendStateUpdateToTelemetry(client, swInfo, OtaPackageUpdateStatus.FAILED, "Client does not support software update or profile misconfiguration!"); sendStateUpdateToTelemetry(client, swInfo, OtaPackageUpdateStatus.FAILED, "Client does not support software update or profile misconfiguration!");
} else if (swInfo.isUpdateRequired()) { } else if (swInfo.isUpdateRequired()) {

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java

@ -20,6 +20,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import java.util.Optional; import java.util.Optional;
@ -39,6 +40,7 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
protected Strategy strategy; protected Strategy strategy;
protected State updateState; protected State updateState;
protected Result result; protected Result result;
protected OtaPackageUpdateStatus status;
protected String failedPackageId; protected String failedPackageId;
protected int retryAttempts; protected int retryAttempts;
@ -91,6 +93,11 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion) || StringUtils.isNotEmpty(currentVersion3); return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion) || StringUtils.isNotEmpty(currentVersion3);
} }
@JsonIgnore
public boolean isAssigned() {
return StringUtils.isNotEmpty(targetName) && StringUtils.isNotEmpty(targetVersion);
}
public abstract void update(Result result); public abstract void update(Result result);
protected static String getPackageId(String name, String version) { protected static String getPackageId(String name, String version) {
@ -99,4 +106,7 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
public abstract OtaPackageType getType(); public abstract OtaPackageType getType();
public String getTargetPackageId() {
return getPackageId(targetName, targetVersion);
}
} }

164
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -77,96 +77,89 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
private final LwM2mUplinkMsgHandler uplinkHandler; private final LwM2mUplinkMsgHandler uplinkHandler;
private final LwM2mDownlinkMsgHandler downlinkHandler; private final LwM2mDownlinkMsgHandler downlinkHandler;
private final LwM2MTelemetryLogService logService; private final LwM2MTelemetryLogService logService;
private final Map<UUID, Long> rpcSubscriptions = new ConcurrentHashMap<>();
@Override @Override
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest, TransportProtos.SessionInfoProto sessionInfo) { public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest, TransportProtos.SessionInfoProto sessionInfo) {
this.cleanupOldSessions();
UUID requestUUID = new UUID(rpcRequest.getRequestIdMSB(), rpcRequest.getRequestIdLSB());
log.debug("Received params: {}", rpcRequest.getParams()); log.debug("Received params: {}", rpcRequest.getParams());
// We use this map to protect from browser issue that the same command is sent twice. LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequest.getMethodName());
// TODO: This is probably not the best place and should be moved to DeviceActor if (operationType == null) {
if (!this.rpcSubscriptions.containsKey(requestUUID)) { this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED, "Unsupported operation type: " + rpcRequest.getMethodName());
LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequest.getMethodName()); return;
if (operationType == null) { }
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED.getName(), "Unsupported operation type: " + rpcRequest.getMethodName()); LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo);
return; if (client.getRegistration() == null) {
} this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR, "Registration is empty");
LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); return;
if (client.getRegistration() == null) { }
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR.getName(), "Registration is empty"); try {
return; if (operationType.isHasObjectId()) {
} String objectId = getIdFromParameters(client, rpcRequest);
try { switch (operationType) {
if (operationType.isHasObjectId()) { case READ:
String objectId = getIdFromParameters(client, rpcRequest); sendReadRequest(client, rpcRequest, objectId);
break;
case OBSERVE:
sendObserveRequest(client, rpcRequest, objectId);
break;
case DISCOVER:
sendDiscoverRequest(client, rpcRequest, objectId);
break;
case EXECUTE:
sendExecuteRequest(client, rpcRequest, objectId);
break;
case WRITE_ATTRIBUTES:
sendWriteAttributesRequest(client, rpcRequest, objectId);
break;
case OBSERVE_CANCEL:
sendCancelObserveRequest(client, rpcRequest, objectId);
break;
case DELETE:
sendDeleteRequest(client, rpcRequest, objectId);
break;
case WRITE_UPDATE:
sendWriteUpdateRequest(client, rpcRequest, objectId);
break;
case WRITE_REPLACE:
sendWriteReplaceRequest(client, rpcRequest, objectId);
break;
default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
}
} else if (operationType.isComposite()) {
if (clientContext.isComposite(client)) {
switch (operationType) { switch (operationType) {
case READ: case READ_COMPOSITE:
sendReadRequest(client, rpcRequest, objectId); sendReadCompositeRequest(client, rpcRequest);
break;
case OBSERVE:
sendObserveRequest(client, rpcRequest, objectId);
break;
case DISCOVER:
sendDiscoverRequest(client, rpcRequest, objectId);
break;
case EXECUTE:
sendExecuteRequest(client, rpcRequest, objectId);
break;
case WRITE_ATTRIBUTES:
sendWriteAttributesRequest(client, rpcRequest, objectId);
break;
case OBSERVE_CANCEL:
sendCancelObserveRequest(client, rpcRequest, objectId);
break;
case DELETE:
sendDeleteRequest(client, rpcRequest, objectId);
break;
case WRITE_UPDATE:
sendWriteUpdateRequest(client, rpcRequest, objectId);
break; break;
case WRITE_REPLACE: case WRITE_COMPOSITE:
sendWriteReplaceRequest(client, rpcRequest, objectId); sendWriteCompositeRequest(client, rpcRequest);
break; break;
default: default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
} }
} else if (operationType.isComposite()) {
if (clientContext.isComposite(client)) {
switch (operationType) {
case READ_COMPOSITE:
sendReadCompositeRequest(client, rpcRequest);
break;
case WRITE_COMPOSITE:
sendWriteCompositeRequest(client, rpcRequest);
break;
default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
}
} else {
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(),
ResponseCode.INTERNAL_SERVER_ERROR.getName(), "This device does not support Composite Operation");
}
} else { } else {
switch (operationType) { this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(),
case OBSERVE_CANCEL_ALL: ResponseCode.INTERNAL_SERVER_ERROR, "This device does not support Composite Operation");
sendCancelAllObserveRequest(client, rpcRequest); }
break; } else {
case OBSERVE_READ_ALL: switch (operationType) {
sendObserveAllRequest(client, rpcRequest); case OBSERVE_CANCEL_ALL:
break; sendCancelAllObserveRequest(client, rpcRequest);
case DISCOVER_ALL: break;
sendDiscoverAllRequest(client, rpcRequest); case OBSERVE_READ_ALL:
break; sendObserveAllRequest(client, rpcRequest);
case FW_UPDATE: break;
//TODO: implement and add break statement case DISCOVER_ALL:
default: sendDiscoverAllRequest(client, rpcRequest);
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); break;
} case FW_UPDATE:
//TODO: implement and add break statement
default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
} }
} catch (IllegalArgumentException e) {
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST.getName(), e.getMessage());
} }
} catch (IllegalArgumentException e) {
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST, e.getMessage());
} }
} }
@ -312,23 +305,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
} }
} }
private void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, String result, String error) { private void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, ResponseCode result, String error) {
String payload = JacksonUtil.toString(JacksonUtil.newObjectNode().put("result", result).put("error", error)); String payload = JacksonUtil.toString(LwM2MRpcResponseBody.builder().result(result.getName()).error(error).build());
TransportProtos.ToDeviceRpcResponseMsg msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); TransportProtos.ToDeviceRpcResponseMsg msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setError(payload).build();
transportService.process(sessionInfo, msg, null); transportService.process(sessionInfo, msg, null);
} }
private void cleanupOldSessions() {
log.debug("Before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size());
if (rpcSubscriptions.size() > 0) {
long currentTime = System.currentTimeMillis();
Set<UUID> rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> currentTime > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet());
log.debug("RpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove);
rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove);
}
log.debug("After rpcSubscriptions.size(): [{}]", rpcSubscriptions.size());
}
@Override @Override
public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, TransportProtos.SessionInfoProto sessionInfo) { public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, TransportProtos.SessionInfoProto sessionInfo) {
log.debug("OnToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); log.debug("OnToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcResponseBody.java

@ -27,6 +27,5 @@ public class LwM2MRpcResponseBody {
private String result; private String result;
private String value; private String value;
private String error; private String error;
private String info;
} }

23
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java

@ -16,12 +16,17 @@
package org.thingsboard.server.transport.lwm2m.server.rpc; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import java.util.concurrent.TimeoutException;
public abstract class RpcDownlinkRequestCallbackProxy<R, T> implements DownlinkRequestCallback<R, T> { public abstract class RpcDownlinkRequestCallbackProxy<R, T> implements DownlinkRequestCallback<R, T> {
private final TransportService transportService; private final TransportService transportService;
@ -39,6 +44,7 @@ public abstract class RpcDownlinkRequestCallbackProxy<R, T> implements DownlinkR
@Override @Override
public void onSuccess(R request, T response) { public void onSuccess(R request, T response) {
transportService.process(client.getSession(), this.request, TransportServiceCallback.EMPTY);
sendRpcReplyOnSuccess(response); sendRpcReplyOnSuccess(response);
if (callback != null) { if (callback != null) {
callback.onSuccess(request, response); callback.onSuccess(request, response);
@ -55,18 +61,23 @@ public abstract class RpcDownlinkRequestCallbackProxy<R, T> implements DownlinkR
@Override @Override
public void onError(String params, Exception e) { public void onError(String params, Exception e) {
sendRpcReplyOnError(e); if (!(e instanceof TimeoutException || e instanceof ClientSleepingException)) {
sendRpcReplyOnError(e);
}
if (callback != null) { if (callback != null) {
callback.onError(params, e); callback.onError(params, e);
} }
} }
protected void reply(LwM2MRpcResponseBody response) { protected void reply(LwM2MRpcResponseBody response) {
TransportProtos.ToDeviceRpcResponseMsg msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() TransportProtos.ToDeviceRpcResponseMsg.Builder msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(request.getRequestId());
.setPayload(JacksonUtil.toString(response)) String responseAsString = JacksonUtil.toString(response);
.setRequestId(request.getRequestId()) if (StringUtils.isEmpty(response.getError())) {
.build(); msg.setPayload(responseAsString);
transportService.process(client.getSession(), msg, null); } else {
msg.setError(responseAsString);
}
transportService.process(client.getSession(), msg.build(), null);
} }
abstract protected void sendRpcReplyOnSuccess(T response); abstract protected void sendRpcReplyOnSuccess(T response);

9
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java

@ -110,11 +110,11 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_NAME_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID;
@ -219,11 +219,16 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
this.initClientTelemetry(lwM2MClient); this.initClientTelemetry(lwM2MClient);
this.initAttributes(lwM2MClient); this.initAttributes(lwM2MClient);
otaService.init(lwM2MClient); otaService.init(lwM2MClient);
lwM2MClient.getRetryAttempts().set(0);
} catch (LwM2MClientStateException stateException) { } catch (LwM2MClientStateException stateException) {
if (LwM2MClientState.UNREGISTERED.equals(stateException.getState())) { if (LwM2MClientState.UNREGISTERED.equals(stateException.getState())) {
log.info("[{}] retry registration due to race condition: [{}].", registration.getEndpoint(), stateException.getState()); log.info("[{}] retry registration due to race condition: [{}].", registration.getEndpoint(), stateException.getState());
// Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again. // Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again.
onRegistered(registration, previousObservations); if (lwM2MClient.getRetryAttempts().incrementAndGet() <= 5) {
context.getScheduler().schedule(() -> onRegistered(registration, previousObservations), 1, TimeUnit.SECONDS);
} else {
logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to retry attempts: " + lwM2MClient.getRetryAttempts().get());
}
} else { } else {
logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState());
} }

5
common/transport/mqtt/pom.xml

@ -88,6 +88,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>

17
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -249,7 +249,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId(); int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId();
TransportProtos.ToDeviceRpcRequestMsg rpcRequest = rpcAwaitingAck.remove(msgId); TransportProtos.ToDeviceRpcRequestMsg rpcRequest = rpcAwaitingAck.remove(msgId);
if (rpcRequest != null) { if (rpcRequest != null) {
transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, false, TransportServiceCallback.EMPTY); transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, TransportServiceCallback.EMPTY);
} }
break; break;
default: default:
@ -304,6 +304,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} catch (RuntimeException | AdaptorException e) { } catch (RuntimeException | AdaptorException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
} }
} }
@ -829,18 +830,22 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
if (rpcRequest.getPersisted() && isAckExpected(payload)) { if (rpcRequest.getPersisted() && isAckExpected(payload)) {
rpcAwaitingAck.put(msgId, rpcRequest); rpcAwaitingAck.put(msgId, rpcRequest);
context.getScheduler().schedule(() -> { context.getScheduler().schedule(() -> {
TransportProtos.ToDeviceRpcRequestMsg awaitingAckMsg = rpcAwaitingAck.remove(msgId); rpcAwaitingAck.remove(msgId);
if (awaitingAckMsg != null) {
transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, true, TransportServiceCallback.EMPTY);
}
}, Math.max(0, rpcRequest.getExpirationTime() - System.currentTimeMillis()), TimeUnit.MILLISECONDS); }, Math.max(0, rpcRequest.getExpirationTime() - System.currentTimeMillis()), TimeUnit.MILLISECONDS);
} }
var cf = publish(payload, deviceSessionCtx); var cf = publish(payload, deviceSessionCtx);
if (rpcRequest.getPersisted() && !isAckExpected(payload)) { if (rpcRequest.getPersisted() && !isAckExpected(payload)) {
cf.addListener(result -> transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, result.cause() != null, TransportServiceCallback.EMPTY)); cf.addListener(result -> {
if (result.cause() == null) {
transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, TransportServiceCallback.EMPTY);
}
});
} }
}); });
} catch (Exception e) { } catch (Exception e) {
transportService.process(deviceSessionCtx.getSessionInfo(),
TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(rpcRequest.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY);
log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
} }
} }

11
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java

@ -101,13 +101,18 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
payload -> { payload -> {
ChannelFuture channelFuture = parent.writeAndFlush(payload); ChannelFuture channelFuture = parent.writeAndFlush(payload);
if (request.getPersisted()) { if (request.getPersisted()) {
channelFuture.addListener(future -> channelFuture.addListener(future -> {
transportService.process(getSessionInfo(), request, future.cause() != null, TransportServiceCallback.EMPTY) if (future.cause() == null) {
); transportService.process(getSessionInfo(), request, TransportServiceCallback.EMPTY);
}
});
} }
} }
); );
} catch (Exception e) { } catch (Exception e) {
transportService.process(getSessionInfo(),
TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(request.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY);
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
} }
} }

35
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java

@ -34,6 +34,7 @@ import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
@ -66,6 +67,8 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import static org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
/** /**
* Created by ashvayka on 19.01.17. * Created by ashvayka on 19.01.17.
*/ */
@ -82,7 +85,7 @@ public class GatewaySessionHandler {
private final UUID sessionId; private final UUID sessionId;
private final ConcurrentMap<String, Lock> deviceCreationLockMap; private final ConcurrentMap<String, Lock> deviceCreationLockMap;
private final ConcurrentMap<String, GatewayDeviceSessionCtx> devices; private final ConcurrentMap<String, GatewayDeviceSessionCtx> devices;
private final ConcurrentMap<String, SettableFuture<GatewayDeviceSessionCtx>> deviceFutures; private final ConcurrentMap<String, ListenableFuture<GatewayDeviceSessionCtx>> deviceFutures;
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap; private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
private final ChannelHandlerContext channel; private final ChannelHandlerContext channel;
private final DeviceSessionCtx deviceSessionCtx; private final DeviceSessionCtx deviceSessionCtx;
@ -95,11 +98,15 @@ public class GatewaySessionHandler {
this.sessionId = sessionId; this.sessionId = sessionId;
this.devices = new ConcurrentHashMap<>(); this.devices = new ConcurrentHashMap<>();
this.deviceFutures = new ConcurrentHashMap<>(); this.deviceFutures = new ConcurrentHashMap<>();
this.deviceCreationLockMap = new ConcurrentHashMap<>(); this.deviceCreationLockMap = createWeakMap();
this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap(); this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap();
this.channel = deviceSessionCtx.getChannel(); this.channel = deviceSessionCtx.getChannel();
} }
ConcurrentReferenceHashMap<String, Lock> createWeakMap() {
return new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
}
public void onDeviceConnect(MqttPublishMessage mqttMsg) throws AdaptorException { public void onDeviceConnect(MqttPublishMessage mqttMsg) throws AdaptorException {
if (isJsonPayloadType()) { if (isJsonPayloadType()) {
onDeviceConnectJson(mqttMsg); onDeviceConnectJson(mqttMsg);
@ -228,21 +235,22 @@ public class GatewaySessionHandler {
if (result == null) { if (result == null) {
return getDeviceCreationFuture(deviceName, deviceType); return getDeviceCreationFuture(deviceName, deviceType);
} else { } else {
return toCompletedFuture(result); return Futures.immediateFuture(result);
} }
} finally { } finally {
deviceCreationLock.unlock(); deviceCreationLock.unlock();
} }
} else { } else {
return toCompletedFuture(result); return Futures.immediateFuture(result);
} }
} }
private ListenableFuture<GatewayDeviceSessionCtx> getDeviceCreationFuture(String deviceName, String deviceType) { private ListenableFuture<GatewayDeviceSessionCtx> getDeviceCreationFuture(String deviceName, String deviceType) {
SettableFuture<GatewayDeviceSessionCtx> future = deviceFutures.get(deviceName); final SettableFuture<GatewayDeviceSessionCtx> futureToSet = SettableFuture.create();
if (future == null) { ListenableFuture<GatewayDeviceSessionCtx> future = deviceFutures.putIfAbsent(deviceName, futureToSet);
final SettableFuture<GatewayDeviceSessionCtx> futureToSet = SettableFuture.create(); if (future != null) {
deviceFutures.put(deviceName, futureToSet); return future;
}
try { try {
transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder() transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
.setDeviceName(deviceName) .setDeviceName(deviceName)
@ -282,15 +290,6 @@ public class GatewaySessionHandler {
deviceFutures.remove(deviceName); deviceFutures.remove(deviceName);
throw e; throw e;
} }
} else {
return future;
}
}
private ListenableFuture<GatewayDeviceSessionCtx> toCompletedFuture(GatewayDeviceSessionCtx result) {
SettableFuture<GatewayDeviceSessionCtx> future = SettableFuture.create();
future.set(result);
return future;
} }
private int getMsgId(MqttPublishMessage mqttMsg) { private int getMsgId(MqttPublishMessage mqttMsg) {
@ -353,6 +352,7 @@ public class GatewaySessionHandler {
processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId); processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId);
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e); log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e);
channel.close();
} }
} }
@ -384,6 +384,7 @@ public class GatewaySessionHandler {
processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId); processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId);
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, msg, e); log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, msg, e);
channel.close();
} }
} }

61
common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandlerTest.java

@ -0,0 +1,61 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.session;
import org.junit.Test;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock;
public class GatewaySessionHandlerTest {
@Test
public void givenWeakHashMap_WhenGC_thenMapIsEmpty() {
WeakHashMap<String, Lock> map = new WeakHashMap<>();
String deviceName = new String("device"); //constants are static and doesn't affected by GC, so use new instead
map.put(deviceName, new ReentrantLock());
assertTrue(map.containsKey(deviceName));
deviceName = null;
System.gc();
await().atMost(10, TimeUnit.SECONDS).until(() -> !map.containsKey("device"));
}
@Test
public void givenConcurrentReferenceHashMap_WhenGC_thenMapIsEmpty() {
GatewaySessionHandler gsh = mock(GatewaySessionHandler.class);
willCallRealMethod().given(gsh).createWeakMap();
ConcurrentMap<String, Lock> map = gsh.createWeakMap();
map.put("device", new ReentrantLock());
assertTrue(map.containsKey("device"));
System.gc();
await().atMost(10, TimeUnit.SECONDS).until(() -> !map.containsKey("device"));
}
}

2
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java

@ -142,7 +142,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to device", sessionId);
snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest);
snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, false, TransportServiceCallback.EMPTY); snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, TransportServiceCallback.EMPTY);
} }
@Override @Override

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -110,7 +110,7 @@ public interface TransportService {
void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, ToDeviceRpcRequestMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback);

12
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -580,17 +580,9 @@ public class DefaultTransportService implements TransportService {
} }
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
if (msg.getPersisted()) { if (msg.getPersisted()) {
RpcStatus status; RpcStatus status = msg.getOneway() ? RpcStatus.SUCCESSFUL : RpcStatus.DELIVERED;
if (isFailedRpc) {
status = RpcStatus.FAILED;
} else if (msg.getOneway()) {
status = RpcStatus.SUCCESSFUL;
} else {
status = RpcStatus.DELIVERED;
}
TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder() TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
.setRequestId(msg.getRequestId()) .setRequestId(msg.getRequestId())

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java

@ -100,7 +100,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
} }
@Override @Override
public ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { public ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java

@ -124,7 +124,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
} }
@Override @Override
public ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { public ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key) {
return Futures.immediateFuture(0); return Futures.immediateFuture(0);
} }

2
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -170,7 +170,7 @@ public class BaseTimeseriesService implements TimeseriesService {
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only"); throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only");
} }
futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey()));
futures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), v -> 0, MoreExecutors.directExecutor())); futures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), v -> 0, MoreExecutors.directExecutor()));
futures.add(timeseriesDao.save(tenantId, entityId, tsKvEntry, ttl)); futures.add(timeseriesDao.save(tenantId, entityId, tsKvEntry, ttl));
} }

7
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -181,11 +181,14 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
} }
@Override @Override
public ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { public ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key) {
if (isFixedPartitioning()) { if (isFixedPartitioning()) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
ttl = computeTtl(ttl); // DO NOT apply custom TTL to partition, otherwise, short TTL will remove partition too early
// partitions must remain in the DB forever or be removed only by systemTtl
// removal of empty partition is too expensive (we need to scan all data keys for these partitions with ALLOW FILTERING)
long ttl = computeTtl(0);
long partition = toPartitionTs(tsKvEntryTs); long partition = toPartitionTs(tsKvEntryTs);
if (cassandraTsPartitionsCache == null) { if (cassandraTsPartitionsCache == null) {
return doSavePartition(tenantId, entityId, key, ttl, partition); return doSavePartition(tenantId, entityId, key, ttl, partition);

2
dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java

@ -33,7 +33,7 @@ public interface TimeseriesDao {
ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl);
ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl); ListenableFuture<Integer> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key);
ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query);

4
dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java

@ -100,10 +100,10 @@ public class CassandraPartitionsCacheTest {
long tsKvEntryTs = System.currentTimeMillis(); long tsKvEntryTs = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) { for (int i = 0; i < 50000; i++) {
cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i, 0); cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i);
} }
for (int i = 0; i < 60000; i++) { for (int i = 0; i < 60000; i++) {
cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i, 0); cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i);
} }
verify(cassandraBaseTimeseriesDao, times(60000)).executeAsyncWrite(any(TenantId.class), any(Statement.class)); verify(cassandraBaseTimeseriesDao, times(60000)).executeAsyncWrite(any(TenantId.class), any(Statement.class));
} }

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java

@ -248,7 +248,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
return o1.getTitle().compareTo(o2.getTitle()); return o1.getTitle().compareTo(o2.getTitle());
} else if (order1 == null && order2 != null) { } else if (order1 == null && order2 != null) {
return 1; return 1;
} else if (order2 == null) { } else if (order2 == null) {
return -1; return -1;
} else { } else {
return order1 - order2; return order1 - order2;

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java

@ -267,7 +267,7 @@ public class MqttClientTest extends AbstractContainerTest {
ListenableFuture<ResponseEntity> future = service.submit(() -> { ListenableFuture<ResponseEntity> future = service.submit(() -> {
try { try {
return restClient.getRestTemplate() return restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", .postForEntity(HTTPS_URL + "/api/rpc/twoway/{deviceId}",
mapper.readTree(serverRpcPayload.toString()), String.class, mapper.readTree(serverRpcPayload.toString()), String.class,
device.getId()); device.getId());
} catch (IOException e) { } catch (IOException e) {

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java

@ -263,7 +263,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
ListenableFuture<ResponseEntity> future = service.submit(() -> { ListenableFuture<ResponseEntity> future = service.submit(() -> {
try { try {
return restClient.getRestTemplate() return restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", .postForEntity(HTTPS_URL + "/api/rpc/twoway/{deviceId}",
mapper.readTree(serverRpcPayload.toString()), String.class, mapper.readTree(serverRpcPayload.toString()), String.class,
createdDevice.getId()); createdDevice.getId());
} catch (IOException e) { } catch (IOException e) {

6
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -1811,12 +1811,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) {
restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); restTemplate.postForLocation(baseURL + "/api/rpc/oneway/{deviceId}", requestBody, deviceId.getId());
} }
public JsonNode handleTwoWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { public JsonNode handleTwoWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) {
return restTemplate.exchange( return restTemplate.exchange(
baseURL + "/api/plugins/rpc/twoway/{deviceId}", baseURL + "/api/rpc/twoway/{deviceId}",
HttpMethod.POST, HttpMethod.POST,
new HttpEntity<>(requestBody), new HttpEntity<>(requestBody),
new ParameterizedTypeReference<JsonNode>() { new ParameterizedTypeReference<JsonNode>() {
@ -2034,7 +2034,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
params.put("useStrictDataTypes", Boolean.toString(useStrictDataTypes)); params.put("useStrictDataTypes", Boolean.toString(useStrictDataTypes));
StringBuilder urlBuilder = new StringBuilder(baseURL); StringBuilder urlBuilder = new StringBuilder(baseURL);
urlBuilder.append("/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&orderBy={orderBy}"); urlBuilder.append("/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&limit={limit}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&orderBy={orderBy}");
if (startTime != null) { if (startTime != null) {
urlBuilder.append("&startTs={startTs}"); urlBuilder.append("&startTs={startTs}");

34
rest-client/src/main/resources/logback.xml

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE configuration>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -188,7 +188,7 @@ class AlarmState {
setAlarmConditionMetadata(ruleState, metaData); setAlarmConditionMetadata(ruleState, metaData);
TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : ServiceQueue.MAIN, "ALARM", TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : ServiceQueue.MAIN, "ALARM",
originator, msg != null ? msg.getCustomerId() : null, metaData, data); originator, msg != null ? msg.getCustomerId() : null, metaData, data);
ctx.tellNext(newMsg, relationType); ctx.enqueueForTellNext(newMsg, relationType);
} }
protected void setAlarmConditionMetadata(AlarmRuleState ruleState, TbMsgMetaData metaData) { protected void setAlarmConditionMetadata(AlarmRuleState ruleState, TbMsgMetaData metaData) {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java

@ -120,7 +120,7 @@ public class TbSendRPCRequestNode implements TbNode {
ctx.enqueueForTellNext(next, TbRelationTypes.SUCCESS); ctx.enqueueForTellNext(next, TbRelationTypes.SUCCESS);
} else { } else {
TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name()));
ctx.tellFailure(next, new RuntimeException(ruleEngineDeviceRpcResponse.getError().get().name())); ctx.enqueueForTellFailure(next, ruleEngineDeviceRpcResponse.getError().get().name());
} }
}); });
ctx.ack(msg); ctx.ack(msg);

8
ui-ngx/src/app/core/api/widget-subscription.ts

@ -663,7 +663,7 @@ export class WidgetSubscription implements IWidgetSubscription {
if (!this.rpcEnabled) { if (!this.rpcEnabled) {
return throwError(new Error('Rpc disabled!')); return throwError(new Error('Rpc disabled!'));
} else { } else {
if (this.rpcRejection && this.rpcRejection.status !== 408) { if (this.rpcRejection && this.rpcRejection.status !== 504) {
this.rpcRejection = null; this.rpcRejection = null;
this.rpcErrorText = null; this.rpcErrorText = null;
this.callbacks.onRpcErrorCleared(this); this.callbacks.onRpcErrorCleared(this);
@ -715,12 +715,10 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
this.executingRpcRequest = this.executingSubjects.length > 0; this.executingRpcRequest = this.executingSubjects.length > 0;
this.callbacks.rpcStateChanged(this); this.callbacks.rpcStateChanged(this);
if (!this.executingRpcRequest || rejection.status === 408) { if (!this.executingRpcRequest || rejection.status === 504) {
this.rpcRejection = rejection; this.rpcRejection = rejection;
if (rejection.status === 408) { if (rejection.status === 504) {
this.rpcErrorText = 'Request Timeout.'; this.rpcErrorText = 'Request Timeout.';
} else if (rejection.status === 409) {
this.rpcErrorText = 'Device is offline.';
} else { } else {
this.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; this.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
const error = this.extractRejectionErrorText(rejection); const error = this.extractRejectionErrorText(rejection);

4
ui-ngx/src/app/core/http/device.service.ts

@ -130,11 +130,11 @@ export class DeviceService {
} }
public sendOneWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable<any> { public sendOneWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable<any> {
return this.http.post<Device>(`/api/plugins/rpc/oneway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); return this.http.post<Device>(`/api/rpc/oneway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config));
} }
public sendTwoWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable<any> { public sendTwoWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable<any> {
return this.http.post<Device>(`/api/plugins/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); return this.http.post<Device>(`/api/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config));
} }
public findByQuery(query: DeviceSearchQuery, public findByQuery(query: DeviceSearchQuery,

4
ui-ngx/src/app/core/interceptors/global-http-interceptor.ts

@ -47,7 +47,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
private internalUrlPrefixes = [ private internalUrlPrefixes = [
'/api/auth/token', '/api/auth/token',
'/api/plugins/rpc' '/api/rpc'
]; ];
private activeRequests = 0; private activeRequests = 0;
@ -142,7 +142,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
} }
} else if (errorResponse.status === 0 || errorResponse.status === -1) { } else if (errorResponse.status === 0 || errorResponse.status === -1) {
this.showError('Unable to connect'); this.showError('Unable to connect');
} else if (!req.url.startsWith('/api/plugins/rpc')) { } else if (!(req.url.startsWith('/api/rpc') || req.url.startsWith('/api/plugins/rpc'))) {
if (errorResponse.status === 404) { if (errorResponse.status === 404) {
if (!ignoreErrors) { if (!ignoreErrors) {
this.showError(req.method + ': ' + req.url + '<br/>' + this.showError(req.method + ': ' + req.url + '<br/>' +

3
ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts

@ -129,7 +129,8 @@ export abstract class StateControllerComponent implements IStateControllerCompon
protected updateStateParam(newState: string, replaceCurrentHistoryUrl = false) { protected updateStateParam(newState: string, replaceCurrentHistoryUrl = false) {
this.currentState = newState; this.currentState = newState;
if (this.syncStateWithQueryParam) { if (this.syncStateWithQueryParam) {
const queryParams: Params = {state: encodeURIComponent(this.currentState)}; const state = this.currentState ? encodeURIComponent(this.currentState) : this.currentState;
const queryParams: Params = {state};
this.ngZone.run(() => { this.ngZone.run(() => {
this.router.navigate( this.router.navigate(
[], [],

5
ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html

@ -60,10 +60,9 @@
<mat-error *ngIf="lwm2mConfigFormGroup.get('client.key').hasError('pattern')"> <mat-error *ngIf="lwm2mConfigFormGroup.get('client.key').hasError('pattern')">
{{ 'device.lwm2m-security-config.client-key-pattern' | translate }} {{ 'device.lwm2m-security-config.client-key-pattern' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="(lwm2mConfigFormGroup.get('client.key').hasError('maxlength') || <mat-error *ngIf="lwm2mConfigFormGroup.get('client.key').hasError('length')">
lwm2mConfigFormGroup.get('client.key').hasError('minlength'))">
{{ 'device.lwm2m-security-config.client-key-length' | translate: { {{ 'device.lwm2m-security-config.client-key-length' | translate: {
count: lenMaxKeyClient count: allowLengthKey.join(', ')
} }} } }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>

19
ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts

@ -16,6 +16,7 @@
import { Component, forwardRef, OnDestroy } from '@angular/core'; import { Component, forwardRef, OnDestroy } from '@angular/core';
import { import {
AbstractControl,
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
FormGroup, FormGroup,
@ -64,6 +65,7 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va
securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType);
credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap;
lenMaxKeyClient = LEN_MAX_PSK; lenMaxKeyClient = LEN_MAX_PSK;
allowLengthKey: number[];
private destroy$ = new Subject(); private destroy$ = new Subject();
private propagateChange = (v: any) => {}; private propagateChange = (v: any) => {};
@ -119,7 +121,7 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va
config.key = this.lwm2mConfigFormGroup.get('client.key').value; config.key = this.lwm2mConfigFormGroup.get('client.key').value;
break; break;
} }
this.lwm2mConfigFormGroup.get('client').patchValue(config, {emitEvent: false}); this.lwm2mConfigFormGroup.get('client').reset(config, {emitEvent: false});
this.securityConfigClientUpdateValidators(type); this.securityConfigClientUpdateValidators(type);
} }
@ -135,11 +137,13 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va
break; break;
case Lwm2mSecurityType.PSK: case Lwm2mSecurityType.PSK:
this.lenMaxKeyClient = LEN_MAX_PSK; this.lenMaxKeyClient = LEN_MAX_PSK;
this.allowLengthKey = [32, 64, LEN_MAX_PSK];
this.setValidatorsPskRpk(mode); this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('client.identity').enable({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.identity').enable({emitEvent: false});
break; break;
case Lwm2mSecurityType.RPK: case Lwm2mSecurityType.RPK:
this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK;
this.allowLengthKey = [LEN_MAX_PUBLIC_KEY_RPK];
this.setValidatorsPskRpk(mode); this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false});
break; break;
@ -164,13 +168,22 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va
this.lwm2mConfigFormGroup.get('client.key').setValidators([ this.lwm2mConfigFormGroup.get('client.key').setValidators([
Validators.required, Validators.required,
Validators.pattern(KEY_REGEXP_HEX_DEC), Validators.pattern(KEY_REGEXP_HEX_DEC),
Validators.maxLength(this.lenMaxKeyClient), this.maxLength(this.allowLengthKey)
Validators.minLength(this.lenMaxKeyClient)
]); ]);
this.lwm2mConfigFormGroup.get('client.key').enable({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.key').enable({emitEvent: false});
this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false});
} }
private maxLength(keyLengths: number[]) {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (keyLengths.some(len => value.length === len)) {
return null;
}
return {length: true};
};
}
private initLwm2mConfigForm = (): FormGroup => { private initLwm2mConfigForm = (): FormGroup => {
const formGroup = this.fb.group({ const formGroup = this.fb.group({
client: this.fb.group({ client: this.fb.group({

2
ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts

@ -168,7 +168,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
const credentialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; const credentialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType;
switch (credentialsType) { switch (credentialsType) {
case DeviceCredentialsType.ACCESS_TOKEN: case DeviceCredentialsType.ACCESS_TOKEN:
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,32}$/)]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});

6
ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts

@ -101,9 +101,9 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control
}), }),
clientSettings: this.fb.group({ clientSettings: this.fb.group({
powerMode: [PowerMode.DRX, Validators.required], powerMode: [PowerMode.DRX, Validators.required],
edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], edrxCycle: [{disabled: true, value: 0}, Validators.required],
psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], psmActivityTimer: [{disabled: true, value: 0}, Validators.required],
pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]] pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required]
})} })}
); );
this.coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').valueChanges.pipe( this.coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').valueChanges.pipe(

6
ui-ngx/src/app/modules/home/components/profile/device/common/device-profile-common.module.ts

@ -15,13 +15,15 @@
/// ///
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { PowerModeSettingComponent } from '@home/components/profile/device/common/power-mode-setting.component'; import { PowerModeSettingComponent } from './power-mode-setting.component';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TimeUnitSelectComponent } from './time-unit-select.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
PowerModeSettingComponent PowerModeSettingComponent,
TimeUnitSelectComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

55
ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html

@ -26,38 +26,27 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<section class="mat-block" fxFlex *ngIf="parentForm.get('powerMode').value === 'E_DRX'"> <section class="mat-block" fxFlex *ngIf="parentForm.get('powerMode').value === 'E_DRX'">
<mat-form-field class="mat-block" fxFlex> <tb-time-unit-select [labelText]="'device-profile.edrx-cycle'"
<mat-label>{{ 'device-profile.edrx-cycle' | translate }}</mat-label> [requiredText]="'device-profile.edrx-cycle-required'"
<input matInput type="number" min="0" formControlName="edrxCycle" required> [patternText]="'device-profile.edrx-cycle-pattern'"
<mat-error *ngIf="parentForm.get('edrxCycle').hasError('required')"> [minTime]="20480"
{{ 'device-profile.edrx-cycle-required' | translate }} [minText]="'device-profile.edrx-cycle-min'"
</mat-error> formControlName="edrxCycle">
<mat-error *ngIf="parentForm.get('edrxCycle').hasError('pattern') || </tb-time-unit-select>
parentForm.get('edrxCycle').hasError('min')"> <tb-time-unit-select [labelText]="'device-profile.paging-transmission-window'"
{{ 'device-profile.edrx-cycle-pattern' | translate }} [requiredText]="'device-profile.paging-transmission-window-required'"
</mat-error> [patternText]="'device-profile.paging-transmission-window-pattern'"
</mat-form-field> [minTime]="2000"
<mat-form-field class="mat-block" fxFlex> [minText]="'device-profile.paging-transmission-window-min'"
<mat-label>{{ 'device-profile.paging-transmission-window' | translate }}</mat-label> formControlName="pagingTransmissionWindow">
<input matInput type="number" min="0" formControlName="pagingTransmissionWindow" required> </tb-time-unit-select>
<mat-error *ngIf="parentForm.get('pagingTransmissionWindow').hasError('required')">
{{ 'device-profile.paging-transmission-window-required' | translate }}
</mat-error>
<mat-error *ngIf="parentForm.get('pagingTransmissionWindow').hasError('pattern') ||
parentForm.get('pagingTransmissionWindow').hasError('min')">
{{ 'device-profile.paging-transmission-window-pattern' | translate }}
</mat-error>
</mat-form-field>
</section> </section>
<mat-form-field class="mat-block" fxFlex *ngIf="parentForm.get('powerMode').value === 'PSM'"> <tb-time-unit-select *ngIf="parentForm.get('powerMode').value === 'PSM'"
<mat-label>{{ 'device-profile.psm-activity-timer' | translate }}</mat-label> [labelText]="'device-profile.psm-activity-timer'"
<input matInput type="number" min="0" formControlName="psmActivityTimer" required> [requiredText]="'device-profile.psm-activity-timer-required'"
<mat-error *ngIf="parentForm.get('psmActivityTimer').hasError('required')"> [patternText]="'device-profile.psm-activity-timer-pattern'"
{{ 'device-profile.psm-activity-timer-required' | translate }} [minTime]="2000"
</mat-error> [minText]="'device-profile.psm-activity-timer-min'"
<mat-error *ngIf="parentForm.get('psmActivityTimer').hasError('pattern') || formControlName="psmActivityTimer">
parentForm.get('psmActivityTimer').hasError('min')"> </tb-time-unit-select>
{{ 'device-profile.psm-activity-timer-pattern' | translate }}
</mat-error>
</mat-form-field>
</section> </section>

13
ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.ts

@ -16,7 +16,12 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { PowerMode, PowerModeTranslationMap } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; import {
DEFAULT_EDRX_CYCLE,
DEFAULT_PAGING_TRANSMISSION_WINDOW, DEFAULT_PSM_ACTIVITY_TIMER,
PowerMode,
PowerModeTranslationMap
} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -63,13 +68,13 @@ export class PowerModeSettingComponent implements OnInit, OnDestroy {
private disablePSKMode() { private disablePSKMode() {
this.parentForm.get('psmActivityTimer').disable({emitEvent: false}); this.parentForm.get('psmActivityTimer').disable({emitEvent: false});
this.parentForm.get('psmActivityTimer').reset(0, {emitEvent: false}); this.parentForm.get('psmActivityTimer').reset(DEFAULT_PSM_ACTIVITY_TIMER, {emitEvent: false});
} }
private disableEdrxMode() { private disableEdrxMode() {
this.parentForm.get('edrxCycle').disable({emitEvent: false}); this.parentForm.get('edrxCycle').disable({emitEvent: false});
this.parentForm.get('edrxCycle').reset(0, {emitEvent: false}); this.parentForm.get('edrxCycle').reset(DEFAULT_EDRX_CYCLE, {emitEvent: false});
this.parentForm.get('pagingTransmissionWindow').disable({emitEvent: false}); this.parentForm.get('pagingTransmissionWindow').disable({emitEvent: false});
this.parentForm.get('pagingTransmissionWindow').reset(0, {emitEvent: false}); this.parentForm.get('pagingTransmissionWindow').reset(DEFAULT_PAGING_TRANSMISSION_WINDOW, {emitEvent: false});
} }
} }

40
ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html

@ -0,0 +1,40 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section [formGroup]="timeUnitSelectFormGroup" fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field class="mat-block" fxFlex>
<mat-label>{{ labelText | translate }}</mat-label>
<input matInput type="number" min="0" formControlName="time" required>
<mat-error *ngIf="timeUnitSelectFormGroup.get('time').hasError('required')">
{{ requiredText | translate }}
</mat-error>
<mat-error *ngIf="timeUnitSelectFormGroup.get('time').hasError('pattern')">
{{ patternText | translate }}
</mat-error>
<mat-error *ngIf="timeUnitSelectFormGroup.get('time').hasError('min')">
{{ (minText || patternText) | translate : {min: minTime/1000} }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>device-profile.condition-duration-time-unit</mat-label>
<mat-select formControlName="unit">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
{{ timeUnitTranslations.get(timeUnit) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</section>

194
ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts

@ -0,0 +1,194 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
FullTimeUnit,
HOUR,
MINUTE,
SECOND,
TimeUnit,
TimeUnitMilli,
timeUnitTranslationMap
} from '@shared/models/time/time.models';
import { isDefinedAndNotNull, isNumber } from '@core/utils';
interface FormGroupModel {
time: number;
unit: FullTimeUnit;
}
@Component({
selector: 'tb-time-unit-select',
templateUrl: './time-unit-select.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeUnitSelectComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => TimeUnitSelectComponent),
multi: true
}
]
})
export class TimeUnitSelectComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
timeUnitSelectFormGroup: FormGroup;
timeUnits = Object.values({...TimeUnitMilli, ...TimeUnit}).filter(item => item !== TimeUnit.DAYS);
timeUnitTranslations = timeUnitTranslationMap;
private destroy$ = new Subject();
private timeUnitToTimeMap = new Map<FullTimeUnit, number>(
[
[TimeUnitMilli.MILLISECONDS, 1],
[TimeUnit.SECONDS, SECOND],
[TimeUnit.MINUTES, MINUTE],
[TimeUnit.HOURS, HOUR]
]
);
private timeToTimeUnitMap = new Map<number, FullTimeUnit>(
[
[SECOND, TimeUnitMilli.MILLISECONDS],
[MINUTE, TimeUnit.SECONDS],
[HOUR, TimeUnit.MINUTES]
]
);
@Input()
disabled: boolean;
@Input()
labelText: string;
@Input()
requiredText: string;
@Input()
patternText: string;
@Input()
minTime = 0;
@Input()
minText: string;
private propagateChange = (v: any) => {
}
constructor(private fb: FormBuilder) {
}
ngOnInit() {
this.timeUnitSelectFormGroup = this.fb.group({
time: [0, [Validators.required, Validators.min(this.minTime), Validators.pattern('[0-9]*')]],
unit: [TimeUnitMilli.MILLISECONDS]
});
this.timeUnitSelectFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
this.updateModel(value);
});
this.timeUnitSelectFormGroup.get('unit').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((unit: FullTimeUnit) => {
if (this.minTime > 0) {
const unitTime = this.timeUnitToTimeMap.get(unit);
const validationTime = Math.ceil(this.minTime / unitTime);
this.timeUnitSelectFormGroup.get('time').setValidators([Validators.required, Validators.min(validationTime), Validators.pattern('[0-9]*')]);
this.timeUnitSelectFormGroup.get('time').updateValueAndValidity({emitEvent: false});
}
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(fn: any) {
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
if (this.disabled) {
this.timeUnitSelectFormGroup.disable({emitEvent: false});
} else {
this.timeUnitSelectFormGroup.enable({emitEvent: false});
}
}
writeValue(value: number) {
const formValue: FormGroupModel = {
time: 0,
unit: TimeUnitMilli.MILLISECONDS
};
if (isDefinedAndNotNull(value) && isNumber(value) && value >= 0) {
formValue.unit = this.calculateTimeUnit(value);
formValue.time = value / this.timeUnitToTimeMap.get(formValue.unit);
}
this.timeUnitSelectFormGroup.reset(formValue, {emitEvent: false});
this.timeUnitSelectFormGroup.get('unit').updateValueAndValidity({onlySelf: true});
}
validate(): ValidationErrors | null {
return this.timeUnitSelectFormGroup.valid ? null : {
timeUnitSelect: false
};
}
private updateModel(value: FormGroupModel) {
const time = value.time * this.timeUnitToTimeMap.get(value.unit);
this.propagateChange(time);
}
private calculateTimeUnit(value: number): FullTimeUnit {
if (value === 0) {
return TimeUnitMilli.MILLISECONDS;
}
const iterators = this.timeToTimeUnitMap[Symbol.iterator]();
let iterator = iterators.next();
while (!iterator.done) {
if (!Number.isInteger(value / iterator.value[0])) {
return iterator.value[1];
}
iterator = iterators.next();
}
return TimeUnit.HOURS;
}
}

2
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html

@ -23,11 +23,9 @@
<tb-profile-lwm2m-object-list <tb-profile-lwm2m-object-list
(addList)="addObjectsList($event)" (addList)="addObjectsList($event)"
(removeList)="removeObjectsList($event)" (removeList)="removeObjectsList($event)"
[required]="required"
formControlName="objectIds"> formControlName="objectIds">
</tb-profile-lwm2m-object-list> </tb-profile-lwm2m-object-list>
<tb-profile-lwm2m-observe-attr-telemetry <tb-profile-lwm2m-observe-attr-telemetry
[required]="required"
formControlName="observeAttrTelemetry"> formControlName="observeAttrTelemetry">
</tb-profile-lwm2m-observe-attr-telemetry> </tb-profile-lwm2m-observe-attr-telemetry>
</section> </section>

28
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts

@ -18,7 +18,8 @@ import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
FormGroup, NG_VALIDATORS, FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
ValidationErrors, ValidationErrors,
Validator, Validator,
@ -30,11 +31,14 @@ import {
BingingMode, BingingMode,
BingingModeTranslationsMap, BingingModeTranslationsMap,
DEFAULT_BINDING, DEFAULT_BINDING,
DEFAULT_EDRX_CYCLE,
DEFAULT_FW_UPDATE_RESOURCE, DEFAULT_FW_UPDATE_RESOURCE,
DEFAULT_ID_SERVER, DEFAULT_ID_SERVER,
DEFAULT_LIFE_TIME, DEFAULT_LIFE_TIME,
DEFAULT_MIN_PERIOD, DEFAULT_MIN_PERIOD,
DEFAULT_NOTIF_IF_DESIBLED, DEFAULT_NOTIF_IF_DESIBLED,
DEFAULT_PAGING_TRANSMISSION_WINDOW,
DEFAULT_PSM_ACTIVITY_TIMER,
DEFAULT_SW_UPDATE_RESOURCE, DEFAULT_SW_UPDATE_RESOURCE,
getDefaultBootstrapServerSecurityConfig, getDefaultBootstrapServerSecurityConfig,
getDefaultLwM2MServerSecurityConfig, getDefaultLwM2MServerSecurityConfig,
@ -50,7 +54,7 @@ import {
TELEMETRY TELEMETRY
} from './lwm2m-profile-config.models'; } from './lwm2m-profile-config.models';
import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceProfileService } from '@core/http/device-profile.service';
import { deepClone, isDefinedAndNotNull, isEmpty } from '@core/utils'; import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils';
import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point';
import { Direction } from '@shared/models/page/sort-order'; import { Direction } from '@shared/models/page/sort-order';
import _ from 'lodash'; import _ from 'lodash';
@ -100,8 +104,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
constructor(private fb: FormBuilder, constructor(private fb: FormBuilder,
private deviceProfileService: DeviceProfileService) { private deviceProfileService: DeviceProfileService) {
this.lwm2mDeviceProfileFormGroup = this.fb.group({ this.lwm2mDeviceProfileFormGroup = this.fb.group({
objectIds: [null, Validators.required], objectIds: [null],
observeAttrTelemetry: [null, Validators.required], observeAttrTelemetry: [null],
bootstrap: this.fb.group({ bootstrap: this.fb.group({
servers: this.fb.group({ servers: this.fb.group({
binding: [DEFAULT_BINDING], binding: [DEFAULT_BINDING],
@ -120,9 +124,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
fwUpdateResource: [{value: '', disabled: true}, []], fwUpdateResource: [{value: '', disabled: true}, []],
swUpdateResource: [{value: '', disabled: true}, []], swUpdateResource: [{value: '', disabled: true}, []],
powerMode: [PowerMode.DRX, Validators.required], powerMode: [PowerMode.DRX, Validators.required],
edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], edrxCycle: [{disabled: true, value: 0}, Validators.required],
psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], psmActivityTimer: [{disabled: true, value: 0}, Validators.required],
pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required],
compositeOperationsSupport: [false] compositeOperationsSupport: [false]
}) })
}); });
@ -183,7 +187,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
async writeValue(value: Lwm2mProfileConfigModels | null) { async writeValue(value: Lwm2mProfileConfigModels | null) {
if (isDefinedAndNotNull(value) && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { if (isDefinedAndNotNull(value) && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) {
this.configurationValue = value; this.configurationValue = value;
const defaultFormSettings = !(value.observeAttr.attribute.length && value.observeAttr.telemetry.length); const defaultFormSettings = value.clientLwM2mSettings.fwUpdateStrategy === 1 &&
isUndefined(value.clientLwM2mSettings.fwUpdateResource);
if (defaultFormSettings) { if (defaultFormSettings) {
await this.defaultProfileConfig(); await this.defaultProfileConfig();
} }
@ -246,9 +251,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
fwUpdateResource: this.configurationValue.clientLwM2mSettings.fwUpdateResource || '', fwUpdateResource: this.configurationValue.clientLwM2mSettings.fwUpdateResource || '',
swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '', swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '',
powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX, powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX,
edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || 0, edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || DEFAULT_EDRX_CYCLE,
pagingTransmissionWindow: this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || 0, pagingTransmissionWindow:
psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || 0, this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || DEFAULT_PAGING_TRANSMISSION_WINDOW,
psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || DEFAULT_PSM_ACTIVITY_TIMER,
compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false
} }
}, },

11
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html

@ -44,9 +44,14 @@
<span [innerHTML]="objectLwm2m.keyId + ': ' + (objectLwm2m.name | highlight: searchText)"></span> <span [innerHTML]="objectLwm2m.keyId + ': ' + (objectLwm2m.name | highlight: searchText)"></span>
</mat-option> </mat-option>
<mat-option *ngIf="!(filteredObjectsList | async)?.length" [value]="null"> <mat-option *ngIf="!(filteredObjectsList | async)?.length" [value]="null">
<span> <div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
{{ 'device-profile.lwm2m.no-objects-matching' | translate: {object: searchText} }} <span translate>device-profile.lwm2m.no-objects-found</span>
</span> </div>
<ng-template #searchNotEmpty>
<span>
{{ 'device-profile.lwm2m.no-objects-matching' | translate:{object: truncate.transform(searchText, true, 6, &apos;...&apos;)} }}
</span>
</ng-template>
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
<mat-error *ngIf="lwm2mListFormGroup.get('objectsList').hasError('required')"> <mat-error *ngIf="lwm2mListFormGroup.get('objectsList').hasError('required')">

8
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts

@ -33,6 +33,7 @@ import { DeviceProfileService } from '@core/http/device-profile.service';
import { Direction } from '@shared/models/page/sort-order'; import { Direction } from '@shared/models/page/sort-order';
import { isDefined, isDefinedAndNotNull, isString } from '@core/utils'; import { isDefined, isDefinedAndNotNull, isString } from '@core/utils';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
@Component({ @Component({
selector: 'tb-profile-lwm2m-object-list', selector: 'tb-profile-lwm2m-object-list',
@ -82,7 +83,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
private propagateChange = (v: any) => { private propagateChange = (v: any) => {
} }
constructor(private deviceProfileService: DeviceProfileService, constructor(public truncate: TruncatePipe,
private deviceProfileService: DeviceProfileService,
private fb: FormBuilder) { private fb: FormBuilder) {
this.lwm2mListFormGroup = this.fb.group({ this.lwm2mListFormGroup = this.fb.group({
objectsList: [this.objectsList], objectsList: [this.objectsList],
@ -196,6 +198,10 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
} }
} }
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
private clear(value = '', emitEvent = true) { private clear(value = '', emitEvent = true) {
this.objectInput.nativeElement.value = value; this.objectInput.nativeElement.value = value;
this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value, {emitEvent}); this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value, {emitEvent});

20
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts

@ -42,7 +42,9 @@ export const INSTANCES_ID_VALUE_MAX = 65535;
export const DEFAULT_OTA_UPDATE_PROTOCOL = 'coap://'; export const DEFAULT_OTA_UPDATE_PROTOCOL = 'coap://';
export const DEFAULT_FW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC; export const DEFAULT_FW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC;
export const DEFAULT_SW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC; export const DEFAULT_SW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC;
export const DEFAULT_PSM_ACTIVITY_TIMER = 10000;
export const DEFAULT_EDRX_CYCLE = 81000;
export const DEFAULT_PAGING_TRANSMISSION_WINDOW = 10000;
export enum BingingMode { export enum BingingMode {
U = 'U', U = 'U',
@ -164,8 +166,8 @@ export interface ClientLwM2mSettings {
clientOnlyObserveAfterConnect: number; clientOnlyObserveAfterConnect: number;
fwUpdateStrategy: number; fwUpdateStrategy: number;
swUpdateStrategy: number; swUpdateStrategy: number;
fwUpdateResource: string; fwUpdateResource?: string;
swUpdateResource: string; swUpdateResource?: string;
powerMode: PowerMode; powerMode: PowerMode;
edrxCycle?: number; edrxCycle?: number;
pagingTransmissionWindow?: number; pagingTransmissionWindow?: number;
@ -178,7 +180,7 @@ export interface ObservableAttributes {
attribute: string[]; attribute: string[];
telemetry: string[]; telemetry: string[];
keyName: {}; keyName: {};
attributeLwm2m?: AttributesNameValueMap[]; attributeLwm2m: AttributesNameValueMap;
} }
export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig {
@ -193,13 +195,13 @@ export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecu
export function getDefaultBootstrapServerSecurityConfig(): ServerSecurityConfig { export function getDefaultBootstrapServerSecurityConfig(): ServerSecurityConfig {
return { return {
bootstrapServerAccountTimeout: DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT,
clientHoldOffTime: DEFAULT_CLIENT_HOLD_OFF_TIME,
host: DEFAULT_LOCAL_HOST_NAME, host: DEFAULT_LOCAL_HOST_NAME,
port: DEFAULT_PORT_BOOTSTRAP_NO_SEC, port: DEFAULT_PORT_BOOTSTRAP_NO_SEC,
securityMode: securityConfigMode.NO_SEC, securityMode: securityConfigMode.NO_SEC,
serverPublicKey: '',
clientHoldOffTime: DEFAULT_CLIENT_HOLD_OFF_TIME,
serverId: DEFAULT_ID_BOOTSTRAP, serverId: DEFAULT_ID_BOOTSTRAP,
bootstrapServerAccountTimeout: DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT serverPublicKey: ''
}; };
} }
@ -216,7 +218,7 @@ export function getDefaultProfileObserveAttrConfig(): ObservableAttributes {
attribute: [], attribute: [],
telemetry: [], telemetry: [],
keyName: {}, keyName: {},
attributeLwm2m: [] attributeLwm2m: {}
}; };
} }
@ -225,8 +227,6 @@ export function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSetting
clientOnlyObserveAfterConnect: 1, clientOnlyObserveAfterConnect: 1,
fwUpdateStrategy: 1, fwUpdateStrategy: 1,
swUpdateStrategy: 1, swUpdateStrategy: 1,
fwUpdateResource: DEFAULT_FW_UPDATE_RESOURCE,
swUpdateResource: DEFAULT_SW_UPDATE_RESOURCE,
powerMode: PowerMode.DRX, powerMode: PowerMode.DRX,
compositeOperationsSupport: false compositeOperationsSupport: false
}; };

4
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts

@ -495,8 +495,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
const serverAttributes: AttributeData[] = []; const serverAttributes: AttributeData[] = [];
const sharedAttributes: AttributeData[] = []; const sharedAttributes: AttributeData[] = [];
const telemetry: AttributeData[] = []; const telemetry: AttributeData[] = [];
for (const key of this.visibleKeys(toSave)) { for (const key of toSave.keys) {
const currentValue = this.multipleInputFormGroup.get(key.formId).value; const currentValue = key.settings.dataKeyHidden ? key.value : this.multipleInputFormGroup.get(key.formId).value;
if (!isEqual(currentValue, key.value) || this.settings.updateAllValues) { if (!isEqual(currentValue, key.value) || this.settings.updateAllValues) {
const attribute: AttributeData = { const attribute: AttributeData = {
key: key.name, key: key.name,

5
ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html

@ -16,6 +16,7 @@
--> -->
<div fxLayout="column" fxLayoutAlign="center center" style="width: 100%; height: 100%;"> <div fxLayout="column" fxLayoutAlign="center center" style="width: 100%; height: 100%;">
<canvas fxFlex #canvas [ngStyle]="{display: qrCodeText ? 'block' : 'none'}"></canvas> <canvas fxFlex #canvas [ngStyle]="{display: qrCodeText && !invalidQrCodeText ? 'block' : 'none'}"></canvas>
<div *ngIf="!qrCodeText" translate>entity.no-data</div> <div *ngIf="!qrCodeText && !invalidQrCodeText" translate>entity.no-data</div>
<div *ngIf="invalidQrCodeText" translate>widgets.invalid-qr-code-text</div>
</div> </div>

20
ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts

@ -19,7 +19,6 @@ import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import QRCode from 'qrcode';
import { import {
fillPattern, fillPattern,
parseData, parseData,
@ -30,6 +29,7 @@ import {
import { FormattedData } from '@home/components/widget/lib/maps/map-models'; import { FormattedData } from '@home/components/widget/lib/maps/map-models';
import { DatasourceData } from '@shared/models/widget.models'; import { DatasourceData } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isNumber, isObject } from '@core/utils';
interface QrCodeWidgetSettings { interface QrCodeWidgetSettings {
qrCodeTextPattern: string; qrCodeTextPattern: string;
@ -53,6 +53,7 @@ export class QrCodeWidgetComponent extends PageComponent implements OnInit, Afte
ctx: WidgetContext; ctx: WidgetContext;
qrCodeText: string; qrCodeText: string;
invalidQrCodeText = false;
private viewInited: boolean; private viewInited: boolean;
private scheduleUpdateCanvas: boolean; private scheduleUpdateCanvas: boolean;
@ -109,8 +110,13 @@ export class QrCodeWidgetComponent extends PageComponent implements OnInit, Afte
private updateQrCodeText(newQrCodeText: string): void { private updateQrCodeText(newQrCodeText: string): void {
if (this.qrCodeText !== newQrCodeText) { if (this.qrCodeText !== newQrCodeText) {
this.qrCodeText = newQrCodeText; this.qrCodeText = newQrCodeText;
if (this.qrCodeText) { if (!(isObject(newQrCodeText) || isNumber(newQrCodeText))) {
this.updateCanvas(); this.invalidQrCodeText = false;
if (this.qrCodeText) {
this.updateCanvas();
}
} else {
this.invalidQrCodeText = true;
} }
this.cd.detectChanges(); this.cd.detectChanges();
} }
@ -118,9 +124,11 @@ export class QrCodeWidgetComponent extends PageComponent implements OnInit, Afte
private updateCanvas() { private updateCanvas() {
if (this.viewInited) { if (this.viewInited) {
QRCode.toCanvas(this.canvasRef.nativeElement, this.qrCodeText); import('qrcode').then((QRCode) => {
this.canvasRef.nativeElement.style.width = 'auto'; QRCode.toCanvas(this.canvasRef.nativeElement, this.qrCodeText);
this.canvasRef.nativeElement.style.height = 'auto'; this.canvasRef.nativeElement.style.width = 'auto';
this.canvasRef.nativeElement.style.height = 'auto';
});
} else { } else {
this.scheduleUpdateCanvas = true; this.scheduleUpdateCanvas = true;
} }

6
ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts

@ -71,9 +71,9 @@ export class CoapDeviceTransportConfigurationComponent implements ControlValueAc
ngOnInit() { ngOnInit() {
this.coapDeviceTransportForm = this.fb.group({ this.coapDeviceTransportForm = this.fb.group({
powerMode: [null], powerMode: [null],
edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], edrxCycle: [{disabled: true, value: 0}, Validators.required],
psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], psmActivityTimer: [{disabled: true, value: 0}, Validators.required],
pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]] pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required]
}); });
this.coapDeviceTransportForm.valueChanges.pipe( this.coapDeviceTransportForm.valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)

6
ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts

@ -71,9 +71,9 @@ export class Lwm2mDeviceTransportConfigurationComponent implements ControlValueA
ngOnInit() { ngOnInit() {
this.lwm2mDeviceTransportConfigurationFormGroup = this.fb.group({ this.lwm2mDeviceTransportConfigurationFormGroup = this.fb.group({
powerMode: [null], powerMode: [null],
edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], edrxCycle: [{disabled: true, value: 0}, Validators.required],
psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], psmActivityTimer: [{disabled: true, value: 0}, Validators.required],
pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]] pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required]
}); });
this.lwm2mDeviceTransportConfigurationFormGroup.valueChanges.pipe( this.lwm2mDeviceTransportConfigurationFormGroup.valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)

2
ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
export const LEN_MAX_PSK = 64; export const LEN_MAX_PSK = 128;
export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PRIVATE_KEY = 134;
export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_RPK = 182;
export const LEN_MAX_PUBLIC_KEY_X509 = 3000; export const LEN_MAX_PUBLIC_KEY_X509 = 3000;

9
ui-ngx/src/app/shared/models/time/time.models.ts

@ -812,8 +812,15 @@ export enum TimeUnit {
DAYS = 'DAYS' DAYS = 'DAYS'
} }
export const timeUnitTranslationMap = new Map<TimeUnit, string>( export enum TimeUnitMilli {
MILLISECONDS = 'MILLISECONDS'
}
export type FullTimeUnit = TimeUnit | TimeUnitMilli;
export const timeUnitTranslationMap = new Map<FullTimeUnit, string>(
[ [
[TimeUnitMilli.MILLISECONDS, 'timeunit.milliseconds'],
[TimeUnit.SECONDS, 'timeunit.seconds'], [TimeUnit.SECONDS, 'timeunit.seconds'],
[TimeUnit.MINUTES, 'timeunit.minutes'], [TimeUnit.MINUTES, 'timeunit.minutes'],
[TimeUnit.HOURS, 'timeunit.hours'], [TimeUnit.HOURS, 'timeunit.hours'],

6
ui-ngx/src/assets/locale/locale.constant-cs_CZ.json

@ -74,7 +74,7 @@
"admin": { "admin": {
"general": "Obecné", "general": "Obecné",
"general-settings": "Obecná nastavení", "general-settings": "Obecná nastavení",
"home-settings": "Domácí nastavení", "home-settings": "Nastavení",
"outgoing-mail": "Odchozí email", "outgoing-mail": "Odchozí email",
"outgoing-mail-settings": "Nastavení odchozího emailu", "outgoing-mail-settings": "Nastavení odchozího emailu",
"system-settings": "Systémová nastavení", "system-settings": "Systémová nastavení",
@ -953,7 +953,7 @@
"credentials-type": "Typ přístupových údajů", "credentials-type": "Typ přístupových údajů",
"access-token": "Přístupový token", "access-token": "Přístupový token",
"access-token-required": "Přístupový token je povinný.", "access-token-required": "Přístupový token je povinný.",
"access-token-invalid": "Délka přístupového tokenu musí být od 1 do 20 znaků.", "access-token-invalid": "Délka přístupového tokenu musí být od 1 do 32 znaků.",
"rsa-key": "RSA veřejný klíč", "rsa-key": "RSA veřejný klíč",
"rsa-key-required": "RSA veřejný klíč je povinný.", "rsa-key-required": "RSA veřejný klíč je povinný.",
"lwm2m-security-config": { "lwm2m-security-config": {
@ -1229,7 +1229,7 @@
"drx": "Přerušovaný přenos (DRX)", "drx": "Přerušovaný přenos (DRX)",
"edrx": "Rozšířený přerušovaný přenos (eDRX)" "edrx": "Rozšířený přerušovaný přenos (eDRX)"
}, },
"edrx-cycle": "eDRX cyklus v milisekudnách", "edrx-cycle": "eDRX cyklus",
"edrx-cycle-required": "eDRX cyklus je povinný.", "edrx-cycle-required": "eDRX cyklus je povinný.",
"edrx-cycle-pattern": "eDRX cyklus musí být kladné číslo.", "edrx-cycle-pattern": "eDRX cyklus musí být kladné číslo.",
"lwm2m": { "lwm2m": {

2
ui-ngx/src/assets/locale/locale.constant-de_DE.json

@ -686,7 +686,7 @@
"credentials-type": "Art der Zugangsdaten", "credentials-type": "Art der Zugangsdaten",
"access-token": "Zugangs-Token", "access-token": "Zugangs-Token",
"access-token-required": "Zugangs-Token ist erforderlich.", "access-token-required": "Zugangs-Token ist erforderlich.",
"access-token-invalid": "Die Länge des Zugangs-Tokens muss zwischen 1 und 20 Zeichen betragen.", "access-token-invalid": "Die Länge des Zugangs-Tokens muss zwischen 1 und 32 Zeichen betragen.",
"rsa-key": "RSA öffentlicher Schlüssel", "rsa-key": "RSA öffentlicher Schlüssel",
"rsa-key-required": "RSA öffentlicher Schlüssel ist erforderlich.", "rsa-key-required": "RSA öffentlicher Schlüssel ist erforderlich.",
"secret": "Geheimnis", "secret": "Geheimnis",

2
ui-ngx/src/assets/locale/locale.constant-el_GR.json

@ -798,7 +798,7 @@
"credentials-type": "Τύπος διαπιστευτηρίων", "credentials-type": "Τύπος διαπιστευτηρίων",
"access-token": "Διακριτικό πρόσβασης", "access-token": "Διακριτικό πρόσβασης",
"access-token-required": "Απαιτείται διακριτικό πρόσβασης.", "access-token-required": "Απαιτείται διακριτικό πρόσβασης.",
"access-token-invalid": "Access token length must be from 1 to 20 characters.", "access-token-invalid": "Access token length must be from 1 to 32 characters.",
"rsa-key": "RSA public key", "rsa-key": "RSA public key",
"rsa-key-required": "Απαιτείται RSA public key.", "rsa-key-required": "Απαιτείται RSA public key.",
"secret": "Secret", "secret": "Secret",

16
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -956,7 +956,7 @@
"credentials-type": "Credentials type", "credentials-type": "Credentials type",
"access-token": "Access token", "access-token": "Access token",
"access-token-required": "Access token is required.", "access-token-required": "Access token is required.",
"access-token-invalid": "Access token length must be from 1 to 20 characters.", "access-token-invalid": "Access token length must be from 1 to 32 characters.",
"rsa-key": "RSA public key", "rsa-key": "RSA public key",
"rsa-key-required": "RSA public key is required.", "rsa-key-required": "RSA public key is required.",
"lwm2m-security-config": { "lwm2m-security-config": {
@ -1232,18 +1232,22 @@
"drx": "Discontinuous Reception (DRX)", "drx": "Discontinuous Reception (DRX)",
"edrx": "Extended Discontinuous Reception (eDRX)" "edrx": "Extended Discontinuous Reception (eDRX)"
}, },
"edrx-cycle": "eDRX cycle in milliseconds", "edrx-cycle": "eDRX cycle",
"edrx-cycle-required": "eDRX cycle is required.", "edrx-cycle-required": "eDRX cycle is required.",
"edrx-cycle-pattern": "eDRX cycle must be a positive integer.", "edrx-cycle-pattern": "eDRX cycle must be a positive integer.",
"paging-transmission-window": "Paging Transmission Window in milliseconds", "edrx-cycle-min": "Minimum number of eDRX cycle is {{ min }} seconds.",
"paging-transmission-window": "Paging Transmission Window",
"paging-transmission-window-required": "Paging transmission window is required.", "paging-transmission-window-required": "Paging transmission window is required.",
"paging-transmission-window-pattern": "Paging transmission window must be a positive integer.", "paging-transmission-window-pattern": "Paging transmission window must be a positive integer.",
"psm-activity-timer": "PSM Activity Timer in milliseconds", "paging-transmission-window-min": "Minimum number ofpPaging transmission window is {{ min }} seconds.",
"psm-activity-timer": "PSM Activity Timer",
"psm-activity-timer-required": "PSM activity timer is required.", "psm-activity-timer-required": "PSM activity timer is required.",
"psm-activity-timer-pattern": "PSM activity timer must be a positive integer.", "psm-activity-timer-pattern": "PSM activity timer must be a positive integer.",
"psm-activity-timer-min": "Minimum number of PSM activity timer is {{ min }} seconds.",
"lwm2m": { "lwm2m": {
"object-list": "Object list", "object-list": "Object list",
"object-list-empty": "No objects selected.", "object-list-empty": "No objects selected.",
"no-objects-found": "No objects found.",
"no-objects-matching": "No objects matching '{{object}}' were found.", "no-objects-matching": "No objects matching '{{object}}' were found.",
"model-tab": "LWM2M Model", "model-tab": "LWM2M Model",
"add-new-instances": "Add new instances", "add-new-instances": "Add new instances",
@ -2735,6 +2739,7 @@
} }
}, },
"timeunit": { "timeunit": {
"milliseconds": "Milliseconds",
"seconds": "Seconds", "seconds": "Seconds",
"minutes": "Minutes", "minutes": "Minutes",
"hours": "Hours", "hours": "Hours",
@ -3123,7 +3128,8 @@
"update-attribute": "Update attribute", "update-attribute": "Update attribute",
"update-timeseries": "Update timeseries", "update-timeseries": "Update timeseries",
"value": "Value" "value": "Value"
} },
"invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type"
}, },
"icon": { "icon": {
"icon": "Icon", "icon": "Icon",

2
ui-ngx/src/assets/locale/locale.constant-es_ES.json

@ -893,7 +893,7 @@
"credentials-type": "Tipo de credencial", "credentials-type": "Tipo de credencial",
"access-token": "Tóken de acceso", "access-token": "Tóken de acceso",
"access-token-required": "Access token requerido.", "access-token-required": "Access token requerido.",
"access-token-invalid": "Access token debe tener entre 1 a 20 caracteres.", "access-token-invalid": "Access token debe tener entre 1 a 32 caracteres.",
"rsa-key": "Clave pública RSA", "rsa-key": "Clave pública RSA",
"rsa-key-required": "Clave pública RSA requerida.", "rsa-key-required": "Clave pública RSA requerida.",
"client-id": "ID Cliente", "client-id": "ID Cliente",

2
ui-ngx/src/assets/locale/locale.constant-fa_IR.json

@ -640,7 +640,7 @@
"credentials-type": "نوع اعتبارنامه ها", "credentials-type": "نوع اعتبارنامه ها",
"access-token": "شناسه دسترسي", "access-token": "شناسه دسترسي",
"access-token-required": ".شناسه دسترسي مورد نياز است", "access-token-required": ".شناسه دسترسي مورد نياز است",
"access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 20 حرف باشد", "access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 32 حرف باشد",
"rsa-key": "RSA کليد عمومي", "rsa-key": "RSA کليد عمومي",
"rsa-key-required": ".مورد نياز است RSA کليد عمومي", "rsa-key-required": ".مورد نياز است RSA کليد عمومي",
"secret": "محرمانه", "secret": "محرمانه",

2
ui-ngx/src/assets/locale/locale.constant-fr_FR.json

@ -639,7 +639,7 @@
}, },
"device": { "device": {
"access-token": "Jeton d'accès", "access-token": "Jeton d'accès",
"access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 20 caractéres.", "access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 32 caractéres.",
"access-token-required": "Le jeton d'accès est requis.", "access-token-required": "Le jeton d'accès est requis.",
"accessTokenCopiedMessage": "Le jeton d'accès au dispositif a été copié dans le presse-papier", "accessTokenCopiedMessage": "Le jeton d'accès au dispositif a été copié dans le presse-papier",
"add": "Ajouter un dispositif", "add": "Ajouter un dispositif",

2
ui-ngx/src/assets/locale/locale.constant-it_IT.json

@ -665,7 +665,7 @@
"credentials-type": "Tipo credenziali", "credentials-type": "Tipo credenziali",
"access-token": "Token di accesso", "access-token": "Token di accesso",
"access-token-required": "Token di accesso obbligatorio.", "access-token-required": "Token di accesso obbligatorio.",
"access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 20 caratteri.", "access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 32 caratteri.",
"rsa-key": "Chiave pubblica RSA", "rsa-key": "Chiave pubblica RSA",
"rsa-key-required": "Chiave pubblica RSA obbligatoria.", "rsa-key-required": "Chiave pubblica RSA obbligatoria.",
"secret": "Secret", "secret": "Secret",

2
ui-ngx/src/assets/locale/locale.constant-ja_JA.json

@ -623,7 +623,7 @@
"credentials-type": "資格情報タイプ", "credentials-type": "資格情報タイプ",
"access-token": "アクセストークン", "access-token": "アクセストークン",
"access-token-required": "アクセストークンが必要です。", "access-token-required": "アクセストークンが必要です。",
"access-token-invalid": "アクセストークンの長さは、1〜20文字でなければなりません。", "access-token-invalid": "アクセストークンの長さは、1〜32文字でなければなりません。",
"rsa-key": "RSA公開鍵", "rsa-key": "RSA公開鍵",
"rsa-key-required": "RSA公開鍵が必要です。", "rsa-key-required": "RSA公開鍵が必要です。",
"secret": "秘密", "secret": "秘密",

2
ui-ngx/src/assets/locale/locale.constant-ka_GE.json

@ -683,7 +683,7 @@
"credentials-type": "მომხმარებლის ჩანაწერი", "credentials-type": "მომხმარებლის ჩანაწერი",
"access-token": "ტოკენი", "access-token": "ტოკენი",
"access-token-required": "ტოკენი სავალდებულოა", "access-token-required": "ტოკენი სავალდებულოა",
"access-token-invalid": "ტოკენის სიგრძე უნდა იყოს 1 დან 20 სიმბოლომდე", "access-token-invalid": "ტოკენის სიგრძე უნდა იყოს 1 დან 32 სიმბოლომდე",
"rsa-key": "ღია გასაღები RSA", "rsa-key": "ღია გასაღები RSA",
"rsa-key-required": "ღია გასაღები RSA აუცილებელია", "rsa-key-required": "ღია გასაღები RSA აუცილებელია",
"secret": "საიდუმლო", "secret": "საიდუმლო",

2
ui-ngx/src/assets/locale/locale.constant-ko_KR.json

@ -869,7 +869,7 @@
"credentials-type": "크리덴셜 타입", "credentials-type": "크리덴셜 타입",
"access-token": "억세스 토큰", "access-token": "억세스 토큰",
"access-token-required": "액세스 토큰을 입력하세요.", "access-token-required": "액세스 토큰을 입력하세요.",
"access-token-invalid": "액세스 토큰 길이는 1 - 20 자 여야합니다.", "access-token-invalid": "액세스 토큰 길이는 1 - 32 자 여야합니다.",
"rsa-key": "RSA public key", "rsa-key": "RSA public key",
"rsa-key-required": "RSA public key 를 입력하세요.", "rsa-key-required": "RSA public key 를 입력하세요.",
"client-id": "클라이언트 ID", "client-id": "클라이언트 ID",

2
ui-ngx/src/assets/locale/locale.constant-lv_LV.json

@ -641,7 +641,7 @@
"credentials-type": "Akreditācijas datu tips", "credentials-type": "Akreditācijas datu tips",
"access-token": "Piekļuves tokens", "access-token": "Piekļuves tokens",
"access-token-required": "Piekļuves tokens ir nepieciešams.", "access-token-required": "Piekļuves tokens ir nepieciešams.",
"access-token-invalid": "Piekļuves tokena garumam ir jābūt no 1 līdz 20 rakstzīmēm.", "access-token-invalid": "Piekļuves tokena garumam ir jābūt no 1 līdz 32 rakstzīmēm.",
"rsa-key": "RSA publiskā atslēga", "rsa-key": "RSA publiskā atslēga",
"rsa-key-required": "RSA publiskā atslēga ir nepieciešama.", "rsa-key-required": "RSA publiskā atslēga ir nepieciešama.",
"secret": "Noslēpums", "secret": "Noslēpums",

2
ui-ngx/src/assets/locale/locale.constant-pt_BR.json

@ -715,7 +715,7 @@
"credentials-type": "Tipo de credencial", "credentials-type": "Tipo de credencial",
"access-token": "Token de acesso", "access-token": "Token de acesso",
"access-token-required": "O token de acesso é obrigatório.", "access-token-required": "O token de acesso é obrigatório.",
"access-token-invalid": "O token de acesso deve ter de 1 a 20 caracteres.", "access-token-invalid": "O token de acesso deve ter de 1 a 32 caracteres.",
"rsa-key": "Chave pública RSA", "rsa-key": "Chave pública RSA",
"rsa-key-required": "A chave pública RSA é obrigatória.", "rsa-key-required": "A chave pública RSA é obrigatória.",
"secret": "Segredo", "secret": "Segredo",

4
ui-ngx/src/assets/locale/locale.constant-ro_RO.json

@ -674,7 +674,7 @@
"credentials-type": "Tip Credențiale", "credentials-type": "Tip Credențiale",
"access-token": "Token Acces", "access-token": "Token Acces",
"access-token-required": "Tokenul de acces este necesar", "access-token-required": "Tokenul de acces este necesar",
"access-token-invalid": "Dimensiunea tokenului de acces trebuie să fie de 1-20 caractere", "access-token-invalid": "Dimensiunea tokenului de acces trebuie să fie de 1-32 caractere",
"rsa-key": "Cheie RSA publică", "rsa-key": "Cheie RSA publică",
"rsa-key-required": "Cheia RSA publică key este necesară", "rsa-key-required": "Cheia RSA publică key este necesară",
"secret": "Cod Secret", "secret": "Cod Secret",
@ -1797,4 +1797,4 @@
"language": { "language": {
"language": "Limba" "language": "Limba"
} }
} }

2
ui-ngx/src/assets/locale/locale.constant-ru_RU.json

@ -677,7 +677,7 @@
"credentials-type": "Тип учетных данных", "credentials-type": "Тип учетных данных",
"access-token": "Токен", "access-token": "Токен",
"access-token-required": "Токен обязателен.", "access-token-required": "Токен обязателен.",
"access-token-invalid": "Длина токена должна быть от 1 до 20 символов.", "access-token-invalid": "Длина токена должна быть от 1 до 32 символов.",
"rsa-key": "Открытый ключ RSA", "rsa-key": "Открытый ключ RSA",
"rsa-key-required": "Открытый ключ RSA обязателен.", "rsa-key-required": "Открытый ключ RSA обязателен.",
"secret": "Секрет", "secret": "Секрет",

2
ui-ngx/src/assets/locale/locale.constant-sl_SI.json

@ -869,7 +869,7 @@
"credentials-type": "Vrsta poverilnic", "credentials-type": "Vrsta poverilnic",
"access-token": "Dostopni žeton", "access-token": "Dostopni žeton",
"access-token-required": "Zahtevan je žeton za dostop.", "access-token-required": "Zahtevan je žeton za dostop.",
"access-token-invalid": "Dolžina žetona za dostop mora biti od 1 do 20 znakov.", "access-token-invalid": "Dolžina žetona za dostop mora biti od 1 do 32 znakov.",
"rsa-key": "Javni ključ RSA", "rsa-key": "Javni ključ RSA",
"rsa-key-required": "Potreben je javni ključ RSA.", "rsa-key-required": "Potreben je javni ključ RSA.",
"client-id": "Client ID", "client-id": "Client ID",

2
ui-ngx/src/assets/locale/locale.constant-tr_TR.json

@ -667,7 +667,7 @@
"credentials-type": "Kimlik Bilgi Türü", "credentials-type": "Kimlik Bilgi Türü",
"access-token": "Erişim şifresi", "access-token": "Erişim şifresi",
"access-token-required": "Erişim şifresi gerekli.", "access-token-required": "Erişim şifresi gerekli.",
"access-token-invalid": "Erişim şifresi uzunluğu 1 ile 20 karakter arasında olmalıdır.", "access-token-invalid": "Erişim şifresi uzunluğu 1 ile 32 karakter arasında olmalıdır.",
"rsa-key": "RSA açık anahtarı", "rsa-key": "RSA açık anahtarı",
"rsa-key-required": "RSA açık anahtarı gerekli.", "rsa-key-required": "RSA açık anahtarı gerekli.",
"secret": "Secret", "secret": "Secret",

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save