LwM2M Application Note
This document describes how to use the LwM2M protocol in QuecPython to connect to a device management server, including protocol fundamentals, server parameter preparation, client configuration, registration status checking, heartbeat update, deregistration, and troubleshooting.
The following models support the
lwm2mmodule: EC200UEU_AB_SANX, BG95M8_SANX, EC200AAU_HA_SANX, EG912NEN_AA. Feature support of customized firmware depends on the actual firmware.
LwM2M Protocol Overview
LwM2M (Lightweight Machine to Machine) is a lightweight device management protocol defined by OMA SpecWorks for IoT devices. It adopts a client-server model, typically communicating over CoAP and UDP, and can work with DTLS for identity authentication and transport encryption.
LwM2M organizes device capabilities into Objects, Object Instances, and Resources. Through a unified data model, the server identifies device information, connection status, and firmware upgrade capabilities, and performs management operations such as read, write, execute, and observe on the device.
LwM2M has the following features:
- Lightweight: Low protocol overhead, suitable for cellular IoT devices and resource-constrained terminals.
- Standardized data model: Describes device capabilities through objects, instances, and resources, facilitating access of different devices to the same management platform.
- Device lifecycle management: Supports Bootstrap, registration, update, and deregistration flows.
- Secure communication: Supports pre-shared key and certificate security modes, and can use the no-encryption mode in debug environments.
- Remote management: Can be used for device status observation, parameter configuration, firmware upgrade, and more.
LwM2M Application Scenarios
LwM2M is commonly used for IoT devices that need batch access, unified monitoring, and remote maintenance, for example:
- Smart meters: Collect meter status and metering data, remotely adjust reporting intervals.
- Asset tracking: Observe terminal online status, signal quality, and location information.
- Industrial devices: Read operating status, deliver configurations, and perform remote maintenance operations.
- Smart city: Centrally manage street lights, parking detectors, and environmental sensors.
- Remote upgrade: The server delivers the firmware address, and the device downloads and updates according to the configuration.
LwM2M Communication Mechanism
A typical LwM2M system includes the following roles:
- LwM2M Client: Runs on the QuecPython device, identified by the Endpoint Name, and initiates registration to the server.
- Bootstrap Server: An optional role that delivers the device management server address and security credentials to the client.
- LwM2M Server: Also known as the device management server (DM Server), responsible for device registration, status observation, parameter configuration, firmware upgrade, and other management operations.
Client Lifecycle
When a QuecPython client connects to an LwM2M server, it mainly goes through the following flow:
- The device completes network attachment and obtains a usable data network.
- Create and initialize the
lwm2mobject. - Configure security parameters, server parameters, Endpoint Name, URC, and FOTA policy.
- Register the callback function to receive connection results and server-delivered events.
- Call
register()to initiate the registration request. - Determine the final registration result through the
readyURC in the callback andstat(). - After successful registration, the module automatically performs heartbeat updates; only call
update()manually in special scenarios. - Call
unregister()to deregister when the service is no longer used.
register(), update(), and unregister() are all asynchronous operations. A return value of 0 only means the request has been successfully submitted, not that the server has completed the operation. The final result should be determined by combining URC and stat().
Security Modes
The security_mode in the lwm2m.Security configuration determines the security method between the client and the server.
security_mode |
Security Method | config_list |
|---|---|---|
0 |
Pre-Shared Key (PSK) | [serverID, SSID, server_addr, bootstrap, 0, psk_id, psk_key] |
2 |
Certificate | [serverID, SSID, server_addr, bootstrap, 2, ca_cert, client_cert, client_key] |
3 |
No Security | [serverID, SSID, server_addr, bootstrap, 3] |
It is recommended to use PSK or certificate mode in production environments. The no-security mode does not encrypt communication or provide identity authentication, and is only suitable for joint debugging in controlled networks. For the no-security mode, use a coap:// address; if a coaps:// address is used by mistake, the module will process it as PSK mode.
Server Types
The client can connect directly to the device management server, or connect to the Bootstrap Server first.
| Connection Method | serverID |
SSID |
bootstrap |
Description |
|---|---|---|---|---|
| Device management server | 1 |
1000 |
0 |
The client registers directly when it already knows the server and security parameters |
| Bootstrap Server | 0 |
100 |
1 |
Obtain the device management server and security configuration first |
For the server address server_addr, it is recommended to use the full URI format, such as coap://address:port for the no-security mode and coaps://address:port for PSK/certificate modes, with a maximum length of 256 characters; the address:port format is also supported, and the module automatically prepends the protocol scheme according to the security mode. The port, security mode, Endpoint Name, and security credentials must be consistent with the server configuration.
QuecPython Configuration Items
config(config_type, config_list) is used to set LwM2M client parameters. Common configuration items are as follows:
| Configuration Type | config_list |
Purpose |
|---|---|---|
lwm2m.Reset |
[] |
Clear existing configurations |
lwm2m.Security |
Varies with the security mode | Configure server address and security credentials |
lwm2m.Server |
[serverID, life_time, pmin, pmax, disable_timeout, storing, binding_mode] |
Configure device management server parameters |
lwm2m.Epnamemode |
[mode] |
Select how the Endpoint Name is generated |
lwm2m.Epname |
[epname] |
Set a custom Endpoint Name when mode=1 |
lwm2m.Urc |
[URC_onoff] |
Enable or disable URC reporting |
lwm2m.Fota |
[download, update] |
Configure firmware automatic download and update policy |
The meaning of each parameter in lwm2m.Server:
life_time: Registration lifetime, range 1-86400, in seconds.pmin: Default minimum observation period, range 1-86400, in seconds.pmax: Default maximum observation period, range 1-86400, in seconds.disable_timeout: Timeout after the server connection is disabled, range 1-86400, in seconds.storing: Whether to save server information,0for no,1for yes.binding_mode: Server connection method. Currently uses the UDP-based"U"mode.
Endpoint Name supports the following configuration methods:
mode=1: Set a custom Endpoint Name vialwm2m.Epname.mode=3: Usepsk_idas the Endpoint Name in PSK mode.
To receive URC through the callback, lwm2m.Urc must be configured as [1]. If the application needs to handle firmware download and update notifications by itself, configure lwm2m.Fota as [0, 0].
LwM2M Application
This section takes the client directly connecting to the device management server as an example, introducing the complete flow from environment check to successful registration. The example uses the no-security mode to make the parameter structure clear; in actual projects, switch to PSK or certificate mode according to the server configuration.
Prepare Server Parameters
Before running the code, obtain the following information from the LwM2M platform:
| Parameter | Example Placeholder | Description |
|---|---|---|
| Server address | coap://lwm2m.example.com:5683 |
Replace with the actual domain name or IP address and port; use the coap:// prefix for the no-security mode |
| Endpoint Name | quecpython-device-001 |
Must be consistent with the device identifier created on the platform |
| Security mode | 3 |
The example uses the no-security mode; 0 or 2 is recommended in production environments |
| PSK ID, PSK Key | Assigned by the platform | Only required for PSK mode |
| CA certificate, client certificate, client private key | Provided by the platform | Only required for certificate mode |
Different platforms may have different requirements for Endpoint Name, PSK Key encoding, and certificate content; refer to the parameters generated when creating the device on the platform.
Check Firmware and Network
Use QPYcom to import modules in the interactive interface. If no exception is raised, the current firmware includes the lwm2m module.
import lwm2m
Use checkNet.waitNetworkReady() to check the cellular network status:
import checkNet
stage, state = checkNet.waitNetworkReady(30)
print(stage, state)
A return value of stage=3 and state=1 means the network is ready. If the network is not ready, first check the SIM card, antenna, signal, and APN configuration.
Create and Initialize the Client
Create the lwm2m object and call init() to initialize:
import lwm2m
client = lwm2m()
result = client.init()
print("init result:", result)
init() returns 0 on success and -1 on failure. Subsequent configuration should be performed after initialization succeeds.
Register the Callback Function
The callback parameter is a list, where event[0] and event[1] are the event type and event code, and event[2] carries the URC data. Event types may vary slightly across platforms; the connection status should be determined by the URC in event[2].
def lwm2m_callback(event):
print("LwM2M event:", event)
if len(event) < 3:
return
urc = str(event[2])
if '"ready","successfully"' in urc:
print("LwM2M register success")
elif '"ready","failed"' in urc:
print("LwM2M register failed")
elif '"update","successfully"' in urc:
print("LwM2M update success")
elif '"deregister"' in urc:
print("LwM2M deregister event")
result = client.register_call(lwm2m_callback)
print("register callback result:", result)
register_call() returns 0 on success and -1 on failure. To remove the callback function, call:
client.register_call(None)
Configure Direct Connection in No-Security Mode
The following configuration is used to connect directly to the device management server. Replace the server address and Endpoint Name first.
server_address = "coap://lwm2m.example.com:5683"
endpoint_name = "quecpython-device-001"
# Clear the old configuration. In production, only execute when the server or security parameters change.
client.config(client.Reset, [])
# [serverID, SSID, server_addr, bootstrap, security_mode]
security_config = [1, 1000, server_address, 0, 3]
client.config(client.Security, security_config)
# [serverID, life_time, pmin, pmax, disable_timeout, storing, binding_mode]
server_config = [1, 300, 1, 60, 86400, 1, "U"]
client.config(client.Server, server_config)
# Use a custom Endpoint Name
client.config(client.Epnamemode, [1])
client.config(client.Epname, [endpoint_name])
# Enable URC; FOTA download and update are both controlled by the application
client.config(client.Urc, [1])
client.config(client.Fota, [0, 0])
config() returns 0 on success, -1 on failure, and -2 if the configuration item is not supported by the current firmware. In actual projects, check the return value of each configuration; do not continue registration if a configuration fails.
Configure PSK Mode
In PSK mode, the security configuration needs to add psk_id and psk_key. Both must be consistent with the server.
server_address = "coaps://lwm2m.example.com:5684"
psk_id = "device-001"
psk_key = "replace-with-server-psk"
security_config = [
1,
1000,
server_address,
0,
0,
psk_id,
psk_key,
]
client.config(client.Security, security_config)
If the platform requires the PSK ID to be used as the Endpoint Name, set the Endpoint Name mode to 3:
client.config(client.Epnamemode, [3])
If the platform uses an independent Endpoint Name, use mode=1 and configure it via lwm2m.Epname.
Configure Certificate Mode
The certificate mode requires configuring the CA certificate, client certificate, and client private key:
server_address = "coaps://lwm2m.example.com:5684"
ca_cert = "replace-with-ca-certificate"
client_cert = "replace-with-client-certificate"
client_key = "replace-with-client-private-key"
security_config = [
1,
1000,
server_address,
0,
2,
ca_cert,
client_cert,
client_key,
]
client.config(client.Security, security_config)
The content and format of the certificate strings should follow the requirements of the server and the current firmware. During connection, the dtls URC can be used to determine the handshake result.
Configure the Bootstrap Server
When the device management server configuration needs to be obtained through the Bootstrap Server, set serverID, SSID, and bootstrap to 0, 100, and 1 respectively. The following uses PSK mode as an example:
bootstrap_address = "coaps://bootstrap.example.com:5684"
bootstrap_psk_id = "device-001"
bootstrap_psk_key = "replace-with-bootstrap-psk"
bootstrap_security_config = [
0,
100,
bootstrap_address,
1,
0,
bootstrap_psk_id,
bootstrap_psk_key,
]
client.config(client.Security, bootstrap_security_config)
When Bootstrap starts and completes, the callback receives the bootstraping and bootstrap URCs. After the server delivers the configuration, the client registers with the device management server.
Note: The LwM2M standard defines dedicated ports for the Bootstrap Server as CoAP
5685/ CoAPS5686(5683/5684are the ports of the device management server), but different platforms may customize the ports; follow the actual platform configuration.
Initiate Registration and Query Status
After completing the configuration and callback registration, call register() to initiate the registration request:
result = client.register()
print("register request result:", result)
It returns 0 on success and -1 on failure. After the registration request is successfully submitted, the status can be queried via stat():
stat() Return Value |
Status |
|---|---|
0 |
Not registered |
1 |
Registering |
2 |
Registered |
3 |
Deregistering |
4 |
Registration failed |
For example, wait up to 60 seconds to confirm the registration result:
import utime
for elapsed in range(60):
register_state = client.stat()
print("register state:", register_state)
if register_state == 2:
print("LwM2M client is online")
break
if register_state == 4:
print("LwM2M registration failed")
break
utime.sleep(1)
If the status remains 1 for a long time, check the network, server address, and security credentials in combination with the initial, dtls, and ready URCs.
Send an Update Manually
The module includes an automatic heartbeat flow internally, so calling update() manually is usually not required. Call it manually only when debugging or when the business explicitly requires an immediate registration update:
ssid = 1000
if client.stat() == 2:
result = client.update(ssid)
print("update request result:", result)
update() returns 0 on success and -1 on failure; the final result is notified through the update URC.
Deregister the Client
When the device no longer uses the LwM2M service, call unregister() to initiate deregistration:
result = client.unregister()
print("unregister request result:", result)
A return value of 0 means the deregistration request has been submitted. The final result is notified through the deregister URC, and stat() returns 0 after deregistration completes.
Complete Example Code
The following example encapsulates the network check, object initialization, parameter configuration, callback registration, and registration status waiting flow. The example uses the no-security mode; SERVER_ADDRESS and ENDPOINT_NAME must be replaced before running.
import utime
import checkNet
import lwm2m
SERVER_ADDRESS = "coap://lwm2m.example.com:5683"
ENDPOINT_NAME = "quecpython-device-001"
SERVER_ID = 1
SERVER_SSID = 1000
class Lwm2mClient(object):
def __init__(self, server_address, endpoint_name):
self.server_address = server_address
self.endpoint_name = endpoint_name
self.client = lwm2m()
def _require_success(self, operation, result):
if result != 0:
raise RuntimeError(
"{} failed, result={}".format(operation, result)
)
def _callback(self, event):
print("LwM2M event:", event)
if len(event) < 3:
return
urc = str(event[2])
if '"ready","successfully"' in urc:
print("LwM2M register success")
elif '"ready","failed"' in urc:
print("LwM2M register failed")
elif '"update","successfully"' in urc:
print("LwM2M update success")
elif '"deregister"' in urc:
print("LwM2M deregister event")
elif '"fota/pkgurl"' in urc:
print("LwM2M FOTA package URL received")
def configure(self):
self._require_success("init", self.client.init())
# Clear the old parameters to ensure this demo uses a complete new configuration.
self._require_success(
"reset config",
self.client.config(self.client.Reset, []),
)
security_config = [
SERVER_ID,
SERVER_SSID,
self.server_address,
0,
3,
]
self._require_success(
"security config",
self.client.config(self.client.Security, security_config),
)
server_config = [
SERVER_ID,
300,
1,
60,
86400,
1,
"U",
]
self._require_success(
"server config",
self.client.config(self.client.Server, server_config),
)
self._require_success(
"endpoint mode config",
self.client.config(self.client.Epnamemode, [1]),
)
self._require_success(
"endpoint name config",
self.client.config(self.client.Epname, [self.endpoint_name]),
)
self._require_success(
"URC config",
self.client.config(self.client.Urc, [1]),
)
self._require_success(
"FOTA config",
self.client.config(self.client.Fota, [0, 0]),
)
self._require_success(
"callback config",
self.client.register_call(self._callback),
)
def register(self):
self._require_success("register", self.client.register())
def wait_registered(self, timeout):
for elapsed in range(timeout):
register_state = self.client.stat()
print("register state:", register_state)
if register_state == 2:
return True
if register_state == 4:
return False
utime.sleep(1)
return False
def update(self):
if self.client.stat() != 2:
print("client is not registered")
return -1
return self.client.update(SERVER_SSID)
def unregister(self):
return self.client.unregister()
stage, state = checkNet.waitNetworkReady(30)
if stage == 3 and state == 1:
lwm2m_client = Lwm2mClient(SERVER_ADDRESS, ENDPOINT_NAME)
lwm2m_client.configure()
lwm2m_client.register()
if lwm2m_client.wait_registered(60):
print("LwM2M client is ready")
else:
print("LwM2M client registration timeout or failed")
else:
print(
"Network connection failed, stage={}, state={}".format(
stage,
state,
)
)
After successful registration, the application can continue with its own business. Call lwm2m_client.update() when an immediate registration update is required, and call lwm2m_client.unregister() when exiting the service.
Common URCs
The URCs received in the callback reflect the stage the client is in. Different platforms may lack some events; use the key result events as the criterion.
| URC | Description | Key Fields |
|---|---|---|
+QLWURC: "pdp active",result,apn |
PDP activation result | result, apn |
+QLWURC: "initial",result,ssid |
Initialization result between the client and the server | result, ssid |
+QLWURC: "dtls",result,ssid |
DTLS handshake result | result, ssid |
+QLWURC: "bootstraping" |
Bootstrap flow starts | None |
+QLWURC: "bootstrap",result,ssid |
Bootstrap result | result, ssid |
+QLWURC: "registering" |
Registering with the server | None |
+QLWURC: "ready",result,ssid |
Registration result | result, ssid |
+QLWURC: "update",result,ssid |
Registration update result | result, ssid |
+QLWURC: "deregister",ssid,code or +QLWURC: "deregister",code |
Deregistration result | Some platforms may omit ssid |
+QLWURC: "fota/pkgurl",url |
Firmware URL delivered by the server | url |
The server may also deliver change events such as APN, user name, password, authentication type, registration lifetime, and current time. For the complete event list, refer to the lwm2m API documentation.
Frequently Asked Questions
Q: What should I do if importing the lwm2m module fails?
A: Confirm that the module model is in the supported list, and check whether the current QuecPython firmware contains this module. Module support for customized firmware may vary.
Q: register() returns 0, but the platform still shows the device as offline?
A: A return value of 0 only means the registration request was submitted successfully. Wait for the ready URC and confirm that stat() eventually returns 2. Also check whether the Endpoint Name is consistent with the platform configuration.
Q: What should I do if no callback event is received?
A: Confirm that register_call() has been called, and enable URC reporting via config(lwm2m.Urc, [1]) before registration.
Q: How to troubleshoot when ready,failed is always received?
A: Check the network status, server address and port, serverID, SSID, Endpoint Name, security mode, and credentials in order. When using PSK or certificate mode, also check whether the dtls URC indicates a successful handshake.
Q: What if the DTLS handshake fails in PSK mode?
A: Confirm that psk_id, psk_key, and their encoding format are exactly consistent with the server, and confirm that the connection port supports DTLS. If mode=3 is used, also confirm that the platform's Endpoint Name is consistent with psk_id.
Q: Is it necessary to call update() periodically?
A: Usually not. The module has an automatic heartbeat flow internally; frequent manual calls may cause invalid traffic. Call it only when debugging or when the business explicitly requires an immediate registration update.
Q: Why is the FOTA download address not received?
A: Confirm that URC is enabled and the FOTA download and update policy is configured as manual mode [0, 0], then check whether the fota/pkgurl URC appears in the callback.
Q: What does config() returning -2 mean?
A: It means the current firmware does not support this configuration item. Check the module model and firmware version; customized features depend on the actual firmware.