IoT/透过oc_lwm2m/boudica150 源码中的AT指令序列,分析NB-IoT接入华为云物联网平台IoTDA的工作机制
文章目录
- 概述
- 指令调用顺序
- 具体接入指令分析
- ATE0 关闭回显
- AT+QREGSWT 设置(平台)注册模式
- AT+QLWSREGIND=0 手动注册平台
- set_autoconnect / AT+NCONFIG
- AT+NBAND=x,x
- set_plmn / AT+COPS
- _set_apn / AT+CGDCONT
- (安全)接入参数 CDP+DTLS+PSK
- AT+NNMI 设置新消息指示
- _check_netattach 网络附着状态
- AT+COPS=0 和 AT+CGATT=1
- _check_observe网络附着状态
- urc_qlwevtind
- 总结和后记
概述
在一个不断探索的过程中,我们已经可以真正的将NB-IoT设备以安全的方式接入打破IoTDA平台,包括使用PC-AT指令、使用MCU-AT指令及其数据处理代码,也已经了解了NB-IoT-AT的实现机制。本文我们将继续深入,详谈 NB-IoT设备是如何通过不同的AT指令,先接入运营商网络,再接入华为云物联网平台的。
@HISTORY
我们已经从<实验5 基于NB-IoT的智慧农业实验> 的乌龙坑里爬了出来。并有了以下知识点作为后文分析的支撑知识:
0、PC-AT指令的部分设置是存储在模组内的,即使MCU-AT不执行这些操作,也会成功连接!
1、NB-IoT通信模组通过串口以AT指令形式与MCU通信。
1、注册NB-IoT设备时,设备标识只能是IMEI国际移动设备识别码?Yes
2、在当下连接到IoTDA,是否必须启用DTLS?不再支持5683非安全接入?Yes
3、NB-IoT设备密钥格式,在代码中以数组字符串定义,和Wifi设备不一样?Yes
@NOTE
转载请标明出处,https://blog.csdn.net/quguanxin/category_12929470.html
指令调用顺序
在 #<IoT/实现和分析 NB-IoT+DTLS+PSK 接入华为云物联网平台IoTDA过程,总结避坑攻略># 文中,我们已经实现了较完整和准确的设备安全(PSK+DTLS)接入IoTDA的程序,并详细分析了 boudica150_boot 上层的调用关系,在 boudica150_boot 函数中被循环执行的指令序列如下。结合上文以及 #<IoT/基于NB28-A/BC28-CNV通信模组使用AT指令连接华为云IoTDA平台># 中的表述,我们将展开更细致的NB-IoT设备AT指令接入运营商网络和接入华为云物联网平台过程和原理的分析。
//如下过程的外层是while(1)//Do:执行命令 AT+NRB 重启UE 即重启我们的NB-IoT通信模组 并延时等待10sboudica150_reboot();
//Do:执行ATE0,等待返回OK /禁用模组对接收到的AT指令的回显(Echo)/什么是回显下文另谈boudica150_set_echo (0);
//Do: 设置UE是自动还是手动触发以注册IoT平台 /这就是一个本地设置操作,些模组内存或KV? 应该会立即返回Okboudica150_set_regmode(1);
//Do:使能 +CME ERROR: <err> 结果码 https://blog.csdn.net/quguanxin/article/details/146547709boudica150_set_cmee(1);
//Do:关闭AUTOCONNECT自动连接,本质上是想关闭CFUN射频
//cgatt and cfun must be called if autoconnect is falseboudica150_set_autoconnect(0);
//Do:设置通信模组支持的频带(依据SIM卡运营商选择,或默认选择全部支持的频段)/Band20并不被支持,需要修改掉boudica150_set_bands(bands);
//Do:AT+CFUN=1 使能射频boudica150_set_fun(1);
//Do:AT+COPS=0 设置自动注册网络/源码中实参plmn==NULLboudica150_set_plmn(plmn);
//Do:由于apn==NULL,呼应plmn==NULL;本质上该函数内部什么也没干,直接返回trueboudica150_set_apn(apn);
//Do:参见PC-AT指令,设置CDP服务器boudica150_set_cdp(server,port);
//Do:开启dtls /river.qu 202506boudica150_set_dtls(1);
//Do:设置 PSK ID 和 PSK /river.qu 202506boudica150_set_psk(s_boudica150_oc_cb.oc_param.app_server.psk_id, s_boudica150_oc_cb.oc_param.app_server.psk);
//Do:AT+CGATT=1boudica150_set_cgatt(1);
//Do:AT+NNMI=1 /使能新消息指示和数据,会返回当前所有缓存的消息boudica150_set_nnmi(1);
//Do:检测网络附着状态/重复执行AT+CGATT?查询指令if(false == boudica150_check_netattach(16)) {continue;}
//Do:等待模组程序成功创建所需要的lwm2m对象if(false == boudica150_check_observe(16)) {continue; //we should do the reboot for the nB}
@NOTE 要意识到一个事情是,以上函数内部几乎都有等待反馈返回的过程,器本质上是在等信号量(后续其他文会继续讲述这块)。这些返回大多是瞬时完成的,因为它们不涉及到与基站或平台的交互,是由模组固件程序本地直接处理并完成的。
具体接入指令分析
部分指令分析请参考 #<PC-AT实验># 。
ATE0 关闭回显
所谓的回显,是在主机端显示你发送出去的AT指令。
AT+QREGSWT 设置(平台)注册模式
AT+QREGSWT=1 该命令用于在重启模块后设置注册模式:
如果不需要使用 LwM2M 物联网平台,必须使用命令 AT+QREGSWT=2 禁用注册功能。该命令只有在使用 AT+NRB 重启 UE 后才生效,若不禁用注册功能会使 UE 从网络中分离,造成相关服务(例如 TCP/UDP)失败。
连接到网络后自动触发注册,这里的网络是指?这里的注册是像谁发起?
这里的“网络”指 蜂窝移动网络,具体为NB-IoT无线接入网(RAN)和核心网(EPC/5GC),包括以下关键节点:
基站(eNodeB/gNB):提供无线信号覆盖。
移动管理实体(MME/AMF):负责终端接入认证和位置管理。
数据网关(PGW/UPF):提供互联网或专网连接。
连接过程: 模组通过PLMN(公共陆地移动网络)搜索并附着到运营商网络,完成空口同步、鉴权、默认承载建立等流程。
而注册对象是,LwM2M物联网平台(如华为OceanConnect、移动OneNET)
无论是 =0(手动注册)、当 =1(自动注册),其注册对象都是物联网平台。手动注册时,要通过AT+QLWSREGIND命令显式触发。而自动触发的时机在,模组重启后成功连接蜂窝网络(完成PSM激活或eDRX唤醒),此时,模组通过CoAP协议向LwM2M服务器(如coap://lwm2m.iot.platform.com)发送Register请求,携带Endpoint Client Name、Lifetime等参数,服务器返回2.01 Created响应,分配注册ID并维护在线状态。
AT+QLWSREGIND=0 手动注册平台
在PC-AT指令的实验中,我曾经有个疑问就是,我的NB-IoT是在什么时候注册勾搭上的物联网平台,当时只知道这个过程自动完成了,并不知道所以然。现在大约明白了,在手动注册模式(AT+QREGSWT=0)下,用户要发送AT+QLWSREGIND=0 指令,然后模组的固件程序会解析此指令,将其转换为LwM2M协议交互过程,这在固件实现内,其与自动注册模式下的LwM2M客户端和服务端建立连接的过程应该是同源的。这个大体过程可参考lwm2m_tiny源码实现。
set_autoconnect / AT+NCONFIG
static bool_t boudica150_set_autoconnect(int enable) {if(enable) mode = "AUTOCONNECT,TRUE";else //源码中传递的是0mode = "AUTOCONNECT,FALSE";(void) memset(resp,0,64);ret = boudica150_atcmd_response("AT+NCONFIG?\r","+NCONFIG",resp,64);//which means we need to set it if((false == ret)||(NULL == strstr(resp,mode))) {(void) memset(cmd,0,64);(void) snprintf(cmd,64,"AT+NCONFIG=%s\r",mode);ret = boudica150_atcmd(cmd,"OK");//上述设置后,需要再次软重启boudica150_reboot();}//如果检测到已经设置过,则直接返回成功
在小熊派社区/或HCIP_Lab源码中,这里的实参都是是0哈,不是1,即失能配置。此时,指令展开为:
AT+NCONFIG=AUTOCONNECT,FALSE
上述操作可能起到关闭CFUN的作用,这么猜想,主要是因为从后续代码来看,还会有CFUN射频开启过程。另外猜测注释 cgatt and cfun must be called if autoconnect is false 的含义可能是,若上述操作没有关闭成功,则需要手动去关闭 cgatt和cfun,起初这只是猜测哈,下一小节中,这一猜测被验证是正确的,NBAND操作时要求 CFUN 是关闭状态。
@OTHER
NCONFIG 指令的 参数有超级多的选项,这里就不列举了,哈哈,感觉我一年内就只能折腾这第一个选项了。通过指令手册,结合观察实际实验过程中的日志输出,我们可以看到,NCONFIG 查询指令,会输出 类型的设置值,我们可以先只关注AUTOCONNECT 功能类型的设置值是否为true即可。
@NOTE
我们在PC-AT实验中并没有使用到该指令,但是我们手动执行了 CFUN 的关闭操作过程。
AT+NBAND=x,x
在 boudica150_oc.c 源码中的 int oc_lwm2m_imp_init(void) 函数下,bands 已经被赋值,
//实测中,如下BAND配置是设置失败的,因为20不被我的模组支持。合适的配置是3,5,8
#define CONFIG_BOUDICA150_BANDS "5,8,20"
//from oc_lwm2m_imp_init
s_boudica150_oc_cb.bands = CONFIG_BOUDICA150_BANDS;
先看看,让我高兴的一句话,
这个备注说明了,前边boudica150_set_autoconnect传0,进行失能操作,试图关闭CFUN射频功能的猜想是对的。
所谓频段,是指3GPP标准定义的全球蜂窝网络频率范围,直接影响模组与运营商基站的通信能力,每个频段对应特定的上行(UL)和下行(DL)频率,确保模组与运营商基站在同一频段上收发信号,避免干扰。如,
Band 5:上行824-849MHz,下行869-894MHz
Band 8:上行880-915MHz,下行925-960MHz
不同国家/地区的运营商根据工信部或国际频谱管理机构的授权使用特定频段。以中国为例:
要注意的是频段由模组的射频芯片(如高通MDM9205、海思Boudica 200)硬件支持,指令仅启用/禁用特定频段,这个事情或能力与SIM关系不大,SIM卡仅提供运营商身份认证(IMSI/Ki密钥)和签约数据,不参与物理频段选择。综合上述分析,一个更合适的BAND配置应该是 3,5,8,而不是源码中的那样,但是有Band8就基本够用了,所以早期实验是成功的。我们通过NBAND测试指令,查询指令等,也是可以确认我们SIM卡的频段支持的,并确定源代码示例中的设置过程是不生效的,
set_plmn / AT+COPS
AT+COPS指令,用来选择和注册 EPS 网络运营商。PLMN(Public Land Mobile Network,公共陆地移动网络) 是由政府或运营商建立的移动通信网络,用于为公众提供无线通信服务。其核心标识由 MCC(移动国家码) 和 MNC(移动网络码) 组成,例如中国移动的 PLMN 为 46000,中国联通为 46001。我们在PC-AT实验中也是用过的。这里首先看看,我的卡是哪个运营商的呢?
AT+CIMI 请求国际移动用户识别码,其返回的是IMSI。
IMSI(国际移动用户识别码)和IMEI(国际移动设备识别码)是两种完全不同的标识符,分别用于识别SIM卡和物理设备。
通过 CIMI号、COPS查询结果,都可以断定此SIM卡是中国移动运营商的。COPS/PLMN 选择,在老文章中有讲过,
要注意的一点是,
当我们执行AT+CFUN=0关闭设备后,执行COPS查询指令,结果为+COPS:2,即mode=2 注销网络。之后实验中再执行AT+CFUN=1 打开射频,COPS查询结果不变,还是注销状态。只有执行AT+COPS=0后,才会注册网络,COPS才能查询到有效信息,相关验证过程,
AT+CFUN=0
OKAT+COPS?
+COPS:2
OKAT+CFUN=1
OK
AT+COPS?
+COPS:2
OKAT+COPS=0
OK
+QDTLSSTAT:0
+QLWEVTIND:0
+QLWEVTIND:3AT+COPS?
+COPS:0,2,"46000"
OK
接下来,我们回到boudica150源码中,
static bool_t boudica120_set_plmn(const char *plmn) {if (NULL != plmn) {ret = boudica120_atcmd_response("AT+COPS?\r","+COPS",resp,64);...//which means we need to set itif((false == ret)||(NULL == strstr(resp,cmd))) {(void) snprintf(cmd,63,"AT+COPS=1,2,\"%s\"\r",plmn);}//HCIP-Lab源码中,走的就是这个分支else {(void) snprintf(cmd,64,"AT+COPS=0\r");ret = boudica150_atcmd(cmd,"OK");}...
}//实际上该宏在代码中并未被使用,//初始化函数中s_boudica150_oc_cb.plmn = NULL;
#define CONFIG_BOUDICA150_PLMN "460001"
在源码中,BOUDICA150_PLMN,被配置为46001,这是中国联通,好在源码中并没有使用,否则就又多一个乌龙。前文我们已经谈到过,我使用的SIM卡其是46008中国移动物联网号段,COPS查询到的国家代码和运营商代码是46000,中国移动。
一开始我没有注意到 NULL != plmn 的判断,还担心了会,因为我知道,
C99/C11标准(§7.21.6.5)和C++标准均未明确定义%s接收NULL时的行为,属于未定义行为,这是非常危险的。
好在虚惊一场,_set_plmn处理了plmn是空的情况,且boudica150初始化函数中,使用的就是NULL。在此场景下,上述函数就是执行了一次 AT+COPS=0,这与我们在PC-AT实验中的方式是一致的。
我们也要看懂手动模式的PLMN选择,其3个参数如下,
_set_apn / AT+CGDCONT
该过程名为设置APN。APN 即 Access Point Name,接入点名称。 APN是移动通信网络中的一个关键参数,用于标识设备接入运营商数据网络的方式。它决定了设备如何连接到互联网、企业专网或其他数据服务,并影响IP地址分配、计费策略和网络服务质量。
源码中出现的 _APN “cmnet” 是未被使用的,即使用了也不对。CMNET 是中国移动(China Mobile)的传统移动互联网APN,主要用于2G/3G/4G数据业务,而非专为NB-IoT设计,可能以前是能用的,现在不行了估计。
AT+CGDCONT,我们打开BC28的AT指令使用手册,该指令看上去非常复杂,
从设置指令格式上看,源码中的格式似乎不咋对啊,在PC串口终端中进行尝试,也确实总返回ERROR。好在我们的源码中,apn字符串是被初始化为NULL的,与plmn被初始化为NULL交相呼应。在我们的源码中,本质上_set_apn函数啥也没干,太累不展开了。
(安全)接入参数 CDP+DTLS+PSK
参见 #<IoT/实现和分析 NB-IoT+DTLS+PSK 接入华为云物联网平台IoTDA过程,总结避坑攻略>#
AT+NNMI 设置新消息指示
在移远通信的AT指令手册中,这个指令的功能表述非常的烂,让人不好理解,甚至是错误的。一个修正后的表述如下,
该命令用于配置模组在接收到LwM2M平台下发的消息后,如何向主机(Host)发送新消息到达的指示。UE 从 LwM2M 物联网平台接收到一个下行消息后会发送新消息指示。这里,所谓的消息(可含数据),是指平台到NB通信模组的消息数据,而消息指示是模组对MCU主机的一种异步通信机制。NNMI 默认值为 1,模块重启后 会还原到默认值。
AT+NNMI=1 使能新消息指示和数据,会返回当前所有缓存的消息,格式为 +NNMI:,。
例如,+NNMI:5,48656C6C6F。
AT+NNMI=2 仅使能新消息指示,每次收到新的消息都会触发指示 URC,响应结果格式为:+NNMI。
可以通过命令 AT+NMGR 接收缓存消息。
_check_netattach 网络附着状态
上述函数的本质是在循环执行 AT+CGATT? 查询操作,
我们在前文操作中执行了AT+CGATT=1,试图将 MT 附着于 PS 域,这个附着过程是需要时间的,_check_netattach是在等待成功。这里重点关注上图中红色标注的部分,其表明在网络附着一旦成功后,将自动执行 AT+COPS=0,即
AT+COPS=0 和 AT+CGATT=1
按照源码中的接入流程,先被执行的是 AT+COPS=0 自动选择和注册 EPS 网络运营商,之后又执行了一次 AT+CGATT=1 将 MT 附着于 PS 域,最后再循环执行AT+CGATT?查询用户设备网络附着状态。同时,我们在CGATT的指令备注中可以看到,AT+CGATT=1 的过程会自动选择 AT+COPS=0。实际上,在NB-IoT模组的网络注册流程中,AT+CGATT=1 和 AT+COPS=0 的功能既有重叠又有分工。AT+CGATT=1 设置工作的第一步是检查与COPS指令操作结果相关的EPS注册状态,若不符合,则会先执行 AT+COPS=0 指令。详细过程如下:
AT+COPS=0 的独立功能,仅完成EPS网络注册(无线接入网和核心网控制面),主要包含以下工作:
1、PLMN选择:自动搜索并注册优先级最高的运营商网络(如中国移动46000)。
2、RRC/NAS层连接:与基站(eNodeB)和MME完成控制面信令交互(鉴权、安全模式激活)。
结果状态对应如上两个功能:
AT+COPS? 返回运营商名称(如"CHINA MOBILE") AT+CEREG? 返回EPS注册状态(如+CEREG: 1,1表示已注册)
AT+CGATT=1 的本职功能,即 PS 域专属功能(扣除AT+COPS=0后)
1、PDP上下文激活:向PGW申请IP地址(依赖APN配置,如cmnbiot)。核心网建立SGW→PGW的数据通道(默认承载QCI=9)。
2、IP地址分配:动态获取IPv4/IPv6地址(可通过AT+CGPADDR查询)。
3、数据就绪状态:AT+CGACT?返回1,1(默认承载已激活)。模组可进行TCP/UDP通信(仍需APN正确)。
总结下来,设备接入运营商的主要的几个步骤,PLMN选择、EPS注册、PS附着、IP地址分配,大约流程如下:
其他补充:
PS域(Packet Switched Domain,分组交换域)是移动通信网络中用于处理数据业务的逻辑域,其核心特点是通过分组交换技术实现高效的数据传输。PS域采用分组交换(Packet Switching)机制,将数据拆分为多个带地址标识的“分组”(数据包),每个分组独立传输并通过最优路径到达目的地,接收端重新组装为完整数据。PS域并非物理上的地理区域,而是逻辑上的业务承载划分。其覆盖范围由核心网设备互联构成,用户通过无线接入网(如基站)接入PS域,实现数据业务的端到端传输。运营商需在核心网中部署PS域设备(如SGSN、GGSN、SGW/PGW等),并通过策略控制(如PCRF)实现流量管理、计费和服务质量(QoS)保障。用户通过AT+CGATT命令附着PS域时,终端与核心网完成鉴权(AT+COPS的作用)、IP地址分配等流程,建立数据会话(PDP上下文),从而接入互联网。
_check_observe网络附着状态
网络附着成功后,也即 CGATT查询到状态1后,即数据会话(PDP上下文)已经被成功建立。接下来设备就要真正去和物联网平台握手交互了啦,我最初的问题是,psk、dtls等配置信息是在哪个指令环节中使用的呢?AT+QREGSWT指令只是设置了一种平台注册模式,并没有真正启动注册过程。我们以自动触发模式为例,结合我对lwm2m_tiny(wifi通信时使用的)的已有的部分理解,
当网络附着CGATT成功后,NB模组内的固件程序就会,
通过CoAP协议向LwM2M服务器(如coap://lwm2m.iot.platform.com)发送Register请求,携带 Endpoint Client Name、Lifetime、dtls、psk 等参数。模组固件的实际代码实现我们暂时看不到,但我猜测,其与lwm2m_tiny源码的如下实现基本一致,
static int agent_add_objects(void *handle, atiny_param_t *lwm2m_params) {int ret = (int) ATINY_ERR;ret = agent_add_security_object(handle, lwm2m_params); // 安全对象(ID=0) //psk秘钥用在这里哦ret = agent_add_server_object(handle, lwm2m_params); // 服务器对象(ID=1)ret = agent_add_acc_ctrl_object(handle); // 访问控制对象(ID=2)ret = agent_add_device_object(handle); // 设备对象(ID=3)ret = agent_add_conn_m_object(handle); // 连接监控对象(ID=4)ret = agent_add_firmware_object(handle); // 固件对象(ID=5)ret = agent_add_location_object(handle); // 位置对象(ID=6)// 二进制应用数据对象(ID=19)ret = agent_add_binary_app_data_object(handle, &(lwm2m_params->server_params.storing_cnt)); return ret;
}
上述操作,添加了7种标准对象和1种扩展标准对象,具体如下表,
呢?在上述理解的基础上,我们再回到NB源码的 _check_observe 函数,就好理解多了,
//unit second
static bool_t boudica150_check_observe(int time) {//wait for the server observedbool_t ret = false; int times;for(times =0;times <time;times++ ) {if(s_boudica150_oc_cb.lwm2m_observe) {ret = true;break;}osal_task_sleep(1000);}return ret;
}
函数很简单,就是在循环着检查 s_boudica150_oc_cb.lwm2m_observe 标志字,该标记只在如下回调函数实现中赋值,
#define cn_urc_qlwevtind "\r\n+QLWEVTIND:"//wait for the lwm2m observe
static int urc_qlwevtind(void *args,void *msg,size_t len) { ...index_str = strlen(cn_urc_qlwevtind);if(len > index_str) {ind = data[index_str]-'0';LINK_LOG_DEBUG("GET THE LWM2M:ind:%d\n\r",ind);if(ind == 3) { //这是关键哦,s_boudica150_oc_cb.lwm2m_observe = true;}}return len;
}
urc_qlwevtind 函数是在 boudica150_boot 函数的一开始被注册的,它是urc消息处理函数,与前边我们进行NNMI设置密切相关。
urc_qlwevtind
在 #<IoT/基于NB28-A/BC28-CNV通信模组使用AT指令连接华为云IoTDA平台># 中,我们已经知道,
若设备注册平台成功,就会看到两条异步消息+QLWEVTIND:0和+QLWEVTIND:3,表明我们可以使用数据传输相关AT指令和平台进行通讯啦,平台设备也会变为在线。
URC(Unsolicited Result Code,非请求结果码)是AT指令通信中的一种异步通知机制,用于设备(如NB-IoT模组)主动向主机(MCU/处理器)推送状态变化或事件,无需主机主动查询。URC广泛应用于网络状态、短信接收、数据到达等场景。
+QLWEVTIND:0
通知主机,平台注册流程已启动,但尚未获得平台最终确认,主机应等待+QLWEVTIND:3(注册成功)后再发送业务数据。此时的底层状态大约是模组已附着PS域(AT+CGATT=1成功)、已解析平台地址。
+QLWEVTIND:3
通知主机,平台注册完成,可安全使用数据传输指令。此时底层的状态大约为,平台已分配资源路径(如/12345/0/1)、设备状态在平台显示为在线。LwM2M平台注册成功,模组已收到平台的最终确认(如2.01 Created响应)时,会触发此URC消息。
如下 NB-IoT-AT 代码实现,就是上述理论和工作机制的一种具体实践,
//
static bool_t boudica150_boot(const char *plmn, const char *apn, const char *bands,const char *server,const char *port) {at_oobregister("qlwevind",cn_urc_qlwevtind,strlen(cn_urc_qlwevtind),urc_qlwevtind,NULL);...
}//
int at_oobregister(const char *name,const void *index,size_t len,fn_at_oob func,void *args) {...oob->func = func;oob->args = args;
}//check if any out of band method could deal the data
static int __oob_match(void *data,size_t len) {int ret = -1;at_oob_item *oob;int i = 0;for(i =0;i<CONFIG_AT_OOBTABLEN;i++) {oob = &g_at_cb.oob[i];if((oob->func != NULL)&&(oob->index != NULL) && (0 == memcmp(oob->index,data,oob->len))) {ret = oob->func(oob->args,data,len); //执行回调函数urc_qlwevtind break;}}return ret;
}
//任务入口函数
static int __rcv_task_entry(void *args) {while(NULL != g_at_cb.devhandle) {if (1 == g_at_cb.streammode) {rcvlen += __resp_rcv(g_at_cb.rcvbuf+ rcvlen,CONFIG_AT_RECVMAXLEN,cn_osal_timeout_forever);...oobret = __oob_match(g_at_cb.rcvbuf,rcvlen);...
函数 __oob_match 实现在 \iot_link\at\at.c 文件中,而 _oob_match 函数在 __rcv_task_entry 接收任务入口函数中调用。上述接收任务器消息来源可能是串口数据接收任务或中断服务,这个问题我们在后续新文章中继续探讨。
总结和后记
boudica150_boot 函数上层相关的源码解析,请参考#<IoT/实现和分析 NB-IoT+DTLS+PSK 接入华为云物联网平台IoTDA过程,总结避坑攻略>#
boudica150_boot 函数下层相关的源码解析,请参考#<…>#