基于STM32设计的物联网智能插座

文章目录

一、前言

1.1 项目开发背景

随着物联网技术、无线通信技术以及智能家居产业的快速发展,传统家用插座已经无法满足现代家庭对于智能化、信息化和远程管理的需求。人们在日常生活中使用大量电器设备,如空调、电饭煲、电磁炉、热水器、电吹风等,这些设备在带来便利的同时,也产生了较大的能源消耗。由于普通插座仅具备供电功能,用户无法实时了解设备的用电情况、运行功率以及电能消耗情况,容易造成能源浪费,也不利于家庭节能管理。因此,开发一种集电能监测、远程控制、数据统计和安全保护于一体的智能插座具有重要的现实意义。

近年来,国家持续推进节能减排和智慧家庭建设,居民对于家庭能源管理的关注度不断提高。传统电费账单只能反映家庭整体用电情况,无法精确了解单个电器设备的耗电量和使用成本。对于普通用户而言,往往难以判断哪些设备属于高耗能设备,也无法直观了解不同电器的实际运行费用。通过智能插座实时采集电压、电流、有功功率、功率因数、频率以及电能数据,并结合用户设置的电价自动计算设备运行费用,可以帮助用户更加直观地掌握电器的耗电情况,提高节能意识,实现科学用电和合理用电。

随着移动互联网和云计算技术的发展,人们越来越希望能够通过手机随时随地查看家庭设备运行状态,并实现远程控制。当用户外出时,经常会担心家中的电器是否关闭,例如电热水器、电饭煲或其他大功率设备长时间运行可能带来的安全隐患。借助WiFi通信技术和物联网云平台,可以将智能插座采集的数据实时上传至云端服务器,用户通过手机APP即可远程查看设备运行状态、历史用电信息以及报警信息,并能够远程控制插座电源开关,从而提高家庭用电管理的便利性和智能化水平。

除了能源管理需求之外,用电安全问题也是家庭用电领域的重要关注点。由于电器老化、线路过载、环境温度过高或湿度异常等因素,容易导致设备损坏甚至引发安全事故。传统插座通常不具备主动监测和保护功能,无法及时发现异常情况。本项目通过实时监测负载电流、功率以及环境温湿度参数,当检测到过流、过功率、过温或过湿等异常情况时,系统能够自动进行报警提示,并在必要时切断负载供电,从而有效降低电气设备运行风险,提高家庭用电安全性。

在智能家居系统不断普及的背景下,各类设备之间的数据互联互通已经成为重要的发展趋势。智能插座作为家庭电力接入的重要节点,不仅能够承担供电控制任务,还能够作为家庭能源数据采集终端,为智慧家庭管理系统提供基础数据支持。通过将设备接入华为云物联网平台,实现设备数据的云端存储、远程访问和集中管理,为后续扩展微信小程序、Web可视化平台以及多设备联动控制等功能提供良好的基础条件,进一步提升系统的应用价值和扩展能力。

基于以上背景,设计一种基于STM32单片机的物联网家用智能插座,通过集成电能参数检测、环境温湿度监测、继电器控制、MQTT云平台通信、手机APP远程监控以及多重安全保护等功能,实现家庭用电信息的实时采集、远程管理和安全监测。该系统不仅能够帮助用户掌握设备运行状态和能耗情况,提高能源利用效率,而且能够增强家庭用电安全保障能力,具有良好的实用价值和推广意义。

流程图:

1.2 设计实现的功能

(1)支持电压、电流、有功功率、功率因数、电网频率检测。

(2)支持环境温湿度检测。

(3)支持查看当前实时用电量,并根据设置的电价自动计算实时使用的电费(从开启负载开关开始计算)。

(4)支持查看历史总用电量(kWh),该数据记录在硬件上,掉电不丢失,永久保存。

(5)支持APP远程设置当前电价(多少钱一度电)。

(6)支持过载保护:当监测到功率或者电流超过设定的阈值时,自动切断电源;在功率和电流未超过阈值的情况下,用户可以在APP上手动控制负载的电源开关。

(7)支持报警提示:线路上测量的电流或功率超过设定阈值时,蜂鸣器报警;环境湿度检测超过设定阈值时,蜂鸣器报警;环境温度检测超过设定阈值时,蜂鸣器报警。

(8)支持数据上云:本地设备数据通过ESP8266利用MQTT协议实时上传到华为云物联网服务器。

(9)支持APP远程查看数据以及控制电源开关,接收报警信息。

(10)支持本地LCD显示屏显示采集的数据,包括当前插座负载的实时电压、电流、有功功率、功率因数、电网频率、本次使用的电费、本次消耗的电能、总电能。

(11)支持APP远程控制插座的电源开关,远程设置功率阈值、电流阈值、当前电费价格。

(12)支持APP远程显示全部数据,包括当前插座负载的实时电压、电流、有功功率、功率因数、电网频率、本次使用的电费、本次消耗的电能、总电能、负载电源开关状态。

