一、前言

1.1 项目介绍

1.1.1 开发背景

在当前全球水资源日益紧张与环境污染加剧的背景下,淡水湖养殖业面临着前所未有的挑战。传统的淡水湖养殖方式往往依赖于自然条件,缺乏有效的水质监测与调控手段,这不仅限制了养殖效率,也增加了疾病爆发的风险,进而影响到水产品的品质与产量。随着物联网技术的迅猛发展及其在农业领域的广泛应用,基于物联网的人工淡水湖养殖系统应运而生,成为提升养殖智能化水平、优化资源管理的关键所在。

本项目致力于设计一套集成化的淡水湖养殖管理系统,以STM32F103RCT6为主控芯片,结合PH值、浑浊度、TDS值等多种水质参数的实时监测,辅以远程控制与自动化设备,实现了淡水湖养殖环境的全方位智能监管。通过采用先进的传感器技术,系统能够准确检测水质状况,及时反馈至本地LCD显示屏与手机APP,为养殖人员提供直观的数据支持。特别地,项目引入了远程操控功能,用户不仅能在手机APP上远程启动换水、投喂、充氧等操作,还能通过Windows电脑APP进行更为细致的管理设定,极大地提升了养殖管理的灵活性与便捷性。

为了确保系统的稳定运行与数据的安全传输,项目选用了ESP8266-WIFI模块,借助MQTT协议与华为云IOT物联网服务器建立连接,实现了设备与云端的无缝对接。这样一来,不仅养殖现场的数据能够实时上传至云端,便于数据分析与长期存储,同时也使得用户能够随时随地通过手机APP或电脑APP访问养殖信息,实现真正的远程监控与智能决策。

考虑到淡水湖养殖过程中可能遇到的突发情况,如水质污染等,项目特别设计了基于阈值触发的蜂鸣器报警系统,一旦检测到浑浊度超出安全范围,系统将立即发出警报,提醒养殖人员及时采取措施,避免潜在的经济损失。整个系统通过外部5V 2A电源供电,保证了稳定持久的运行能力。

基于物联网的人工淡水湖养殖系统设计,不仅代表了现代养殖业向着智慧化、精细化方向发展的趋势,也为解决传统养殖中存在的诸多问题提供了创新解决方案,有望推动淡水湖养殖业迈向更加可持续与高效的未来。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_02

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_03

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)连接与通信模块

  • ESP8266-WIFI模块:作为系统与外界通信的桥梁,ESP8266负责将主控芯片处理后的数据上传至华为云IOT物联网服务器,并接收来自远程客户端的控制指令,实现数据的双向传输。
  • MQTT协议:用于系统与华为云服务器之间的数据通信,确保数据传输的安全与高效。

(5)显示与用户界面模块

  • 本地LCD显示屏:用于现场显示实时监测数据和系统状态,包括PH值、浑浊度、TDS值、下一次换水/投喂/充氧的倒计时等信息。
  • 手机APP与Windows电脑软件:通过华为云IOT服务器提供的API接口,用户可在手机APP和电脑软件上远程监控水质参数,接收报警信息,以及进行远程控制操作。

