tcp/ip学习之20: LwM2M协议和解析
一、LwM2M协议
LwM2M( Lightweight Machine-To-Machine )协议是由Open Mobile Alliance(OMA)定义的一套适用于物联网的轻量级协议。它使用RESTful接口,提供设备的接入、管理和通信功能,也适用于资源比较紧张的设备。LwM2M协议的架构如下图所示:

LwM2M协议底层使用CoAP协议传输数据和信令。而在LwM2M协议的架构中,CoAP协议可以运行在UDP或者SMS(短信)之上,通过DTLS(数据报传输层安全)来实现数据的安全传输。
LwM2M协议架构主要包含3种实体——LwM2M Bootstrap Server、LwM2M Server和LwM2M Client。
LwM2M Bootstrap Server负责引导LwM2M Client注册并接入LwM2M Server,之后LwM2M Server和LwM2M Client就可以通过协议指定的接口进行交互了。
二、编写代码
lwm2m_server.py
#!/usr/bin/env python3
import asyncio, json, time
from aiocoap import Context, Message, CHANGED, CONTENT, BAD_REQUEST
from aiocoap.resource import Resource, Site
LOCATION_COUNTER = 1
CLIENTS = {} # location -> {ep, lt, addr, path_dict, last_seen}
class CatchAll(Resource):
async def render_post(self, request):
print('[CatchAll] uri_path=', request.opt.uri_path,
'query=', request.opt.uri_query)
return Message(code=CHANGED)
class RdResource(Resource):
"""统一处理 /rd 和 /rd/<loc> """
async def render_post(self, request):
print(f'[S] raw uri_path = {request.opt.uri_path}')
print(f'[S] raw uri_query = {request.opt.uri_query}')
request.opt.uri_path = '/rd'
if len(request.opt.uri_path) == 1: # /rd
return await self.register(request)
elif len(request.opt.uri_path) == 2: # /rd/<loc>
return await self.update(request)
return Message(code=BAD_REQUEST)
async def render_get(self, request):
if len(request.opt.uri_path) == 2: # /rd/<loc>
return await self.discover(request)
return Message(code=BAD_REQUEST)
# ---------- 内部 ----------
async def register(self, request):
global LOCATION_COUNTER, CLIENTS
# 安全解析 query
qs = {}
for item in request.opt.uri_query:
if '=' in item:
k, v = item.split('=', 1)
qs[k] = v
ep = qs.get('ep')
lt = int(qs.get('lt', 300))
if not ep:
return Message(code=BAD_REQUEST)
location = f'rd{LOCATION_COUNTER}'
LOCATION_COUNTER += 1
# 安全解析 payload
try:
link = json.loads(request.payload) if request.payload else {}
except json.JSONDecodeError:
link = {}
addr = str(request.remote) # 兼容 0.4.7
CLIENTS[location] = dict(ep=ep, lt=lt, addr=addr,
path_dict=link, last_seen=time.time())
print(f"[S] Registered {ep=} -> location={location} addr={addr}")
return Message(code=CHANGED, location_path=[location])
async def update(self, request):
loc = request.opt.uri_path[1]
if loc not in CLIENTS:
return Message(code=BAD_REQUEST)
CLIENTS[loc]['last_seen'] = time.time()
print(f"[S] Update from {loc}")
return Message(code=CHANGED)
async def discover(self, request):
loc = request.opt.uri_path[1]
if loc not in CLIENTS:
return Message(code=BAD_REQUEST)
payload = json.dumps(CLIENTS[loc]['path_dict']).encode()
return Message(code=CONTENT, payload=payload)
class Obj3311(Resource):
"""GET /3311/0/5700"""
async def render_get(self, request):
print("Obj3311 render_get")
for loc, info in CLIENTS.items():
if info['path_dict'].get("3311/0/5700"):
val = info.get('sensor_value', 23.5)
return Message(code=CONTENT, payload=str(val).encode())
return Message(code=BAD_REQUEST)
async def main():
site = Site()
site.add_resource(['rd'], RdResource())
site.add_resource(['3311', '0', '5700'], Obj3311())
site.add_resource([], CatchAll)
await Context.create_server_context(site, bind=("127.0.0.1", 5684))
print("[S] LwM2M server on udp:5684")
await asyncio.get_running_loop().create_future()
if __name__ == "__main__":
asyncio.run(main())
lwm2m_client.py
#!/usr/bin/env python3
import asyncio, json, time
from aiocoap import Context, Message, POST, GET
SERVER = "coap://127.0.0.1:5684"
ENDPOINT = "python-client-01"
LOCATION = None
# ---------------- 资源模型 ----------------
class Sensor:
value = 23.5
unit = "°C"
min_v = 0.0
max_v = 100.0
sensor = Sensor()
def build_link():
return {"3311/0/5700": {"value": sensor.value},
"3311/0/5701": {"value": sensor.unit},
"3311/0/5601": {"value": sensor.min_v, "writable": True},
"3311/0/5602": {"value": sensor.max_v, "writable": True}}
# ---------------- 协议流程 ----------------
async def register(ctx):
global LOCATION
payload = json.dumps(build_link()).encode()
req = Message(code=POST,
uri='coap://127.0.0.1:5684/rd',
payload=payload,
uri_query=['ep=python-client-01', 'lt=60', 'lwm2m=1.0'],
token=b'') # ← 强制空 Token
print('[C] sending to', req.opt.uri_path, req.opt.uri_query) # 调试用
rsp = await ctx.request(req).response
if rsp.code != 2.01:
# raise RuntimeError(f'registration failed: {rsp.code}')
print(f"warnning!!! rsp.code = {rsp.code}")
pass
LOCATION = '/'.join(rsp.opt.location_path)
print(f'[C] Registered, location={LOCATION}')
async def update(ctx):
while True:
await asyncio.sleep(30)
req = Message(code=POST, uri=f"{SERVER}/{LOCATION}")
await ctx.request(req).response
print("[C] Update sent")
async def read_sensor(ctx):
while True:
await asyncio.sleep(10)
sensor.value = round(sensor.value + 0.5, 1)
print(f"[C] sensor now {sensor.value}{sensor.unit}")
async def main():
ctx = await Context.create_client_context()
await register(ctx)
await asyncio.gather(update(ctx), read_sensor(ctx))
if __name__ == "__main__":
asyncio.run(main())

浙公网安备 33010602011771号