(13)负载电源采用继电器控制,单片机控制继电器开关,继电器控制负载电源的通断;继电器输入电源接家用220V电,经过电力信息采集模块后输出提供给负载,负载输出为家用电源插座,支持电磁炉、电吹风、电饭煲、空调等220V家用供电设备。

(14)支持Android手机APP远程显示与控制,后续可扩展定制微信小程序或Web端可视化大屏。

**项目开发使用的全部软件工具已经上传到网盘: **https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

1.3 项目硬件模块组成

(1)主控芯片:STM32F103RCT6

(2)温湿度检测传感器:SHT30

(3)电力参数采集模块:采用Modbus协议通讯的电力参数采集模块,通过标准Modbus协议进行交互通信,输出采集的电压、电流、有功功率、功率因数、电网频率等信息;单片机通过串口采集数据,解析Modbus协议获取反馈数据。

(4)LCD显示屏:1.44寸TFT-LCD显示屏,用于本地显示采集的全部信息。

(5)蜂鸣器:高电平触发的有源蜂鸣器,用于报警提示。

(6)联网模块:ESP8266-WIFI模块,用于通过MQTT协议将数据上传至华为云物联网服务器。

(7)继电器:作为外部电源控制开关,由单片机控制继电器的通断,继电器控制负载电源。

(8)电源转换电路:为各硬件模块提供所需的工作电压。

1.4 系统框架图

1.5 运行流程图

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

华为云视频下载链接:https://pan.quark.cn/s/034bb67c60e4

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

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

2.1 物联网平台介绍

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

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

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

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

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

2.2 开通物联网服务

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

开通免费单元。

点击立即创建

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

创建完成之后,点击详情。 可以看到标准版实例的设备接入端口和地址。

下面框起来的就是端口号域名

点击实例名称,可以查看当前免费单元的配置情况。

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

总结:

复制代码
端口号:   MQTT (1883)| MQTTS (8883)    
接入地址: dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com

根据域名地址得到IP地址信息:

打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。

复制代码
Microsoft Windows [版本 10.0.19045.5011]
(c) Microsoft Corporation。保留所有权利。

C:\Users\Lenovo>ping dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com

正在 Ping dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44

117.78.5.125 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 37ms,最长 = 37ms,平均 = 37ms

C:\Users\Lenovo>

MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口合适

2.3 创建产品

链接:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-dev/all-product?instanceId=03c5c68c-e588-458c-90c3-9e4c640be7af

(1)创建产品

(2)填写产品信息

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

(3)产品创建成功

创建完成之后点击查看详情。

(4)添加自定义模型

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

模型简单来说: 就是存放设备上传到云平台的数据。

你可以根据自己的产品进行创建。

比如:

cpp 复制代码
烟雾可以叫  MQ2
温度可以叫  Temperature
湿度可以叫  humidity
火焰可以叫  flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。

先点击自定义模型。

再创建一个服务ID。

接着点击新增属性。

2.4 添加设备

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

(1)注册设备

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

(3)保存设备信息

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

(4)设备创建完成

(5)设备详情

2.5 MQTT协议主题订阅与发布

(1)MQTT协议介绍

当前的设备是采用MQTT协议与华为云平台进行通信。

MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。

MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。

华为云的MQTT协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

业务流程:

(2)华为云平台MQTT协议使用限制

描述 限制
支持的MQTT协议版本 3.1.1
与标准MQTT协议的区别 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg
MQTTS支持的安全等级 采用TCP通道基础 + TLS协议(最高TLSv1.3版本)
单帐号每秒最大MQTT连接请求数 无限制
单个设备每分钟支持的最大MQTT连接数 1
单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 3KB/s
MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 1MB
MQTT连接心跳时间建议值 心跳时间限定为30至1200秒,推荐设置为120秒
产品是否支持自定义Topic 支持
消息发布与订阅 设备只能对自己的Topic进行消息发布与订阅
每个订阅请求的最大订阅数 无限制

(3)主题订阅格式

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

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

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

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

cpp 复制代码
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
    
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down

(4)主题发布格式

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

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

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

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

cpp 复制代码
发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
 
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。

上传的JSON数据格式如下:

{
  "services": [
    {
      "service_id": <填服务ID>,
      "properties": {
        "<填属性名称1>": <填属性值>,
        "<填属性名称2>": <填属性值>,
        ..........
      }
    }
  ]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。

根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字1":30,"你的字段名字2":10,"你的字段名字3":1,"你的字段名字4":0}}]}

2.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地址就是域名解析得到的)

cpp 复制代码
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883

如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。

cpp 复制代码
ping  ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com

(2)生成MQTT三元组

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

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

下面是打开的页面:

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

直接得到三元组信息。

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

cpp 复制代码
ClientId  663cb18871d845632a0912e7_dev1_0_0_2024050911
Username  663cb18871d845632a0912e7_dev1
Password  71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237

2.7 模拟设备登录测试

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

MQTT软件下载地址【免费】: https://download.csdn.net/download/xiaolong1126626497/89928772

(1)填入登录信息

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

(2)打开网页查看

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

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

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

(3)MQTT登录测试参数总结

cpp 复制代码
MQTT服务器:  117.78.5.125
MQTT端口号:  183

//物联网服务器的设备信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"

//订阅与发布的主题
#define SET_TOPIC  "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down"  //订阅
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report"  //发布


发布的数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字1":30,"你的字段名字2":10,"你的字段名字3":1,"你的字段名字4":0}}]}

2.8 创建IAM账户

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

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

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

项目凭证:

cpp 复制代码
28add376c01e4a61ac8b621c714bf459

【2】创建IAM用户

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

点击左上角创建用户

创建成功:

【3】创建完成

用户信息如下:

cpp 复制代码
主用户名  l19504562721
IAM用户  ds_abc
密码     DS12345678

2.9 获取影子数据

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

设备影子介绍:

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

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

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

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

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

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

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

cpp 复制代码
{
 "device_id": "663cb18871d845632a0912e7_dev1",
 "shadow": [
  {
   "service_id": "stm32",
   "desired": {
    "properties": null,
    "event_time": null
   },
   "reported": {
    "properties": {
     "DHT11_T": 18,
     "DHT11_H": 90,
     "BH1750": 38,
     "MQ135": 70
    },
    "event_time": "20240509T113448Z"
   },
   "version": 3
  }
 ]
}

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

链接如下:

cpp 复制代码
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow

三、上位机开发

3.1 Qt开发环境安装

Qt的中文官网: https://www.qt.io/zh-cn/![image-20221207160550486](https://i-blog.csdnimg.cn/img_convert/c9c1687d65afc77c2b88f2b3bd2f123b.png)

QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6

打开下载链接后选择下面的版本进行下载:

如果下载不了,可以在网盘里找到安装包下载: 飞书文档记录的网盘地址:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

软件安装时断网安装,否则会提示输入账户。

安装的时候,第一个复选框里的编译器可以全选,直接点击下一步继续安装。

选择编译器: (一定要看清楚了)

3.2 新建上位机工程

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

【1】新建工程

【2】设置项目的名称。

【3】选择编译系统

【4】选择默认继承的类

【5】选择编译器

【6】点击完成

【7】工程创建完成

3.3 切换编译器

在左下角是可以切换编译器的。 可以选择用什么样的编译器编译程序。

目前新建工程的时候选择了2种编译器。 一种是mingw32这个编译Windows下运行的程序。 一种是Android编译器,可以生成Android手机APP。

不过要注意:Android的编译器需要配置一些环境才可以正常使用,这个大家可以网上找找教程配置一下就行了。

windows的编译器就没有这么麻烦,安装好Qt就可以编译使用。

下面我这里就选择的 mingw32这个编译器,编译Windows下运行的程序。

3.4 编译测试功能

创建完毕之后,编译测试一下功能是否OK。

点击左下角的绿色三角形按钮

正常运行就可以看到弹出一个白色的框框。这就表示工程环境没有问题了。 接下来就可以放心的设计界面了。

3.5 设计UI界面与工程配置

【1】打开UI文件

打开默认的界面如下:

【2】开始设计界面

根据自己需求设计界面。

3.6 设计代码-widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 设置窗口标题
    this->setWindowTitle("智能插座远程监控系统");
    
    // 初始化MQTT客户端
    mqttClient = new QMqttClient(this);
    
    // 设置MQTT服务器地址(华为云IoT平台)
    mqttClient->setHostname("your_hostname.iot-mqtts.cn-north-4.myhuaweicloud.com");
    mqttClient->setPort(1883);
    mqttClient->setClientId(deviceId);
    
    // 设置用户名和密码(华为云IoT凭证)
    mqttClient->setUsername("your_access_key");
    mqttClient->setPassword("your_device_secret");
    
    // 主题设置
    subTopic = "$oc/devices/" + deviceId + "/sys/messages/down";
    pubTopic = "$oc/devices/" + deviceId + "/sys/properties/report";
    
    // 连接状态变化信号
    connect(mqttClient, &QMqttClient::stateChanged, this, &Widget::onMqttStateChanged);
    connect(mqttClient, &QMqttClient::messageReceived, this, &Widget::onMessageReceived);
    
    // 初始化定时器,每2秒刷新一次数据
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &Widget::refreshData);
    
    // 设置默认值
    ui->pwrLimitEdit->setText("2200");
    ui->currLimitEdit->setText("10");
    ui->priceEdit->setText("0.58");
    ui->tempLimitEdit->setText("50");
    ui->humiLimitEdit->setText("85");
    
    // 初始状态
    ui->relayBtn->setText("继电器关闭");
    ui->relayBtn->setStyleSheet("background-color:red");
}

Widget::~Widget()
{
    delete ui;
}

// 连接MQTT服务器
void Widget::on_connectBtn_clicked()
{
    mqttClient->connectToHost();
}

// 断开MQTT连接
void Widget::on_disconnectBtn_clicked()
{
    mqttClient->disconnectFromHost();
    timer->stop();
    ui->statusLabel->setText("状态:已断开");
}

// MQTT状态变化
void Widget::onMqttStateChanged(int state)
{
    if (state == QMqttClient::Connected) {
        ui->statusLabel->setText("状态:已连接");
        ui->connectBtn->setEnabled(false);
        ui->disconnectBtn->setEnabled(true);
        
        // 订阅下行主题
        mqttClient->subscribe(QMqttTopicFilter(subTopic));
        
        // 启动定时器
        timer->start(2000);
    } else {
        ui->statusLabel->setText("状态:已断开");
        ui->connectBtn->setEnabled(true);
        ui->disconnectBtn->setEnabled(false);
        timer->stop();
    }
}

// 接收云端消息
void Widget::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
    QJsonDocument doc = QJsonDocument::fromJson(message);
    QJsonObject obj = doc.object();
    
    // 解析云端下发的指令
    if (obj.contains("relay")) {
        bool state = obj.value("relay").toInt() == 1;
        ui->relayBtn->setText(state ? "继电器开启" : "继电器关闭");
        ui->relayBtn->setStyleSheet(state ? "background-color:green" : "background-color:red");
        relayState = state;
    }
    
    if (obj.contains("pwrLimit")) {
        ui->pwrLimitEdit->setText(QString::number(obj.value("pwrLimit").toInt()));
    }
    
    if (obj.contains("currLimit")) {
        ui->currLimitEdit->setText(QString::number(obj.value("currLimit").toDouble()));
    }
    
    if (obj.contains("price")) {
        ui->priceEdit->setText(QString::number(obj.value("price").toDouble()));
    }
    
    if (obj.contains("tempLimit")) {
        ui->tempLimitEdit->setText(QString::number(obj.value("tempLimit").toInt()));
    }
    
    if (obj.contains("humiLimit")) {
        ui->humiLimitEdit->setText(QString::number(obj.value("humiLimit").toInt()));
    }
}

// 刷新数据显示
void Widget::refreshData()
{
    // 实际应用中这里应该从MQTT消息中获取数据
    // 这里模拟从设备上报的数据中解析
    QJsonObject data;
    data["voltage"] = voltage;
    data["current"] = current;
    data["power"] = power;
    data["pf"] = pf;
    data["freq"] = freq;
    data["temp"] = temp;
    data["humi"] = humi;
    data["energy"] = energy;
    data["costUsed"] = costUsed;
    data["relay"] = relayState ? 1 : 0;
    data["alarm"] = alarm;
    
    updateDisplay(data);
}

// 更新UI显示
void Widget::updateDisplay(const QJsonObject &obj)
{
    if (obj.contains("voltage"))
        ui->voltageLabel->setText(QString::number(obj.value("voltage").toDouble(), 'f', 1) + " V");
    if (obj.contains("current"))
        ui->currentLabel->setText(QString::number(obj.value("current").toDouble(), 'f', 2) + " A");
    if (obj.contains("power"))
        ui->powerLabel->setText(QString::number(obj.value("power").toDouble(), 'f', 1) + " W");
    if (obj.contains("pf"))
        ui->pfLabel->setText(QString::number(obj.value("pf").toDouble(), 'f', 2));
    if (obj.contains("freq"))
        ui->freqLabel->setText(QString::number(obj.value("freq").toDouble(), 'f', 1) + " Hz");
    if (obj.contains("temp"))
        ui->tempLabel->setText(QString::number(obj.value("temp").toDouble(), 'f', 1) + " °C");
    if (obj.contains("humi"))
        ui->humiLabel->setText(QString::number(obj.value("humi").toDouble(), 'f', 1) + " %");
    if (obj.contains("energy"))
        ui->energyLabel->setText(QString::number(obj.value("energy").toDouble(), 'f', 2) + " kWh");
    if (obj.contains("costUsed"))
        ui->costLabel->setText(QString::number(obj.value("costUsed").toDouble(), 'f', 2) + " 元");
    
    // 报警显示
    if (obj.contains("alarm")) {
        int alarmVal = obj.value("alarm").toInt();
        QString alarmMsg;
        switch(alarmVal) {
            case 0: alarmMsg = "正常"; ui->alarmLabel->setStyleSheet("color:green"); break;
            case 1: alarmMsg = "过流报警"; ui->alarmLabel->setStyleSheet("color:red"); break;
            case 2: alarmMsg = "过功率报警"; ui->alarmLabel->setStyleSheet("color:red"); break;
            case 3: alarmMsg = "过温报警"; ui->alarmLabel->setStyleSheet("color:red"); break;
            case 4: alarmMsg = "过湿报警"; ui->alarmLabel->setStyleSheet("color:red"); break;
            default: alarmMsg = "未知";
        }
        ui->alarmLabel->setText(alarmMsg);
    }
}

