开发者学堂课程【基于STM32的端到端物联网全栈开发:LinkKit SDK 接入阿里云物联网平台(4)】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/574/detail/7944
LinkKit SDK 接入阿里云物联网平台(4)
目录:
一、项目例程软件架构
二、网络数据流
三、三个属性
四、用户代码如何调用 MQTT API
一、项目例程软件架构
1.项目例程的软件架构图
蓝框是 CubeMX 在生成工程时侯自动拷贝生成好的,绿框内是由后来用户自己拷贝文件并手动添加到工程里,它们在整个项目里的逻辑层次如此图所示。本项目历程是基于 SDK 节点设备连接阿里云平台,主要的应用逻辑是联网这一块,核心部分就是 Linkkit C-SDK 和 mbedTLS 的使用
Linkkit C-SDK 向下通过 ST 提供安全网络通信抽象层调用底层的 WIFI 驱动实现网络数据的通信
向上提供 MQTT 协议通信 API,给用户业务逻辑层完成业务功能。
二、网络数据流
1.介绍代码中通信部分的分层结构发送和接受方向的数据流传递。
这里列出的是各个软件层中主要对应的c文件以及他们之间的调用关系。
左边的文件和右边的软件层通过颜色对应,
用户的业务程序是来自蓝色部分,主要是
在 main.c 和 mqtt_example.c 文件中实现,main.c 是整个历程项目的初始化,先是硬件 ip 初始化,然后用这些初始化好的通信接口去初始化 wifi。
三个用户任务中,最主要的连接阿里云 lp 平台是在
mqtt_example.c 中实现,调用下面的 mqtt_client.c 提供的 mqtt 进行连接,发布,操作。
其中 mqtt 的报文格式按照阿里云平台高级版中规定的协议进行打包和解析数据,在这个文件里用户还要实现自己的订阅回调函数。
橙色部分可以看做网络通信的中间介层,其中大部分是由 linkkit c_sdk 实现,它通过 HAL_TLS_mbedtls.c 来把自己的 mqtt 和网络通信管理架在 TLS 上,最后由 mbedtls_net.c 来处理安全网络通信协议站到具体网络介质访问层的适配,本项目历程采用的是 WiFi 模块联网,但是可以看到在这样的软件架构下面很容易把 WiFi 通信方式切换到蜂窝通信或者以太网通信等。
2.数据从应用层到网络层到驱动层涉及到的相关原文件分别处于工程里不同文件组里如图所示
本页和下一页的颜色注意一下,在这里橙色表示来自阿里的开源代码,蓝色表示来自 STM 的开源代码,绿色表示来自其他第三方的开源代码。
按照他们在工程里的文件组的排列,从上到下分别是 Application 分组下 Ali 子分组中 linkkit c_sdk 需要适配到本地的三个文件之一的 iot_HAL_TLS_mbedtls.c 按照颜色规则蓝色字体,橙色方框说明这是在 linkkit c_sdk 中由阿里封装好的 apa 接口,但是由 st 在本节点设备上做的适配。
然后是 Application 分组下 Share 分组中的 mbedtls_net.c 和 WiFi 子分组下的三个WiFi驱动文件。
接下来 Middlewares 分组下的 Ali 的 misc 中 utils_net.c 负责网络通信。
mqtt 子目录下所有文件是 mqtt 协议的客户端实现以及对阿里平台上 mqtt 服务器的连接适配。
最后 Middlewares 分组下 Mbedtls 子分组是开源协议。
3.这六组文件按照逻辑彼此调用的关系如图
最上层是 mqtt_client.c 向用户应用层提供的 mqtt 接口及
IOT_MQTT_XXX。
最下面的是WiFi驱动抽象层,WiFi.C 和具体使用的 WiFi 模块3080的驱动文件但凡网络通信无非涉及到初始化,连接,发送数据,接受数据这四大操作。
在 IOT_MQTT_Construct 中会先执行初始化部分,它通过 iotx_mc_init()走到网络抽象管理层的 iotx_net_init()中,在这里为网络操作做初始化,包括要访问的服务器,地址,端口号服务器证书。然后就是连接、断开连接、读、写四大函数成员的赋值,称之为功能函数的注册。
Construct 的第二部分任务就是进行 MQTT 通道的连接,通过 iotx_mc_connect()pClient,它先调用 ipstack 的成员函数,及 iotx_net_connect 来进行连接握手。
在组建好 mqttconnect 报文后,来调用 Write 成员函数及 utils_net_write()来把报文发出去。
读取数据是根据周期性调用 Yield,它主要是在 iotx_mc_read_packet()中来调用 ipstack 的 read 成员函数。
而发布和订阅这样的报文最终都是在 iotx_mc_send_packet()中调用 ipstack 的 write 成员函数。及刚才注册的utils_net_write。
由此可见阿里 Linkkit C-SDK 中 mqtt 协议对下操作的都是同属 Linkkit C-SDK 中的 utils_net.c 里定义好的。
对于不太安全的连接,和使用 TLS 保证安全连接这两种情况下,使用的是两套函数去实现。
这个项目历程采用的是 TLS 安全连接,因此 iotx_net_xxx 和 utils_net_xxx 调用的是 connect、read、write_ssl 这一套然后再落到 HAL_SSL_XXX 这一套上。这是 Linkkit C-SDK 需要用户自己实现的一套 API。
会在 iot_HAL_TLS_Mbedtls.c 中实现,对 ssl 层的操作,初始化读,写被抽象成了 HAL_SSL_XXX 这一套 API,在HAL_SSL_Establish 里面 ssl_client_init 执行 Mbedtls_net_init 和 connect,同时把 mbedtls_net 两个读写操作函数注册到 ssl_client 上,所有函数名称是以 mbedtls_net 开头的函数都是在 mbedtls_net.c 中来实现,这样就把mbedtls 协议向下到 net 落到具体的 WiFi 收发驱动上面。
当来自 HAL_SSL_Read 的调用经过 Mbedtls 走到ssl所指向的 f_recv_timeout,而当来自
HAL_SSL_Write 调用经过 Mbedtls 协议站走到 ssl 所指向的 f_send。实际就会调用刚才注册的 mbedtls_net_send。
4.用户业务程序如何搭建在 Linkkit C-SDK 的 mqtt 的 API 上面的。
在之前的课程指南中提到,本历程项目作为节点设备的第二种连接场景的实现,在阿里云 IoT 平台,使用物管理的高级版,因此节点设备在平台是按照物模型来创建的,物模型是对设备在云端的功能描述。
包括设备具有什么样的属性,设备需要相应什么样的服务,设备会产生或者汇报什么样的事件。
物联网平台通过一种物的描述语言,来采用格式描述物模型,节点会使用组装和上报设备的数据。使用物模型的好处之一就是一旦物模型确定了,连接阿里云 IOT 平台的数据前半程开发,和阿里云 IOT 平台到用户服务器的数据后半程开发。
三、三个属性
1.三个属性
在阿里云平台上创建产品后可以导出物模型文件,这个文件描述了设备具有的属性,可以响应的服务,以及会产生或者汇报的事件。这个文件给到嵌入式开发者和 web 开发者上下游开发者就可以同时来到开发数据的上半程应用和下半程应用了,根据对物模型文件的解析,可以知道这个节点的设备有三个属性,分别是当前温度、当前湿度、温度阈值。
它们各自是否可读可写,以及他们属性数值的类型,它们的单位最大最小值,在建立该产品时,都已经指定好了,在这个物模型导出文件中也一一都有反应。
2.两个事件
事件是指从节点设备端往阿里云 iot 平台发出的,在本历程项目的连接场景中,有两种节点段向上报告的事件。
一个是每五秒一次定时的属性上报,看到右边的思维导图里,first 节点及后面的分支节点就是对该事件的描述。
可以看到 outputData 是有内容的说明该事件是要携带数据负载上去,就是设备的三个属性的当前值,及协议中规定的在 mqtt 报文的 message 字段需要填充的内容,如截图里面,而这里的 message 就是后面要使用 mqtt 协议进行发布时的消息主题。
second 节点及后面的分支节点描述的是连接场景的节点端向云端上报的第二种事件,及在当前温度超过阈值时发出的警报,它的 outputData 是没有内容的说明该事件无需携带数据负载,在协议中规定的 mqtt 报文的 message 部分里面,字段不需要填充内容。如第二个截图里红框部分。同样的,字段字符串是节点要上报该消息所需要发布的消息主题。
3.三个服务
服务可以理解为由阿里云 IoT 平台向下发出的并且需要节点响应的命令,可以理解成一个云端需要设备去执行函数input 是输入参数,output 是输出参数,当设备的属性设置好以后,系统会自动生成两个服务,一个是设置设备属性,一个是读取设备属性。
在连接场景中,用户可以从应用服务器通过阿里云平台来设置节点设备的温度阈值,因此会用到第一个设置属性的服务,也就是节点端要响应来自云端发出的该命令,因此需要在 mqtt 应用中来定义 method 这个字符串所代表的消息主题。
历程项目中节点是定时来上报属性的,并不实现由云端随时读取节点功能,因此第二个服务可以不管。
第三个服务是用户从服务器解除警报,比如表示管理者已经知道报警事件,派人查看维修,那么解除警报表示已经对改报警做出应答,这是我们的场景需要做出的应答,以此需要处理这个命令及节点设备要订阅该主题以已在接收到命令后去做相应的节点段处理。它的 input 和 output 都没有内容,说明该服务没有参数也无需返回输出,而 method 字符串是节点设备要订阅的消息主题响应云端下发的解除警报的命令。该命令也是不带数据负载。
4.通信模型
mqtt 发布或者接收的 mqtt 报文中,message 部分按照物模型使用tsl来造,paramt 部分是真正的数据负载,按照之前对事件服务的拆析,温度报警事件和解除警报这个云端起用的服务,对应的 outputDate 和 inputDate 为空,因此这里的 params 也是为空。
四、用户代码如何调用MQTT API
1.用代码如何调用 MQTT API
这些和业务相关的 mqtt 协议的元素包括主题 massage 内容确定好之后,来看如何在应用层面上使用 linkket SDK 的API,在历程中 Middlewares 下 Ali 源代码包,在 protocol 下的 mqtt_client.c,封装了一系列的 mqtt 操作 API 都是以 IOT_MQTT 字符串开头。
整个 MQTT 业务是由在.c 中建立的 Alilotkit 中 Thread 来执行的,它调用 cloud,test.cloud_test 在调用mqtt_client_example 这两个函数都是在 mqtt.c 中定义的。到了 mqtt_client_example 就是按业务逻辑来依次调用mqtt 的 API,按照 linkket SDK 指导,先要调用 IOT_SetupConnlnfo 来根据设备在阿里云平台上面的三元组转化成该设备进行 mqtt 连接时的几个关键词,然后构造并发送 linkket SDK 报文,mqtt 连接建立后订阅两个服务对应的消息主题,分别是设置温度属性和解除警报。然后在定时发布上报属性这个报文,并在温度超限时发布温度报警这个报文。
为了及时收到订阅来自云端下的消息以及由节点发起的操作对应的云端下发的响应。
还要周期性的来调用 IOT_MQTT_Yield 对应的 API。
2.这是 mqtt_example.c 中使用 mqtt api 的详细步骤。
在调用 Setup 之前通过三个 get 函数来从闪存中读出设备的三元组信息,然后 Setup 就可以计算该设备连接阿里云iot 平台的 mqtt 服务器的连接参数。这些参数连同 linkkit sdk 中保存的阿里云服务器证书一起传递给 mqtt 用作随后的 mqtt construct 这个重要的 API 调用参数。
这里面主要有三大操作,首先是 iotx_mc_init 先是注册几个网络操作函数包括连接、断开、读、写到 utils_net.c 层中的 disconnect_ssl、read_ssl、Write_ssl。
接着 iotx_mc_content 主要工作是注册 Mbedtls 所指向的读写来到 Mbedtls.c 中 Mbedtls_net_recv_timeout 并执行刚注册好的网络连接和网络发送。
最后 iotx_report_devinfo 主要工作是执行网络读写操作来发送 mqtt publish 报文来发布信息,这里发布的不是用户主导的业务信息而是它自己收集的设备信息,比如版本号,WiFi 模块等。
后面在 mqtt_example.c 中调用 MQTT API 就比较简单,在应用层先通过 Subscribe 来订阅两个主题,分别是云端向节点设备设置属性,实际只取里面的温度阈值这个属性,另外一个订阅的主题是云端下发的解除警报。在网络层就是调用 ssl 来发出 mqtt_Subscribe 这个报文包。
随后是用户应用主动的业务报文发布分别是发布当前设备属性值和发布温度报警事件通知服务器。
在网络层也是调用 ssl cend 来发出报文包,一个客户端它可惜没有地域或者没有发布,但是不能没有 iot_mqtt 应用的调用它主要负责两个工作,发送 mqtt 来保持连接,检测到断开连接后会执行重连的操作并且通过调用来接收网络下行的数据。如果主题匹配表示刚才订阅的主题对应的消息已经到来。就要执行 handle 的 h_fp 指针所指向的函数及刚才用户设置的回调函数。
3.MQTT 订阅消息的回调函数
客户端订阅某个主题后,对该主题消息的处理是通过回调函数进行的,每个主题都有对应的回调函数。在调用 mqtt subscribe 函数来订阅某个主题的时候,就需要指定对应的回调函数。
从前面可以看到,里面的订阅了两个业务主题时候,都注册的是 them a message arrived 函数来作为回调处理,首先它分辨是哪个主题的消息到了。如果是解除警报的通知,就去操作红灯,若是更新当前节点的温度报警阈值,就去读取消息,报文里面 message 几个部分的负载,提取出来新的预值,再写到缓存里里面。
同时比较这个新的预值和当前节点温度的高低来决定是否需要向用户服务器发出报警事件,这个绿色的虚线框代表了两个事件,是这两个主题消息收到后可能会做个节点操作。由于本例程引入了弗拉托色控制,LED 灯和节点报警都不是在本函数里面处理,而仅仅是在这里历史出对应的同步信号量,来让负责处理该事件的任务,在下次被调用的时候可以做相应操作。
4.用户输入运行参数
Demo 运行过程中有一些参数是需要保存在 flash 里面的,比如通过串口输入的 WiFi 配网参数或者三元组信息,还有通过网络从云端温度报警阈值的设置。
在 lot_flash_conflig.h 中定义了保存这些信息的数据结构体。user conflig 这个结构体,如果需要保存更多的信息或者去掉补贴信息,要在这里修改结构体的定义,并在 flash 这个点中去添加或者删除对应的操作。
5.Demo 参数存储
MCU 有2M 的 flash,在双 bank 的模式下,最小擦除的配置是4K,自己为了保证在更新用户程序时,不会将这些用户擦除,在操作用户参数的时候也不会影响用户程序,所以需要将其固定在某个 flash 区域,在内存里面取了整个flash 的最末尾的32K 来作为用户参数的存储蓄,在区域带 L 的历史文件中进行定义。
指定的这个区域地址是0408EF800,它的大小是32K,区域的名字叫做 fixed location。它在代码 lot_flash_conflig.c中,在定义保存用户参数的变量时,指定将其放在这个区域里面。
6.Sensor 数据的读取
Sensor 数据的读取由两个部分组成,一个是 sensor 的驱动,由 cubemx 自动添加到自动生成的初始工程里面。
在图中所示的这个初始工程里面可以看到 BSP 分组下面绿框出的两个文件就是 HTS21这颗温湿度传感器的驱动,里面有传感器的初始化温度传感器,湿度传感器,读温度值,读湿度值等。
Application 分组下面 User 分组里面篮框框出的两个文件,Application 是由 cube- memsl 自动生成的两个应用层API,用户需要在这两个函数体里面去填充代码来实现自己的功能。
另外一个 customas.C 文件是在配置 mems 驱动插件时根据用户使用的端口来自动生成的 hts221传感器的底层驱动。
比较最终交付的 IAR 历程项目可以看到,我们需要手动增加一个文件,文件名由用户自己取。里面实现的是和业务逻辑有关的三个操作,它们是用来被 max maps in it 和 imax maps process 所调用的。
这张照片整理出来的是和 Sener 数据操作有关的三部分文件的业务关系,蓝色的绿色分别是第76页中看到的,由 QX自动生成的H7S21温湿度传感器的逻辑驱动IC总线驱动应用层 API 集合文件。红色就是第77页中讲到的需要用户自己编写的部分,可以看到,它是被 Sensor 插件的两个应用层 API 来调用,然后自己本身。
就会调用 QMX 生成的传感线逻辑驱动和 FC 总线驱动来实现业务逻辑及通过 FC 总线去读取温度值,湿度值并保存在全局变量中。已被后续阿里 LT 任务周期性的将他们上报。还有就是根据当前的节点温度值和温度阈值来释放三个同步信号量,以同步阿里 LT 任务和 led 任务中的相关操作。MX_MEMS_Process 底层 API 是在 Sensor task 中被启动的。
7.Sensor_CTRL 任务
之前规划的的用法,在第46页照片中见过这个三个任务和五个信号量之间的关系图,这里把 Sensor_CTRL 这个任务无关的信号量都淡化,留下的是和它相关的内容。
可以看到 Sensor_CTRL 这个 task 会根据不同的条件释放出三个信号量,而这三个信号量又是被另外两个 task 还在等待。
process 这个顶层 API 是在 Sensor task 被启动的,而 mx map 里面的这个底层 API 是在 main 函数中被调用的,左边是生成的初始 AI 工程,右边是用户基于此的最终交付工程。红框里是我们后续自己在 main 函数里面添加的代码。
8.项目例程流程图
该项目例程启动初始化到进入用户业务逻辑的流程。
注意一下这个颜色规则,蓝色的都是自动生成的代码,红色的是自己添加的,最开始是从 main.C 里面的 main 函数开始执行,依次是 HL lnit 系统是都配置,然后是几个用到的外侧的初始化,GPIO 随机数产生器。都是自动生成的代码,接下来历程会输出开始运行这样的打印信息,这是自己添加的。初始化蓝色它也是系统生成的。WiFi 模块初始化是由于这个3080的驱动还没有像温湿度传感器那样以插件形式集成在 tobe max 中,因此,初始化代码需要我们自己去编写,并添加到这个位置和 max 模块初始化放在一起,因为它都属于扩展版的初始化。
在 freeRTOS 中会建立同步化信号量建立任务,都是由 MX 生成的,因为关键元素都在 MX 设置好了三个任务具体的函数名称,也是之前在 MX 中输入的依次在这里,只需在在三个任务对应的执行函数里添加代码。
一但 main 函数启动了,三个任务就可以运行起来了。前一页更多是从哪些代码是 MX 自动生成,哪些是用户后续添加的。
这页更多从功能的逻辑性角度来整理,初始部分所有外设软件模块的初始化包括 WiFi 模块初始化然后连接到 WiFi 热点并获取 ip 地址,直到启动了 FREERTOS 结束所有的初始化部分。
三个任务在调度下跑了起来,它先进行设备的身份认证然后连接到阿里云 iot 平台,去订阅业务主题。根据当前温度值判断是否需要温度报警,在此会条件性的释放两个 A/B 这两个同步信号量,从全局变量里读出当前温湿度值发送到云端同时释放 E 这个信号量。
注意 Sensor 本身的操作不是在阿里云,而是在 Sensor_CTRL 中去做。
读取云端下发的消息,这里也会条件性的释放 ABD 三个同步信号量,可以去结合第46页对 ABCDE 这五个同步信号量的详细介绍来理解这里的任务间同步。Ali_lot_kit 任务基本每五秒释放依一次。
LED_CTRL 任务就是控制红灯状态,闪一次绿灯点亮红灯,闪烁红灯,熄灭红灯,它们分别是等待 ABDE 这四个同步的信号量。
Sensor_CTRl 任务是根据当前的温度值和用户设置的温度阈值去条件性的释放 ABC 三个同步信号量。
9.内存占用
来看主要功能模块对存储区域的使用情况,橙色部分是阿里的 Linkkit c_SDK 占用了约45k 的闪存。绿色的是第三方软件 Mbedtls 协议站,它需要约55k 闪存容量,占整个项目 flash 容量的大头,同样绿色表示第三方 freeRTOS,它主要是使用的 ram 区域,有13k,主要是给任务分配的。而蓝色是 STM32L4HAL 驱动,大约12k。
对比第三张第二节的连接方式。这两种方案的存储资源占用情况,显然第二种方式更适合于资源丰富的设备,它通过Mbedtls 保证了阿里云的安全双向连接,但是 Mbedtls 的使用代价是引用了约55k 的 flash 占用量,FREERTOS 的使用主要是对 RTOS 的使用增加,用了大约13k 的 RTOS 因此对于资源受限的节点设备是使用不带 TOS 连接更加合适。