(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 系统功能总结

功能点

描述

显示位置

操作方式

PH值检测

检测淡水湖中PH值情况

本地LCD显示屏、手机APP

自动检测,远程查看

浑浊度检测

检测淡水湖中水质浑浊度

本地LCD显示屏、手机APP

自动检测,远程查看

TDS值检测

检测淡水湖中TDS值情况

本地LCD显示屏、手机APP

自动检测,远程查看

手动换水

远程启动换水电机进行换水

本地LCD显示屏(显示换水倒计时)、手机APP

手机APP远程启动

周期自动投喂

远程启动食物投喂电机进行自动投喂

本地LCD显示屏(显示投喂倒计时)、手机APP

手机APP远程启动

数据上云

将检测数据上传至华为云IOT物联网服务器

华为云IOT服务器

自动上传

远程控制

支持手机APP、Windows电脑远程控制

手机APP、Windows电脑APP

远程操作

周期自动充氧

远程设置时间进行自动充氧

本地LCD显示屏(显示充氧倒计时)、手机APP、Windows电脑APP

手机APP、Windows电脑APP远程设置

蜂鸣器报警

当浑浊度高于设定值时报警

本地蜂鸣器

自动报警

此表格总结了基于物联网的人工淡水湖养殖系统的各项功能,包括其描述、显示位置以及操作方式。这些功能通过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是有点不适应界面的。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_04

1.5.2 上位机开发

上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_05

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_06

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.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

1.7 系统框架图

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_07

二、硬件选

2.1 STM32系统板与LCD显示屏

主控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 PCB板


2.3 蜂鸣器模块


2.4 电源扩展接口(x2)

买2个扩展板,方便扩展5v电源 和 3.3V电源。


2.5 ESP8266-WIFI(ESP-01S)


2.6 母对母杜邦线(X2)

作用: 连接模块与单片机。


2.7 继电器(x3)


2.8 稳压模块


【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_08

2.9 5V2A电源插头


2.10 5V 28BYJ4 步进电机


步进电机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度。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_09

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_10

2.11 增氧泵


2.12 TDS检测传感器


TDS (Total Dissolved Solids)、中文名总溶解固体、又称溶解性固体、又称溶解性固体总量、表明1升水肿容有多少毫克溶解性固体、一般来说、TDS值越高、表示水中含有溶解物越多、水就越不洁净、虽然在特定情况下TDS并不能有效反映水质的情况、但作为一种可快速检测的参数、TDS目前还可以作为有效的在水质情况反映参数来作为参考。常用的TDS检测设备为TDS笔、虽然价格低廉、简单易用、但不能把数据传给控制系统、做长时间的在线监测、并做水质状况分析、使用专门的仪器、虽然能传数据、精度也高、但价格很贵、为此这款TDS传感器模块、即插即用、使用简单方便、测量用的激励源采用交流信号、可有效防止探头极化、延长探头寿命的同时、也增加了输出信号的稳定性、TDS探头为防水探头、可长期侵入水中测量、该产品可以应用于生活用水、水培等领域的水质检测、有了这个传感器、可轻松DIY--套TDS检测仪了、轻松检测水的洁净程度。

2.13 浑浊度检测传感器


2.14 抽水马达(X2)

进水和出水控制


2.15 USB母头(接电机使用的)


2.16 PH值检测传感器


三、部署华为云物联网平台

华为云官网:  https://www.huaweicloud.com/

打开官网,搜索物联网,就能快速找到 设备接入IoTDA

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_11

3.1 物联网平台介绍

华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。

使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。

物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。

设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。

业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_12

3.2 开通物联网服务

地址:  https://www.huaweicloud.com/product/iothub.html

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_13

点击立即创建

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_14

正在创建标准版实例,需要等待片刻。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_15

创建完成之后,点击详情。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_16

可以看到标准版实例的设备接入端口和地址。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_17

在上面也能看到 免费单元的限制。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_18

开通之后,点击总览,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_19

总结:

端口号:   MQTT (1883)| MQTTS (8883)	
接入地址:3cee0d1a66.st1.iotda-device.cn-north-4.myhuaweicloud.com
  • 1.
  • 2.

根据域名地址得到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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。

3.3 创建产品

(1)创建产品

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_20

(2)填写产品信息

根据自己产品名字填写,下面的设备类型选择自定义类型。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_21

(3)产品创建成功

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_22

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_23

(4)添加自定义模型

产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_24

模型简单来说: 就是存放设备上传到云平台的数据。比如:水温、换水电机、充氧电机、水质状态、充氧时间间隔等等,我们都可以单独创建一个模型保存。

当前设备需要与云平台交互的属性如下: 接下来就按照下面的属性创建 华为云平台的模型。

上传到华为云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         整型
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

先点击自定义模型。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_25

再创建一个服务ID。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_26

接着点击新增属性。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_27

PH值检测 PH 整型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_28

浑浊度检测 water_quality 整型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_29

温度检测 DS18B20 浮点数

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_30

换水电机 water_motor BOOL类型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_31

充氧电机 oxygen_motor BOOL类型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_32

定时充氧 oxygen_motor_time 整型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_33

水温阀值 DS18B20_MAX 整型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_34

** 换水电机-出水 clean_motor BOOL类型**

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_35

照明灯 lighting_led BOOL类型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_36

水位检测 water_monitor 整型

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_37

3.4 添加设备

产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。

(1)注册设备

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_38

(2)根据自己的设备填写

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_39

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_40

(3)保存设备信息

创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_41

当前设备的信息如下:

{
    "device_id": "65dd4fc72ccc1a583879a7e1_dev1",
    "secret": "12345678"
}
  • 1.
  • 2.
  • 3.
  • 4.
(4) 设备创建完成

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_42

点击详情:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_43

这就是设备页面:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_44

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

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_45

业务流程:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_46

(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

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_47

对于设备而言,一般会订阅平台下发消息给设备 这个主题。

设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。

如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。

以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
    
最终的格式:
$oc/devices/65dd4fc72ccc1a583879a7e1_dev1/sys/messages/down
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
(4)主题发布格式

对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。

这个操作称为:属性上报。

帮助文档地址: https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_48

根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:

发布的主题格式:
$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}}]}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

