committed by
GitHub
77 changed files with 1206 additions and 1490 deletions
@ -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); |
|||
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/" + deviceId + "/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/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue