[TOC]
一、前言
1.1 项目介绍
1.1.1 开发背景
在当前全球水资源日益紧张与环境污染加剧的背景下,淡水湖养殖业面临着前所未有的挑战。传统的淡水湖养殖方式往往依赖于自然条件,缺乏有效的水质监测与调控手段,这不仅限制了养殖效率,也增加了疾病爆发的风险,进而影响到水产品的品质与产量。随着物联网技术的迅猛发展及其在农业领域的广泛应用,基于物联网的人工淡水湖养殖系统应运而生,成为提升养殖智能化水平、优化资源管理的关键所在。
本项目致力于设计一套集成化的淡水湖养殖管理系统,以 STM32F103RCT6 为主控芯片,结合 PH 值、浑浊度、TDS 值等多种水质参数的实时监测,辅以远程控制与自动化设备,实现了淡水湖养殖环境的全方位智能监管。通过采用先进的传感器技术,系统能够准确检测水质状况,及时反馈至本地 LCD 显示屏与手机 APP,为养殖人员提供直观的数据支持。特别地,项目引入了远程操控功能,用户不仅能在手机 APP 上远程启动换水、投喂、充氧等操作,还能通过 Windows 电脑 APP 进行更为细致的管理设定,极大地提升了养殖管理的灵活性与便捷性。
为了确保系统的稳定运行与数据的安全传输,项目选用了 ESP8266-WIFI 模块,借助 MQTT 协议与华为云 IOT 物联网服务器建立连接,实现了设备与云端的无缝对接。这样一来,不仅养殖现场的数据能够实时上传至云端,便于数据分析与长期存储,同时也使得用户能够随时随地通过手机 APP 或电脑 APP 访问养殖信息,实现真正的远程监控与智能决策。
考虑到淡水湖养殖过程中可能遇到的突发情况,如水质污染等,项目特别设计了基于阈值触发的蜂鸣器报警系统,一旦检测到浑浊度超出安全范围,系统将立即发出警报,提醒养殖人员及时采取措施,避免潜在的经济损失。整个系统通过外部 5V 2A 电源供电,保证了稳定持久的运行能力。
基于物联网的人工淡水湖养殖系统设计,不仅代表了现代养殖业向着智慧化、精细化方向发展的趋势,也为解决传统养殖中存在的诸多问题提供了创新解决方案,有望推动淡水湖养殖业迈向更加可持续与高效的未来。
1.1.2 项目实现的功能
(1)PH 值监测与展示
系统配备 PH 值检测传感器,能够持续监测淡水湖中的酸碱度,确保水质维持在适宜鱼类生长的最佳范围内。监测数据不仅实时显示在本地 LCD 显示屏上,供现场管理人员即时查看,同时,通过物联网技术,这些数据也会同步推送至用户的手机 APP 和 Windows 电脑端,实现远程监控,确保无论身处何地,养殖者都能掌握水质的最新动态。
(2)浑浊度检测与预警
利用浊度检测传感器,系统能够精确测定水体的浑浊程度,及时反映水体中悬浮物含量的变化。浑浊度数据同样在本地显示屏和远程终端上实时更新,一旦检测到浑浊度超过预设的安全阈值,系统将启动蜂鸣器报警,提醒管理者迅速采取应对措施,防止水质恶化对养殖生物造成不利影响。
(3)TDS 值监控
TDS(Total Dissolved Solids,总溶解固体)传感器用于监测水中溶解物质的总量,帮助养殖者了解水质的纯净度。系统将 TDS 值数据实时传输至本地和远程终端,为养殖决策提供重要依据,确保水质始终符合养殖需求。
(4)远程手动换水控制
通过手机 APP 和电脑端,养殖者可远程启动换水电机,实现淡水湖的自动换水。系统设计了两个电机,分别负责抽水进出,以循环方式更新水质。在本地 LCD 显示屏上,用户可查看下一次换水的倒计时,合理规划换水频率,保持水体的健康状态。
(5)周期自动投喂管理
借助手机 APP 和电脑端的远程控制功能,系统支持自动投喂周期设定。养殖者可根据鱼群的生长需求,定制定时投喂计划,系统将自动启动食物投喂电机,适时投放饲料。本地 LCD 显示屏显示下一次投喂的时间,确保养殖过程的自动化与精准化。
(6)数据上云与远程监控
系统通过 ESP8266-WIFI 模块与华为云 IOT 物联网服务器相连,所有监测数据均上传至云端,支持历史数据查询与分析,便于养殖者做出科学决策。同时,用户可通过手机 APP 和 Windows 电脑 APP 实时访问云端数据,实现远程监控与管理,即使远离养殖现场,也能随时掌握养殖环境状况。
(7)周期自动充氧功能
系统具备远程设定充氧周期的能力,养殖者可通过手机 APP 和电脑端自定义充氧时间,系统将自动启动充氧电机,确保水体含氧量充足,促进鱼类健康成长。本地 LCD 显示屏显示下一次充氧的倒计时,便于养殖者安排日常管理工作。
(8)异常报警机制
当水质参数超出正常范围,如浑浊度过高,系统将通过蜂鸣器发出警报,并在手机 APP 和电脑端同步推送警告信息,确保养殖者第一时间获知异常情况,及时采取补救措施,降低潜在风险。
1.1.3 项目硬件模块组成
(1)主控芯片模块
采用高性能的 STM32F103RCT6 微控制器作为系统的核心大脑,负责接收来自各种传感器的数据,执行控制逻辑,并通过无线模块与远程设备进行通信。STM32F103RCT6 凭借其强大的处理能力和丰富的外设接口,能够高效处理复杂的数据流和控制任务,确保系统的稳定运行。
(2)水质监测传感器模块
包括 PH 值、浑浊度和 TDS 值三种传感器,用于实时监测水质状况。这些传感器通过模拟电压输出的方式,将水质参数转化为电信号,由主控芯片读取并处理。PH 值传感器监测水体酸碱度;浑浊度传感器检测水中悬浮物浓度;TDS 传感器则测定水中的溶解固体总量,三者共同构成了水质监测的基础。
(3)控制与执行模块
换水系统:由两个 5V 高电平触发的继电器控制抽水电机组成,实现淡水湖的自动换水。一个电机负责抽水出去,另一个负责抽水进来,形成水体循环,保持水质清洁。
食物投喂系统:采用 ULN2003 驱动的 28BYJ4 步进电机,用于控制食物投放阀门的开关,实现定时定量的自动投喂。
充氧系统:同样是 5V 高电平触发的继电器控制充氧电机,用于调节水体中的氧气含量,确保养殖生物的健康生长。
(4)连接与通信模块
(5)显示与用户界面模块
(6)电源模块
采用外部 5V 2A 电源供电,为整个系统提供稳定电力,确保各模块的正常运行。
(7)报警模块
系统配置了高电平触发的有源蜂鸣器,当水质参数超出预设安全范围时,蜂鸣器将发出警报声,确保养殖者能及时采取措施。
1.1.4 ESP8266 工作模式配置
在整个设计里,STM32 端的 ESP8266 配置成 STA 模式+TCP 客户端模式,上电时连接家里的路由器 WIF 热点,连接互联网,以 TCP 客户端模式(通过 MQTT 协议)去连接腾讯云联网服务器,实时上传当前的设备状态等各种参数信息。用户在 Android 手机 APP 可以远程查看设备的状态信息。
ESP8266 模块具有两种常用的工作模式,分别是 STA 模式和 AP 模式:
(1)STA 模式(Station Mode):在 STA 模式下,ESP8266 可以连接到已存在的 Wi-Fi 网络作为一个客户端设备。它可以扫描周围的 Wi-Fi 网络,并且根据提供的 SSID 和密码进行连接,获取 IP 地址后可以通过该网络与其他设备进行通信。在 STA 模式下,ESP8266 可以实现与互联网的连接,执行各种网络相关的操作。
(2)AP 模式(Access Point Mode):在 AP 模式下,ESP8266 可以作为一个独立的 Wi-Fi 接入点(热点)运行。它会创建一个自己的 Wi-Fi 网络,允许其他设备(如手机、电脑等)连接到这个热点上。在 AP 模式下,ESP8266 可以充当局域网内部的服务器,通过建立 TCP/IP 连接与其他设备进行通信,提供 Web 页面访问、数据传输等服务。
通过 STA 模式,ESP8266 可以连接到互联网上的其他设备或服务器,实现远程控制和数据交换;而通过 AP 模式,ESP8266 可以作为一个独立的接入点,让其他设备通过它进行连接和通信。
1.2 系统设计方案
1.2.1 关键技术与创新点
本项目打造了一个高度自动化、智能化的养殖环境监测与控制系统。首要关键技术在于物联网技术的应用,通过 ESP8266-WIFI 模块与华为云 IOT 物联网服务器的无缝连接,实现了淡水湖养殖数据的实时采集、远程传输与云端存储。这一技术不仅确保了水质参数的连续监测,还为养殖者提供了随时随地的远程监控与管理能力,极大提升了养殖作业的灵活性与响应速度。
创新点之一在于系统的集成化设计。将 PH 值、浑浊度、TDS 值等水质参数的监测与自动化控制功能融为一体,通过主控芯片 STM32F103RCT6 的高效数据处理,实现了水质监测、换水、投喂、充氧等关键环节的自动化管理,显著减少了人工干预,提高了养殖效率与水质管理的精准度。
另一创新点体现在远程控制与数据可视化方面。借助手机 APP 与 Windows 电脑软件,养殖者不仅可以实时查看水质参数,还能远程设定换水、投喂、充氧的周期与时间,甚至在紧急情况下,如水质异常时,系统会自动触发报警机制,通过蜂鸣器与远程通知提醒养殖者及时采取行动,确保养殖环境的安全与稳定。
系统在数据处理与分析上也进行了创新。通过 MQTT 协议与华为云 IOT 物联网服务器的深度整合,不仅保证了数据传输的安全性与可靠性,还为养殖者提供了历史数据查询与分析功能,有助于总结养殖规律,优化管理策略,推动养殖业向数据驱动的方向发展。
本项目的关键技术与创新点集中体现在物联网技术的深度应用、系统集成化设计、远程控制与数据可视化、以及数据处理与分析等方面,共同构建了一个高效、智能、可靠的淡水湖养殖管理平台,为现代养殖业的可持续发展注入了新的活力。
1.2.2 功能需求分析
本项目功能聚焦于如何通过技术手段实现养殖环境的智能化监控与自动化管理,以提升养殖效率、保障水质安全、简化操作流程,并为养殖决策提供数据支持。首先,系统必须具备实时监测水质参数的能力,包括但不限于 PH 值、浑浊度和 TDS 值,确保这些关键指标处于适宜养殖生物生长的范围内。监测数据需通过本地 LCD 显示屏直观呈现,借助物联网技术,实现数据的远程传输,确保养殖者无论身在何处,都能通过手机 APP 或 Windows 电脑软件实时掌握水质状况。
系统应支持自动化控制功能,涵盖定时换水、自动投喂和周期性充氧等关键操作。养殖者应能在手机 APP 或电脑软件上设定换水、投喂和充氧的周期与时间,系统自动执行相应任务,减少人力投入,提高养殖过程的自动化水平。系统还应提供倒计时显示,便于养殖者提前规划相关工作。
数据上云与远程监控是不可或缺的需求。通过 ESP8266-WIFI 模块与华为云 IOT 物联网服务器的连接,系统需将监测数据定期上传至云端,一方面实现数据的长期存储,另一方面,养殖者能通过云平台提供的 API 接口,实现远程数据访问,进行历史数据查询与分析,为养殖策略的优化提供科学依据。
报警机制也是系统的重要组成部分。当监测到水质参数异常,如浑浊度超过安全阈值时,系统应立即触发蜂鸣器报警,并通过手机 APP 与电脑软件发送警告通知,确保养殖者能及时响应,采取有效措施,避免水质恶化对养殖生物造成伤害。
考虑到淡水湖养殖环境的特殊性,系统还需具备一定的抗干扰与稳定性,确保在复杂环境下仍能持续、准确地执行各项功能。此外,操作界面应简洁友好,无论是现场的 LCD 显示屏还是远程的手机 APP 与电脑软件,都应易于理解和操作,降低养殖者的使用门槛。
1.2.3 现有技术与市场分析
当前,随着物联网、大数据和人工智能技术的飞速发展,智慧农业正逐渐成为现代农业转型的重要方向,特别是在水产养殖领域,科技的应用正在重塑养殖模式,提升养殖效率与产品质量。
从技术角度来看,物联网技术在淡水湖养殖中的应用已初具规模。通过部署各类水质传感器,如 PH 值、浑浊度和 TDS 值传感器,养殖者能够实时监测水质变化,及时发现并解决水质问题。同时,结合远程控制技术,自动化执行换水、投喂和充氧等操作,不仅节省了人力成本,还提高了养殖的精准度和效率。然而,目前市面上的解决方案往往侧重于单一功能,如水质监测或自动化投喂,缺乏一个集成化、智能化的综合管理系统,难以满足养殖者对水质全面监控与智能决策的需求。
市场层面,随着消费者对食品安全和营养价值的重视,高品质水产品的需求日益增长,促使养殖业向精细化、标准化方向发展。养殖者急需一套能够提供全方位水质监控、自动化管理与数据分析的解决方案,以提升养殖效率,保障水产品质量。此外,政府对环保和资源节约的政策导向,也推动着养殖业寻求低能耗、低污染的养殖模式,物联网技术的应用恰能满足这一需求,通过精准控制减少资源浪费,降低环境污染。
市场上现有的淡水湖养殖系统在数据处理与分析能力、远程监控的便捷性以及系统的稳定性和易用性方面仍有待提升。养殖者渴望获得一个集成度高、操作简便、数据处理能力强的养殖管理系统,以实现养殖过程的智能化升级。
基于物联网的人工淡水湖养殖系统正处于技术革新与市场需求双重驱动的有利时机。通过集成水质监测、自动化控制、远程监控与数据分析等功能,不仅能填补市场空白,满足养殖者对智能化养殖管理的迫切需求,还能顺应现代农业向智慧化转型的大势,推动淡水湖养殖业的高质量发展。
1.2.4 硬件架构设计
本项目的硬件架构设计围绕基于物联网的人工淡水湖养殖系统展开,实现水质参数的实时监测、自动化控制以及远程管理。系统的核心是由 STM32F103RCT6 微控制器组成的主控单元,它负责协调整个系统的运作,包括数据采集、处理和传输。STM32F103RCT6 作为高性能的 32 位 ARM Cortex-M3 微控制器,具备足够的处理能力和丰富的外设接口,能够高效处理来自各种传感器的数据,并控制执行器的动作。
传感器模块是硬件架构的重要组成部分,其中包括 PH 值检测传感器、浑浊度检测传感器和 TDS 值检测传感器,用于实时监测水质的各项关键指标。这些传感器通过模拟电压输出的方式,将物理或化学信号转换为电信号,STM32F103RCT6 通过 ADC(模数转换器)读取这些信号,并进行相应的数据处理。
执行器模块则负责执行自动化控制任务,如换水、投喂和充氧。系统中采用 5V 高电平触发的继电器来控制抽水电机,实现淡水湖的自动换水;ULN2003 驱动的 28BYJ4 步进电机用于控制食物投喂阀门的开闭,实现定时定量的食物投喂;充氧设备同样通过继电器控制,确保水体含氧量的充足。所有执行器的操作均由 STM32F103RCT6 通过数字 I/O 口控制,实现精准的自动化管理。
为了实现远程监控和数据上云,系统集成了 ESP8266-WIFI 模块,该模块负责将 STM32F103RCT6 处理后的数据通过 Wi-Fi 网络上传至华为云 IOT 物联网服务器,同时接收来自服务器的控制指令。ESP8266-WIFI 模块与 STM32F103RCT6 之间通过串行通信接口(如 UART)进行数据交换,确保数据的实时传输。
本地 LCD 显示屏用于现场显示水质参数和系统状态,方便现场工作人员实时监控。此外,系统还配置了蜂鸣器报警装置,当水质异常时,如浑浊度超过预设阈值,蜂鸣器将发出警报,提醒工作人员采取相应措施。
最后,系统采用外部 5V 2A 电源供电,确保整个硬件架构的稳定运行。电源模块不仅为微控制器、传感器和执行器提供必要的电力,还配备了过载保护和稳压功能,以应对淡水湖养殖环境中的电压波动。
1.2.5 软件架构设计
本项目的软件架构设计是整体逻辑核心,实现水质参数的实时监测、自动化控制以及远程管理。软件架构的核心是基于 STM32F103RCT6 微控制器的嵌入式软件,负责数据采集、处理、执行自动化任务和通信控制。该软件架构遵循分层设计理念,包括数据采集层、数据处理层、控制逻辑层和通信层,每一层都有明确的功能和职责,确保系统的稳定性和可维护性。
数据采集层主要负责与各种传感器交互,读取 PH 值、浑浊度和 TDS 值等水质参数的原始数据。这一层通过 STM32F103RCT6 的 ADC 模块读取模拟电压信号,并进行适当的校准和预处理,确保数据的准确性和一致性。
数据处理层则对采集到的原始数据进行深入处理和分析,包括数据过滤、异常检测、数据转换和存储。这一层软件采用先进的算法,如滑动平均滤波和阈值比较,以剔除噪声和异常值,确保水质参数的可靠性和有效性。此外,数据处理层还负责将处理后的数据格式化,以便于传输和远程显示。
控制逻辑层是软件架构的关键部分,它基于处理后的水质数据,执行自动化控制策略,如启动换水、投喂和充氧等操作。这一层软件包含了复杂的控制算法,能够根据水质参数的实时变化,动态调整执行器的动作,实现淡水湖养殖环境的精准管理。
通信层负责与 ESP8266-WIFI 模块和远程服务器的交互,通过 MQTT 协议实现数据的上传和接收控制指令。这一层软件不仅处理数据的序列化和反序列化,还负责数据包的完整性检查和重传机制,确保数据传输的可靠性和安全性。此外,通信层还支持与手机 APP 和电脑软件的交互,通过 RESTful API 提供数据访问和控制接口。
为了提供直观的用户界面和远程监控功能,本项目还设计了基于 Qt 框架的手机 APP 和 Windows 电脑软件。这些应用程序通过调用云平台提供的 API,实现水质数据的实时查看、远程控制和历史数据分析,为养殖者提供全面的管理工具和决策支持。
本项目的软件架构设计通过分层结构和模块化编程,实现了淡水湖养殖系统的智能化和自动化管理,不仅提升了水质监测的精度和效率,还简化了养殖管理流程,为养殖者提供了便捷的远程监控和控制手段。
1.2.6 上位机开发思路
项目里,上位机是采用 Qt 开发,Qt 是一个基于 C++的跨平台软件开发框架。
Qt 框架提供了网络模块,能够支持 HTTPS 协议的请求和响应。可以利用 Qt 的网络模块来建立与华为云 IOT 平台的 HTTPS 连接,并通过 API 接口获取设备的影子数据。
(1)从华为云 IOT 平台获取数据的流程
认证授权:使用设备的 Access Key 和 Secret Key 进行认证授权,获取访问令牌。
构建 HTTPS 请求:利用 Qt 的网络模块构建 HTTPS 请求,包括 API 接口的 URL、Header 信息、请求参数等。
发送 HTTPS 请求:发送构建好的 HTTPS 请求给华为云 IOT 平台,获取设备的影子数据。
处理响应数据:解析 HTTPS 响应,提取设备的影子数据并进行处理。
(2)数据展示与交互
在获取到设备的影子数据后,可以利用 Qt 的界面设计模块,结合自定义的数据展示控件,将设备的影子数据以直观的方式呈现给用户。
1.3 系统功能总结
此表格总结了基于物联网的人工淡水湖养殖系统的各项功能,包括其描述、显示位置以及操作方式。这些功能通过 STM32F103RCT6 主控芯片、各类传感器、继电器、步进电机、ESP8266-WIFI 模块等硬件实现,并通过手机 APP 和 Windows 电脑 APP 进行远程控制和监测。
1.4 关键技术选型与说明
1.4.1 STM32F103RCT6 最小系统板介绍
在基于物联网的人工淡水湖养殖系统中,STM32F103RCT6 最小系统板作为整个系统的大脑,它负责处理来自各种传感器的数据,执行自动化控制逻辑,并管理与外部设备和远程服务器的通信。STM32F103RCT6 最小系统板是一个高度集成的开发平台,专为嵌入式应用设计,它基于 ARM Cortex-M3 内核,提供高性能的 32 位微处理器,具备高速的处理能力和低功耗特性,非常适合要求实时响应和高精度数据处理的场景。
该系统板集成了主控芯片 STM32F103RCT6,拥有丰富的外设资源,包括多个通用输入输出(GPIO)引脚,可以连接各种传感器和执行器,如水质检测传感器、电机控制电路等。此外,它还配备了精确的时钟源,如外部晶振,以及电源管理电路,确保系统稳定运行。系统板上还设计有调试接口,如 SWD 接口,便于程序的烧录和调试,以及 LED 指示灯和按键,用于状态显示和用户交互。
在本项目中,STM32F103RCT6 最小系统板不仅负责接收和处理来自 PH 值、浑浊度和 TDS 值传感器的数据,还控制着换水、投喂和充氧等自动化操作,通过集成的通信模块如 ESP8266-WIFI,实现与华为云 IOT 物联网服务器的连接,支持数据上云和远程控制功能。此外,系统板上的资源还被用于驱动本地 LCD 显示屏,显示实时水质参数和系统状态,以及控制蜂鸣器报警系统,当水质异常时及时发出警报。
STM32F103RCT6 最小系统板凭借其强大的处理能力、丰富的外设接口和高度集成的设计,成为了构建基于物联网的淡水湖养殖系统的关键组件,确保了水质监测的准确性、自动化控制的可靠性以及远程管理的便利性,为实现淡水湖养殖的智能化和高效化奠定了坚实的硬件基础。
1.4.2 ESP8266 与 MQTT 协议应用
在基于物联网的人工淡水湖养殖系统中,ESP8266 与 MQTT 协议的应用是实现远程数据传输和设备管理的核心技术。ESP8266 是一款低成本、低功耗的 Wi-Fi 模块,特别适用于物联网应用,因其集成了 TCP/IP 协议栈和内置的微控制器,能够直接与各种传感器和执行器通信,无需额外的微处理器,极大地简化了物联网设备的设计和开发。
MQTT(Message Queuing Telemetry Transport)协议是一种轻量级的发布/订阅消息协议,专为低带宽、高延迟或不可靠的网络设计。它基于 TCP/IP 协议,通过最小化数据包大小和带宽使用,使得设备能够在资源受限的环境中高效地交换信息。MQTT 协议的发布/订阅模型允许设备(如淡水湖养殖系统中的传感器和执行器)作为订阅者接收特定主题的消息,同时作为发布者发送数据,这种机制非常适合分布式物联网系统中设备间的通信。
在本项目中,ESP8266 Wi-Fi 模块作为淡水湖养殖系统与华为云 IOT 物联网服务器之间的通信桥梁,负责将水质监测数据(如 PH 值、浑浊度、TDS 值等)通过 MQTT 协议上传至云服务器,同时接收来自云服务器的控制指令,如换水、投喂和充氧的调度。通过 MQTT 协议,养殖系统可以将大量传感器数据压缩成小包传输,减少网络拥堵,同时确保数据传输的可靠性和安全性。
STM32F103RCT6 主控芯片收集水质传感器的数据后,通过串行通信接口(如 UART)将数据发送给 ESP8266 模块。ESP8266 模块利用 MQTT 协议将这些数据打包并发送到华为云服务器的指定主题,服务器则通过预先设置的规则引擎处理这些数据,将其存储、分析或转发给授权的用户(如通过手机 APP 或电脑软件)。此外,用户可以通过同样的 MQTT 主题发送控制命令,ESP8266 模块接收到这些命令后,再将它们转发给 STM32F103RCT6,从而实现远程控制功能,如启动换水电机、调整投喂周期或设定充氧时间。
ESP8266 与 MQTT 协议在淡水湖养殖系统中的应用,不仅实现了水质参数的实时监测与远程管理,还构建了一个高效、稳定的数据传输通道,确保了养殖环境的智能化和自动化,为养殖者提供了便捷的远程监控和控制手段,推动了淡水湖养殖业向智慧农业的转型。
1.4.3 Qt(C++)手机 APP 开发框架
在基于物联网的人工淡水湖养殖系统中,Qt(C++)框架被用于开发手机应用程序,为养殖者提供一个直观且功能全面的远程监控与控制平台。Qt 是一个跨平台的开发框架,以其丰富的 GUI 工具、强大的网络功能以及广泛的设备支持而闻名。Qt 不仅支持 C++编程,还提供了一套完整的工具链,包括 Qt Creator 集成开发环境(IDE)、Qt Widgets 和 Qt Quick/QML 用于 UI 设计,以及一系列库和模块,如 Qt Networking 和 Qt Sensors,适用于构建复杂的物联网应用。
Qt(C++)手机 APP 开发框架在本项目中的应用,使得开发者能够构建出既美观又功能强大的 Android 应用程序,而无需深入了解底层的 Android SDK。通过 Qt Quick 和 QML,开发者可以快速设计出响应式的用户界面,这些界面能够适应不同尺寸的屏幕,提供一致的用户体验。QML 是一种声明式语言,它简化了 UI 的构建过程,允许开发者使用简单的语法描述界面布局、动画和交互逻辑,同时还可以与 C++代码无缝集成,实现复杂业务逻辑的编写。
在淡水湖养殖系统中,Qt 开发的手机 APP 充当了养殖者与物联网设备之间的交互界面。它不仅实时显示水质参数,如 PH 值、浑浊度和 TDS 值,还允许用户远程控制关键的养殖操作,比如启动换水、设置投喂周期和充氧时间。
Qt 的网络模块在实现数据传输方面发挥了重要作用,它支持多种通信协议,包括 HTTP、HTTPS 和 MQTT,这使得 APP 能够与华为云 IOT 物联网服务器建立稳定连接,实现数据的双向通信。通过调用华为云提供的 API 接口,Qt 开发的 APP 能够从云服务器下载最新的水质数据,同时上传控制指令,确保养殖系统的远程监控和管理。
Qt(C++)框架在淡水湖养殖系统的手机 APP 开发中,提供了强大的开发工具和丰富的功能库,使得开发者能够高效地构建出功能全面、界面友好的移动应用程序,极大地提升了养殖者的操作便利性和养殖系统的智能化水平。
1.4.4 通信协议与云平台对接方案
在本项目中,系统采用了 MQTT(Message Queuing Telemetry Transport)协议作为主要的通信机制,这是一种轻量级的发布/订阅模式的消息传输协议,专为低带宽和高延迟的网络环境设计,非常适合物联网场景下的数据传输。MQTT 协议的特点在于其低开销、低网络流量和良好的稳定性,能够有效减少设备端与云平台之间的通信延迟,并保证数据传输的可靠性。
为了实现淡水湖养殖系统与华为云 IOT 平台的对接,在系统中集成了 MQTT 客户端,该客户端运行在基于 STM32 的微控制器上,负责收集各种传感器数据,如水质参数检测模块采集的 PH 值、浑浊度和 TDS 值等,并通过无线模块将这些数据封装成 MQTT 消息,发送至华为云 IOT 物联网平台。同时,MQTT 客户端也接收来自云平台的控制指令,如启动换水、设置投喂周期和充氧时间等操作,从而实现对养殖系统的远程控制。
在云平台侧,利用华为云 IOT 提供的 SDK 和 API 接口,构建了一个数据处理和分析的后端服务。当淡水湖养殖系统上传的数据到达华为云 IOT 平台后,后端服务会自动接收并解析这些数据,将其存储到数据库中,以便于后续的数据分析和可视化展示。同时,后端服务还负责处理来自手机 APP 的请求,将用户的控制指令转换为 MQTT 消息,通过华为云 IOT 平台重新下发给淡水湖养殖系统的 MQTT 客户端,实现了云平台、手机 APP 与养殖系统三者之间的数据闭环。
本项目的通信协议与云平台对接方案,充分利用了 MQTT 协议的特性,结合华为云 IOT 平台的强大功能,构建了一个稳定、安全且高效的淡水湖养殖系统远程监控与控制系统,极大地提升了养殖效率和管理水平。
1.5 开发工具的选择
1.5.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 是有点不适应界面的。
1.5.2 上位机开发
上位机的开发选择 Qt 框架,编程语言采用 C++;Qt 是一个 1991 年由 Qt Company 开发的跨平台 C++图形用户界面应用程序开发框架。它既可以开发 GUI 程序,也可用于开发非 GUI 程序,比如控制台工具和服务器。Qt 是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt 很容易扩展,并且允许真正地组件编程。Qt 能轻松创建具有原生 C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。
1.6 参考文献
1. 宦娟,吴帆,曹伟建等.基于窄带物联网的养殖塘水质监测系统研制[J].农业工程学报,2019,35(08):252-261.
2. Sajal Saha, Rakibul Hasan Rajib et al. “IoT Based Automated Fish Farm Aquaculture Monitoring System.” 2018 International Conference on Innovations in Science, Engineering and Technology (ICISET) (2018). 201-206.
3. K. Preetham, B. Mallikarjun et al. “Aquaculture monitoring and control system: An IoT based approach.” (2019). 1167-1170.
4. Nikitha Rosaline and Dr. S. Sathyalakshimi. “IoT Based Aquaculture Monitoring and Control System.” Journal of Physics: Conference Series (2019).
5. Rodolfo W. L. Coutinho and A. Boukerche. “Towards a Novel Architectural Design for IoT-Based Smart Marine Aquaculture.” IEEE Internet of Things Magazine (2022). 174-179.
6. 彭琛,陈伟平,曾昱.物联网技术在智能水产养殖系统中的应用[J].湖南文理学院学报(自然科学版),2021,33(04):37-41+87.
7. 杨金明,余情,朱红飞等.基于物联网技术的水产养殖智能管理系统设计[J].湖北农业科学,2016,55(16):4276-4279.
8. 冼锂东,龙祖连.基于物联网技术智慧水产养殖系统的研究设计[J].物联网技术,2022,12(02):65-68.
9. 徐晓姗.基于物联网和3G技术的智能水产养殖环境监测系统的设计与应用[J].网络安全技术与应用,2014,No.165(09):235-236.
10. 刘星桥,骆波,朱成云.基于物联网和GIS的水产养殖测控系统平台设计[J].渔业现代化,2016,43(06):16-20.
11. 李新成,林德峰,王胜涛等.基于物联网的水产养殖池塘智能管控系统设计[J].水产学杂志,2020,33(01):81-86.
12. 杨轶霞.基于物联网技术的智能水产养殖监控系统应用[J].电子技术,2021,50(05):178-179.
13. B. Paul, Shubham Agnihotri et al. “Sustainable Smart Aquaponics Farming Using IoT and Data Analytics.” Journal of Information Technology Research (2022). 1-27.
14. 林永铖,林超洋,梁志锋等.基于物联网技术的淡水养殖监控系统[J].电气技术,2015,No.192(10):59-62.
15. 基于物联网的智慧水产系统开发 [2021-08-05]
16. 吴小峰.基于物联网技术的智能水产养殖管理系统设计[J].襄阳职业技术学院学报,2015,14(06):15-18.
17. R. Ismail, K. Shafinah et al. “A Proposed Model of Fishpond Water Quality Measurement and Monitoring System based on Internet of Things (IoT).” IOP Conference Series: Earth and Environment (2020).
18. 王韵琪,尤文杰,李呈祥等.基于物联网的水产智能养殖环境监控系统设计[J].科技风,2022,No.510(34):66-68.
19. 潘春霖.基于物联网技术的智能渔业监控系统设计[J].天津农业科学,2017,23(12):26-30.
20. 汤朝婧.基于物联网技术的水产养殖系统设计[J].物联网技术,2024,14(02):82-85+89.
21. 基于物联网的水产养殖环境智能监控系统 [2014-02-20]
22. 胡琼.基于物联网的智慧水产养殖系统模型设计[J].无线互联科技,2019,16(02):33-34.
23. 李卓然.基于嵌入式Linux的水产养殖物联网监测系统设计[J].农机化研究,2019,41(11):229-233.
24. 王英杰. 基于物联网的水产养殖测控系统的设计与实现[D].江苏大学,2017.
25. 黄劲斐.物联网水产监测系统的设计[J].科技视界,2020,No.318(24):24-25.
26. 杨宁生,袁永明,孙英泽.物联网技术在我国水产养殖上的应用发展对策[J].中国工程科学,2016,18(03):57-61.
27. 陈浩成,袁永明,马晓飞等.基于物联网的水产养殖水质监控集成技术[J].现代农业科技,2013,No.608(18):324-326.
28. M. Lafont, Samuel Dupont et al. “Back to the future: IoT to improve aquaculture : Real-time monitoring and algorithmic prediction of water parameters for aquaculture needs.” Global Internet of Things Summit (2019). 1-6.
29. U. Acar, Frank Kane et al. “Designing An IoT Cloud Solution for Aquaculture.” Global Internet of Things Summit (2019). 1-6.
30. 顾丽敏.基于物联网的数字化渔业养殖监测系统设计[J].信息系统工程,2021,No.325(01):74-75.
复制代码
1.7 系统框架图
二、硬件选
2.1 STM32 系统板与 LCD 显示屏
链接:https://detail.tmall.com/item.htm?spm=a1z10.3-b-s.w4011-22005753285.30.5737310cXpmN0y&id=620032209322&rn=37a37f544a247f36db4df3cb971b28a7&abbucket=13&skuId=4380279868463
主控 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 彩屏,可以显示当前传感器数据以及一些运行状态信息。
1.44 寸 LCD 显示屏的链接:https://detail.tmall.com/item.htm?spm=a1z10.3-b-s.w4011-22005753285.30.5737310cXpmN0y&id=620032209322&rn=37a37f544a247f36db4df3cb971b28a7&abbucket=13&skuId=4380340448771
2.2 PCB 板
链接:https://detail.tmall.com/item.htm?abbucket=9&id=525489414251&ns=1&skuId=3929211749440&spm=a230r.1.14.34.16b221829wBwAI
2.3 蜂鸣器模块
链接:https://detail.tmall.com/item.htm?ali_refid=a3_430582_1006:1104520036:N:X/YIdD%20/nzZWyWHIKhozj3ahdFvQYGOd:09a834d50903c653d8893f1f618eb321&ali_trackid=1_09a834d50903c653d8893f1f618eb321&id=21124132861&spm=a230r.1.14.1&skuId=4319138558993
2.4 电源扩展接口(x2)
买 2 个扩展板,方便扩展 5v 电源 和 3.3V 电源。
链接:https://item.taobao.com/item.htm?id=647681090119&skuId=4672158745999&spm=a1z0d.6639537/tb.1997196601.4.754374841n18eN
2.5 ESP8266-WIFI(ESP-01S)
链接:https://detail.tmall.com/item.htm?spm=a21n57.1.item.39.42bb523cSULOQH&priceTId=213e38c717199909300956449ecfca&utparam=%7B%22aplus_abtest%22:%228edbe31ddaa06b3212d4f9a079965932%22%7D&id=757493104729&ns=1&abbucket=13&skuId=5396864971038
2.6 母对母杜邦线(X2)
作用: 连接模块与单片机。
链接:https://detail.tmall.com/item.htm?ali_refid=a3_430582_1006:1104520036:N:MsF9mE9KLTC2IibWJh%20K1A==:adaa6d3d7abe6f1f07b87a36416ee4fb&ali_trackid=1_adaa6d3d7abe6f1f07b87a36416ee4fb&id=14466195609&skuId=3108214440215&spm=a230r.1.14.1
2.7 继电器(x3)
链接:https://detail.tmall.com/item.htm?id=15909056050&ali_refid=a3_430582_1006:1104520036:N:sGzbt9RI84M4qtD4oBlF3Q==:94221238ccf10c5aeb7c31df1a993981&ali_trackid=1_94221238ccf10c5aeb7c31df1a993981&spm=a230r.1.14.1&skuId=3931798090624
2.8 稳压模块
链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.2921523cQeodt2&id=16606969730&ns=1&abbucket=7#detail
2.9 5V2A 电源插头
链接:https://item.taobao.com/item.htm?spm=a1z0d.7625083.1998302264.6.5c5f4e69WfgpgO&id=616513772095
2.10 5V 28BYJ4 步进电机
链接: https://detail.tmall.com/item.htm?id=41303683115&ali_refid=a3_430582_1006:1109983619:N:2nt6mzKrI7Z4Z+4S7irb6TVt9Q0NFo+R:c6bbda7f39718df42ff324c85021cf7e&ali_trackid=1_c6bbda7f39718df42ff324c85021cf7e&spm=a230r.1.14.1
步进电机28BYJ-48名称含义:
28:表示步进电机的有效最大外径为28毫米
B: 表示步进电机“步”字汉语拼音首字母
Y: 表示永磁式“永”字汉语拼音首字母
J: 表示减速型“减”字汉语拼音首字母
BYJ: 组合即为永磁式减速步进电机
48:表示四相八拍
5V:表示额定电压为5V,且为直流电压
步进角:5.625度,就是1个脉冲信号转5.625度,64个信号转360度。
减速比:1/64,电机壳里边的电机转64圈,电机壳外边的部分转1圈。
四相:ABCD四相(电机定子上有8个齿,相对的2个齿是1相),
八拍:(A-AB-B-BC-C-CD-D-DA-A)。
一拍就是一个脉冲信号,完成一个循环用8个脉冲信号。
当通电状态的改变完成一个循环时,转子转过一个齿距。转8个齿距就是一圈,8×8=64
64拍,64个脉冲信号转一圈360度。
复制代码
2.11 增氧泵
链接:https://item.taobao.com/item.htm?id=613985790640&ali_refid=a3_430582_1006:1226360064:N:2BeiTZ6q9YYxgXM%2BVERvrA%2FFUJg%2FXa1Y:1cfd93a70ad4f25752bf8ced2f95c95f&ali_trackid=1_1cfd93a70ad4f25752bf8ced2f95c95f&spm=a230r.1.14.1#detail
2.12 TDS 检测传感器
链接:https://item.taobao.com/item.htm?spm=a21n57.1.item.99.42bb523cSULOQH&priceTId=213e385317199920382462481e622d&utparam=%7B%22aplus_abtest%22:%22839189b7b1235738d42966f0795b6672%22%7D&id=649460401747&ns=1&abbucket=13&skuId=5588303190398
TDS (Total Dissolved Solids)、中文名总溶解固体、又称溶解性固体、又称溶解性固体总量、表明 1 升水肿容有多少毫克溶解性固体、一般来说、TDS 值越高、表示水中含有溶解物越多、水就越不洁净、虽然在特定情况下 TDS 并不能有效反映水质的情况、但作为一种可快速检测的参数、TDS 目前还可以作为有效的在水质情况反映参数来作为参考。常用的 TDS 检测设备为 TDS 笔、虽然价格低廉、简单易用、但不能把数据传给控制系统、做长时间的在线监测、并做水质状况分析、使用专门的仪器、虽然能传数据、精度也高、但价格很贵、为此这款 TDS 传感器模块、即插即用、使用简单方便、测量用的激励源采用交流信号、可有效防止探头极化、延长探头寿命的同时、也增加了输出信号的稳定性、TDS 探头为防水探头、可长期侵入水中测量、该产品可以应用于生活用水、水培等领域的水质检测、有了这个传感器、可轻松 DIY--套 TDS 检测仪了、轻松检测水的洁净程度。
2.13 浑浊度检测传感器
链接:https://item.taobao.com/item.htm?spm=a21n57.1.item.2.426f523cArKJnA&priceTId=213e394a17199924273215703ee6b5&utparam=%7B%22aplus_abtest%22:%22dc113eb55c74290e427c17dd33dfe431%22%7D&id=634709383404&ns=1&abbucket=13&skuId=4534228449927
2.14 抽水马达(X2)
进水和出水控制
链接地址:https://detail.tmall.com/item.htm?abbucket=6&id=599450427587&ns=1&skuId=4531922407703&spm=a21n57.1.0.0.438e523cDKEtmS
2.15 USB 母头(接电机使用的)
链接:https://item.taobao.com/item.htm?spm=a1z09.2.0.0.4ff12e8dBjY7rQ&id=660481026591&_u=31pq7ueodfb1&skuId=4760127756241
2.16 PH 值检测传感器
链接:https://item.taobao.com/item.htm?id=634709383404&ali_refid=a3_430582_1006:1102529383:N:JlEYFEO17l7srXd1PIPepQ==:156a9250417b127e9fef702f7e0ad206&ali_trackid=1_156a9250417b127e9fef702f7e0ad206&spm=a21n57.1.item.49&skuId=5411506393849
三、部署华为云物联网平台
华为云官网: 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)
接入地址:3cee0d1a66.st1.iotda-device.cn-north-4.myhuaweicloud.com
复制代码
根据域名地址得到 IP 地址信息:
C:\Users\11266>ping 3cee0d1a66.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping 3cee0d1a66.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=94
来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=94
来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=94
来自 117.78.5.125 的回复: 字节=32 时间=43ms TTL=94
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 42ms,最长 = 43ms,平均 = 42ms
C:\Users\11266>
复制代码
MQTT 协议接入端口号有两个,1883 是非加密端口,8883 是证书加密端口,单片机无法加载证书,所以使用 1883 端口比较合适。 接下来的 ESP8266 就采用 1883 端口连接华为云物联网平台。
3.3 创建产品
(1)创建产品
(2)填写产品信息
根据自己产品名字填写,下面的设备类型选择自定义类型。
(3)产品创建成功
(4)添加自定义模型
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
模型简单来说: 就是存放设备上传到云平台的数据。比如:水温、换水电机、充氧电机、水质状态、充氧时间间隔等等,我们都可以单独创建一个模型保存。
当前设备需要与云平台交互的属性如下: 接下来就按照下面的属性创建 华为云平台的模型。
上传到华为云IOT平台的属性:
PH值检测 PH 整型
浑浊度检测 water_quality 整型
温度检测 DS18B20 浮点数
换水电机 water_motor BOOL类型
充氧电机 oxygen_motor BOOL类型
定时充氧 oxygen_motor_time 整型
水温阀值 DS18B20_MAX 整型
换水电机-出水 clean_motor BOOL类型
照明灯 lighting_led BOOL类型
水位检测 water_monitor 整型
复制代码
先点击自定义模型。
再创建一个服务 ID。
接着点击新增属性。
PH 值检测 PH 整型
浑浊度检测 water_quality 整型
温度检测 DS18B20 浮点数
换水电机 water_motor BOOL 类型
充氧电机 oxygen_motor BOOL 类型
定时充氧 oxygen_motor_time 整型
水温阀值 DS18B20_MAX 整型
** 换水电机-出水 clean_motor BOOL 类型**
照明灯 lighting_led BOOL 类型
水位检测 water_monitor 整型
3.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成 MQTT 三元组的时候需要使用。
当前设备的信息如下:
{
"device_id": "65dd4fc72ccc1a583879a7e1_dev1",
"secret": "12345678"
}
复制代码
(4) 设备创建完成
点击详情:
这就是设备页面:
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 协议使用限制
(3)主题订阅格式
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/65dd4fc72ccc1a583879a7e1_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/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"PH":20,"water_quality":60,"DS18B20":14.1,"oxygen_motor_time":10,"DS18B20_MAX":15,"water_monitor":10,"clean_motor":1,"lighting_led":1,"water_motor":1,"oxygen_motor":1}}]}
复制代码
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
复制代码
(2)生成 MQTT 三元组
华为云提供了一个在线工具,用来生成 MQTT 鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到 MQTT 的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过 MQTT 协议登录鉴权的时候,填入参数即可。
ClientId 65dd4fc72ccc1a583879a7e1_dev1_0_0_2024022705
Username 65dd4fc72ccc1a583879a7e1_dev1
Password 91c783515515d883c533df05ef0e15ed526e583cfb141de54e9ba1545fba0513
复制代码
3.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到 MQTT 登录信息。 接下来就用 MQTT 客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
当前模拟设备登录,调试设备的 MQTT 客户端软件可以在这里下载:
https://download.csdn.net/download/xiaolong1126626497/18784012
(1)填入登录信息
打开 MQTT 客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
(3)MQTT 登录测试参数总结
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
ClientId 65dd4fc72ccc1a583879a7e1_dev1_0_0_2024022705
Username 65dd4fc72ccc1a583879a7e1_dev1
Password 91c783515515d883c533df05ef0e15ed526e583cfb141de54e9ba1545fba0513
订阅主题: $oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/messages/down
发布主题: $oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/report
发布的数据:
{"services": [{"service_id": "stm32","properties":{"PH":20,"water_quality":60,"DS18B20":14.1,"oxygen_motor_time":10,"DS18B20_MAX":15,"water_monitor":10,"clean_motor":1,"lighting_led":1,"water_motor":1,"oxygen_motor":1}}]}
复制代码
四、上位机开发
为了方便查看设备上传的数据,对设备进行远程控制,接下来利用 Qt 开发一款 Android 和 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
编译器即可,其他的不管默认就行,直接点击下一步继续安装。
说明: 我这里只是介绍 PC 端的环境搭建(这个比较简单)。 Android 的开发环境比较麻烦,如果想学习 Android 开发,想编译 Android 程序的 APP,可以去我的博客里看详细文章。
Android 环境搭建的博客链接: https://blog.csdn.net/xiaolong1126626497/article/details/117254453
选择 MinGW 32-bit 编译器:
4.2 创建 IAM 账户
创建一个 IAM 账户,因为接下来开发上位机,需要使用云平台的 API 接口,这些接口都需要 token 进行鉴权。简单来说,就是身份的认证。 调用接口获取 Token 时,就需要填写 IAM 账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】获取项目凭证 ** 点击左上角用户名,选择下拉菜单里的我的凭证
项目凭证:
756f8211ec6847c3a5ee4061b37d4ddb
复制代码
【2】创建 IAM 用户
鼠标放在左上角头像上,在下拉菜单里选择统一身份认证
。
点击左上角创建用户
。
创建成功:
【3】创建完成
用户信息如下:
主用户名 hid_x13ruy5yb1ruano
IAM用户 ds_abc
密码 DS12345678
复制代码
4.3 获取影子数据
帮助文档: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": "65dd4fc72ccc1a583879a7e1_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"PH": 20,
"water_quality": 60,
"DS18B20": 14.1,
"oxygen_motor_time": 10,
"DS18B20_MAX": 15,
"water_monitor": 10,
"clean_motor": 1,
"lighting_led": 1,
"water_motor": 1,
"oxygen_motor": 1
},
"event_time": "20240227T052838Z"
},
"version": 0
}
]
}
复制代码
调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用 Qt 写代码访问此链接,获取影子数据,完成上位机开发。
4.4 修改设备属性
地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html
接口说明
设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。
复制代码
修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。
在线调试地址:
https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties
修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用 MQTT 客户端登录服务器,模拟设备上线。
然后进行调试,测试数据远程下发给设备。
【1】利用 MQTT 客户端先登录设备 (这是同步命令,必须在线才能调试)
【2】点击调试
填入的测试数据:
{"services":{"oxygen_motor":1}}
复制代码
【4】可以看到,MQTT 客户端软件上已经收到了服务器下发的消息
由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程(当然,设备不回应也没影响),设备响应了服务器才能确定数据下发成功。
MQTT 设备端如何响应呢?
设备响应格式说明:https://support.huaweicloud.com/api-iothub/iot_06_v5_3008.html
4.5 新建上位机工程
前面 2 讲解了需要用的 API 接口,接下来就使用 Qt 设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
4.6 设计 UI 界面与工程配置
【1】打开 UI 文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
【3】配置 pro 工程文件
其中,加了注释的代码,表示 Android 环境才需要,而当前是配置的 Windows 下的开发环境,在 Windows 下编译,就将其注释掉,暂时不使用。
【4】配置软件图标
在工程文件下方,增加当前软件的图标配置,图标需要是ICO
格式,将图标放在工程同级路径下,在工程配置文件里指定好图标名称。
4.7 设计代码
【1】获取 token
调用华为云的 API 都需要填 token 参数,先看帮助文章,了解如何获取 token。
帮助文档:https://support.huaweicloud.com/api-iam/iam_30_0001.html
根据帮助文档,写完成下面代码编写:
/*
功能: 获取token
*/
void Widget::GetToken()
{
//表示获取token
function_select=3;
QString requestUrl;
QNetworkRequest request;
//设置请求地址
QUrl url;
//获取token请求地址
requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens")
.arg(SERVER_ID);
//自己创建的TCP服务器,测试用
//requestUrl="http://10.0.0.6:8080";
//设置数据提交格式
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
//构造请求
url.setUrl(requestUrl);
request.setUrl(url);
QString text =QString("{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":"
"{\"user\":{\"domain\": {"
"\"name\":\"%1\"},\"name\": \"%2\",\"password\": \"%3\"}}},"
"\"scope\":{\"project\":{\"name\":\"%4\"}}}}")
.arg(MAIN_USER)
.arg(IAM_USER)
.arg(IAM_PASSWORD)
.arg(SERVER_ID);
//发送请求
manager->post(request, text.toUtf8());
}
复制代码
【2】时间校准
前面已经介绍了如何发送数据给设备,也就是修改属性的接口: https://support.huaweicloud.com/api-iothub/iot_06_v5_0035.html
根据文档介绍, 完成代码编写:
void Widget::on_pushButton_rtc_clicked()
{
QDateTime time = QDateTime::currentDateTime();//获取系统现在的时间
QString str="按照系统时间校准:\n";
str+= time.toString("yyyy-MM-dd hh:mm:ss ddd"); //设置显示格式
QMessageBox::about(this,"校准设备RTC时间",str);
//获取本地时间校准物联网开发板RTC时间
str=time.toString("yyyyMMddhhmmss"); //设置显示格式
//修改属性
function_select=13;
QString requestUrl;
QNetworkRequest request;
//设置请求地址
QUrl url;
//修改属性的地址
requestUrl=QString("https://16cc7801b6.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/%1/devices/%2/properties")
.arg(PROJECT_ID).arg(device_id);
//设置数据提交格式
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
//设置token
request.setRawHeader("X-Auth-Token",Token);
//构造请求
url.setUrl(requestUrl);
request.setUrl(url);
//打包请求参数赋值
QString post_param=QString("{\"services\":{\"rtc_set\":\"%1\"}}").arg(str);
//发送请求
manager->put(request, post_param.toUtf8());
}
复制代码
【3】获取影子数据
前面 4.3 小节介绍了影子数据获取接口。下面是对应编写的代码:
//查询设备属性
void Widget::Get_device_properties()
{
//label_time
QDateTime current_date_time =QDateTime::currentDateTime();
QString current_date =current_date_time.toString("yyyy/MM/dd hh:mm:ss");
ui->label_time->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->label_time->setText(current_date);
function_select=0;
QString requestUrl;
QNetworkRequest request;
//设置请求地址
QUrl url;
//获取token请求地址
requestUrl = QString("https://%1:443/v5/iot/%2/devices/%3/shadow")
.arg(IP_ADDR)
.arg(PROJECT_ID)
.arg(device_id);
//设置数据提交格式
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
//设置token
request.setRawHeader("X-Auth-Token",Token);
//构造请求
url.setUrl(requestUrl);
request.setUrl(url);
//发送请求
manager->get(request);
}
复制代码
【4】解析数据更新界面
//解析反馈结果
void Widget::replyFinished(QNetworkReply *reply)
{
QString displayInfo;
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
//读取所有数据
QByteArray replyData = reply->readAll();
qDebug()<<"状态码:"<<statusCode;
qDebug()<<"反馈的数据:"<<QString(replyData);
//更新token
if(function_select==3)
{
displayInfo="token 更新失败.";
//读取HTTP响应头的数据
QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs();
qDebug()<<"HTTP响应头数量:"<<RawHeader.size();
qDebug()<<"RawHeader:"<<RawHeader;
for(int i=0;i<RawHeader.size();i++)
{
QString first=RawHeader.at(i).first;
QString second=RawHeader.at(i).second;
if(first=="X-Subject-Token")
{
Token=second.toUtf8();
displayInfo="token 更新成功.";
//保存到文件
SaveDataToFile(Token);
break;
}
}
QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok);
return;
}
//判断状态码
if(200 != statusCode)
{
//解析数据
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判断是否是对象,然后开始解析数据
if(document.isObject())
{
QString error_str="";
QJsonObject obj = document.object();
QString error_code;
//解析错误代码
if(obj.contains("error_code"))
{
error_code=obj.take("error_code").toString();
error_str+="错误代码:";
error_str+=error_code;
error_str+="\n";
}
if(obj.contains("error_msg"))
{
error_str+="错误消息:";
error_str+=obj.take("error_msg").toString();
error_str+="\n";
}
//显示错误代码
QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok);
}
}
return;
}
//设置属性
if(function_select==12 || function_select==13)
{
//解析数据
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判断是否是对象,然后开始解析数据
if(document.isObject())
{
QJsonObject obj = document.object();
if(obj.contains("response"))
{
QJsonObject obj1=obj.take("response").toObject();
int val=0;
QString success;
if(obj1.contains("result_code"))
{
val=obj1.take("result_code").toInt();
}
if(obj1.contains("result_desc"))
{
success=obj1.take("result_desc").toString();
}
if(val==0 && success =="success")
{
//显示状态
QMessageBox::information(this,"提示","远程命令操作完成.",QMessageBox::Ok,QMessageBox::Ok);
return;
}
else
{
//显示状态
QMessageBox::information(this,"提示","设备未正确回应.请检查设备网络.",QMessageBox::Ok,QMessageBox::Ok);
return;
}
}
}
}
}
//查询设备属性
if(function_select==0)
{
//解析数据
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判断是否是对象,然后开始解析数据
if(document.isObject())
{
QJsonObject obj = document.object();
if(obj.contains("shadow"))
{
QJsonArray array=obj.take("shadow").toArray();
for(int i=0;i<array.size();i++)
{
QJsonObject obj2=array.at(i).toObject();
if(obj2.contains("reported"))
{
QJsonObject obj3=obj2.take("reported").toObject();
if(obj3.contains("properties"))
{
QJsonObject properties=obj3.take("properties").toObject();
qDebug()<<"开始解析数据....";
int PH; //PH值检测
int water_quality; //浑浊度检测
double DS18B20; //温度检测
int water_monitor; //水位检测
int clean_motor; // 换水电机-出水
int lighting_led; //照明灯
int water_motor; //换水电机
int oxygen_motor; //充氧电机
int oxygen_motor_time; //定时充氧
int DS18B20_MAX; //水温阀值
//提取数据
water_quality=properties.take("water_quality").toInt();
DS18B20=properties.take("DS18B20").toDouble();
water_monitor=properties.take("water_monitor").toInt();
clean_motor=properties.take("clean_motor").toInt();
water_motor=properties.take("water_motor").toInt();
oxygen_motor=properties.take("oxygen_motor").toInt();
oxygen_motor_time=properties.take("oxygen_motor_time").toInt();
DS18B20_MAX=properties.take("DS18B20_MAX").toInt();
PH=properties.take("PH").toInt();
lighting_led=properties.take("lighting_led").toInt();
//鱼缸水温
ui->label_DS18B20->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->label_DS18B20->setText(QString("%1℃").arg(DS18B20));
//鱼缸浑浊度检测
ui->label_water_quality->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->label_water_quality->setText(QString("%1%").arg(water_quality));
//鱼缸水位检测
ui->label_water_monitor->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->label_water_monitor->setText(QString("%1%").arg(water_monitor));
//鱼缸PH值检测
ui->label_PH->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->label_PH->setText(QString("%1").arg(PH));
//定时充氧
ui->oxygen_food->setValue(oxygen_motor_time);
//水温阀值
ui->temp_max->setValue(DS18B20_MAX);
// 换水电机-出水
ui->pushButton_clean_motor->setChecked(!!clean_motor);
//照明灯
ui->pushButton_lighting_led->setChecked(!!lighting_led);
//换水电机
ui->pushButton_water_motor->setChecked(!!water_motor);
//充氧电机
ui->pushButton_oxygen_motor->setChecked(!!oxygen_motor);
}
}
}
}
}
}
return;
}
}
复制代码
【5】下面命令给设备端
/// 像设备端发送命令
/// \brief Widget::MQTT_Cmd_Send
/// \param cmd
///
void Widget::MQTT_Cmd_Send(QString cmd)
{
//修改属性
function_select=13;
QString requestUrl;
QNetworkRequest request;
//设置请求地址
QUrl url;
//修改属性的地址
requestUrl=QString("https://%1:443/v5/iot/%2/devices/%3/properties")
.arg(IP_ADDR).arg(PROJECT_ID).arg(device_id);
//设置数据提交格式
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
//设置token
request.setRawHeader("X-Auth-Token",Token);
//构造请求
url.setUrl(requestUrl);
request.setUrl(url);
//打包请求参数赋值
QString post_param=QString("{\"services\":{%1}}").arg(cmd);
//发送请求
manager->put(request, post_param.toUtf8());
}
复制代码
4.8 编译 Windows 上位机
编译之后的效果:
4.9 配置 Android 环境
如果想编译 Android 手机 APP,可以参考此章节配置。 但是: 生成 Android 手机 APP 必须要先自己配置 Android 环境,这个配置相对比较复杂。
配置 Android 环境可以参考教程: https://blog.csdn.net/xiaolong1126626497/article/details/117254453
【1】创建 Android 配置文件
创建完成。
【2】配置 Android 图标与名称
【3】编译 Android 上位机
Qt 本身是跨平台的,直接选择 Android 的编译器,就可以将程序编译到 Android 平台。
然后点击构建。
成功之后,在目录下可以看到生成的apk
文件,也就是 Android 手机的安装包,电脑端使用QQ
发送给手机 QQ,手机登录 QQ 接收,就能直接安装。
生成的apk
的目录在哪里呢?
从这里可以查看。
知道目录在哪里之后,在 Windows 的文件资源管理器里,找到路径,具体看下图,找到生成的 apk 文件。
D:\linux-share-dir\QT\build-Smarts_Fishbowl_HuaWeiYunIot-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release\android-build\build\outputs\apk\debug
复制代码
4.10 交互命令
上位机给设备下发的命令:
len:140,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=17f9bd7d-d81a-493c-94fa-5d7fc4d637a3{"services":{"lighting_led":1}}
len:139,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=bc1d53f6-dd28-48f6-a39c-fea3f1c7c45c{"services":{"clean_motor":0}}
len:140,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=2fb1619b-9d48-4bc2-9d07-054895275005{"services":{"oxygen_motor":1}}
len:139,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=9b2b0c35-12ea-4c6a-a43d-496d229b1783{"services":{"water_motor":1}}
len:145,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=f27f9be3-4e78-483b-82ae-d145ba78ce60{"services":{"oxygen_motor_time":1}}
len:136,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=09f0831e-6e9b-4220-a3d0-25912c67b48e{"services":{"temp_max":1}}
len:149,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=696f8046-fcf7-4307-b9a5-9604d31c8b94{"services":{"rtc_time":20240227162845}}
复制代码
五、STM32 设备端代码设计
了解 STM32F103 的芯片配置:
5.1 硬件连线
1. ESP8266 WIFI接线
ATK-ESP8266串口WIFI模块与STM32的串口2相连接。
PA2(TX)--RXD 模块接收脚
PA3(RX)--TXD 模块发送脚
GND---GND 地
VCC---VCC 3.3V
2. TFT 1.44 寸彩屏接线
GND 电源地
VCC 3.3v电源
SCL 接PC8(SCL)
SDA 接PC9(SDA)
RST 接PC10
DC 接PB7
CS 接PB8
BL 接PB11
3. DS18B20温度传感器
VCC--3.3v
GND---GND
OUT---PB3
4. SG90舵机-模拟鱼缸换水-出水
VCC--->5V
OUT--->PA7
GND--->GND
5. 水质传感器(ADC通道1)
VCC--->5V
GND--->GND
OUT--->PA1
6. SG90舵机-模拟鱼缸换水-进水
OUT----PB5
GND---GND 地
VCC---5v
7. 鱼缸水温加热--继电器控制
GND----GND
VCC---3.3V
OUT---PB4
8. 增氧泵--继电器控制
GND----GND
VCC---5V
OUT---PC11
9. PH值检测
VCC--->3.3V
GND--->GND
OUT--->PA4
10. 照明灯开关
VCC--->3.3V
GND--->GND
OUT--->PA6
11. 水位检测
VCC--->3.3V
GND--->GND
OUT--->PA5
13. 板载LED灯接线(这个不用接,这是开发板本身的)
LED1---PA8
LED2---PD2
14. 板载按键接线(这个不用接,这是开发板本身的)
K0---PA0
K1---PC5
K2---PA15
复制代码
5.2 取模软件使用
本地设备的 LCD 显示屏上会显示各种传感器数据,需要用到中文、数字、字母。
这是软件的设置页面:
5.3 通信协议
STM32 设备端与华为云服务器通信的协议:
//如果WIFI已经连接到网络
if(esp8266_connect)
{
//组合JSON报文数据
sprintf(data_buff,"{\"services\": [{\"service_id\": \"stm32\",\"properties\":{\"PH\":%d,\"water_quality\":%d,\"DS18B20\":%.1f,\"oxygen_motor_time\":%d,\"DS18B20_MAX\":%d,\"water_monitor\":%d,\"clean_motor\":%d,\"lighting_led\":%d,\"water_motor\":%d,\"oxygen_motor\":%d}}]}",
PH,percentage,DS18B20,oxygen_motor_time,DS18B20_MAX,water_monitor,clean_motor,lighting_led,water_motor,oxygen_motor);
//上传数据
MQTT_PublishData(POST_TOPIC,data_buff,0);
printf("更新一次数据.\r\n");
}
复制代码
手机 APP 向 STM32 下发的数据协议:
len:140,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=17f9bd7d-d81a-493c-94fa-5d7fc4d637a3{"services":{"lighting_led":1}}
len:139,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=bc1d53f6-dd28-48f6-a39c-fea3f1c7c45c{"services":{"clean_motor":0}}
len:140,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=2fb1619b-9d48-4bc2-9d07-054895275005{"services":{"oxygen_motor":1}}
len:139,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=9b2b0c35-12ea-4c6a-a43d-496d229b1783{"services":{"water_motor":1}}
len:145,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=f27f9be3-4e78-483b-82ae-d145ba78ce60{"services":{"oxygen_motor_time":1}}
len:136,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=09f0831e-6e9b-4220-a3d0-25912c67b48e{"services":{"temp_max":1}}
len:149,Data:l$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/request_id=696f8046-fcf7-4307-b9a5-9604d31c8b94{"services":{"rtc_time":20240227162845}}
复制代码
5.4 按键的功能说明
开发板自带了 4 个按键,其中第 2 是个RST
是复位键、另外 3 个按键是可编程的独立按键。
按键 1 功能: 显示翻页。
按键 2 功能: 启动-SG90 电机。
按键 3: 功能: 开启或关闭照明灯。
详细代码如下:
key=KEY_Scan();
if(key)printf("key=%d\r\n",key);
//按下按键1进行翻页
if(key==1)
{
Lcd_Clear(0); //清屏为黑色
page++;
if(page>=2)page=0;
if(page==0)
{
page_1();
}
else
{
page_2();
}
}
//按下按键2控制照明灯打开
else if(key==2)
{
lighting_led=!lighting_led;
}
//按下按键3控制 控制
else if(key==3)
{
//清洗3次
open_clean_motor();
close_clean_motor();
delay_ms(1000);
open_clean_motor();
close_clean_motor();
delay_ms(1000);
open_clean_motor();
close_clean_motor();
}
复制代码
5.5 自动模式控制逻辑
在 while(1)循环里,每 1 秒钟的周期整体执行一次。读取传感器的数据,处理,上传到华为云 IOT 平台。
如果检测到水质超过阀值,会启用换水功能自动换水。
如果检测到温度低于设置阀值,会启用加热功能自动加热升温。
如果检测到充氧时间到达,会启用充氧功能自动充氧。
在 LCD 显示屏上会实时当前检测到所有数据。
具体代码如下:
//轮询时间到达
if(time_cnt>20)
{
time_cnt=0;
LED1=!LED1;
//--------------------------------采集数据--------------------------------
//读取水温度
DS18B20_int=DS18B20_Get_Temp();
//转换温度为浮点数
sprintf(mqtt_message,"%d.%d",DS18B20_int>>4,DS18B20_int&0xF);
DS18B20=atof(mqtt_message);
//读取水质
//水质: 纯净水300多 自来水800多 直接纯牛奶2000
water_quality=GetAvgAdcCHxDATA(1);
percentage = water_quality / 3000.0 * 100.0;
//读取PH值
PH=GetAvgAdcCHxDATA(4);
if(PH>=4000)PH=5;
//读取水位
//水越深值越大,最大值1640
water_monitor=GetAvgAdcCHxDATA(5);
water_monitor= water_monitor / 1640.0 * 100.0;
//--------------------------------下次充氧时间倒计时计算--------------------------------
if(oxygen_food_sec<=0)
{
//默认充氧5秒
oxygen_motor=1;
DelayMs(5000);
oxygen_motor=0;
//时间归位
oxygen_food_sec=oxygen_motor_time*60;
}
//水质太差 就启动换水
if(percentage>80)
{
//正转 换水电机
open_water_motor();
}
else
{
//--反转 换水电机
close_water_motor();
}
//显示页面1
if(page==0)
{
//实时时间与日期
Gui_DrawFont_GBK16(16*1,16*6+2,WHITE,0,(u8*)date_buff);
Gui_DrawFont_GBK16(16*1+8,16*7+2,WHITE,0,(u8*)time_buff);
//显示温度
sprintf(mqtt_message,"%4.1fC",DS18B20);
Gui_DrawFont_GBK16(72,16*0+2,WHITE,0,(u8*)mqtt_message);
// printf("%s\r\n",mqtt_message);
//显示水质
sprintf(mqtt_message,"%4d%%",percentage);
Gui_DrawFont_GBK16(72,16*1+2,WHITE,0,(u8*)mqtt_message);
//PH值
sprintf(mqtt_message,"%4d",PH);
Gui_DrawFont_GBK16(72,16*2+2,WHITE,0,(u8*)mqtt_message);
//充氧间隔
sprintf(mqtt_message,"%4dm",oxygen_motor_time);
Gui_DrawFont_GBK16(72,16*3+2,WHITE,0,(u8*)mqtt_message);
//恒温温度
sprintf(mqtt_message,"%4dC",DS18B20_MAX);
Gui_DrawFont_GBK16(72,16*4+2,WHITE,0,(u8*)mqtt_message);
//水位距离
sprintf(mqtt_message,"%4d%%",water_monitor);
Gui_DrawFont_GBK16(72,16*5+2,WHITE,0,(u8*)mqtt_message);
}
else if(page==1)
{
//实时时间与日期
Gui_DrawFont_GBK16(16*0,16*2+2,WHITE,0,(u8*)date_buff);
Gui_DrawFont_GBK16(16*0+8,16*3+2,WHITE,0,(u8*)time_buff);
char *p=format_time(oxygen_food_sec);
Gui_DrawFont_GBK16(0,16*5+2,WHITE,0,(u8*)p);
}
//恒温判断. 如果小于温度阀值
if(DS18B20<DS18B20_MAX)
{
temp_heat=1; //开启加热
}
else
{
temp_heat=0; //停止加热
}
//如果WIFI已经连接到网络
if(esp8266_connect)
{
//组合JSON报文数据
sprintf(data_buff,"{\"services\": [{\"service_id\": \"stm32\",\"properties\":{\"PH\":%d,\"water_quality\":%d,\"DS18B20\":%.1f,\"oxygen_motor_time\":%d,\"DS18B20_MAX\":%d,\"water_monitor\":%d,\"clean_motor\":%d,\"lighting_led\":%d,\"water_motor\":%d,\"oxygen_motor\":%d}}]}",
PH,percentage,DS18B20,oxygen_motor_time,DS18B20_MAX,water_monitor,clean_motor,lighting_led,water_motor,oxygen_motor);
//上传数据
MQTT_PublishData(POST_TOPIC,data_buff,0);
printf("更新一次数据.\r\n");
}
}
复制代码
5.6 手机 APP 远程控制
如果 STM32 收到 APP 远程下发的控制指令之后,会进行判断处理。
具体代码如下:
// 接收WIFI返回的数据
if(USART2_RX_FLAG)
{
USART2_RX_BUFFER[USART2_RX_CNT]='\0';
printf("WIFI收到数据:\r\n");
//向串口打印服务器返回的数据
for(i=0;i<USART2_RX_CNT;i++)
{
printf("%c",USART2_RX_BUFFER[i]);
}
#if 0
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=8401c98b-268e-4382-82fd-b69d78275020{"services":{"motor_food":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=22e6e1ff-3e5c-4a25-bbbf-fd4e8f314a68{"services":{"led_sw":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=a15b8f8d-06cf-4597-8cd9-f92e3e0a7b4f{"services":{"motor_oxygen":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=c5b708d8-7c58-43c8-9943-64e25fe6f4df{"services":{"motor_water":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=852659ce-183c-42bf-b896-f9c1e960f405{"services":{"time_food":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=e401d20d-8c10-453d-b1cf-ba7c97a66f7d{"services":{"oxygen_food":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=dd10158b-3da0-45a2-be68-27ddb14a797c{"services":{"temp_max":15}}
#endif
if(USART2_RX_CNT>5)
{
//开 照明灯
if(strstr((char*)&USART2_RX_BUFFER[5],"\"lighting_led\":1"))
{
lighting_led=1;
}
//关 照明灯
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"lighting_led\":0"))
{
lighting_led=0;
}
//开 换水电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"water_motor\":1"))
{
//--反转 换水电机
close_water_motor();
}
//关 换水电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"water_motor\":0"))
{
//正转 换水电机
open_water_motor();
}
//开 充氧电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor\":1"))
{
oxygen_motor=1;
}
//关 充氧电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor\":0"))
{
oxygen_motor=0;
}
//开 换水电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"clean_motor\":1"))
{
//--反转 换水电机-出水
close_clean_motor();
}
//关 换水电机-出水
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"clean_motor\":0"))
{
//正转 换水电机-出水
open_clean_motor();
}
//充氧时间间隔
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor_time\":"))
{
char *p=strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor_time\":");
oxygen_motor_time=atoi(p+20);
oxygen_food_sec = oxygen_motor_time*60; //转为秒单位
printf("oxygen_motor_time=%d\r\n",oxygen_motor_time);
}
//加热温度上限阀值
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"temp_max\":"))
{
char *p=strstr((char*)&USART2_RX_BUFFER[5],"\"temp_max\":");
DS18B20_MAX=atoi(p+11);
printf("DS18B20_MAX=%d\r\n",DS18B20_MAX);
}
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"rtc_time\":"))
{
char *p=strstr((char*)&USART2_RX_BUFFER[5],"\"rtc_time\":");
p+=11; //向后偏移,指向正确的时间
char *time=p;
int tm_sec; //秒
int tm_min; //分
int tm_hour; //时
int tm_mday; //日
int tm_mon; //月
int tm_year; //年
tm_year=(time[0]-48)*1000+(time[1]-48)*100+(time[2]-48)*10+(time[3]-48)*1;
tm_mon=(time[4]-48)*10+(time[5]-48)*1;
tm_mday=(time[6]-48)*10+(time[7]-48)*1;
tm_hour=(time[8]-48)*10+(time[9]-48)*1;
tm_min=(time[10]-48)*10+(time[11]-48)*1;
tm_sec=(time[12]-48)*10+(time[13]-48)*1;
SetRtcTime(tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec);
printf("RTC时间设置成功:%d-%d-%d %d:%d:%d\r\n",tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec);
}
//下发指令请求回应给服务器
if(strstr((char*)&USART2_RX_BUFFER[5],"properties/set/request_id"))
{
char *p=NULL;
p=strstr((char*)&USART2_RX_BUFFER[5],"request_id");
if(p)
{
//解析数据
//$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"gps"}
strncpy(request_id,p,47);
}
//上报数据
sprintf(mqtt_message,"{\"result_code\": 0,\"result_desc\": \"success\"}");
sprintf(data_buff,"$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/response/%s",
request_id);
MQTT_PublishData(data_buff,mqtt_message,0);
printf("发布主题:%s\r\n",data_buff);
printf("发布数据:%s\r\n",mqtt_message);
}
}
USART2_RX_CNT=0;
USART2_RX_FLAG=0;
}
复制代码
5.7 KEIL 工程
六、使用 STM32 代码的流程以及注意事项
6.1 第一步
照着设计文档,买回来硬件模块。 然后照着第五章节的第 1 小节(5.1 章节)的硬件连线说明,将模块与 STM32 开发板之间连接好线。
注意:LCD 显示屏,直接插上去就行了,买的开发板上本身就有排母,照着接,看准开发板板子上的 丝印说明。
6.2 第二步
将 Android 手机 APP 安装到自己的 Android 手机上,打开手机 APP,点击更新 Token 按钮,点击更新数据,然后就可以了。 (这时候无法点击控制按钮,点击会报错,因为设备没有在线,无法进行远程控制设备,这是正常的)
6.3 第三步
使用手机开一个热点。(1)名字设置为: abc(2)密码设置为:12345678
注意事项:WIFI 频段设置为:2.4GHZ
千万注意:热点的名字,密码,频段一点要设置正确。 否则,到时候,ESP8266-WIFI 连接不上。 ESP8266 只能连接 2.4GHZ 的 WIFI。
6.4 第四步
打 STM32 的 keil 工程,编译代码、然后,使用 USB 线将开发板的左边的 USB 口(串口 1)与电脑的 USB 连接,打开程序下载软件下载程序。
具体下载过程看下面图:
打开程序下载软件:
6.5 第五步
下载成功之后,本地的 LCD 显示屏会显示硬件的初始化过程。 比如:ESP8266 的初始化过程,以及 WIFI 热点的连接过程。
如何提示 ESP8266-错误,那么就认真检查 WIFI 接线。
如果显示 WIFI 连接失败,请认真检查 第三步。
如果一切正常,就进入了程序主界面。
这时候,打开手机 APP,也能看到设备的最新数据,点击控制按钮,也能控制设备了。
到此,恭喜你,整个项目已经完成开发。
七、制作过程
串口调试助手:
WIFI模式:STA+TCP客户端
Connect_WIFI热点名称:abc
Connect_WIFI热点密码:12345678
TCP服务器端口号:1883
TCP服务器IP地址:117.78.5.125
ESP8266成功连接上热点...
准备连接MQTT服务器...
0x20 0x2 0 0 服务器连接成功.
复制代码
八、STM32 完整代码
下面是 main.c 文件的完整代码。
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include <string.h>
#include "timer.h"
#include "esp8266.h"
#include "oled.h"
#include "adc.h"
#include <string.h>
#include <stdlib.h>
#include "font.h"
#include "mqtt.h"
#include "ds18b20.h"
#include "rtc.h"
#include "hardware.h"
//物联网服务器的设备信息
#define MQTT_ClientID "65dd4fc72ccc1a583879a7e1_dev1_0_0_2024022705"
#define MQTT_UserName "65dd4fc72ccc1a583879a7e1_dev1"
#define MQTT_PassWord "91c783515515d883c533df05ef0e15ed526e583cfb141de54e9ba1545fba0513"
//订阅与发布的主题
#define SET_TOPIC "$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/messages/down" //订阅
#define POST_TOPIC "$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/report" //发布
//设置连接的路由器信息
#define CONNECT_WIFI "abc" //将要连接的路由器名称 --不要出现中文、空格等特殊字符
#define CONNECT_PASS "12345678" //将要连接的路由器密码
#define CONNECT_SERVER_IP "117.78.5.125" //服务器IP地址
#define CONNECT_SERVER_PORT 1883 //服务器端口号
//JTAG模式设置,用于设置JTAG的模式
//mode:jtag,swd模式设置;00,全使能;01,使能SWD;10,全关闭;
#define JTAG_SWD_DISABLE 0X02
#define SWD_ENABLE 0X01
#define JTAG_SWD_ENABLE 0X00
void JTAG_Set(u8 mode)
{
u32 temp;
temp=mode;
temp<<=25;
RCC->APB2ENR|=1<<0; //开启辅助时钟
AFIO->MAPR&=0XF8FFFFFF; //清除MAPR的[26:24]
AFIO->MAPR|=temp; //设置jtag模式
}
char request_id[100];
char mqtt_message[100];
//WIFI发送数据存储区域
char data_buff[300];
char time_buff[50];
char date_buff[50];
double DS18B20;// 环境温度
int DS18B20_int;// 环境温度
int DS18B20_MAX=15; //加热温度上限阀值: DS18B20_MAX;
int oxygen_motor_time=5; //充氧时间间隔: oxygen_motor_time
int oxygen_food_sec=300; // 充氧时间间隔 秒
int water_quality=0; //浑浊度检测 水质状态: 浑浊度检测;
int led_sw=0; //氛围灯开关
int ledNumber=1; //LED灯的编号
int percentage=0; //水质
int water_monitor; //水位检测
u32 SecCnt=0;
int PH; //PH值检测
/*
函数功能: 定时器1的更新中断服务函数 模拟RTC
*/
void TIM1_UP_IRQHandler(void)
{
//1秒钟进来一次
if(TIM1->SR&1<<0)
{
//记录时间
SecCnt++;
GetRtcTime(SecCnt); //转换标准时间
sprintf(time_buff,"%02d:%02d:%02d",rtc_time.tm_hour,rtc_time.tm_min,rtc_time.tm_sec);
sprintf(date_buff,"%02d-%02d-%02d",rtc_time.tm_year,rtc_time.tm_mon,rtc_time.tm_mday);
//倒计时
if(oxygen_food_sec>0)oxygen_food_sec--;
}
TIM1->SR=0;
}
/*
这个函数接受一个整数参数(秒数),并返回一个指向固定长度字符串的指针。
使用 sprintf 函数将小时、分钟和秒格式化为 HH:MM:SS 的字符串,并将其存储在 result 数组中,最后将其作为返回值返回。
在主函数中,程序要求用户输入秒数,调用 format_time 函数将其转换为格式化后的时分秒字符串,并将其打印输出。
注意,这个程序假设用户输入的秒数不超过一天(86400秒)。如果需要处理更长的时间单位,需要修改 format_time 函数的实现。
*/
char* format_time(int seconds)
{
static char result[9]; // 存储结果的字符串,固定长度为8(HH:MM:SS\0)
int minutes = seconds / 60;
seconds = seconds % 60;
int hours = minutes / 60;
minutes = minutes % 60;
sprintf(result, "%02d:%02d:%02d", hours, minutes, seconds);
// printf("seconds:%d\r\n",seconds);
return result;
}
/*
实时水温水质浊度投喂间隔充氧间隔恒温温度下次投喂时间下次充氧时间水位距离
*/
//页面1
void page_1()
{
//实时水温
LCD_ShowChineseFont(0,0+2,16,HZ_FONT_16[0],RED,0);
LCD_ShowChineseFont(16*1,0+2,16,HZ_FONT_16[1],RED,0);
LCD_ShowChineseFont(16*2,0+2,16,HZ_FONT_16[2],RED,0);
LCD_ShowChineseFont(16*3,0+2,16,HZ_FONT_16[3],RED,0);
//水质浊度
LCD_ShowChineseFont(0,16*1+2,16,HZ_FONT_16[4],RED,0);
LCD_ShowChineseFont(16*1,16*1+2,16,HZ_FONT_16[5],RED,0);
LCD_ShowChineseFont(16*2,16*1+2,16,HZ_FONT_16[6],RED,0);
LCD_ShowChineseFont(16*3,16*1+2,16,HZ_FONT_16[7],RED,0);
//PH值
Gui_DrawFont_GBK16(0,16*2+2,RED,0,(u8*)"PH");
//充氧间隔
LCD_ShowChineseFont(0,16*3+2,16,HZ_FONT_16[12],RED,0);
LCD_ShowChineseFont(16*1,16*3+2,16,HZ_FONT_16[13],RED,0);
LCD_ShowChineseFont(16*2,16*3+2,16,HZ_FONT_16[14],RED,0);
LCD_ShowChineseFont(16*3,16*3+2,16,HZ_FONT_16[15],RED,0);
//恒温温度
LCD_ShowChineseFont(0,16*4+2,16,HZ_FONT_16[16],RED,0);
LCD_ShowChineseFont(16*1,16*4+2,16,HZ_FONT_16[17],RED,0);
LCD_ShowChineseFont(16*2,16*4+2,16,HZ_FONT_16[18],RED,0);
LCD_ShowChineseFont(16*3,16*4+2,16,HZ_FONT_16[19],RED,0);
//鱼缸水位
LCD_ShowChineseFont(0,16*5+2,16,HZ_FONT_16[22],RED,0);
LCD_ShowChineseFont(16*1,16*5+2,16,HZ_FONT_16[23],RED,0);
LCD_ShowChineseFont(16*2,16*5+2,16,HZ_FONT_16[32],RED,0);
LCD_ShowChineseFont(16*3,16*5+2,16,HZ_FONT_16[33],RED,0);
}
//页面2
void page_2()
{
//当前实时时间
LCD_ShowChineseFont(0,16*1+2,16,HZ_FONT_16[20],RED,0);
LCD_ShowChineseFont(16*1,16*1+2,16,HZ_FONT_16[21],RED,0);
LCD_ShowChineseFont(16*2,16*1+2,16,HZ_FONT_16[22],RED,0);
LCD_ShowChineseFont(16*3,16*1+2,16,HZ_FONT_16[23],RED,0);
LCD_ShowChineseFont(16*4,16*1+2,16,HZ_FONT_16[24],RED,0);
LCD_ShowChineseFont(16*5,16*1+2,16,HZ_FONT_16[25],RED,0);
//下次充氧时间
LCD_ShowChineseFont(0,16*4+2,16,HZ_FONT_16[26],RED,0);
LCD_ShowChineseFont(16*1,16*4+2,16,HZ_FONT_16[27],RED,0);
LCD_ShowChineseFont(16*2,16*4+2,16,HZ_FONT_16[28],RED,0);
LCD_ShowChineseFont(16*3,16*4+2,16,HZ_FONT_16[29],RED,0);
LCD_ShowChineseFont(16*4,16*4+2,16,HZ_FONT_16[30],RED,0);
LCD_ShowChineseFont(16*5,16*4+2,16,HZ_FONT_16[31],RED,0);
}
int main()
{
u8 key;
u8 i;
u32 time_cnt=0;
u32 timer_hour_cnt=0; //记录定时的时间
u8 page=0; //翻页
u8 run_state=0;
u8 esp8266_connect=0; //连接状态 1表示连接 0表示未连接
//释放PA15
JTAG_Set(JTAG_SWD_DISABLE);
//板载LED初始化
LED_Init();
//板载按键初始化
KEY_Init();
//串口1初始化,用于打印
USART1_Init(115200);
//串口2初始化:
USART2_Init(115200);//串口-WIFI
TIMER2_Init(72,20000); //超时时间20ms
//LCD显示屏初始化
Lcd_Init(); //LCD初始化
Lcd_Clear(0); //清屏为黑色
LCD_LED_SET;//通过IO控制背光亮(通过这个引脚控制显示屏开关)
//ADC初始化 水质状态、PH值检测、水位
AdcInit();
//其他硬件初始化
hardware_init();
//DS18B20--温度传感器初始化
DS18B20_Init();
#if 1
//----------------------------------------------初始化ESP8266-WIFI模块----------------------------------
//清屏为黑色
Lcd_Clear(0);
Gui_DrawFont_GBK16(0,16*0+2,WHITE,0,(u8*)"WIFI Init...");
for(i=0;i<10;i++)
{
if(ESP8266_Init()==0)
{
Gui_DrawFont_GBK16(0,16*0+2,WHITE,0,(u8*)"WIFI OK...");
run_state=1;
break;
}
else
{
Gui_DrawFont_GBK16(0,16*0+2,WHITE,0,(u8*)"WIFI ERROR...");
run_state=0;
printf("ESP8266硬件检测错误.\n");
}
}
//如果初始化成功。就去连接指定的热点。
if(run_state)
{
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)"Connect WIFI...");
printf("ESP8266硬件正常。准备连接WIFI热点(必须2.4GHZ)....\r\n");
printf("准备连接热点名称:%s 密码:%s\r\n",CONNECT_WIFI,CONNECT_PASS);
Gui_DrawFont_GBK16(0,16*2+2,WHITE,0,(u8*)CONNECT_WIFI);
Gui_DrawFont_GBK16(0,16*3+2,WHITE,0,(u8*)CONNECT_PASS);
AA:
//开始连接热点
run_state=ESP8266_STA_TCP_Client_Mode(CONNECT_WIFI,CONNECT_PASS,CONNECT_SERVER_IP,CONNECT_SERVER_PORT,1);
//如果为真, 就表示连接错误
if(run_state)
{
printf("热点连接失败:正在重试...\r\n");
printf("注意: ESP8266只支持2.4GHZ频段的WiFi. 供电要稳定.\r\n");
Gui_DrawFont_GBK16(0,16*4+2,WHITE,0,(u8*)"Connect Error..");
goto AA;
}
Gui_DrawFont_GBK16(0,16*4+2,WHITE,0,(u8*)"Connect Success");
printf("ESP8266成功连接上热点...\r\n");
printf("准备连接MQTT服务器...\r\n");
//清屏为黑色
Lcd_Clear(0);
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)" ");
Gui_DrawFont_GBK16(0,16*0+2,WHITE,0,(u8*)"Connect IOT MQTT");
//2. MQTT协议初始化
MQTT_Init();
//3. 连接服务器
for(i=0;i<5;i++)
{
if(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)==0)
{
esp8266_connect=1;
run_state=1;
break;
}
run_state=0;
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)" ");
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)"Connect Error");
printf("服务器连接失败,正在重试...\r\n");
delay_ms(500);
}
//如果服务器已连接
if(esp8266_connect)
{
esp8266_connect=0;
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)" ");
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)"Connect Success");
printf("服务器连接成功.\r\n");
printf("准备订阅主题...\r\n");
//3. 订阅主题
if(MQTT_SubscribeTopic(SET_TOPIC,0,1))
{
printf("主题订阅失败.\r\n");
Gui_DrawFont_GBK16(0,16*2+2,WHITE,0,(u8*)" ");
Gui_DrawFont_GBK16(0,16*2+2,WHITE,0,(u8*)"TOPIC Sub ERROR");
goto AA;
}
else
{
Gui_DrawFont_GBK16(0,16*2+2,WHITE,0,(u8*)" ");
Gui_DrawFont_GBK16(0,16*2+2,WHITE,0,(u8*)"TOPIC Sub OK.");
printf("主题订阅成功.\r\n");
//表示WIFI连接成功
esp8266_connect=1;
}
}
else
{
printf("服务器连接失败.请保证WIFI能够连接互联网.\r\n");
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)" ");
Gui_DrawFont_GBK16(0,16*1+2,WHITE,0,(u8*)"Connect ERROR");
Gui_DrawFont_GBK16(0,16*2+2,WHITE,0,(u8*)"NET ERROR");
//视觉停留
delay_ms(1000);delay_ms(1000);delay_ms(1000);delay_ms(1000);delay_ms(1000);
}
}
#endif
SetRtcTime(2023,03,22,22,22,10); //设置时间
//定时器初始化。1秒中断一次,为了方便计时
TIMER1_Init(7200,15000); //1000000us 1000000us
//清屏为黑色
Lcd_Clear(0);
//复位 正转 换水电机-出水
open_clean_motor();
//复位 正转 换水电机
open_water_motor();
//默认显示页面1
page_1();
while(1)
{
//---------------------------------按键检测---------------------------------
key=KEY_Scan();
if(key)printf("key=%d\r\n",key);
//按下按键1进行翻页
if(key==1)
{
Lcd_Clear(0); //清屏为黑色
page++;
if(page>=2)page=0;
if(page==0)
{
page_1();
}
else
{
page_2();
}
}
//按下按键2控制照明灯打开
else if(key==2)
{
lighting_led=!lighting_led;
}
//按下按键3控制 控制鱼缸清洗
else if(key==3)
{
//清洗3次
open_clean_motor();
close_clean_motor();
delay_ms(1000);
open_clean_motor();
close_clean_motor();
delay_ms(1000);
open_clean_motor();
close_clean_motor();
}
//轮询时间到达
if(time_cnt>20)
{
time_cnt=0;
LED1=!LED1;
//--------------------------------采集数据--------------------------------
//读取水温度
DS18B20_int=DS18B20_Get_Temp();
//转换温度为浮点数
sprintf(mqtt_message,"%d.%d",DS18B20_int>>4,DS18B20_int&0xF);
DS18B20=atof(mqtt_message);
//读取水质
//水质: 纯净水300多 自来水800多 直接纯牛奶2000
water_quality=GetAvgAdcCHxDATA(1);
percentage = water_quality / 3000.0 * 100.0;
//读取PH值
PH=GetAvgAdcCHxDATA(4);
if(PH>=4000)PH=5;
//读取水位
//水越深值越大,最大值1640
water_monitor=GetAvgAdcCHxDATA(5);
water_monitor= water_monitor / 1640.0 * 100.0;
//--------------------------------下次充氧时间倒计时计算--------------------------------
if(oxygen_food_sec<=0)
{
//默认充氧5秒
oxygen_motor=1;
DelayMs(5000);
oxygen_motor=0;
//时间归位
oxygen_food_sec=oxygen_motor_time*60;
}
//水质太差 就启动换水
if(percentage>80)
{
//正转 换水电机
open_water_motor();
}
else
{
//--反转 换水电机
close_water_motor();
}
//显示页面1
if(page==0)
{
//实时时间与日期
Gui_DrawFont_GBK16(16*1,16*6+2,WHITE,0,(u8*)date_buff);
Gui_DrawFont_GBK16(16*1+8,16*7+2,WHITE,0,(u8*)time_buff);
//显示温度
sprintf(mqtt_message,"%4.1fC",DS18B20);
Gui_DrawFont_GBK16(72,16*0+2,WHITE,0,(u8*)mqtt_message);
// printf("%s\r\n",mqtt_message);
//显示水质
sprintf(mqtt_message,"%4d%%",percentage);
Gui_DrawFont_GBK16(72,16*1+2,WHITE,0,(u8*)mqtt_message);
//PH值
sprintf(mqtt_message,"%4d",PH);
Gui_DrawFont_GBK16(72,16*2+2,WHITE,0,(u8*)mqtt_message);
//充氧间隔
sprintf(mqtt_message,"%4dm",oxygen_motor_time);
Gui_DrawFont_GBK16(72,16*3+2,WHITE,0,(u8*)mqtt_message);
//恒温温度
sprintf(mqtt_message,"%4dC",DS18B20_MAX);
Gui_DrawFont_GBK16(72,16*4+2,WHITE,0,(u8*)mqtt_message);
//水位距离
sprintf(mqtt_message,"%4d%%",water_monitor);
Gui_DrawFont_GBK16(72,16*5+2,WHITE,0,(u8*)mqtt_message);
}
else if(page==1)
{
//实时时间与日期
Gui_DrawFont_GBK16(16*0,16*2+2,WHITE,0,(u8*)date_buff);
Gui_DrawFont_GBK16(16*0+8,16*3+2,WHITE,0,(u8*)time_buff);
char *p=format_time(oxygen_food_sec);
Gui_DrawFont_GBK16(0,16*5+2,WHITE,0,(u8*)p);
}
//恒温判断. 如果小于温度阀值
if(DS18B20<DS18B20_MAX)
{
temp_heat=1; //开启加热
}
else
{
temp_heat=0; //停止加热
}
//如果WIFI已经连接到网络
if(esp8266_connect)
{
//组合JSON报文数据
sprintf(data_buff,"{\"services\": [{\"service_id\": \"stm32\",\"properties\":{\"PH\":%d,\"water_quality\":%d,\"DS18B20\":%.1f,\"oxygen_motor_time\":%d,\"DS18B20_MAX\":%d,\"water_monitor\":%d,\"clean_motor\":%d,\"lighting_led\":%d,\"water_motor\":%d,\"oxygen_motor\":%d}}]}",
PH,percentage,DS18B20,oxygen_motor_time,DS18B20_MAX,water_monitor,clean_motor,lighting_led,water_motor,oxygen_motor);
//上传数据
MQTT_PublishData(POST_TOPIC,data_buff,0);
printf("更新一次数据.\r\n");
}
}
// 接收WIFI返回的数据
if(USART2_RX_FLAG)
{
USART2_RX_BUFFER[USART2_RX_CNT]='\0';
printf("WIFI收到数据:\r\n");
//向串口打印服务器返回的数据
for(i=0;i<USART2_RX_CNT;i++)
{
printf("%c",USART2_RX_BUFFER[i]);
}
#if 0
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=8401c98b-268e-4382-82fd-b69d78275020{"services":{"motor_food":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=22e6e1ff-3e5c-4a25-bbbf-fd4e8f314a68{"services":{"led_sw":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=a15b8f8d-06cf-4597-8cd9-f92e3e0a7b4f{"services":{"motor_oxygen":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=c5b708d8-7c58-43c8-9943-64e25fe6f4df{"services":{"motor_water":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=852659ce-183c-42bf-b896-f9c1e960f405{"services":{"time_food":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=e401d20d-8c10-453d-b1cf-ba7c97a66f7d{"services":{"oxygen_food":1}}
$oc/devices/640ee9ee40773741f9fb55cb_dev1/sys/properties/set/request_id=dd10158b-3da0-45a2-be68-27ddb14a797c{"services":{"temp_max":15}}
#endif
if(USART2_RX_CNT>5)
{
//开 照明灯
if(strstr((char*)&USART2_RX_BUFFER[5],"\"lighting_led\":1"))
{
lighting_led=1;
}
//关 照明灯
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"lighting_led\":0"))
{
lighting_led=0;
}
//开 换水电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"water_motor\":1"))
{
//--反转 换水电机
close_water_motor();
}
//关 换水电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"water_motor\":0"))
{
//正转 换水电机
open_water_motor();
}
//开 充氧电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor\":1"))
{
oxygen_motor=1;
}
//关 充氧电机
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor\":0"))
{
oxygen_motor=0;
}
//开 换水电机-出水
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"clean_motor\":1"))
{
//--反转 换水电机-出水
close_clean_motor();
}
//关 换水电机-出水
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"clean_motor\":0"))
{
//正转 换水电机-出水
open_clean_motor();
}
//充氧时间间隔
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor_time\":"))
{
char *p=strstr((char*)&USART2_RX_BUFFER[5],"\"oxygen_motor_time\":");
oxygen_motor_time=atoi(p+20);
oxygen_food_sec = oxygen_motor_time*60; //转为秒单位
printf("oxygen_motor_time=%d\r\n",oxygen_motor_time);
}
//加热温度上限阀值
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"temp_max\":"))
{
char *p=strstr((char*)&USART2_RX_BUFFER[5],"\"temp_max\":");
DS18B20_MAX=atoi(p+11);
printf("DS18B20_MAX=%d\r\n",DS18B20_MAX);
}
else if(strstr((char*)&USART2_RX_BUFFER[5],"\"rtc_time\":"))
{
char *p=strstr((char*)&USART2_RX_BUFFER[5],"\"rtc_time\":");
p+=11; //向后偏移,指向正确的时间
char *time=p;
int tm_sec; //秒
int tm_min; //分
int tm_hour; //时
int tm_mday; //日
int tm_mon; //月
int tm_year; //年
tm_year=(time[0]-48)*1000+(time[1]-48)*100+(time[2]-48)*10+(time[3]-48)*1;
tm_mon=(time[4]-48)*10+(time[5]-48)*1;
tm_mday=(time[6]-48)*10+(time[7]-48)*1;
tm_hour=(time[8]-48)*10+(time[9]-48)*1;
tm_min=(time[10]-48)*10+(time[11]-48)*1;
tm_sec=(time[12]-48)*10+(time[13]-48)*1;
SetRtcTime(tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec);
printf("RTC时间设置成功:%d-%d-%d %d:%d:%d\r\n",tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec);
}
//下发指令请求回应给服务器
if(strstr((char*)&USART2_RX_BUFFER[5],"properties/set/request_id"))
{
char *p=NULL;
p=strstr((char*)&USART2_RX_BUFFER[5],"request_id");
if(p)
{
//解析数据
//$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"gps"}
strncpy(request_id,p,47);
}
//上报数据
sprintf(mqtt_message,"{\"result_code\": 0,\"result_desc\": \"success\"}");
sprintf(data_buff,"$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/properties/set/response/%s",
request_id);
MQTT_PublishData(data_buff,mqtt_message,0);
printf("发布主题:%s\r\n",data_buff);
printf("发布数据:%s\r\n",mqtt_message);
}
}
USART2_RX_CNT=0;
USART2_RX_FLAG=0;
}
DelayMs(10);
time_cnt++;
timer_hour_cnt++;
}
}
复制代码
评论