3.6 MQTT三元组

MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。

接下来介绍,华为云平台的MQTT三元组参数如何得到。

(1)MQTT服务器地址

要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。

帮助文档地址: https://console.huaweicloud.com/iotdm/?reginotallow=cn-north-4#/dm-portal/home

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_49

MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。

根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)

华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
  • 1.
  • 2.
(2)生成MQTT三元组

华为云提供了一个在线工具,用来生成MQTT鉴权三元组:  https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。

下面是打开的页面:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_50

填入设备的信息: (上面两行就是设备创建完成之后保存得到的)

直接得到三元组信息。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_51

得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。

ClientId  65dd4fc72ccc1a583879a7e1_dev1_0_0_2024022705
Username  65dd4fc72ccc1a583879a7e1_dev1
Password  91c783515515d883c533df05ef0e15ed526e583cfb141de54e9ba1545fba0513
  • 1.
  • 2.
  • 3.

3.7 模拟设备登录测试

经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。

当前模拟设备登录,调试设备的MQTT客户端软件可以在这里下载:

 https://download.csdn.net/download/xiaolong1126626497/18784012

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_52

(1)填入登录信息

打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_53

(2)打开网页查看

完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_54

点击详情页面,可以看到上传的数据:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_55

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_56

到此,云平台的部署已经完成,设备已经可以正常上传数据了。

(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}}]}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

四、上位机开发

为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。

使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。

4.1 Qt开发环境安装

Qt的中文官网:  https://www.qt.io/zh-cn/

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_57

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_58

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编译器即可,其他的不管默认就行,直接点击下一步继续安装。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_59

说明: 我这里只是介绍PC端的环境搭建(这个比较简单)。 Android的开发环境比较麻烦,如果想学习Android开发,想编译Android程序的APP,可以去我的博客里看详细文章。

Android环境搭建的博客链接:  https://blog.csdn.net/xiaolong1126626497/article/details/117254453

选择MinGW 32-bit 编译器:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_60

4.2 创建IAM账户

创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。

地址:  https://console.huaweicloud.com/iam/?reginotallow=cn-north-4#/iam/users

【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_61

项目凭证:

756f8211ec6847c3a5ee4061b37d4ddb
  • 1.

【2】创建IAM用户

鼠标放在左上角头像上,在下拉菜单里选择统一身份认证

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_62

点击左上角创建用户

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_63

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_64

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_65

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_66

创建成功:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_67

【3】创建完成

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_68

用户信息如下:

主用户名  hid_x13ruy5yb1ruano
IAM用户  ds_abc
密码     DS12345678
  • 1.
  • 2.
  • 3.

4.3 获取影子数据

帮助文档: https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html

设备影子介绍:

设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性
  • 1.
  • 2.
  • 3.
  • 4.

简单来说:设备影子就是保存,设备最新上传的一次数据。

我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。

如果对接口不熟悉,可以先进行在线调试: https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow

在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。

调试完成看右下角的响应体,就是返回的影子数据。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_69

设备影子接口返回的数据如下:

{
 "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
  }
 ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_70

4.4 修改设备属性

地址:  https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html

接口说明

设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。
  • 1.

修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。

在线调试地址:

 https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties

修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用MQTT客户端登录服务器,模拟设备上线。

然后进行调试,测试数据远程下发给设备。

【1】利用MQTT客户端先登录设备 (这是同步命令,必须在线才能调试)

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_71

【2】点击调试

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_72

填入的测试数据:

{"services":{"oxygen_motor":1}}
  • 1.

【4】可以看到,MQTT客户端软件上已经收到了服务器下发的消息

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_73

由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程(当然,设备不回应也没影响),设备响应了服务器才能确定数据下发成功。