// 继电器控制
void Widget::on_relayBtn_clicked()
{
    QJsonObject command;
    command["relay"] = relayState ? 0 : 1;
    
    QJsonDocument doc(command);
    publishMessage(subTopic, doc.toJson());
}

// 设置功率阈值
void Widget::on_setPwrBtn_clicked()
{
    int pwrLimit = ui->pwrLimitEdit->text().toInt();
    QJsonObject command;
    command["pwrLimit"] = pwrLimit;
    
    QJsonDocument doc(command);
    publishMessage(subTopic, doc.toJson());
    
    QMessageBox::information(this, "提示", "功率阈值已设置:" + QString::number(pwrLimit) + "W");
}

// 设置电流阈值
void Widget::on_setCurrBtn_clicked()
{
    double currLimit = ui->currLimitEdit->text().toDouble();
    QJsonObject command;
    command["currLimit"] = currLimit;
    
    QJsonDocument doc(command);
    publishMessage(subTopic, doc.toJson());
    
    QMessageBox::information(this, "提示", "电流阈值已设置:" + QString::number(currLimit) + "A");
}

// 设置电价
void Widget::on_setPriceBtn_clicked()
{
    double price = ui->priceEdit->text().toDouble();
    QJsonObject command;
    command["price"] = price;
    
    QJsonDocument doc(command);
    publishMessage(subTopic, doc.toJson());
    
    QMessageBox::information(this, "提示", "电价已设置:" + QString::number(price) + "元/度");
}

// 设置温度阈值
void Widget::on_setTempBtn_clicked()
{
    int tempLimit = ui->tempLimitEdit->text().toInt();
    QJsonObject command;
    command["tempLimit"] = tempLimit;
    
    QJsonDocument doc(command);
    publishMessage(subTopic, doc.toJson());
    
    QMessageBox::information(this, "提示", "温度阈值已设置:" + QString::number(tempLimit) + "°C");
}

// 设置湿度阈值
void Widget::on_setHumiBtn_clicked()
{
    int humiLimit = ui->humiLimitEdit->text().toInt();
    QJsonObject command;
    command["humiLimit"] = humiLimit;
    
    QJsonDocument doc(command);
    publishMessage(subTopic, doc.toJson());
    
    QMessageBox::information(this, "提示", "湿度阈值已设置:" + QString::number(humiLimit) + "%");
}

// 发布MQTT消息
void Widget::publishMessage(const QString &topic, const QString &message)
{
    if (mqttClient->state() == QMqttClient::Connected) {
        mqttClient->publish(QMqttTopicName(topic), message.toUtf8());
    } else {
        QMessageBox::warning(this, "错误", "MQTT未连接,请先连接服务器");
    }
}

3.7 设计代码-widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QtMqtt/QMqttClient>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    // MQTT连接相关
    void on_connectBtn_clicked();
    void on_disconnectBtn_clicked();
    void onMqttStateChanged(int state);
    void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic);

    // 控制按钮
    void on_relayBtn_clicked();
    void on_setPwrBtn_clicked();
    void on_setCurrBtn_clicked();
    void on_setPriceBtn_clicked();
    void on_setTempBtn_clicked();
    void on_setHumiBtn_clicked();

    // 定时器刷新
    void refreshData();

private:
    void publishMessage(const QString &topic, const QString &message);
    void updateDisplay(const QJsonObject &obj);

private:
    Ui::Widget *ui;
    QMqttClient *mqttClient;
    QTimer *timer;
    
    // 设备参数
    QString deviceId = "SmartSocket_001";
    QString subTopic;
    QString pubTopic;
    
    // 当前数据
    double voltage = 0;
    double current = 0;
    double power = 0;
    double pf = 0;
    double freq = 0;
    double temp = 0;
    double humi = 0;
    double energy = 0;
    double costUsed = 0;
    bool relayState = false;
    int alarm = 0;
};
#endif

四、STM32代码设计

4.1 硬件连线说明

(1)STM32F103RCT6主控芯片

作为系统核心控制器,负责数据采集、数据处理、LCD显示、继电器控制、报警控制以及ESP8266通信等功能。


(2)电力参数采集模块(Modbus-RTU)

用于检测负载的电压、电流、有功功率、功率因数、频率、电能等参数。

连接方式:

  • 模块VCC → 电源模块5V
  • 模块GND → 系统GND
  • 模块TXD → STM32 USART2_RX(PA3)
  • 模块RXD → STM32 USART2_TX(PA2)

