diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml
index d13bb54557..cbbd76a1f6 100644
--- a/msa/black-box-tests/pom.xml
+++ b/msa/black-box-tests/pom.xml
@@ -186,6 +186,10 @@
             allure-testng
             test
         
+        
+            org.eclipse.leshan
+            leshan-client-cf
+        
     
     
         
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java
new file mode 100644
index 0000000000..d0a8b53c32
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java
@@ -0,0 +1,321 @@
+/**
+ * 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.junit.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.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 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);
+                });
+        Assert.assertTrue(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 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;
+    }
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
index 83c9c927aa..c76627d3a1 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
@@ -23,6 +23,7 @@ import io.restassured.config.RestAssuredConfig;
 import io.restassured.filter.log.RequestLoggingFilter;
 import io.restassured.filter.log.ResponseLoggingFilter;
 import io.restassured.http.ContentType;
+import io.restassured.internal.ValidatableResponseImpl;
 import io.restassured.path.json.JsonPath;
 import io.restassured.response.ValidatableResponse;
 import io.restassured.specification.RequestSpecification;
@@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.DeviceProfile;
 import org.thingsboard.server.common.data.EntityView;
 import org.thingsboard.server.common.data.EventInfo;
+import org.thingsboard.server.common.data.TbResource;
 import org.thingsboard.server.common.data.User;
 import org.thingsboard.server.common.data.alarm.Alarm;
 import org.thingsboard.server.common.data.asset.Asset;
@@ -63,6 +65,7 @@ import java.util.List;
 import java.util.Map;
 
 import static io.restassured.RestAssured.given;
+import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
 import static java.net.HttpURLConnection.HTTP_OK;
 import static org.hamcrest.Matchers.is;
@@ -548,6 +551,43 @@ public class TestRestClient {
                 .as(new TypeRef<>() {});
     }
 
+    public ValidatableResponse postTbResourceIfNotExists(TbResource lwModel) {
+        return given().spec(requestSpec).body(lwModel)
+                .post("/api/resource")
+                .then()
+                .statusCode(anyOf(is(HTTP_OK), is(HTTP_BAD_REQUEST)));
+    }
+    public void deleteDeviceProfileIfExists(DeviceProfile deviceProfile) {
+        given().spec(requestSpec)
+                .delete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
+                .then()
+                .statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
+    }
+
+    public Device getDeviceByNameIfExists(String deviceName) {
+        ValidatableResponse response = given().spec(requestSpec)
+                .pathParams("deviceName", deviceName)
+                .get("/api/tenant/devices?deviceName={deviceName}")
+                .then()
+                .statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
+        if(((ValidatableResponseImpl) response).extract().response().getStatusCode()==HTTP_OK){
+            return   response.extract()
+                    .as(Device.class);
+        } else {
+            return  null;
+        }
+    }
+
+    public DeviceCredentials postDeviceCredentials(DeviceCredentials deviceCredentials) {
+        return given().spec(requestSpec).body(deviceCredentials)
+                .post("/api/device/credentials")
+                .then()
+                .assertThat()
+                .statusCode(HTTP_OK)
+                .extract()
+                .as(DeviceCredentials.class);
+    }
+
     private void addTimePageLinkToParam(Map params, TimePageLink pageLink) {
         this.addPageLinkToParam(params, pageLink);
         if (pageLink.getStartTime() != null) {
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java
new file mode 100644
index 0000000000..15658140b0
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java
@@ -0,0 +1,45 @@
+/**
+ * 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;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.thingsboard.server.msa.AbstractLwm2mClientTest;
+import org.thingsboard.server.msa.DisableUIListeners;
+
+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 Lwm2mClientNoSecTest extends AbstractLwm2mClientTest {
+
+    @BeforeMethod
+    public void setUp() throws Exception {
+        testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD);
+        initTest("lwm2m-NoSec");
+    }
+
+    @AfterMethod
+    public void tearDown() {
+        destroyAfter();
+    }
+
+    @Test
+    public void connectLwm2mClientNoSecWithLwm2mServer() throws Exception {
+        connectLwm2mClientNoSec();
+    }
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java
new file mode 100644
index 0000000000..fbd8139d8f
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java
@@ -0,0 +1,45 @@
+/**
+ * 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;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.thingsboard.server.msa.AbstractLwm2mClientTest;
+import org.thingsboard.server.msa.DisableUIListeners;
+
+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 Lwm2mClientPskTest extends AbstractLwm2mClientTest {
+
+    @BeforeMethod
+    public void setUp() throws Exception {
+        testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD);
+        initTest("lwm2m-Psk");
+    }
+
+    @AfterMethod
+    public void tearDown() {
+        destroyAfter();
+    }
+
+    @Test
+    public void connectLwm2mClientPskWithLwm2mServer() throws Exception {
+        connectLwm2mClientPsk();
+    }
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java
new file mode 100644
index 0000000000..2910a2b7da
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java
@@ -0,0 +1,157 @@
+/**
+ * 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.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.node.LwM2mResource;
+import org.eclipse.leshan.core.request.argument.Arguments;
+import org.eclipse.leshan.core.response.ExecuteResponse;
+import org.eclipse.leshan.core.response.ReadResponse;
+import org.eclipse.leshan.core.response.WriteResponse;
+import org.thingsboard.common.util.ThingsBoardThreadFactory;
+
+import javax.security.auth.Destroyable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
+
+    private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 5, 6, 7, 9);
+
+    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope"));
+
+    private final AtomicInteger state = new AtomicInteger(0);
+
+    private final AtomicInteger updateResult = new AtomicInteger(0);
+
+    @Override
+    public ReadResponse read(LwM2mServer identity, int resourceId) {
+        if (!identity.isSystem())
+            log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
+        switch (resourceId) {
+            case 3:
+                return ReadResponse.success(resourceId, getState());
+            case 5:
+                return ReadResponse.success(resourceId, getUpdateResult());
+            case 6:
+                return ReadResponse.success(resourceId, getPkgName());
+            case 7:
+                return ReadResponse.success(resourceId, getPkgVersion());
+            case 9:
+                return ReadResponse.success(resourceId, getFirmwareUpdateDeliveryMethod());
+            default:
+                return super.read(identity, resourceId);
+        }
+    }
+
+    @Override
+    public ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) {
+        String withArguments = "";
+        if (!arguments.isEmpty())
+            withArguments = " with arguments " + arguments;
+        log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments);
+
+
+        switch (resourceId) {
+            case 2:
+                startUpdating();
+                return ExecuteResponse.success();
+            default:
+                return super.execute(identity, resourceId, arguments);
+        }
+    }
+
+    @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:
+                startDownloading();
+                return WriteResponse.success();
+            case 1:
+                startDownloading();
+                return WriteResponse.success();
+            default:
+                return super.write(identity, replace, resourceId, value);
+        }
+    }
+
+    private int getState() {
+        return state.get();
+    }
+
+    private int getUpdateResult() {
+        return updateResult.get();
+    }
+
+    private String getPkgName() {
+        return "firmware";
+    }
+
+    private String getPkgVersion() {
+        return "1.0.0";
+    }
+
+    private int getFirmwareUpdateDeliveryMethod() {
+        return 1;
+    }
+
+    @Override
+    public List getAvailableResourceIds(ObjectModel model) {
+        return supportedResources;
+    }
+
+    @Override
+    public void destroy() {
+        scheduler.shutdownNow();
+    }
+
+    private void startDownloading() {
+        scheduler.schedule(() -> {
+            try {
+                state.set(1);
+                fireResourceChange(3);
+                Thread.sleep(100);
+                state.set(2);
+                fireResourceChange(3);
+            } catch (Exception e) {
+            }
+        }, 100, TimeUnit.MILLISECONDS);
+    }
+
+    private void startUpdating() {
+        scheduler.schedule(() -> {
+            try {
+                state.set(3);
+                fireResourceChange(3);
+                Thread.sleep(100);
+                updateResult.set(1);
+                fireResourceChange(5);
+            } catch (Exception e) {
+            }
+        }, 100, TimeUnit.MILLISECONDS);
+    }
+
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java
new file mode 100644
index 0000000000..4c20199c8e
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java
@@ -0,0 +1,337 @@
+/**
+ * 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.eclipse.californium.elements.config.Configuration;
+import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
+import org.eclipse.leshan.client.LeshanClient;
+import org.eclipse.leshan.client.LeshanClientBuilder;
+import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory;
+import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider;
+import org.eclipse.leshan.client.californium.endpoint.ClientProtocolProvider;
+import org.eclipse.leshan.client.californium.endpoint.coap.CoapOscoreProtocolProvider;
+import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientEndpointFactory;
+import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientProtocolProvider;
+import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider;
+import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
+import org.eclipse.leshan.client.object.Security;
+import org.eclipse.leshan.client.object.Server;
+import org.eclipse.leshan.client.observer.LwM2mClientObserver;
+import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
+import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
+import org.eclipse.leshan.client.resource.ObjectsInitializer;
+import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter;
+import org.eclipse.leshan.client.send.ManualDataSender;
+import org.eclipse.leshan.client.servers.LwM2mServer;
+import org.eclipse.leshan.core.ResponseCode;
+import org.eclipse.leshan.core.model.InvalidDDFFileException;
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.model.ObjectLoader;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.StaticModel;
+import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
+import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
+import org.eclipse.leshan.core.request.BootstrapRequest;
+import org.eclipse.leshan.core.request.DeregisterRequest;
+import org.eclipse.leshan.core.request.RegisterRequest;
+import org.eclipse.leshan.core.request.UpdateRequest;
+import org.junit.Assert;
+import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
+import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
+import static org.eclipse.leshan.core.LwM2mId.DEVICE;
+import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
+import static org.eclipse.leshan.core.LwM2mId.SECURITY;
+import static org.eclipse.leshan.core.LwM2mId.SERVER;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE;
+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_REGISTRATION_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.serverId;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId;
+
+
+@Slf4j
+@Data
+public class LwM2MTestClient {
+
+    private final String endpoint;
+    private LeshanClient leshanClient;
+    private SimpleLwM2MDevice lwM2MDevice;
+
+    private Set clientStates;
+
+    private FwLwM2MDevice fwLwM2MDevice;
+    private Map clientDtlsCid;
+
+    public void init(Security security, int clientPort) throws InvalidDDFFileException, IOException {
+        Assert.assertNull("client already initialized", leshanClient);
+
+        List models = new ArrayList<>();
+        for (String resourceName : resources) {
+            models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName), resourceName));
+        }
+        LwM2mModel model = new StaticModel(models);
+        ObjectsInitializer initializer = new ObjectsInitializer(model);
+
+        // SECURITY
+        initializer.setInstancesForObject(SECURITY, security);
+        // SERVER
+        Server lwm2mServer = new Server(shortServerId, 300);
+        lwm2mServer.setId(serverId);
+        initializer.setInstancesForObject(SERVER, lwm2mServer);
+
+        initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice());
+        initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
+        initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
+
+        List enablers = initializer.createAll();
+
+        // Create Californium Endpoints Provider:
+        // --------------------------------------
+        // Define Custom CoAPS protocol provider
+        CoapsClientProtocolProvider customCoapsProtocolProvider = new CoapsClientProtocolProvider() {
+            @Override
+            public CaliforniumClientEndpointFactory createDefaultEndpointFactory() {
+                return new CoapsClientEndpointFactory() {
+
+                    @Override
+                    protected DtlsConnectorConfig.Builder createRootDtlsConnectorConfigBuilder(
+                            Configuration configuration) {
+                        DtlsConnectorConfig.Builder builder = super.createRootDtlsConnectorConfigBuilder(configuration);
+                        return builder;
+                    };
+                };
+            }
+        };
+
+        // Create client endpoints Provider
+        List protocolProvider = new ArrayList<>();
+
+        /**
+         * "Use java-coap for CoAP protocol instead of Californium."
+         */
+        protocolProvider.add(new CoapOscoreProtocolProvider());
+        protocolProvider.add(customCoapsProtocolProvider);
+        CaliforniumClientEndpointsProvider.Builder endpointsBuilder = new CaliforniumClientEndpointsProvider.Builder(
+                protocolProvider.toArray(new ClientProtocolProvider[protocolProvider.size()]));
+
+
+        // Create Californium Configuration
+        Configuration clientCoapConfig = endpointsBuilder.createDefaultConfiguration();
+
+        // Set some DTLS stuff
+        // These configuration values are always overwritten by CLI therefore set them to transient.
+        clientCoapConfig.setTransient(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY);
+        clientCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH);
+        boolean supportDeprecatedCiphers = false;
+        clientCoapConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers);
+
+        // Set Californium Configuration
+
+        endpointsBuilder.setConfiguration(clientCoapConfig);
+        endpointsBuilder.setClientAddress(new InetSocketAddress(clientPort).getAddress());
+
+
+        // creates EndpointsProvider
+        List endpointsProvider = new ArrayList<>();
+        endpointsProvider.add(endpointsBuilder.build());
+
+        // Configure Registration Engine
+        DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
+        /**
+         * Force reconnection/rehandshake on registration update.
+         */
+        int comPeriodInSec = 5;
+        if (comPeriodInSec > 0)   engineFactory.setCommunicationPeriod(comPeriodInSec * 1000);
+
+        /**
+         * By default client will try to resume DTLS session by using abbreviated Handshake. This option force to always do a full handshake."
+         */
+        boolean reconnectOnUpdate = false;
+        engineFactory.setReconnectOnUpdate(reconnectOnUpdate);
+        engineFactory.setResumeOnConnect(true);
+
+        /**
+         * Client use queue mode.
+         */
+        engineFactory.setQueueMode(false);
+
+        // Create client
+        LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
+        builder.setObjects(enablers);
+        builder.setEndpointsProviders(endpointsProvider.toArray(new LwM2mClientEndpointsProvider[endpointsProvider.size()]));
+        builder.setDataSenders(new ManualDataSender());
+        builder.setRegistrationEngineFactory(engineFactory);
+        boolean supportOldFormat =  true;
+        if (supportOldFormat) {
+            builder.setDecoder(new DefaultLwM2mDecoder(supportOldFormat));
+            builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), supportOldFormat));
+        }
+
+        builder.setRegistrationEngineFactory(engineFactory);
+//        builder.setSharedExecutor(executor);
+
+        clientStates = new HashSet<>();
+        clientDtlsCid = new HashMap<>();
+        clientStates.add(ON_INIT);
+        leshanClient = builder.build();
+
+        LwM2mClientObserver observer = new LwM2mClientObserver() {
+            @Override
+            public void onBootstrapStarted(LwM2mServer bsserver, BootstrapRequest request) {
+                clientStates.add(ON_BOOTSTRAP_STARTED);
+            }
+
+            @Override
+            public void onBootstrapSuccess(LwM2mServer bsserver, BootstrapRequest request) {
+                clientStates.add(ON_BOOTSTRAP_SUCCESS);
+            }
+
+            @Override
+            public void onBootstrapFailure(LwM2mServer bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+                clientStates.add(ON_BOOTSTRAP_FAILURE);
+            }
+
+            @Override
+            public void onBootstrapTimeout(LwM2mServer bsserver, BootstrapRequest request) {
+                clientStates.add(ON_BOOTSTRAP_TIMEOUT);
+            }
+
+            @Override
+            public void onRegistrationStarted(LwM2mServer server, RegisterRequest request) {
+                clientStates.add(ON_REGISTRATION_STARTED);
+            }
+
+            @Override
+            public void onRegistrationSuccess(LwM2mServer server, RegisterRequest request, String registrationID) {
+                clientStates.add(ON_REGISTRATION_SUCCESS);
+            }
+
+            @Override
+            public void onRegistrationFailure(LwM2mServer server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+                clientStates.add(ON_REGISTRATION_FAILURE);
+            }
+
+            @Override
+            public void onRegistrationTimeout(LwM2mServer server, RegisterRequest request) {
+                clientStates.add(ON_REGISTRATION_TIMEOUT);
+            }
+
+            @Override
+            public void onUpdateStarted(LwM2mServer server, UpdateRequest request) {
+                clientStates.add(ON_UPDATE_STARTED);
+            }
+
+            @Override
+            public void onUpdateSuccess(LwM2mServer server, UpdateRequest request) {
+                clientStates.add(ON_UPDATE_SUCCESS);
+            }
+
+            @Override
+            public void onUpdateFailure(LwM2mServer server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+                clientStates.add(ON_UPDATE_FAILURE);
+            }
+
+            @Override
+            public void onUpdateTimeout(LwM2mServer server, UpdateRequest request) {
+                clientStates.add(ON_UPDATE_TIMEOUT);
+            }
+
+            @Override
+            public void onDeregistrationStarted(LwM2mServer server, DeregisterRequest request) {
+                clientStates.add(ON_DEREGISTRATION_STARTED);
+            }
+
+            @Override
+            public void onDeregistrationSuccess(LwM2mServer server, DeregisterRequest request) {
+                clientStates.add(ON_DEREGISTRATION_SUCCESS);
+            }
+
+            @Override
+            public void onDeregistrationFailure(LwM2mServer server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+                clientStates.add(ON_DEREGISTRATION_FAILURE);
+            }
+
+            @Override
+            public void onDeregistrationTimeout(LwM2mServer server, DeregisterRequest request) {
+                clientStates.add(ON_DEREGISTRATION_TIMEOUT);
+            }
+
+            @Override
+            public void onUnexpectedError(Throwable unexpectedError) {
+                clientStates.add(ON_EXPECTED_ERROR);
+            }
+        };
+        this.leshanClient.addObserver(observer);
+
+        // Add some log about object tree life cycle.
+        this.leshanClient.getObjectTree().addListener(new ObjectsListenerAdapter() {
+
+            @Override
+            public void objectRemoved(LwM2mObjectEnabler object) {
+                log.info("Object {} v{} disabled.", object.getId(), object.getObjectModel().version);
+            }
+
+            @Override
+            public void objectAdded(LwM2mObjectEnabler object) {
+                log.info("Object {} v{} enabled.", object.getId(), object.getObjectModel().version);
+            }
+        });
+
+        leshanClient.start();
+    }
+
+    public void destroy() {
+        if (leshanClient != null) {
+            leshanClient.destroy(true);
+        }
+        if (lwM2MDevice != null) {
+            lwM2MDevice.destroy();
+        }
+        if (fwLwM2MDevice != null) {
+            fwLwM2MDevice.destroy();
+        }
+    }
+}
\ No newline at end of file
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java
new file mode 100644
index 0000000000..b49d54f27e
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java
@@ -0,0 +1,192 @@
+/**
+ * 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.extern.slf4j.Slf4j;
+import org.eclipse.leshan.core.model.ResourceModel.Type;
+import org.eclipse.leshan.core.node.LwM2mPath;
+import org.eclipse.leshan.core.node.ObjectLink;
+import org.eclipse.leshan.core.node.codec.CodecException;
+import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
+import org.eclipse.leshan.core.util.Hex;
+import org.thingsboard.server.common.data.StringUtils;
+
+import java.math.BigInteger;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+
+import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
+
+@Slf4j
+public class LwM2mValueConverterImpl implements LwM2mValueConverter {
+
+    private static final LwM2mValueConverterImpl INSTANCE = new LwM2mValueConverterImpl();
+
+    public static LwM2mValueConverterImpl getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath)
+            throws CodecException {
+        if (value == null) {
+            return null;
+        }
+        if (expectedType == null) {
+            /** unknown resource, trusted value */
+            return value;
+        }
+
+        if (currentType == expectedType) {
+            /** expected type */
+            return value;
+        }
+        if (currentType == null) {
+            currentType = OPAQUE;
+        }
+
+        switch (expectedType) {
+            case INTEGER:
+                switch (currentType) {
+                    case FLOAT:
+                        log.debug("Trying to convert float value [{}] to Integer", value);
+                        Long longValue = ((Double) value).longValue();
+                        if ((double) value == longValue.doubleValue()) {
+                            return longValue;
+                        }
+                    case STRING:
+                        log.debug("Trying to convert String value [{}] to Integer", value);
+                        return Long.parseLong((String) value);
+                    default:
+                        break;
+                }
+                break;
+            case FLOAT:
+                switch (currentType) {
+                    case INTEGER:
+                        log.debug("Trying to convert integer value [{}] to float", value);
+                        Double floatValue = ((Long) value).doubleValue();
+                        if ((long) value == floatValue.longValue()) {
+                            return floatValue;
+                        }
+                    case STRING:
+                        log.debug("Trying to convert String value [{}] to Float", value);
+                        return Float.valueOf((String) value);
+                    default:
+                        break;
+                }
+                break;
+            case BOOLEAN:
+                switch (currentType) {
+                    case STRING:
+                        log.debug("Trying to convert string value {} to boolean", value);
+                        if (StringUtils.equalsIgnoreCase((String) value, "true")) {
+                            return true;
+                        } else if (StringUtils.equalsIgnoreCase((String) value, "false")) {
+                            return false;
+                        }
+                        break;
+                    case INTEGER:
+                        log.debug("Trying to convert int value {} to boolean", value);
+                        Long val = (Long) value;
+                        if (val == 1) {
+                            return true;
+                        } else if (val == 0) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case TIME:
+                switch (currentType) {
+                    case INTEGER:
+                        log.debug("Trying to convert long value {} to date", value);
+                        /* let's assume we received the millisecond since 1970/1/1 */
+                        return new Date(((Number) value).longValue() * 1000L);
+                    case STRING:
+                        log.debug("Trying to convert string value {} to date", value);
+                        /** let's assume we received an ISO 8601 format date */
+                        try {
+                            return new Date(Long.decode(value.toString()));
+                            /**
+                             DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
+                             XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value);
+                             return cal.toGregorianCalendar().getTime();
+                             **/
+                        } catch (IllegalArgumentException e) {
+                            log.debug("Unable to convert string to date", e);
+                            throw new CodecException("Unable to convert string (%s) to date for resource %s", value,
+                                    resourcePath);
+                        }
+                    default:
+                        break;
+                }
+                break;
+            case STRING:
+                switch (currentType) {
+                    case BOOLEAN:
+                    case INTEGER:
+                    case FLOAT:
+                        return String.valueOf(value);
+                    case TIME:
+                        String DATE_FORMAT = "MMM d, yyyy HH:mm a";
+                        Long timeValue;
+                        try {
+                            timeValue = ((Date) value).getTime();
+                        }
+                        catch (Exception e){
+                            timeValue = new BigInteger((byte [])value).longValue();
+                        }
+                        DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
+                        return formatter.format(new Date(timeValue));
+                    case OPAQUE:
+                        return Hex.encodeHexString((byte[])value);
+                    case OBJLNK:
+                        return ObjectLink.decodeFromString((String) value);
+                    default:
+                        break;
+                }
+                break;
+            case OPAQUE:
+                if (currentType == Type.STRING) {
+                    /** let's assume we received an hexadecimal string */
+                    log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value);
+                    try {
+                        return Hex.decodeHex(((String)value).toCharArray());
+                    } catch (IllegalArgumentException e) {
+                        try {
+                            return Base64.getDecoder().decode((String) value);
+                        } catch (IllegalArgumentException ea) {
+                            throw new CodecException("Unable to convert hexastring or base64 [%s] to byte array for resource %s",
+                                    value, resourcePath);
+                        }
+                    }
+                }
+                break;
+            case OBJLNK:
+                if (currentType == Type.STRING) {
+                    return ObjectLink.fromPath(value.toString());
+                }
+            default:
+        }
+        throw new CodecException("Invalid value type for resource %s, expected %s, got %s", resourcePath, expectedType,
+                currentType);
+    }
+}
\ No newline at end of file
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java
new file mode 100644
index 0000000000..dfb4c71a1e
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java
@@ -0,0 +1,134 @@
+/**
+ * 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 org.eclipse.californium.elements.config.Configuration;
+import org.eclipse.leshan.client.object.Security;
+
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_NODE_ID;
+import static org.eclipse.leshan.client.object.Security.noSec;
+
+public class Lwm2mTestHelper {
+
+    // Models
+    public static final String[] resources = new String[]{ "0.xml", "1.xml", "2.xml", "3.xml", "5.xml"};
+    public static final int serverId = 1;
+
+    public static final int port = 5685;
+    public static final int securityPort = 5686;
+    public static final Integer shortServerId = 123;
+
+    public static final String host = "localhost";
+    public static final String COAP = "coap://";
+    public static final String COAPS = "coaps://";
+    public static final String URI = COAP + host + ":" + port;
+    public static final String SECURE_URI = COAPS + host + ":" + securityPort;
+    public static final String CLIENT_ENDPOINT_NO_SEC = "LwNoSec00000000";
+    public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId);
+
+    public static final String CLIENT_ENDPOINT_PSK = "LwPsk00000000";
+    public static final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID";
+    public static final String CLIENT_PSK_KEY = "73656372657450534b73656372657450";
+
+
+    public static final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS =
+            "    {\n" +
+                    "    \"keyName\": {},\n" +
+                    "    \"observe\": [],\n" +
+                    "    \"attribute\": [],\n" +
+                    "    \"telemetry\": [],\n" +
+                    "    \"attributeLwm2m\": {}\n" +
+                    "  }";
+
+    public static final String CLIENT_LWM2M_SETTINGS =
+            "     {\n" +
+                    "    \"edrxCycle\": null,\n" +
+                    "    \"powerMode\": \"DRX\",\n" +
+                    "    \"fwUpdateResource\": null,\n" +
+                    "    \"fwUpdateStrategy\": 1,\n" +
+                    "    \"psmActivityTimer\": null,\n" +
+                    "    \"swUpdateResource\": null,\n" +
+                    "    \"swUpdateStrategy\": 1,\n" +
+                    "    \"pagingTransmissionWindow\": null,\n" +
+                    "    \"clientOnlyObserveAfterConnect\": 1\n" +
+                    "  }";
+
+    public static final int BINARY_APP_DATA_CONTAINER = 19;
+    public static final int OBJECT_INSTANCE_ID_0 = 0;
+    public static final int OBJECT_INSTANCE_ID_1 = 1;
+
+    public enum LwM2MClientState {
+
+        ON_INIT(0, "onInit"),
+        ON_BOOTSTRAP_STARTED(1, "onBootstrapStarted"),
+        ON_BOOTSTRAP_SUCCESS(2, "onBootstrapSuccess"),
+        ON_BOOTSTRAP_FAILURE(3, "onBootstrapFailure"),
+        ON_BOOTSTRAP_TIMEOUT(4, "onBootstrapTimeout"),
+        ON_REGISTRATION_STARTED(5, "onRegistrationStarted"),
+        ON_REGISTRATION_SUCCESS(6, "onRegistrationSuccess"),
+        ON_REGISTRATION_FAILURE(7, "onRegistrationFailure"),
+        ON_REGISTRATION_TIMEOUT(7, "onRegistrationTimeout"),
+        ON_UPDATE_STARTED(8, "onUpdateStarted"),
+        ON_UPDATE_SUCCESS(9, "onUpdateSuccess"),
+        ON_UPDATE_FAILURE(10, "onUpdateFailure"),
+        ON_UPDATE_TIMEOUT(11, "onUpdateTimeout"),
+        ON_DEREGISTRATION_STARTED(12, "onDeregistrationStarted"),
+        ON_DEREGISTRATION_SUCCESS(13, "onDeregistrationSuccess"),
+        ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"),
+        ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"),
+        ON_EXPECTED_ERROR(16, "onUnexpectedError"),
+        ON_READ_CONNECTION_ID (17, "onReadConnection"),
+        ON_WRITE_CONNECTION_ID (18, "onWriteConnection");
+
+        public int code;
+        public String type;
+
+        LwM2MClientState(int code, String type) {
+            this.code = code;
+            this.type = type;
+        }
+
+        public static LwM2MClientState fromLwM2MClientStateByType(String type) {
+            for (LwM2MClientState to : LwM2MClientState.values()) {
+                if (to.type.equals(type)) {
+                    return to;
+                }
+            }
+            throw new IllegalArgumentException(String.format("Unsupported Client State type  : %s", type));
+        }
+
+        public static LwM2MClientState fromLwM2MClientStateByCode(int code) {
+            for (LwM2MClientState to : LwM2MClientState.values()) {
+                if (to.code == code) {
+                    return to;
+                }
+            }
+            throw new IllegalArgumentException(String.format("Unsupported Client State code : %s", code));
+        }
+    }
+
+    public static void setDtlsConnectorConfigCidLength(Configuration serverCoapConfig, Integer cIdLength) {
+        serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH);
+        serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID);
+        serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength);
+        if ( cIdLength > 4) {
+            serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0);
+        } else {
+            serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java
new file mode 100644
index 0000000000..d2aa1af4d2
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java
@@ -0,0 +1,218 @@
+/**
+ * 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.extern.slf4j.Slf4j;
+import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
+import org.eclipse.leshan.client.servers.LwM2mServer;
+import org.eclipse.leshan.core.Destroyable;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.ResourceModel;
+import org.eclipse.leshan.core.node.LwM2mResource;
+import org.eclipse.leshan.core.request.argument.Arguments;
+import org.eclipse.leshan.core.response.ExecuteResponse;
+import org.eclipse.leshan.core.response.ReadResponse;
+import org.eclipse.leshan.core.response.WriteResponse;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PrimitiveIterator;
+import java.util.Random;
+import java.util.TimeZone;
+
+@Slf4j
+public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
+
+
+    private static final Random RANDOM = new Random();
+    private static final int min = 5;
+    private static final int max = 50;
+    private static final  PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator();
+    private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
+
+
+    public SimpleLwM2MDevice() {
+    }
+
+
+
+    @Override
+    public ReadResponse read(LwM2mServer identity, int resourceId) {
+        if (!identity.isSystem())
+            log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
+        switch (resourceId) {
+            case 0:
+                return ReadResponse.success(resourceId, getManufacturer());
+            case 1:
+                return ReadResponse.success(resourceId, getModelNumber());
+            case 2:
+                return ReadResponse.success(resourceId, getSerialNumber());
+            case 3:
+                return ReadResponse.success(resourceId, getFirmwareVersion());
+            case 6:
+                return ReadResponse.success(resourceId, getAvailablePowerSources(), ResourceModel.Type.INTEGER);
+            case 9:
+                return ReadResponse.success(resourceId, getBatteryLevel());
+            case 10:
+                return ReadResponse.success(resourceId, getMemoryFree());
+            case 11:
+                Map errorCodes = new HashMap<>();
+                errorCodes.put(0, getErrorCode());
+                return ReadResponse.success(resourceId, errorCodes, ResourceModel.Type.INTEGER);
+            case 14:
+                return ReadResponse.success(resourceId, getUtcOffset());
+            case 15:
+                return ReadResponse.success(resourceId, getTimezone());
+            case 16:
+                return ReadResponse.success(resourceId, getSupportedBinding());
+            case 17:
+                return ReadResponse.success(resourceId, getDeviceType());
+            case 18:
+                return ReadResponse.success(resourceId, getHardwareVersion());
+            case 19:
+                return ReadResponse.success(resourceId, getSoftwareVersion());
+            case 20:
+                return ReadResponse.success(resourceId, getBatteryStatus());
+            case 21:
+                return ReadResponse.success(resourceId, getMemoryTotal());
+            default:
+                return super.read(identity, resourceId);
+        }
+    }
+
+    @Override
+    public ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) {
+        String withArguments = "";
+        if (!arguments.isEmpty())
+            withArguments = " with arguments " + arguments;
+        log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments);
+        return ExecuteResponse.success();
+    }
+
+    @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 13:
+                return WriteResponse.notFound();
+            case 14:
+                setUtcOffset((String) value.getValue());
+                fireResourceChange(resourceId);
+                return WriteResponse.success();
+            case 15:
+                setTimezone((String) value.getValue());
+                fireResourceChange(resourceId);
+                return WriteResponse.success();
+            default:
+                return super.write(identity, replace, resourceId, value);
+        }
+    }
+
+    private String getManufacturer() {
+        return "Thingsboard Demo Lwm2mDevice";
+    }
+
+    private String getModelNumber() {
+        return "Model 500";
+    }
+
+    private String getSerialNumber() {
+        return "Thingsboard-500-000-0001";
+    }
+
+    private String getFirmwareVersion() {
+        return "1.0.2";
+    }
+
+    private long getErrorCode() {
+        return 0;
+    }
+
+    private Map getAvailablePowerSources() {
+        Map availablePowerSources = new HashMap<>();
+        availablePowerSources.put(0, 1L);
+        availablePowerSources.put(1, 2L);
+        availablePowerSources.put(2, 5L);
+        return availablePowerSources;
+    }
+
+    private int getBatteryLevel() {
+        return randomIterator.nextInt();
+//        return 42;
+    }
+
+    private long getMemoryFree() {
+        return Runtime.getRuntime().freeMemory() / 1024;
+    }
+
+    private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime());
+
+    private String getUtcOffset() {
+        return utcOffset;
+    }
+
+    private void setUtcOffset(String t) {
+        utcOffset = t;
+    }
+
+    private String timeZone = TimeZone.getDefault().getID();
+
+    private String getTimezone() {
+        return timeZone;
+    }
+
+    private void setTimezone(String t) {
+        timeZone = t;
+    }
+
+    private String getSupportedBinding() {
+        return "U";
+    }
+
+    private String getDeviceType() {
+        return "Demo";
+    }
+
+    private String getHardwareVersion() {
+        return "1.0.1";
+    }
+
+    private String getSoftwareVersion() {
+        return "1.0.2";
+    }
+
+    private int getBatteryStatus() {
+        return RANDOM.nextInt(7);
+    }
+
+    private long getMemoryTotal() {
+        return Runtime.getRuntime().totalMemory() / 1024;
+    }
+
+    @Override
+    public List getAvailableResourceIds(ObjectModel model) {
+        return supportedResources;
+    }
+
+    @Override
+    public void destroy() {
+    }
+}
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/0.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/0.xml
new file mode 100644
index 0000000000..81e8523880
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/0.xml
@@ -0,0 +1,405 @@
+
+
+
+
+
+  
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/1.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/1.xml
new file mode 100644
index 0000000000..f31e839c96
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/1.xml
@@ -0,0 +1,360 @@
+
+
+
+
+
+  
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/2.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/2.xml
new file mode 100644
index 0000000000..4ea5805b36
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/2.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+	
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/3.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/3.xml
new file mode 100644
index 0000000000..e71c2045c2
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/3.xml
@@ -0,0 +1,331 @@
+
+
+
+
+
+	
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/5.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/5.xml
new file mode 100644
index 0000000000..ddcea323b0
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/5.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+	
+
diff --git a/docker/tb-transports/coap/conf/logback.xml b/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml
similarity index 86%
rename from docker/tb-transports/coap/conf/logback.xml
rename to msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml
index 22621a93de..f8ecd5d96f 100644
--- a/docker/tb-transports/coap/conf/logback.xml
+++ b/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml
@@ -21,10 +21,10 @@
 
     
-        /var/log/tb-coap-transport/${TB_SERVICE_ID}/tb-coap-transport.log
+        /var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/tb-lwm2m-transport.log
         
-            /var/log/tb-coap-transport/${TB_SERVICE_ID}/tb-coap-transport.%d{yyyy-MM-dd}.%i.log
+            /var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/tb-lwm2m-transport.%d{yyyy-MM-dd}.%i.log
             100MB
             30
             3GB
@@ -47,6 +47,8 @@
     
     
 
+    
+
     
         
         
diff --git a/msa/tb/README.md b/msa/tb/README.md
index 6987c42bfa..2ece5a3e2b 100644
--- a/msa/tb/README.md
+++ b/msa/tb/README.md
@@ -21,7 +21,7 @@ In this example `thingsboard/tb` image will be used. You can choose any other im
 Execute the following command to run this docker directly:
 
 ` 
-$ docker run -it -p 9090:9090 -p 1883:1883 -p 5683:5683/udp  -p 5685:5685/udp -v ~/.mytb-data:/data --name mytb thingsboard/tb
+$ docker run -it -p 9090:9090 -p 1883:1883 -p 5683:5683/udp  -p 5685:5685/udp -p 5686:5686/udp -v ~/.mytb-data:/data --name mytb thingsboard/tb
 ` 
 
 Where: 
@@ -32,6 +32,7 @@ Where:
 - `-p 1883:1883`            - connect local port 1883 to exposed internal MQTT port 1883    
 - `-p 5683:5683`            - connect local port 5683 to exposed internal COAP port 5683 
 - `-p 5685:5685`            - connect local port 5685 to exposed internal COAP port 5685 (lwm2m) 
+- `-p 5686:5686`            - connect local port 5686 to exposed internal COAPS port 5686 (lwm2m) 
 - `-v ~/.mytb-data:/data`   - mounts the host's dir `~/.mytb-data` to ThingsBoard DataBase data directory
 - `--name mytb`             - friendly local name of this machine
 - `thingsboard/tb`          - docker image, can be also `thingsboard/tb-postgres` or `thingsboard/tb-cassandra`
@@ -47,6 +48,7 @@ Where:
 > $ VBoxManage controlvm "default" natpf1 "tcp-port1883,tcp,,1883,,1883"
 > $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5683,,5683"
 > $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5685,,5685"
+> $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5686,,5686"
 > ```
 
 After executing `docker run` command you can open `http://{your-host-ip}:9090` in you browser (for ex. `http://localhost:9090`). You should see ThingsBoard login page.
diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile
index 3d4997dd88..18071b249b 100644
--- a/msa/tb/docker-cassandra/Dockerfile
+++ b/msa/tb/docker-cassandra/Dockerfile
@@ -87,6 +87,7 @@ EXPOSE 9090
 EXPOSE 1883
 EXPOSE 5683/udp
 EXPOSE 5685/udp
+EXPOSE 5686/udp
 
 VOLUME ["/data"]
 
diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile
index dcf766f00e..984e390496 100644
--- a/msa/tb/docker-postgres/Dockerfile
+++ b/msa/tb/docker-postgres/Dockerfile
@@ -69,6 +69,7 @@ EXPOSE 9090
 EXPOSE 1883
 EXPOSE 5683/udp
 EXPOSE 5685/udp
+EXPOSE 5686/udp
 
 VOLUME ["/data"]