MQTT设备端如何响应呢?

设备响应格式说明: https://support.huaweicloud.com/api-iothub/iot_06_v5_3008.html

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_74

4.5 新建上位机工程

前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。

【1】新建工程

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_75

【2】设置项目的名称。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_76

【3】选择编译系统

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_77

【4】选择默认继承的类

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_78

【5】选择编译器

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_79

【6】点击完成

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_80

【7】工程创建完成

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_81

4.6 设计UI界面与工程配置

【1】打开UI文件

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_82

打开默认的界面如下:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_83

【2】开始设计界面

根据自己需求设计界面。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_84

【3】配置pro工程文件

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_85

其中,加了注释的代码,表示Android环境才需要,而当前是配置的Windows下的开发环境,在Windows下编译,就将其注释掉,暂时不使用。

【4】配置软件图标

在工程文件下方,增加当前软件的图标配置,图标需要是ICO格式,将图标放在工程同级路径下,在工程配置文件里指定好图标名称。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_86

4.7 设计代码

【1】获取token

调用华为云的API都需要填token参数,先看帮助文章,了解如何获取token。

帮助文档: https://support.huaweicloud.com/api-iam/iam_30_0001.html

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_87

根据帮助文档,写完成下面代码编写:

/*
功能: 获取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());
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
【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());
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
【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);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
【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 secnotallow=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_mnotallow=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;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
【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());
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

4.8 编译Windows上位机

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_88

编译之后的效果:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_89

4.9 配置Android环境

如果想编译Android手机APP,可以参考此章节配置。 但是: 生成Android手机APP必须要先自己配置Android环境,这个配置相对比较复杂。

配置Android环境可以参考教程:  https://blog.csdn.net/xiaolong1126626497/article/details/117254453

【1】创建Android配置文件

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_90

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_91

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_92

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_93

创建完成。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_94

【2】配置Android图标与名称

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_95

【3】编译Android上位机

Qt本身是跨平台的,直接选择Android的编译器,就可以将程序编译到Android平台。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_96

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_97

然后点击构建。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_98

成功之后,在目录下可以看到生成的apk文件,也就是Android手机的安装包,电脑端使用QQ发送给手机QQ,手机登录QQ接收,就能直接安装。

生成的apk的目录在哪里呢?

从这里可以查看。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_99

知道目录在哪里之后,在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
  • 1.

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_100

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}}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

五、STM32设备端代码设计

了解STM32F103的芯片配置:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_101

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.

5.2 取模软件使用

本地设备的LCD显示屏上会显示各种传感器数据,需要用到中文、数字、字母。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_102

这是软件的设置页面:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_103

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"); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

手机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}}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

5.4 按键的功能说明

开发板自带了4个按键,其中第2是个RST 是复位键、另外3个按键是可编程的独立按键。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_104

按键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();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

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_mnotallow=GetAvgAdcCHxDATA(5);
    water_mnotallow= 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"); 
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.

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_mnotallow=(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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.

5.7 KEIL工程

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_105

六、使用STM32代码的流程以及注意事项

6.1 第一步

照着设计文档,买回来硬件模块。 然后照着第五章节的第1小节(5.1章节)的硬件连线说明,将模块与STM32开发板之间连接好线。

注意:LCD显示屏,直接插上去就行了,买的开发板上本身就有排母,照着接,看准开发板板子上的 丝印说明。

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_106

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连接,打开程序下载软件下载程序。

具体下载过程看下面图:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_107

打开程序下载软件:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_108

6.5 第五步

下载成功之后,本地的LCD显示屏会显示硬件的初始化过程。 比如:ESP8266的初始化过程,以及WIFI热点的连接过程。

如何提示ESP8266-错误,那么就认真检查WIFI接线。

如果显示WIFI连接失败,请认真检查 第三步。

如果一切正常,就进入了程序主界面。

这时候,打开手机APP,也能看到设备的最新数据,点击控制按钮,也能控制设备了。

到此,恭喜你,整个项目已经完成开发。

七、制作过程

串口调试助手:

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_109

WIFI模式:STA+TCP客户端
Connect_WIFI热点名称:abc
Connect_WIFI热点密码:12345678
TCP服务器端口号:1883
TCP服务器IP地址:117.78.5.125
ESP8266成功连接上热点...
准备连接MQTT服务器...
0x20 0x2 0 0 服务器连接成功.
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_110

【附源码+教程】基于华为云物联网设计的人工淡水湖养殖系统_淡水湖养殖_111

八、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_cnotallow=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_cnotallow=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_cnotallow=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_cnotallow=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_mnotallow=GetAvgAdcCHxDATA(5);
            water_mnotallow= 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_mnotallow=(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++;
	 }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.
  • 436.
  • 437.
  • 438.
  • 439.
  • 440.
  • 441.
  • 442.
  • 443.
  • 444.
  • 445.
  • 446.
  • 447.
  • 448.
  • 449.
  • 450.
  • 451.
  • 452.
  • 453.
  • 454.
  • 455.
  • 456.
  • 457.
  • 458.
  • 459.
  • 460.
  • 461.
  • 462.
  • 463.
  • 464.
  • 465.
  • 466.
  • 467.
  • 468.
  • 469.
  • 470.
  • 471.
  • 472.
  • 473.
  • 474.
  • 475.
  • 476.
  • 477.
  • 478.
  • 479.
  • 480.
  • 481.
  • 482.
  • 483.
  • 484.
  • 485.
  • 486.
  • 487.
  • 488.
  • 489.
  • 490.
  • 491.
  • 492.
  • 493.
  • 494.
  • 495.
  • 496.
  • 497.
  • 498.
  • 499.
  • 500.
  • 501.
  • 502.
  • 503.
  • 504.
  • 505.
  • 506.
  • 507.
  • 508.
  • 509.
  • 510.
  • 511.
  • 512.
  • 513.
  • 514.
  • 515.
  • 516.
  • 517.
  • 518.
  • 519.
  • 520.
  • 521.
  • 522.
  • 523.
  • 524.
  • 525.
  • 526.
  • 527.
  • 528.
  • 529.
  • 530.
  • 531.
  • 532.
  • 533.
  • 534.
  • 535.
  • 536.
  • 537.
  • 538.
  • 539.
  • 540.
  • 541.
  • 542.
  • 543.
  • 544.
  • 545.
  • 546.
  • 547.
  • 548.
  • 549.
  • 550.
  • 551.
  • 552.
  • 553.
  • 554.
  • 555.
  • 556.
  • 557.
  • 558.
  • 559.
  • 560.
  • 561.
  • 562.
  • 563.
  • 564.
  • 565.
  • 566.
  • 567.
  • 568.
  • 569.
  • 570.
  • 571.
  • 572.
  • 573.
  • 574.
  • 575.
  • 576.
  • 577.
  • 578.
  • 579.
  • 580.
  • 581.
  • 582.
  • 583.
  • 584.
  • 585.
  • 586.
  • 587.
  • 588.
  • 589.
  • 590.
  • 591.
  • 592.
  • 593.
  • 594.
  • 595.
  • 596.
  • 597.
  • 598.
  • 599.
  • 600.
  • 601.
  • 602.
  • 603.
  • 604.
  • 605.
  • 606.
  • 607.
  • 608.
  • 609.
  • 610.
  • 611.
  • 612.
  • 613.
  • 614.
  • 615.
  • 616.
  • 617.
  • 618.
  • 619.
  • 620.
  • 621.
  • 622.
  • 623.
  • 624.
  • 625.
  • 626.
  • 627.
  • 628.
  • 629.
  • 630.
  • 631.
  • 632.
  • 633.
  • 634.
  • 635.
  • 636.
  • 637.
  • 638.
  • 639.
  • 640.
  • 641.
  • 642.
  • 643.
  • 644.
  • 645.
  • 646.
  • 647.
  • 648.
  • 649.
  • 650.
  • 651.
  • 652.
  • 653.
  • 654.
  • 655.
  • 656.
  • 657.
  • 658.
  • 659.
  • 660.
  • 661.
  • 662.
  • 663.
  • 664.
  • 665.
  • 666.
  • 667.
  • 668.
  • 669.
  • 670.
  • 671.
  • 672.
  • 673.
  • 674.
  • 675.
  • 676.
  • 677.
  • 678.
  • 679.
  • 680.
  • 681.
  • 682.
  • 683.
  • 684.
  • 685.
  • 686.
  • 687.
  • 688.
  • 689.
  • 690.
  • 691.
  • 692.
  • 693.
  • 694.
  • 695.
  • 696.
  • 697.
  • 698.
  • 699.
  • 700.