55 changed files with 3740 additions and 1528 deletions
@ -1,320 +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.msa; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.IOUtils; |
|||
import org.eclipse.leshan.client.object.Security; |
|||
import org.eclipse.leshan.core.util.Hex; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.DeviceProfileProvisionType; |
|||
import org.thingsboard.server.common.data.DeviceProfileType; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.data.ResourceType; |
|||
import org.thingsboard.server.common.data.TbResource; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; |
|||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; |
|||
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.LwM2MTestClient; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState; |
|||
|
|||
import java.net.ServerSocket; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.eclipse.leshan.client.object.Security.psk; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_NO_SEC; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_IDENTITY; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_KEY; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITHOUT_PARAMS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURE_URI; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURITY_NO_SEC; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId; |
|||
|
|||
@Slf4j |
|||
public class AbstractLwm2mClientTest extends AbstractContainerTest{ |
|||
protected ScheduledExecutorService executor; |
|||
|
|||
protected Security security; |
|||
|
|||
protected final PageLink pageLink = new PageLink(30); |
|||
protected TenantId tenantId; |
|||
protected DeviceProfile lwm2mDeviceProfile; |
|||
protected Device lwM2MDeviceTest; |
|||
protected LwM2MTestClient lwM2MTestClient; |
|||
|
|||
public final Set<LwM2MClientState> expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); |
|||
|
|||
public void connectLwm2mClientNoSec() throws Exception { |
|||
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC)); |
|||
basicTestConnection(SECURITY_NO_SEC, |
|||
deviceCredentials, |
|||
CLIENT_ENDPOINT_NO_SEC, |
|||
"TestConnection Lwm2m NoSec (msa)"); |
|||
} |
|||
|
|||
public void connectLwm2mClientPsk() throws Exception { |
|||
String clientEndpoint = CLIENT_ENDPOINT_PSK; |
|||
String identity = CLIENT_PSK_IDENTITY; |
|||
String keyPsk = CLIENT_PSK_KEY; |
|||
PSKClientCredential clientCredentials = new PSKClientCredential(); |
|||
clientCredentials.setEndpoint(clientEndpoint); |
|||
clientCredentials.setIdentity(identity); |
|||
clientCredentials.setKey(keyPsk); |
|||
Security security = psk(SECURE_URI, |
|||
shortServerId, |
|||
identity.getBytes(StandardCharsets.UTF_8), |
|||
Hex.decodeHex(keyPsk.toCharArray())); |
|||
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecurePsk(clientCredentials); |
|||
|
|||
basicTestConnection(security, |
|||
deviceCredentials, |
|||
clientEndpoint, |
|||
"TestConnection Lwm2m Rpc (msa)"); |
|||
} |
|||
|
|||
public void basicTestConnection(Security security, |
|||
LwM2MDeviceCredentials deviceCredentials, |
|||
String clientEndpoint, String alias) throws Exception { |
|||
// create lwm2mClient and lwM2MDevice
|
|||
lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint); |
|||
lwM2MTestClient = createNewClient(security, clientEndpoint, executor); |
|||
|
|||
LwM2MClientState finishState = ON_REGISTRATION_SUCCESS; |
|||
await(alias + " - " + ON_REGISTRATION_STARTED) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> { |
|||
log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); |
|||
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); |
|||
}); |
|||
await(alias + " - " + ON_UPDATE_SUCCESS) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> { |
|||
log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); |
|||
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); |
|||
}); |
|||
assertThat(lwM2MTestClient.getClientStates()).containsAll(expectedStatusesRegistrationLwm2mSuccess); |
|||
} |
|||
|
|||
public LwM2MTestClient createNewClient(Security security, |
|||
String endpoint, ScheduledExecutorService executor) throws Exception { |
|||
this.executor = executor; |
|||
LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(endpoint); |
|||
try (ServerSocket socket = new ServerSocket(0)) { |
|||
int clientPort = socket.getLocalPort(); |
|||
lwM2MTestClient.init(security, clientPort); |
|||
} |
|||
return lwM2MTestClient; |
|||
} |
|||
|
|||
protected void destroyAfter(){ |
|||
clientDestroy(); |
|||
deviceDestroy(); |
|||
deviceProfileDestroy(); |
|||
if (executor != null) { |
|||
executor.shutdown(); |
|||
} |
|||
} |
|||
|
|||
protected void clientDestroy() { |
|||
try { |
|||
if (lwM2MTestClient != null) { |
|||
lwM2MTestClient.destroy(); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed client Destroy", e); |
|||
} |
|||
} |
|||
protected void deviceDestroy() { |
|||
try { |
|||
if (lwM2MDeviceTest != null) { |
|||
testRestClient.deleteDeviceIfExists(lwM2MDeviceTest.getId()); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed device Delete", e); |
|||
} |
|||
} |
|||
|
|||
protected void initTest(String deviceProfileName) throws Exception { |
|||
if (executor != null) { |
|||
executor.shutdown(); |
|||
} |
|||
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-scheduled-" + deviceProfileName)); |
|||
|
|||
lwm2mDeviceProfile = getDeviceProfile(deviceProfileName); |
|||
tenantId = lwm2mDeviceProfile.getTenantId(); |
|||
|
|||
for (String resourceName : resources) { |
|||
TbResource lwModel = new TbResource(); |
|||
lwModel.setResourceType(ResourceType.LWM2M_MODEL); |
|||
lwModel.setTitle(resourceName); |
|||
lwModel.setFileName(resourceName); |
|||
lwModel.setTenantId(tenantId); |
|||
byte[] bytes = IOUtils.toByteArray(AbstractLwm2mClientTest.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName)); |
|||
lwModel.setData(bytes); |
|||
testRestClient.postTbResourceIfNotExists(lwModel); |
|||
} |
|||
} |
|||
|
|||
protected DeviceProfile getDeviceProfile(String deviceProfileName) throws Exception { |
|||
DeviceProfile deviceProfile = getDeviceProfileIfExists(deviceProfileName); |
|||
if (deviceProfile == null) { |
|||
deviceProfile = testRestClient.postDeviceProfile(createDeviceProfile(deviceProfileName)); |
|||
} |
|||
return deviceProfile; |
|||
} |
|||
|
|||
protected DeviceProfile getDeviceProfileIfExists(String deviceProfileName) throws Exception { |
|||
return testRestClient.getDeviceProfiles(pageLink).getData().stream() |
|||
.filter(x -> x.getName().equals(deviceProfileName)) |
|||
.findFirst() |
|||
.orElse(null); |
|||
} |
|||
|
|||
|
|||
protected DeviceProfile createDeviceProfile(String deviceProfileName) throws Exception { |
|||
DeviceProfile deviceProfile = new DeviceProfile(); |
|||
deviceProfile.setName(deviceProfileName); |
|||
deviceProfile.setType(DeviceProfileType.DEFAULT); |
|||
deviceProfile.setTransportType(DeviceTransportType.LWM2M); |
|||
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); |
|||
deviceProfile.setDescription(deviceProfile.getName()); |
|||
|
|||
DeviceProfileData deviceProfileData = new DeviceProfileData(); |
|||
deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); |
|||
deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); |
|||
deviceProfileData.setTransportConfiguration(getTransportConfiguration()); |
|||
deviceProfile.setProfileData(deviceProfileData); |
|||
return deviceProfile; |
|||
} |
|||
|
|||
protected void deviceProfileDestroy(){ |
|||
try { |
|||
if (lwm2mDeviceProfile != null) { |
|||
testRestClient.deleteDeviceProfileIfExists(lwm2mDeviceProfile); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed deviceProfile Delete", e); |
|||
} |
|||
} |
|||
|
|||
protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint) throws Exception { |
|||
Device device = createDevice(deviceCredentials, clientEndpoint); |
|||
return device; |
|||
} |
|||
|
|||
protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint) throws Exception { |
|||
Device device = testRestClient.getDeviceByNameIfExists(clientEndpoint); |
|||
if (device == null) { |
|||
device = new Device(); |
|||
device.setName(clientEndpoint); |
|||
device.setDeviceProfileId(lwm2mDeviceProfile.getId()); |
|||
device.setTenantId(tenantId); |
|||
device = testRestClient.postDevice("", device); |
|||
} |
|||
|
|||
DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); |
|||
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); |
|||
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); |
|||
deviceCredentials = testRestClient.postDeviceCredentials(deviceCredentials); |
|||
assertThat(deviceCredentials).isNotNull(); |
|||
return device; |
|||
} |
|||
|
|||
protected LwM2MDeviceCredentials getDeviceCredentialsNoSec(LwM2MClientCredential clientCredentials) { |
|||
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); |
|||
credentials.setClient(clientCredentials); |
|||
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); |
|||
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); |
|||
bootstrapCredentials.setBootstrapServer(serverCredentials); |
|||
bootstrapCredentials.setLwm2mServer(serverCredentials); |
|||
credentials.setBootstrap(bootstrapCredentials); |
|||
return credentials; |
|||
} |
|||
|
|||
public NoSecClientCredential createNoSecClientCredentials(String clientEndpoint) { |
|||
NoSecClientCredential clientCredentials = new NoSecClientCredential(); |
|||
clientCredentials.setEndpoint(clientEndpoint); |
|||
return clientCredentials; |
|||
} |
|||
|
|||
protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration() { |
|||
List<LwM2MBootstrapServerCredential> bootstrapServerCredentials = new ArrayList<>(); |
|||
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); |
|||
TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, TelemetryMappingConfiguration.class); |
|||
OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); |
|||
transportConfiguration.setBootstrapServerUpdateEnable(true); |
|||
transportConfiguration.setObserveAttr(observeAttrConfiguration); |
|||
transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); |
|||
transportConfiguration.setBootstrap(bootstrapServerCredentials); |
|||
return transportConfiguration; |
|||
} |
|||
|
|||
protected LwM2MDeviceCredentials getDeviceCredentialsSecurePsk(LwM2MClientCredential clientCredentials) { |
|||
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); |
|||
credentials.setClient(clientCredentials); |
|||
LwM2MBootstrapClientCredentials bootstrapCredentials; |
|||
bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials); |
|||
credentials.setBootstrap(bootstrapCredentials); |
|||
return credentials; |
|||
} |
|||
|
|||
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsPsk(LwM2MClientCredential clientCredentials) { |
|||
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); |
|||
PSKBootstrapClientCredential serverCredentials = new PSKBootstrapClientCredential(); |
|||
if (clientCredentials != null) { |
|||
serverCredentials.setClientSecretKey(((PSKClientCredential) clientCredentials).getKey()); |
|||
serverCredentials.setClientPublicKeyOrId(((PSKClientCredential) clientCredentials).getIdentity()); |
|||
} |
|||
bootstrapCredentials.setBootstrapServer(serverCredentials); |
|||
bootstrapCredentials.setLwm2mServer(serverCredentials); |
|||
return bootstrapCredentials; |
|||
} |
|||
} |
|||
@ -0,0 +1,493 @@ |
|||
/** |
|||
* 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.msa.connectivity.lwm2m; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import com.google.common.collect.Sets; |
|||
import com.google.gson.JsonArray; |
|||
import com.google.gson.JsonElement; |
|||
import com.google.gson.JsonParser; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.IOUtils; |
|||
import org.eclipse.leshan.client.object.Security; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.util.Hex; |
|||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; |
|||
import org.testng.Assert; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.DeviceProfileProvisionType; |
|||
import org.thingsboard.server.common.data.DeviceProfileType; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.data.ResourceType; |
|||
import org.thingsboard.server.common.data.TbResource; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; |
|||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; |
|||
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|||
import org.thingsboard.server.msa.AbstractContainerTest; |
|||
import org.thingsboard.server.msa.WsClient; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState; |
|||
import org.thingsboard.server.msa.mapper.WsTelemetryResponse; |
|||
|
|||
import java.net.ServerSocket; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.time.Instant; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.eclipse.leshan.client.object.Security.psk; |
|||
import static org.junit.jupiter.api.Assertions.assertTrue; |
|||
import static org.testng.AssertJUnit.assertEquals; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_PSK_IDENTITY; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_PSK_KEY; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_INIT; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITH_PARAMS; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_2; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.SECURE_URI; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.SECURITY_NO_SEC; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.resources; |
|||
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId; |
|||
|
|||
@Slf4j |
|||
public class AbstractLwm2mClientTest extends AbstractContainerTest { |
|||
protected ScheduledExecutorService executor; |
|||
|
|||
protected Security security; |
|||
|
|||
protected final PageLink pageLink = new PageLink(30); |
|||
protected TenantId tenantId; |
|||
|
|||
public final Set<LwM2MClientState> expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); |
|||
|
|||
public void createLwm2mDevicesForConnectNoSec(String name, Lwm2mDevicesForTest devicesForTest) throws Exception { |
|||
String clientEndpoint = name + "-" + RandomStringUtils.randomAlphanumeric(7); |
|||
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); |
|||
Device lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint, devicesForTest.getLwm2mDeviceProfile().getId()); |
|||
LwM2MTestClient lwM2MTestClient = createNewClient(SECURITY_NO_SEC, clientEndpoint, executor); |
|||
devicesForTest.setLwM2MDeviceTest(lwM2MDeviceTest); |
|||
devicesForTest.setLwM2MTestClient(lwM2MTestClient); |
|||
} |
|||
|
|||
public void createLwm2mDevicesForConnectPsk(Lwm2mDevicesForTest devicesForTest) throws Exception { |
|||
String clientEndpoint = CLIENT_ENDPOINT_PSK + "-" + RandomStringUtils.randomAlphanumeric(7); |
|||
String identity = CLIENT_PSK_IDENTITY; |
|||
String keyPsk = CLIENT_PSK_KEY; |
|||
PSKClientCredential clientCredentials = new PSKClientCredential(); |
|||
clientCredentials.setEndpoint(clientEndpoint); |
|||
clientCredentials.setIdentity(identity); |
|||
clientCredentials.setKey(keyPsk); |
|||
Security security = psk(SECURE_URI, |
|||
shortServerId, |
|||
identity.getBytes(StandardCharsets.UTF_8), |
|||
Hex.decodeHex(keyPsk.toCharArray())); |
|||
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecurePsk(clientCredentials); |
|||
Device lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint, devicesForTest.getLwm2mDeviceProfile().getId()); |
|||
LwM2MTestClient lwM2MTestClient = createNewClient(security, clientEndpoint, executor); |
|||
devicesForTest.setLwM2MDeviceTest(lwM2MDeviceTest); |
|||
devicesForTest.setLwM2MTestClient(lwM2MTestClient); |
|||
} |
|||
|
|||
/** |
|||
* Observe {"id":"/3/0/0"} |
|||
* Observe {"id":"/3/0/9"} |
|||
* ObserveCancel {"id":"/3"} - Bad |
|||
* ObserveCancel {"/3/0/0"} - Ok |
|||
* ObserveCancelAl - Ok |
|||
* |
|||
* @param lwM2MTestClient |
|||
* @param deviceId |
|||
* @throws Exception |
|||
*/ |
|||
public void observeResource_Update_AfterUpdateRegistration_test(LwM2MTestClient lwM2MTestClient, DeviceId deviceId) throws Exception { |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 5); |
|||
sendCancelObserveAllWithAwait(deviceId.toString()); |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); |
|||
long tsBefore = Instant.now().toEpochMilli(); |
|||
String param = "/3_1.2/0/9"; |
|||
sendRpcObserveWithContainsLwM2mSingleResource(param, deviceId.toString(), 1); |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); |
|||
checkLatestTelemetryUploaded(deviceId, tsBefore, RESOURCE_ID_NAME_3_9); |
|||
param = "/3_1.2/0/0"; |
|||
sendRpcObserveWithContainsLwM2mSingleResource(param, deviceId.toString(), 2); |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 2); |
|||
param = "/3_1.2"; |
|||
String expected = "Could not find active Observe component with path: " + param; |
|||
String actual = sendObserveCancel_BadRequest("ObserveCancel", param, deviceId.toString()); |
|||
assertEquals(expected, actual); |
|||
param = "/3_1.2/0/0"; |
|||
sendObserveCancel_Ok("ObserveCancel", param, deviceId.toString()); |
|||
sendCancelObserveAllWithAwait(deviceId.toString()); |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); |
|||
} |
|||
|
|||
public void observeCompositeResource_Update_AfterUpdateRegistration_test(LwM2MTestClient lwM2MTestClient, DeviceId deviceId) throws Exception { |
|||
String id_3_0_9 = "/3/0/9"; |
|||
String id_3_0_14 = "/3/0/14"; |
|||
String id_19_0_0 = "/19/0/0"; |
|||
String id_19_1_0 = "/19/1/0"; |
|||
String id_19_0_2 = "/19/0/2"; |
|||
|
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 5); |
|||
sendCancelObserveAllWithAwait(deviceId.toString()); |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); |
|||
long tsBefore = Instant.now().toEpochMilli(); |
|||
String expectedKeys = "[\"" + RESOURCE_ID_NAME_3_9 + "\", \"" + RESOURCE_ID_NAME_3_14 + "\", \"" + RESOURCE_ID_NAME_19_0_0 + "\", \"" + RESOURCE_ID_NAME_19_0_2 + "\", \"" +RESOURCE_ID_NAME_19_1_0 + "\"]"; |
|||
String actualResult = sendRpcObserveCompositeWithResultValue(expectedKeys, deviceId.toString()); |
|||
assertTrue(actualResult.contains(id_3_0_9 + "=LwM2mSingleResource")); |
|||
assertTrue(actualResult.contains(id_3_0_14 + "=LwM2mSingleResource")); |
|||
assertTrue(actualResult.contains(id_19_0_0 + "=LwM2mMultipleResource")); |
|||
assertTrue(actualResult.contains(id_19_1_0 + "=LwM2mMultipleResource")); |
|||
assertTrue(actualResult.contains(id_19_0_2 + "=LwM2mSingleResource")); |
|||
// ObserveComposite: - verify
|
|||
ObjectNode rpcActualResultBefore = sendRpcObserve("ObserveReadAll", null, deviceId.toString()); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); |
|||
JsonElement element = JsonParser.parseString(rpcActualResultBefore.get("value").asText()); |
|||
assertEquals(1, ((JsonArray) element).size()); |
|||
actualResult = ((JsonArray) element).asList().get(0).getAsString(); |
|||
assertTrue(actualResult.contains("CompositeObservation:")); |
|||
checkLatestTelemetryUploaded(deviceId, tsBefore, RESOURCE_ID_NAME_3_9, RESOURCE_ID_NAME_3_14, |
|||
RESOURCE_ID_NAME_19_0_0, RESOURCE_ID_NAME_19_0_2, RESOURCE_ID_NAME_19_1_0); |
|||
|
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); |
|||
sendCancelObserveAllWithAwait(deviceId.toString()); |
|||
awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); |
|||
assertEquals(0, (Object) Optional.ofNullable(getCntObserveAll(deviceId.toString())).get()); |
|||
} |
|||
|
|||
|
|||
public void basicTestConnection(LwM2MTestClient lwM2MTestClient, String alias) throws Exception { |
|||
LwM2MClientState finishState = ON_REGISTRATION_SUCCESS; |
|||
await(alias + " - " + ON_REGISTRATION_STARTED) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> { |
|||
log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); |
|||
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); |
|||
}); |
|||
await(alias + " - " + ON_UPDATE_SUCCESS) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> { |
|||
log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); |
|||
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); |
|||
}); |
|||
assertThat(lwM2MTestClient.getClientStates()).containsAll(expectedStatusesRegistrationLwm2mSuccess); |
|||
|
|||
} |
|||
|
|||
|
|||
public LwM2MTestClient createNewClient(Security security, |
|||
String endpoint, ScheduledExecutorService executor) throws Exception { |
|||
this.executor = executor; |
|||
LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(executor, endpoint); |
|||
try (ServerSocket socket = new ServerSocket(0)) { |
|||
int clientPort = socket.getLocalPort(); |
|||
lwM2MTestClient.init(security, clientPort); |
|||
} |
|||
return lwM2MTestClient; |
|||
} |
|||
|
|||
protected void destroyAfter(Lwm2mDevicesForTest devicesForTest) { |
|||
clientDestroy(devicesForTest.getLwM2MTestClient()); |
|||
deviceDestroy(devicesForTest.getLwM2MDeviceTest()); |
|||
deviceProfileDestroy(devicesForTest.getLwm2mDeviceProfile()); |
|||
if (executor != null) { |
|||
executor.shutdown(); |
|||
} |
|||
} |
|||
|
|||
protected void clientDestroy(LwM2MTestClient lwM2MTestClient) { |
|||
try { |
|||
if (lwM2MTestClient != null) { |
|||
lwM2MTestClient.destroy(); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed client Destroy", e); |
|||
} |
|||
} |
|||
|
|||
protected void deviceDestroy(Device lwM2MDeviceTest) { |
|||
try { |
|||
if (lwM2MDeviceTest != null) { |
|||
testRestClient.deleteDeviceIfExists(lwM2MDeviceTest.getId()); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed device Delete", e); |
|||
} |
|||
} |
|||
|
|||
protected DeviceProfile initTest(String deviceProfileName) throws Exception { |
|||
if (executor != null) { |
|||
executor.shutdown(); |
|||
} |
|||
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-scheduled-" + deviceProfileName)); |
|||
|
|||
DeviceProfile lwm2mDeviceProfile = getDeviceProfile(deviceProfileName); |
|||
tenantId = lwm2mDeviceProfile.getTenantId(); |
|||
|
|||
for (String resourceName : resources) { |
|||
TbResource lwModel = new TbResource(); |
|||
lwModel.setResourceType(ResourceType.LWM2M_MODEL); |
|||
lwModel.setTitle(resourceName); |
|||
lwModel.setFileName(resourceName); |
|||
lwModel.setTenantId(tenantId); |
|||
byte[] bytes = IOUtils.toByteArray(AbstractLwm2mClientTest.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName)); |
|||
lwModel.setData(bytes); |
|||
testRestClient.postTbResourceIfNotExists(lwModel); |
|||
} |
|||
return lwm2mDeviceProfile; |
|||
} |
|||
|
|||
protected DeviceProfile getDeviceProfile(String deviceProfileName) throws Exception { |
|||
DeviceProfile deviceProfile = getDeviceProfileIfExists(deviceProfileName); |
|||
if (deviceProfile == null) { |
|||
deviceProfile = testRestClient.postDeviceProfile(createDeviceProfile(deviceProfileName)); |
|||
} |
|||
return deviceProfile; |
|||
} |
|||
|
|||
protected DeviceProfile getDeviceProfileIfExists(String deviceProfileName) throws Exception { |
|||
return testRestClient.getDeviceProfiles(pageLink).getData().stream() |
|||
.filter(x -> x.getName().equals(deviceProfileName)) |
|||
.findFirst() |
|||
.orElse(null); |
|||
} |
|||
|
|||
|
|||
protected DeviceProfile createDeviceProfile(String deviceProfileName) throws Exception { |
|||
DeviceProfile deviceProfile = new DeviceProfile(); |
|||
deviceProfile.setName(deviceProfileName); |
|||
deviceProfile.setType(DeviceProfileType.DEFAULT); |
|||
deviceProfile.setTransportType(DeviceTransportType.LWM2M); |
|||
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); |
|||
deviceProfile.setDescription(deviceProfile.getName()); |
|||
|
|||
DeviceProfileData deviceProfileData = new DeviceProfileData(); |
|||
deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); |
|||
deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); |
|||
deviceProfileData.setTransportConfiguration(getTransportConfiguration()); |
|||
deviceProfile.setProfileData(deviceProfileData); |
|||
return deviceProfile; |
|||
} |
|||
|
|||
protected void deviceProfileDestroy(DeviceProfile lwm2mDeviceProfile) { |
|||
try { |
|||
if (lwm2mDeviceProfile != null) { |
|||
testRestClient.deleteDeviceProfileIfExists(lwm2mDeviceProfile); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed deviceProfile Delete", e); |
|||
} |
|||
} |
|||
|
|||
protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint, DeviceProfileId profileId) throws Exception { |
|||
Device device = createDevice(deviceCredentials, clientEndpoint, profileId); |
|||
return device; |
|||
} |
|||
|
|||
protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint, DeviceProfileId profileId) throws Exception { |
|||
Device device = testRestClient.getDeviceByNameIfExists(clientEndpoint); |
|||
if (device == null) { |
|||
device = new Device(); |
|||
device.setName(clientEndpoint); |
|||
device.setDeviceProfileId(profileId); |
|||
device.setTenantId(tenantId); |
|||
device = testRestClient.postDevice("", device); |
|||
} |
|||
|
|||
DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); |
|||
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); |
|||
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); |
|||
deviceCredentials = testRestClient.postDeviceCredentials(deviceCredentials); |
|||
assertThat(deviceCredentials).isNotNull(); |
|||
return device; |
|||
} |
|||
|
|||
protected LwM2MDeviceCredentials getDeviceCredentialsNoSec(LwM2MClientCredential clientCredentials) { |
|||
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); |
|||
credentials.setClient(clientCredentials); |
|||
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); |
|||
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); |
|||
bootstrapCredentials.setBootstrapServer(serverCredentials); |
|||
bootstrapCredentials.setLwm2mServer(serverCredentials); |
|||
credentials.setBootstrap(bootstrapCredentials); |
|||
return credentials; |
|||
} |
|||
|
|||
public NoSecClientCredential createNoSecClientCredentials(String clientEndpoint) { |
|||
NoSecClientCredential clientCredentials = new NoSecClientCredential(); |
|||
clientCredentials.setEndpoint(clientEndpoint); |
|||
return clientCredentials; |
|||
} |
|||
|
|||
protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration() { |
|||
List<LwM2MBootstrapServerCredential> bootstrapServerCredentials = new ArrayList<>(); |
|||
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); |
|||
TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); |
|||
OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); |
|||
transportConfiguration.setBootstrapServerUpdateEnable(true); |
|||
transportConfiguration.setObserveAttr(observeAttrConfiguration); |
|||
transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); |
|||
transportConfiguration.setBootstrap(bootstrapServerCredentials); |
|||
return transportConfiguration; |
|||
} |
|||
|
|||
protected LwM2MDeviceCredentials getDeviceCredentialsSecurePsk(LwM2MClientCredential clientCredentials) { |
|||
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); |
|||
credentials.setClient(clientCredentials); |
|||
LwM2MBootstrapClientCredentials bootstrapCredentials; |
|||
bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials); |
|||
credentials.setBootstrap(bootstrapCredentials); |
|||
return credentials; |
|||
} |
|||
|
|||
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsPsk(LwM2MClientCredential clientCredentials) { |
|||
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); |
|||
PSKBootstrapClientCredential serverCredentials = new PSKBootstrapClientCredential(); |
|||
if (clientCredentials != null) { |
|||
serverCredentials.setClientSecretKey(((PSKClientCredential) clientCredentials).getKey()); |
|||
serverCredentials.setClientPublicKeyOrId(((PSKClientCredential) clientCredentials).getIdentity()); |
|||
} |
|||
bootstrapCredentials.setBootstrapServer(serverCredentials); |
|||
bootstrapCredentials.setLwm2mServer(serverCredentials); |
|||
return bootstrapCredentials; |
|||
} |
|||
|
|||
protected String sendObserveCancel_BadRequest(String method, String params, String deviceIdStr) throws Exception { |
|||
ObjectNode rpcActualResult = sendRpcObserve(method, params, deviceIdStr); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
return rpcActualResult.get("error").asText(); |
|||
} |
|||
|
|||
protected void sendObserveCancel_Ok(String method, String params, String deviceIdStr) throws Exception { |
|||
ObjectNode rpcActualResult = sendRpcObserve(method, params, deviceIdStr); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
assertEquals("1", rpcActualResult.get("value").asText()); |
|||
} |
|||
|
|||
protected void sendCancelObserveAllWithAwait(String deviceIdStr) throws Exception { |
|||
ObjectNode rpcActualResultCancelAll = sendRpcObserve("ObserveCancelAll", null, deviceIdStr); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText()); |
|||
awaitObserveReadAll(0, deviceIdStr); |
|||
} |
|||
|
|||
protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { |
|||
await("ObserveReadAll after start client/test: countObserve " + cntObserve) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> cntObserve == getCntObserveAll(deviceIdStr)); |
|||
} |
|||
|
|||
protected void awaitUpdateRegistrationSuccess(LwM2MTestClient lwM2MTestClient, int cntUpdate) throws Exception { |
|||
cntUpdate = cntUpdate + lwM2MTestClient.getCountUpdateRegistrationSuccess(); |
|||
int finalCntUpdate = cntUpdate; |
|||
await("Update Registration client: countUpdateSuccess " + finalCntUpdate) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> finalCntUpdate <= lwM2MTestClient.getCountUpdateRegistrationSuccess()); |
|||
} |
|||
|
|||
protected void awaitObserveReadResource_3_0_9(int cntRead, String deviceIdStr) throws Exception { |
|||
await("Read value 3/0/9 after start observe: countRead " + cntRead) |
|||
.atMost(40, TimeUnit.SECONDS) |
|||
.until(() -> cntRead == getCntObserveAll(deviceIdStr)); |
|||
} |
|||
|
|||
protected Integer getCntObserveAll(String deviceIdStr) throws Exception { |
|||
ObjectNode rpcActualResultBefore = sendRpcObserve("ObserveReadAll", null, deviceIdStr); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); |
|||
JsonElement element = JsonParser.parseString(rpcActualResultBefore.get("value").asText()); |
|||
return element.isJsonArray() ? ((JsonArray) element).size() : null; |
|||
} |
|||
|
|||
private void sendRpcObserveWithContainsLwM2mSingleResource(String params, String deviceIdStr, int cnt) throws Exception { |
|||
String rpcActualResult = sendRpcObserveWithResultValue(params, deviceIdStr); |
|||
assertTrue(rpcActualResult.contains("LwM2mSingleResource")); |
|||
assertEquals(Optional.of(cnt).get(), Optional.ofNullable(getCntObserveAll(deviceIdStr)).get()); |
|||
} |
|||
|
|||
private String sendRpcObserveWithResultValue(String params, String deviceIdStr) throws Exception { |
|||
ObjectNode rpcActualResult = sendRpcObserve("Observe", params, deviceIdStr); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
return rpcActualResult.get("value").asText(); |
|||
} |
|||
|
|||
private String sendRpcObserveCompositeWithResultValue(String params, String deviceIdStr) throws Exception { |
|||
ObjectNode rpcActualResult = sendRpcObserveComposite(params, deviceIdStr); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
return rpcActualResult.get("value").asText(); |
|||
} |
|||
|
|||
protected ObjectNode sendRpcObserve(String method, String params, String deviceIdStr) throws Exception { |
|||
String sendRpcRequest; |
|||
if (params == null) { |
|||
sendRpcRequest = "{\"method\": \"" + method + "\"}"; |
|||
} else { |
|||
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; |
|||
} |
|||
return testRestClient.postRpcLwm2mParams(deviceIdStr, sendRpcRequest); |
|||
} |
|||
|
|||
protected ObjectNode sendRpcObserveComposite(String keys, String deviceIdStr) throws Exception { |
|||
String method = "ObserveComposite"; |
|||
String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; |
|||
return testRestClient.postRpcLwm2mParams(deviceIdStr, sendRpcRequest); |
|||
} |
|||
|
|||
public void checkLatestTelemetryUploaded(DeviceId deviceId, long tsBefore, String... keyNames) throws Exception { |
|||
WsClient wsClient = subscribeToWebSocket(deviceId, "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); |
|||
WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); |
|||
wsClient.closeBlocking(); |
|||
Assert.assertEquals(actualLatestTelemetry.getData().size(), 1); |
|||
String actualKeyName = actualLatestTelemetry.getLatestValues().keySet().toArray()[0].toString(); |
|||
Assert.assertTrue(Sets.newHashSet(keyNames).contains(actualKeyName)); |
|||
Assert.assertTrue((Long) actualLatestTelemetry.getDataValuesByKey(actualKeyName).get(0) > tsBefore); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 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.msa.connectivity.lwm2m; |
|||
|
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class Lwm2mDevicesForTest { |
|||
|
|||
Device lwM2MDeviceTest; |
|||
LwM2MTestClient lwM2MTestClient; |
|||
DeviceProfile lwm2mDeviceProfile; |
|||
public Lwm2mDevicesForTest(DeviceProfile deviceProfile) { |
|||
this.lwm2mDeviceProfile = deviceProfile; |
|||
} |
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
/** |
|||
* 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.msa.connectivity.lwm2m.client; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.client.resource.BaseInstanceEnabler; |
|||
import org.eclipse.leshan.client.servers.LwM2mServer; |
|||
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.LwM2mResource; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
|
|||
import javax.security.auth.Destroyable; |
|||
import java.sql.Time; |
|||
import java.util.Arrays; |
|||
import java.util.Date; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
|
|||
@Slf4j |
|||
public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable { |
|||
|
|||
/** |
|||
* id = 0 |
|||
* Multiple |
|||
* base64 |
|||
*/ |
|||
|
|||
/** |
|||
* Example1: |
|||
* InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka |
|||
* W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo= |
|||
* "serviceId":"Meter", |
|||
* "serviceData":{ |
|||
* "currentReading":"46.3", |
|||
* "signalStrength":16, |
|||
* "dailyActivityTime":5706 |
|||
*/ |
|||
|
|||
/** |
|||
* Example2: |
|||
* InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX |
|||
* 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K |
|||
* "serviceId":"WaterMeter", |
|||
* "cmd":"SET_TEMPERATURE_READ_PERIOD", |
|||
* "paras":{ |
|||
* "value":4 |
|||
* }, |
|||
*/ |
|||
|
|||
Map<Integer, byte[]> data; |
|||
private Integer priority = 0; |
|||
private Time timestamp; |
|||
private String description; |
|||
private String dataFormat; |
|||
private Integer appID = -1; |
|||
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5); |
|||
|
|||
public LwM2mBinaryAppDataContainer() { |
|||
} |
|||
|
|||
public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) { |
|||
try { |
|||
if (id != null) this.setId(id); |
|||
executorService.scheduleWithFixedDelay(() -> { |
|||
fireResourceChange(0); |
|||
fireResourceChange(2); |
|||
} |
|||
, 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
|
|||
} catch (Throwable e) { |
|||
log.error("[{}]Throwable", e.toString()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ReadResponse read(LwM2mServer identity, int resourceId) { |
|||
try { |
|||
switch (resourceId) { |
|||
case 0: |
|||
ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE); |
|||
return response; |
|||
case 1: |
|||
return ReadResponse.success(resourceId, getPriority()); |
|||
case 2: |
|||
return ReadResponse.success(resourceId, getTimestamp()); |
|||
case 3: |
|||
return ReadResponse.success(resourceId, getDescription()); |
|||
case 4: |
|||
return ReadResponse.success(resourceId, getDataFormat()); |
|||
case 5: |
|||
return ReadResponse.success(resourceId, getAppID()); |
|||
default: |
|||
return super.read(identity, resourceId); |
|||
} |
|||
} catch (Exception e) { |
|||
return ReadResponse.badRequest(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { |
|||
log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 0: |
|||
if (setData(value, replace)) { |
|||
return WriteResponse.success(); |
|||
} else { |
|||
WriteResponse.badRequest("Invalidate value ..."); |
|||
} |
|||
case 1: |
|||
setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); |
|||
fireResourceChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 2: |
|||
setTimestamp(((Date) value.getValue()).getTime()); |
|||
fireResourceChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 3: |
|||
setDescription((String) value.getValue()); |
|||
fireResourceChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 4: |
|||
setDataFormat((String) value.getValue()); |
|||
fireResourceChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 5: |
|||
setAppID((Integer) value.getValue()); |
|||
fireResourceChange(resourceId); |
|||
return WriteResponse.success(); |
|||
default: |
|||
return super.write(identity, replace, resourceId, value); |
|||
} |
|||
} |
|||
|
|||
private Integer getAppID() { |
|||
return this.appID; |
|||
} |
|||
|
|||
private void setAppID(Integer appId) { |
|||
this.appID = appId; |
|||
} |
|||
|
|||
private void setDataFormat(String value) { |
|||
this.dataFormat = value; |
|||
} |
|||
|
|||
private String getDataFormat() { |
|||
return this.dataFormat == null ? "OPAQUE" : this.dataFormat; |
|||
} |
|||
|
|||
private void setDescription(String value) { |
|||
this.description = value; |
|||
} |
|||
|
|||
private String getDescription() { |
|||
// return this.description == null ? "meter reading" : this.description;
|
|||
return this.description; |
|||
} |
|||
|
|||
private void setTimestamp(long time) { |
|||
this.timestamp = new Time(time); |
|||
} |
|||
|
|||
private Time getTimestamp() { |
|||
return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); |
|||
} |
|||
|
|||
private boolean setData(LwM2mResource value, boolean replace) { |
|||
try { |
|||
if (value instanceof LwM2mMultipleResource) { |
|||
if (replace || this.data == null) { |
|||
this.data = new HashMap<>(); |
|||
} |
|||
value.getInstances().values().forEach(v -> { |
|||
this.data.put(v.getId(), (byte[]) v.getValue()); |
|||
}); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} catch (Exception e) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
private Map<Integer, byte[]> getData() { |
|||
if (data == null) { |
|||
this.data = new HashMap<>(); |
|||
this.data.put(0, new byte[]{(byte) 0xAC}); |
|||
} |
|||
return data; |
|||
} |
|||
|
|||
@Override |
|||
public List<Integer> getAvailableResourceIds(ObjectModel model) { |
|||
return supportedResources; |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
} |
|||
|
|||
private int getPriority() { |
|||
return this.priority; |
|||
} |
|||
|
|||
private void setPriority(int value) { |
|||
this.priority = value; |
|||
} |
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
/** |
|||
* 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.msa.connectivity.lwm2m.client; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.client.LeshanClient; |
|||
import org.eclipse.leshan.client.resource.BaseInstanceEnabler; |
|||
import org.eclipse.leshan.client.send.ManualDataSender; |
|||
import org.eclipse.leshan.client.servers.LwM2mServer; |
|||
import org.eclipse.leshan.core.model.ObjectModel; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.request.argument.Arguments; |
|||
import org.eclipse.leshan.core.response.ExecuteResponse; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
|
|||
import javax.security.auth.Destroyable; |
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Random; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Slf4j |
|||
public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable { |
|||
|
|||
private static final String UNIT_CELSIUS = "cel"; |
|||
private double currentTemp = 20d; |
|||
private double minMeasuredValue = currentTemp; |
|||
private double maxMeasuredValue = currentTemp; |
|||
|
|||
private LeshanClient leshanClient; |
|||
private List<Double> containingValues; |
|||
protected static final Random RANDOM = new Random(); |
|||
private static final List<Integer> supportedResources = Arrays.asList(5601, 5602, 5700, 5701); |
|||
|
|||
public LwM2mTemperatureSensor() { |
|||
|
|||
} |
|||
|
|||
public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { |
|||
try { |
|||
if (id != null) this.setId(id); |
|||
executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS); |
|||
} catch (Throwable e) { |
|||
log.error("[{}]Throwable", e.toString()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { |
|||
log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 5601: |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); |
|||
case 5602: |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); |
|||
case 5700: |
|||
if (identity == LwM2mServer.SYSTEM) { |
|||
setTemperature(); |
|||
setData(); |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); |
|||
} else if (this.getId() == 12 && this.leshanClient != null) { |
|||
containingValues = new ArrayList<>(); |
|||
sendCollected(5700); |
|||
return ReadResponse.success(resourceId, getData()); |
|||
} else { |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); |
|||
} |
|||
case 5701: |
|||
return ReadResponse.success(resourceId, UNIT_CELSIUS); |
|||
default: |
|||
return super.read(identity, resourceId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public synchronized ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { |
|||
log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 5605: |
|||
resetMinMaxMeasuredValues(); |
|||
return ExecuteResponse.success(); |
|||
default: |
|||
return super.execute(identity, resourceId, arguments); |
|||
} |
|||
} |
|||
|
|||
private double getTwoDigitValue(double value) { |
|||
BigDecimal toBeTruncated = BigDecimal.valueOf(value); |
|||
return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue(); |
|||
} |
|||
|
|||
private void adjustTemperature() { |
|||
setTemperature(); |
|||
Integer changedResource = adjustMinMaxMeasuredValue(currentTemp); |
|||
fireResourceChange(5700); |
|||
if (changedResource != null) { |
|||
fireResourceChange(changedResource); |
|||
} |
|||
} |
|||
|
|||
private void setTemperature(){ |
|||
float delta = (RANDOM.nextInt(20) - 10) / 10f; |
|||
currentTemp += delta; |
|||
} |
|||
private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) { |
|||
if (newTemperature > maxMeasuredValue) { |
|||
maxMeasuredValue = newTemperature; |
|||
return 5602; |
|||
} else if (newTemperature < minMeasuredValue) { |
|||
minMeasuredValue = newTemperature; |
|||
return 5601; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private void resetMinMaxMeasuredValues() { |
|||
minMeasuredValue = currentTemp; |
|||
maxMeasuredValue = currentTemp; |
|||
} |
|||
|
|||
@Override |
|||
public List<Integer> getAvailableResourceIds(ObjectModel model) { |
|||
return supportedResources; |
|||
} |
|||
|
|||
protected void setLeshanClient(LeshanClient leshanClient){ |
|||
this.leshanClient = leshanClient; |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
} |
|||
|
|||
private void sendCollected(int resourceId) { |
|||
try { |
|||
LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); |
|||
ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME, |
|||
ManualDataSender.class); |
|||
sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); |
|||
Thread.sleep(1000); |
|||
sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); |
|||
sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); |
|||
} catch (InterruptedException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
private LwM2mPath getPathForCollectedValue(int resourceId) { |
|||
return new LwM2mPath(3303, this.getId(), resourceId); |
|||
} |
|||
|
|||
private double getData() { |
|||
if (containingValues.size() > 1) { |
|||
Integer t0 = Math.toIntExact(Math.round(containingValues.get(0) * 100)); |
|||
Integer t1 = Math.toIntExact(Math.round(containingValues.get(1) * 100)); |
|||
long to_t1 = (((long) t0) << 32) | (t1 & 0xffffffffL); |
|||
return Double.longBitsToDouble(to_t1); |
|||
} else { |
|||
return currentTemp; |
|||
} |
|||
|
|||
} |
|||
|
|||
private void setData() { |
|||
if (containingValues == null){ |
|||
containingValues = new ArrayList<>(); |
|||
} |
|||
containingValues.add(getTwoDigitValue(currentTemp)); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* 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.msa.connectivity.lwm2m.rpc; |
|||
|
|||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; |
|||
import org.testng.annotations.AfterMethod; |
|||
import org.testng.annotations.BeforeMethod; |
|||
import org.testng.annotations.Test; |
|||
import org.thingsboard.server.msa.DisableUIListeners; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; |
|||
|
|||
import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; |
|||
import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; |
|||
|
|||
@DisableUIListeners |
|||
public class Lwm2mObserveCompositeTest extends AbstractLwm2mClientTest { |
|||
|
|||
private Lwm2mDevicesForTest lwm2mDevicesForTest; |
|||
|
|||
private final static String name = "lwm2m-NoSec-ObserveComposite"; |
|||
|
|||
@BeforeMethod |
|||
public void setUp() throws Exception { |
|||
testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); |
|||
this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest(name + "-profile" + RandomStringUtils.randomAlphanumeric(7))); |
|||
} |
|||
|
|||
@AfterMethod |
|||
public void tearDown() { |
|||
destroyAfter(this.lwm2mDevicesForTest); |
|||
} |
|||
|
|||
@Test |
|||
public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { |
|||
createLwm2mDevicesForConnectNoSec( name + "-" + RandomStringUtils.randomAlphanumeric(7), this.lwm2mDevicesForTest ); |
|||
observeCompositeResource_Update_AfterUpdateRegistration_test(this.lwm2mDevicesForTest.getLwM2MTestClient(), this.lwm2mDevicesForTest.getLwM2MDeviceTest().getId()); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* 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.msa.connectivity.lwm2m.rpc; |
|||
|
|||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; |
|||
import org.testng.annotations.AfterMethod; |
|||
import org.testng.annotations.BeforeMethod; |
|||
import org.testng.annotations.Test; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; |
|||
import org.thingsboard.server.msa.DisableUIListeners; |
|||
import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; |
|||
|
|||
import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; |
|||
import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; |
|||
|
|||
@DisableUIListeners |
|||
public class Lwm2mObserveTest extends AbstractLwm2mClientTest { |
|||
|
|||
private Lwm2mDevicesForTest lwm2mDevicesForTest; |
|||
|
|||
private final static String name = "lwm2m-NoSec-Observe"; |
|||
|
|||
@BeforeMethod |
|||
public void setUp() throws Exception { |
|||
testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); |
|||
this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest(name + "-profile" + RandomStringUtils.randomAlphanumeric(7))); |
|||
} |
|||
|
|||
@AfterMethod |
|||
public void tearDown() { |
|||
destroyAfter(this.lwm2mDevicesForTest); |
|||
} |
|||
|
|||
@Test |
|||
public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { |
|||
createLwm2mDevicesForConnectNoSec( name + "-" + RandomStringUtils.randomAlphanumeric(7), this.lwm2mDevicesForTest ); |
|||
observeResource_Update_AfterUpdateRegistration_test(this.lwm2mDevicesForTest.getLwM2MTestClient(), this.lwm2mDevicesForTest.getLwM2MDeviceTest().getId()); |
|||
} |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- |
|||
FILE INFORMATION |
|||
|
|||
OMA Permanent Document |
|||
File: OMA-SUP-LwM2M_BinaryAppDataContainer-V1_0_1-20190221-A |
|||
Type: xml |
|||
|
|||
Public Reachable Information |
|||
Path: http://www.openmobilealliance.org/tech/profiles |
|||
Name: LwM2M_BinaryAppDataContainer-v1_0_1.xml |
|||
|
|||
NORMATIVE INFORMATION |
|||
|
|||
Information about this file can be found in the latest revision of |
|||
|
|||
OMA-TS-LWM2M_BinaryAppDataContainer-V1_0_1 |
|||
|
|||
This is available at http://www.openmobilealliance.org/ |
|||
|
|||
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues |
|||
|
|||
CHANGE HISTORY |
|||
|
|||
15062018 Status changed to Approved by DM, Doc Ref # OMA-DM&SE-2018-0061-INP_LWM2M_APPDATA_V1_0_ERP_for_final_Approval |
|||
21022019 Status changed to Approved by IPSO, Doc Ref # OMA-IPSO-2019-0025-INP_LwM2M_Object_App_Data_Container_1_0_1_for_Final_Approval |
|||
|
|||
LEGAL DISCLAIMER |
|||
|
|||
Copyright 2019 Open Mobile Alliance. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions |
|||
are met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
2. Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
3. Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
The above license is used as a license under copyright only. Please |
|||
reference the OMA IPR Policy for patent licensing terms: |
|||
https://www.omaspecworks.org/about/intellectual-property-rights/ |
|||
|
|||
--> |
|||
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd"> |
|||
<Object ObjectType="MODefinition"> |
|||
<Name>BinaryAppDataContainer</Name> |
|||
<Description1><![CDATA[This LwM2M Objects provides the application service data related to a LwM2M Server, eg. Water meter data. |
|||
There are several methods to create instance to indicate the message direction based on the negotiation between Application and LwM2M. The Client and Server should negotiate the instance(s) used to exchange the data. For example: |
|||
- Using a single instance for both directions communication, from Client to Server and from Server to Client. |
|||
- Using an instance for communication from Client to Server and another one for communication from Server to Client |
|||
- Using several instances |
|||
]]></Description1> |
|||
<ObjectID>19</ObjectID> |
|||
<ObjectURN>urn:oma:lwm2m:oma:19:1.1</ObjectURN> |
|||
<LWM2MVersion>1.1</LWM2MVersion> |
|||
<ObjectVersion>1.1</ObjectVersion> |
|||
<MultipleInstances>Multiple</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Resources> |
|||
<Item ID="0"><Name>Data</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Multiple</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Opaque</Type> |
|||
<RangeEnumeration /> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the application data content.]]></Description> |
|||
</Item> |
|||
<Item ID="1"><Name>Data Priority</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Integer</Type> |
|||
<RangeEnumeration>1 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the Application data priority: |
|||
0:Immediate |
|||
1:BestEffort |
|||
2:Latest |
|||
3-100: Reserved for future use. |
|||
101-254: Proprietary mode.]]></Description> |
|||
</Item> |
|||
<Item ID="2"><Name>Data Creation Time</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Time</Type> |
|||
<RangeEnumeration /> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the Data instance creation timestamp.]]></Description> |
|||
</Item> |
|||
<Item ID="3"><Name>Data Description</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>String</Type> |
|||
<RangeEnumeration>32 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the data description. |
|||
e.g. "meter reading".]]></Description> |
|||
</Item> |
|||
<Item ID="4"><Name>Data Format</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>String</Type> |
|||
<RangeEnumeration>32 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the format of the Application Data. |
|||
e.g. YG-Meter-Water-Reading |
|||
UTF8-string |
|||
]]></Description> |
|||
</Item> |
|||
<Item ID="5"><Name>App ID</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Integer</Type> |
|||
<RangeEnumeration>2 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the destination Application ID.]]></Description> |
|||
</Item></Resources> |
|||
<Description2><![CDATA[]]></Description2> |
|||
</Object> |
|||
</LWM2M> |
|||
@ -0,0 +1,103 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2018 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. |
|||
|
|||
--> |
|||
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd"> |
|||
<Object ObjectType="MODefinition"> |
|||
<Name>Temperature</Name> |
|||
<Description1>This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius.</Description1> |
|||
<ObjectID>3303</ObjectID> |
|||
<ObjectURN>urn:oma:lwm2m:ext:3303</ObjectURN> |
|||
<LWM2MVersion>1.0</LWM2MVersion> |
|||
<ObjectVersion>1.0</ObjectVersion> |
|||
<MultipleInstances>Multiple</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Resources> |
|||
<Item ID="5700"> |
|||
<Name>Sensor Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>Last or Current Measured Value from the Sensor</Description> |
|||
</Item> |
|||
<Item ID="5601"> |
|||
<Name>Min Measured Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The minimum value measured by the sensor since power ON or reset</Description> |
|||
</Item> |
|||
<Item ID="5602"> |
|||
<Name>Max Measured Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The maximum value measured by the sensor since power ON or reset</Description> |
|||
</Item> |
|||
<Item ID="5603"> |
|||
<Name>Min Range Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The minimum value that can be measured by the sensor</Description> |
|||
</Item> |
|||
<Item ID="5604"> |
|||
<Name>Max Range Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The maximum value that can be measured by the sensor</Description> |
|||
</Item> |
|||
<Item ID="5701"> |
|||
<Name>Sensor Units</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>String</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>Measurement Units Definition.</Description> |
|||
</Item> |
|||
<Item ID="5605"> |
|||
<Name>Reset Min and Max Measured Values</Name> |
|||
<Operations>E</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type></Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>Reset the Min and Max Measured Values to Current Value</Description> |
|||
</Item> |
|||
</Resources> |
|||
<Description2></Description2> |
|||
</Object> |
|||
</LWM2M> |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* 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.rule.engine.metadata; |
|||
|
|||
public enum FetchMode { |
|||
|
|||
FIRST, ALL, LAST |
|||
|
|||
} |
|||
Loading…
Reference in new issue