915 changed files with 7825 additions and 30525 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,98 @@ |
|||
/** |
|||
* 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.entitiy.dashboard; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.ResourceType; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.dao.resource.ResourceService; |
|||
import org.thingsboard.server.dao.widget.WidgetsBundleService; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.util.AfterStartUp; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.sync.GitSyncService; |
|||
import org.thingsboard.server.service.sync.vc.GitRepository.FileType; |
|||
import org.thingsboard.server.service.sync.vc.GitRepository.RepoFile; |
|||
|
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.stream.Stream; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
@ConditionalOnProperty(value = "transport.gateway.dashboard.sync.enabled", havingValue = "true") |
|||
public class DashboardSyncService { |
|||
|
|||
private final GitSyncService gitSyncService; |
|||
private final ResourceService resourceService; |
|||
private final WidgetsBundleService widgetsBundleService; |
|||
private final PartitionService partitionService; |
|||
|
|||
@Value("${transport.gateway.dashboard.sync.repository_url:}") |
|||
private String repoUrl; |
|||
@Value("${transport.gateway.dashboard.sync.branch:main}") |
|||
private String branch; |
|||
@Value("${transport.gateway.dashboard.sync.fetch_frequency:24}") |
|||
private int fetchFrequencyHours; |
|||
|
|||
private static final String REPO_KEY = "gateways-dashboard"; |
|||
private static final String GATEWAYS_DASHBOARD_KEY = "gateways_dashboard.json"; |
|||
|
|||
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE) |
|||
public void init() throws Exception { |
|||
gitSyncService.registerSync(REPO_KEY, repoUrl, branch, TimeUnit.HOURS.toMillis(fetchFrequencyHours), this::update); |
|||
} |
|||
|
|||
private void update() { |
|||
if (!partitionService.isMyPartition(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)) { |
|||
return; |
|||
} |
|||
|
|||
List<RepoFile> resources = listFiles("resources"); |
|||
for (RepoFile resourceFile : resources) { |
|||
String data = getFileContent(resourceFile.path()); |
|||
resourceService.createOrUpdateSystemResource(ResourceType.JS_MODULE, resourceFile.name(), data); |
|||
} |
|||
|
|||
Stream<String> widgetsBundles = listFiles("widget_bundles").stream() |
|||
.map(widgetsBundleFile -> getFileContent(widgetsBundleFile.path())); |
|||
Stream<String> widgetTypes = listFiles("widget_types").stream() |
|||
.map(widgetTypeFile -> getFileContent(widgetTypeFile.path())); |
|||
widgetsBundleService.updateSystemWidgets(widgetsBundles, widgetTypes); |
|||
|
|||
RepoFile dashboardFile = listFiles("dashboards").get(0); |
|||
String dashboardJson = getFileContent(dashboardFile.path()); |
|||
resourceService.createOrUpdateSystemResource(ResourceType.DASHBOARD, GATEWAYS_DASHBOARD_KEY, dashboardJson); |
|||
|
|||
log.info("Gateways dashboard sync completed"); |
|||
} |
|||
|
|||
private List<RepoFile> listFiles(String path) { |
|||
return gitSyncService.listFiles(REPO_KEY, path, 1, FileType.FILE); |
|||
} |
|||
|
|||
private String getFileContent(String path) { |
|||
return gitSyncService.getFileContent(REPO_KEY, path); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
/** |
|||
* 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.sync; |
|||
|
|||
import jakarta.annotation.PreDestroy; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.sync.vc.RepositorySettings; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.sync.vc.GitRepository; |
|||
import org.thingsboard.server.service.sync.vc.GitRepository.FileType; |
|||
import org.thingsboard.server.service.sync.vc.GitRepository.RepoFile; |
|||
|
|||
import java.net.URI; |
|||
import java.nio.file.Path; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@TbCoreComponent |
|||
@Service |
|||
@Slf4j |
|||
public class DefaultGitSyncService implements GitSyncService { |
|||
|
|||
@Value("${vc.git.repositories-folder:${java.io.tmpdir}/repositories}") |
|||
private String repositoriesFolder; |
|||
|
|||
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("git-sync")); |
|||
private final Map<String, GitRepository> repositories = new ConcurrentHashMap<>(); |
|||
private final Map<String, Runnable> updateListeners = new ConcurrentHashMap<>(); |
|||
|
|||
@Override |
|||
public void registerSync(String key, String repoUri, String branch, long fetchFrequencyMs, Runnable onUpdate) { |
|||
RepositorySettings settings = new RepositorySettings(); |
|||
settings.setRepositoryUri(repoUri); |
|||
settings.setDefaultBranch(branch); |
|||
if (onUpdate != null) { |
|||
updateListeners.put(key, onUpdate); |
|||
} |
|||
|
|||
executor.execute(() -> { |
|||
initRepository(key, settings); |
|||
}); |
|||
|
|||
executor.scheduleWithFixedDelay(() -> { |
|||
GitRepository repository = repositories.get(key); |
|||
if (repository == null || !GitRepository.exists(repository.getDirectory())) { |
|||
initRepository(key, settings); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
log.debug("[{}] Fetching repository", key); |
|||
boolean updated = repository.fetch(); |
|||
if (updated) { |
|||
onUpdate(key); |
|||
} else { |
|||
log.debug("[{}] No changes in the repository", key); |
|||
} |
|||
} catch (Throwable e) { |
|||
log.error("[{}] Failed to fetch repository", key, e); |
|||
} |
|||
}, fetchFrequencyMs, fetchFrequencyMs, TimeUnit.MILLISECONDS); |
|||
} |
|||
|
|||
@Override |
|||
public List<RepoFile> listFiles(String key, String path, int depth, FileType type) { |
|||
GitRepository repository = getRepository(key); |
|||
return repository.listFilesAtCommit(getBranchRef(repository), path, depth).stream() |
|||
.filter(file -> type == null || file.type() == type) |
|||
.toList(); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public String getFileContent(String key, String path) { |
|||
GitRepository repository = getRepository(key); |
|||
try { |
|||
return repository.getFileContentAtCommit(path, getBranchRef(repository)); |
|||
} catch (Exception e) { |
|||
log.warn("[{}] Failed to get file content for path {}: {}", key, path, e.getMessage()); |
|||
return "{}"; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public String getGithubRawContentUrl(String key, String path) { |
|||
if (path == null) { |
|||
return ""; |
|||
} |
|||
RepositorySettings settings = getRepository(key).getSettings(); |
|||
return StringUtils.removeEnd(settings.getRepositoryUri(), ".git") + "/blob/" + settings.getDefaultBranch() + "/" + path + "?raw=true"; |
|||
} |
|||
|
|||
private GitRepository getRepository(String key) { |
|||
GitRepository repository = repositories.get(key); |
|||
if (repository != null) { |
|||
if (!GitRepository.exists(repository.getDirectory())) { |
|||
// reinitializing the repository because folder was deleted
|
|||
initRepository(key, repository.getSettings()); |
|||
} |
|||
} |
|||
|
|||
repository = repositories.get(key); |
|||
if (repository == null) { |
|||
throw new IllegalStateException(key + " repository is not initialized"); |
|||
} |
|||
return repository; |
|||
} |
|||
|
|||
private void initRepository(String key, RepositorySettings settings) { |
|||
try { |
|||
repositories.remove(key); |
|||
Path directory = getRepoDirectory(settings); |
|||
|
|||
GitRepository repository = GitRepository.openOrClone(directory, settings, true); |
|||
repositories.put(key, repository); |
|||
log.info("[{}] Initialized repository", key); |
|||
|
|||
onUpdate(key); |
|||
} catch (Throwable e) { |
|||
log.error("[{}] Failed to initialize repository with settings {}", key, settings, e); |
|||
} |
|||
} |
|||
|
|||
private void onUpdate(String key) { |
|||
Runnable listener = updateListeners.get(key); |
|||
if (listener != null) { |
|||
log.debug("[{}] Handling repository update", key); |
|||
try { |
|||
listener.run(); |
|||
} catch (Throwable e) { |
|||
log.error("[{}] Failed to handle repository update", key, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private Path getRepoDirectory(RepositorySettings settings) { |
|||
// using uri to define folder name in case repo url is changed
|
|||
String name = URI.create(settings.getRepositoryUri()).getPath().replaceAll("[^a-zA-Z]", ""); |
|||
return Path.of(repositoriesFolder, name); |
|||
} |
|||
|
|||
private String getBranchRef(GitRepository repository) { |
|||
return "refs/remotes/origin/" + repository.getSettings().getDefaultBranch(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
private void preDestroy() { |
|||
executor.shutdownNow(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
/** |
|||
* 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.entitiy.dashboard; |
|||
|
|||
import org.junit.After; |
|||
import org.junit.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.mock.web.MockHttpServletResponse; |
|||
import org.springframework.test.context.TestPropertySource; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.Dashboard; |
|||
import org.thingsboard.server.controller.AbstractControllerTest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
import org.thingsboard.server.dao.sql.resource.TbResourceRepository; |
|||
import org.thingsboard.server.dao.sql.widget.WidgetTypeRepository; |
|||
import org.thingsboard.server.dao.sql.widget.WidgetsBundleRepository; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@DaoSqlTest |
|||
@TestPropertySource(properties = { |
|||
"transport.gateway.dashboard.sync.enabled=true" |
|||
}) |
|||
public class DashboardSyncServiceTest extends AbstractControllerTest { |
|||
|
|||
@Autowired |
|||
private WidgetTypeRepository widgetTypeRepository; |
|||
@Autowired |
|||
private WidgetsBundleRepository widgetsBundleRepository; |
|||
@Autowired |
|||
private TbResourceRepository resourceRepository; |
|||
|
|||
@After |
|||
public void after() throws Exception { |
|||
widgetsBundleRepository.deleteAll(); |
|||
widgetTypeRepository.deleteAll(); |
|||
resourceRepository.deleteAll(); |
|||
} |
|||
|
|||
@Test |
|||
public void testGatewaysDashboardSync() throws Exception { |
|||
loginTenantAdmin(); |
|||
await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { |
|||
MockHttpServletResponse response = doGet("/api/resource/dashboard/system/gateways_dashboard.json") |
|||
.andExpect(status().isOk()) |
|||
.andReturn().getResponse(); |
|||
String dashboardJson = response.getContentAsString(); |
|||
String etag = response.getHeader("ETag"); |
|||
|
|||
Dashboard dashboard = JacksonUtil.fromString(dashboardJson, Dashboard.class); |
|||
assertThat(dashboard).isNotNull(); |
|||
assertThat(dashboard.getTitle()).containsIgnoringCase("gateway"); |
|||
assertThat(etag).isNotBlank(); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,186 @@ |
|||
/** |
|||
* 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 org.junit.jupiter.api.Test; |
|||
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.plugin.ComponentLifecycleEvent; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals; |
|||
import static org.junit.jupiter.api.Assertions.assertFalse; |
|||
import static org.junit.jupiter.api.Assertions.assertNotNull; |
|||
import static org.junit.jupiter.api.Assertions.assertNull; |
|||
import static org.junit.jupiter.api.Assertions.assertTrue; |
|||
|
|||
public class TbEntityLocalSubsInfoTest { |
|||
|
|||
@Test |
|||
public void addTest() { |
|||
Set<TbAttributeSubscription> expectedSubs = new HashSet<>(); |
|||
TbEntityLocalSubsInfo subsInfo = createSubsInfo(); |
|||
TenantId tenantId = subsInfo.getTenantId(); |
|||
EntityId entityId = subsInfo.getEntityId(); |
|||
TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() |
|||
.sessionId("session1") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key1", 1L, "key2", 2L)) |
|||
.build(); |
|||
expectedSubs.add(attrSubscription1); |
|||
TbEntitySubEvent created = subsInfo.add(attrSubscription1); |
|||
assertFalse(subsInfo.isEmpty()); |
|||
assertNotNull(created); |
|||
assertEquals(expectedSubs, subsInfo.getSubs()); |
|||
checkEvent(created, expectedSubs, ComponentLifecycleEvent.CREATED); |
|||
|
|||
assertNull(subsInfo.add(attrSubscription1)); |
|||
|
|||
TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() |
|||
.sessionId("session2") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key3", 3L, "key4", 4L)) |
|||
.build(); |
|||
expectedSubs.add(attrSubscription2); |
|||
TbEntitySubEvent updated = subsInfo.add(attrSubscription2); |
|||
assertNotNull(updated); |
|||
|
|||
assertEquals(expectedSubs, subsInfo.getSubs()); |
|||
checkEvent(updated, expectedSubs, ComponentLifecycleEvent.UPDATED); |
|||
} |
|||
|
|||
@Test |
|||
public void removeTest() { |
|||
Set<TbAttributeSubscription> expectedSubs = new HashSet<>(); |
|||
TbEntityLocalSubsInfo subsInfo = createSubsInfo(); |
|||
TenantId tenantId = subsInfo.getTenantId(); |
|||
EntityId entityId = subsInfo.getEntityId(); |
|||
TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() |
|||
.sessionId("session1") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key1", 1L, "key2", 2L)) |
|||
.build(); |
|||
|
|||
TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() |
|||
.sessionId("session2") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key3", 3L, "key4", 4L)) |
|||
.build(); |
|||
|
|||
expectedSubs.add(attrSubscription1); |
|||
expectedSubs.add(attrSubscription2); |
|||
|
|||
subsInfo.add(attrSubscription1); |
|||
subsInfo.add(attrSubscription2); |
|||
|
|||
assertEquals(expectedSubs, subsInfo.getSubs()); |
|||
|
|||
TbEntitySubEvent updatedEvent = subsInfo.remove(attrSubscription1); |
|||
expectedSubs.remove(attrSubscription1); |
|||
assertNotNull(updatedEvent); |
|||
assertEquals(expectedSubs, subsInfo.getSubs()); |
|||
checkEvent(updatedEvent, expectedSubs, ComponentLifecycleEvent.UPDATED); |
|||
|
|||
TbEntitySubEvent deletedEvent = subsInfo.remove(attrSubscription2); |
|||
expectedSubs.remove(attrSubscription2); |
|||
assertNotNull(deletedEvent); |
|||
assertEquals(expectedSubs, subsInfo.getSubs()); |
|||
checkEvent(deletedEvent, expectedSubs, ComponentLifecycleEvent.DELETED); |
|||
|
|||
assertTrue(subsInfo.isEmpty()); |
|||
} |
|||
|
|||
@Test |
|||
public void removeAllTest() { |
|||
TbEntityLocalSubsInfo subsInfo = createSubsInfo(); |
|||
TenantId tenantId = subsInfo.getTenantId(); |
|||
EntityId entityId = subsInfo.getEntityId(); |
|||
TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() |
|||
.sessionId("session1") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key1", 1L, "key2", 2L)) |
|||
.build(); |
|||
|
|||
TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() |
|||
.sessionId("session2") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key3", 3L, "key4", 4L)) |
|||
.build(); |
|||
|
|||
TbAttributeSubscription attrSubscription3 = TbAttributeSubscription.builder() |
|||
.sessionId("session3") |
|||
.tenantId(tenantId) |
|||
.entityId(entityId) |
|||
.keyStates(Map.of("key5", 5L, "key6", 6L)) |
|||
.build(); |
|||
|
|||
subsInfo.add(attrSubscription1); |
|||
subsInfo.add(attrSubscription2); |
|||
subsInfo.add(attrSubscription3); |
|||
|
|||
assertFalse(subsInfo.isEmpty()); |
|||
|
|||
TbEntitySubEvent updatedEvent = subsInfo.removeAll(List.of(attrSubscription1, attrSubscription2)); |
|||
assertNotNull(updatedEvent); |
|||
checkEvent(updatedEvent, Set.of(attrSubscription3), ComponentLifecycleEvent.UPDATED); |
|||
|
|||
assertFalse(subsInfo.isEmpty()); |
|||
|
|||
TbEntitySubEvent deletedEvent = subsInfo.removeAll(List.of(attrSubscription3)); |
|||
assertNotNull(deletedEvent); |
|||
checkEvent(deletedEvent, null, ComponentLifecycleEvent.DELETED); |
|||
|
|||
assertTrue(subsInfo.isEmpty()); |
|||
} |
|||
|
|||
private TbEntityLocalSubsInfo createSubsInfo() { |
|||
return new TbEntityLocalSubsInfo(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID())); |
|||
} |
|||
|
|||
private void checkEvent(TbEntitySubEvent event, Set<TbAttributeSubscription> expectedSubs, ComponentLifecycleEvent expectedType) { |
|||
assertEquals(expectedType, event.getType()); |
|||
TbSubscriptionsInfo info = event.getInfo(); |
|||
if (event.getType() == ComponentLifecycleEvent.DELETED) { |
|||
assertNull(info); |
|||
return; |
|||
} |
|||
assertNotNull(info); |
|||
assertFalse(info.notifications); |
|||
assertFalse(info.alarms); |
|||
assertFalse(info.attrAllKeys); |
|||
assertFalse(info.tsAllKeys); |
|||
assertNull(info.tsKeys); |
|||
assertEquals(getAttrKeys(expectedSubs), info.attrKeys); |
|||
} |
|||
|
|||
private Set<String> getAttrKeys(Set<TbAttributeSubscription> attributeSubscriptions) { |
|||
return attributeSubscriptions.stream().map(s -> s.getKeyStates().keySet()).flatMap(Collection::stream).collect(Collectors.toSet()); |
|||
} |
|||
} |
|||
@ -1,732 +0,0 @@ |
|||
/** |
|||
* 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.transport.lwm2m.client; |
|||
|
|||
import org.eclipse.leshan.client.LwM2mClient; |
|||
import org.eclipse.leshan.client.resource.BaseObjectEnabler; |
|||
import org.eclipse.leshan.client.resource.DummyInstanceEnabler; |
|||
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; |
|||
import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory; |
|||
import org.eclipse.leshan.client.resource.listener.ResourceListener; |
|||
import org.eclipse.leshan.client.servers.LwM2mServer; |
|||
import org.eclipse.leshan.client.servers.ServersInfoExtractor; |
|||
import org.eclipse.leshan.client.util.LinkFormatHelper; |
|||
import org.eclipse.leshan.core.Destroyable; |
|||
import org.eclipse.leshan.core.LwM2mId; |
|||
import org.eclipse.leshan.core.Startable; |
|||
import org.eclipse.leshan.core.Stoppable; |
|||
import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; |
|||
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; |
|||
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; |
|||
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; |
|||
import org.eclipse.leshan.core.model.ObjectModel; |
|||
import org.eclipse.leshan.core.model.ResourceModel; |
|||
import org.eclipse.leshan.core.node.LwM2mMultipleResource; |
|||
import org.eclipse.leshan.core.node.LwM2mObject; |
|||
import org.eclipse.leshan.core.node.LwM2mObjectInstance; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.core.node.LwM2mResource; |
|||
import org.eclipse.leshan.core.node.LwM2mResourceInstance; |
|||
import org.eclipse.leshan.core.request.BootstrapDeleteRequest; |
|||
import org.eclipse.leshan.core.request.BootstrapReadRequest; |
|||
import org.eclipse.leshan.core.request.BootstrapWriteRequest; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.request.CreateRequest; |
|||
import org.eclipse.leshan.core.request.DeleteRequest; |
|||
import org.eclipse.leshan.core.request.DiscoverRequest; |
|||
import org.eclipse.leshan.core.request.DownlinkRequest; |
|||
import org.eclipse.leshan.core.request.ExecuteRequest; |
|||
import org.eclipse.leshan.core.request.ObserveRequest; |
|||
import org.eclipse.leshan.core.request.ReadRequest; |
|||
import org.eclipse.leshan.core.request.WriteAttributesRequest; |
|||
import org.eclipse.leshan.core.request.WriteRequest; |
|||
import org.eclipse.leshan.core.request.WriteRequest.Mode; |
|||
import org.eclipse.leshan.core.response.BootstrapDeleteResponse; |
|||
import org.eclipse.leshan.core.response.BootstrapReadResponse; |
|||
import org.eclipse.leshan.core.response.BootstrapWriteResponse; |
|||
import org.eclipse.leshan.core.response.CreateResponse; |
|||
import org.eclipse.leshan.core.response.DeleteResponse; |
|||
import org.eclipse.leshan.core.response.DiscoverResponse; |
|||
import org.eclipse.leshan.core.response.ExecuteResponse; |
|||
import org.eclipse.leshan.core.response.ObserveResponse; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Map.Entry; |
|||
|
|||
public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyable, Startable, Stoppable { |
|||
|
|||
private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class); |
|||
|
|||
protected Map<Integer, LwM2mInstanceEnabler> instances; |
|||
|
|||
protected LwM2mInstanceEnablerFactory instanceFactory; |
|||
protected ContentFormat defaultContentFormat; |
|||
|
|||
private LinkFormatHelper tbLinkFormatHelper; |
|||
protected Map<LwM2mPath, LwM2mAttributeSet> lwM2mAttributes; |
|||
public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map<Integer, LwM2mInstanceEnabler> instances, |
|||
LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { |
|||
super(id, objectModel); |
|||
this.instances = new HashMap<>(instances); |
|||
; |
|||
this.instanceFactory = instanceFactory; |
|||
this.defaultContentFormat = defaultContentFormat; |
|||
for (Entry<Integer, LwM2mInstanceEnabler> entry : this.instances.entrySet()) { |
|||
instances.put(entry.getKey(), entry.getValue()); |
|||
listenInstance(entry.getValue(), entry.getKey()); |
|||
} |
|||
this.lwM2mAttributes = new HashMap<>(); |
|||
} |
|||
|
|||
public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) { |
|||
super(id, objectModel); |
|||
} |
|||
|
|||
@Override |
|||
public synchronized List<Integer> getAvailableInstanceIds() { |
|||
List<Integer> ids = new ArrayList<>(instances.keySet()); |
|||
Collections.sort(ids); |
|||
return ids; |
|||
} |
|||
|
|||
@Override |
|||
public synchronized List<Integer> getAvailableResourceIds(int instanceId) { |
|||
LwM2mInstanceEnabler instanceEnabler = instances.get(instanceId); |
|||
if (instanceEnabler != null) { |
|||
return instanceEnabler.getAvailableResourceIds(getObjectModel()); |
|||
} else { |
|||
return Collections.emptyList(); |
|||
} |
|||
} |
|||
|
|||
public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) { |
|||
instances.put(instanceId, newInstance); |
|||
listenInstance(newInstance, instanceId); |
|||
fireInstancesAdded(instanceId); |
|||
} |
|||
|
|||
public synchronized LwM2mInstanceEnabler getInstance(int instanceId) { |
|||
return instances.get(instanceId); |
|||
} |
|||
|
|||
public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) { |
|||
LwM2mInstanceEnabler removedInstance = instances.remove(instanceId); |
|||
if (removedInstance != null) { |
|||
fireInstancesRemoved(removedInstance.getId()); |
|||
} |
|||
return removedInstance; |
|||
} |
|||
|
|||
@Override |
|||
protected CreateResponse doCreate(LwM2mServer server, CreateRequest request) { |
|||
if (!getObjectModel().multiple && instances.size() > 0) { |
|||
return CreateResponse.badRequest("an instance already exist for this single instance object"); |
|||
} |
|||
|
|||
if (request.unknownObjectInstanceId()) { |
|||
// create instance
|
|||
LwM2mInstanceEnabler newInstance = createInstance(server, getObjectModel().multiple ? null : 0, |
|||
request.getResources()); |
|||
|
|||
// add new instance to this object
|
|||
instances.put(newInstance.getId(), newInstance); |
|||
listenInstance(newInstance, newInstance.getId()); |
|||
fireInstancesAdded(newInstance.getId()); |
|||
|
|||
return CreateResponse |
|||
.success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString()); |
|||
} else { |
|||
List<LwM2mObjectInstance> instanceNodes = request.getObjectInstances(); |
|||
|
|||
// checks single object instances
|
|||
if (!getObjectModel().multiple) { |
|||
if (request.getObjectInstances().size() > 1) { |
|||
return CreateResponse.badRequest("can not create several instances on this single instance object"); |
|||
} |
|||
if (request.getObjectInstances().get(0).getId() != 0) { |
|||
return CreateResponse.badRequest("single instance object must use 0 as ID"); |
|||
} |
|||
} |
|||
// ensure instance does not already exists
|
|||
for (LwM2mObjectInstance instance : instanceNodes) { |
|||
if (instances.containsKey(instance.getId())) { |
|||
return CreateResponse.badRequest(String.format("instance %d already exists", instance.getId())); |
|||
} |
|||
} |
|||
|
|||
// create the new instances
|
|||
int[] instanceIds = new int[request.getObjectInstances().size()]; |
|||
int i = 0; |
|||
for (LwM2mObjectInstance instance : request.getObjectInstances()) { |
|||
// create instance
|
|||
LwM2mInstanceEnabler newInstance = createInstance(server, instance.getId(), |
|||
instance.getResources().values()); |
|||
|
|||
// add new instance to this object
|
|||
instances.put(newInstance.getId(), newInstance); |
|||
listenInstance(newInstance, newInstance.getId()); |
|||
|
|||
// store instance ids
|
|||
instanceIds[i] = newInstance.getId(); |
|||
i++; |
|||
} |
|||
fireInstancesAdded(instanceIds); |
|||
return CreateResponse.success(); |
|||
} |
|||
} |
|||
|
|||
protected LwM2mInstanceEnabler createInstance(LwM2mServer server, Integer instanceId, |
|||
Collection<LwM2mResource> resources) { |
|||
// create the new instance
|
|||
LwM2mInstanceEnabler newInstance = instanceFactory.create(getObjectModel(), instanceId, instances.keySet()); |
|||
newInstance.setLwM2mClient(getLwm2mClient()); |
|||
|
|||
// add/write resource
|
|||
for (LwM2mResource resource : resources) { |
|||
newInstance.write(server, true, resource.getId(), resource); |
|||
} |
|||
|
|||
return newInstance; |
|||
} |
|||
|
|||
@Override |
|||
protected ReadResponse doRead(LwM2mServer server, ReadRequest request) { |
|||
LwM2mPath path = request.getPath(); |
|||
|
|||
// Manage Object case
|
|||
if (path.isObject()) { |
|||
List<LwM2mObjectInstance> lwM2mObjectInstances = new ArrayList<>(); |
|||
for (LwM2mInstanceEnabler instance : instances.values()) { |
|||
ReadResponse response = instance.read(server); |
|||
if (response.isSuccess()) { |
|||
lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); |
|||
} |
|||
} |
|||
return ReadResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); |
|||
} |
|||
|
|||
// Manage Instance case
|
|||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|||
if (instance == null) |
|||
return ReadResponse.notFound(); |
|||
|
|||
if (path.getResourceId() == null) { |
|||
return instance.read(server); |
|||
} |
|||
|
|||
// Manage Resource case
|
|||
if (path.getResourceInstanceId() == null) { |
|||
return instance.read(server, path.getResourceId()); |
|||
} |
|||
|
|||
// Manage Resource Instance case
|
|||
return instance.read(server, path.getResourceId(), path.getResourceInstanceId()); |
|||
} |
|||
|
|||
@Override |
|||
protected BootstrapReadResponse doRead(LwM2mServer server, BootstrapReadRequest request) { |
|||
// Basic implementation we delegate to classic Read Request
|
|||
ReadResponse response = doRead(server, |
|||
new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest())); |
|||
return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage()); |
|||
} |
|||
|
|||
@Override |
|||
protected ObserveResponse doObserve(final LwM2mServer server, final ObserveRequest request) { |
|||
final LwM2mPath path = request.getPath(); |
|||
|
|||
// Manage Object case
|
|||
if (path.isObject()) { |
|||
List<LwM2mObjectInstance> lwM2mObjectInstances = new ArrayList<>(); |
|||
for (LwM2mInstanceEnabler instance : instances.values()) { |
|||
ReadResponse response = instance.observe(server); |
|||
if (response.isSuccess()) { |
|||
lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); |
|||
} |
|||
} |
|||
return ObserveResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); |
|||
} |
|||
|
|||
// Manage Instance case
|
|||
final LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|||
if (instance == null) |
|||
return ObserveResponse.notFound(); |
|||
|
|||
if (path.getResourceId() == null) { |
|||
return instance.observe(server); |
|||
} |
|||
|
|||
// Manage Resource case
|
|||
if (path.getResourceInstanceId() == null) { |
|||
return instance.observe(server, path.getResourceId()); |
|||
} |
|||
|
|||
// Manage Resource Instance case
|
|||
return instance.observe(server, path.getResourceId(), path.getResourceInstanceId()); |
|||
} |
|||
|
|||
@Override |
|||
protected WriteResponse doWrite(LwM2mServer server, WriteRequest request) { |
|||
LwM2mPath path = request.getPath(); |
|||
|
|||
// Manage Instance case
|
|||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|||
if (instance == null) |
|||
return WriteResponse.notFound(); |
|||
|
|||
if (path.isObjectInstance()) { |
|||
return instance.write(server, request.isReplaceRequest(), (LwM2mObjectInstance) request.getNode()); |
|||
} |
|||
|
|||
// Manage Resource case
|
|||
if (path.getResourceInstanceId() == null) { |
|||
return instance.write(server, request.isReplaceRequest(), path.getResourceId(), |
|||
(LwM2mResource) request.getNode()); |
|||
} |
|||
|
|||
// Manage Resource Instance case
|
|||
return instance.write(server, false, path.getResourceId(), path.getResourceInstanceId(), |
|||
((LwM2mResourceInstance) request.getNode())); |
|||
} |
|||
|
|||
@Override |
|||
protected BootstrapWriteResponse doWrite(LwM2mServer server, BootstrapWriteRequest request) { |
|||
LwM2mPath path = request.getPath(); |
|||
|
|||
// Manage Object case
|
|||
if (path.isObject()) { |
|||
for (LwM2mObjectInstance instanceNode : ((LwM2mObject) request.getNode()).getInstances().values()) { |
|||
LwM2mInstanceEnabler instanceEnabler = instances.get(instanceNode.getId()); |
|||
if (instanceEnabler == null) { |
|||
doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); |
|||
} else { |
|||
doWrite(server, new WriteRequest(Mode.REPLACE, path.getObjectId(), instanceEnabler.getId(), |
|||
instanceNode.getResources().values())); |
|||
} |
|||
} |
|||
return BootstrapWriteResponse.success(); |
|||
} |
|||
|
|||
// Manage Instance case
|
|||
if (path.isObjectInstance()) { |
|||
LwM2mObjectInstance instanceNode = (LwM2mObjectInstance) request.getNode(); |
|||
LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); |
|||
if (instanceEnabler == null) { |
|||
doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); |
|||
} else { |
|||
doWrite(server, new WriteRequest(Mode.REPLACE, request.getContentFormat(), path.getObjectId(), |
|||
path.getObjectInstanceId(), instanceNode.getResources().values())); |
|||
} |
|||
return BootstrapWriteResponse.success(); |
|||
} |
|||
|
|||
// Manage resource case
|
|||
LwM2mResource resource = (LwM2mResource) request.getNode(); |
|||
LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); |
|||
if (instanceEnabler == null) { |
|||
doCreate(server, new CreateRequest(path.getObjectId(), |
|||
new LwM2mObjectInstance(path.getObjectInstanceId(), resource))); |
|||
} else { |
|||
instanceEnabler.write(server, true, path.getResourceId(), resource); |
|||
} |
|||
return BootstrapWriteResponse.success(); |
|||
} |
|||
|
|||
@Override |
|||
protected ExecuteResponse doExecute(LwM2mServer server, ExecuteRequest request) { |
|||
LwM2mPath path = request.getPath(); |
|||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|||
if (instance == null) { |
|||
return ExecuteResponse.notFound(); |
|||
} |
|||
return instance.execute(server, path.getResourceId(), request.getArguments()); |
|||
} |
|||
|
|||
@Override |
|||
protected DeleteResponse doDelete(LwM2mServer server, DeleteRequest request) { |
|||
LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId()); |
|||
if (deletedInstance != null) { |
|||
deletedInstance.onDelete(server); |
|||
fireInstancesRemoved(deletedInstance.getId()); |
|||
return DeleteResponse.success(); |
|||
} |
|||
return DeleteResponse.notFound(); |
|||
} |
|||
|
|||
@Override |
|||
public BootstrapDeleteResponse doDelete(LwM2mServer server, BootstrapDeleteRequest request) { |
|||
if (request.getPath().isRoot() || request.getPath().isObject()) { |
|||
if (id == LwM2mId.SECURITY) { |
|||
// For security object, we clean everything except bootstrap Server account.
|
|||
|
|||
// Get bootstrap account and store removed instances ids
|
|||
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerAccount = null; |
|||
int[] instanceIds = new int[instances.size()]; |
|||
int i = 0; |
|||
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) { |
|||
if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) { |
|||
bootstrapServerAccount = instance; |
|||
} else { |
|||
// Store instance ids
|
|||
instanceIds[i] = instance.getKey(); |
|||
i++; |
|||
} |
|||
} |
|||
// Clear everything
|
|||
instances.clear(); |
|||
|
|||
// Put bootstrap account again
|
|||
if (bootstrapServerAccount != null) { |
|||
instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue()); |
|||
} |
|||
|
|||
fireInstancesRemoved(instanceIds); |
|||
return BootstrapDeleteResponse.success(); |
|||
} else if (id == LwM2mId.OSCORE) { |
|||
// For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account.
|
|||
|
|||
// Get bootstrap account
|
|||
LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( |
|||
getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); |
|||
// Get OSCORE instance ID associated to it
|
|||
Integer bootstrapOscoreInstanceId = bootstrapInstance != null |
|||
? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) |
|||
: null; |
|||
|
|||
// if bootstrap server use OSCORE,
|
|||
// search the OSCORE instance for this ID and store removed instances ids
|
|||
if (bootstrapOscoreInstanceId != null) { |
|||
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerOscore = null; |
|||
int[] instanceIds = new int[instances.size()]; |
|||
int i = 0; |
|||
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) { |
|||
if (bootstrapOscoreInstanceId.equals(instance.getKey())) { |
|||
bootstrapServerOscore = instance; |
|||
} else { |
|||
// Store instance ids
|
|||
instanceIds[i] = instance.getKey(); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
// Clear everything
|
|||
instances.clear(); |
|||
|
|||
// Put bootstrap OSCORE instance again
|
|||
if (bootstrapServerOscore != null) { |
|||
instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue()); |
|||
} |
|||
fireInstancesRemoved(instanceIds); |
|||
return BootstrapDeleteResponse.success(); |
|||
} |
|||
// else delete everything.
|
|||
} |
|||
|
|||
// In all other cases, just delete everything
|
|||
instances.clear(); |
|||
// fired instances removed
|
|||
int[] instanceIds = new int[instances.size()]; |
|||
int i = 0; |
|||
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) { |
|||
instanceIds[i] = instance.getKey(); |
|||
i++; |
|||
} |
|||
fireInstancesRemoved(instanceIds); |
|||
|
|||
return BootstrapDeleteResponse.success(); |
|||
} else if (request.getPath().isObjectInstance()) { |
|||
if (id == LwM2mId.SECURITY) { |
|||
// For security object, deleting bootstrap Server account is not allowed
|
|||
LwM2mInstanceEnabler instance = instances.get(request.getPath().getObjectInstanceId()); |
|||
if (instance == null) { |
|||
return BootstrapDeleteResponse |
|||
.badRequest(String.format("Instance %s not found", request.getPath())); |
|||
} else if (ServersInfoExtractor.isBootstrapServer(instance)) { |
|||
return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted"); |
|||
} |
|||
} else if (id == LwM2mId.OSCORE) { |
|||
// For OSCORE object, deleting instance linked to Bootstrap account is not allowed
|
|||
|
|||
// Get bootstrap instance
|
|||
LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( |
|||
getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); |
|||
// Get OSCORE instance ID associated to it
|
|||
Integer bootstrapOscoreInstanceId = bootstrapInstance != null |
|||
? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) |
|||
: null; |
|||
|
|||
if (bootstrapOscoreInstanceId != null |
|||
&& bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) { |
|||
return BootstrapDeleteResponse |
|||
.badRequest("OSCORE instance linked to bootstrap server can not be deleted"); |
|||
} |
|||
} |
|||
if (null != instances.remove(request.getPath().getObjectInstanceId())) { |
|||
fireInstancesRemoved(request.getPath().getObjectInstanceId()); |
|||
return BootstrapDeleteResponse.success(); |
|||
} else { |
|||
return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath())); |
|||
} |
|||
} |
|||
return BootstrapDeleteResponse.badRequest(String.format("unexcepted path %s", request.getPath())); |
|||
} |
|||
|
|||
protected void listenInstance(LwM2mInstanceEnabler instance, final int instanceId) { |
|||
instance.addResourceListener(new ResourceListener() { |
|||
@Override |
|||
public void resourceChanged(LwM2mPath... paths) { |
|||
for (LwM2mPath path : paths) { |
|||
if (!isValid(instanceId, path)) { |
|||
LOG.warn("InstanceEnabler ({}) of object ({}) try to raise a change of {} which seems invalid.", |
|||
instanceId, getId(), path); |
|||
} |
|||
} |
|||
fireResourcesChanged(paths); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected boolean isValid(int instanceId, LwM2mPath pathToValidate) { |
|||
if (!(pathToValidate.isResource() || pathToValidate.isResourceInstance())) |
|||
return false; |
|||
|
|||
if (pathToValidate.getObjectId() != getId()) { |
|||
return false; |
|||
} |
|||
|
|||
if (pathToValidate.getObjectInstanceId() != instanceId) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public ContentFormat getDefaultEncodingFormat(DownlinkRequest<?> request) { |
|||
return defaultContentFormat; |
|||
} |
|||
|
|||
@Override |
|||
public void init(LwM2mClient client, LinkFormatHelper linkFormatHelper) { |
|||
super.init(client, linkFormatHelper); |
|||
this.tbLinkFormatHelper = linkFormatHelper; |
|||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|||
instanceEnabler.setLwM2mClient(client); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|||
if (instanceEnabler instanceof Destroyable) { |
|||
((Destroyable) instanceEnabler).destroy(); |
|||
} else if (instanceEnabler instanceof Stoppable) { |
|||
((Stoppable) instanceEnabler).stop(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void start() { |
|||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|||
if (instanceEnabler instanceof Startable) { |
|||
((Startable) instanceEnabler).start(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void stop() { |
|||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|||
if (instanceEnabler instanceof Stoppable) { |
|||
((Stoppable) instanceEnabler).stop(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public synchronized WriteAttributesResponse writeAttributes(LwM2mServer server, WriteAttributesRequest request) { |
|||
// execute is not supported for bootstrap
|
|||
if (server.isLwm2mBootstrapServer()) { |
|||
return WriteAttributesResponse.methodNotAllowed(); |
|||
} |
|||
// return WriteAttributesResponse.internalServerError("not implemented");
|
|||
return doWriteAttributes(server, request); |
|||
} |
|||
|
|||
/** |
|||
* <NOTIFICATION> Class Attributes |
|||
* - pmin (def = 0(sec)) Integer Resource/Object Instance/Object Readable Resource |
|||
* - pmax (def = -- ) Integer Resource/Object Instance/Object Readable Resource |
|||
* - Greater Than gt (def = -- ) Float Resource Numerical&Readable Resource |
|||
* - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource |
|||
* - Step st (def = -- ) Float Resource Numerical&Readable Resource |
|||
*/ |
|||
public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { |
|||
LwM2mPath lwM2mPath = request.getPath(); |
|||
LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath); |
|||
Map <String, LwM2mAttribute<?>> attributes = new HashMap<>(); |
|||
|
|||
for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) { |
|||
if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) { |
|||
if (lwM2mPath.isObject() || lwM2mPath.isObjectInstance() || lwM2mPath.isResource()) { |
|||
attributes.put(attr.getName(), attr); |
|||
} else { |
|||
return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource/Object Instance/Object."); |
|||
} |
|||
} else if (attr.getName().equals("gt") || attr.getName().equals("lt") || attr.getName().equals("st")) { |
|||
if (lwM2mPath.isResource()) { |
|||
attributes.put(attr.getName(), attr); |
|||
} else { |
|||
return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource."); |
|||
} |
|||
} |
|||
} |
|||
if (attributes.size()>0){ |
|||
if (attributeSet == null) { |
|||
attributeSet = new LwM2mAttributeSet(attributes.values()); |
|||
} else { |
|||
Iterable<LwM2mAttribute<?>> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes(); |
|||
Map <String, LwM2mAttribute<?>> attributesOld = new HashMap<>(); |
|||
for (LwM2mAttribute<?> attr : lwM2mAttributeIterable) { |
|||
attributesOld.put(attr.getName(), attr); |
|||
} |
|||
attributesOld.putAll(attributes); |
|||
attributeSet = new LwM2mAttributeSet(attributesOld.values()); |
|||
} |
|||
lwM2mAttributes.put(lwM2mPath, attributeSet); |
|||
return WriteAttributesResponse.success(); |
|||
} |
|||
return WriteAttributesResponse.internalServerError("not implemented"); |
|||
} |
|||
|
|||
@Override |
|||
public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) { |
|||
|
|||
if (server.isLwm2mBootstrapServer()) { |
|||
// discover is not supported for bootstrap
|
|||
return DiscoverResponse.methodNotAllowed(); |
|||
} |
|||
|
|||
if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { |
|||
return DiscoverResponse.notFound(); |
|||
} |
|||
return doDiscover(server, request); |
|||
|
|||
} |
|||
|
|||
protected DiscoverResponse doDiscover(LwM2mServer server, DiscoverRequest request) { |
|||
|
|||
LwM2mPath path = request.getPath(); |
|||
if (path.isObject()) { |
|||
LwM2mLink[] ObjectLinks = linkUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(this, null), server); |
|||
return DiscoverResponse.success(ObjectLinks); |
|||
|
|||
} else if (path.isObjectInstance()) { |
|||
// Manage discover on instance
|
|||
if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) |
|||
return DiscoverResponse.notFound(); |
|||
|
|||
LwM2mLink[] instanceLink = linkUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null), server); |
|||
return DiscoverResponse.success(instanceLink); |
|||
|
|||
} else if (path.isResource()) { |
|||
// Manage discover on resource
|
|||
if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) |
|||
return DiscoverResponse.notFound(); |
|||
|
|||
ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); |
|||
if (resourceModel == null) |
|||
return DiscoverResponse.notFound(); |
|||
|
|||
if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) |
|||
return DiscoverResponse.notFound(); |
|||
|
|||
LwM2mLink resourceLink = linkAddAttribute( |
|||
this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null), |
|||
server); |
|||
return DiscoverResponse.success(new LwM2mLink[] { resourceLink }); |
|||
} |
|||
return DiscoverResponse.badRequest(null); |
|||
} |
|||
|
|||
private LwM2mLink[] linkUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { |
|||
return Arrays.stream(links) |
|||
.map(link -> linkAddAttribute(link, server)) |
|||
.toArray(LwM2mLink[]::new); |
|||
} |
|||
|
|||
private LwM2mLink linkAddAttribute(LwM2mLink link, LwM2mServer server) { |
|||
|
|||
LwM2mAttributeSet lwM2mAttributeSetDop = null; |
|||
if (this.lwM2mAttributes.get(link.getPath())!= null){ |
|||
lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); |
|||
} |
|||
LwM2mAttribute resourceAttributeDim = getResourceAttributes (server, link.getPath()); |
|||
|
|||
Map <String, LwM2mAttribute<?>> attributes = new HashMap<>(); |
|||
if (link.getAttributes() != null) { |
|||
for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { |
|||
attributes.put(attr.getName(), attr); |
|||
} |
|||
} |
|||
if (lwM2mAttributeSetDop != null) { |
|||
for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { |
|||
attributes.put(attr.getName(), attr); |
|||
} |
|||
} |
|||
if (resourceAttributeDim != null) { |
|||
attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); |
|||
} |
|||
return new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values()); |
|||
} |
|||
|
|||
protected LwM2mAttribute getResourceAttributes (LwM2mServer server, LwM2mPath path) { |
|||
ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); |
|||
if (path.isResource() && resourceModel.multiple) { |
|||
return getResourceAttributeDim(path, server); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) { |
|||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|||
try { |
|||
ReadResponse readResponse = instance.read(server, path.getResourceId()); |
|||
if (readResponse.getCode().getCode()==205 && readResponse.getContent() instanceof LwM2mMultipleResource) { |
|||
long valueDim = ((LwM2mMultipleResource)readResponse.getContent()).getInstances().size(); |
|||
return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim); |
|||
} else { |
|||
return null; |
|||
} |
|||
} catch (Exception e ){ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -1,71 +0,0 @@ |
|||
/** |
|||
* 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.transport.lwm2m.client; |
|||
|
|||
import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory; |
|||
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; |
|||
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; |
|||
import org.eclipse.leshan.client.resource.ObjectsInitializer; |
|||
import org.eclipse.leshan.core.model.LwM2mModel; |
|||
import org.eclipse.leshan.core.model.ObjectModel; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
public class TbObjectsInitializer extends ObjectsInitializer { |
|||
|
|||
|
|||
public TbObjectsInitializer(LwM2mModel model) { |
|||
super(model); |
|||
} |
|||
|
|||
public List<LwM2mObjectEnabler> create(int... objectId) { |
|||
List<LwM2mObjectEnabler> enablers = new ArrayList<>(); |
|||
for (int anObjectId : objectId) { |
|||
LwM2mObjectEnabler objectEnabler = create(anObjectId); |
|||
if (objectEnabler != null) |
|||
enablers.add(objectEnabler); |
|||
} |
|||
return enablers; |
|||
} |
|||
|
|||
public LwM2mObjectEnabler create(int objectId) { |
|||
ObjectModel objectModel = model.getObjectModel(objectId); |
|||
if (objectModel == null) { |
|||
throw new IllegalArgumentException( |
|||
"Cannot create object for id " + objectId + " because no model is defined for this id."); |
|||
} |
|||
return createNodeEnabler(objectModel); |
|||
} |
|||
|
|||
protected LwM2mObjectEnabler createNodeEnabler(ObjectModel objectModel) { |
|||
Map<Integer, LwM2mInstanceEnabler> instances = new HashMap<>(); |
|||
LwM2mInstanceEnabler[] newInstances = createInstances(objectModel); |
|||
for (LwM2mInstanceEnabler instance : newInstances) { |
|||
// set id if not already set
|
|||
if (instance.getId() == null) { |
|||
int id = BaseInstanceEnablerFactory.generateNewInstanceId(instances.keySet()); |
|||
instance.setId(id); |
|||
} |
|||
instance.setModel(objectModel); |
|||
instances.put(instance.getId(), instance); |
|||
} |
|||
return new TbLwm2mObjectEnabler(objectModel.id, objectModel, instances, getFactoryFor(objectModel), |
|||
getContentFormat(objectModel.id)); |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
/** |
|||
* 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.transport.lwm2m.ota.sql; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.Test; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
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.kv.TsKvEntry; |
|||
import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest; |
|||
|
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; |
|||
|
|||
@Slf4j |
|||
public class Ota9LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* => Start -> INITIAL (State=0) -> DOWNLOAD STARTED; |
|||
* => PKG / URI Write -> DOWNLOAD STARTED (Res=1 (Downloading) && State=1) -> DOWNLOADED |
|||
* => PKG Written -> DOWNLOADED (Res=1 Initial && State=2) -> DELIVERED; |
|||
* => PKG integrity verified -> DELIVERED (Res=3 (Successfully Downloaded and package integrity verified) && State=3) -> INSTALLED; |
|||
* => Install -> INSTALLED (Res=2 SW successfully installed) && State=4) -> Start |
|||
* |
|||
* */ |
|||
@Test |
|||
public void testSoftwareUpdateByObject9() throws Exception { |
|||
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA9, getBootstrapServerCredentialsNoSec(NONE)); |
|||
DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_OTA9, transportConfiguration); |
|||
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); |
|||
final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9, deviceProfile.getId()); |
|||
createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9, device.getId().getId().toString()); |
|||
awaitObserveReadAll(4, device.getId().getId().toString()); |
|||
|
|||
device.setSoftwareId(createSoftware(deviceProfile.getId()).getId()); |
|||
final Device savedDevice = doPost("/api/device", device, Device.class); //sync call
|
|||
|
|||
assertThat(savedDevice).as("saved device").isNotNull(); |
|||
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); |
|||
|
|||
expectedStatuses = List.of( |
|||
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); |
|||
List<TsKvEntry> ts = await("await on timeseries") |
|||
.atMost(TIMEOUT, TimeUnit.SECONDS) |
|||
.until(() -> getFwSwStateTelemetryFromAPI(device.getId().getId(), "sw_state"), this::predicateForStatuses); |
|||
log.warn("Object9: Got the ts: {}", ts); |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/** |
|||
* 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.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 lombok.extern.slf4j.Slf4j; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
import java.util.concurrent.atomic.AtomicReference; |
|||
import static java.util.concurrent.TimeUnit.SECONDS; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; |
|||
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_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_NAME_3303_12_5700; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; |
|||
|
|||
@Slf4j |
|||
public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* Read {"id":"/3303/12/5700"} |
|||
* 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] |
|||
* 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 |
|||
*/ |
|||
@Test |
|||
public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { |
|||
// init test
|
|||
int cntValues = 2; |
|||
int resourceId = 5700; |
|||
String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; |
|||
sendRPCById(expectedIdVer); |
|||
|
|||
// verify time start/end send CollectedValue;
|
|||
await().atMost(40, SECONDS).until(() -> RESOURCE_ID_3303_12_5700_TS_0 > 0 |
|||
&& RESOURCE_ID_3303_12_5700_TS_1 > 0); |
|||
|
|||
// verify result read: verify count value: 1-2: send CollectedValue;
|
|||
AtomicReference<ObjectNode> actualValues = new AtomicReference<>(); |
|||
await().atMost(40, SECONDS).until(() -> { |
|||
actualValues.set(doGetAsync( |
|||
"/api/plugins/telemetry/DEVICE/" + lwM2MTestClient.getDeviceIdStr() + "/values/timeseries?keys=" |
|||
+ RESOURCE_ID_NAME_3303_12_5700 |
|||
+ "&startTs=" + (RESOURCE_ID_3303_12_5700_TS_0 - RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) |
|||
+ "&endTs=" + (RESOURCE_ID_3303_12_5700_TS_1 + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) |
|||
+ "&interval=0&limit=100&useStrictDataTypes=false", |
|||
ObjectNode.class)); |
|||
return actualValues.get() != null && actualValues.get().size() > 0 |
|||
&& actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() >= cntValues && verifyTs(actualValues); |
|||
}); |
|||
} |
|||
|
|||
private boolean verifyTs(AtomicReference<ObjectNode> actualValues) { |
|||
String expectedVal_0 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); |
|||
String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); |
|||
ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); |
|||
long actualTS0 = 0; |
|||
long actualTS1 = 0; |
|||
for (JsonNode tsNode : actual) { |
|||
if (tsNode.get("value").asText().equals(expectedVal_0)) { |
|||
actualTS0 = tsNode.get("ts").asLong(); |
|||
} else if (tsNode.get("value").asText().equals(expectedVal_1)) { |
|||
actualTS1 = tsNode.get("ts").asLong(); |
|||
} |
|||
} |
|||
return actualTS0 >= RESOURCE_ID_3303_12_5700_TS_0 |
|||
&& actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1 |
|||
&& (actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; |
|||
} |
|||
|
|||
private String sendRPCById(String path) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue