已开源的全部工具软件、源码、教程文档、视频都已经上传到网盘【内容一直在更新】。
https://pan.quark.cn/s/145a9b3f7f53
随着现代农业技术的发展,智能化管理在农业生产中扮演着越来越重要的角色。特别是在养殖业中,环境条件的好坏直接关系到养殖动物的生长状态和健康水平。因此,建立一个能够实时监控并调节养殖环境的系统显得尤为重要。这样的系统不仅可以提高养殖效率,减少人工成本,还可以通过精准控制环境参数来提升动物福利,进而保证产品质量。
基于此背景,本项目设计并实现一套基于STM32微控制器的养殖场环境监测系统。该系统将集成多种传感器用于采集环境数据,如温度、湿度和光照强度等关键参数。通过本地OLED显示屏,工作人员可以实时查看这些数据,确保养殖环境处于最佳状态。同时,考虑到现代养殖业对于远程管理和自动化的需求,系统还将配备ESP8266 WiFi模块,通过MQTT协议与华为云物联网平台进行通讯。这样一来,借助于在Windows平台上设计的上位机软件以及Android移动应用,管理者可以从任何地点远程监控环境状况,调整预设阈值,并且根据实际情况启动相应的设备,比如控制通风风扇、加湿器或照明灯具。
此外,系统的硬件选型也经过了深思熟虑。为了确保系统的稳定性和可靠性,采用了成熟可靠的传感器和技术方案,如DHT11温湿度传感器、BH1750光敏传感器以及通过继电器控制的5V风扇和加湿器模块。供电方面选择了常见的USB线-5V供电方式,既方便又节能。整个系统的开发不仅能够满足现代化养殖业的实际需求,同时也为未来进一步的技术升级提供了坚实的基础。
(1)实时环境监测
系统配备了DHT11温湿度传感器和BH1750光敏传感器,能够持续监测养殖区域内的温度、湿度以及光照强度等关键环境参数。这些数据的实时收集有助于及时发现环境变化,从而迅速采取措施调整环境条件。
(2)本地数据显示
通过OLED显示屏,系统能够在本地即时显示所监测到的各项环境参数。这使得工作人员无需依赖外部设备即可了解当前的环境状况,便于快速响应环境变化。
(3)远程监控与管理
系统内置了ESP8266 WiFi模块,允许它通过MQTT协议与华为云物联网平台建立连接。借助于该平台,用户可以利用Windows上位机软件或Android移动应用程序远程访问环境监测数据,实现了跨越地理限制的环境监控。此外,用户还可以通过上位机软件或移动应用设置环境参数的阈值,并接收超出阈值范围的告警通知。
(4)自动化控制
当环境监测数据超出预定的安全范围时,系统能够自动启动相应的设备进行环境调节。例如,当湿度超过预设值时,系统会自动开启通风风扇来降低湿度;当湿度低于设定值,则启动加湿器增加空气湿度;若光照不足,则自动开启灯光补充光源。这样,即使在无人值守的情况下,也能确保环境条件保持在适宜范围内。
(5)设备控制接口
系统设计了继电器控制接口来操作5V风扇和加湿器模块,确保了设备的安全启停。通过这种方式,不仅简化了硬件设计,而且提高了系统的可靠性和使用寿命。
【1】STM32微控制器:作为整个系统的主控单元,负责协调各个传感器的数据采集、处理逻辑运算以及控制指令的发送。
【2】DHT11温湿度传感器:用于实时监测养殖环境中的温度和湿度,并将数据传送给STM32进行处理。
【3】BH1750光敏传感器:用于检测养殖环境内的光照强度,为系统提供光照数据,以便做出相应的照明控制决策。
【4】OLED显示屏:用于在本地显示传感器采集到的环境数据,包括温度、湿度和光照强度等信息。
【5】ESP8266 WiFi模块:使系统能够接入无线网络,并通过MQTT协议与华为云物联网平台通信,实现远程数据传输。
【6】继电器模块:用于控制5V风扇和加湿器的开关,确保设备根据环境参数的变化自动启停。
【7】5V小风扇:当湿度超标时,通过继电器模块启动,以帮助降低环境湿度。
【8】5V加湿器模块:当湿度低于设定值时,由继电器模块控制开启,以增加环境湿度。
【9】LED照明系统:当光照强度低于阈值时,自动开启以补充光照,确保养殖环境的光照条件符合要求。
【10】USB供电模块:系统采用USB线5V供电方式,为整个系统提供稳定的电力供应,确保所有组件正常工作。
基于STM32设计的养殖场环境监测系统
支持的功能如下:
(1)支持对养殖环境中的温度、湿度、光照强度实时监测;
(2)本地通过0LED显示屏实时显示检测数据。
(3)能够通过WiFi模块通过MQTT协议连接华为云物联网平台,通过Qt设计Windows上位机和Android手机APP,能远程查看环境参数,设置阀值参数,控制通风风扇。
(4)当湿度超过阀值,通过风扇实现送风;当湿度低于值,利用加湿模块实现加湿;当光照强度低于阈值,打开灯;
硬件选型:
(1)WiFi模块采用ESP8266-WIFI模块
(2)光敏模块:采用BH1750光敏传感器
(3)风扇模块:采用5V小风扇,通过继电器控制开关。
(4)加湿器模块:采用5V加湿器模块,通过继电器控制开关。
(5)温湿度传感器:采用DHT11
(6)供电电源:采用USB线-5V供电。
设计思路的核心在于构建一个高效且易于维护的智能环境监测系统,通过自动化的手段来优化养殖环境的管理。
第一步,选择STM32作为主控芯片是因为其高性能与低功耗特性,能够很好地支持多任务处理,同时保证了系统的稳定运行。此外,STM32丰富的外设接口也方便了与其他传感器和执行机构的连接,为后续的功能扩展提供了便利。
针对环境参数的监测,项目选用了DHT11温湿度传感器和BH1750光敏传感器。DHT11因其高性价比和简单的接口协议被广泛应用于各种环境监测场景,而BH1750则以其高精度测量能力和良好的稳定性成为了光照强度检测的理想选择。这两类传感器的数据采集结果将通过STM32的ADC模块进行读取,并经由主控芯片内部的逻辑判断后,决定是否需要启动环境调节装置。
为了便于现场人员随时了解环境状况,设计中加入了OLED显示屏,用于直观展示传感器采集的数据。OLED屏幕的优势在于其清晰度高且视角宽广,即便是在光线较暗的环境中也能清晰地显示信息。
考虑到远程监控的重要性,系统引入了ESP8266 WiFi模块。通过WiFi模块,系统可以连接至互联网,并利用MQTT协议与华为云物联网平台进行数据交换。MQTT协议因其轻量级和高效性非常适合于物联网应用场景,能够有效支持大量的并发连接,确保数据的实时传输。借助云端服务,用户可以通过上位机软件或智能手机应用程序实时查看环境参数,并根据需要调整设置。
在执行机构的选择上,系统采用了5V小风扇和加湿器模块,并通过继电器进行控制。这种设计不仅简化了电路结构,还增强了系统的安全性和可靠性。当环境湿度或光照不符合预设标准时,系统会自动启动相应的设备来进行调节,确保环境始终处于最佳状态。
在供电方案上选择了USB线5V供电,这不仅简化了电源设计,降低了系统的复杂性,还使得系统更加节能环保。整体设计思路强调了系统集成度与自动化程度,力求通过最小的硬件配置实现最有效的环境管理。
功能类别 | 具体功能描述 |
---|---|
环境监测 | - 实时监测养殖环境中的温度、湿度及光照强度。 - 数据通过DHT11温湿度传感器和BH1750光敏传感器采集。 |
本地数据显示 | - 利用OLED显示屏实时显示各项环境参数。 |
远程监控 | - 通过ESP8266 WiFi模块连接互联网。 - 使用MQTT协议与华为云物联网平台进行数据交互。 |
远程管理 | - 在Windows上位机软件或Android应用程序上查看环境参数。 - 设置环境参数的阈值。 - 接收超出阈值的告警信息。 |
自动化控制 | - 当湿度超过设定阈值时,自动启动风扇降低湿度。 - 当湿度低于设定阈值时,启动加湿器增加湿度。 - 当光照强度低于设定阈值时,自动开启灯光补充光照。 |
设备控制接口 | - 通过继电器模块控制5V风扇和加湿器模块的开关。 |
供电系统 | - 采用USB线5V供电,确保系统的稳定运行。 |
STM32的编程语言选择C语言,C语言执行效率高,大学里主学的C语言,C语言编译出来的可执行文件最接近于机器码,汇编语言执行效率最高,但是汇编的移植性比较差,目前在一些操作系统内核里还有一些低配的单片机使用的较多,平常的单片机编程还是以C语言为主。C语言的执行效率仅次于汇编,语法理解简单、代码通用性强,也支持跨平台,在嵌入式底层、单片机编程里用的非常多,当前的设计就是采用C语言开发。
开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。目前作为MCU芯片开发的软件也不只是keil一家独大,IAR在MCU微处理器开发领域里也使用的非常多,IAR扩展性更强,也支持STM32开发,也支持其他芯片,比如:CC2530,51单片机的开发。从软件的使用上来讲,IAR比keil更加简洁,功能相对少一些。如果之前使用过keil,而且使用频率较多,已经习惯再使用IAR是有点不适应界面的。
上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA。
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
地址: https://www.huaweicloud.com/product/iothub.html
点击立即创建。
正在创建标准版实例,需要等待片刻。
创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。
在上面也能看到 免费单元的限制。
开通之后,点击总览,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。
总结:
端口号: MQTT (1883)| MQTTS (8883)
接入地址:ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
**根据域名地址得到IP地址信息: **
打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。
Microsoft Windows [版本 10.0.19045.4170]
(c) Microsoft Corporation。保留所有权利。
C:\Users\11266>ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=39ms TTL=93
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 35ms,最长 = 39ms,平均 = 36ms
C:\Users\11266>
MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。
根据自己产品名字填写,下面的设备类型选择自定义类型。
创建完成之后点击查看详情。
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
模型简单来说: 就是存放设备上传到云平台的数据。
你可以根据自己的产品进行创建。
比如:
烟雾可以叫 MQ2
温度可以叫 Temperature
湿度可以叫 humidity
火焰可以叫 flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。
先点击自定义模型。
再创建一个服务ID。
接着点击新增属性。
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
当前的设备是采用MQTT协议与华为云平台进行通信。
MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。
MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。
华为云的MQTT协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
业务流程:
描述 | 限制 |
---|---|
支持的MQTT协议版本 | 3.1.1 |
与标准MQTT协议的区别 | 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg |
MQTTS支持的安全等级 | 采用TCP通道基础 + TLS协议(最高TLSv1.3版本) |
单帐号每秒最大MQTT连接请求数 | 无限制 |
单个设备每分钟支持的最大MQTT连接数 | 1 |
单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 | 3KB/s |
MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 | 1MB |
MQTT连接心跳时间建议值 | 心跳时间限定为30至1200秒,推荐设置为120秒 |
产品是否支持自定义Topic | 支持 |
消息发布与订阅 | 设备只能对自己的Topic进行消息发布与订阅 |
每个订阅请求的最大订阅数 | 无限制 |
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。
这个操作称为:属性上报。
帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:
发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。
接下来介绍,华为云平台的MQTT三元组参数如何得到。
要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。
帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。
根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
MQTT服务器: 117.78.5.125
MQTT端口号: 183
//物联网服务器的设备信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//订阅与发布的主题
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //订阅
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //发布
发布的数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】获取项目凭证 ** 点击左上角用户名,选择下拉菜单里的我的凭证
项目凭证:
28add376c01e4a61ac8b621c714bf459
【2】创建IAM用户
鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。
点击左上角创建用户。
创建成功:
【3】创建完成
用户信息如下:
主用户名 l19504562721
IAM用户 ds_abc
密码 DS12345678
帮助文档:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
设备影子介绍:
设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性
简单来说:设备影子就是保存,设备最新上传的一次数据。
我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。
如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。
调试完成看右下角的响应体,就是返回的影子数据。
设备影子接口返回的数据如下:
{
"device_id": "663cb18871d845632a0912e7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 90,
"BH1750": 38,
"MQ135": 70
},
"event_time": "20240509T113448Z"
},
"version": 3
}
]
}
调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。
链接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。
使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。
Qt的中文官网: https://www.qt.io/zh-cn/
QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6
或者去网盘里下载:https://pan.quark.cn/s/145a9b3f7f53
打开下载链接后选择下面的版本进行下载:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里勾选一个mingw 32编译器即可,其他的不管默认就行,直接点击下一步继续安装。
选择MinGW 32-bit 编译器: (一定要看清楚了)
说明: 我这里只是介绍PC端,也就是Windows系统下的Qt环境搭建。 Android的开发环境比较麻烦,如果想学习Android开发,想编译Android程序的APP,需要自己去搭建Android环境。
Android环境搭建的链接: https://www.cdsy.xyz/computer/programme/android/20201130/cd16067529525182.html
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
打开默认的界面如下:
根据自己需求设计界面。
点击软件左下角的绿色三角形按钮进行编译运行。
编译之后的效果:
如果想编译Android手机APP,必须要先自己配置好自己的Android环境。(搭建环境的过程可以自行百度搜索学习)
然后才可以进行下面的步骤。
创建完成。
Qt本身是跨平台的,直接选择Android的编译器,就可以将程序编译到Android平台。
然后点击构建。
成功之后,在目录下可以看到生成的apk文件,也就是Android手机的安装包,电脑端使用QQ发送给手机QQ,手机登录QQ接收,就能直接安装。
生成的apk的目录在哪里呢? 编译完成之后,在控制台会输出APK文件的路径。
知道目录在哪里之后,在Windows的文件资源管理器里,找到路径,具体看下图,找到生成的apk文件。
D:/linux-share-dir/QT/build-app_Huawei_Eco_tracking-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk
#include "MQTTClient.h"
#include <string.h>
#include <stdlib.h>
#if !defined(_WINDOWS)
#include <sys/time.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#define MAXHOSTNAMELEN 256
#define EAGAIN WSAEWOULDBLOCK
#define EINTR WSAEINTR
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#define ENOTCONN WSAENOTCONN
#define ECONNRESET WSAECONNRESET
#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
char* topics[] = {"TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA"};
char* wildtopics[] = {"TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#"};
char* nosubscribe_topics[] = {"nosubscribe",};
struct Options
{
char* connection; /**< connection to system under test. */
char* clientid1;
char* clientid2;
char* username;
char* password;
int verbose;
int MQTTVersion;
int iterations;
int run_dollar_topics_test;
int run_subscribe_failure_test;
} options =
{
"tcp://localhost:1883",
"myclientid",
"myclientid2",
NULL,
NULL,
0,
MQTTVERSION_3_1_1,
1,
0,
0,
};
void usage(void)
{
printf("options:\n connection, clientid1, clientid2, username, password, MQTTversion, iterations, verbose\n");
exit(EXIT_FAILURE);
}
void getopts(int argc, char** argv)
{
int count = 1;
while (count < argc)
{
if (strcmp(argv[count], "--dollar_topics_test") == 0 || strcmp(argv[count], "--$") == 0)
{
options.run_dollar_topics_test = 1;
printf("Running $ topics test\n");
}
else if (strcmp(argv[count], "--subscribe_failure_test") == 0 || strcmp(argv[count], "-s") == 0)
{
options.run_subscribe_failure_test = 1;
printf("Running subscribe failure test\n");
}
else if (strcmp(argv[count], "--connection") == 0)
{
if (++count < argc)
{
options.connection = argv[count];
printf("Setting connection to %s\n", options.connection);
}
else
usage();
}
else if (strcmp(argv[count], "--clientid1") == 0)
{
if (++count < argc)
{
options.clientid1 = argv[count];
printf("Setting clientid1 to %s\n", options.clientid1);
}
else
usage();
}
else if (strcmp(argv[count], "--clientid2") == 0)
{
if (++count < argc)
{
options.clientid2 = argv[count];
printf("Setting clientid2 to %s\n", options.clientid2);
}
else
usage();
}
else if (strcmp(argv[count], "--username") == 0)
{
if (++count < argc)
{
options.username = argv[count];
printf("Setting username to %s\n", options.username);
}
else
usage();
}
else if (strcmp(argv[count], "--password") == 0)
{
if (++count < argc)
{
options.password = argv[count];
printf("Setting password to %s\n", options.password);
}
else
usage();
}
else if (strcmp(argv[count], "--MQTTversion") == 0)
{
if (++count < argc)
{
options.MQTTVersion = atoi(argv[count]);
printf("Setting MQTT version to %d\n", options.MQTTVersion);
}
else
usage();
}
else if (strcmp(argv[count], "--iterations") == 0)
{
if (++count < argc)
{
options.iterations = atoi(argv[count]);
printf("Setting iterations to %d\n", options.iterations);
}
else
usage();
}
else if (strcmp(argv[count], "--verbose") == 0)
{
options.verbose = 1;
printf("\nSetting verbose on\n");
}
count++;
}
}
#if defined(_WIN32) || defined(_WINDOWS)
#define msleep Sleep
#define START_TIME_TYPE DWORD
static DWORD start_time = 0;
START_TIME_TYPE start_clock(void)
{
return GetTickCount();
}
#elif defined(AIX)
#define mqsleep sleep
#define START_TIME_TYPE struct timespec
START_TIME_TYPE start_clock(void)
{
static struct timespec start;
clock_gettime(CLOCK_REALTIME, &start);
return start;
}
#else
#define msleep(A) usleep(A*1000)
#define START_TIME_TYPE struct timeval
/* TODO - unused - remove? static struct timeval start_time; */
START_TIME_TYPE start_clock(void)
{
struct timeval start_time;
gettimeofday(&start_time, NULL);
return start_time;
}
#endif
#define LOGA_DEBUG 0
#define LOGA_INFO 1
#include <stdarg.h>
#include <time.h>
#include <sys/timeb.h>
void MyLog(int LOGA_level, char* format, ...)
{
static char msg_buf[256];
va_list args;
#if defined(_WIN32) || defined(_WINDOWS)
struct timeb ts;
#else
struct timeval ts;
#endif
struct tm timeinfo;
if (LOGA_level == LOGA_DEBUG && options.verbose == 0)
return;
#if defined(_WIN32) || defined(_WINDOWS)
ftime(&ts);
localtime_s(&timeinfo, &ts.time);
#else
gettimeofday(&ts, NULL);
localtime_r(&ts.tv_sec, &timeinfo);
#endif
strftime(msg_buf, 80, "%Y%m%d %H%M%S", &timeinfo);
#if defined(_WIN32) || defined(_WINDOWS)
sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm);
#else
sprintf(&msg_buf[strlen(msg_buf)], ".%.3lu ", ts.tv_usec / 1000L);
#endif
va_start(args, format);
vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args);
va_end(args);
printf("%s\n", msg_buf);
fflush(stdout);
}
int tests = 0;
int failures = 0;
void myassert(char* filename, int lineno, char* description, int value, char* format, ...)
{
++tests;
if (!value)
{
int count;
va_list args;
++failures;
printf("Assertion failed, file %s, line %d, description: %s\n", filename, lineno, description);
va_start(args, format);
count = vprintf(format, args);
va_end(args);
if (count)
printf("\n");
//cur_output += sprintf(cur_output, "<failure type=\"%s\">file %s, line %d </failure>\n",
// description, filename, lineno);
}
else
MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description);
}
#define assert(a, b, c, d) myassert(__FILE__, __LINE__, a, b, c, d)
#define assert1(a, b, c, d, e) myassert(__FILE__, __LINE__, a, b, c, d, e)
typedef struct
{
char* topicName;
int topicLen;
MQTTClient_message* m;
} messageStruct;
messageStruct messagesArrived[1000];
int messageCount = 0;
int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m)
{
messagesArrived[messageCount].topicName = topicName;
messagesArrived[messageCount].topicLen = topicLen;
messagesArrived[messageCount++].m = m;
MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.",
messageCount, topicName, m->payloadlen, (char*)(m->payload));
return 1;
}
void clearMessages(void)
{
int i;
for (i = 0; i < messageCount; ++i)
{
MQTTClient_free(messagesArrived[i].topicName);
MQTTClient_freeMessage(&messagesArrived[i].m);
}
messageCount = 0;
}
void cleanup(void)
{
// clean all client state
char* clientids[] = {options.clientid1, options.clientid2};
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Cleaning up");
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
for (i = 0; i < 2; ++i)
{
rc = MQTTClient_create(&aclient, options.connection, clientids[i], MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
}
// clean retained messages
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, "#", 0);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(2000); // wait for all retained messages to arrive
rc = MQTTClient_unsubscribe(aclient, "#");
assert("Good rc from unsubscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
for (i = 0; i < messageCount; ++i)
{
if (messagesArrived[i].m->retained)
{
MyLog(LOGA_INFO, "Deleting retained message for topic %s", (char*)messagesArrived[i].topicName);
rc = MQTTClient_publish(aclient, messagesArrived[i].topicName, 0, "", 0, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
}
}
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
clearMessages();
MyLog(LOGA_INFO, "Finished cleaning up");
}
int basic_test(void)
{
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Starting basic test");
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, topics[0], 0);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[0], 5, "qos 0", 0, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[0], 5, "qos 1", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[0], 5, "qos 2", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
rc = MQTTClient_disconnect(aclient, 10000);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("3 Messages received", messageCount == 3, "messageCount was %d", messageCount);
clearMessages();
/*opts.MQTTVersion = MQTTVERSION_3_1;
rc = MQTTClient_connect(aclient, &opts); // should fail - wrong protocol version
assert("Bad rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc);*/
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Basic test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int offline_message_queueing_test(void)
{
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MQTTClient bclient;
MyLog(LOGA_INFO, "Offline message queueing test");
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 0;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
opts.cleansession = 1;
rc = MQTTClient_connect(bclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(bclient, topics[1], 5, "qos 0", 0, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(bclient, topics[2], 5, "qos 1", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(bclient, topics[3], 5, "qos 2", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(2000);
rc = MQTTClient_disconnect(bclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&bclient);
opts.cleansession = 0;
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000); // receive the queued messages
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
assert("2 or 3 messages received", messageCount == 3 || messageCount == 2, "messageCount was %d", messageCount);
MyLog(LOGA_INFO, "This server %s queueing QoS 0 messages for offline clients", (messageCount == 3) ? "is" : "is not");
clearMessages();
MyLog(LOGA_INFO, "Offline message queueing test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int retained_message_test(void)
{
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Retained message test");
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);
// set retained messages
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[1], 5, "qos 0", 0, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[2], 5, "qos 1", 1, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], 5, "qos 2", 2, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(2000);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("3 messages received", messageCount == 3, "messageCount was %d", messageCount);
for (i = 0; i < messageCount; ++i)
{
assert("messages should be retained", messagesArrived[i].m->retained, "retained was %d",
messagesArrived[i].m->retained);
MQTTClient_free(messagesArrived[i].topicName);
MQTTClient_freeMessage(&messagesArrived[i].m);
}
messageCount = 0;
// clear retained messages
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[1], 0, "", 0, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[2], 0, "", 1, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], 0, "", 2, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(200); // wait for QoS 2 exchange to be completed
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(200);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Retained message test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
#define SOCKET_ERROR -1
int test6_socket_error(char* aString, int sock)
{
#if defined(_WIN32)
int errno;
#endif
#if defined(_WIN32)
errno = WSAGetLastError();
#endif
if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK)
{
if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET))
printf("Socket error %d in %s for socket %d", errno, aString, sock);
}
return errno;
}
int test6_socket_close(int socket)
{
int rc;
#if defined(_WIN32)
if (shutdown(socket, SD_BOTH) == SOCKET_ERROR)
test6_socket_error("shutdown", socket);
if ((rc = closesocket(socket)) == SOCKET_ERROR)
test6_socket_error("close", socket);
#else
if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR)
test6_socket_error("shutdown", socket);
if ((rc = close(socket)) == SOCKET_ERROR)
test6_socket_error("close", socket);
#endif
return rc;
}
typedef struct
{
int socket;
time_t lastContact;
#if defined(OPENSSL)
SSL* ssl;
SSL_CTX* ctx;
#endif
} networkHandles;
typedef struct
{
char* clientID; /**< the string id of the client */
char* username; /**< MQTT v3.1 user name */
char* password; /**< MQTT v3.1 password */
unsigned int cleansession : 1; /**< MQTT clean session flag */
unsigned int connected : 1; /**< whether it is currently connected */
unsigned int good : 1; /**< if we have an error on the socket we turn this off */
unsigned int ping_outstanding : 1;
int connect_state : 4;
networkHandles net;
/* ... */
} Clients;
typedef struct
{
char* serverURI;
Clients* c;
MQTTClient_connectionLost* cl;
MQTTClient_messageArrived* ma;
MQTTClient_deliveryComplete* dc;
void* context;
int connect_sem;
int rc; /* getsockopt return code in connect */
int connack_sem;
int suback_sem;
int unsuback_sem;
void* pack;
} MQTTClients;
int will_message_test(void)
{
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer;
MQTTClient aclient, bclient;
MyLog(LOGA_INFO, "Will message test");
tests = failures = 0;
opts.keepAliveInterval = 2;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
opts.will = &wopts;
opts.will->message = "client not disconnected";
opts.will->qos = 1;
opts.will->retained = 0;
opts.will->topicName = topics[2];
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
opts.keepAliveInterval = 20;
opts.will = NULL;
rc = MQTTClient_connect(bclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(bclient, topics[2], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(100);
test6_socket_close(((MQTTClients*)aclient)->c->net.socket);
while (messageCount == 0 && ++count < 10)
msleep(1000);
rc = MQTTClient_disconnect(bclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&bclient);
assert("will message received", messageCount == 1, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Will message test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int overlapping_subscriptions_test(void)
{
/* overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
the server may send back one message with the highest QoS of any matching subscription, or one message for
each subscription with a matching QoS. */
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
char* topicList[] = {wildtopics[6], wildtopics[0]};
int qosList[] = {2, 1};
MyLog(LOGA_INFO, "Starting overlapping subscriptions test");
clearMessages();
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribeMany(aclient, 2, topicList, qosList);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], strlen("overlapping topic filters") + 1,
"overlapping topic filters", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
assert("1 or 2 messages received", messageCount == 1 || messageCount == 2, "messageCount was %d", messageCount);
if (messageCount == 1)
{
MyLog(LOGA_INFO, "This server is publishing one message for all matching overlapping subscriptions, not one for each.");
assert("QoS should be 2", messagesArrived[0].m->qos == 2, "QoS was %d", messagesArrived[0].m->qos);
}
else
{
MyLog(LOGA_INFO, "This server is publishing one message per each matching overlapping subscription.");
assert1("QoSs should be 1 and 2",
(messagesArrived[0].m->qos == 2 && messagesArrived[1].m->qos == 1) ||
(messagesArrived[0].m->qos == 1 && messagesArrived[1].m->qos == 2),
"QoSs were %d %d", messagesArrived[0].m->qos, messagesArrived[1].m->qos);
}
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Overlapping subscription test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int keepalive_test(void)
{
/* keepalive processing. We should be kicked off by the server if we don't send or receive any data, and don't send
any pings either. */
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer;
MQTTClient aclient, bclient;
MyLog(LOGA_INFO, "Starting keepalive test");
tests = failures = 0;
clearMessages();
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
opts.will = &wopts;
opts.will->message = "keepalive expiry";
opts.will->qos = 1;
opts.will->retained = 0;
opts.will->topicName = topics[4];
opts.keepAliveInterval = 20;
rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(bclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(bclient, topics[4], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
opts.keepAliveInterval = 2;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
while (messageCount == 0 && ++count < 20)
msleep(1000);
rc = MQTTClient_disconnect(bclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("Should have will message", messageCount == 1, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Keepalive test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int redelivery_on_reconnect_test(void)
{
/* redelivery on reconnect. When a QoS 1 or 2 exchange has not been completed, the server should retry the
appropriate MQTT packets */
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Starting redelivery on reconnect test");
tests = failures = 0;
clearMessages();
opts.keepAliveInterval = 0;
opts.cleansession = 0;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, wildtopics[6], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_yield();
// no background processing because no callback has been set
rc = MQTTClient_publish(aclient, topics[1], 6, "qos 1", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], 6, "qos 2", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 0);
assert("No messages should have been received yet", messageCount == 0, "messageCount was %d", messageCount);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
while (messageCount < 2 && ++count < 5)
msleep(1000);
assert("Should have 2 messages", messageCount == 2, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Redelivery on reconnect test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int zero_length_clientid_test(void)
{
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Starting zero length clientid test");
tests = failures = 0;
clearMessages();
opts.keepAliveInterval = 0;
opts.cleansession = 0;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, "", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("rc 2 from connect", rc == 2, "rc was %d", rc); // this should always fail
opts.cleansession = 1;
rc = MQTTClient_connect(aclient, &opts);
assert("Connack rc should be 0 or 2", rc == MQTTCLIENT_SUCCESS || rc == 2, "rc was %d", rc);
MyLog(LOGA_INFO, "This server %s support zero length clientids", (rc == 2) ? "does not" : "does");
if (rc == MQTTCLIENT_SUCCESS)
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Zero length clientid test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int dollar_topics_test(void)
{
/* $ topics. The specification says that a topic filter which starts with a wildcard does not match topic names that
begin with a $. Publishing to a topic which starts with a $ may not be allowed on some servers (which is entirely valid),
so this test will not work and should be omitted in that case.
*/
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
char dollartopic[20];
MyLog(LOGA_INFO, "Starting $ topics test");
sprintf(dollartopic, "$%s", topics[1]);
clearMessages();
opts.keepAliveInterval = 5;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000); // wait for any retained messages, hopefully
clearMessages();
rc = MQTTClient_publish(aclient, topics[1], 20, "not sent to dollar topic", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, dollartopic, 20, "sent to dollar topic", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
assert("Should have 1 message", messageCount == 1, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "$ topics test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int subscribe_failure_test(void)
{
/* Subscribe failure. A new feature of MQTT 3.1.1 is the ability to send back negative reponses to subscribe
requests. One way of doing this is to subscribe to a topic which is not allowed to be subscribed to.
*/
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
int subqos = 2;
MyLog(LOGA_INFO, "Starting subscribe failure test");
clearMessages();
opts.keepAliveInterval = 5;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribeMany(aclient, 1, &nosubscribe_topics[0], &subqos);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("0x80 rc from subscribe", subqos == 0x80, "subqos was %d", subqos);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Subscribe failure test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int main(int argc, char** argv)
{
int i;
int all_failures = 0;
getopts(argc, argv);
for (i = 0; i < options.iterations; ++i)
{
cleanup();
all_failures += basic_test() +
offline_message_queueing_test() +
retained_message_test() +
will_message_test() +
overlapping_subscriptions_test() +
keepalive_test() +
redelivery_on_reconnect_test() +
zero_length_clientid_test();
if (options.run_dollar_topics_test)
all_failures += dollar_topics_test();
if (options.run_subscribe_failure_test)
all_failures += subscribe_failure_test();
}
MyLog(LOGA_INFO, "Test suite %s", (all_failures == 0) ? "succeeded" : "failed");
}
#include "dht11.h"
#include "delay.h"
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
delay_us(30); //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN();//SET INPUT
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()==0)
{
for(i=0;i<5;i++)//读取40位数据
{
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi=buf[0];
*temp=buf[2];
}
}else return 1;
return 0;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
RCC->APB2ENR|=1<<8; //使能PORTG口时钟
GPIOG->CRH&=0XFFFF0FFF;//PORTG.11 推挽输出
GPIOG->CRH|=0X00003000;
GPIOG->ODR|=1<<11; //输出1
DHT11_Rst();
return DHT11_Check();
}
#include "adc.h"
#include "delay.h"
//初始化ADC1
//这里我们仅以规则通道为例
//我们默认仅开启通道1
void Adc_Init(void)
{
//先初始化IO口
RCC->APB2ENR|=1<<2; //使能PORTA口时钟
GPIOA->CRL&=0XFFFFFF0F;//PA1 anolog输入
RCC->APB2ENR|=1<<9; //ADC1时钟使能
RCC->APB2RSTR|=1<<9; //ADC1复位
RCC->APB2RSTR&=~(1<<9);//复位结束
RCC->CFGR&=~(3<<14); //分频因子清零
//SYSCLK/DIV2=12M ADC时钟设置为12M,ADC最大时钟不能超过14M!
//否则将导致ADC准确度下降!
RCC->CFGR|=2<<14;
ADC1->CR1&=0XF0FFFF; //工作模式清零
ADC1->CR1|=0<<16; //独立工作模式
ADC1->CR1&=~(1<<8); //非扫描模式
ADC1->CR2&=~(1<<1); //单次转换模式
ADC1->CR2&=~(7<<17);
ADC1->CR2|=7<<17; //软件控制转换
ADC1->CR2|=1<<20; //使用用外部触发(SWSTART)!!! 必须使用一个事件来触发
ADC1->CR2&=~(1<<11); //右对齐
ADC1->CR2|=1<<23; //使能温度传感器
ADC1->SQR1&=~(0XF<<20);
ADC1->SQR1|=0<<20; //1个转换在规则序列中 也就是只转换规则序列1
//设置通道1的采样时间
ADC1->SMPR2&=~(3*1); //通道1采样时间清空
ADC1->SMPR2|=7<<(3*1); //通道1 239.5周期,提高采样时间可以提高精确度
ADC1->SMPR1&=~(7<<3*6);//清除通道16原来的设置
ADC1->SMPR1|=7<<(3*6); //通道16 239.5周期,提高采样时间可以提高精确度
ADC1->CR2|=1<<0; //开启AD转换器
ADC1->CR2|=1<<3; //使能复位校准
while(ADC1->CR2&1<<3); //等待校准结束
//该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。
ADC1->CR2|=1<<2; //开启AD校准
while(ADC1->CR2&1<<2); //等待校准结束
//该位由软件设置以开始校准,并在校准结束时由硬件清除
}
//获得ADC1某个通道的值
//ch:通道值 0~16
//返回值:转换结果
u16 Get_Adc(u8 ch)
{
//设置转换序列
ADC1->SQR3&=0XFFFFFFE0;//规则序列1 通道ch
ADC1->SQR3|=ch;
ADC1->CR2|=1<<22; //启动规则转换通道
while(!(ADC1->SR&1<<1));//等待转换结束
return ADC1->DR; //返回adc值
}
//获取通道ch的转换值,取times次,然后平均
//ch:通道编号
//times:获取次数
//返回值:通道ch的times次转换结果平均值
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
//得到温度值
//返回值:温度值(扩大了100倍,单位:℃.)
short Get_Temprate(void)
{
u32 adcx;
short result;
double temperate;
adcx=Get_Adc_Average(ADC_CH_TEMP,20); //读取通道16,20次取平均
temperate=(float)adcx*(3.3/4096); //电压值
temperate=(1.43-temperate)/0.0043+25; //转换为温度值
result=temperate*=100; //扩大100倍.
return result;
}
//初始化ADC3
//这里我们仅以规则通道为例
//我们默认仅开启通道6
void Adc3_Init(void)
{
RCC->APB2ENR|=1<<15; //ADC3时钟使能
RCC->APB2RSTR|=1<<15; //ADC复位
RCC->APB2RSTR&=~(1<<15);//复位结束
RCC->CFGR&=~(3<<14); //分频因子清零
//SYSCLK/DIV2=12M ADC时钟设置为12M,ADC最大时钟不能超过14M!
//否则将导致ADC准确度下降!
RCC->CFGR|=2<<14;
ADC3->CR1&=0XF0FFFF; //工作模式清零
ADC3->CR1|=0<<16; //独立工作模式
ADC3->CR1&=~(1<<8); //非扫描模式
ADC3->CR2&=~(1<<1); //单次转换模式
ADC3->CR2&=~(7<<17);
ADC3->CR2|=7<<17; //软件控制转换
ADC3->CR2|=1<<20; //使用用外部触发(SWSTART)!!! 必须使用一个事件来触发
ADC3->CR2&=~(1<<11); //右对齐
ADC3->SQR1&=~(0XF<<20);
ADC3->SQR1|=0<<20; //1个转换在规则序列中 也就是只转换规则序列1
//设置通道1的采样时间
ADC3->SMPR2&=~(7<<(3*6));//通道6采样时间清空
ADC3->SMPR2|=7<<(3*6); //通道6 239.5个周期,提高采样时间可以提高精确度
ADC3->CR2|=1<<0; //开启AD转换器
ADC3->CR2|=1<<3; //使能复位校准
while(ADC1->CR2&1<<3); //等待校准结束
//该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。
ADC3->CR2|=1<<2; //开启AD校准
while(ADC3->CR2&1<<2); //等待校准结束
//该位由软件设置以开始校准,并在校准结束时由硬件清除
}
//获得ADC3某个通道的值
//ch:通道值 0~16
//返回值:转换结果
u16 Get_Adc3(u8 ch)
{
//设置转换序列
ADC3->SQR3&=0XFFFFFFE0;//规则序列1 通道ch
ADC3->SQR3|=ch;
ADC3->CR2|=1<<22; //启动规则转换通道
while(!(ADC3->SR&1<<1));//等待转换结束
return ADC3->DR; //返回adc值
}
本项目致力于开发一款基于STM32微控制器的智能养殖场环境监测系统。该系统集成了DHT11温湿度传感器、BH1750光敏传感器等,用于实时采集养殖环境中的温度、湿度及光照强度数据,并通过OLED显示屏提供本地数据显示功能。为了实现远程监控与管理,系统还配备了ESP8266 WiFi模块,利用MQTT协议与华为云物联网平台连接,使得用户可以通过定制的Windows上位机软件或Android应用程序随时随地访问环境数据,设定报警阈值,并远程控制通风风扇、加湿器和灯光等设备,以确保养殖环境维持在最优状态。系统采用USB线5V供电,确保了运行的便捷性和能耗的有效控制。
总体而言,这一环境监测系统通过智能化手段提升养殖场的管理水平,降低运营成本,同时提高动物的生长质量和健康水平。