LiteOS学习笔记-8LiteOS SDK oc流程之LwM2M
LwM2M协议
LwM2M协议简介
通信协议又称为传输协议,用于定义多个设备之间传播信息时的系统标准。通信协议定义了设备通信中的语法、语义、同步规则和发生错误时的处理原则,可以理解为机器之间使用的语言。
在物联网场景中,通信主要发生在设备和物联网平台之间,由于大部分物联网设备都是资源受限型设备,它们的物理资源和网络资源都非常有限,直接使用现有的HTTP协议进行通信对它们来说要求实在是太高了。因此,物联网场景中主要使用的通信协议都是轻量级的,为资源受限环境而设计的通信协议,例如CoAP/LWM2M协议和MQTT协议。
LwM2M(Lightweight M2M,轻量级M2M),由开发移动联盟(OMA)提出,是一种轻量级的、标准通用的物联网设备管理协议,可用于快速部署客户端/服务器模式的物联网业务。
LwM2M 协议特性
LwM2M协议主要特性包括:
- 基于资源模型的简单对象
- 资源操作:创建/检索/更新/删除/属性配置
- 资源的观察/通知
- 支持的数据格式:TLV/JSON/Plain Text/Opaque
- 传输层协议:UDP/SMS
- 安全协议:DTLS
- NAT/防火墙应对方案: Queue模式
- 支持多LwM2M Server
- 基本的M2M功能:LwM2MServer,访问控制,设备,网络连接监测,固件更新,位置和定位服务,统计
LwM2M协议架构
LwM2M 对象定义
对象是逻辑上用于特定目的的一组资源的集合。例如固件更新对象,它就包含了用于固件更新目的的所有资源,例如固件包、固件URL、执行更新、更新结果等。
OMA定义了一些标准对象,LwM2M协议为这些对象及其资源已经定义了固定的ID。例如:固件更新对象的对象ID为5,该对象内部有8个资源,资源ID分别为0~7,其中“固件包名字”这个资源的ID为6。
使用对象的功能之前,必须对该对象进行实例化,对象可以有多个对象实例,对象实例的编号从0开始递增。
通过对象ID,实例ID和资源ID,我们就可以用三个数字指示一个具体的资源,例如5/0/6表示固件对象第一个实例的固件包名称。在注册阶段,客户端就会把自己支持的对象的示例写入服务端,用于通知服务端自己支持的能力。
OMA的LwM2M规范中定义了22个标准对象,比如:
LwM2M 资源定义
LwM2M定义了一个资源模型,所有信息都可以抽象为资源以提供访问。资源是对象的内在组成,隶属于对象,LwM2M客户端可以拥有任意数量的资源。和对象一样,资源也可以有多个实例。
LwM2M客户端、对象以及资源的关系如图所示:
对象和资源定义格式
-
对象定义格式
-
资源定义格式
LwM2M 接口定义
LWM2M协议定义了三个逻辑实体:LWM2M Server(服务器端),LWM2M Client(客户端),LWM2M Bootstrap Server(引导服务),其中LWM2M Server和LWM2M Bootstrap Server可以是同一个服务器。服务器,部署在M2M服务供应商处或网络服务供应商处,客户端,部署在各个LwM2M设备上。在这些实体间,LWM2M协议定义了四个接口:
- 设备发现和注册
- 引导程序
- 设备管理和服务实现
- 信息上报
LwM2M接口模型如图所示:
Bootstrap:引导接口。客户端首次启动后,可以通过该接口访问引导服务(需要厂家提前把引导服务器的地址写入设备),获取服务器端的地址。
Device Discovery and Registration:设备发现与注册接口。客户端通过该接口将自己的基本信息写到服务器端,包括自己支持哪些能力。该接口同时还可以用于升级注册信息和注销设备。
Device Management and Service Enablement:设备管理和业务实现接口。服务器端通过该接口给客户端下发指令,客户端处理指令并返回响应。该接口定义了7种操作,分别是:“Create”、“Read”、“Write”、“Delete”、“Execute”、“Write Attributes”和“Discover”。
设备管理和业务实现接口的交互过程如图所示:
Information Reporting:信息上报接口。LWM2M允许服务器端向客户端订阅资源信息,客户端被订阅后按照接口约定的模式(事件触发或定期)向服务器端主动上报信息。
在上述接口中,服务器端对客户端进行操作时都需要指定一个具体的操作目标,例如读某个属性,写某个属性。在HTTP协议中,这种目标的指定是通过URI或者消息体内携带的文本消息进行指定。
消息流程示例
LiteOS中LwM2M实现
LiteOS OC lwm2m 抽象组件
概述
为了适应各种各样的使用lwm2m接入华为OC的模式,特采用该层次接口,对上提供应用所需的接口,对下允许接入方式的灵活适配。
oc_lwm2m_agent是处理使用lwm2m协议对接华为OC的流程抽象层,允许使用流程进行对接,也允许使用NB芯片内置的流程进行对接。
OC AL提供的基于LwM2M协议抽象层接口代码目录为:…\iot_link\oc\oc_lwm2m
,具体如下:
目录 | 描述 |
---|---|
…/oc_lwm2m_al | 华为云服务OC LwM2M的抽象层接口及实现。通过注册,最终调用底层的适配函数 |
…/atiny_lwm2m | SDK内置的soft类设备适配华为OC服务的LwM2M的具体实现。通过抽象层提供的oc_lwm2m_register注册函数,将相关功能注册到SDK中 |
…/boudica150 | SDK内置的boudica150适配华为OC服务的LwM2M的具体实现。通过抽象层提供的oc_lwm2m_register注册函数,将相关功能注册到SDK中 |
OC lwm2m AL的api接口声明在<oc_lwm2m_al.h>
中,使用相关的接口需要包含该头文件。
配置并连接
对接服务器的所有信息保存在结构体oc_config_param_t
中,其定义在oc_lwm2m_al.h
中,如下:
typedef struct
{
en_oc_boot_strap_mode_t boot_mode; ///< bootmode,if boot client_initialize, then the bs must be set
oc_server_t boot_server; ///< which will be used by the bootstrap, if not, set NULL here
oc_server_t app_server; ///< if factory or smart boot, must be set here
fn_oc_lwm2m_msg_deal rcv_func; ///< receive function caller here
void *usr_data; ///< used for the user
}oc_config_param_t;
其中boot_mode是对接模式,对应华为平台的三种模式:
typedef enum
{
en_oc_boot_strap_mode_factory = 0,
en_oc_boot_strap_mode_client_initialize,
en_oc_boot_strap_mode_sequence,
} en_oc_boot_strap_mode_t;
app_server参数是服务器信息,参数中包括设备标识符,服务器地址,端口,psk_id:DTLS使用,不用为null。定义如下:
typedef struct
{
char *ep_id; ///< endpoint identifier, which could be recognized by the server
char *address; ///< server address,maybe domain name
char *port; ///< server port
char *psk_id; ///< server encode by psk, if not set NULL here
char *psk;
int psk_len;
} oc_server_t;
rcv_func是回调函数的函数指针,当设备接收到lwm2m消息后回调:
typedef int (*fn_oc_lwm2m_msg_deal)(void *usr_data, en_oc_lwm2m_msg_t type, void *msg, int len);
在配置结构体完成之后,调用配置函数进行配置并连接,API如下:
/**
* @brief the application use this function to configure the lwm2m agent
* @param[in] param, refer to oc_config_param_t
* @return the context, while NULL means failed
*/
void* oc_lwm2m_config(oc_config_param_t *param);
函数实现如下:
int oc_lwm2m_config( oc_config_param_t *param)
{
int ret = (int)en_oc_lwm2m_err_system;
if ((NULL != s_oc_lwm2m_ops.opt) && (NULL != s_oc_lwm2m_ops.opt->config))
{
if(NULL != param)
{
ret = s_oc_lwm2m_ops.opt->config(param);
}
else
{
ret = (int)en_oc_lwm2m_err_parafmt;
}
}
return ret;
}
首先赋值en_oc_lwm2m_err_system
。
然后确定lwM2M实现方法指针是否为空,查看配置信息是否为空,不为空就会走到结构体oc_config_param_t
里面查看包括一些诸如对接模式以及服务器的相关信息,如果不为空,则使用其进行配置。
数据上报
连接成功之后,因为平台部署了编解码插件,直接向华为云平台上报二进制数据即可,oc_lwm2m提供的API如下:
/**
* @brief the application use this function to send the message to the cdp
* @param[in] buf the message to send
* @param[in] len the message length
* @param[in] timeout block time
* @return 0 success while others the error code described as en_oc_lwm2m_err_code_t
*/
int oc_lwm2m_report(char *buf, int len, int timeout);;
对接流程选择
/**
* @brief this data structure defines the lwm2m agent implement
*/
typedef struct
{
fn_oc_lwm2m_config config; ///< this function used for the configuration
fn_oc_lwm2m_report report; ///< this function used for the report data to the cdp
fn_oc_lwm2m_deconfig deconfig; ///< this function used for the deconfig
} oc_lwm2m_opt_t;
/**
*@brief the lwm2m agent should use this function to register the method for the application
*
*@param[in] name, the operation method name
*@param[in] opt, the operation method implement by the lwm2m agent developer
*@return 0 success while <0 failed
*/
int oc_lwm2m_register(const char *name, const oc_lwm2m_opt_t *opt)
{
int ret = -1;
if (NULL == s_oc_lwm2m_ops.opt)
{
s_oc_lwm2m_ops.name = name;
s_oc_lwm2m_ops.opt = opt;
ret = 0;
}
return ret;
}
由注册函数可看到,协议实现主要实现三个基本功能:config 、report、deconfig。
对接方式有两种,从编译流程可知,协议的选择在oc_lwm2m.mk文件中根据config中的宏来选择。
ifeq ($(CONFIG_OCLWM2M_ENABLE), y)
C_SOURCES += $(iot_link_root)/oc/oc_lwm2m/oc_lwm2m_al/oc_lwm2m_al.c
C_INCLUDES += -I $(iot_link_root)/oc/oc_lwm2m/oc_lwm2m_al
ifeq ($(CONFIG_OCLWM2MTINY_ENABLE), y)
include $(iot_link_root)/oc/oc_lwm2m/atiny_lwm2m/atiny_lwm2m.mk
else ifeq ($(CONFIG_BOUDICA150_ENABLE),y)
include $(iot_link_root)/oc/oc_lwm2m/boudica150_oc/boudica150_oc.mk
endif
ifeq ($(CONFIG_OCLWM2M_DEMO_DTLS_ENABLE),y)
C_SOURCES += $(iot_link_root)/oc/oc_lwm2m/oc_lwm2m_al/oc_dtls_lwm2m_demo.c
else ifeq ($(CONFIG_OCLWM2M_DEMO_ENABLE),y)
C_SOURCES += $(iot_link_root)/oc/oc_lwm2m/oc_lwm2m_al/oc_lwm2m_demo.c
endif
endif
整体流程如下图所示
link_main
函数会根据iot_config.h
文件中的宏定义取值决定什么组件进行初始化操作,初始化时,当使能OC_LWM2M_ENABLE
时,会执行oc_lwm2m_init()
,进行lwm2m相关初始化操作; oc_lwm2m_al是处理使用lwm2m协议对接华为OC流程的抽象层。允许使用流程进行对接,也允许使用NB芯片内置的流程进行对接。oc_lwm2m_init()
函数会调用oc_lwm2m_imp_init()
函数,这个OC层的实现可以分为两个不同的场景,一种使用NB进行对接即boudica150
,其内部包含lwm2m协议栈。第二种是使用agent_lwm2m.c
中实现对应的config,report,deconfig函数进行流程对接。
NB模组对接华为云
boudica150
对接华为云,其主要指内部包含lwM2M协议栈的模组之类。这里主要就是在boudica150.c
中实现了oc_lwm2m_imp_init()
函数,该函数中对应实现了OC类的接口实现部分,即config,report,deconfig函数,其中config的实现会调用对应的boudica150_boot()设置NB模组的一些基本参数,该函数内部调用at_oobregister注册函数。上报函数中主要是通过AT指令进行实现,包括字节转换为ASCII码等以及at_command函数等。
agent流程对接华为云
图示清晰的勾勒出函数调用层次关系。可以看到使用agent_lwm2m.c
实现三个接口函数调用了lwm2m_al.c
中的函数。即上述过程其内部实现细节深层调用的是lwm2m协议栈,因此如果想使用agent进行流程对接,需要同时使能开关CONFIG_LWM2M_AL_ENABLE
物联组件协议层
物联组件协议层目录:…\iot_link\network\
。IoT Device SDK Tiny集成了LwM2M、CoAP、MQTT等物联网标准协议,您可以直接调用已实现协议,或添加自定义协议,将其注册进SDK中。
如lwm2m协议文件夹中mk文件:
ifeq ($(CONFIG_LWM2M_AL_ENABLE), y)
C_SOURCES += ${wildcard $(iot_link_root)/network/lwm2m/lwm2m_al/*.c}
C_INCLUDES += -I $(iot_link_root)/network/lwm2m/lwm2m_al
ifeq ($(CONFIG_WAKAAMALWM2M_ENABLE), y)
include $(iot_link_root)/network/lwm2m/wakaama_lwm2m/wakaama.mk
else ifeq ($(CONFIG_WAKAAMARAW_ENABLE), y)
include $(iot_link_root)/network/lwm2m/wakaama_raw/wakaama.mk
else
#you could extend the lwm2m support implement
endif
endif
此处同样是在link_main函数中通过宏控制调用lwm2m_al_init()
,在此函数中再调用实现初始化函数lwm2m_imp_init()
,此函数具体实现在文件iot_link-> network-> lwm2m-> wakaama_lwm2m-> port-> lwm2m_port.c
文件中。
应用开发
Makefile配置
因为本次实验用到的组件较多:
- AT框架
- ESP8266设备驱动
- 串口驱动框架
- SAL组件
- lwm2m组件
- oc_lwm2m组件
这些实验代码全部编译下来,有350KB,而小熊派开发板所使用的主控芯片STM32L431RCT6的 Flash 仅有256KB,会导致编译器无法链接出可执行文件,所以要在makefile中修改优化选项,修改为-Os参数,即最大限度的优化代码尺寸,并去掉-g参数,即代码只能下载运行,无法调试
################################################################################
# building variables
################################################################################
# debug build?
DEBUG = 1
# optimization
#OPT = -O0 -g
OPT = -Os
硬件参数配置
配置ESP8266设备的波特率和设备名称,WIFI信息等。
代码实现
(1)使用oc_lwm2m_config
函数进行配置;主要包括服务器IP,端口以及加密方式等,调用该函数成功,则表示成功和服务器握手
(2)调用oc_lwm2m_report
进行数据上报(按需调用)
(3)如果需要,调用oc_lwm2m_deconfig
替换服务器
当然具体细节还包括后续关联配置文件定义的相应数据结构体属性和命令,定义ID等。接下来介绍这边的函数调用关系,前文已经介绍到link_main()
函数中的一些初始化工作,在函数最后通过使能CONFIG_LINKDEMO_ENABLE
开关调用了函数standard_app_demo_main()
,在其内部用户创建了多个任务,包括数据采集任务,数据上报任务,命令处理任务。
在实验文件头部定义通信协议参数
#define cn_AG_SensorID 0x0
#define cn_Control_Light 0x1
#define cn_response_Control_Light 0x2
#define cn_Control_Motor 0x3
#define cn_response_Control_Motor 0x4
//上报数据协议,messageID与上述宏一一对应
#pragma pack(1)
typedef struct
{
int8_t messageId;
int8_t Temperature;
int8_t Humidity;
uint16_t Luminance;
uint8_t Light_state;
uint8_t Motor_state;
} tag_AG_Sensor_report;
typedef struct
{
int8_t messageId;
uint16_t mid;
int8_t errcode;
int8_t Light_State;
} tag_Response_Control_Light;
typedef struct
{
int8_t messageId;
uint16_t mid;
int8_t errcode;
int8_t Motor_State;
} tag_Response_Control_Motor;
typedef struct
{
int8_t messageId;
uint16_t mid;
char Light[3];
} tag_Control_Light;
typedef struct
{
uint8_t messageId;
uint16_t mid;
char Motor[3];
} tag_Control_Motor;
#pragma pack()
解析后平台收到的JSON上报数据格式如下
{
services":[
{"serviceId":"Agriculture",
"serviceType":"Agriculture",
"data":
{"properties":
{"Temperature":21,
"Humidity":34,
"Iuminance":128}]
}
数据采集任务
static int app_sensor_collect_entry()
{
Init_E53_IA1();
while (1)
{
E53_IA1_Read_Data();
printf("\r\n******************************Lux Value is %d\r\n", (int)E53_IA1_Data.Lux);
printf("\r\n******************************Humidity is %d\r\n", (int)E53_IA1_Data.Humidity);
printf("\r\n******************************Temperature is %d\r\n", (int)E53_IA1_Data.Temperature);
osal_task_sleep(2 * 1000);
}
return 0;
}
数据上报任务
static int app_report_task_entry()
{
int ret = -1;
oc_config_param_t oc_param;
tag_AG_Sensor_report Agriculture;
(void)memset(&oc_param, 0, sizeof(oc_param));
/********配置通信参数,尝试通信********/
oc_param.app_server.address = cn_app_server;
oc_param.app_server.port = cn_app_port;
oc_param.app_server.ep_id = cn_endpoint_id;
oc_param.boot_mode = en_oc_boot_strap_mode_factory;
oc_param.rcv_func = app_msg_deal;
ret = oc_lwm2m_config(&oc_param);
if (0 != ret)
{
return ret;
}
/********通信建立,上报封装数据********/
//install a dealer for the led message received
while (1) //--TODO ,you could add your own code here
{
Agriculture.messageId = cn_AG_SensorID;
Agriculture.Temperature = (int8_t)E53_IA1_Data.Temperature;
Agriculture.Humidity = (int8_t)E53_IA1_Data.Humidity;
Agriculture.Luminance = htons((uint16_t)E53_IA1_Data.Lux);
Agriculture.Light_state = (int8_t)Light_state;
Agriculture.Motor_state = (int8_t)Motor_state;
//HAL_UART_Transmit_DMA(&huart1,(char *)&Agriculture, sizeof(Agriculture));
oc_lwm2m_report((char *)&Agriculture, sizeof(Agriculture), 1000);
osal_task_sleep(2 * 1000);
}
return ret;
}
命令处理函数
static int app_cmd_task_entry()
{
int ret = -1;
tag_Response_Control_Light Response_Agriculture_Control_Light;
tag_Response_Control_Motor Response_Agriculture_Control_Motor;
tag_Control_Light *Agriculture_Control_Light;
tag_Control_Motor *Agriculture_Control_Motor;
int8_t msgid;
while (1)
{
if (osal_semp_pend(s_rcv_sync, cn_osal_timeout_forever))
{
//取后八位
msgid = s_rcv_buffer[0] & 0x000000FF;
switch (msgid)
{
case cn_Control_Light:
Agriculture_Control_Light = (tag_Control_Light *)s_rcv_buffer;
printf("Agriculture_Control_Light:msgid:%d mid:%d", Agriculture_Control_Light->messageId, ntohs(Agriculture_Control_Light->mid));
/********** code area for cmd from IoT cloud **********/
if (Agriculture_Control_Light->Light[0] == 'O' && Agriculture_Control_Light->Light[1] == 'N')
{
HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_SET);
POINT_COLOR = GREEN;
LCD_ShowString(175, 198, 210, 32, 32, "ON ");
Light_state = 1;
Response_Agriculture_Control_Light.messageId = cn_response_Control_Light;
Response_Agriculture_Control_Light.mid = Agriculture_Control_Light->mid;
Response_Agriculture_Control_Light.errcode = 0;
Response_Agriculture_Control_Light.Light_State = 1;
oc_lwm2m_report((char *)&Response_Agriculture_Control_Light, sizeof(Response_Agriculture_Control_Light), 1000); ///< report cmd reply message
}
if (Agriculture_Control_Light->Light[0] == 'O' && Agriculture_Control_Light->Light[1] == 'F' && Agriculture_Control_Light->Light[2] == 'F')
{
HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_RESET);
POINT_COLOR = RED;
LCD_ShowString(175, 198, 210, 32, 32, "OFF");
Light_state = 0;
Response_Agriculture_Control_Light.messageId = cn_response_Control_Light;
Response_Agriculture_Control_Light.mid = Agriculture_Control_Light->mid;
Response_Agriculture_Control_Light.errcode = 0;
Response_Agriculture_Control_Light.Light_State = 0;
oc_lwm2m_report((char *)&Response_Agriculture_Control_Light, sizeof(Response_Agriculture_Control_Light), 1000); ///< report cmd reply message
}
/********** code area end **********/
break;
default:
break;
}
}
}
return ret;
}
app_msg_deal()
//use this function to push all the message to the buffer
static int app_msg_deal(void *usr_data, en_oc_lwm2m_msg_t type, void *data, int len)
{
unsigned char *msg;
msg = data;
int ret = -1;
if (len <= cn_app_rcv_buf_len)
{
if (msg[0] == 0xaa && msg[1] == 0xaa)
{
printf("OC respond message received! \n\r");
return ret;
}
memcpy(s_rcv_buffer, msg, len);
s_rcv_datalen = len;
(void)osal_semp_post(s_rcv_sync);
ret = 0;
}
return ret;
}
就是当收到是aaaa时打印oc接收到响应消息,如不是则释放信号量进入命令处理函数。
总结
LwM2M协议服务器端开发复杂,仅用来与云平台对接,后续开发产品时,仅保留此接口,由用户选择接入哪个平台。