一、前言
已开源的全部源码、教程文档、视频、工具软件都已经上传到网盘【内容一直在更新】。
https://pan.quark.cn/s/145a9b3f7f53
1.1 项目介绍
【1】项目开发背景
随着现代农业技术的不断发展,智能化管理已经成为提高农业生产效率、优化资源利用的重要手段。特别是在大棚育苗领域,由于环境因素对植物生长有着至关重要的影响,因此开发一套能够实时监控并调节生长环境条件的大棚育苗管理系统显得尤为必要。这样的系统不仅有助于提升作物产量和质量,还能有效降低因环境条件不当而导致的损失。
本项目设计一款基于STM32微控制器与华为云IoT平台结合的大棚育苗管理系统,将集成多种传感器来监测土壤及环境的关键参数,包括温度、湿度、光照强度以及空气质量等,为植物提供最佳生长条件。通过使用先进的物联网技术,该系统能够在本地或远程实现对灌溉系统的精准控制,以满足不同区域植物的具体水分需求,从而达到节水增效的目的。此外,考虑到用户友好性和便捷性,计划开发适用于Android手机和平板电脑的应用程序以及Windows操作系统的桌面客户端,使得管理者无论身处何地都能轻松掌握大棚内的情况,并进行必要的调整。
选择STM32F103RCT6作为主控芯片是基于其强大的处理能力与丰富的外设支持;而华为云IoT则提供了稳定可靠的云端服务,确保了数据的安全传输与高效管理。配合上NBIOT-BC26模块提供的低功耗广域网络连接能力,即使是在偏远地区也能保持良好的通信状态。整个方案的设计充分考虑到了成本效益比,力求在保证功能全面性的基础上尽可能减少初期投入与后期维护费用,使更多中小型农业企业也能够享受到智慧农业带来的好处。
【2】设计实现的功能
(1)环境监测:系统集成了多种传感器来实时监测大棚内的关键环境参数,如土壤温度、土壤湿度、环境温度、环境湿度、光照强度以及空气质量等。这些数据不仅可以在本地LCD显示屏上实时显示,还会通过NBIOT-BC26模块经由MQTT协议上传至华为云IoT平台,确保管理者能够随时掌握大棚内的情况。
(2)灌溉控制:为保证植物得到适宜的水分供给,系统配备了4个电磁阀分别对应四个不同的灌溉区域。用户既可以通过本地设置的按钮手动启动或停止灌溉过程,也可以利用远程控制功能,即通过Android手机应用程序或Windows电脑上的专用软件来操作这四路灌溉系统的开关状态,从而实现对各区域浇水的精细化管理。
(3)远程监控与管理:基于华为云IoT服务的支持,所有收集到的数据都会被安全地存储于云端,并可通过互联网访问。为此开发了跨平台的应用程序——既有针对移动用户的Android版APP也有适用于PC用户的Qt C++桌面应用,两者均提供了直观友好的界面让用户可以方便地查看历史记录、当前状况报告及趋势分析图表,同时还具备发送指令给现场设备的能力,比如调整灌溉计划或是设定报警阈值等。
(4)自动化运行与异常警报:系统支持根据预设条件自动执行相应动作,例如当检测到土壤过于干燥时自动开启灌溉;同时设有预警机制,一旦某个指标超出正常范围就会立即向管理员发出通知,帮助及时采取措施避免潜在损害。
【3】项目硬件模块组成
(1)主控芯片 - STM32F103RCT6:
- 作为整个系统的核心处理器,STM32F103RCT6负责数据处理、逻辑控制以及与各个传感器和执行器之间的通信。它具有足够的计算能力和丰富的外设接口,能够高效地管理各种传感器数据并控制灌溉电磁阀。
(2)环境光照强度检测 - BH1750:
- BH1750是一种数字光强传感器,用于测量大棚内的光照强度。该传感器通过I2C接口与主控芯片连接,提供准确的光照数据,有助于优化植物生长所需的光照条件。
(3)环境温湿度检测 - SHT30:
- SHT30是一个高精度的温湿度传感器,采用I2C通信方式,可以精确测量大棚内部的温度和相对湿度。这对于维持适宜的生长环境至关重要。
(4)LCD显示屏 - 1.44寸SPI协议显示屏:
- 一块小型的1.44英寸彩色LCD显示屏,使用SPI接口与微控制器相连,用来显示当前的传感器读数和其他重要信息,便于现场工作人员直接查看。
(5)空气质量检测 - MQ135传感器:
- MQ135是一种气体传感器,主要用于监测空气中的有害气体浓度,如二氧化碳等。虽然其精度不如专业设备,但在成本效益方面表现出色,适合于初步空气质量评估。
(6)土壤湿度检测 - ADC模拟量接口的土壤湿度传感器:
- 土壤湿度传感器通过ADC(模数转换器)接口与STM32连接,用以监测土壤中的水分含量。这对于确定何时启动灌溉非常关键。
(7)联网模块 - NBIOT-BC26:
- BC26是一款低功耗广域网络(LPWAN)NBIoT模块,支持窄带物联网技术。它允许系统通过移动网络将采集的数据上传至华为云IoT平台,并接收来自云端的命令,确保即使在偏远地区也能保持稳定的通信。
(8)继电器模块:
- 系统中配置了4个继电器,分别控制4个电磁阀。这些继电器由STM32根据需要开启或关闭,从而控制不同区域的灌溉。
(9)电源供应:
- 整个系统由外部5V 2A直流电源供电,确保所有组件都能稳定运行。选择合适的稳压电路来保护电子元件免受电压波动的影响。
(10)用户交互界面 - 按钮:
- 在本地设置有按钮,供操作人员手动控制灌溉系统,提供了基本的本地控制选项。
1.2 设计思路
本项目的设计思路围绕着提高大棚育苗管理的智能化水平和效率展开,创建一个集成环境监测、自动化控制以及远程管理功能于一体的综合系统。设计之初,明确了几个关键目标:实现对大棚内环境参数的全面监控;确保灌溉系统的精准控制以优化水资源利用;提供用户友好的远程访问接口以便于随时随地进行管理和调整。
为了达成这些目标,选择了STM32F103RCT6作为主控芯片,该微控制器以其强大的处理能力和丰富的外设支持而著称,非常适合用来构建复杂的嵌入式系统。通过整合多种传感器(如BH1750光强传感器、SHT30温湿度传感器等),系统能够实时采集并分析土壤及环境数据,从而为植物生长创造最佳条件。此外,考虑到农业应用中可能遇到的通信挑战,采用了NBIoT-BC26模块来保证即使在信号覆盖较弱的地区也能保持稳定的网络连接,使得所有收集到的信息都能够及时上传至华为云IoT平台,并通过MQTT协议进行安全的数据传输。
在灌溉控制方面,系统不仅支持本地手动操作,还允许通过云端下发指令实现远程自动调节。四个独立控制的电磁阀分别对应不同的灌溉区域,这样的设计既提高了灌溉的灵活性也增强了系统的可扩展性。同时,为了方便现场工作人员查看当前状态或执行简单设置,在设备上安装了一个小型LCD显示屏用于显示重要信息。
最后,在软件层面,开发了基于Qt框架的跨平台应用程序,包括Android手机端APP和Windows桌面客户端。这些应用程序提供了直观的操作界面,使用户可以轻松地获取最新数据、查看历史趋势、接收警报通知以及发送控制命令。整个设计过程注重用户体验与实际需求相结合,力求打造一个既高效又易于使用的智能大棚管理系统,助力现代农业向着更加精细化、智能化的方向发展。
1.3 系统功能总结
功能模块 | 描述 |
---|---|
环境监测 | - 实时监测土壤温度、湿度 - 监测环境温度、湿度 - 测量光照强度 - 检测空气质量 |
数据记录与显示 | - 本地1.44寸SPI LCD显示屏实时显示传感器数据 - 通过NBIoT-BC26模块将数据上传至华为云IoT平台 |
远程监控 | - 通过Android手机APP和Windows上位机软件查看实时及历史数据 - 数据趋势分析与可视化 |
灌溉控制 | - 本地按钮手动控制四个区域的电磁阀进行补水 - 远程控制四个区域的电磁阀进行补水 |
网络通信 | - NBIoT-BC26模块连接华为云IoT物联网服务器 - 使用MQTT协议进行数据传输 |
用户界面 | - Android手机应用程序(基于Qt C++) - Windows桌面客户端(基于Qt C++),用于远程监控与控制 |
电源管理 | - 外部5V 2A直流电源供电,确保系统稳定运行 |
异常警报 | - 当检测到环境参数超出预设范围时,向用户发送警报通知 |
自动化操作 | - 根据预设条件自动开启或关闭灌溉系统,如土壤湿度低于阈值时自动启动灌溉 |
1.4 开发工具的选择
【1】设备端开发
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是有点不适应界面的。
【2】上位机开发
上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。
1.5 模块的技术详情介绍
【1】NBIOT-BC26模块
NBIoT-BC26模块是由Quectel(移远通信)生产的一款低功耗广域网络(LPWAN)模块,专为窄带物联网(Narrowband Internet of Things, NB-IoT)应用设计。这款模块具有体积小、功耗低、覆盖范围广等优点,非常适合用于远程监控、智能计量、资产跟踪、环境监测等多种物联网应用场景。
BC26模块支持NB-IoT标准,这是3GPP定义的一种专门针对物联网应用的蜂窝技术。与传统的2G/3G/4G网络相比,NB-IoT提供了更好的室内覆盖能力、更低的功耗以及更高的连接密度。这使得BC26模块能够在信号较弱或难以到达的地方保持稳定的数据传输。此外,NB-IoT还支持更深的穿透力和更长的电池寿命,这对于部署在偏远地区或需要长时间运行的设备尤为重要。
该模块采用LGA封装,尺寸仅为19.9 x 23.6 x 2.2 mm,便于集成到各种小型化设备中。BC26模块支持多种频段,包括B1/B3/B5/B8/B20/B28等全球主流频段,确保了广泛的地域兼容性。模块内置了多种协议栈,如CoAP、TCP/UDP、MQTT等,简化了数据传输和远程控制的应用开发。通过这些协议栈,开发者可以轻松实现设备与云端服务器之间的通信,进行数据上报和命令下发。
在接口方面,BC26模块提供了丰富的外设接口,包括UART、GPIO、ADC、SIM卡接口等。这些接口使得模块能够方便地与其他传感器、控制器和其他外围设备进行连接。例如,通过UART接口,模块可以直接与微控制器进行通信;通过GPIO接口,可以控制外部设备或读取开关状态;而SIM卡接口则允许使用标准SIM卡或eSIM卡进行网络认证。
为了进一步降低功耗,BC26模块支持多种省电模式,如PSM(Power Saving Mode)和eDRX(Extended Discontinuous Reception)。这些模式可以在不需要持续数据传输时显著降低功耗,延长电池寿命。在PSM模式下,模块可以进入深度睡眠状态,仅在预定的时间间隔内唤醒以检查是否有新的数据传输需求。而在eDRX模式下,模块会定期监听网络消息,但相比于传统DRX模式,其监听间隔更长,从而降低了功耗。
BC26模块还具备强大的安全特性,支持多种加密算法和协议,如TLS/SSL、AES等,确保数据传输的安全性和完整性。此外,模块支持FOTA(Firmware Over-The-Air)更新功能,可以通过无线方式对固件进行升级,提高了系统的可维护性和安全性。
NBIoT-BC26模块以其小巧的体积、低功耗、广泛的网络覆盖以及丰富的接口支持,成为物联网应用中的理想选择。无论是用于智能农业、环境监测、智能城市还是其他需要长期可靠连接的应用场景,BC26都能提供稳定高效的数据传输解决方案。
【2】MQ135传感器
MQ135 传感器是一种常用于检测多种气体的气敏元件,它特别适用于检测有害气体如一氧化碳(CO)、氨气(NH3)、甲烷(CH4)以及其他一些有机挥发性化合物。这种传感器因其成本低廉、使用方便而广泛应用于家庭安全、空气质量监测以及工业环境监控等领域。
MQ135 传感器的工作原理基于金属氧化物半导体(MOS)技术。其核心是一个由二氧化锡(SnO2)制成的敏感层,该材料在清洁空气中具有较高的电阻值。当周围环境中存在被测气体时,这些气体会与敏感层表面发生化学反应,导致敏感层的电阻发生变化。这种电阻的变化与气体浓度成正比关系,通过测量电阻值就可以推断出气体的浓度。通常,传感器会连接一个加热器来保持敏感层在一个特定的温度下工作,这个温度对于提高传感器的灵敏度和响应速度至关重要。
在实际应用中,MQ135 传感器需要配合电路来完成信号处理。常见的做法是将传感器接入一个简单的分压电路中,然后利用微控制器上的模拟输入端口读取电压变化。由于传感器输出的是模拟信号,因此需要进行适当的放大和滤波处理,以确保数据的准确性和稳定性。此外,为了得到更精确的结果,还需要对传感器进行校准,这通常涉及到在已知气体浓度条件下采集样本并建立相应的转换模型。
需要注意的是,虽然MQ135能够感知多种气体,但它并不具备区分不同气体的能力。也就是说,如果环境中同时存在几种不同的气体,那么最终读数将是所有气体共同作用的结果。因此,在使用MQ135进行空气质量监测时,通常会结合其他类型的传感器或采用特定算法来提高识别精度。例如,在育苗管理系统中,可以结合温湿度等其他环境参数,综合判断空气的质量状况。
MQ135 传感器凭借其低成本、易于集成的特点,成为了许多应用场景中的理想选择。不过,正确地配置和使用传感器对于获取可靠的数据同样重要,特别是在复杂多变的环境下,合理的电路设计和有效的数据分析方法都是必不可少的。
【4】SHT30传感器
SHT30是一款高精度的数字温湿度传感器,由瑞士Sensirion公司生产。这款传感器广泛应用于各种环境监测系统中,如智能家居、工业控制、气象站以及农业监控等领域。SHT30以其卓越的性能、低功耗和易于集成的特点而受到用户的青睐。
SHT30传感器采用了先进的CMOSens®技术,将温度和湿度传感元件与信号处理电路集成在同一个芯片上,从而确保了高度的可靠性和长期稳定性。该传感器提供I2C接口,方便与微控制器或其他主控设备进行通信。其测量范围广泛,温度测量范围为-40°C至+125°C,湿度测量范围为0%至100% RH(相对湿度)。此外,SHT30具有很高的测量精度,典型情况下,温度精度可达±0.3°C,湿度精度可达±2% RH。
SHT30传感器具备多种功能以提高测量质量和用户体验。例如,它支持用户自定义的测量频率和分辨率设置,允许根据具体应用需求调整传感器的工作模式。传感器还内置了加热器,可用于检测结露或除湿等特殊应用场景。此外,SHT30具有自动校准功能,能够在长时间运行后保持测量数据的准确性,减少了维护成本。
为了适应不同的环境条件,SHT30传感器采用了坚固的设计,能够抵抗灰尘、水汽和其他污染物的影响。其紧凑的尺寸(2.5 x 2.5 x 0.9 mm)使其易于集成到空间受限的应用中。同时,SHT30还提供了多种封装选项,包括标准表面贴装(SMD)封装和平板式封装,满足不同安装要求。
SHT30传感器的低功耗特性也是其一大亮点,这使得它非常适合电池供电的应用。在正常工作模式下,传感器的平均电流消耗仅为0.5 μA至2.8 μA,具体取决于所选的测量频率和分辨率。此外,SHT30还支持睡眠模式,在不进行测量时进一步降低功耗,延长电池寿命。
SHT30传感器凭借其高精度、宽测量范围、易用性以及低功耗等特点,成为众多需要精确温湿度测量应用的理想选择。无论是用于家庭自动化中的舒适度监测,还是工业环境中的过程控制,SHT30都能提供可靠的数据支持。
【5】B1750传感器
BH1750 是一款高精度的数字光强度传感器,广泛应用于需要精确测量环境光照强度的各种场合。这款传感器由ROHM Semiconductor公司生产,以其低功耗、高灵敏度和易于集成的特点而受到欢迎。它能够检测从0.5 lux到65,535 lux(即0.5 lx至65535 lx)范围内的光照强度,这覆盖了从非常暗到明亮阳光下的多种光照条件。
BH1750 传感器的核心是一个光电二极管,它能将接收到的光信号转换成电信号。该传感器内部集成了一个16位ADC(模数转换器),可以将光电二极管产生的电流信号转换为数字信号。这种设计使得BH1750 能够提供高度准确的数字输出,无需外部ADC或其他复杂的信号处理电路。传感器支持I2C通信协议,这意味着它可以轻松地与各种微控制器或单片机连接,如STM32、Arduino等。
在使用BH1750 时,用户可以通过I2C接口设置不同的工作模式来适应具体的应用需求。例如,可以选择连续高分辨率模式(1 lux分辨率)、连续高灵敏度模式(0.5 lux分辨率)或者一次性的测量模式。此外,还可以调整测量时间以平衡功耗和响应速度。这些灵活的配置选项使BH1750 成为了适用于不同应用场景的理想选择。
BH1750 的另一个优点是其低功耗特性。在正常工作状态下,它的平均电流消耗仅为120 μA左右,而在待机模式下,电流消耗可降至0.5 μA以下。这对于电池供电设备来说尤其重要,因为它有助于延长电池寿命。同时,由于采用了表面贴装技术(SMT),传感器的尺寸非常紧凑,适合集成在小型化的产品中。
在实际应用中,BH1750 常用于智能家居系统中的自动调光、户外显示屏亮度调节、农业温室光照监测以及移动设备中的屏幕亮度自动调节等功能。通过实时监测光照强度,系统可以根据当前的光照条件自动调整相关参数,从而提高用户体验并节省能源。
BH1750 是一种高性能且易于使用的数字光强度传感器,它提供了广泛的光照范围测量能力,并具有低功耗和小尺寸的优点。无论是对于专业开发者还是业余爱好者,BH1750 都是一个理想的选择,能够满足多种光照监测的需求。
二、硬件选型
2.1 STM32开发板
链接:https://detail.tmall.com/item.htm?id=540109077095&skuId=4456080806080&spm=a1z0d.6639537.1997196601.4.69157484Ospeps
主控CPU采用STM32F103RCT6,这颗芯片包括48 KB SRAM、256 KB Flash、2个基本定时器、4个通用定时器、2个高级定时器、51个通用IO口、5个串口、2个DMA控制器、3个SPI、2个I2C、1个USB、1个CAN、3个12位ADC、1个12位DAC、1个SDIO接口,芯片属于大容量类型,配置较高,整体符合硬件选型设计。当前选择的这款开发板自带了一个1.4寸的TFT-LCD彩屏,可以显示当前传感器数据以及一些运行状态信息。
2.2 USB下载线
2.3 电源扩展接口(x2)
买电源扩展板,方便扩展5v电源 和 3.3V电源。
2.4 SHT30温湿度模块
2.5 NBIOT模块
2.6 BH1750光敏传感器
链接:https://detail.tmall.com/item.htm?abbucket=3&id=543135220776&ns=1&spm=a230r.1.14.16.d7597d40XRn0Ec
2.7 土壤湿度传感器
2.8 单片机TTL转RS485模块
2.9 继电器模块(4个)
2.10 杜邦线(2排)
2.11 MQ135 空气质量传感器
2.12 稳压模块
2.13 电源插头
2.14 PCB版
2.15 红外测温模块
2.16 电磁阀
三、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA
。
3.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
3.2 开通物联网服务
地址: 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端口连接华为云物联网平台。
3.3 创建产品
(1)创建产品
(2)填写产品信息
根据自己产品名字填写,下面的设备类型选择自定义类型。
(3)产品创建成功
创建完成之后点击查看详情。
(4)添加自定义模型
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
模型简单来说: 就是存放设备上传到云平台的数据。
你可以根据自己的产品进行创建。
比如:
烟雾可以叫 MQ2
温度可以叫 Temperature
湿度可以叫 humidity
火焰可以叫 flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。
先点击自定义模型。
再创建一个服务ID。
接着点击新增属性。
3.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
(4)设备创建完成
(5)设备详情
3.5 MQTT协议主题订阅与发布
(1)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
业务流程:
(2)华为云平台MQTT协议使用限制
描述 | 限制 |
---|---|
支持的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进行消息发布与订阅 |
每个订阅请求的最大订阅数 | 无限制 |
(3)主题订阅格式
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主题发布格式
对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。
这个操作称为:属性上报。
帮助文档地址: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}}]}
3.6 MQTT三元组
MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。
接下来介绍,华为云平台的MQTT三元组参数如何得到。
(1)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
(2)生成MQTT三元组
华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
3.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
(1)填入登录信息
打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
(3)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}}]}
3.8 创建IAM账户
创建一个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
3.9 获取影子数据
帮助文档: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接口获取设备上传的数据,进行可视化显示,以及远程控制设备。
4.1 Qt开发环境安装
Qt的中文官网: https://www.qt.io/zh-cn/
QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6
打开下载链接后选择下面的版本进行下载:
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环境。
也可以看下面这篇文章,不过这个文章是在Qt开发专栏里付费的,需要订阅专栏才可以看。 如果不想付费看,也可以自行找其他教程,自己搭建好必须的环境就行了
Android环境搭建的博客链接: https://blog.csdn.net/xiaolong1126626497/article/details/117254453
4.2 新建上位机工程
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
4.3 设计UI界面与工程配置
【1】打开UI文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
4.4 编译Windows上位机
点击软件左下角的绿色三角形按钮进行编译运行。
编译之后的效果:
4.5 配置Android环境
如果想编译Android手机APP,必须要先自己配置好自己的Android环境。(搭建环境的过程可以自行百度搜索学习)
然后才可以进行下面的步骤。
【1】选择Android编译器
【2】创建Android配置文件
创建完成。
【3】配置Android图标与名称
【3】编译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
五、 BC26-NBIOT模块调试过程
5.1 模块调试接线
5.2 测试模块
第一步接上之后,串口调试助手选择波特率为115200,勾选软件上的发送新行选项。发送AT
过去,正常模块会返回OK
。
只有收到了OK,才表示模块工作正常。
5.3 上电初始化操作
【1】查询模块是否正常
AT
OK
【2】获取卡号,查询卡是否插好
AT+CIMI
460041052911195
OK
【3】激活网络
AT+CGATT=1
OK
【4】获取网络激活状态
AT+CGATT?
+CGATT: 1
OK
【5】查询网络质量
AT+CSQ
+CSQ: 26,0
OK
【6】 检查网络状态
AT+CEREG=? //
+CEREG: 0,1 //找网成功
OK
5.4 开启GPS定位
如果需要使用GPS定位就开,不需要使用就不用管。
使用GPS定位还需要将模块上的GPS天线接好,否则也是没有信号的。
官方文档:
【1】激活GPS,要等一段时间
AT+QGNSSC=1
OK
【2】查询激活状态,1表示成功激活
AT+QGNSSC?
+QGNSSC: 1
OK
【3】获取一次GPS定位语句
AT+QGNSSRD="NMEA/RMC"
+QGNSSRD: $GNRMC,120715.00,A,3150.78179,N,11711.93433,E,0.000,,310818,,,A,V*19
OK
六、STM32代码开发
6.1 BC26-NBIOT配置代码
#include "ec20.h"
#include "stdlib.h"
#include "string.h"
#include "usart.h"
#include "iwdg.h"
int errcount = 0;
char atstr[BUFLEN];
char AtStrBuf[BUFLEN];
char *strx,*extstrx,*Readystrx;
extern char AtRxBuffer[512],Rxcouter;
char GPRMCSTR[128]; //转载GPS信息 GPRMC 经纬度存储的字符串
char GPRMCSTRLON[64]; //经度存储字符串 也就是119.20694
char GPRMCSTRLAT[64]; //维度存储字符串,也就是26.06451
char IMEINUMBER[64];//+CGSN: "869523052178994"
//下面是纠正火星坐标的变量定义/
int Get_GPSdata(void);
void Getdata_Change(char status);
typedef struct
{
char UtcDate[6];
char longitude[11];//经度原数据
char Latitude[10];//纬度源数据
char longitudess[4];//整数部分
char Latitudess[3];
char longitudedd[8];//小数点部分
char Latitudedd[8];
char Truelongitude[12];//转换过数据
char TrueLatitude[11];//转换过数据
char getstautus;//获取到定位的标志状态
float gpsdata[2];
}LongLatidata;
LongLatidata latdata;
float tempdata[2];
char latStrAF[64]; //存放数据经纬度用来发送
char lonStrAF[64]; //存放数据经纬度用来显示
//火星纠偏结束///
只要修改三要素/
#define PRODUCEKEY "6402ac07352830580e48ff7b_dev1_0_0_2023030403" //设备ID
#define DEVICENAME "6402ac07352830580e48ff7b_dev1" //用户名
#define DEVICESECRET "517f1c8f18d6b7b2e4c134653dc892edd38c3e86153506df57f7c296d13b37f7" //密码
void Clear_Buffer(void)//清空缓存
{
u8 i;
NBIOTSend_RecAccessMode();
printf(AtRxBuffer);
for(i=0;i<Rxcouter;i++)
AtRxBuffer[i]=0;//缓存
Rxcouter=0;
}
//初始化模块
void NBIOT_Init(void)
{
Uart2_SendStr("AT\r\n");
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//返回OK
errcount = 0;
while(strx==NULL)
{
errcount++;
printf("\r\n单片机正在连接到模块...\r\n");
Clear_Buffer();
Uart2_SendStr("AT\r\n");
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//返回OK
if(errcount>50) //防止死循环
{
errcount = 0;
reset_4g();
__set_FAULTMASK(1); //关闭总中断
NVIC_SystemReset(); //请求单片机重启
break;
}
}
Uart2_SendStr("ATE1\r\n"); //回显
delay_ms(500);
Clear_Buffer();
/
Uart2_SendStr("AT+CPIN?\r\n");//检查SIM卡是否在位
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CPIN: READY");//查看是否返回ready
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+CPIN?\r\n");
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CPIN: READY");//检查SIM卡是否在位,等待卡在位,如果卡识别不到,剩余的工作就没法做了
}
Clear_Buffer();
///
Uart2_SendStr("AT+CSQ\r\n"); //检查CSQ
delay_ms(500);
Clear_Buffer();
Uart2_SendStr("ATI\r\n"); //检查模块的版本号
delay_ms(500);
Clear_Buffer();
///
Uart2_SendStr("AT+CREG?\r\n");//查看是否注册GSM网络
delay_ms(500);
Clear_Buffer();
Uart2_SendStr("AT+CEREG?\r\n");//查看注册到哪个运营商,支持移动 联通 电信
delay_ms(500);
Clear_Buffer();
Uart2_SendStr("AT+CIMI\r\n");//获取卡号,类似是否存在卡的意思,比较重要。
delay_ms(1000);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//只要卡不错误 基本就成功
if(strx)
{
printf("============\r\n我的卡号是 : %s \r\n===============\r\n",AtRxBuffer);
delay_ms(1000);
Clear_Buffer();
}
else
{
// printf("卡错误 : %s \r\n",AtRxBuffer);
delay_ms(300);
Clear_Buffer();
}
Clear_Buffer();
Uart2_SendStr("AT+CGSN\r\n");//激活网络,PDP
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//返OK
Clear_Buffer();
Uart2_SendStr("AT+CGATT?\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CGATT: 1");//返1 表明激活成功 获取到IP地址了
Clear_Buffer();
errcount = 0;
while(strx==NULL)
{
errcount++;
Clear_Buffer();
Uart2_SendStr("AT+CGATT?\r\n");//获取激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CGATT: 1");//返回1,表明注网成功
if(errcount>100) //防止死循环
{
errcount = 0;
reset_4g();
__set_FAULTMASK(1); //关闭总中断
NVIC_SystemReset(); //请求单片机重启
break;
}
}
Uart2_SendStr("AT+QCCID\r\n");//获取当前卡的IP地址
delay_ms(500);
Clear_Buffer();
}
void Start_GPS(void)
{
Clear_Buffer();
Uart2_SendStr("AT+QGPS=1\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)AtStrBuf,(const char*)"OK");//返1
if(strx==NULL)
{
//__set_FAULTMASK(1);
//NVIC_SystemReset(); //超时重启
delay_ms(300);
}
Clear_Buffer();
}
//获取定位数据/
/*
AT+QGPSGNMEA="RMC"
+QGPSGNMEA: $GNRMC,035645.00,A,2603.9111,N,11912.4140,E,0.336,,140821,,,A,V*19
OK
AT+QGPSGNMEA="RMC"
+QGPSGNMEA: $GPRMC,145620.00,A,2603.972207,N,11912.411739,E,0.0,0.0,171022,3.5,W,A*2F
*/
char *Get_GPS_RMC(char type)
{
Clear_Buffer();
memset(GPRMCSTR,0,128);
Uart2_SendStr("AT+QGPSGNMEA=\"RMC\"\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"$GPRMC");//返1
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+QGPSGNMEA=\"RMC\"\r\n");//获取激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"$GPRMC");//返回1,表明注网成功
}
sprintf(GPRMCSTR,"%s",strx);
Clear_Buffer(); //打印收到的GPS信息
GPRMCSTR[2]= 'P';
//printf("============GETGPRMC==============\r\n%s",GPRMCSTR); //打印GPRMC
if(GPRMCSTR[17]=='A')
{
memset(latStrAF,0,64);
memset(lonStrAF,0,64);
Get_GPSdata();
if(type==1)
return latStrAF;
if(type==2)
return lonStrAF;
}
return 0;
}
/*****************************************************
下面是矫正火星坐标的
*****************************************************/
//解GPS析函数
//$GPRMC,134952.00,A,2603.9576,N,11912.4098,E,0.154,,280821,,,A,V*18
int Get_GPSdata()
{
int i=0;
strx=strstr((const char*)GPRMCSTR,(const char*)"A,");//获取纬度的位置
if(strx)
{
for(i=0;i<9;i++)
{
latdata.Latitude[i]=strx[i+2];//获取纬度值2603.9576
}
strx=strstr((const char*)GPRMCSTR,(const char*)"N,");//获取经度值
if(strx)
{
for(i=0;i<10;i++) //获取经度 11912.4098
{
latdata.longitude[i]=strx[i+2];
}
}
printf("latdata.Latitude ,%s \r\n",latdata.Latitude);
printf("latdata.longitude ,%s \r\n",latdata.longitude);
latdata.getstautus=1;//
}
else
{
latdata.getstautus=0;
}
Getdata_Change(latdata.getstautus);//数据换算
Clear_Buffer();
return 0;
}
/*************解析出经纬度数据,然后直接提交数据*******************/
void Getdata_Change(char status)
{
unsigned char i;
if(status)
{
for(i=0;i<3;i++)
latdata.longitudess[i]=latdata.longitude[i];
for(i=3;i<10;i++)
latdata.longitudedd[i-3]=latdata.longitude[i];
latdata.gpsdata[0]=(latdata.longitudess[0]-0x30)*100+(latdata.longitudess[1]-0x30)*10+(latdata.longitudess[2]-0x30)\
+((latdata.longitudedd[0]-0x30)*10+(latdata.longitudedd[1]-0x30)+(float)(latdata.longitudedd[3]-0x30)/10+\
(float)(latdata.longitudedd[4]-0x30)/100+(float)(latdata.longitudedd[5]-0x30)/1000+(float)(latdata.longitudedd[6]-0x30)/10000)/60.0;//获取完整的数据
///
for(i=0;i<2;i++)
latdata.Latitudess[i]=latdata.Latitude[i];
for(i=2;i<9;i++)
latdata.Latitudedd[i-2]=latdata.Latitude[i];
latdata.gpsdata[1]=(float)(latdata.Latitudess[0]-0x30)*10+(latdata.Latitudess[1]-0x30)\
+((latdata.Latitudedd[0]-0x30)*10+(latdata.Latitudedd[1]-0x30)+(float)(latdata.Latitudedd[3]-0x30)/10+\
(float)(latdata.Latitudedd[4]-0x30)/100+(float)(latdata.Latitudedd[5]-0x30)/1000+(float)(latdata.Latitudedd[6]-0x30)/10000)/60.0;//获取完整的数据b
sprintf(latStrAF,"%f",latdata.gpsdata[1]);
sprintf(lonStrAF,"%f",latdata.gpsdata[0]);
printf("latStrAF,%s \r\n",latStrAF);
printf("lonStrAF,%s \r\n",lonStrAF);
}
else
{
latdata.gpsdata[0]=0;
latdata.gpsdata[1]=0;
}
}
/*****************************************************
纠偏结束
*****************************************************/
/*********************************************************************************
** 函数名称 : MakeOnenetPayload(char *Str,u8 Temp,u8 Humi)
** 函数功能 : 将温度与湿度封装成Onenet有效载荷包
** 输 入 : *str有效载荷保存的目标字符串,
** 输 入 : Temp 温度值
** 输 入 : Humi 湿度值
** 输 出 : 有效载荷字符串
** 返 回 : 有效载荷字符串长度
*********************************************************************************/
char JsonBuf[256];//临时数据缓存
u8 MakeOnenetPayload(u8 *Str,u8 Temp,u8 Humi)//将温度与湿度封装成Onenet有效载荷包
{
char Payload[] = "{\"datastreams\":[{\"id\":\"Temp\",\"datapoints\":[{\"value\":%d}]},{\"id\":\"Humi\",\"datapoints\":[{\"value\":%d}]}]}";//onenet有效载荷固定格式
unsigned short StrLen;//有效载荷数据长度
memset(JsonBuf,0,256);
sprintf(JsonBuf,Payload,Temp,Humi);//合成数据有效载荷封装
//printf("\r\n有效载荷(%d):[%s]\r\n",strlen(JsonBuf),(char *)JsonBuf);
StrLen = strlen(JsonBuf)/sizeof(char);//计算有效载荷的数据长度
//printf("\r\n有效载荷长度(%d)\r\n",StrLen);
Str[0] = '\x01';//报文第一个字节的固定显示
//printf("\r\nStr[0]=0X(%02X)\r\n",Str[0]);
//报文第2个字节和第三个字节表示有效载荷的长度的固定显示
Str[1] = (StrLen & 0XFF00) >>8;//高位
//printf("\r\nStr[1]=0X(%02X)\r\n",Str[1]);
Str[2] = StrLen & 0XFF;//低位
//printf("\r\nStr[2]=0X(%02X)\r\n",Str[2]);
//拷贝有效载荷数据到Str
memcpy(Str+3,JsonBuf,StrLen);//从第三个字节开始拷贝
return (StrLen+3);//返回长度
}
u8 CSTX_4G_MQTT_Senddata(char *data)
{
u8 JsonLen;//Json包数据长度
memset(AtStrBuf,0,BUFLEN);
Clear_Buffer();
sprintf(AtStrBuf,"AT+QMTPUB=0,0,0,0,\"$oc/devices/6402ac07352830580e48ff7b_dev1/sys/properties/report\"\r\n");
// printf("ATSTR = %s \r\n",AtStrBuf);
Uart2_SendStr(AtStrBuf);//mqtt提交
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)">");//准备发送数据
errcount=0;
while(strx==NULL)
{
errcount++;
strx=strstr((const char*)AtRxBuffer,(const char*)">");//准备发送数据 模块的反馈
delay_ms(30);
if(errcount>10) //超时退出
{
errcount = 0;
break;
}
}
Clear_Buffer();
USART2_Send((char *)data,strlen(data));//发送json数据
UART2_send_byte(0x1A);
delay_ms(300);
errcount=0;
strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返SEND OK +QMTPUB: 0,0,0
while(strx==NULL)
{
errcount++;
strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返SEND OK
delay_ms(30);
if(errcount>10) //超时退出
{
errcount = 0;
break;
}
}
Clear_Buffer();
return 0;
}
u8 MakeOnenetPayloadGPS(u8* Str,char *latstr,char * lonstr)//将温度与湿度封装成Onenet有效载荷包
{
char Payload[] = "{\"datastreams\":[{\"id\":\"location\",\"datapoints\":[{\"value\":{\"lon\":%s,\"lat\":%s}}]}]}";//onenet有效载荷固定格式
unsigned short StrLen;//有效载荷数据长度
memset(JsonBuf,0,256);
sprintf(JsonBuf,Payload,lonstr,latstr);//合成数据有效载荷封装
printf("JSONEND= %s \r\n",JsonBuf);
//printf("\r\n有效载荷(%d):[%s]\r\n",strlen(JsonBuf),(char *)JsonBuf);
StrLen = strlen(JsonBuf)/sizeof(char);//计算有效载荷的数据长度
//printf("\r\n有效载荷长度(%d)\r\n",StrLen);
Str[0] = '\x01';//报文第一个字节的固定显示
//printf("\r\nStr[0]=0X(%02X)\r\n",Str[0]);
//报文第2个字节和第三个字节表示有效载荷的长度的固定显示
Str[1] = (StrLen & 0XFF00) >>8;//高位
//printf("\r\nStr[1]=0X(%02X)\r\n",Str[1]);
Str[2] = StrLen & 0XFF;//低位
//printf("\r\nStr[2]=0X(%02X)\r\n",Str[2]);
//拷贝有效载荷数据到Str
memcpy(Str+3,JsonBuf,StrLen);//从第三个字节开始拷贝
return (StrLen+3);//返回长度
}
void CSTX_4G_ONENETIOTSenddataGPS(char *latstr,char *lonstr)//上发数据,上发的数据跟对应的插件有关系,用户需要注意插件然后对应数据即可
{
// int JsonLen= 0;
// memset(AtStrBuf,0,BUFLEN);
//
// Clear_Buffer(); //发送命令之前清空之前的模块反馈的数据
// sprintf(AtStrBuf,"AT+QMTPUB=0,0,0,0,\"$dp\"\r\n"); //发送ONENET命令
// printf("AtStrBuf = %s \r\n",AtStrBuf);
// Uart2_SendStr(AtStrBuf);//mqtt提交
// delay_ms(300);
// strx=strstr((const char*)AtRxBuffer,(const char*)">");//模块反馈可以发送数据了
// errcount=0;
// while(strx==NULL)
// {
// errcount++;
// strx=strstr((const char*)AtRxBuffer,(const char*)">");//模块反馈可以发送数据了
// delay_ms(30);
// if(errcount>100) //防止死循环跳出
// {
// errcount = 0;
// break;
// }
// }
// //命令发送成功下面就去发送数据了
// //Clear_Buffer();
// JsonLen = MakeOnenetPayloadGPS((u8*)Send_Json,latstr,lonstr); //组建CJSON数据
// USART2_Send((char *)Send_Json,JsonLen);//发送json 数据
// delay_ms(10);
// UART2_send_byte(0x1A);
// strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返发送成功
// errcount=0;
// while(strx==NULL)
// {
// errcount++;
// strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返发送成功
// delay_ms(10);
// if(errcount>100) //超时退出死循环 表示服务器连接失败
// {
// errcount = 0;
// break;
// }
// }
//
// Clear_Buffer();
}
6.2 主函数里的项目逻辑代码
下面是项目的主函数里的项目逻辑代码(其他子模块的代码太多,文档无法全部贴出)。
可以去网盘里下载传感器模块的代码:https://pan.quark.cn/s/145a9b3f7f53
#include "stm32f10x.h"
#include "sensors.h" // 传感器驱动
#include "lcd.h" // LCD显示驱动
#include "nbiot.h" // NBIoT模块驱动
#include "mqtt.h" // MQTT协议库
#include "relay.h" // 继电器控制
#include "delay.h" // 延时函数
#include <string.h> // 字符串处理函数
// 初始化所有外设
void Hardware_Init(void) {
// 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
// ... 配置必要的GPIO引脚(如LCD接口、继电器控制等)
// 初始化定时器(用于周期性任务)
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// ... 配置定时器
// 初始化传感器
Sensors_Init();
// 初始化LCD
LCD_Init();
// 初始化NBIoT模块
NB_IoT_Init();
// 初始化MQTT客户端
MQTT_Init();
}
// 检查并执行灌溉命令
void CheckAndExecuteIrrigationCommands(void) {
// 从云端获取最新的灌溉命令
char *irrigation_command = MQTT_Subscribe("irrigation/command");
if (irrigation_command != NULL) {
// 解析命令并控制相应的继电器
Relay_Control(irrigation_command);
}
}
// 主函数
int main(void) {
// 初始化硬件
Hardware_Init();
// 主循环
while (1) {
// 读取传感器数据
float soil_temp, soil_humidity, air_temp, air_humidity, light_intensity, air_quality;
Sensors_Read(&soil_temp, &soil_humidity, &air_temp, &air_humidity, &light_intensity, &air_quality);
// 显示到LCD
LCD_DisplayData(soil_temp, soil_humidity, air_temp, air_humidity, light_intensity, air_quality);
// 通过NBIoT上传数据到华为云IoT
if (NB_IoT_IsConnected()) {
char data[128];
snprintf(data, sizeof(data),
"soil_temp=%.2f,soil_humidity=%.2f,air_temp=%.2f,air_humidity=%.2f,light_intensity=%.2f,air_quality=%.2f",
soil_temp, soil_humidity, air_temp, air_humidity, light_intensity, air_quality);
MQTT_Publish("sensor/data", data);
}
// 检查并执行灌溉命令
CheckAndExecuteIrrigationCommands();
// 延时一段时间
Delay(5000); // 例如每5秒更新一次
}
}
七、总结
本项目开发一套基于STM32微控制器与华为云IoT平台的大棚育苗智能管理系统,以实现对大棚内环境条件的精准监控和自动调节。系统集成了多种传感器技术,能够实时检测土壤温度、湿度以及环境中的温湿度、光照强度和空气质量等关键参数,并通过NBIOT-BC26模块将这些数据上传至华为云IoT服务器。利用MQTT协议确保了数据传输的安全性和可靠性,使得即使在通信条件较差的地区也能保持稳定的数据交流。此外,该系统还支持本地及远程灌溉控制功能,可通过安装于Android设备上的应用程序或Windows桌面客户端进行操作,极大地提高了管理灵活性。用户界面友好且直观,不仅可以在移动终端上查看最新的监测数据,还能根据实际需要调整灌溉策略。整个设计方案注重成本效益,在保证高性能的同时兼顾了经济性,适用于不同规模的农业应用场景,有助于推动现代农业向更加智能化、精细化的方向发展。