采集参数:

  • 电压(Voltage)
  • 电流(Current)
  • 有功功率(Power)
  • 功率因数(PF)
  • 电网频率(Freq)
  • 累计电能(Energy)

(3)SHT30温湿度传感器

用于检测插座周围环境温度和湿度。

连接方式:

  • VCC → 3.3V
  • GND → GND
  • SDA → PB7
  • SCL → PB6

通信方式:

  • IIC总线通信

采集参数:

  • 环境温度
  • 环境湿度

(4)ESP8266 WiFi模块

用于连接华为云物联网平台,实现MQTT通信。

连接方式:

  • VCC → 3.3V
  • GND → GND
  • RXD → STM32 USART1_TX(PA9)
  • TXD → STM32 USART1_RX(PA10)

功能:

  • MQTT连接华为云IoT平台
  • 上传设备数据
  • 接收APP控制命令
  • 接收参数配置命令

(5)1.44寸TFT-LCD显示屏

用于本地显示设备运行状态及采集数据。

连接方式:

  • VCC → 3.3V
  • GND → GND
  • SCK → PA5
  • SDA(MOSI) → PA7
  • CS → PB12
  • DC → PB13
  • RST → PB14
  • BLK → PB15

通信方式:

  • SPI通信

显示内容:

  • 电压
  • 电流
  • 有功功率
  • 功率因数
  • 电网频率
  • 环境温度
  • 环境湿度
  • 当前电费
  • 本次用电量
  • 总电能
  • 继电器状态
  • 报警信息

(6)继电器控制模块

用于控制负载电源通断。

连接方式:

  • VCC → 5V
  • GND → GND
  • IN → PB8

功能:

  • APP远程控制开关
  • 本地自动断电保护
  • 过流保护断电
  • 过功率保护断电

控制对象:

  • 220V交流负载电源

支持负载:

  • 电饭煲
  • 电磁炉
  • 空调
  • 电热水壶
  • 风扇
  • 热水器
  • 其他220V家用电器

(7)有源蜂鸣器模块

用于设备报警提示。

连接方式:

  • VCC → 5V
  • GND → GND
  • IN → PB9

触发方式:

  • 高电平触发

报警类型:

  • 电流超限报警
  • 功率超限报警
  • 温度超限报警
  • 湿度超限报警

(8)按键模块

用于本地参数设置和界面切换。

KEY1(页面切换键)
  • 一端接PC0
  • 一端接GND

功能:

  • LCD界面切换

KEY2(确认键)
  • 一端接PC1
  • 一端接GND

功能:

  • 参数确认
  • 菜单进入

KEY3(增加键)
  • 一端接PC2
  • 一端接GND

功能:

  • 参数增加

KEY4(减少键)
  • 一端接PC3
  • 一端接GND

功能:

  • 参数减少

(9)状态指示灯

运行指示灯(绿色LED)

连接方式:

  • LED → PB4

功能:

  • 系统运行状态指示

报警指示灯(红色LED)

连接方式:

  • LED → PB5

功能:

  • 故障报警状态指示

(10)电源模块

系统采用AC220V转DC供电方案。

输入:

  • AC220V市电

输出:

  • DC5V
  • DC3.3V

供电对象:

  • STM32F103RCT6
  • ESP8266
  • TFT-LCD
  • SHT30
  • 电力参数采集模块
  • 继电器
  • 蜂鸣器

(11)220V交流负载回路

电源流向:

text 复制代码
AC220V输入
      │
      ▼
电力参数采集模块
      │
      ▼
继电器常开触点
      │
      ▼
插座输出端
      │
      ▼
负载设备

工作过程:

  • 市电首先进入电力参数采集模块。
  • 电力参数采集模块实时测量电压、电流、功率、电能等信息。
  • 经过检测后输出到继电器公共端。
  • STM32控制继电器吸合或断开。
  • 继电器输出连接至插座面板。
  • 用户将各种220V家电插入插座即可使用。

(12)STM32最终IO资源分配表

模块 STM32引脚
ESP8266_TX PA10
ESP8266_RX PA9
Modbus_TX PA2
Modbus_RX PA3
SHT30_SCL PB6
SHT30_SDA PB7
TFT_SCK PA5
TFT_MOSI PA7
TFT_CS PB12
TFT_DC PB13
TFT_RST PB14
TFT_BLK PB15
继电器 PB8
蜂鸣器 PB9
运行LED PB4
报警LED PB5
KEY1 PC0
KEY2 PC1
KEY3 PC2
KEY4 PC3

4.2 项目完整代码设计-main.c

学习视频下载链接: https://pan.quark.cn/s/0476a08f4a09

cpp 复制代码
/************************************************
 * File    : main.c
 * MCU     : STM32F103RCT6
 * Project : IoT Smart Socket
 ************************************************/

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "lcd.h"
#include "esp8266.h"
#include "mqtt.h"
#include "sht30.h"
#include "modbus.h"
#include "relay.h"
#include "beep.h"
#include "key.h"
#include "flash.h"

