Merge branch 'rc' into fix/3912-device-table
This commit is contained in:
		
						commit
						4f11a320c7
					
				@ -37,7 +37,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "tag": "icon",
 | 
					      "tag": "icon",
 | 
				
			||||||
      "stateRenderFunction": "var showIcon = ctx.properties.showIcon;\nif (showIcon) {\n    element.show();\n    var icon = ctx.properties.icon;\n    var iconSize = ctx.properties.iconSize;\n    var iconColor = ctx.properties.iconColor;\n    ctx.api.icon(element, icon, iconSize, iconColor, true);\n} else {\n    element.hide()\n}",
 | 
					      "stateRenderFunction": "var showIcon = ctx.properties.showIcon;\nvar showLabel = ctx.properties.label;\nif (showIcon) {\n    element.show();\n    var icon = ctx.properties.icon;\n    var iconSize = ctx.properties.iconSize;\n    var iconColor = ctx.properties.iconColor;\n    ctx.api.icon(element, icon, iconSize, iconColor, true);\n    if (!showLabel) {\n        element.transform({translateX: 83,translateY: 137});\n    }\n} else {\n    element.hide()\n}\n",
 | 
				
			||||||
      "actions": null
 | 
					      "actions": null
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB  | 
@ -37,7 +37,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "tag": "icon",
 | 
					      "tag": "icon",
 | 
				
			||||||
      "stateRenderFunction": "var showIcon = ctx.properties.showIcon;\nif (showIcon) {\n    element.show();\n    var icon = ctx.properties.icon;\n    var iconSize = ctx.properties.iconSize;\n    var iconColor = ctx.properties.iconColor;\n    ctx.api.icon(element, icon, iconSize, iconColor, true);\n} else {\n    element.hide()\n}",
 | 
					      "stateRenderFunction": "var showIcon = ctx.properties.showIcon;\nvar showLabel = ctx.properties.label;\nif (showIcon) {\n    element.show();\n    var icon = ctx.properties.icon;\n    var iconSize = ctx.properties.iconSize;\n    var iconColor = ctx.properties.iconColor;\n    ctx.api.icon(element, icon, iconSize, iconColor, true);\n    if (!showLabel) {\n        element.transform({translateX: 119, translateY: 137});\n    }\n} else {\n    element.hide()\n}",
 | 
				
			||||||
      "actions": null
 | 
					      "actions": null
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB  | 
@ -95,7 +95,7 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
 | 
				
			|||||||
                () -> {
 | 
					                () -> {
 | 
				
			||||||
                    RuleChain ruleChain = provider.apply(ruleChainId);
 | 
					                    RuleChain ruleChain = provider.apply(ruleChainId);
 | 
				
			||||||
                    if (ruleChain == null) {
 | 
					                    if (ruleChain == null) {
 | 
				
			||||||
                        return new RuleChainErrorActor.ActorCreator(systemContext, tenantId,
 | 
					                        return new RuleChainErrorActor.ActorCreator(systemContext, tenantId, ruleChainId,
 | 
				
			||||||
                                new RuleEngineException("Rule Chain with id: " + ruleChainId + " not found!"));
 | 
					                                new RuleEngineException("Rule Chain with id: " + ruleChainId + " not found!"));
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain);
 | 
					                        return new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain);
 | 
				
			||||||
 | 
				
			|||||||
@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j;
 | 
				
			|||||||
import org.thingsboard.server.actors.ActorSystemContext;
 | 
					import org.thingsboard.server.actors.ActorSystemContext;
 | 
				
			||||||
import org.thingsboard.server.actors.TbActor;
 | 
					import org.thingsboard.server.actors.TbActor;
 | 
				
			||||||
import org.thingsboard.server.actors.TbActorId;
 | 
					import org.thingsboard.server.actors.TbActorId;
 | 
				
			||||||
import org.thingsboard.server.actors.TbStringActorId;
 | 
					import org.thingsboard.server.actors.TbEntityActorId;
 | 
				
			||||||
import org.thingsboard.server.actors.service.ContextAwareActor;
 | 
					import org.thingsboard.server.actors.service.ContextAwareActor;
 | 
				
			||||||
import org.thingsboard.server.actors.service.ContextBasedCreator;
 | 
					import org.thingsboard.server.actors.service.ContextBasedCreator;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.RuleChainId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
import org.thingsboard.server.common.msg.TbActorMsg;
 | 
					import org.thingsboard.server.common.msg.TbActorMsg;
 | 
				
			||||||
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
 | 
					import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
 | 
				
			||||||
import org.thingsboard.server.common.msg.queue.RuleEngineException;
 | 
					import org.thingsboard.server.common.msg.queue.RuleEngineException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.UUID;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class RuleChainErrorActor extends ContextAwareActor {
 | 
					public class RuleChainErrorActor extends ContextAwareActor {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,9 +42,8 @@ public class RuleChainErrorActor extends ContextAwareActor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected boolean doProcess(TbActorMsg msg) {
 | 
					    protected boolean doProcess(TbActorMsg msg) {
 | 
				
			||||||
        if (msg instanceof RuleChainAwareMsg) {
 | 
					        if (msg instanceof RuleChainAwareMsg rcMsg) {
 | 
				
			||||||
            log.debug("[{}] Reply with {} for message {}", tenantId, error.getMessage(), msg);
 | 
					            log.debug("[{}] Reply with {} for message {}", tenantId, error.getMessage(), msg);
 | 
				
			||||||
            var rcMsg = (RuleChainAwareMsg) msg;
 | 
					 | 
				
			||||||
            rcMsg.getMsg().getCallback().onFailure(error);
 | 
					            rcMsg.getMsg().getCallback().onFailure(error);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@ -56,17 +54,19 @@ public class RuleChainErrorActor extends ContextAwareActor {
 | 
				
			|||||||
    public static class ActorCreator extends ContextBasedCreator {
 | 
					    public static class ActorCreator extends ContextBasedCreator {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private final TenantId tenantId;
 | 
					        private final TenantId tenantId;
 | 
				
			||||||
 | 
					        private final RuleChainId ruleChainId;
 | 
				
			||||||
        private final RuleEngineException error;
 | 
					        private final RuleEngineException error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleEngineException error) {
 | 
					        public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleEngineException error) {
 | 
				
			||||||
            super(context);
 | 
					            super(context);
 | 
				
			||||||
            this.tenantId = tenantId;
 | 
					            this.tenantId = tenantId;
 | 
				
			||||||
 | 
					            this.ruleChainId = ruleChainId;
 | 
				
			||||||
            this.error = error;
 | 
					            this.error = error;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public TbActorId createActorId() {
 | 
					        public TbActorId createActorId() {
 | 
				
			||||||
            return new TbStringActorId(UUID.randomUUID().toString());
 | 
					            return new TbEntityActorId(ruleChainId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode;
 | 
				
			|||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.ToString;
 | 
					import lombok.ToString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -48,7 +49,7 @@ public class TbSubscriptionsInfo {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected TbSubscriptionsInfo copy(int seqNumber) {
 | 
					    protected TbSubscriptionsInfo copy(int seqNumber) {
 | 
				
			||||||
        return new TbSubscriptionsInfo(notifications, alarms, tsAllKeys, tsKeys, attrAllKeys, attrKeys, seqNumber);
 | 
					        return new TbSubscriptionsInfo(notifications, alarms, tsAllKeys, tsKeys != null ? new HashSet<>(tsKeys) : null, attrAllKeys, attrKeys != null ? new HashSet<>(attrKeys) : null, seqNumber);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,57 +18,130 @@ package org.thingsboard.server.actors.tenant;
 | 
				
			|||||||
import org.junit.Before;
 | 
					import org.junit.Before;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
import org.thingsboard.server.actors.ActorSystemContext;
 | 
					import org.thingsboard.server.actors.ActorSystemContext;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.DefaultTbActorSystem;
 | 
				
			||||||
import org.thingsboard.server.actors.TbActorCtx;
 | 
					import org.thingsboard.server.actors.TbActorCtx;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.TbActorMailbox;
 | 
				
			||||||
import org.thingsboard.server.actors.TbActorRef;
 | 
					import org.thingsboard.server.actors.TbActorRef;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.TbActorSystem;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.TbActorSystemSettings;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.TbEntityActorId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.ruleChain.RuleChainActor;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg;
 | 
				
			||||||
 | 
					import org.thingsboard.server.actors.shared.RuleChainErrorActor;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.ApiUsageState;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.DeviceId;
 | 
					import org.thingsboard.server.common.data.id.DeviceId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.RuleChainId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
 | 
					import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.rule.RuleChain;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.msg.TbMsg;
 | 
				
			||||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
 | 
					import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
 | 
				
			||||||
import org.thingsboard.server.common.msg.queue.ServiceType;
 | 
					import org.thingsboard.server.common.msg.queue.ServiceType;
 | 
				
			||||||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
 | 
					import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
 | 
				
			||||||
import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg;
 | 
					import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg;
 | 
				
			||||||
 | 
					import org.thingsboard.server.dao.rule.RuleChainService;
 | 
				
			||||||
import org.thingsboard.server.dao.tenant.TenantService;
 | 
					import org.thingsboard.server.dao.tenant.TenantService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.queue.discovery.PartitionService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertNotNull;
 | 
				
			||||||
import static org.mockito.ArgumentMatchers.any;
 | 
					import static org.mockito.ArgumentMatchers.any;
 | 
				
			||||||
import static org.mockito.ArgumentMatchers.eq;
 | 
					import static org.mockito.ArgumentMatchers.eq;
 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
import static org.mockito.Mockito.never;
 | 
					import static org.mockito.Mockito.never;
 | 
				
			||||||
import static org.mockito.Mockito.reset;
 | 
					import static org.mockito.Mockito.reset;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.spy;
 | 
				
			||||||
import static org.mockito.Mockito.verify;
 | 
					import static org.mockito.Mockito.verify;
 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.actors.service.DefaultActorService.RULE_DISPATCHER_NAME;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class TenantActorTest {
 | 
					public class TenantActorTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TenantActor tenantActor;
 | 
					    TenantActor tenantActor;
 | 
				
			||||||
    TbActorCtx ctx;
 | 
					 | 
				
			||||||
    ActorSystemContext systemContext;
 | 
					    ActorSystemContext systemContext;
 | 
				
			||||||
 | 
					    RuleChainService ruleChainService;
 | 
				
			||||||
 | 
					    PartitionService partitionService;
 | 
				
			||||||
    TenantId tenantId = TenantId.SYS_TENANT_ID;
 | 
					    TenantId tenantId = TenantId.SYS_TENANT_ID;
 | 
				
			||||||
    DeviceId deviceId = DeviceId.fromString("78bf9b26-74ef-4af2-9cfb-ad6cf24ad2ec");
 | 
					    DeviceId deviceId = DeviceId.fromString("78bf9b26-74ef-4af2-9cfb-ad6cf24ad2ec");
 | 
				
			||||||
 | 
					    RuleChainId ruleChainId = new RuleChainId(UUID.fromString("48cfa2b0-3dca-11ef-8d1a-37c2894cc59c"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Before
 | 
					    @Before
 | 
				
			||||||
    public void setUp() throws Exception {
 | 
					    public void setUp() throws Exception {
 | 
				
			||||||
        systemContext = mock(ActorSystemContext.class);
 | 
					        systemContext = mock(ActorSystemContext.class);
 | 
				
			||||||
        ctx = mock(TbActorCtx.class);
 | 
					        ruleChainService = mock(RuleChainService.class);
 | 
				
			||||||
 | 
					        partitionService = mock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        TbServiceInfoProvider serviceInfoProvider = mock(TbServiceInfoProvider.class);
 | 
				
			||||||
 | 
					        TbApiUsageStateService apiUsageService = mock(TbApiUsageStateService.class);
 | 
				
			||||||
 | 
					        TenantService tenantService = mock(TenantService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        when(systemContext.getRuleChainService()).thenReturn(ruleChainService);
 | 
				
			||||||
        tenantActor = (TenantActor) new TenantActor.ActorCreator(systemContext, tenantId).createActor();
 | 
					        tenantActor = (TenantActor) new TenantActor.ActorCreator(systemContext, tenantId).createActor();
 | 
				
			||||||
        when(systemContext.getTenantService()).thenReturn(mock(TenantService.class));
 | 
					
 | 
				
			||||||
        tenantActor.init(ctx);
 | 
					        when(tenantService.findTenantById(tenantId)).thenReturn(mock());
 | 
				
			||||||
        tenantActor.cantFindTenant = false;
 | 
					        when(systemContext.getTenantService()).thenReturn(tenantService);
 | 
				
			||||||
 | 
					        when(serviceInfoProvider.isService(ServiceType.TB_CORE)).thenReturn(true);
 | 
				
			||||||
 | 
					        when(serviceInfoProvider.isService(ServiceType.TB_RULE_ENGINE)).thenReturn(true);
 | 
				
			||||||
 | 
					        when(systemContext.getServiceInfoProvider()).thenReturn(serviceInfoProvider);
 | 
				
			||||||
 | 
					        when(partitionService.isManagedByCurrentService(tenantId)).thenReturn(true);
 | 
				
			||||||
 | 
					        when(systemContext.getPartitionService()).thenReturn(partitionService);
 | 
				
			||||||
 | 
					        when(systemContext.getApiUsageStateService()).thenReturn(apiUsageService);
 | 
				
			||||||
 | 
					        when(apiUsageService.getApiUsageState(tenantId)).thenReturn(new ApiUsageState());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void deleteDeviceTest() {
 | 
					    public void deleteDeviceTest() throws Exception {
 | 
				
			||||||
 | 
					        TbActorCtx ctx = mock(TbActorCtx.class);
 | 
				
			||||||
 | 
					        tenantActor.init(ctx);
 | 
				
			||||||
        TbActorRef deviceActorRef = mock(TbActorRef.class);
 | 
					        TbActorRef deviceActorRef = mock(TbActorRef.class);
 | 
				
			||||||
        when(systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(new TopicPartitionInfo("Main", tenantId, 0,true));
 | 
					        when(systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(new TopicPartitionInfo("Main", tenantId, 0, true));
 | 
				
			||||||
        when(ctx.getOrCreateChildActor(any(), any(), any(), any())).thenReturn(deviceActorRef);
 | 
					        when(ctx.getOrCreateChildActor(any(), any(), any(), any())).thenReturn(deviceActorRef);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ComponentLifecycleMsg componentLifecycleMsg = new ComponentLifecycleMsg(tenantId, deviceId, ComponentLifecycleEvent.DELETED);
 | 
					        ComponentLifecycleMsg componentLifecycleMsg = new ComponentLifecycleMsg(tenantId, deviceId, ComponentLifecycleEvent.DELETED);
 | 
				
			||||||
        tenantActor.doProcess(componentLifecycleMsg);
 | 
					        tenantActor.doProcess(componentLifecycleMsg);
 | 
				
			||||||
        verify(deviceActorRef).tellWithHighPriority(eq(new DeviceDeleteMsg(tenantId, deviceId)));
 | 
					        verify(deviceActorRef).tellWithHighPriority(eq(new DeviceDeleteMsg(tenantId, deviceId)));
 | 
				
			||||||
 | 
					 | 
				
			||||||
        reset(ctx, deviceActorRef);
 | 
					        reset(ctx, deviceActorRef);
 | 
				
			||||||
        when(systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(new TopicPartitionInfo("Main", tenantId, 1,false));
 | 
					
 | 
				
			||||||
 | 
					        when(systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(new TopicPartitionInfo("Main", tenantId, 1, false));
 | 
				
			||||||
        tenantActor.doProcess(componentLifecycleMsg);
 | 
					        tenantActor.doProcess(componentLifecycleMsg);
 | 
				
			||||||
        verify(ctx, never()).getOrCreateChildActor(any(), any(), any(), any());
 | 
					        verify(ctx, never()).getOrCreateChildActor(any(), any(), any(), any());
 | 
				
			||||||
        verify(deviceActorRef, never()).tellWithHighPriority(any());
 | 
					        verify(deviceActorRef, never()).tellWithHighPriority(any());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void ruleChainErrorActorTest() throws Exception {
 | 
				
			||||||
 | 
					        TbActorSystemSettings settings = new TbActorSystemSettings(0, 0, 0);
 | 
				
			||||||
 | 
					        TbActorSystem system = spy(new DefaultTbActorSystem(settings));
 | 
				
			||||||
 | 
					        system.createDispatcher(RULE_DISPATCHER_NAME, mock());
 | 
				
			||||||
 | 
					        TbActorMailbox tenantCtx = new TbActorMailbox(system, settings, null, mock(), mock(), null);
 | 
				
			||||||
 | 
					        tenantActor.init(tenantCtx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        TbMsg msg = mock(TbMsg.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        when(ruleChainService.findRuleChainById(tenantId, ruleChainId)).thenReturn(new RuleChain(ruleChainId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RuleChainToRuleChainMsg ruleChainMsg = new RuleChainToRuleChainMsg(ruleChainId, null, msg, null);
 | 
				
			||||||
 | 
					        tenantActor.doProcess(ruleChainMsg);
 | 
				
			||||||
 | 
					        verify(system).createChildActor(eq(RULE_DISPATCHER_NAME), any(RuleChainActor.ActorCreator.class), any());
 | 
				
			||||||
 | 
					        reset(system);
 | 
				
			||||||
 | 
					        tenantActor.doProcess(ruleChainMsg);
 | 
				
			||||||
 | 
					        verify(system, never()).createChildActor(any(), any(), any());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Delete rule-chain
 | 
				
			||||||
 | 
					        TbActorRef ruleChainActor = system.getActor(new TbEntityActorId(ruleChainId));
 | 
				
			||||||
 | 
					        assertNotNull(ruleChainActor);
 | 
				
			||||||
 | 
					        system.stop(ruleChainActor);
 | 
				
			||||||
 | 
					        when(ruleChainService.findRuleChainById(tenantId, ruleChainId)).thenReturn(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tenantActor.doProcess(ruleChainMsg);
 | 
				
			||||||
 | 
					        verify(system).createChildActor(eq(RULE_DISPATCHER_NAME), any(RuleChainErrorActor.ActorCreator.class), any());
 | 
				
			||||||
 | 
					        reset(system);
 | 
				
			||||||
 | 
					        tenantActor.doProcess(ruleChainMsg);
 | 
				
			||||||
 | 
					        verify(system, never()).createChildActor(any(), any(), any());
 | 
				
			||||||
 | 
					        system.stop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2024 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.service.subscription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.qos.logback.classic.Logger;
 | 
				
			||||||
 | 
					import ch.qos.logback.classic.spi.ILoggingEvent;
 | 
				
			||||||
 | 
					import ch.qos.logback.core.read.ListAppender;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.Futures;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.ListenableFuture;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.ListeningExecutorService;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.MoreExecutors;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.RandomStringUtils;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.AfterEach;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.BeforeEach;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.test.util.ReflectionTestUtils;
 | 
				
			||||||
 | 
					import org.thingsboard.server.cache.limits.RateLimitService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.DeviceId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.limit.LimitedApi;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
 | 
				
			||||||
 | 
					import org.thingsboard.server.gen.transport.TransportProtos;
 | 
				
			||||||
 | 
					import org.thingsboard.server.queue.discovery.PartitionService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.ws.WebSocketSessionRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					import java.util.concurrent.Executors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertFalse;
 | 
				
			||||||
 | 
					import static org.mockito.ArgumentMatchers.any;
 | 
				
			||||||
 | 
					import static org.mockito.ArgumentMatchers.eq;
 | 
				
			||||||
 | 
					import static org.mockito.ArgumentMatchers.nullable;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class DefaultTbLocalSubscriptionServiceTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ListAppender<ILoggingEvent> testLogAppender;
 | 
				
			||||||
 | 
					    TbLocalSubscriptionService subscriptionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @BeforeEach
 | 
				
			||||||
 | 
					    public void setUp() throws Exception {
 | 
				
			||||||
 | 
					        Logger logger = (Logger) LoggerFactory.getLogger(DefaultTbLocalSubscriptionService.class);
 | 
				
			||||||
 | 
					        testLogAppender = new ListAppender<>();
 | 
				
			||||||
 | 
					        testLogAppender.start();
 | 
				
			||||||
 | 
					        logger.addAppender(testLogAppender);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RateLimitService rateLimitService = mock();
 | 
				
			||||||
 | 
					        when(rateLimitService.checkRateLimit(eq(LimitedApi.WS_SUBSCRIPTIONS), any(Object.class), nullable(String.class))).thenReturn(true);
 | 
				
			||||||
 | 
					        PartitionService partitionService = mock();
 | 
				
			||||||
 | 
					        when(partitionService.resolve(any(), any(), any())).thenReturn(TopicPartitionInfo.builder().build());
 | 
				
			||||||
 | 
					        subscriptionService = new DefaultTbLocalSubscriptionService(mock(), mock(), mock(), partitionService, mock(), mock(), mock(), rateLimitService);
 | 
				
			||||||
 | 
					        ReflectionTestUtils.setField(subscriptionService, "serviceId", "serviceId");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @AfterEach
 | 
				
			||||||
 | 
					    public void tearDown() {
 | 
				
			||||||
 | 
					        if (testLogAppender != null) {
 | 
				
			||||||
 | 
					            testLogAppender.stop();
 | 
				
			||||||
 | 
					            Logger logger = (Logger) LoggerFactory.getLogger(DefaultTbLocalSubscriptionService.class);
 | 
				
			||||||
 | 
					            logger.detachAppender(testLogAppender);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void addSubscriptionConcurrentModificationTest() throws Exception {
 | 
				
			||||||
 | 
					        ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
 | 
				
			||||||
 | 
					        TenantId tenantId = new TenantId(UUID.randomUUID());
 | 
				
			||||||
 | 
					        DeviceId deviceId = new DeviceId(UUID.randomUUID());
 | 
				
			||||||
 | 
					        WebSocketSessionRef sessionRef = mock();
 | 
				
			||||||
 | 
					        ReflectionTestUtils.setField(subscriptionService, "subscriptionUpdateExecutor", executorService);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<ListenableFuture<?>> futures = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            subscriptionService.onCoreStartupMsg(TransportProtos.CoreStartupMsg.newBuilder().addAllPartitions(List.of(0)).getDefaultInstanceForType());
 | 
				
			||||||
 | 
					            for (int i = 0; i < 50; i++) {
 | 
				
			||||||
 | 
					                futures.add(executorService.submit(() -> subscriptionService.addSubscription(createSubscription(tenantId, deviceId), sessionRef)));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Futures.allAsList(futures).get();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            executorService.shutdownNow();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<ILoggingEvent> logs = testLogAppender.list;
 | 
				
			||||||
 | 
					        boolean exceptionLogged = logs.stream()
 | 
				
			||||||
 | 
					                .filter(event -> event.getThrowableProxy() != null)
 | 
				
			||||||
 | 
					                .map(event -> event.getThrowableProxy().getClassName())
 | 
				
			||||||
 | 
					                .anyMatch(log -> log.equals("java.util.ConcurrentModificationException"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertFalse(exceptionLogged, "Detected ConcurrentModificationException!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private TbSubscription<?> createSubscription(TenantId tenantId, EntityId entityId) {
 | 
				
			||||||
 | 
					        Map<String, Long> keys = new HashMap<>();
 | 
				
			||||||
 | 
					        for (int i = 0; i < 50; i++) {
 | 
				
			||||||
 | 
					            keys.put(RandomStringUtils.randomAlphanumeric(5), 1L);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return TbAttributeSubscription.builder()
 | 
				
			||||||
 | 
					                .tenantId(tenantId)
 | 
				
			||||||
 | 
					                .entityId(entityId)
 | 
				
			||||||
 | 
					                .subscriptionId(1)
 | 
				
			||||||
 | 
					                .sessionId(RandomStringUtils.randomAlphanumeric(5))
 | 
				
			||||||
 | 
					                .keyStates(keys)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -42,6 +42,7 @@ public class Lwm2mTestHelper {
 | 
				
			|||||||
    public static final int RESOURCE_ID_11 = 11;
 | 
					    public static final int RESOURCE_ID_11 = 11;
 | 
				
			||||||
    public static final int RESOURCE_ID_14 = 14;
 | 
					    public static final int RESOURCE_ID_14 = 14;
 | 
				
			||||||
    public static final int RESOURCE_ID_15 = 15;
 | 
					    public static final int RESOURCE_ID_15 = 15;
 | 
				
			||||||
 | 
					    public static final int RESOURCE_ID_5700 = 5700;
 | 
				
			||||||
    public static final int RESOURCE_INSTANCE_ID_0 = 0;
 | 
					    public static final int RESOURCE_INSTANCE_ID_0 = 0;
 | 
				
			||||||
    public static final int RESOURCE_INSTANCE_ID_2 = 2;
 | 
					    public static final int RESOURCE_INSTANCE_ID_2 = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,6 +52,12 @@ public class Lwm2mTestHelper {
 | 
				
			|||||||
    public static final String RESOURCE_ID_NAME_19_0_2 = "dataCreationTime";
 | 
					    public static final String RESOURCE_ID_NAME_19_0_2 = "dataCreationTime";
 | 
				
			||||||
    public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite";
 | 
					    public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite";
 | 
				
			||||||
    public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription";
 | 
					    public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription";
 | 
				
			||||||
 | 
					    public static final String RESOURCE_ID_NAME_3303_12_5700 = "sensorValue";
 | 
				
			||||||
 | 
					    public static final double RESOURCE_ID_3303_12_5700_VALUE_0 = 25.05d;
 | 
				
			||||||
 | 
					    public static final double RESOURCE_ID_3303_12_5700_VALUE_1 = 35.12d;
 | 
				
			||||||
 | 
					    public static long RESOURCE_ID_3303_12_5700_TS_0 = 0;
 | 
				
			||||||
 | 
					    public static long RESOURCE_ID_3303_12_5700_TS_1 = 0;
 | 
				
			||||||
 | 
					    public static final int RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS = 3000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public enum LwM2MClientState {
 | 
					    public enum LwM2MClientState {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -72,8 +79,8 @@ public class Lwm2mTestHelper {
 | 
				
			|||||||
        ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"),
 | 
					        ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"),
 | 
				
			||||||
        ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"),
 | 
					        ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"),
 | 
				
			||||||
        ON_EXPECTED_ERROR(16, "onUnexpectedError"),
 | 
					        ON_EXPECTED_ERROR(16, "onUnexpectedError"),
 | 
				
			||||||
        ON_READ_CONNECTION_ID (17, "onReadConnection"),
 | 
					        ON_READ_CONNECTION_ID(17, "onReadConnection"),
 | 
				
			||||||
        ON_WRITE_CONNECTION_ID (18, "onWriteConnection");
 | 
					        ON_WRITE_CONNECTION_ID(18, "onWriteConnection");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public int code;
 | 
					        public int code;
 | 
				
			||||||
        public String type;
 | 
					        public String type;
 | 
				
			||||||
 | 
				
			|||||||
@ -137,7 +137,6 @@ public class LwM2MTestClient {
 | 
				
			|||||||
    private Map<LwM2MClientState, Integer> clientDtlsCid;
 | 
					    private Map<LwM2MClientState, Integer> clientDtlsCid;
 | 
				
			||||||
    private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
 | 
					    private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
 | 
				
			||||||
    private LwM2mClientContext clientContext;
 | 
					    private LwM2mClientContext clientContext;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void init(Security security, Security securityBs, int port, boolean isRpc,
 | 
					    public void init(Security security, Security securityBs, int port, boolean isRpc,
 | 
				
			||||||
                     LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
 | 
					                     LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
 | 
				
			||||||
                     LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode,
 | 
					                     LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode,
 | 
				
			||||||
@ -159,11 +158,11 @@ public class LwM2MTestClient {
 | 
				
			|||||||
            initializer.setClassForObject(SECURITY, Security.class);
 | 
					            initializer.setClassForObject(SECURITY, Security.class);
 | 
				
			||||||
            initializer.setInstancesForObject(SECURITY, instances);
 | 
					            initializer.setInstancesForObject(SECURITY, instances);
 | 
				
			||||||
            // SERVER
 | 
					            // SERVER
 | 
				
			||||||
           Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60));
 | 
					            Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60));
 | 
				
			||||||
            lwm2mServer.setId(serverId);
 | 
					            lwm2mServer.setId(serverId);
 | 
				
			||||||
            Server  serverBs = new Server(shortServerIdBs0, TimeUnit.MINUTES.toSeconds(60));
 | 
					            Server serverBs = new Server(shortServerIdBs0, TimeUnit.MINUTES.toSeconds(60));
 | 
				
			||||||
            serverBs.setId(serverIdBs);
 | 
					            serverBs.setId(serverIdBs);
 | 
				
			||||||
             instances = new LwM2mInstanceEnabler[]{serverBs, lwm2mServer};
 | 
					            instances = new LwM2mInstanceEnabler[]{serverBs, lwm2mServer};
 | 
				
			||||||
            initializer.setClassForObject(SERVER, Server.class);
 | 
					            initializer.setClassForObject(SERVER, Server.class);
 | 
				
			||||||
            initializer.setInstancesForObject(SERVER, instances);
 | 
					            initializer.setInstancesForObject(SERVER, instances);
 | 
				
			||||||
        } else if (securityBs != null) {
 | 
					        } else if (securityBs != null) {
 | 
				
			||||||
@ -177,7 +176,7 @@ public class LwM2MTestClient {
 | 
				
			|||||||
            // SERVER
 | 
					            // SERVER
 | 
				
			||||||
            Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60));
 | 
					            Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60));
 | 
				
			||||||
            lwm2mServer.setId(serverId);
 | 
					            lwm2mServer.setId(serverId);
 | 
				
			||||||
            initializer.setInstancesForObject(SERVER, lwm2mServer );
 | 
					            initializer.setInstancesForObject(SERVER, lwm2mServer);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor));
 | 
					        initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor));
 | 
				
			||||||
@ -239,11 +238,11 @@ public class LwM2MTestClient {
 | 
				
			|||||||
        boolean supportDeprecatedCiphers = false;
 | 
					        boolean supportDeprecatedCiphers = false;
 | 
				
			||||||
        clientCoapConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers);
 | 
					        clientCoapConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (cIdLength!= null) {
 | 
					        if (cIdLength != null) {
 | 
				
			||||||
            setDtlsConnectorConfigCidLength(clientCoapConfig, cIdLength);
 | 
					            setDtlsConnectorConfigCidLength(clientCoapConfig, cIdLength);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (cIdLength!= null) {
 | 
					        if (cIdLength != null) {
 | 
				
			||||||
            setDtlsConnectorConfigCidLength(clientCoapConfig, cIdLength);
 | 
					            setDtlsConnectorConfigCidLength(clientCoapConfig, cIdLength);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -262,12 +261,12 @@ public class LwM2MTestClient {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Configure Registration Engine
 | 
					        // Configure Registration Engine
 | 
				
			||||||
        DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
 | 
					        DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
 | 
				
			||||||
            // old
 | 
					        // old
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * Force reconnection/rehandshake on registration update.
 | 
					         * Force reconnection/rehandshake on registration update.
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        int comPeriodInSec = 5;
 | 
					        int comPeriodInSec = 5;
 | 
				
			||||||
        if (comPeriodInSec > 0)   engineFactory.setCommunicationPeriod(comPeriodInSec * 1000);
 | 
					        if (comPeriodInSec > 0) engineFactory.setCommunicationPeriod(comPeriodInSec * 1000);
 | 
				
			||||||
//        engineFactory.setCommunicationPeriod(5000); // old
 | 
					//        engineFactory.setCommunicationPeriod(5000); // old
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * By default client will try to resume DTLS session by using abbreviated Handshake. This option force to always do a full handshake."
 | 
					         * By default client will try to resume DTLS session by using abbreviated Handshake. This option force to always do a full handshake."
 | 
				
			||||||
@ -288,7 +287,7 @@ public class LwM2MTestClient {
 | 
				
			|||||||
        builder.setDataSenders(new ManualDataSender());
 | 
					        builder.setDataSenders(new ManualDataSender());
 | 
				
			||||||
        builder.setRegistrationEngineFactory(engineFactory);
 | 
					        builder.setRegistrationEngineFactory(engineFactory);
 | 
				
			||||||
        Map<ContentFormat, NodeDecoder> decoders = new HashMap<>();
 | 
					        Map<ContentFormat, NodeDecoder> decoders = new HashMap<>();
 | 
				
			||||||
        Map<ContentFormat, NodeEncoder> encoders =  new HashMap<>();
 | 
					        Map<ContentFormat, NodeEncoder> encoders = new HashMap<>();
 | 
				
			||||||
        if (supportFormatOnly_SenMLJSON_SenMLCBOR) {
 | 
					        if (supportFormatOnly_SenMLJSON_SenMLCBOR) {
 | 
				
			||||||
//                decoders.put(ContentFormat.OPAQUE, new LwM2mNodeOpaqueDecoder());
 | 
					//                decoders.put(ContentFormat.OPAQUE, new LwM2mNodeOpaqueDecoder());
 | 
				
			||||||
            decoders.put(ContentFormat.CBOR, new LwM2mNodeCborDecoder());
 | 
					            decoders.put(ContentFormat.CBOR, new LwM2mNodeCborDecoder());
 | 
				
			||||||
 | 
				
			|||||||
@ -26,16 +26,20 @@ import org.eclipse.leshan.core.request.ContentFormat;
 | 
				
			|||||||
import org.eclipse.leshan.core.request.argument.Arguments;
 | 
					import org.eclipse.leshan.core.request.argument.Arguments;
 | 
				
			||||||
import org.eclipse.leshan.core.response.ExecuteResponse;
 | 
					import org.eclipse.leshan.core.response.ExecuteResponse;
 | 
				
			||||||
import org.eclipse.leshan.core.response.ReadResponse;
 | 
					import org.eclipse.leshan.core.response.ReadResponse;
 | 
				
			||||||
 | 
					import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.security.auth.Destroyable;
 | 
					import javax.security.auth.Destroyable;
 | 
				
			||||||
import java.math.BigDecimal;
 | 
					import java.math.BigDecimal;
 | 
				
			||||||
import java.math.RoundingMode;
 | 
					import java.math.RoundingMode;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.time.Instant;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
import java.util.concurrent.ScheduledExecutorService;
 | 
					import java.util.concurrent.ScheduledExecutorService;
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable {
 | 
					public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable {
 | 
				
			||||||
@ -46,7 +50,9 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
 | 
				
			|||||||
    private double maxMeasuredValue = currentTemp;
 | 
					    private double maxMeasuredValue = currentTemp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private LeshanClient leshanClient;
 | 
					    private LeshanClient leshanClient;
 | 
				
			||||||
    private List<Double> containingValues;
 | 
					    private int cntRead_5700;
 | 
				
			||||||
 | 
					    private int cntIdentitySystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected static final Random RANDOM = new Random();
 | 
					    protected static final Random RANDOM = new Random();
 | 
				
			||||||
    private static final List<Integer> supportedResources = Arrays.asList(5601, 5602, 5700, 5701);
 | 
					    private static final List<Integer> supportedResources = Arrays.asList(5601, 5602, 5700, 5701);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,7 +63,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
 | 
				
			|||||||
    public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) {
 | 
					    public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            if (id != null) this.setId(id);
 | 
					            if (id != null) this.setId(id);
 | 
				
			||||||
        executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS);
 | 
					            executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS);
 | 
				
			||||||
        } catch (Throwable e) {
 | 
					        } catch (Throwable e) {
 | 
				
			||||||
            log.error("[{}]Throwable", e.toString());
 | 
					            log.error("[{}]Throwable", e.toString());
 | 
				
			||||||
            e.printStackTrace();
 | 
					            e.printStackTrace();
 | 
				
			||||||
@ -73,15 +79,18 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
 | 
				
			|||||||
            case 5602:
 | 
					            case 5602:
 | 
				
			||||||
                return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue));
 | 
					                return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue));
 | 
				
			||||||
            case 5700:
 | 
					            case 5700:
 | 
				
			||||||
                if (identity == LwM2mServer.SYSTEM)  {
 | 
					                if (identity == LwM2mServer.SYSTEM) { // return value for ForCollectedValue
 | 
				
			||||||
                    setTemperature();
 | 
					                    cntIdentitySystem++;
 | 
				
			||||||
                    setData();
 | 
					                    return ReadResponse.success(resourceId, cntIdentitySystem == 1 ?
 | 
				
			||||||
 | 
					                            RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                cntRead_5700++;
 | 
				
			||||||
 | 
					                if (cntRead_5700 == 1) {            // read value after start
 | 
				
			||||||
                    return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp));
 | 
					                    return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp));
 | 
				
			||||||
                } else if (this.getId() == 12 && this.leshanClient != null)  {
 | 
					 | 
				
			||||||
                    containingValues = new ArrayList<>();
 | 
					 | 
				
			||||||
                    sendCollected(5700);
 | 
					 | 
				
			||||||
                    return ReadResponse.success(resourceId, getData());
 | 
					 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (this.getId() == 12 && this.leshanClient != null) {
 | 
				
			||||||
 | 
					                        sendCollected();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp));
 | 
					                    return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            case 5701:
 | 
					            case 5701:
 | 
				
			||||||
@ -117,10 +126,11 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void setTemperature(){
 | 
					    private void setTemperature() {
 | 
				
			||||||
        float delta = (RANDOM.nextInt(20) - 10) / 10f;
 | 
					        float delta = (RANDOM.nextInt(20) - 10) / 10f;
 | 
				
			||||||
        currentTemp += delta;
 | 
					        currentTemp += delta;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) {
 | 
					    private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) {
 | 
				
			||||||
        if (newTemperature > maxMeasuredValue) {
 | 
					        if (newTemperature > maxMeasuredValue) {
 | 
				
			||||||
            maxMeasuredValue = newTemperature;
 | 
					            maxMeasuredValue = newTemperature;
 | 
				
			||||||
@ -143,7 +153,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
 | 
				
			|||||||
        return supportedResources;
 | 
					        return supportedResources;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected void setLeshanClient(LeshanClient leshanClient){
 | 
					    protected void setLeshanClient(LeshanClient leshanClient) {
 | 
				
			||||||
        this.leshanClient = leshanClient;
 | 
					        this.leshanClient = leshanClient;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -151,40 +161,26 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
 | 
				
			|||||||
    public void destroy() {
 | 
					    public void destroy() {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void sendCollected(int resourceId) {
 | 
					    private void sendCollected() {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            int resourceId = 5700;
 | 
				
			||||||
            LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next();
 | 
					            LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next();
 | 
				
			||||||
            ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME,
 | 
					            ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME,
 | 
				
			||||||
                    ManualDataSender.class);
 | 
					                    ManualDataSender.class);
 | 
				
			||||||
            sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId)));
 | 
					            sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId)));
 | 
				
			||||||
            Thread.sleep(1000);
 | 
					            Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli();
 | 
				
			||||||
 | 
					            Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS);
 | 
				
			||||||
            sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId)));
 | 
					            sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId)));
 | 
				
			||||||
 | 
					            Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1 = Instant.now().toEpochMilli();
 | 
				
			||||||
            sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false);
 | 
					            sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false);
 | 
				
			||||||
        } catch (InterruptedException e) {
 | 
					        } catch (InterruptedException e) {
 | 
				
			||||||
            throw new RuntimeException(e);
 | 
					            throw new RuntimeException(e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private LwM2mPath getPathForCollectedValue(int resourceId) {
 | 
					    private LwM2mPath getPathForCollectedValue(int resourceId) {
 | 
				
			||||||
        return new LwM2mPath(3303, this.getId(), resourceId);
 | 
					        return new LwM2mPath(3303, this.getId(), resourceId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private double getData() {
 | 
					 | 
				
			||||||
        if (containingValues.size() > 1) {
 | 
					 | 
				
			||||||
            Integer t0 = Math.toIntExact(Math.round(containingValues.get(0) * 100));
 | 
					 | 
				
			||||||
            Integer t1 = Math.toIntExact(Math.round(containingValues.get(1) * 100));
 | 
					 | 
				
			||||||
            long to_t1 = (((long) t0) << 32) | (t1 & 0xffffffffL);
 | 
					 | 
				
			||||||
            return Double.longBitsToDouble(to_t1);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            return currentTemp;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void setData() {
 | 
					 | 
				
			||||||
        if (containingValues == null){
 | 
					 | 
				
			||||||
            containingValues = new ArrayList<>();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        containingValues.add(getTwoDigitValue(currentTemp));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,20 +15,30 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.transport.lwm2m.rpc;
 | 
					package org.thingsboard.server.transport.lwm2m.rpc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.eclipse.leshan.core.link.LinkParser;
 | 
					import org.eclipse.leshan.core.link.LinkParser;
 | 
				
			||||||
import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser;
 | 
					import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser;
 | 
				
			||||||
import org.junit.Before;
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.mockito.Mockito;
 | 
				
			||||||
 | 
					import org.springframework.boot.test.mock.mockito.SpyBean;
 | 
				
			||||||
import org.thingsboard.server.common.data.Device;
 | 
					import org.thingsboard.server.common.data.Device;
 | 
				
			||||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
 | 
					import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
 | 
				
			||||||
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
 | 
					import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
 | 
				
			||||||
import org.thingsboard.server.dao.service.DaoSqlTest;
 | 
					import org.thingsboard.server.dao.service.DaoSqlTest;
 | 
				
			||||||
 | 
					import org.thingsboard.server.gen.transport.TransportProtos;
 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
 | 
					import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
 | 
				
			||||||
 | 
					import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
 | 
				
			||||||
 | 
					import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap;
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
					import java.util.concurrent.atomic.AtomicInteger;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicLong;
 | 
				
			||||||
import java.util.function.Predicate;
 | 
					import java.util.function.Predicate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.awaitility.Awaitility.await;
 | 
				
			||||||
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
 | 
					import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
 | 
				
			||||||
import static org.eclipse.leshan.core.LwM2mId.DEVICE;
 | 
					import static org.eclipse.leshan.core.LwM2mId.DEVICE;
 | 
				
			||||||
import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
 | 
					import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
 | 
				
			||||||
@ -40,19 +50,23 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0
 | 
				
			|||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_5700;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_2;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_2;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
 | 
					import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
@DaoSqlTest
 | 
					@DaoSqlTest
 | 
				
			||||||
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
 | 
					public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,6 +98,12 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    protected String idVer_19_0_0;
 | 
					    protected String idVer_19_0_0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SpyBean
 | 
				
			||||||
 | 
					    protected DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SpyBean
 | 
				
			||||||
 | 
					    protected LwM2mTransportServerHelper lwM2mTransportServerHelperTest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public AbstractRpcLwM2MIntegrationTest() {
 | 
					    public AbstractRpcLwM2MIntegrationTest() {
 | 
				
			||||||
        setResources(resources);
 | 
					        setResources(resources);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -144,7 +164,8 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
 | 
				
			|||||||
                        "      \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
 | 
					                        "      \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
 | 
				
			||||||
                        "      \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
 | 
					                        "      \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
 | 
				
			||||||
                        "      \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" +
 | 
					                        "      \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" +
 | 
				
			||||||
                        "      \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" +
 | 
					                        "      \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\",\n" +
 | 
				
			||||||
 | 
					                        "      \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" +
 | 
				
			||||||
                        "    },\n" +
 | 
					                        "    },\n" +
 | 
				
			||||||
                        "    \"observe\": [\n" +
 | 
					                        "    \"observe\": [\n" +
 | 
				
			||||||
                        "      \"" + idVer_3_0_9 + "\",\n" +
 | 
					                        "      \"" + idVer_3_0_9 + "\",\n" +
 | 
				
			||||||
@ -159,7 +180,8 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
 | 
				
			|||||||
                        "    \"telemetry\": [\n" +
 | 
					                        "    \"telemetry\": [\n" +
 | 
				
			||||||
                        "      \"" + idVer_3_0_9 + "\",\n" +
 | 
					                        "      \"" + idVer_3_0_9 + "\",\n" +
 | 
				
			||||||
                        "      \"" + idVer_19_0_0 + "\",\n" +
 | 
					                        "      \"" + idVer_19_0_0 + "\",\n" +
 | 
				
			||||||
                        "      \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" +
 | 
					                        "      \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" +
 | 
				
			||||||
 | 
					                        "      \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" +
 | 
				
			||||||
                        "    ],\n" +
 | 
					                        "    ],\n" +
 | 
				
			||||||
                        "    \"attributeLwm2m\": {}\n" +
 | 
					                        "    \"attributeLwm2m\": {}\n" +
 | 
				
			||||||
                        "  }";
 | 
					                        "  }";
 | 
				
			||||||
@ -183,4 +205,56 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
 | 
				
			|||||||
        return pathIdVer;
 | 
					        return pathIdVer;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected long countUpdateAttrTelemetryAll() {
 | 
				
			||||||
 | 
					        return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
 | 
				
			||||||
 | 
					                .getInvocations().stream()
 | 
				
			||||||
 | 
					                .filter(invocation -> invocation.getMethod().getName().equals("updateAttrTelemetry"))
 | 
				
			||||||
 | 
					                .count();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected long countUpdateAttrTelemetryResource(String idVerRez) {
 | 
				
			||||||
 | 
					        return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
 | 
				
			||||||
 | 
					                .getInvocations().stream()
 | 
				
			||||||
 | 
					                .filter(invocation ->
 | 
				
			||||||
 | 
					                        invocation.getMethod().getName().equals("updateAttrTelemetry") &&
 | 
				
			||||||
 | 
					                                invocation.getArguments().length > 1 &&
 | 
				
			||||||
 | 
					                                idVerRez.equals(invocation.getArguments()[1])
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .count();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected void updateRegAtLeastOnceAfterAction() {
 | 
				
			||||||
 | 
					        long initialInvocationCount = countUpdateReg();
 | 
				
			||||||
 | 
					        AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
 | 
				
			||||||
 | 
					        log.trace("updateRegAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
 | 
				
			||||||
 | 
					        await("Update Registration at-least-once after action")
 | 
				
			||||||
 | 
					                .atMost(50, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					                .until(() -> {
 | 
				
			||||||
 | 
					                    newInvocationCount.set(countUpdateReg());
 | 
				
			||||||
 | 
					                    return newInvocationCount.get() > initialInvocationCount;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					        log.trace("updateRegAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected long countUpdateReg() {
 | 
				
			||||||
 | 
					        return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
 | 
				
			||||||
 | 
					                .getInvocations().stream()
 | 
				
			||||||
 | 
					                .filter(invocation -> invocation.getMethod().getName().equals("updatedReg"))
 | 
				
			||||||
 | 
					                .count();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   protected long countSendParametersOnThingsboardTelemetryResource(String rezName) {
 | 
				
			||||||
 | 
					        return Mockito.mockingDetails(lwM2mTransportServerHelperTest)
 | 
				
			||||||
 | 
					                .getInvocations().stream()
 | 
				
			||||||
 | 
					                .filter(invocation ->
 | 
				
			||||||
 | 
					                        invocation.getMethod().getName().equals("sendParametersOnThingsboardTelemetry") &&
 | 
				
			||||||
 | 
					                                invocation.getArguments().length > 0 &&
 | 
				
			||||||
 | 
					                                invocation.getArguments()[0] instanceof List &&
 | 
				
			||||||
 | 
					                                ((List<?>) invocation.getArguments()[0]).stream()
 | 
				
			||||||
 | 
					                                        .filter(arg -> arg instanceof TransportProtos.KeyValueProto)
 | 
				
			||||||
 | 
					                                        .anyMatch(arg -> rezName.equals(((TransportProtos.KeyValueProto) arg).getKey()))
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .count();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,11 +20,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
				
			|||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.eclipse.leshan.core.ResponseCode;
 | 
					import org.eclipse.leshan.core.ResponseCode;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
import org.mockito.Mockito;
 | 
					 | 
				
			||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
 | 
					 | 
				
			||||||
import org.thingsboard.common.util.JacksonUtil;
 | 
					import org.thingsboard.common.util.JacksonUtil;
 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
 | 
					import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
@ -55,10 +52,6 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr
 | 
				
			|||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest {
 | 
					public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SpyBean
 | 
					 | 
				
			||||||
    DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     *  ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok
 | 
					     *  ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok
 | 
				
			||||||
     * @throws Exception
 | 
					     * @throws Exception
 | 
				
			||||||
@ -517,13 +510,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
 | 
				
			|||||||
        return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
 | 
					        return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private long countUpdateAttrTelemetryAll() {
 | 
					 | 
				
			||||||
        return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
 | 
					 | 
				
			||||||
                .getInvocations().stream()
 | 
					 | 
				
			||||||
                .filter(invocation -> invocation.getMethod().getName().equals("updateAttrTelemetry"))
 | 
					 | 
				
			||||||
                .count();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void updateAttrTelemetryAllAtLeastOnceAfterAction(long initialInvocationCount) {
 | 
					    private void updateAttrTelemetryAllAtLeastOnceAfterAction(long initialInvocationCount) {
 | 
				
			||||||
        AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
 | 
					        AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
 | 
				
			||||||
        log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
 | 
					        log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
 | 
				
			||||||
@ -536,19 +522,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
 | 
				
			|||||||
        log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
 | 
					        log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private long countUpdateAttrTelemetryResource(String idVerRez) {
 | 
					 | 
				
			||||||
        return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
 | 
					 | 
				
			||||||
                .getInvocations().stream()
 | 
					 | 
				
			||||||
                .filter(invocation ->
 | 
					 | 
				
			||||||
                        invocation.getMethod().getName().equals("updateAttrTelemetry") &&
 | 
					 | 
				
			||||||
                                invocation.getArguments().length > 1 &&
 | 
					 | 
				
			||||||
                                idVerRez.equals(invocation.getArguments()[1])
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .count();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void updateAttrTelemetryResourceAtLeastOnceAfterAction(long initialInvocationCount, String idVerRez) {
 | 
					    private void updateAttrTelemetryResourceAtLeastOnceAfterAction(long initialInvocationCount, String idVerRez) {
 | 
				
			||||||
        AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
 | 
					        AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
 | 
				
			||||||
        log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
 | 
					        log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
 | 
				
			||||||
@ -560,24 +533,4 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
 | 
				
			|||||||
                });
 | 
					                });
 | 
				
			||||||
        log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
 | 
					        log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private long countUpdateReg() {
 | 
					 | 
				
			||||||
        return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
 | 
					 | 
				
			||||||
                .getInvocations().stream()
 | 
					 | 
				
			||||||
                .filter(invocation -> invocation.getMethod().getName().equals("updatedReg"))
 | 
					 | 
				
			||||||
                .count();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void updateRegAtLeastOnceAfterAction() {
 | 
					 | 
				
			||||||
        long initialInvocationCount = countUpdateReg();
 | 
					 | 
				
			||||||
        AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
 | 
					 | 
				
			||||||
        log.warn("updateRegAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
 | 
					 | 
				
			||||||
        await("Update Registration at-least-once after action")
 | 
					 | 
				
			||||||
                .atMost(50, TimeUnit.SECONDS)
 | 
					 | 
				
			||||||
                .until(() -> {
 | 
					 | 
				
			||||||
                    newInvocationCount.set(countUpdateReg());
 | 
					 | 
				
			||||||
                    return newInvocationCount.get() > initialInvocationCount;
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
        log.warn("updateRegAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,9 +24,7 @@ import org.eclipse.leshan.core.response.ReadResponse;
 | 
				
			|||||||
import org.eclipse.leshan.server.registration.Registration;
 | 
					import org.eclipse.leshan.server.registration.Registration;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
import org.mockito.Mockito;
 | 
					import org.mockito.Mockito;
 | 
				
			||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
 | 
					 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
 | 
					import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,14 +39,12 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST
 | 
				
			|||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
 | 
					import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationObserveTest {
 | 
					public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationObserveTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SpyBean
 | 
					 | 
				
			||||||
    DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testObserveReadAll_Count_4_CancelAll_Count_0_Ok() throws Exception {
 | 
					    public void testObserveReadAll_Count_4_CancelAll_Count_0_Ok() throws Exception {
 | 
				
			||||||
        String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
 | 
					        String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
 | 
				
			||||||
@ -64,12 +60,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
 | 
					    public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
 | 
				
			||||||
 | 
					        long initSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9);
 | 
				
			||||||
        sendObserveCancelAllWithAwait(deviceId);
 | 
					        sendObserveCancelAllWithAwait(deviceId);
 | 
				
			||||||
        sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9);
 | 
					        sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9);
 | 
				
			||||||
 | 
					        updateRegAtLeastOnceAfterAction();
 | 
				
			||||||
        int cntUpdate = 3;
 | 
					        long lastSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9);
 | 
				
			||||||
        verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
 | 
					        assertTrue(lastSendTelemetryAtCount > initSendTelemetryAtCount);
 | 
				
			||||||
                .onUpdateValueAfterReadResponse(Mockito.any(Registration.class), eq(idVer_3_0_9), Mockito.any(ReadResponse.class));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -84,7 +80,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        int cntUpdate = 3;
 | 
					        int cntUpdate = 3;
 | 
				
			||||||
        verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
 | 
					        verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
 | 
				
			||||||
                .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
 | 
					                .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9), eq(null));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -99,7 +95,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        int cntUpdate = 3;
 | 
					        int cntUpdate = 3;
 | 
				
			||||||
        verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
 | 
					        verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
 | 
				
			||||||
                .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
 | 
					                .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9), eq(null));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -334,7 +330,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        cntUpdate = 10;
 | 
					        cntUpdate = 10;
 | 
				
			||||||
        verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate))
 | 
					        verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate))
 | 
				
			||||||
                .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
 | 
					                .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9), eq(null));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void sendRpcObserveWithWithTwoResource(String expectedId_1, String expectedId_2) throws Exception {
 | 
					    private void sendRpcObserveWithWithTwoResource(String expectedId_1, String expectedId_2) throws Exception {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,30 +15,26 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.transport.lwm2m.rpc.sql;
 | 
					package org.thingsboard.server.transport.lwm2m.rpc.sql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.JsonNode;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.node.ArrayNode;
 | 
				
			||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
					import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.apache.commons.collections4.map.HashedMap;
 | 
				
			||||||
import org.eclipse.leshan.core.ResponseCode;
 | 
					import org.eclipse.leshan.core.ResponseCode;
 | 
				
			||||||
import org.eclipse.leshan.core.node.LwM2mNode;
 | 
					 | 
				
			||||||
import org.eclipse.leshan.core.node.LwM2mPath;
 | 
					import org.eclipse.leshan.core.node.LwM2mPath;
 | 
				
			||||||
import org.eclipse.leshan.core.node.LwM2mResource;
 | 
					 | 
				
			||||||
import org.eclipse.leshan.core.node.TimestampedLwM2mNodes;
 | 
					 | 
				
			||||||
import org.eclipse.leshan.server.registration.Registration;
 | 
					 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
import org.mockito.Mockito;
 | 
					 | 
				
			||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
 | 
					 | 
				
			||||||
import org.thingsboard.common.util.JacksonUtil;
 | 
					import org.thingsboard.common.util.JacksonUtil;
 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
 | 
					import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
 | 
				
			||||||
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.time.Instant;
 | 
					import java.time.Instant;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static java.util.concurrent.TimeUnit.SECONDS;
 | 
				
			||||||
 | 
					import static org.awaitility.Awaitility.await;
 | 
				
			||||||
import static org.eclipse.leshan.core.LwM2mId.SERVER;
 | 
					import static org.eclipse.leshan.core.LwM2mId.SERVER;
 | 
				
			||||||
import static org.junit.Assert.assertEquals;
 | 
					import static org.junit.Assert.assertEquals;
 | 
				
			||||||
import static org.junit.Assert.assertTrue;
 | 
					import static org.junit.Assert.assertTrue;
 | 
				
			||||||
import static org.mockito.Mockito.doAnswer;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.timeout;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.verify;
 | 
					 | 
				
			||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 | 
					import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
 | 
				
			||||||
@ -49,20 +45,22 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID
 | 
				
			|||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
 | 
				
			||||||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest {
 | 
					public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SpyBean
 | 
					 | 
				
			||||||
    DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Read {"id":"/3"}
 | 
					     * Read {"id":"/3"}
 | 
				
			||||||
     * Read {"id":"/6"}...
 | 
					     * Read {"id":"/6"}...
 | 
				
			||||||
@ -88,7 +86,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
                    e.printStackTrace();
 | 
					                    e.printStackTrace();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } catch (Exception e2){
 | 
					        } catch (Exception e2) {
 | 
				
			||||||
            e2.printStackTrace();
 | 
					            e2.printStackTrace();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -99,10 +97,10 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
     * @throws Exception
 | 
					     * @throws Exception
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testReadAllInstancesInClientById_Result_CONTENT_Value_IsInstances_IsResources() throws Exception{
 | 
					    public void testReadAllInstancesInClientById_Result_CONTENT_Value_IsInstances_IsResources() throws Exception {
 | 
				
			||||||
        expectedObjectIdVerInstances.forEach(expected -> {
 | 
					        expectedObjectIdVerInstances.forEach(expected -> {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                String actualResult  = sendRPCById((String) expected);
 | 
					                String actualResult = sendRPCById((String) expected);
 | 
				
			||||||
                String expectedObjectId = pathIdVerToObjectId((String) expected);
 | 
					                String expectedObjectId = pathIdVerToObjectId((String) expected);
 | 
				
			||||||
                LwM2mPath expectedPath = new LwM2mPath(expectedObjectId);
 | 
					                LwM2mPath expectedPath = new LwM2mPath(expectedObjectId);
 | 
				
			||||||
                ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
					                ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
				
			||||||
@ -122,7 +120,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testReadMultipleResourceById_Result_CONTENT_Value_IsLwM2mMultipleResource() throws Exception {
 | 
					    public void testReadMultipleResourceById_Result_CONTENT_Value_IsLwM2mMultipleResource() throws Exception {
 | 
				
			||||||
       String expectedIdVer = objectInstanceIdVer_3 +"/" + RESOURCE_ID_11;
 | 
					        String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_11;
 | 
				
			||||||
        String actualResult = sendRPCById(expectedIdVer);
 | 
					        String actualResult = sendRPCById(expectedIdVer);
 | 
				
			||||||
        ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
					        ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
				
			||||||
        assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
 | 
					        assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
 | 
				
			||||||
@ -135,7 +133,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testReadSingleResourceById_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception {
 | 
					    public void testReadSingleResourceById_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception {
 | 
				
			||||||
         String expectedIdVer = objectInstanceIdVer_3 +"/" + RESOURCE_ID_14;
 | 
					        String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
 | 
				
			||||||
        String actualResult = sendRPCById(expectedIdVer);
 | 
					        String actualResult = sendRPCById(expectedIdVer);
 | 
				
			||||||
        ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
					        ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
				
			||||||
        assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
 | 
					        assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
 | 
				
			||||||
@ -161,7 +159,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectIsLwM2mSingleResourceIsLwM2mMultipleResource() throws Exception {
 | 
					    public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectIsLwM2mSingleResourceIsLwM2mMultipleResource() throws Exception {
 | 
				
			||||||
        String expectedIdVer_1 = (String) expectedObjectIdVers.stream().filter(path -> (!((String)path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String)path).contains("/" + SERVER))).findFirst().get();
 | 
					        String expectedIdVer_1 = (String) expectedObjectIdVers.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get();
 | 
				
			||||||
        String objectId_1 = pathIdVerToObjectId(expectedIdVer_1);
 | 
					        String objectId_1 = pathIdVerToObjectId(expectedIdVer_1);
 | 
				
			||||||
        String expectedIdVer3_0_1 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_1;
 | 
					        String expectedIdVer3_0_1 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_1;
 | 
				
			||||||
        String expectedIdVer3_0_11 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_11;
 | 
					        String expectedIdVer3_0_11 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_11;
 | 
				
			||||||
@ -221,8 +219,8 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
        String objectId_19 = pathIdVerToObjectId(objectIdVer_19);
 | 
					        String objectId_19 = pathIdVerToObjectId(objectIdVer_19);
 | 
				
			||||||
        String expected3_0_9 = objectInstanceId_3 + "/" + RESOURCE_ID_9 + "=LwM2mSingleResource [id=" + RESOURCE_ID_9 + ", value=";
 | 
					        String expected3_0_9 = objectInstanceId_3 + "/" + RESOURCE_ID_9 + "=LwM2mSingleResource [id=" + RESOURCE_ID_9 + ", value=";
 | 
				
			||||||
        String expected3_0_14 = objectInstanceId_3 + "/" + RESOURCE_ID_14 + "=LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=";
 | 
					        String expected3_0_14 = objectInstanceId_3 + "/" + RESOURCE_ID_14 + "=LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=";
 | 
				
			||||||
        String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 +  expectedKey19_X_0;
 | 
					        String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + expectedKey19_X_0;
 | 
				
			||||||
        String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 +  expectedKey19_X_0;
 | 
					        String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + expectedKey19_X_0;
 | 
				
			||||||
        String actualValues = rpcActualResult.get("value").asText();
 | 
					        String actualValues = rpcActualResult.get("value").asText();
 | 
				
			||||||
        assertTrue(actualValues.contains(expected3_0_9));
 | 
					        assertTrue(actualValues.contains(expected3_0_9));
 | 
				
			||||||
        assertTrue(actualValues.contains(expected3_0_14));
 | 
					        assertTrue(actualValues.contains(expected3_0_14));
 | 
				
			||||||
@ -232,56 +230,55 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * /3303/0/5700
 | 
					     * Read {"id":"/3303/12/5700"}
 | 
				
			||||||
     *  Read {"id":"/3303/0/5700"}
 | 
					 | 
				
			||||||
     * Trigger a Send operation from the client with multiple values for the same resource as a payload
 | 
					     * Trigger a Send operation from the client with multiple values for the same resource as a payload
 | 
				
			||||||
     * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes]
 | 
					     * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes]
 | 
				
			||||||
     * 2 values for the resource /3303/12/5700 should be stored with timestamps1 =  Instance.now(), timestamps2 =  Instance.now()
 | 
					     * 2 values for the resource /3303/12/5700 should be stored with:
 | 
				
			||||||
     *
 | 
					     * - timestamps1 =  Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1
 | 
				
			||||||
 | 
					     * - timestamps2 =  (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2
 | 
				
			||||||
     * @throws Exception
 | 
					     * @throws Exception
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception {
 | 
					    public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception {
 | 
				
			||||||
        TimestampedLwM2mNodes[] tsNodesHolder = new TimestampedLwM2mNodes[1];
 | 
					        // init test
 | 
				
			||||||
        doAnswer(inv -> {
 | 
					        long startTs = Instant.now().toEpochMilli();
 | 
				
			||||||
            tsNodesHolder[0] = inv.getArgument(1);
 | 
					        int cntValues = 4;
 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }).when(defaultUplinkMsgHandlerTest).onUpdateValueWithSendRequest(
 | 
					 | 
				
			||||||
                Mockito.any(Registration.class),
 | 
					 | 
				
			||||||
                Mockito.any(TimestampedLwM2mNodes.class)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        int resourceId = 5700;
 | 
					        int resourceId = 5700;
 | 
				
			||||||
        String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId;
 | 
					        String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId;
 | 
				
			||||||
        String actualResult = sendRPCById(expectedIdVer);
 | 
					        sendRPCById(expectedIdVer);
 | 
				
			||||||
        verify(defaultUplinkMsgHandlerTest,  timeout(10000).times(1))
 | 
					        // verify result read: verify count value: 1-2: send CollectedValue; 3 - response for read;
 | 
				
			||||||
                .onUpdateValueWithSendRequest(Mockito.any(Registration.class), Mockito.any(TimestampedLwM2mNodes.class));
 | 
					        long endTs = Instant.now().toEpochMilli() + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS * 4;
 | 
				
			||||||
 | 
					        String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0);
 | 
				
			||||||
        ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
 | 
					        String expectedVal_2 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1);
 | 
				
			||||||
        assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
 | 
					        AtomicReference<ObjectNode> actualValues = new AtomicReference<>();
 | 
				
			||||||
        String expected = "LwM2mSingleResource [id=" + resourceId + ", value=";
 | 
					        await().atMost(40, SECONDS).until(() -> {
 | 
				
			||||||
        String actual = rpcActualResult.get("value").asText();
 | 
					            actualValues.set(doGetAsync(
 | 
				
			||||||
        assertTrue(actual.contains(expected));
 | 
					                    "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys="
 | 
				
			||||||
        int indStart = actual.indexOf(expected) + expected.length();
 | 
					                            + RESOURCE_ID_NAME_3303_12_5700
 | 
				
			||||||
        int indEnd = actual.indexOf(",", indStart);
 | 
					                            + "&startTs=" + startTs
 | 
				
			||||||
        String valStr = actual.substring(indStart, indEnd);
 | 
					                            + "&endTs=" + endTs
 | 
				
			||||||
        double dd = Double.parseDouble(valStr);
 | 
					                            + "&interval=0&limit=100&useStrictDataTypes=false",
 | 
				
			||||||
        long combined = Double.doubleToRawLongBits(dd);
 | 
					                    ObjectNode.class));
 | 
				
			||||||
        int t0 = (int) (combined >> 32);
 | 
					            // verify cntValues
 | 
				
			||||||
        int t1 = (int) combined;
 | 
					            return actualValues.get() != null && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() == cntValues;
 | 
				
			||||||
        double[] expectedValues ={(double)t0/100, (double)t1/100};
 | 
					        });
 | 
				
			||||||
        int ind = 0;
 | 
					        // verify ts
 | 
				
			||||||
        LwM2mPath expectedPath = new LwM2mPath("/3303/12/5700");
 | 
					        ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700);
 | 
				
			||||||
        for (Instant ts : tsNodesHolder[0].getTimestamps()) {
 | 
					        Map<String, Long> keyTsMaps = new HashedMap();
 | 
				
			||||||
            Map<LwM2mPath, LwM2mNode> nodesAt = tsNodesHolder[0].getNodesAt(ts);
 | 
					        for (JsonNode tsNode: actual) {
 | 
				
			||||||
            for (var instant : nodesAt.entrySet()) {
 | 
					            if (tsNode.get("value").asText().equals(expectedVal_1) || tsNode.get("value").asText().equals(expectedVal_2)) {
 | 
				
			||||||
                LwM2mPath actualPath = instant.getKey();
 | 
					                keyTsMaps.put(tsNode.get("value").asText(), tsNode.get("ts").asLong());
 | 
				
			||||||
                LwM2mNode node = instant.getValue();
 | 
					 | 
				
			||||||
                LwM2mResource lwM2mResource = (LwM2mResource) node;
 | 
					 | 
				
			||||||
                assertEquals(expectedPath, actualPath);
 | 
					 | 
				
			||||||
                assertEquals(expectedValues[ind], lwM2mResource.getValue());
 | 
					 | 
				
			||||||
                ind++;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        assertTrue(keyTsMaps.size() == 2);
 | 
				
			||||||
 | 
					        long actualTS0 = keyTsMaps.get(expectedVal_1).longValue();
 | 
				
			||||||
 | 
					        long actualTS1 = keyTsMaps.get(expectedVal_2).longValue();
 | 
				
			||||||
 | 
					        assertTrue(actualTS0 > 0);
 | 
				
			||||||
 | 
					        assertTrue(actualTS1 > 0);
 | 
				
			||||||
 | 
					        assertTrue(actualTS1 > actualTS0);
 | 
				
			||||||
 | 
					        assertTrue((actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS);
 | 
				
			||||||
 | 
					        assertTrue(actualTS0 <= RESOURCE_ID_3303_12_5700_TS_0);
 | 
				
			||||||
 | 
					        assertTrue(actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -301,7 +298,6 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
 | 
				
			|||||||
        assertEquals(actualValue, expectedValue);
 | 
					        assertEquals(actualValue, expectedValue);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private String sendRPCById(String path) throws Exception {
 | 
					    private String sendRPCById(String path) throws Exception {
 | 
				
			||||||
        String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}";
 | 
					        String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}";
 | 
				
			||||||
        return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
 | 
					        return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
 | 
				
			||||||
 | 
				
			|||||||
@ -407,6 +407,7 @@ public class ProtoUtils {
 | 
				
			|||||||
                .setRequestIdMSB(msg.getMsg().getId().getMostSignificantBits())
 | 
					                .setRequestIdMSB(msg.getMsg().getId().getMostSignificantBits())
 | 
				
			||||||
                .setRequestIdLSB(msg.getMsg().getId().getLeastSignificantBits())
 | 
					                .setRequestIdLSB(msg.getMsg().getId().getLeastSignificantBits())
 | 
				
			||||||
                .setOneway(msg.getMsg().isOneway())
 | 
					                .setOneway(msg.getMsg().isOneway())
 | 
				
			||||||
 | 
					                .setPersisted(msg.getMsg().isPersisted())
 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return TransportProtos.ToDeviceRpcRequestActorMsgProto.newBuilder()
 | 
					        return TransportProtos.ToDeviceRpcRequestActorMsgProto.newBuilder()
 | 
				
			||||||
 | 
				
			|||||||
@ -133,7 +133,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
 | 
				
			|||||||
                log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
 | 
					                log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
 | 
				
			||||||
                log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint());
 | 
					                log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint());
 | 
				
			||||||
                String logMsg = String.format("%s: Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR);
 | 
					                String logMsg = String.format("%s: Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR);
 | 
				
			||||||
                helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo);
 | 
					                helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo, null);
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.ByteArrayInputStream;
 | 
					import java.io.ByteArrayInputStream;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.time.Instant;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@ -58,12 +59,12 @@ public class LwM2mTransportServerHelper {
 | 
				
			|||||||
        context.getTransportService().process(sessionInfo, postAttributeMsg, TransportServiceCallback.EMPTY);
 | 
					        context.getTransportService().process(sessionInfo, postAttributeMsg, TransportServiceCallback.EMPTY);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void sendParametersOnThingsboardTelemetry(List<TransportProtos.KeyValueProto> kvList, SessionInfoProto sessionInfo) {
 | 
					    public void sendParametersOnThingsboardTelemetry(List<TransportProtos.KeyValueProto> kvList, SessionInfoProto sessionInfo, @Nullable Map<String, AtomicLong> keyTsLatestMaps){
 | 
				
			||||||
        sendParametersOnThingsboardTelemetry(kvList, sessionInfo, null);
 | 
					        sendParametersOnThingsboardTelemetry(kvList, sessionInfo, keyTsLatestMaps, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void sendParametersOnThingsboardTelemetry(List<TransportProtos.KeyValueProto> kvList, SessionInfoProto sessionInfo, @Nullable Map<String, AtomicLong> keyTsLatestMap) {
 | 
					    public void sendParametersOnThingsboardTelemetry(List<TransportProtos.KeyValueProto> kvList, SessionInfoProto sessionInfo, @Nullable Map<String, AtomicLong> keyTsLatestMap, @Nullable Instant ts) {
 | 
				
			||||||
        TransportProtos.TsKvListProto tsKvList = toTsKvList(kvList, keyTsLatestMap);
 | 
					        TransportProtos.TsKvListProto tsKvList = toTsKvList(kvList, keyTsLatestMap, ts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        PostTelemetryMsg postTelemetryMsg = PostTelemetryMsg.newBuilder()
 | 
					        PostTelemetryMsg postTelemetryMsg = PostTelemetryMsg.newBuilder()
 | 
				
			||||||
                .addTsKvList(tsKvList)
 | 
					                .addTsKvList(tsKvList)
 | 
				
			||||||
@ -72,9 +73,9 @@ public class LwM2mTransportServerHelper {
 | 
				
			|||||||
        context.getTransportService().process(sessionInfo, postTelemetryMsg, TransportServiceCallback.EMPTY);
 | 
					        context.getTransportService().process(sessionInfo, postTelemetryMsg, TransportServiceCallback.EMPTY);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TransportProtos.TsKvListProto toTsKvList(List<TransportProtos.KeyValueProto> kvList, Map<String, AtomicLong> keyTsLatestMap) {
 | 
					    TransportProtos.TsKvListProto toTsKvList(List<TransportProtos.KeyValueProto> kvList, Map<String, AtomicLong> keyTsLatestMap, @Nullable Instant ts) {
 | 
				
			||||||
        return TransportProtos.TsKvListProto.newBuilder()
 | 
					        return TransportProtos.TsKvListProto.newBuilder()
 | 
				
			||||||
                .setTs(getTs(kvList, keyTsLatestMap))
 | 
					                .setTs(ts == null ? getTs(kvList, keyTsLatestMap) : ts.toEpochMilli())
 | 
				
			||||||
                .addAllKv(kvList)
 | 
					                .addAllKv(kvList)
 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,6 @@ import org.eclipse.leshan.core.observation.Observation;
 | 
				
			|||||||
import org.eclipse.leshan.core.request.CreateRequest;
 | 
					import org.eclipse.leshan.core.request.CreateRequest;
 | 
				
			||||||
import org.eclipse.leshan.core.request.ObserveRequest;
 | 
					import org.eclipse.leshan.core.request.ObserveRequest;
 | 
				
			||||||
import org.eclipse.leshan.core.request.ReadRequest;
 | 
					import org.eclipse.leshan.core.request.ReadRequest;
 | 
				
			||||||
import org.eclipse.leshan.core.request.SendRequest;
 | 
					 | 
				
			||||||
import org.eclipse.leshan.core.request.WriteCompositeRequest;
 | 
					import org.eclipse.leshan.core.request.WriteCompositeRequest;
 | 
				
			||||||
import org.eclipse.leshan.core.request.WriteRequest;
 | 
					import org.eclipse.leshan.core.request.WriteRequest;
 | 
				
			||||||
import org.eclipse.leshan.core.request.WriteRequest.Mode;
 | 
					import org.eclipse.leshan.core.request.WriteRequest.Mode;
 | 
				
			||||||
@ -117,6 +116,7 @@ import java.util.UUID;
 | 
				
			|||||||
import java.util.concurrent.ConcurrentHashMap;
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
import java.util.concurrent.CountDownLatch;
 | 
					import java.util.concurrent.CountDownLatch;
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicLong;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
 | 
					import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
 | 
				
			||||||
@ -382,7 +382,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
 | 
				
			|||||||
                        this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path.toString(), 0);
 | 
					                        this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path.toString(), 0);
 | 
				
			||||||
                    } else if (node instanceof LwM2mResource) {
 | 
					                    } else if (node instanceof LwM2mResource) {
 | 
				
			||||||
                        LwM2mResource lwM2mResource = (LwM2mResource) node;
 | 
					                        LwM2mResource lwM2mResource = (LwM2mResource) node;
 | 
				
			||||||
                        this.updateResourcesValue(lwM2MClient, lwM2mResource, path.toString(), Mode.UPDATE, 0);
 | 
					                        this.updateResourcesValueWithTs(lwM2MClient, lwM2mResource, path.toString(), Mode.UPDATE, ts);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                tryAwake(lwM2MClient);
 | 
					                tryAwake(lwM2MClient);
 | 
				
			||||||
@ -612,12 +612,21 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
 | 
				
			|||||||
                otaService.onCurrentSoftwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue());
 | 
					                otaService.onCurrentSoftwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (ResponseCode.BAD_REQUEST.getCode() > code) {
 | 
					            if (ResponseCode.BAD_REQUEST.getCode() > code) {
 | 
				
			||||||
                this.updateAttrTelemetry(registration, path);
 | 
					                this.updateAttrTelemetry(registration, path, null);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            log.error("Fail update path [{}] Resource [{}]", path, lwM2mResource);
 | 
					            log.error("Fail update path [{}] Resource [{}]", path, lwM2mResource);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    private void updateResourcesValueWithTs(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String stringPath, Mode mode, Instant ts) {
 | 
				
			||||||
 | 
					        Registration registration = lwM2MClient.getRegistration();
 | 
				
			||||||
 | 
					        String path = convertObjectIdToVersionedId(stringPath, lwM2MClient);
 | 
				
			||||||
 | 
					        if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider, mode)) {
 | 
				
			||||||
 | 
					            this.updateAttrTelemetry(registration, path, ts);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            log.error("Fail update path [{}] Resource [{}] with ts.", path, lwM2mResource);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -629,7 +638,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param registration - Registration LwM2M Client
 | 
					     * @param registration - Registration LwM2M Client
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public void updateAttrTelemetry(Registration registration, String path) {
 | 
					    public void updateAttrTelemetry(Registration registration, String path, Instant ts) {
 | 
				
			||||||
        log.trace("UpdateAttrTelemetry paths [{}]", path);
 | 
					        log.trace("UpdateAttrTelemetry paths [{}]", path);
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, path);
 | 
					            ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, path);
 | 
				
			||||||
@ -640,8 +649,8 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
 | 
				
			|||||||
                    this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
 | 
					                    this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (results.getResultTelemetries().size() > 0) {
 | 
					                if (results.getResultTelemetries().size() > 0) {
 | 
				
			||||||
                    log.trace("UpdateTelemetry paths [{}] value [{}]", path, results.getResultTelemetries().get(0).toString());
 | 
					                    log.trace("UpdateTelemetry paths [{}] value [{}] ts [{}]", path, results.getResultTelemetries().get(0).toString(), ts == null ? "null" : ts.toEpochMilli());
 | 
				
			||||||
                    this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo);
 | 
					                    this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo, null, ts);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
				
			|||||||
@ -223,7 +223,7 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
 | 
				
			|||||||
                    var currentSettings = vcService.getRepositorySettings(ctx.getTenantId());
 | 
					                    var currentSettings = vcService.getRepositorySettings(ctx.getTenantId());
 | 
				
			||||||
                    var newSettings = ctx.getSettings();
 | 
					                    var newSettings = ctx.getSettings();
 | 
				
			||||||
                    if (!newSettings.equals(currentSettings)) {
 | 
					                    if (!newSettings.equals(currentSettings)) {
 | 
				
			||||||
                        vcService.initRepository(ctx.getTenantId(), ctx.getSettings());
 | 
					                        vcService.initRepository(ctx.getTenantId(), ctx.getSettings(), false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if (msg.hasCommitRequest()) {
 | 
					                    if (msg.hasCommitRequest()) {
 | 
				
			||||||
                        handleCommitRequest(ctx, msg.getCommitRequest());
 | 
					                        handleCommitRequest(ctx, msg.getCommitRequest());
 | 
				
			||||||
@ -464,7 +464,7 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private void handleInitRepositoryCommand(VersionControlRequestCtx ctx) {
 | 
					    private void handleInitRepositoryCommand(VersionControlRequestCtx ctx) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            vcService.initRepository(ctx.getTenantId(), ctx.getSettings());
 | 
					            vcService.initRepository(ctx.getTenantId(), ctx.getSettings(), false);
 | 
				
			||||||
            reply(ctx, Optional.empty());
 | 
					            reply(ctx, Optional.empty());
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            log.debug("[{}] Failed to connect to the repository: ", ctx, e);
 | 
					            log.debug("[{}] Failed to connect to the repository: ", ctx, e);
 | 
				
			||||||
@ -564,4 +564,5 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
 | 
				
			|||||||
            }, MoreExecutors.directExecutor());
 | 
					            }, MoreExecutors.directExecutor());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -205,7 +205,7 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (!Files.exists(Path.of(gitRepository.getDirectory()))) {
 | 
					        if (!Files.exists(Path.of(gitRepository.getDirectory()))) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                return cloneRepository(tenantId, gitRepository.getSettings());
 | 
					                return openOrCloneRepository(tenantId, gitRepository.getSettings(), false);
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
                throw new IllegalStateException("Could not initialize the repository: " + e.getMessage(), e);
 | 
					                throw new IllegalStateException("Could not initialize the repository: " + e.getMessage(), e);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -239,11 +239,11 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception {
 | 
					    public void initRepository(TenantId tenantId, RepositorySettings settings, boolean fetch) throws Exception {
 | 
				
			||||||
        if (!settings.isLocalOnly()) {
 | 
					        if (!settings.isLocalOnly()) {
 | 
				
			||||||
            clearRepository(tenantId);
 | 
					            clearRepository(tenantId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        cloneRepository(tenantId, settings);
 | 
					        openOrCloneRepository(tenantId, settings, fetch);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -280,13 +280,16 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
 | 
				
			|||||||
        return EntityIdFactory.getByTypeAndUuid(entityType, entityId);
 | 
					        return EntityIdFactory.getByTypeAndUuid(entityType, entityId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private GitRepository cloneRepository(TenantId tenantId, RepositorySettings settings) throws Exception {
 | 
					    private GitRepository openOrCloneRepository(TenantId tenantId, RepositorySettings settings, boolean fetch) throws Exception {
 | 
				
			||||||
        log.debug("[{}] Init tenant repository started.", tenantId);
 | 
					        log.debug("[{}] Init tenant repository started.", tenantId);
 | 
				
			||||||
        Path repositoryDirectory = Path.of(repositoriesFolder, settings.isLocalOnly() ? "local_" + settings.getRepositoryUri() : tenantId.getId().toString());
 | 
					        Path repositoryDirectory = Path.of(repositoriesFolder, settings.isLocalOnly() ? "local_" + settings.getRepositoryUri() : tenantId.getId().toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GitRepository repository;
 | 
					        GitRepository repository;
 | 
				
			||||||
        if (Files.exists(repositoryDirectory)) {
 | 
					        if (Files.exists(repositoryDirectory)) {
 | 
				
			||||||
            repository = GitRepository.open(repositoryDirectory.toFile(), settings);
 | 
					            repository = GitRepository.open(repositoryDirectory.toFile(), settings);
 | 
				
			||||||
 | 
					            if (fetch) {
 | 
				
			||||||
 | 
					                repository.fetch();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            Files.createDirectories(repositoryDirectory);
 | 
					            Files.createDirectories(repositoryDirectory);
 | 
				
			||||||
            if (settings.isLocalOnly()) {
 | 
					            if (settings.isLocalOnly()) {
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,7 @@ public interface GitRepositoryService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception;
 | 
					    void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception;
 | 
					    void initRepository(TenantId tenantId, RepositorySettings settings, boolean fetch) throws Exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RepositorySettings getRepositorySettings(TenantId tenantId) throws Exception;
 | 
					    RepositorySettings getRepositorySettings(TenantId tenantId) throws Exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -67,4 +67,5 @@ public interface GitRepositoryService {
 | 
				
			|||||||
    String getContentsDiff(TenantId tenantId, String content1, String content2) throws IOException;
 | 
					    String getContentsDiff(TenantId tenantId, String content1, String content2) throws IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void fetch(TenantId tenantId) throws GitAPIException;
 | 
					    void fetch(TenantId tenantId) throws GitAPIException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -49,6 +49,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
 | 
				
			|||||||
import org.thingsboard.server.common.data.id.DeviceProfileId;
 | 
					import org.thingsboard.server.common.data.id.DeviceProfileId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityViewId;
 | 
					import org.thingsboard.server.common.data.id.EntityViewId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.RpcId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.RuleChainId;
 | 
					import org.thingsboard.server.common.data.id.RuleChainId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.UserId;
 | 
					import org.thingsboard.server.common.data.id.UserId;
 | 
				
			||||||
@ -57,6 +58,7 @@ import org.thingsboard.server.common.data.page.PageLink;
 | 
				
			|||||||
import org.thingsboard.server.common.data.page.TimePageLink;
 | 
					import org.thingsboard.server.common.data.page.TimePageLink;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
					import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.rpc.Rpc;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChain;
 | 
					import org.thingsboard.server.common.data.rule.RuleChain;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 | 
					import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 | 
				
			||||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
 | 
					import org.thingsboard.server.common.data.security.DeviceCredentials;
 | 
				
			||||||
@ -64,6 +66,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
 | 
				
			|||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static io.restassured.RestAssured.given;
 | 
					import static io.restassured.RestAssured.given;
 | 
				
			||||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
 | 
					import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
 | 
				
			||||||
@ -320,6 +323,27 @@ public class TestRestClient {
 | 
				
			|||||||
                .as(JsonNode.class);
 | 
					                .as(JsonNode.class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Rpc getPersistedRpc(RpcId rpcId) {
 | 
				
			||||||
 | 
					        return given().spec(requestSpec)
 | 
				
			||||||
 | 
					                .get("/api/rpc/persistent/{rpcId}", rpcId.toString())
 | 
				
			||||||
 | 
					                .then()
 | 
				
			||||||
 | 
					                .statusCode(HTTP_OK)
 | 
				
			||||||
 | 
					                .extract()
 | 
				
			||||||
 | 
					                .as(Rpc.class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public PageData<Rpc> getPersistedRpcByDevice(DeviceId deviceId, PageLink pageLink) {
 | 
				
			||||||
 | 
					        Map<String, String> params = new HashMap<>();
 | 
				
			||||||
 | 
					        addPageLinkToParam(params, pageLink);
 | 
				
			||||||
 | 
					        return given().spec(requestSpec).queryParams(params)
 | 
				
			||||||
 | 
					                .get("/api/rpc/persistent/device/{deviceId}", deviceId.toString())
 | 
				
			||||||
 | 
					                .then()
 | 
				
			||||||
 | 
					                .statusCode(HTTP_OK)
 | 
				
			||||||
 | 
					                .extract()
 | 
				
			||||||
 | 
					                .as(new TypeRef<>() {
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public PageData<DeviceProfile> getDeviceProfiles(PageLink pageLink) {
 | 
					    public PageData<DeviceProfile> getDeviceProfiles(PageLink pageLink) {
 | 
				
			||||||
        Map<String, String> params = new HashMap<>();
 | 
					        Map<String, String> params = new HashMap<>();
 | 
				
			||||||
        addPageLinkToParam(params, pageLink);
 | 
					        addPageLinkToParam(params, pageLink);
 | 
				
			||||||
 | 
				
			|||||||
@ -46,9 +46,11 @@ 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;
 | 
				
			||||||
import org.thingsboard.server.common.data.StringUtils;
 | 
					import org.thingsboard.server.common.data.StringUtils;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.RpcId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.RuleChainId;
 | 
					import org.thingsboard.server.common.data.id.RuleChainId;
 | 
				
			||||||
import org.thingsboard.server.common.data.page.PageData;
 | 
					import org.thingsboard.server.common.data.page.PageData;
 | 
				
			||||||
import org.thingsboard.server.common.data.page.PageLink;
 | 
					import org.thingsboard.server.common.data.page.PageLink;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.rpc.Rpc;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
 | 
					import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChain;
 | 
					import org.thingsboard.server.common.data.rule.RuleChain;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 | 
					import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 | 
				
			||||||
@ -68,6 +70,7 @@ import java.util.List;
 | 
				
			|||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
import java.util.concurrent.ArrayBlockingQueue;
 | 
					import java.util.concurrent.ArrayBlockingQueue;
 | 
				
			||||||
import java.util.concurrent.BlockingQueue;
 | 
					import java.util.concurrent.BlockingQueue;
 | 
				
			||||||
import java.util.concurrent.ExecutionException;
 | 
					import java.util.concurrent.ExecutionException;
 | 
				
			||||||
@ -76,6 +79,7 @@ import java.util.concurrent.TimeUnit;
 | 
				
			|||||||
import java.util.concurrent.TimeoutException;
 | 
					import java.util.concurrent.TimeoutException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
 | 
					import static org.testng.Assert.assertNotNull;
 | 
				
			||||||
import static org.testng.Assert.fail;
 | 
					import static org.testng.Assert.fail;
 | 
				
			||||||
import static org.thingsboard.server.common.data.DataConstants.DEVICE;
 | 
					import static org.thingsboard.server.common.data.DataConstants.DEVICE;
 | 
				
			||||||
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
 | 
					import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
 | 
				
			||||||
@ -307,6 +311,60 @@ public class MqttClientTest extends AbstractContainerTest {
 | 
				
			|||||||
        assertThat(serverResponse).isEqualTo(mapper.readTree(clientResponse.toString()));
 | 
					        assertThat(serverResponse).isEqualTo(mapper.readTree(clientResponse.toString()));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void serverSidePersistedRpc() throws Exception {
 | 
				
			||||||
 | 
					        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MqttMessageListener listener = new MqttMessageListener();
 | 
				
			||||||
 | 
					        MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
 | 
				
			||||||
 | 
					        mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE).get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait until subscription is processed
 | 
				
			||||||
 | 
					        TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Send an RPC from the server
 | 
				
			||||||
 | 
					        JsonObject serverRpcPayload = new JsonObject();
 | 
				
			||||||
 | 
					        serverRpcPayload.addProperty("method", "getValue");
 | 
				
			||||||
 | 
					        serverRpcPayload.addProperty("params", true);
 | 
				
			||||||
 | 
					        serverRpcPayload.addProperty("persistent", true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        JsonNode persistentRpcId = testRestClient.postServerSideRpc(device.getId(), mapper.readTree(serverRpcPayload.toString()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertNotNull(persistentRpcId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RpcId rpcId = new RpcId(UUID.fromString(persistentRpcId.get("rpcId").asText()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait for RPC call from the server and send the response
 | 
				
			||||||
 | 
					        MqttEvent requestFromServer = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(Objects.requireNonNull(requestFromServer).getMessage()).isEqualTo("{\"method\":\"getValue\",\"params\":true}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Integer requestId = Integer.valueOf(Objects.requireNonNull(requestFromServer).getTopic().substring("v1/devices/me/rpc/request/".length()));
 | 
				
			||||||
 | 
					        JsonObject clientResponse = new JsonObject();
 | 
				
			||||||
 | 
					        clientResponse.addProperty("response", "someResponse");
 | 
				
			||||||
 | 
					        // Send a response to the server's RPC request
 | 
				
			||||||
 | 
					        mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes())).get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PageLink pageLink = new PageLink(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Awaitility.await()
 | 
				
			||||||
 | 
					                .pollInterval(500, TimeUnit.MILLISECONDS)
 | 
				
			||||||
 | 
					                .atMost(5 * timeoutMultiplier, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					                .until(() -> {
 | 
				
			||||||
 | 
					                    PageData<Rpc> rpcByDevice = testRestClient.getPersistedRpcByDevice(device.getId(), pageLink);
 | 
				
			||||||
 | 
					                    for (Rpc rpc : rpcByDevice.getData()) {
 | 
				
			||||||
 | 
					                        if (rpc.getId().equals(rpcId)) {
 | 
				
			||||||
 | 
					                            return true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Rpc persistentRpc = testRestClient.getPersistedRpc(rpcId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertThat(persistentRpc.getResponse()).isEqualTo(mapper.readTree(clientResponse.toString()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void clientSideRpc() throws Exception {
 | 
					    public void clientSideRpc() throws Exception {
 | 
				
			||||||
        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
					        DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
 | 
				
			||||||
 | 
				
			|||||||
@ -47,7 +47,7 @@ public class CertPemCredentials implements ClientCredentials {
 | 
				
			|||||||
    protected String caCert;
 | 
					    protected String caCert;
 | 
				
			||||||
    private String cert;
 | 
					    private String cert;
 | 
				
			||||||
    private String privateKey;
 | 
					    private String privateKey;
 | 
				
			||||||
    private String password = "";
 | 
					    private String password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public CredentialsType getType() {
 | 
					    public CredentialsType getType() {
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,6 @@ import static org.thingsboard.rule.engine.credentials.CertPemCredentials.PRIVATE
 | 
				
			|||||||
public class CertPemCredentialsTest {
 | 
					public class CertPemCredentialsTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String PASS = "test";
 | 
					    private static final String PASS = "test";
 | 
				
			||||||
    private static final String EMPTY_PASS = "";
 | 
					 | 
				
			||||||
    private static final String RSA = "RSA";
 | 
					    private static final String RSA = "RSA";
 | 
				
			||||||
    private static final String EC = "EC";
 | 
					    private static final String EC = "EC";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,10 +80,10 @@ public class CertPemCredentialsTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private static Stream<Arguments> testLoadKeyStore() {
 | 
					    private static Stream<Arguments> testLoadKeyStore() {
 | 
				
			||||||
        return Stream.of(
 | 
					        return Stream.of(
 | 
				
			||||||
                Arguments.of("pem/rsa_cert.pem", "pem/rsa_key.pem", EMPTY_PASS, RSA),
 | 
					                Arguments.of("pem/rsa_cert.pem", "pem/rsa_key.pem", null, RSA),
 | 
				
			||||||
                Arguments.of("pem/rsa_encrypted_cert.pem", "pem/rsa_encrypted_key.pem", PASS, RSA),
 | 
					                Arguments.of("pem/rsa_encrypted_cert.pem", "pem/rsa_encrypted_key.pem", PASS, RSA),
 | 
				
			||||||
                Arguments.of("pem/rsa_encrypted_traditional_cert.pem", "pem/rsa_encrypted_traditional_key.pem", PASS, RSA),
 | 
					                Arguments.of("pem/rsa_encrypted_traditional_cert.pem", "pem/rsa_encrypted_traditional_key.pem", PASS, RSA),
 | 
				
			||||||
                Arguments.of("pem/ec_cert.pem", "pem/ec_key.pem", EMPTY_PASS, EC)
 | 
					                Arguments.of("pem/ec_cert.pem", "pem/ec_key.pem", null, EC)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,7 +97,7 @@ public class CertPemCredentialsTest {
 | 
				
			|||||||
        certPemCredentials.setPassword(password);
 | 
					        certPemCredentials.setPassword(password);
 | 
				
			||||||
        KeyStore keyStore = certPemCredentials.loadKeyStore();
 | 
					        KeyStore keyStore = certPemCredentials.loadKeyStore();
 | 
				
			||||||
        Assertions.assertNotNull(keyStore);
 | 
					        Assertions.assertNotNull(keyStore);
 | 
				
			||||||
        Key key = keyStore.getKey(PRIVATE_KEY_ALIAS, password.toCharArray());
 | 
					        Key key = keyStore.getKey(PRIVATE_KEY_ALIAS, SslUtil.getPassword(password));
 | 
				
			||||||
        Assertions.assertNotNull(key);
 | 
					        Assertions.assertNotNull(key);
 | 
				
			||||||
        Assertions.assertEquals(algorithm, key.getAlgorithm());
 | 
					        Assertions.assertEquals(algorithm, key.getAlgorithm());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -64,9 +64,13 @@ export interface AnalogueGaugeSettings {
 | 
				
			|||||||
  animationRule: AnimationRule;
 | 
					  animationRule: AnimationRule;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface BaseGaugeModel extends BaseGauge {
 | 
				
			||||||
 | 
					  _value?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class TbBaseGauge<S, O extends GenericOptions> {
 | 
					export abstract class TbBaseGauge<S, O extends GenericOptions> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private gauge: BaseGauge;
 | 
					  private gauge: BaseGaugeModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected constructor(protected ctx: WidgetContext, canvasId: string) {
 | 
					  protected constructor(protected ctx: WidgetContext, canvasId: string) {
 | 
				
			||||||
    const gaugeElement = $('#' + canvasId, ctx.$container)[0];
 | 
					    const gaugeElement = $('#' + canvasId, ctx.$container)[0];
 | 
				
			||||||
@ -77,16 +81,20 @@ export abstract class TbBaseGauge<S, O extends GenericOptions> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  protected abstract createGaugeOptions(gaugeElement: HTMLElement, settings: S): O;
 | 
					  protected abstract createGaugeOptions(gaugeElement: HTMLElement, settings: S): O;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected abstract createGauge(gaugeData: O): BaseGauge;
 | 
					  protected abstract createGauge(gaugeData: O): BaseGaugeModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update() {
 | 
					  update() {
 | 
				
			||||||
    if (this.ctx.data.length > 0) {
 | 
					    if (this.ctx.data.length > 0) {
 | 
				
			||||||
      const cellData = this.ctx.data[0];
 | 
					      const cellData = this.ctx.data[0];
 | 
				
			||||||
      if (cellData.data.length > 0) {
 | 
					      if (cellData.data.length > 0) {
 | 
				
			||||||
        const tvPair = cellData.data[cellData.data.length -
 | 
					        const tvPair = cellData.data[cellData.data.length - 1];
 | 
				
			||||||
        1];
 | 
					 | 
				
			||||||
        const value = parseFloat(tvPair[1]);
 | 
					        const value = parseFloat(tvPair[1]);
 | 
				
			||||||
        if (value !== this.gauge.value) {
 | 
					        if (value !== this.gauge.value) {
 | 
				
			||||||
 | 
					          if (!this.gauge.options.animation) {
 | 
				
			||||||
 | 
					            this.gauge._value = value;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            delete this.gauge._value;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          this.gauge.value = value;
 | 
					          this.gauge.value = value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,6 @@ import { prepareFontSettings } from '@home/components/widget/lib/settings.models
 | 
				
			|||||||
import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
 | 
					import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
 | 
				
			||||||
import { DatePipe } from '@angular/common';
 | 
					import { DatePipe } from '@angular/common';
 | 
				
			||||||
import { IWidgetSubscription } from '@core/api/widget-api.models';
 | 
					import { IWidgetSubscription } from '@core/api/widget-api.models';
 | 
				
			||||||
import { Subscription } from 'rxjs';
 | 
					 | 
				
			||||||
import { ColorProcessor, createValueSubscription, ValueSourceType } from '@shared/models/widget-settings.models';
 | 
					import { ColorProcessor, createValueSubscription, ValueSourceType } from '@shared/models/widget-settings.models';
 | 
				
			||||||
import GenericOptions = CanvasGauges.GenericOptions;
 | 
					import GenericOptions = CanvasGauges.GenericOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -260,6 +259,8 @@ export class TbCanvasDigitalGauge {
 | 
				
			|||||||
        if (value !== this.gauge.value) {
 | 
					        if (value !== this.gauge.value) {
 | 
				
			||||||
          if (!this.gauge.options.animation) {
 | 
					          if (!this.gauge.options.animation) {
 | 
				
			||||||
            this.gauge._value = value;
 | 
					            this.gauge._value = value;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            delete this.gauge._value;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          this.gauge.value = value;
 | 
					          this.gauge.value = value;
 | 
				
			||||||
        } else if (this.localSettings.showTimestamp && this.gauge.timestamp !== timestamp) {
 | 
					        } else if (this.localSettings.showTimestamp && this.gauge.timestamp !== timestamp) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user