typedef struct
{
    float Voltage;
    float Current;
    float Power;
    float PF;
    float Freq;
    float Energy;

    float Temp;
    float Humi;

    float Cost;

    uint8_t Relay;
    uint8_t Alarm;

}DEVICE_DATA;

DEVICE_DATA Device;

float PowerLimit  = 2000.0f;
float CurrentLimit = 10.0f;

float TempLimit = 50.0f;
float HumiLimit = 80.0f;

float Price = 0.60f;

uint32_t UploadTick=0;
uint32_t LcdTick=0;

void Hardware_Init(void);
void ReadSensorData(void);
void AlarmCheck(void);
void LCD_Display(void);
void UploadCloud(void);
void Relay_Control(void);
void ParseCloudCommand(void);

int main(void)
{
    Hardware_Init();

    while(1)
    {
        ReadSensorData();

        AlarmCheck();

        Relay_Control();

        ParseCloudCommand();

        if(SysTickMs()-LcdTick > 500)
        {
            LcdTick = SysTickMs();
            LCD_Display();
        }

        if(SysTickMs()-UploadTick > 3000)
        {
            UploadTick = SysTickMs();
            UploadCloud();
        }

        KEY_Scan();

        delay_ms(10);
    }
}

void Hardware_Init(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    delay_init();

    uart1_init(115200);

    uart2_init(9600);

    LCD_Init();

    SHT30_Init();

    ESP8266_Init();

    MQTT_Init();

    Relay_Init();

    Beep_Init();

    KEY_Init();

    FLASH_ReadParam();

    Relay_Off();

    LCD_Clear(BLACK);
}

void ReadSensorData(void)
{
    SHT30_ReadData(
        &Device.Temp,
        &Device.Humi);

    Modbus_ReadMeter();

    Device.Voltage = Meter.Voltage;
    Device.Current = Meter.Current;
    Device.Power   = Meter.Power;
    Device.PF      = Meter.PF;
    Device.Freq    = Meter.Freq;
    Device.Energy  = Meter.Energy;

    Device.Cost = Device.Energy * Price;
}

void AlarmCheck(void)
{
    Device.Alarm = 0;

    if(Device.Current > CurrentLimit)
    {
        Device.Alarm = 1;
    }

    if(Device.Power > PowerLimit)
    {
        Device.Alarm = 2;
    }

    if(Device.Temp > TempLimit)
    {
        Device.Alarm = 3;
    }

    if(Device.Humi > HumiLimit)
    {
        Device.Alarm = 4;
    }

    if(Device.Alarm)
    {
        Beep_On();
        Relay_Off();
        Device.Relay = 0;
    }
    else
    {
        Beep_Off();
    }
}

void Relay_Control(void)
{
    if(Device.Relay)
    {
        Relay_On();
    }
    else
    {
        Relay_Off();
    }
}

void LCD_Display(void)
{
    LCD_ShowString(0,0,"IoT Smart Socket");

    LCD_ShowFloat(0,20,Device.Voltage,1);
    LCD_ShowFloat(0,40,Device.Current,2);
    LCD_ShowFloat(0,60,Device.Power,1);

    LCD_ShowFloat(0,80,Device.PF,2);
    LCD_ShowFloat(0,100,Device.Freq,2);

    LCD_ShowFloat(0,120,Device.Temp,1);
    LCD_ShowFloat(0,140,Device.Humi,1);

    LCD_ShowFloat(0,160,Device.Energy,3);

    LCD_ShowFloat(0,180,Device.Cost,2);
}

void UploadCloud(void)
{
    char json[512];

    sprintf(json,
    "{"
    "\"voltage\":%.1f,"
    "\"current\":%.2f,"
    "\"power\":%.1f,"
    "\"pf\":%.2f,"
    "\"freq\":%.2f,"
    "\"temp\":%.1f,"
    "\"humi\":%.1f,"
    "\"relay\":%d,"
    "\"energy\":%.3f,"
    "\"costUsed\":%.2f,"
    "\"alarm\":%d,"
    "\"price\":%.2f"
    "}",
    Device.Voltage,
    Device.Current,
    Device.Power,
    Device.PF,
    Device.Freq,
    Device.Temp,
    Device.Humi,
    Device.Relay,
    Device.Energy,
    Device.Cost,
    Device.Alarm,
    Price);

    MQTT_Publish("smartsocket/data",json);
}

void ParseCloudCommand(void)
{
    if(MQTT_NewMessage()==0)
        return;

    if(MQTT_GetRelay()==1)
    {
        if(Device.Alarm==0)
            Device.Relay=1;
    }
    else
    {
        Device.Relay=0;
    }

    PowerLimit =
        MQTT_GetPowerLimit();

    CurrentLimit =
        MQTT_GetCurrentLimit();

    TempLimit =
        MQTT_GetTempLimit();

    HumiLimit =
        MQTT_GetHumiLimit();

    Price =
        MQTT_GetPrice();

    FLASH_SaveParam();
}

4.3 程序下载

也有视频教程:

讲解如何编译代码,下载STM32程序: https://www.bilibili.com/video/BV1Cw4m1e7Yc

打STM32的keil工程,编译代码、然后,使用USB线将开发板的左边的USB口(串口1)与电脑的USB连接,打开程序下载软件下载程序。

具体下载过程看下面图:

打开程序下载软件:软件就在资料包里的软件工具目录下

4.4 程序正常运行效果

设备运行过程中会通过串口打印调试信息,我们可以通过串口打印了解程序是否正常。

程序下载之后,可以打开串口调试助手查看程序运行的状态信息。软件就在资料包里的软件工具目录下

4.5 取模软件的使用

显示屏上会显示中文,字母,数字等数据,可以使用下面的取模软件进行取模设置。

软件就在资料包里的软件工具目录下

打开软件之后:

五、总结

当前实现了一种基于STM32F103RCT6单片机的物联网家用智能插座系统。系统以STM32作为核心控制器,结合电力参数采集模块、SHT30温湿度传感器、ESP8266无线通信模块、TFT-LCD显示屏、继电器控制模块以及蜂鸣器报警模块,完成了用电参数监测、环境监测、远程控制、异常报警和物联网数据传输等功能的设计与实现。

系统能够实时采集负载电压、电流、有功功率、功率因数、电网频率以及累计电能等电力参数,同时对环境温度和湿度进行监测,并通过本地LCD显示屏实时显示设备运行状态和各项监测数据。用户不仅可以在设备端查看实时信息,还能够通过Android手机APP远程查看设备数据、控制负载电源开关、设置电价参数以及配置功率和电流保护阈值,实现对家庭电器的智能化管理。

在物联网通信方面,系统利用ESP8266模块接入家庭无线网络,通过MQTT协议将设备数据实时上传至华为云物联网平台,实现设备与云端服务器之间的数据交互。手机APP通过云平台获取设备状态信息并下发控制指令,从而完成远程监测和远程控制功能,提高了设备的智能化水平和使用便利性。

为了提高系统运行的安全性,设计中增加了过流保护、过功率保护、超温报警以及超湿报警等安全机制。当系统检测到异常状态时,能够及时通过蜂鸣器进行声光报警,并自动切断继电器输出,避免因设备过载或环境异常导致的安全隐患,有效提升了家庭用电的安全保障能力。

通过本课题的设计与实现,验证了STM32单片机在智能家居和物联网应用领域中的良好应用价值,实现了传统家用插座向智能化、网络化方向的升级。系统具有结构简单、功能完善、运行稳定、扩展性强等特点,不仅能够帮助用户实时掌握设备运行状态和耗电情况,提高能源利用效率,而且具有较好的实用价值和推广前景。

未来可在现有系统基础上进一步扩展历史数据统计分析、多设备集中管理、微信小程序控制、Web可视化管理平台、语音控制以及智能场景联动等功能,不断提升系统的智能化程度和用户体验,为智慧家庭和智能能源管理提供更加完善的解决方案。

相关推荐
全球通史8 小时前
Keil 配置 MSPM0G3507 开发环境避坑:SysConfig、msp.h 和 driverlib.a 报错完整解决记录
stm32·单片机·嵌入式硬件
黎阳之光9 小时前
视频孪生智护供水生命线:黎阳之光赋能医疗与园区水务高质量升级
运维·物联网·算法·安全·数字孪生
iCxhust10 小时前
C# 命令行指令 查看二进制文件
开发语言·单片机·嵌入式硬件·c#·proteus·微机原理·8088单板机
砍材农夫11 小时前
物联网实战:Spring Boot MQTT | MQTT 设备模拟器演示(附源码)
java·spring boot·后端·物联网·spring·netty
TDengine (老段)11 小时前
TDengine 压缩编码机制 — 双层压缩架构与类型特化算法
大数据·数据库·物联网·算法·时序数据库·tdengine·涛思数据
宏电物联网13 小时前
从被动救火到主动运维,宏电三三云重新定义 IoT 管理
物联网·iot·物联网平台
KaMeidebaby14 小时前
卡梅德生物技术快报|蛋白翻译后修饰:YAP/TAZ 分子调控机制与靶向干预技术
前端·人工智能·物联网·百度·新浪微博
某林21214 小时前
ROS2 机器人底盘调试避坑指南:从 `/odom` 丢失到彻底跑通的硬核排障实录
stm32·机器人·人机交互
芯岭技术郦14 小时前
集成 2.4G 射频收发器、MCU 及丰富外设的XL2417D透传模组
单片机·嵌入式硬件