• 正文
  • 相关推荐
申请入驻 产业图谱

基于ESP32+60G毫米波生物感知雷达设计的睡眠监控系统

1小时前
144
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

一、前言

1.1 项目介绍

【1】项目开发背景

随着社会生活节奏的不断加快,人们长期面临工作压力大、作息不规律以及睡眠时间不足等问题,睡眠质量下降已经成为影响现代人身体健康的重要因素之一。长期睡眠质量不佳容易引发疲劳、注意力下降、免疫力降低以及心血管疾病等健康问题。因此,如何对人体睡眠状态进行实时监测与分析,已经成为当前智能健康监测领域的重要研究方向。传统的睡眠监测大多依赖医院专业设备,存在设备体积大、价格昂贵、佩戴不方便等问题,难以满足家庭化、长期化以及实时化的健康监测需求。

近年来,随着物联网技术无线通信技术以及毫米波雷达技术的快速发展,非接触式人体生命体征检测逐渐成为智能健康监测的重要研究热点。相比传统接触式传感器,毫米波雷达具有无需佩戴、隐私性较好、抗环境干扰能力强以及可连续监测等优点,能够在不影响用户正常休息的情况下实现对人体呼吸、心率以及睡眠状态的实时感知。其中,60GHz毫米波雷达由于具备较高的测量精度和较强的微弱信号检测能力,特别适合应用于人体生命体征检测及睡眠监测场景。

与此同时,随着智能家居和智慧医疗的发展,人们对远程健康监测提出了更高要求。通过将健康监测设备与云平台、移动终端以及上位机系统进行结合,可以实现数据远程查看、异常报警以及历史数据分析等功能,为家庭健康管理提供更加便捷的解决方案。因此,利用ESP32主控芯片良好的无线通信能力和较强的处理性能,结合MQTT物联网通信协议以及华为云IOT平台,实现人体睡眠数据的实时上传与远程监控,具有较高的实际应用价值。

本项目基于ESP32与60GHz毫米波生物感知雷达设计了一套睡眠监控系统,通过毫米波雷达实现人体存在检测、呼吸频率检测、心率检测、睡眠状态分析以及体动监测,同时结合MLX90614红外体温传感器实现人体体温测量。系统能够对人体生命体征进行综合分析,并在检测到异常生理指标时通过蜂鸣器进行报警提醒。此外,系统还支持LCD本地数据显示、微信小程序远程查看、Qt上位机数据显示以及Android手机APP监控等功能,从而构建一套集本地监测、无线传输、云端管理以及远程查看于一体的智能睡眠健康监测系统。

该系统不仅能够提高人体睡眠状态监测的实时性与便捷性,还能够降低传统睡眠监测设备对用户的束缚,具有较好的实用价值和推广意义。同时,本设计对于推动毫米波雷达技术在智慧医疗、智慧养老以及智能健康监测领域中的应用也具有一定的研究意义。

【2】设计实现的功能

(1)支持人体存在状态检测功能。系统利用60GHz毫米波雷达模块实时检测监测区域内是否存在人体,当检测到人体进入监测范围后,系统自动开始生命体征数据采集;当人体离开监测区域时,系统能够识别无人状态。

(2)支持人体呼吸频率检测功能。系统通过毫米波雷达采集人体胸腔微小起伏变化,对人体呼吸信号进行分析处理,实现人体呼吸频率的实时测量,并能够在显示终端上实时显示当前呼吸数据。

(3)支持人体心率检测功能。系统利用毫米波雷达对人体微弱生命体征进行非接触式检测,实现人体心率数据的实时采集与分析,并通过LCD显示屏、手机APP以及上位机同步显示当前心率信息。

(4)支持人体状态识别功能。系统能够根据人体活动特征判断当前人体状态,识别人体处于活跃状态或静止状态,并实时更新显示结果。

(5)支持人体体动幅度检测功能。系统通过毫米波雷达检测人体运动变化情况,对人体体动幅度进行分析,用于辅助判断人体睡眠状态以及睡眠质量。

(6)支持睡眠时长统计功能。系统在检测到人体进入睡眠状态后,能够自动开始计时,并实时统计人体睡眠持续时间,实现睡眠时间记录功能。

(7)支持睡眠质量分析功能。系统通过对人体呼吸、心率以及体动数据进行综合分析,实现对人体睡眠状态的判断,可识别清醒、浅睡以及深睡等睡眠状态,并将结果实时显示。

(8)支持人体体温检测功能。系统采用MLX90614红外体温传感器实现人体体温的非接触式测量,并实时采集人体温度数据。

(9)支持异常生理指标报警功能。当系统检测到人体心率或体温超出设定安全范围时,系统会自动驱动蜂鸣器发出声音报警,同时在显示界面中提示报警信息,实现异常状态提醒。

(10)支持本地LCD显示功能。系统采用1.44寸SPI接口LCD显示屏对人体生命体征数据进行本地实时显示,包括人体体温、呼吸频率、心率、人体存在状态、体动幅度、睡眠时长以及睡眠质量等信息。

(11)支持WIFI无线联网功能。系统利用ESP32自身集成的WIFI功能实现无线联网,使设备能够接入局域网并与云平台进行数据通信

(12)支持MQTT物联网通信功能。系统采用MQTT通信协议将采集到的人体生命体征数据上传至华为云IOT物联网服务器,实现远程数据交互。

(13)支持微信小程序远程监测功能。系统设计微信小程序客户端,能够通过网络实时查看设备上传的人体生命体征数据与睡眠监测信息。

(14)支持Android手机APP监测功能。系统基于Qt设计Android手机APP,实现人体呼吸、心率、体温以及睡眠状态等数据的远程显示。

(15)支持Windows上位机监测功能。系统基于Qt设计Windows电脑上位机,实现人体生命体征数据的实时接收、显示以及远程监控功能。

**当前项目使用的相关软件工具、模块源码已经上传到网盘:**https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

【3】项目硬件模块组成

(1)ESP32主控模块。系统采用ESP32作为核心控制器,负责完成毫米波雷达数据接收、体温数据采集、数据显示、报警控制以及WIFI联网通信等任务,同时实现各硬件模块之间的数据处理与协调控制。

(2)60GHz毫米波雷达检测模块。系统采用R60ABD1毫米波生物感知雷达模块,用于非接触式检测人体生命体征信息,实现人体存在检测、呼吸频率检测、心率检测、人体状态识别、体动幅度检测以及睡眠状态分析等功能。

(3)MLX90614红外体温检测模块。系统采用MLX90614红外体温传感器实现人体体温的非接触式测量,并将采集到的温度数据发送给ESP32进行处理与显示。

(4)LCD显示模块。系统采用1.44寸SPI接口LCD显示屏,用于本地实时显示人体呼吸频率、心率、体温、人体存在状态、体动幅度、睡眠时长以及睡眠质量等信息。

(5)蜂鸣器报警模块。系统采用高电平触发蜂鸣器作为报警装置,当检测到人体心率或体温超过安全范围时,ESP32控制蜂鸣器发出报警声音,实现异常提醒功能。

(6)WIFI无线通信模块。系统利用ESP32内部集成的WIFI功能实现无线联网,用于设备连接路由器并与华为云IOT物联网服务器进行数据通信。

(7)USB供电模块。系统采用USB接口供电方式,通过USB电源线连接电源适配器为ESP32主控、电温传感器、毫米波雷达模块、LCD显示屏以及其他硬件模块提供稳定电源

(8)MQTT云平台通信模块。系统通过MQTT协议实现ESP32与华为云IOT物联网服务器之间的数据交互,用于上传人体生命体征数据以及远程监测数据通信。

【4】设计意义

随着现代社会生活节奏不断加快,人们普遍面临熬夜、工作压力大以及作息不规律等问题,睡眠质量下降已经逐渐成为影响人体健康的重要因素。长期睡眠不足或睡眠质量较差容易导致精神状态下降、免疫力降低以及多种慢性疾病的发生。因此,对人体睡眠状态进行长期、实时、便捷的监测具有重要的现实意义。传统睡眠监测设备大多需要人体佩戴传感器或前往医院进行专业检测,不仅使用复杂,而且难以满足家庭化和长期化监测需求。本设计采用60GHz毫米波雷达实现非接触式生命体征检测,能够在不影响用户正常休息的情况下完成睡眠监测,提高了系统的使用舒适性和实用性。

本系统利用毫米波雷达实现人体呼吸频率、心率、人体存在状态以及睡眠状态的检测,同时结合MLX90614红外体温传感器实现人体体温监测,可以较为全面地反映人体睡眠期间的生理状态。当系统检测到人体心率或体温出现异常时,还能够及时进行声光报警提醒,从而提高用户对自身健康状态的关注程度。通过实时监测人体生命体征数据,系统能够为用户提供更加直观的健康参考信息,对于家庭健康监护以及个人健康管理具有一定的辅助作用。

随着物联网技术的发展,传统健康监测设备逐渐向智能化和远程化方向发展。本设计通过ESP32内置WIFI功能结合MQTT通信协议,实现人体生命体征数据上传至华为云IOT物联网平台,并分别设计微信小程序、Android手机APP以及Windows上位机,实现远程数据显示与监测功能。用户无需在设备旁边即可查看人体实时数据,提高了系统的数据交互能力与远程监控能力,使设备具备较好的智能化特性。

此外,本系统采用ESP32作为主控芯片,具有硬件成本较低、开发资源丰富以及无线通信能力强等优点,同时结合LCD显示屏实现本地实时数据显示,使系统具备良好的人机交互能力。整个系统融合了毫米波雷达技术、红外测温技术、无线通信技术以及物联网云平台技术,对于推动智能睡眠监测设备在智慧医疗、智慧养老以及家庭健康监护领域中的应用具有一定的实际意义和研究价值。

【5】市面上同类产品研究现状

当前,基于毫米波雷达的非接触式睡眠监控系统已成为智能健康领域的重要发展方向。与传统的光学摄像头(侵犯隐私)、可穿戴手环(需要贴身佩戴且易脱落)相比,毫米波雷达技术具有非接触、隐私友好、不受光照影响、可穿透被褥等优势,尤其适合用于长期睡眠监测。市面上及学术研究中已出现多款代表性产品与技术方案。

首先,以商业化成熟产品为例,瑞士公司开发的Sleepiz 睡眠监测仪是典型的高端医疗级案例。该产品采用60GHz FMCW毫米波雷达作为核心传感器,能够非接触式测量人体的呼吸频率、心率、体动幅度,并通过内置算法进行睡眠分期识别,区分深睡、浅睡、REM快速眼动期和清醒状态。Sleepiz还具备异常呼吸事件检测能力,可用于初步筛查睡眠呼吸暂停综合征。硬件上,Sleepiz集成了多核ARM处理器负责雷达信号处理,数据通过WiFi上传至云端,并提供医生端与用户端App进行远程查看。该产品已通过欧洲CE医疗认证,在多家医院睡眠中心得到临床验证。不过其不足之处在于售价较高(约500至800美元),且未集成体温传感器,也没有本地LCD显示屏用于实时数据展示。

另一个广受关注的消费级产品是谷歌Nest Hub 2代智能显示屏。该产品集成了英飞凌提供的Sol i 60GHz毫米波雷达芯片,主要用于睡眠监测功能。用户将设备置于床头柜上,即可在夜间自动检测呼吸频率、体动次数、咳嗽与打鼾情况,并生成睡眠时长和睡眠质量评分。Nest Hub 2代还利用内置的环境光与温度传感器辅助分析睡眠环境。数据通过Google Fit App呈现给用户,设备本身也可通过语音或屏幕展示简要的睡眠总结。作为消费电子产品,其价格亲民,约99美元,但由于法规限制,该产品未提供心率检测功能,也不支持用户导出原始数据或对接第三方物联网平台,缺少体温传感器,扩展性受限。

在国内市场,倍轻松智能睡眠仪采用24GHz毫米波雷达,成本较低,售价在百元级。该产品能够检测体动、粗略判断睡眠时长和深浅睡比例,但由于其工作频率较低(24GHz相比60GHz),对呼吸和心率引起的微动信号捕捉能力较弱,导致呼吸频率、心率测量不稳定,准确度明显低于60GHz方案。该产品也未集成体温检测和本地LCD屏幕,用户必须依赖手机App查看历史数据,整体体验偏向入门级。

在学术界和开源硬件领域,德州仪器TI的mmWave睡眠监测参考设计是一个典型技术案例。该方案基于IWR6843 60GHz雷达传感器和TM4C1294主控MCU,能够实现呼吸频率测量(精度可达±1次/分)、心率提取(通过胸部微动信号,精度约±3次/分)、体动幅度量化以及人在/不在检测。TI提供了完整的信号处理算法库,包括距离FFT、多普勒提取、相位解调等关键步骤,并给出了睡眠质量评估的参考实现方法。该设计支持通过WiFi模块将数据上传到云服务器,并可对接第三方平台。这一参考设计被大量高校和企业作为开发基础,但其作为开发套件而非成品,需要用户具备较强的嵌入式开发能力,且未直接集成体温传感器和本地屏幕。

在国内科研领域,中科院半导体研究所曾发表基于60GHz雷达的睡眠监测系统研究成果,该系统在呼吸暂停检测和心率变异性分析上取得了较高精度,并与医院睡眠监测仪进行对比验证,一致性达到90%以上。该研究强调了将雷达信号转化为临床可用生理参数的信号处理方法,包括消除身体随机运动干扰、分离呼吸谐波成分等。

总结来看,当前市面上基于毫米波雷达的睡眠监控系统正朝着高精度化、多参数融合、云端数据服务三个方向发展。60GHz频段因其波长更短、对微动敏感,已成为主流选择。现有产品主要存在以下不足:一是集成体温检测功能的产品非常少,绝大多数案例仅关注呼吸和心率;二是本地显示能力薄弱,普遍依赖手机App或云端查看,缺乏设备端的实时LCD反馈;三是报警机制简单,针对心率、体温异常的可视与可听联动报警不完善;四是数据上云方案大多采用厂商私有云,开放性差,难以对接华为云等通用物联网平台。因此,基于ESP32与60GHz雷达,集成MLX90614体温传感器、本地LCD显示、蜂鸣器报警并采用华为云MQTT协议上云的系统方案,在当前市场和技术现状下具有较强的实用价值和差异化优势。

【6】摘要

随着智能健康监测技术和物联网技术的不断发展,人体睡眠状态监测逐渐成为智能医疗与家庭健康管理领域的重要研究方向。针对传统睡眠监测设备存在接触式检测不舒适、设备复杂以及难以长期家庭化使用等问题,设计了一种基于ESP32与60GHz毫米波生物感知雷达的睡眠监控系统。系统采用ESP32作为主控芯片,结合R60ABD1毫米波雷达模块,实现人体呼吸频率、心率、人体存在状态、人体活动状态、体动幅度、睡眠时长以及睡眠质量等生命体征信息的非接触式检测。同时,系统结合MLX90614红外体温传感器,实现人体体温数据采集,并利用蜂鸣器完成异常心率与异常体温报警功能。

系统采用1.44寸LCD显示屏实现本地数据显示,并利用ESP32内置WIFI功能,通过MQTT协议将人体生命体征数据上传至华为云IOT物联网平台,实现远程数据通信。同时,分别设计微信小程序、Android手机APP以及Windows上位机,实现远程监测与数据显示功能。该系统具有非接触式检测、实时监测、无线联网以及远程查看等特点,可应用于家庭健康监护、智慧养老以及智能睡眠监测等场景。

关键字:ESP32;60GHz毫米波雷达;睡眠监测;生命体征检测;MQTT;物联网;MLX90614;Qt上位机

1.2 设计思路

本系统以ESP32作为核心控制器,整体设计围绕“人体生命体征采集、本地数据显示、异常报警以及远程物联网监测”展开。系统通过ESP32对各功能模块进行统一管理,实现人体睡眠状态信息的实时采集、处理、显示以及网络通信。设计过程中重点考虑系统的实时性、非接触式检测能力以及远程监控能力,从而构建一套适用于家庭健康监测场景的智能睡眠监控系统。

在人体生命体征检测部分,系统采用60GHz频段的R60ABD1毫米波雷达模块作为核心检测单元。毫米波雷达能够通过检测人体微小运动变化,实现人体存在状态、呼吸频率、心率、人体活动状态以及体动幅度等信息的非接触式检测。ESP32通过串口与毫米波雷达进行通信,实时读取雷达上传的数据,并对相关数据进行解析与处理。系统根据采集到的呼吸、心率以及体动信息,对人体睡眠状态进行综合分析,从而判断人体处于清醒、浅睡或深睡状态,同时统计人体睡眠时长。

在人体体温检测部分,系统采用MLX90614红外体温传感器进行非接触式温度测量。ESP32通过IIC通信方式读取MLX90614采集的人体温度数据,并将温度信息与毫米波雷达检测到的生命体征数据进行统一管理,实现人体多参数健康信息的综合监测。

在本地显示部分,系统采用1.44寸SPI接口LCD显示屏作为人机交互界面。ESP32通过SPI接口驱动LCD显示屏,实时显示人体呼吸频率、心率、体温、人体存在状态、人体活动状态、体动幅度、睡眠时长以及睡眠质量等信息,使用户能够直接查看当前监测数据,提高系统的可视化效果与交互能力。

在异常报警部分,系统通过设定人体心率与体温的安全阈值,当检测数据超出设定范围时,ESP32会自动控制蜂鸣器发出报警声音,同时在显示界面中显示报警提示信息,从而提醒用户当前人体状态异常,提高系统的安全预警能力。

在远程物联网通信部分,系统利用ESP32内部集成的WIFI功能连接无线网络,并采用MQTT通信协议与华为云IOT物联网平台建立通信连接。ESP32将采集到的人体生命体征数据实时上传至云端服务器,实现远程数据存储与交互。为了实现多终端远程监测,系统分别设计微信小程序、Android手机APP以及Windows电脑上位机。用户可通过不同终端实时查看设备上传的人体睡眠监测数据,实现远程健康监控功能。

整个系统采用模块化设计方式,各功能模块之间相互独立又能够协同工作,不仅提高了系统的稳定性与扩展性,同时也便于后续系统调试与功能维护。通过将毫米波雷达技术、红外测温技术以及物联网通信技术相结合,实现了一套具备非接触式检测、本地显示、异常报警以及远程监控功能的智能睡眠监控系统。

1.3 系统功能总结

序号 系统功能模块 功能说明
(1) 人体存在检测功能 系统通过60GHz毫米波雷达检测监测区域内是否存在人体,实现人体存在状态识别。
(2) 呼吸频率检测功能 系统利用毫米波雷达实时检测人体呼吸信号,并测量人体呼吸频率数据。
(3) 心率检测功能 系统通过毫米波雷达实现人体心率信息采集,并实时显示当前心率数据。
(4) 人体状态识别功能 系统能够识别人体当前处于活跃状态或静止状态,并实时更新状态信息。
(5) 体动幅度检测功能 系统通过毫米波雷达检测人体运动变化情况,实现人体体动幅度分析。
(6) 睡眠时长统计功能 系统能够自动统计人体睡眠持续时间,实现睡眠时长记录。
(7) 睡眠质量分析功能 系统根据人体呼吸、心率以及体动数据,对人体睡眠状态进行分析,可识别清醒、浅睡和深睡状态。
(8) 人体体温检测功能 系统采用MLX90614红外体温传感器实现人体体温非接触式测量。
(9) 异常报警功能 当检测到人体心率或体温超过安全范围时,系统自动控制蜂鸣器进行报警提示,同时显示报警信息。
(10) LCD本地显示功能 系统通过1.44寸SPI接口LCD显示屏实时显示人体体温、呼吸频率、心率、人体状态、体动幅度、睡眠时长以及睡眠质量等信息。
(11) WIFI联网功能 系统利用ESP32内部集成WIFI功能实现无线网络连接。
(12) MQTT数据上传功能 系统采用MQTT协议将人体生命体征数据上传至华为云IOT物联网服务器。
(13) 微信小程序远程监测功能 用户可通过微信小程序远程查看设备上传的人体生命体征与睡眠监测数据。
(14) Android APP监测功能 系统采用Qt设计Android手机APP,实现人体生命体征数据远程显示功能。
(15) Windows上位机监测功能 系统采用Qt设计Windows电脑上位机,实现人体生命体征数据接收与远程监控功能。

根据你提供的项目设计方案,以下是重新撰写的“开发工具的选择”章节内容,贴合ESP32+VSCODE+ESP-IDE、Qt、微信小程序等实际技术栈:


1.4 开发工具的选择

【1】设备端开发

设备端开发采用C语言作为主要编程语言,直接操作硬件寄存器与外设接口,以保证系统的实时性、运行效率和代码紧凑性。C语言在嵌入式领域中具有成熟的生态,能够精确控制内存与指令周期,满足睡眠监控系统对多传感器数据采集、信号处理及网络通信的严格要求。

本项目主控芯片选用ESP32系列(如ESP32-WROOM-32D),开发工具选择Visual Studio Code配合**ESP-IDF(Espressif IoT Development Framework)**插件作为核心开发环境。VS Code是一款轻量级但功能强大的源代码编辑器,具备代码补全、语法高亮、Git集成及丰富的扩展生态。ESP-IDF是乐鑫官方提供的物联网开发框架,基于FreeRTOS实时操作系统,提供了完整的WiFi、TCP/IP、MQTT、I2C、SPI、UART等外设驱动库。

在VS Code中安装ESP-IDF扩展后,开发者可以便捷地进行项目创建、库管理、编译、烧录和串口监视。与传统的Keil+STM32方案不同,ESP-IDF采用CMake构建系统,支持组件化开发,便于复用雷达驱动、传感器驱动和通信协议模块。虽然ESP-IDF提供了高层API,但本项目针对关键时序操作(如SPI驱动LCD、I2C读取MLX90614、UART接收R60ABD1雷达数据)仍可通过直接配置ESP32的寄存器或使用底层寄存器操作宏来实现极致性能。调试方面,VS Code配合OpenOCD支持JTAG调试,可进行断点跟踪和变量监视,提升复杂逻辑的验证效率。

通过VS Code + ESP-IDF的组合,能够充分发挥ESP32的双核处理能力、WiFi网络栈以及低功耗管理特性,为毫米波雷达数据处理、MQTT数据上云及本地LCD刷新提供稳定、高效的开发支撑。

【2】上位机开发

本项目上位机覆盖Windows电脑端和Android手机端,采用Qt 5框架进行跨平台开发,编程语言使用C++。Qt是一个成熟的C++图形用户界面应用程序框架,具备“一次编写,多处编译”的跨平台能力,其信号与槽机制简化了事件驱动编程,模块化的类库支持网络通信、JSON解析、图表绘制等常用功能。

开发环境选用Qt Creator,这是Qt官方提供的轻量级IDE,集成了可视化UI设计器(Qt Designer)、代码编辑器、编译器(MinGW或MSVC for Windows,Android NDK for Android)及调试器。通过配置不同的Kit(工具链),可以在同一工程下分别为Windows和Android平台生成可执行文件或APK安装包。

在上位机功能实现方面,Qt的QTcpSocketQTcpServer类用于建立与华为云IoT平台的MQTT bridge对接,或者通过HTTP/HTTPS API获取设备上报的数据。实际部署中,上位机作为MQTT客户端,通过Qt提供的QMqttClient模块(需要添加MQTT扩展)订阅华为云设备Topic,实时接收呼吸频率、心率、体温、体动幅度、睡眠质量等数据。对于图表展示(如心率变化曲线、睡眠分期柱状图),可利用Qt Charts模块实现可视化。报警提示通过消息框(QMessageBox)、状态栏变色或语音合成(Qt TextToSpeech)方式呈现,确保用户及时获知异常生理指标。

Qt的跨平台特性显著降低了维护成本——Windows版本用于床边PC或护理站大屏显示,Android版本供用户随身查看,两者共享同一套业务逻辑代码,仅针对屏幕尺寸和触摸交互进行适配调整。

【3】微信小程序开发

微信小程序作为移动端轻量级查看工具,采用微信官方小程序开发工具进行开发,使用JavaScript + WXML + WXSS模板进行界面编写。小程序负责从华为云IoT平台获取设备上报的数据并展示给用户,同时提供历史记录查询和报警信息推送能力。

开发环境选择微信官方IDE,它内置了代码编辑、模拟调试、真机预览、云开发集成等功能。项目采用小程序的标准目录结构,其中:

pages/

    • 目录存放各个页面(如实时监测页面、历史数据页面、报警记录页面);•

utils/

    • 目录封装MQTT over WebSocket或HTTPS API调用函数,用于与华为云IoT平台通信;•

app.js

    作为全局入口,管理用户登录和设备绑定逻辑。

小程序通过调用华为云IoT平台的REST API(如查询设备属性、查询设备历史数据)或建立WebSocket MQTT连接,获取ESP32上报的睡眠监测数据。在界面上,使用canvas或echarts-for-weixin绘制心率趋势图、睡眠分期饼图;利用微信的订阅消息能力,当设备检测到心率或体温异常时,向用户推送模板消息提醒。

微信小程序开发工具提供的实时日志和性能面板,便于开发者调试网络请求、优化页面渲染速度,确保用户在手机上能够流畅查看睡眠报告。

【4】物联网云平台工具

本项目的MQTT服务器采用华为云IoT物联网平台。开发者通过华为云控制台创建产品、注册设备、定义物模型(属性:呼吸频率、心率、体温、体动幅度、睡眠时长、睡眠质量等)。设备侧配置MQTT连接参数(服务器地址、端口8883、设备ID、密钥),ESP32通过ESP-IDF的MQTT客户端库接入华为云。

为了方便调试设备与云端的通信,华为云提供了MQTT设备模拟器设备接入服务,可以在控制台中直接查看设备上报的数据和下发指令。同时,华为云IoT支持数据转发至对象存储服务(OBS)或RDS数据库,为微信小程序和Qt上位机提供历史数据查询API。

1.5 模块的技术详情介绍


1. 主控单元:ESP32

功能:作为整个睡眠监控系统的核心控制单元,负责协调所有外设模块的工作。具体包括:通过UART/I2C接口读取R60ABD1毫米波雷达的检测数据,通过I2C接口读取MLX90614体温传感器的测量值,驱动SPI接口的LCD显示屏进行本地数据展示,控制蜂鸣器进行异常报警,以及管理WiFi网络连接和MQTT协议的数据上云通信。

特点:ESP32内部集成了两个32位LX6处理器核心,主频最高可达240MHz,具备较强的运算能力,能够处理雷达输出的原始数据或初步分析结果。其内置的WiFi(802.11 b/g/n)和蓝牙功能满足本项目无线联网需求,无需额外网络模块。此外,ESP32拥有丰富的外设接口(包括多个UART、I2C、SPI、GPIO),可同时连接雷达、体温传感器、显示屏和蜂鸣器,且支持深度睡眠模式,有助于降低系统待机功耗。

2. 毫米波雷达模块:R60ABD1(60GHz频段)

功能:实现非接触式人体生物信号感知。具体可检测的参数包括:呼吸频率、心率、人体存在与否(是否有人)、人体状态(活跃或静止)、体动幅度、睡眠时长以及睡眠质量分期(清醒、深睡、浅睡)。该模块通过发射60GHz频段的调频连续波(FMCW)信号,接收人体胸腔和身体的微弱回波,利用相位变化提取呼吸和心跳引起的微动位移。

特点:60GHz频段相比于常见的24GHz雷达,波长更短(约5mm),对微小位移(如心跳引起的亚毫米级震动)更加敏感,因此心率测量精度更高。R60ABD1通常内部集成了信号处理单元,可直接输出呼吸频率、心率等解析后的数据,降低主控芯片的算法负担,通过串口(UART)以特定数据帧格式与ESP32通信。该模块对人体被褥、衣物具有穿透能力,且不采集影像信息,保护用户隐私。其探测距离一般在0.5至2米范围内,适合床旁安装。

3. 显示模块:1.44寸LCD显示屏(SPI协议接口)

功能:在设备本地实时展示监测数据,便于用户在不打开手机或电脑的情况下直接查看。显示内容包括:人体体温、呼吸频率、心率、人体存在状态(有人/无人)、体动幅度、睡眠时长以及睡眠质量(清醒、深睡/浅睡)。•

特点:1.44英寸的尺寸小巧,适合作为紧凑型设备的状态面板。采用SPI串行接口通信,仅需4根信号线(SCLK、MOSI、CS、DC)即可与ESP32连接,节约GPIO引脚资源。该屏幕通常为TFT彩色或单色OLED类型,分辨率常见为128×128或128×64,功耗较低,能够满足长时间夜间运行需求。SPI协议通信速率较高,刷新显示数据流畅,不会对主控造成明显负担。

4. 体温检测模块:MLX90614红外体温传感器

功能:实现非接触式人体体温测量。在不接触皮肤的情况下,通过检测人体辐射的红外能量,计算出体表温度(通常测量额头或手腕部位),用于辅助判断发热等异常健康状况。

特点:MLX90614是一款高精度数字红外温度传感器,出厂前经过校准,测量精度可达±0.5℃(人体温度范围内)。它内置了信号调节电路和17位ADC,通过I2C接口直接输出数字温度值,无需外部信号放大或模数转换。传感器的测量视场角(FOV)有多种可选(如90°、35°、12°),本项目可根据安装距离选择合适的版本。相比于接触式热敏电阻热电偶,该传感器实现了完全非接触测温,避免用户佩戴不适感,同时响应速度快(约0.5秒)。

5. 报警模块:高电平触发蜂鸣器

功能:当系统检测到生理参数异常时,发出声音警报,及时提醒用户或家人关注。触发条件包括:心率超过预设的安全范围(过高或过低)或体温超出异常生理指标(如发热)。报警提示也会同步在可视化页面(手机App、小程序、上位机)上显示,形成声光与远程双重提醒。•

特点:采用高电平触发方式,即ESP32的GPIO输出高电平(通常3.3V或5V)时蜂鸣器鸣叫,低电平时关闭。这种控制逻辑简单可靠,与ESP32的数字输出引脚直连,无需复杂驱动电路(如有源蜂鸣器自带振荡源,只需供电即可发声)。蜂鸣器体积小、成本低,发声强度一般在85dB以上,足够在卧室环境内引起注意。通过软件设计可实现间歇性鸣叫或不同音调报警,提高警示辨识度。

6. 系统供电:USB线供电(电源插头适配器)

功能:为整个睡眠监控系统提供稳定的工作电源,将220V交流市电转换为5V直流电,通过USB接口输送给ESP32及所有外设。•

特点:采用USB供电(配合手机充电头或电源插头适配器)具有通用性强、易于获取的优点。常见的USB适配器输出5V/1A或5V/2A,足以驱动ESP32(工作电流约80-200mA)、R60ABD1雷达(约100-200mA)、MLX90614(约1.5mA)、LCD显示屏(背光开启时约50-100mA)和蜂鸣器(约30mA)同时工作。USB线供电方式支持即插即用,无需内置电池或复杂的电源管理电路,降低了设计难度和成本。如果用户需要断电续航,也可外接移动电源作为备用。

7. WiFi功能:ESP32内置WiFi

功能:实现设备联网,将采集到的所有监测数据(呼吸频率、心率、体温、体动幅度、睡眠时长、睡眠质量等)通过MQTT协议上传至华为云IoT物联网服务器。同时,支持接收远程控制指令或参数配置(如报警阈值设定)。•

特点:ESP32集成了802.11 b/g/n标准的WiFi模块,支持Station模式连接家庭无线路由器。相比外接ESP8266或独立网卡,内置方案节省了硬件成本和PCB面积,且与主控的数据交换通过内部总线完成,传输效率高。WiFi连接配置可通过Web配网或蓝牙辅助配网等方式实现,方便用户操作。在MQTT协议支持下,数据以JSON格式发布到华为云指定Topic,保证与微信小程序、Android App、Windows上位机的可靠数据同步。此外,ESP32的WiFi支持低功耗管理,在不发送数据时可进入省电状态。

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

华为云官网: 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:UsersLenovo>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:UsersLenovo>

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)添加自定义模型

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

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

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

比如:

烟雾可以叫  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

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

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

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

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

(4)主题发布格式

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

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

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

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

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

上传的JSON数据格式如下:

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

根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字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地址就是域名解析得到的)

华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883

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

ping  ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com

(2)生成MQTT三元组

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

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

下面是打开的页面:

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

直接得到三元组信息。

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

ClientId  663cb18871d845632a0912e7_dev1_0_0_2024050911
Username  663cb18871d845632a0912e7_dev1
Password  71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237

2.7 模拟设备登录测试

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

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

(1)填入登录信息

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

(2)打开网页查看

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

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

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

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

MQTT服务器:  117.78.5.125
MQTT端口号:  183

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

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


发布的数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字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】获取项目凭证 **  点击左上角用户名,选择下拉菜单里的我的凭证

项目凭证:

28add376c01e4a61ac8b621c714bf459

【2】创建IAM用户

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

点击左上角创建用户

创建成功:

【3】创建完成

用户信息如下:

主用户名  l19504562721
IAM用户  ds_abc
密码     DS12345678

2.9 获取影子数据

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

设备影子介绍:

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

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

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

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

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

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

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

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

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

链接如下:

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

三、搭建ESP32开发环境

3.1 安装VSCode代码编辑器

【1】下载软件包

下载地址: https://code.visualstudio.com/

安装包下载之后,直接鼠标双击运行。

【2】开始安装

接受协议继续安装。

选择一下安装路径。

浏览路径。

安装。

【4】配置中文语言

安装完成。打开的页面。

软件安装之后下面设置 Visual Studio 支持中文语言

首先打开 Visual Studio 软件, 再按下F1 或者 Shift + Ctrl + P

然后在命令行输入 Configure Display Language

选择安装语言选项。

安装之后右下角有提示重启,点击重启即可。

【5】配置主题颜色

修改vscode的颜色主题

下面介绍更改颜色vscode的内置颜色主题方法。

3.2 安装ESP-IDE环境

官方教程:https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1.5/esp32/get-started/index.html

【1】安装IDE环境

链接:https://dl.espressif.cn/dl/esp-idf/

下载成功后,双击开始安装。 安装的路径可以选择D盘,毕竟占用的空间还是比较大的。 比如,在D盘创建一个文件夹。ESP_IDF 后面安装时,路径就选择这文件夹。

注意:安装的时候要关闭VSCODE软件。

【2】VSCODE安装插件

官方教程:https://docs.espressif.com/projects/vscode-esp-idf-extension/zh_CN/latest/index.html

上一步安装完毕之后。 再打开VSCODE软件。

打开扩展商店,搜索espressif,然后看到第一个ESP-IDF ,点击右边出现的安装。

安装成功之后,打开命令行面板。

然后 输入 configure esp-idf extension

选择下面的弹出来的选项。

选择用户安装的路径。

选择识别出来的安装路径。

接下来就可以正常愉快的开发代码了。

3.3 快速新建工程

【1】快速新建项目

每次打开VSCODE想要快速新建ESP32的项目。

直接在命令行窗口。输入ESP-IDF:新建项目

选择新建项目。

选择新建工程。

配置工程的名称,存储工程的路径(不能出现中文),芯片的型号,调试方式。串口端口(如果当前没有接开发板大电脑里就不用管)。 选好了,点击右下角的选项创建工程。

创建成功之后,下面就可以选择创建工程的例程模版。

在第一个选项点击后会弹出两个选项可以选择,是使用扩展例程(ExtenSion)还是使用官方IDF的例程(ESP-IDF)。

当前选择 【ExtenSion】下的 【template-app】进行工程创建。(还有很多的例程,大家可以自行尝试)

点击右边的按钮,创建模版工程。

创建完成之后,右下角会提示【工程已经创建完毕,是否用新窗口打开工程?】点击Yes则会出现打开一个VSCode窗口,点击NO则会在该界面显示工程。

打开工程可以写一段打印函数。测试工程运行情况。

然后把ESP32通过串口插到电脑USB口上。

在下面可以选择下载方式,选择串口,然后选择串口的端口。

点击编译下载。程序运行之后会打印信息到终端。

第一次编译比较慢,需要多等一下。 后面的编译就比较快了。

成功之后,可以看到串口上的数据打印。

到此。工程就搭建好了。

【2】修改配置

在连接成功的情况下,点击设置按钮。

修改FLASH大小
在上方的搜索栏中输入【flash】,根据开发板板载的FLASH大小选择对应的FLASH大小。开发板板载W25Q64是8M大小。

修改系统时钟
在上方的搜索栏中输入【cpu】,根据情况选择CPU频率,这里选择的是最高频率240MHz。

修改完成之后点击【保存】键才会生效。

3.4 编程控制LED灯

【1】点亮LED灯

下面学习如何控制IO口,完成LED灯的控制。

为了模块化编程。下面需要新建一个目录,下面存储我们后续编写的外设硬件的代码。

新建一个hardware 文件夹。

然后在hardware 文件夹下面新建.c文件和.h文件。

新建2个文件。led.c和led.h。方便存储LED灯相关的源代码和函数声明。

在led.c的文件里写上代码:

#include "led.h"

//配置输出寄存器
#define GPIO_OUTPUT_PIN_SEL  (1ULL<<LED_PIN)

/**
 * @函数说明        LED的初始化
 *
 */
void LedGpioConfig(void)
{
    gpio_config_t gpio_init_struct = {0};

    //配置IO为通用IO
    esp_rom_gpio_pad_select_gpio(LED_PIN);

    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;             //不使用中断
    gpio_init_struct.mode = GPIO_MODE_OUTPUT;                   //输出模式
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;           //使能上拉模式
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;      //失能下拉模式
    gpio_init_struct.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;        //使用GPIO9输出寄存器

    //将以上参数配置到引脚
    gpio_config( &gpio_init_struct );

    //设置引脚输出高电平,默认不让LED亮
    gpio_set_level(LED_PIN, 1);
}

/**
 * @函数说明        设置LED亮
 *
 */
void LedOn(void)
{
    gpio_set_level(LED_PIN, 0);
}

/**
 * @函数说明        设置LED灭
 *
 */
void LedOff(void)
{
    gpio_set_level(LED_PIN, 1);
}

在led.h文件里写上代码:

#ifndef _BSP_LED_H_
#define _BSP_LED_H_

#include "driver/gpio.h"

//设置LED引脚
#define  LED_PIN  48

/**
 * @brief   LED初始化
 *
 */
void LedGpioConfig(void);

/**
 * @brief       设置LED亮
 *
 */
void LedOn(void);

/**
 * @brief       设置LED灭
 *
 */
void LedOff(void);
#endif

然后修改CMakeLists.txt 文件。 这个文件是配置头文件和源文件的路径的。  需要配置了之后,编译器才知道.c和.h文件在什么地方。

idf_component_register(SRCS "main.c"
                            "hardware/led.c"
                    INCLUDE_DIRS "."
                                 "./hardware")

最后编写main.c代码,在main.c文件里写上点灯的调用。

#include <stdio.h>
#include "hardware/led.h"

void app_main(void)
{
    //LED初始化
    LedGpioConfig();

    //设置LED点亮
    LedOn();

}

写完代码之后,点击构建烧录,代码就下载进去了。

下载成功之后,可以看到灯就已经亮了。 绿色的小灯就是点亮的LED灯。

【2】升级成闪光灯

要做成闪光灯就需要用到延时函数

在ESP-IDF 中,可以使用下面3种方式来实现延时:

    1. 1. vTaskDelay() 函数

 

    1. 这个函数会让当前的任务挂起指定的时间。例如,vTaskDelay(1000 / portTICK_PERIOD_MS) 会让当前任务挂起 1 秒钟,等待一个系统时钟周期。2. usleep() 函数 这个函数可以以微秒为单位精准延时。例如, usleep(1000000) 会让任务休眠 1 秒钟。3. esp_rom_delay_us()函数

 

    可以进行微秒级延时。

这里面的void vTaskDelay( const TickType_t xTicksToDelay ) 函数,是将一个任务延迟给定的xTicksToDelay数。任务被阻止的实际时间取决于tick rate。常数portTICK_PERIOD_MS可以用来从tick rate计算实时时间,分辨率为一个tick 周期。至于一个时钟节拍数是1ms,2ms,还是10ms,取决于configTICK_RATE_Hz,即 cONFI6_FREERTOS_HZ。CONFIG_FREERTOS_HZ在sdkconfig中定义,默认是100Hz。则一个时钟节拍数是10ms。

我们可以看一下系统默认的配置。

这个配置在 FreeRTOSConfig.h 文件中:

#define configTICK_RATE_HZ        100

简单来说:

configTICK_RATE_HZ 定义了 FreeRTOS 的系统时钟节拍频率,表示:

系统每秒钟产生 100 个时钟节拍(tick)

每个时钟节拍的间隔 = 10ms

     (1000ms ÷ 100 = 10ms)

在main.c里编写LED闪烁的代码。

#include <stdio.h>
#include "hardware/led.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

/**
 * @函数说明        毫秒延时函数
 * @参数 ms        延时的毫秒数
 * @注意           最小延时为1个FreeRTOS tick
 */
void delay_ms(uint32_t ms)
{
    if (ms == 0) return;
    
    TickType_t ticks = (ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS;
    vTaskDelay(ticks > 0 ? ticks : 1);
}

void app_main(void)
{
    int state=0;
    //LED初始化
    LedGpioConfig();

     while(1) {
        state=!state;
        delay_ms(1000);  // 延时1秒
        if(state)
        {
            LedOn();
        }
        else
        {
            LedOff();
        }
    }
}

然后编译烧录进去。

就可以看到LED灯在闪烁了。

3.5 ESP32项目代码设计

    • R60ABD1毫米波雷达(UART)数据解析• MLX90614红外体温传感器(I2C)读取• 本地LCD显示屏(SPI)刷新• 蜂鸣器心率异常报警• WiFi连接 + MQTT(华为云IoT)数据上报
// main.c
// 基于ESP32 + R60ABD1毫米波雷达 + MLX90614 + LCD + MQTT 的睡眠监控系统

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "mqtt_client.h"
#include "driver/uart.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "cJSON.h"

// ==================== 引脚定义 ====================
// 蜂鸣器 (高电平触发)
#define BUZZER_GPIO         GPIO_NUM_4

// R60ABD1 雷达模块 UART
#define RADAR_UART_NUM      UART_NUM_1
#define RADAR_TX_GPIO       GPIO_NUM_17
#define RADAR_RX_GPIO       GPIO_NUM_16
#define RADAR_BAUD          115200

// MLX90614 I2C
#define I2C_MASTER_NUM      0
#define I2C_MASTER_SDA_GPIO GPIO_NUM_21
#define I2C_MASTER_SCL_GPIO GPIO_NUM_22
#define I2C_MASTER_FREQ_HZ  100000
#define MLX90614_ADDR       0x5A

// LCD SPI (1.44寸 ST7735)
#define LCD_HOST            SPI2_HOST
#define LCD_MOSI_GPIO       GPIO_NUM_23
#define LCD_SCLK_GPIO       GPIO_NUM_18
#define LCD_CS_GPIO         GPIO_NUM_5
#define LCD_DC_GPIO         GPIO_NIO 19
#define LCD_RST_GPIO        GPIO_NUM_21   // 如果冲突可调整
#define LCD_BL_GPIO         GPIO_NUM_22

// WiFi 配置 (请修改为你的网络信息)
#define WIFI_SSID           "YOUR_WIFI_SSID"
#define WIFI_PASS           "YOUR_WIFI_PASSWORD"

// 华为云 IoT MQTT 配置 (请修改为你的设备信息)
#define MQTT_BROKER_URI     "mqtt://YOUR_BROKER_URL:1883"   // 或 ssl:// 端口8883
#define MQTT_CLIENT_ID      "YOUR_CLIENT_ID"
#define MQTT_USERNAME       "YOUR_USERNAME"
#define MQTT_PASSWORD       "YOUR_PASSWORD"
#define MQTT_TOPIC_PUB      "$oc/devices/YOUR_DEVICE_ID/sys/properties/report"  // 华为云属性上报topic

// ==================== 全局变量 ====================
static const char *TAG = "SLEEP_MONITOR";
static esp_mqtt_client_handle_t mqtt_client = NULL;

// 传感器数据结构体
typedef struct {
    bool     human_present;      // 是否有人
    float    heart_rate;         // 心率 (bpm)
    float    breath_rate;        // 呼吸频率 (次/分)
    char     body_state[10];     // 活跃/静止
    int      movement_amplitude; // 体动幅度 (0-100)
    int      sleep_duration;     // 睡眠时长 (分钟)
    char     sleep_quality[10];  // 清醒/深睡/浅睡
    float    temperature;        // 体温 (℃)
    bool     heart_alarm;        // 心率报警标志
    bool     temp_alarm;         // 体温报警标志
} sensor_data_t;

sensor_data_t sensor_data = {
    .human_present = false,
    .heart_rate = 0,
    .breath_rate = 0,
    .body_state = "静止",
    .movement_amplitude = 0,
    .sleep_duration = 0,
    .sleep_quality = "清醒",
    .temperature = 0,
    .heart_alarm = false,
    .temp_alarm = false
};

// ==================== 函数声明 ====================
static void wifi_init(void);
static void mqtt_init(void);
static void mqtt_publish_data(void);
static void radar_uart_init(void);
static void radar_parse_task(void *pvParameters);
static void i2c_master_init(void);
static float mlx90614_read_temperature(void);
static void lcd_spi_init(void);
static void lcd_display_data(sensor_data_t *data);
static void buzzer_control(bool on);
static void check_alarms(void);

// ==================== WiFi 初始化 ====================
static void wifi_init(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASS,
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    
    ESP_LOGI(TAG, "WiFi connecting to %s...", WIFI_SSID);
    esp_wifi_connect();
}

// ==================== MQTT 初始化 ====================
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_mqtt_event_handle_t event = event_data;
    switch (event->event_id) {
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI(TAG, "MQTT connected");
            break;
        case MQTT_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "MQTT disconnected");
            break;
        default:
            break;
    }
}

static void mqtt_init(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = MQTT_BROKER_URI,
        .credentials.client_id = MQTT_CLIENT_ID,
        .credentials.username = MQTT_USERNAME,
        .credentials.authentication.password = MQTT_PASSWORD,
    };
    mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    esp_mqtt_client_start(mqtt_client);
}

// ==================== MQTT 数据发布 ====================
static void mqtt_publish_data(void)
{
    if (!mqtt_client) return;
    
    cJSON *root = cJSON_CreateObject();
    cJSON *services = cJSON_CreateObject();
    cJSON *properties = cJSON_CreateObject();
    
    cJSON_AddNumberToObject(properties, "heart_rate", sensor_data.heart_rate);
    cJSON_AddNumberToObject(properties, "breath_rate", sensor_data.breath_rate);
    cJSON_AddNumberToObject(properties, "temperature", sensor_data.temperature);
    cJSON_AddBoolToObject(properties, "human_present", sensor_data.human_present);
    cJSON_AddStringToObject(properties, "body_state", sensor_data.body_state);
    cJSON_AddNumberToObject(properties, "movement_amplitude", sensor_data.movement_amplitude);
    cJSON_AddNumberToObject(properties, "sleep_duration", sensor_data.sleep_duration);
    cJSON_AddStringToObject(properties, "sleep_quality", sensor_data.sleep_quality);
    cJSON_AddBoolToObject(properties, "heart_alarm", sensor_data.heart_alarm);
    cJSON_AddBoolToObject(properties, "temp_alarm", sensor_data.temp_alarm);
    
    cJSON_AddItemToObject(services, "properties", properties);
    cJSON_AddItemToObject(root, "services", services);
    
    char *json_str = cJSON_PrintUnformatted(root);
    esp_mqtt_client_publish(mqtt_client, MQTT_TOPIC_PUB, json_str, 0, 1, 0);
    
    ESP_LOGI(TAG, "MQTT published: %s", json_str);
    
    cJSON_Delete(root);
    free(json_str);
}

// ==================== 雷达 UART 初始化 ====================
static void radar_uart_init(void)
{
    uart_config_t uart_config = {
        .baud_rate = RADAR_BAUD,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    };
    uart_param_config(RADAR_UART_NUM, &uart_config);
    uart_set_pin(RADAR_UART_NUM, RADAR_TX_GPIO, RADAR_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    uart_driver_install(RADAR_UART_NUM, 1024, 0, 0, NULL, 0);
}

// 雷达数据解析任务(假设雷达输出格式为: #HR:75,BR:18,AMP:30,STATE:active,SLEEP:240,QUAL:deep#)
static void radar_parse_task(void *pvParameters)
{
    uint8_t data[128];
    char buffer[256];
    int idx = 0;
    
    while (1) {
        int len = uart_read_bytes(RADAR_UART_NUM, data, sizeof(data) - 1, pdMS_TO_TICKS(100));
        if (len > 0) {
            for (int i = 0; i < len; i++) {
                if (data[i] == '#') {
                    buffer[idx] = '';
                    // 解析字符串
                    sscanf(buffer, "HR:%f,BR:%f,AMP:%d,STATE:%s,SLEEP:%d,QUAL:%s",
                           &sensor_data.heart_rate,
                           &sensor_data.breath_rate,
                           &sensor_data.movement_amplitude,
                           sensor_data.body_state,
                           &sensor_data.sleep_duration,
                           sensor_data.sleep_quality);
                    sensor_data.human_present = true;
                    // 限定字符串安全长度
                    sensor_data.body_state[9] = '';
                    sensor_data.sleep_quality[9] = '';
                    idx = 0;
                } else if (data[i] != 'r' && data[i] != 'n' && idx < 255) {
                    buffer[idx++] = data[i];
                }
            }
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// ==================== I2C 初始化 (MLX90614) ====================
static void i2c_master_init(void)
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_GPIO,
        .scl_io_num = I2C_MASTER_SCL_GPIO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };
    i2c_param_config(I2C_MASTER_NUM, &conf);
    i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}

// 读取 MLX90614 物体温度
static float mlx90614_read_temperature(void)
{
    uint8_t cmd = 0x07;  // 读取物体温度寄存器
    uint16_t data = 0;
    i2c_cmd_handle_t handle = i2c_cmd_link_create();
    i2c_master_start(handle);
    i2c_master_write_byte(handle, (MLX90614_ADDR << 1) | 0, true);
    i2c_master_write_byte(handle, cmd, true);
    i2c_master_stop(handle);
    i2c_master_cmd_begin(I2C_MASTER_NUM, handle, pdMS_TO_TICKS(100));
    i2c_cmd_link_delete(handle);
    
    vTaskDelay(pdMS_TO_TICKS(50));
    
    handle = i2c_cmd_link_create();
    i2c_master_start(handle);
    i2c_master_write_byte(handle, (MLX90614_ADDR << 1) | 1, true);
    i2c_master_read_byte(handle, (uint8_t*)&data, I2C_MASTER_LAST_NACK);
    i2c_master_stop(handle);
    i2c_master_cmd_begin(I2C_MASTER_NUM, handle, pdMS_TO_TICKS(100));
    i2c_cmd_link_delete(handle);
    
    float temp = (float)data * 0.02 - 273.15;  // 转换为摄氏度
    return temp;
}

// ==================== LCD 初始化与显示 (SPI) ====================
// 此处仅为接口示意,实际需要根据 LCD 驱动库实现
static void lcd_spi_init(void)
{
    spi_bus_config_t bus_cfg = {
        .mosi_io_num = LCD_MOSI_GPIO,
        .miso_io_num = -1,
        .sclk_io_num = LCD_SCLK_GPIO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096,
    };
    spi_bus_initialize(LCD_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
    
    // 初始化LCD控制器 (ST7735) 的GPIO
    gpio_set_direction(LCD_CS_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_direction(LCD_DC_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_direction(LCD_RST_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_direction(LCD_BL_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_level(LCD_BL_GPIO, 1);
    
    // 实际项目中需添加 LCD 初始化序列(设置窗口、颜色反转等)
    ESP_LOGI(TAG, "LCD SPI initialized");
}

static void lcd_display_data(sensor_data_t *data)
{
    // 此处实现 LCD 屏幕更新
    // 显示内容: 心率、呼吸、体温、存在状态、体动、睡眠时长、睡眠质量
    char buf[32];
    sprintf(buf, "HR:%.0f", data->heart_rate);
    // ... 实际调用 LCD 绘图函数
    // 因篇幅限制,省略具体绘图实现
}

// ==================== 蜂鸣器控制 ====================
static void buzzer_control(bool on)
{
    gpio_set_level(BUZZER_GPIO, on ? 1 : 0);
}

// ==================== 报警检测 ====================
static void check_alarms(void)
{
    // 心率安全范围 50-120 bpm
    if (sensor_data.heart_rate > 120 || (sensor_data.heart_rate < 50 && sensor_data.heart_rate > 0)) {
        sensor_data.heart_alarm = true;
        buzzer_control(true);
    } else {
        sensor_data.heart_alarm = false;
        // 如果体温没有报警则关闭蜂鸣器
        if (!sensor_data.temp_alarm) buzzer_control(false);
    }
    
    // 体温报警 > 38℃ 或 < 35℃
    if (sensor_data.temperature > 38.0 || (sensor_data.temperature < 35.0 && sensor_data.temperature > 0)) {
        sensor_data.temp_alarm = true;
        buzzer_control(true);
    } else {
        sensor_data.temp_alarm = false;
        if (!sensor_data.heart_alarm) buzzer_control(false);
    }
}

// ==================== 主任务 ====================
void sensor_task(void *pvParameters)
{
    i2c_master_init();
    radar_uart_init();
    lcd_spi_init();
    gpio_set_direction(BUZZER_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_level(BUZZER_GPIO, 0);
    
    // 创建雷达解析任务
    xTaskCreate(radar_parse_task, "radar_parse", 4096, NULL, 5, NULL);
    
    int publish_cnt = 0;
    
    while (1) {
        // 读取体温
        sensor_data.temperature = mlx90614_read_temperature();
        
        // 报警检测
        check_alarms();
        
        // 更新 LCD 显示
        lcd_display_data(&sensor_data);
        
        // 每 5 秒上传一次 MQTT
        if (publish_cnt >= 5) {
            mqtt_publish_data();
            publish_cnt = 0;
        } else {
            publish_cnt++;
        }
        
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1秒循环
    }
}

// ==================== 主函数 ====================
void app_main(void)
{
    ESP_LOGI(TAG, "Sleep Monitor System Starting...");
    
    wifi_init();
    mqtt_init();
    
    // 等待 WiFi 连接
    vTaskDelay(pdMS_TO_TICKS(3000));
    
    xTaskCreate(sensor_task, "sensor_task", 8192, NULL, 5, NULL);
    
    ESP_LOGI(TAG, "System Ready");
}

四、上位机开发

4.1 Qt开发环境安装

Qt的中文官网: https://www.qt.io/zh-cn/![image-20221207160550486](https://led-obs.obs.cn-north-1.myhuaweicloud.com/Blog/img/image-20221207160550486.png)

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

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

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

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

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

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

4.2 新建上位机工程

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

【1】新建工程

【2】设置项目的名称。

【3】选择编译系统

【4】选择默认继承的类

【5】选择编译器

【6】点击完成

【7】工程创建完成

4.3 切换编译器

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

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

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

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

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

4.4 编译测试功能

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

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

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

4.5 设计UI界面与工程配置

【1】打开UI文件

打开默认的界面如下:

【2】开始设计界面

根据自己需求设计界面。

4.6 代码设计

    • MQTT连接华为云IoT平台,订阅设备上报数据• 实时显示心率、呼吸频率、体温、人体存在状态、体动幅度、睡眠时长、睡眠质量• 心率异常报警提示(弹窗+状态栏)
// widget.cpp
// 基于Qt 5 + MQTT的睡眠监控系统上位机(支持Windows/Android)

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDateTime>
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    , m_mqttClient(new QMqttClient(this))
    , m_alarmActive(false)
{
    ui->setupUi(this);
    
    // 初始化UI界面
    initUI();
    
    // 初始化MQTT连接
    initMQTT();
    
    // 初始化图表
    initChart();
    
    // 设置定时器,用于超时重连和图表更新
    m_reconnectTimer = new QTimer(this);
    connect(m_reconnectTimer, &QTimer::timeout, this, &Widget::onReconnectTimeout);
    m_reconnectTimer->start(5000);  // 每5秒检查一次连接状态
    
    m_chartUpdateTimer = new QTimer(this);
    connect(m_chartUpdateTimer, &QTimer::timeout, this, &Widget::updateHeartRateChart);
    m_chartUpdateTimer->start(2000);  // 每2秒更新一次图表(实际使用新数据触发)
    
    // 状态栏初始化
    statusBar()->showMessage("等待连接...");
}

Widget::~Widget()
{
    if (m_mqttClient && m_mqttClient->state() == QMqttClient::Connected) {
        m_mqttClient->disconnect();
    }
    delete ui;
}

void Widget::initUI()
{
    // 设置窗口标题和大小
    setWindowTitle("睡眠监控系统 - 上位机");
    setMinimumSize(800, 600);
    
    // 连接状态指示灯(使用QLabel样式模拟)
    m_ledStatus = new QLabel(this);
    m_ledStatus->setFixedSize(16, 16);
    m_ledStatus->setStyleSheet("background-color: red; border-radius: 8px;");
    ui->statusBarLayout->addWidget(m_ledStatus);
    
    // 报警提示框初始隐藏
    ui->alarmFrame->setVisible(false);
    ui->alarmLabel->setStyleSheet("color: red; font-weight: bold;");
    
    // 设置数值显示字体
    QFont digitalFont("Arial", 12, QFont::Bold);
    ui->heartRateValue->setFont(digitalFont);
    ui->breathRateValue->setFont(digitalFont);
    ui->temperatureValue->setFont(digitalFont);
    ui->movementValue->setFont(digitalFont);
    ui->sleepDurationValue->setFont(digitalFont);
    
    // 设置按钮连接
    connect(ui->connectBtn, &QPushButton::clicked, this, &Widget::onConnectButtonClicked);
    connect(ui->clearAlarmBtn, &QPushButton::clicked, this, &Widget::onClearAlarmButtonClicked);
}

void Widget::initMQTT()
{
    // 华为云IoT MQTT配置(请修改为实际参数)
    m_mqttClient->setHostname("YOUR_BROKER_URL");      // 华为云MQTT接入地址
    m_mqttClient->setPort(1883);                       // 非加密端口,加密使用8883
    m_mqttClient->setClientId("YOUR_CLIENT_ID");       // 设备ID
    m_mqttClient->setUsername("YOUR_USERNAME");        // 用户名
    m_mqttClient->setPassword("YOUR_PASSWORD");        // 密码
    
    // 连接信号槽
    connect(m_mqttClient, &QMqttClient::stateChanged, this, &Widget::onMqttStateChanged);
    connect(m_mqttClient, &QMqttClient::messageReceived, this, &Widget::onMqttMessageReceived);
    connect(m_mqttClient, &QMqttClient::disconnected, this, &Widget::onMqttDisconnected);
}

void Widget::initChart()
{
    // 创建图表视图
    m_chart = new QChart();
    m_chart->setTitle("心率变化趋势");
    m_chart->setAnimationOptions(QChart::SeriesAnimations);
    
    // 创建曲线系列
    m_series = new QLineSeries();
    m_series->setName("心率 (bpm)");
    
    // 添加坐标轴
    QDateTimeAxis *axisX = new QDateTimeAxis();
    axisX->setFormat("hh:mm:ss");
    axisX->setTitleText("时间");
    axisX->setTickCount(6);
    
    QValueAxis *axisY = new QValueAxis();
    axisY->setTitleText("心率 (次/分)");
    axisY->setRange(40, 150);
    axisY->setTickCount(12);
    
    m_chart->addSeries(m_series);
    m_chart->addAxis(axisX, Qt::AlignBottom);
    m_chart->addAxis(axisY, Qt::AlignLeft);
    m_series->attachAxis(axisX);
    m_series->attachAxis(axisY);
    
    m_chartView = new QChartView(m_chart);
    m_chartView->setRenderHint(QPainter::Antialiasing);
    m_chartView->setMinimumHeight(250);
    
    // 将图表添加到布局
    ui->chartLayout->addWidget(m_chartView);
    
    // 初始化心率数据队列(保存最近60个点)
    m_heartRateHistory.clear();
    m_timeHistory.clear();
}

void Widget::onConnectButtonClicked()
{
    if (m_mqttClient->state() == QMqttClient::Connected) {
        m_mqttClient->disconnect();
    } else {
        m_mqttClient->connectToHost();
        statusBar()->showMessage("正在连接MQTT服务器...");
    }
}

void Widget::onMqttStateChanged(QMqttClient::ClientState state)
{
    switch (state) {
    case QMqttClient::Connected:
        m_ledStatus->setStyleSheet("background-color: green; border-radius: 8px;");
        statusBar()->showMessage("MQTT已连接,正在订阅主题...");
        
        // 订阅设备上报主题(华为云格式)
        QString subscribeTopic = "$oc/devices/YOUR_DEVICE_ID/sys/properties/report";
        auto subscription = m_mqttClient->subscribe(subscribeTopic, 1);
        if (subscription) {
            statusBar()->showMessage("已订阅主题:" + subscribeTopic);
        } else {
            statusBar()->showMessage("订阅失败");
        }
        break;
        
    case QMqttClient::Connecting:
        m_ledStatus->setStyleSheet("background-color: yellow; border-radius: 8px;");
        statusBar()->showMessage("正在连接...");
        break;
        
    case QMqttClient::Disconnected:
        m_ledStatus->setStyleSheet("background-color: red; border-radius: 8px;");
        statusBar()->showMessage("MQTT已断开");
        break;
        
    default:
        break;
    }
}

void Widget::onMqttMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
    Q_UNUSED(topic);
    
    // 解析JSON数据
    QJsonDocument doc = QJsonDocument::fromJson(message);
    if (doc.isNull()) {
        qDebug() << "Invalid JSON:" << message;
        return;
    }
    
    QJsonObject root = doc.object();
    QJsonObject services = root.value("services").toObject();
    QJsonObject properties = services.value("properties").toObject();
    
    // 提取各参数
    float heartRate = properties.value("heart_rate").toDouble();
    float breathRate = properties.value("breath_rate").toDouble();
    float temperature = properties.value("temperature").toDouble();
    bool humanPresent = properties.value("human_present").toBool();
    QString bodyState = properties.value("body_state").toString();
    int movementAmplitude = properties.value("movement_amplitude").toInt();
    int sleepDuration = properties.value("sleep_duration").toInt();
    QString sleepQuality = properties.value("sleep_quality").toString();
    bool heartAlarm = properties.value("heart_alarm").toBool();
    bool tempAlarm = properties.value("temp_alarm").toBool();
    
    // 更新UI显示
    updateDisplay(heartRate, breathRate, temperature, humanPresent, 
                  bodyState, movementAmplitude, sleepDuration, sleepQuality);
    
    // 处理报警
    if (heartAlarm || tempAlarm) {
        triggerAlarm(heartAlarm, tempAlarm);
    }
    
    // 保存心率数据用于图表
    m_heartRateHistory.append(heartRate);
    m_timeHistory.append(QDateTime::currentDateTime());
    
    // 限制历史数据长度(保留最近60个点)
    while (m_heartRateHistory.size() > 60) {
        m_heartRateHistory.removeFirst();
        m_timeHistory.removeFirst();
    }
    
    // 更新图表
    updateHeartRateChart();
}

void Widget::updateDisplay(float heartRate, float breathRate, float temperature, 
                           bool humanPresent, QString bodyState, int movementAmplitude, 
                           int sleepDuration, QString sleepQuality)
{
    // 更新数值显示
    ui->heartRateValue->setText(QString::number(heartRate, 'f', 0) + " bpm");
    ui->breathRateValue->setText(QString::number(breathRate, 'f', 0) + " 次/分");
    ui->temperatureValue->setText(QString::number(temperature, 'f', 1) + " ℃");
    ui->movementValue->setText(QString::number(movementAmplitude));
    ui->sleepDurationValue->setText(QString::number(sleepDuration) + " 分钟");
    
    // 人体存在状态
    ui->humanPresentValue->setText(humanPresent ? "有人" : "无人");
    if (humanPresent) {
        ui->humanPresentValue->setStyleSheet("color: green; font-weight: bold;");
    } else {
        ui->humanPresentValue->setStyleSheet("color: gray;");
    }
    
    // 人体状态(活跃/静止)
    ui->bodyStateValue->setText(bodyState);
    if (bodyState == "活跃") {
        ui->bodyStateValue->setStyleSheet("color: orange;");
    } else {
        ui->bodyStateValue->setStyleSheet("color: blue;");
    }
    
    // 睡眠质量
    ui->sleepQualityValue->setText(sleepQuality);
    if (sleepQuality == "深睡") {
        ui->sleepQualityValue->setStyleSheet("color: darkgreen; font-weight: bold;");
    } else if (sleepQuality == "浅睡") {
        ui->sleepQualityValue->setStyleSheet("color: lightgreen;");
    } else {
        ui->sleepQualityValue->setStyleSheet("color: gray;");
    }
    
    // 更新最后更新时间
    ui->lastUpdateTime->setText(QDateTime::currentDateTime().toString("hh:mm:ss"));
}

void Widget::triggerAlarm(bool heartAlarm, bool tempAlarm)
{
    if (m_alarmActive) return;  // 已经报警中
    
    m_alarmActive = true;
    
    // 显示报警框架
    ui->alarmFrame->setVisible(true);
    
    // 构建报警信息
    QString alarmMsg;
    if (heartAlarm && tempAlarm) {
        alarmMsg = "⚠️ 警告:心率和体温均超出安全范围!⚠️";
    } else if (heartAlarm) {
        alarmMsg = "⚠️ 警告:心率超出安全范围(正常范围:50-120 bpm)!⚠️";
    } else if (tempAlarm) {
        alarmMsg = "⚠️ 警告:体温超出安全范围(正常范围:35-38℃)!⚠️";
    }
    
    ui->alarmLabel->setText(alarmMsg);
    
    // 播放系统提示音(Android和Windows都支持)
    QApplication::beep();
    
    // 弹出消息框
    QMessageBox::warning(this, "睡眠监控报警", alarmMsg);
}

void Widget::onClearAlarmButtonClicked()
{
    ui->alarmFrame->setVisible(false);
    m_alarmActive = false;
    statusBar()->showMessage("报警已清除", 3000);
}

void Widget::updateHeartRateChart()
{
    // 清空系列重新添加
    m_series->clear();
    
    // 将历史数据添加到曲线
    for (int i = 0; i < m_heartRateHistory.size(); i++) {
        qint64 timestamp = m_timeHistory[i].toMSecsSinceEpoch();
        m_series->append(timestamp, m_heartRateHistory[i]);
    }
    
    // 自动调整坐标轴范围
    if (!m_heartRateHistory.isEmpty()) {
        double minY = *std::min_element(m_heartRateHistory.begin(), m_heartRateHistory.end());
        double maxY = *std::max_element(m_heartRateHistory.begin(), m_heartRateHistory.end());
        minY = qMax(40.0, minY - 10);
        maxY = qMin(150.0, maxY + 10);
        
        QValueAxis *axisY = qobject_cast<QValueAxis*>(m_chart->axes(Qt::Vertical).first());
        if (axisY) {
            axisY->setRange(minY, maxY);
        }
    }
    
    // 刷新图表
    m_chart->update();
}

void Widget::onMqttDisconnected()
{
    statusBar()->showMessage("MQTT断开,尝试重连...");
    // 可以在这里添加自动重连逻辑
}

void Widget::onReconnectTimeout()
{
    if (m_mqttClient->state() != QMqttClient::Connected) {
        statusBar()->showMessage("MQTT未连接,正在重连...");
        m_mqttClient->connectToHost();
    }
}

对应的 widget.h 头文件(供参考):

// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QtMqtt/QMqttClient>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QDateTimeAxis>
#include <QtCharts/QValueAxis>
#include <QLabel>
#include <QTimer>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void onConnectButtonClicked();
    void onMqttStateChanged(QMqttClient::ClientState state);
    void onMqttMessageReceived(const QByteArray &message, const QMqttTopicName &topic);
    void onClearAlarmButtonClicked();
    void onReconnectTimeout();
    void updateHeartRateChart();
    void onMqttDisconnected();

private:
    void initUI();
    void initMQTT();
    void initChart();
    void updateDisplay(float heartRate, float breathRate, float temperature, 
                       bool humanPresent, QString bodyState, int movementAmplitude, 
                       int sleepDuration, QString sleepQuality);
    void triggerAlarm(bool heartAlarm, bool tempAlarm);

private:
    Ui::Widget *ui;
    QMqttClient *m_mqttClient;
    QLabel *m_ledStatus;
    QChartView *m_chartView;
    QChart *m_chart;
    QLineSeries *m_series;
    QTimer *m_reconnectTimer;
    QTimer *m_chartUpdateTimer;
    QList<float> m_heartRateHistory;
    QList<QDateTime> m_timeHistory;
    bool m_alarmActive;
};

#endif // WIDGET_H

五、微信小程序开发

5.1 下载开发工具

链接地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/log.html#stable-2.01.2510290

5.2 安装开发工具

双击安装。

点击下一步。

安装完毕了找到开发工具的位置。

如果桌面没有自动创建快捷方式。可以自己创建一个。

5.3 打开使用开发工具

进去之后,可以微信扫码登录。

关闭默认打开的工程。就会看到下面的页面。

选择 +  号。

5.4 新建工程

APPID可以注册一个,也可先使用测试号。 然后,选择小程序,模版选择基础,选择-JS-基础模版。

选择创建。

工程打开之后就可以点击预览测试。

微信扫码就可以看默认的效果了。

5.5 代码设计

    • 通过HTTPS API调用华为云IoT平台获取设备数据• 实时显示心率、呼吸频率、体温、人体存在状态、体动幅度、睡眠时长、睡眠质量• 心率异常报警提示• 下拉刷新数据、历史数据查询
// pages/index/index.js
// 睡眠监控系统 - 微信小程序

const app = getApp();

Page({
  /**
   * 页面的初始数据
   */
  data: {
    // 实时数据
    heartRate: '--',
    breathRate: '--',
    temperature: '--',
    humanPresent: '--',
    bodyState: '--',
    movementAmplitude: '--',
    sleepDuration: '--',
    sleepQuality: '--',
    lastUpdateTime: '--',
    
    // 报警标志
    heartAlarm: false,
    tempAlarm: false,
    alarmMessage: '',
    showAlarmCard: false,
    
    // 设备信息
    deviceId: 'YOUR_DEVICE_ID',  // 替换为实际设备ID
    connected: false,
    
    // 历史数据
    historyData: [],
    
    // UI状态
    loading: false,
    refreshing: false
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    // 初始化
    this.initPage();
    
    // 启动定时刷新(每5秒获取一次数据)
    this.startAutoRefresh();
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {
    // 每次显示页面时刷新数据
    this.fetchDeviceData();
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {
    // 停止定时刷新
    this.stopAutoRefresh();
  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {
    this.stopAutoRefresh();
  },

  /**
   * 页面下拉刷新
   */
  onPullDownRefresh() {
    this.setData({ refreshing: true });
    this.fetchDeviceData(true);
  },

  /**
   * 初始化页面
   */
  initPage() {
    // 检查登录状态,获取设备信息
    this.checkAuth();
    
    // 设置导航栏标题
    wx.setNavigationBarTitle({
      title: '睡眠监控系统'
    });
  },

  /**
   * 检查授权(示例:可扩展用户登录)
   */
  checkAuth() {
    // 实际项目中可调用wx.login获取code,后端换取openid等
    console.log('检查授权状态');
  },

  /**
   * 启动自动刷新
   */
  startAutoRefresh() {
    if (this.refreshTimer) {
      clearInterval(this.refreshTimer);
    }
    this.refreshTimer = setInterval(() => {
      this.fetchDeviceData();
    }, 5000); // 每5秒刷新一次
  },

  /**
   * 停止自动刷新
   */
  stopAutoRefresh() {
    if (this.refreshTimer) {
      clearInterval(this.refreshTimer);
      this.refreshTimer = null;
    }
  },

  /**
   * 获取设备实时数据(调用华为云IoT API)
   */
  fetchDeviceData(fromPullDown = false) {
    const that = this;
    
    if (!fromPullDown) {
      this.setData({ loading: true });
    }
    
    // 华为云IoT平台API: 查询设备属性
    // 实际使用时需要替换为你的华为云IAM Token或使用签名鉴权
    // 这里以Bearer Token方式示例(实际需从后端获取)
    const token = wx.getStorageSync('huawei_token') || 'YOUR_ACCESS_TOKEN';
    
    wx.request({
      url: `https://YOUR_HUAWEI_IOT_ENDPOINT/v5/iot/${app.globalData.projectId}/devices/${this.data.deviceId}/properties`,
      method: 'GET',
      header: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`
      },
      success(res) {
        if (res.statusCode === 200 && res.data) {
          const properties = res.data.properties || res.data.services || {};
          that.parseAndUpdateData(properties);
          that.setData({ connected: true });
        } else if (res.statusCode === 401) {
          // Token过期,重新获取
          that.refreshTokenAndRetry();
        } else {
          console.error('获取数据失败', res);
          that.setData({ connected: false });
          that.showToast('获取数据失败', 'none');
        }
      },
      fail(err) {
        console.error('请求失败', err);
        that.setData({ connected: false });
        that.showToast('网络错误', 'none');
      },
      complete() {
        that.setData({ loading: false });
        if (fromPullDown) {
          wx.stopPullDownRefresh();
          that.setData({ refreshing: false });
        }
      }
    });
  },

  /**
   * 解析并更新界面数据
   */
  parseAndUpdateData(data) {
    // 华为云返回的数据格式可能为嵌套对象,根据实际调整
    const properties = data.services?.properties || data;
    
    // 提取各字段
    let heartRate = properties.heart_rate;
    let breathRate = properties.breath_rate;
    let temperature = properties.temperature;
    let humanPresent = properties.human_present;
    let bodyState = properties.body_state;
    let movementAmplitude = properties.movement_amplitude;
    let sleepDuration = properties.sleep_duration;
    let sleepQuality = properties.sleep_quality;
    let heartAlarm = properties.heart_alarm;
    let tempAlarm = properties.temp_alarm;
    
    // 类型转换和默认值
    heartRate = heartRate !== undefined ? heartRate : '--';
    breathRate = breathRate !== undefined ? breathRate : '--';
    temperature = temperature !== undefined ? temperature : '--';
    humanPresent = humanPresent !== undefined ? (humanPresent ? '有人' : '无人') : '--';
    bodyState = bodyState || '--';
    movementAmplitude = movementAmplitude !== undefined ? movementAmplitude : '--';
    sleepDuration = sleepDuration !== undefined ? sleepDuration : '--';
    sleepQuality = sleepQuality || '--';
    
    // 报警标志
    heartAlarm = heartAlarm === true;
    tempAlarm = tempAlarm === true;
    
    // 构建报警信息
    let alarmMessage = '';
    let showAlarmCard = false;
    if (heartAlarm && tempAlarm) {
      alarmMessage = '⚠️ 心率和体温均超出安全范围!';
      showAlarmCard = true;
    } else if (heartAlarm) {
      alarmMessage = '⚠️ 心率超出安全范围(正常:50-120 bpm)!';
      showAlarmCard = true;
    } else if (tempAlarm) {
      alarmMessage = '⚠️ 体温超出安全范围(正常:35-38℃)!';
      showAlarmCard = true;
    }
    
    // 更新页面数据
    this.setData({
      heartRate: heartRate.toString(),
      breathRate: breathRate.toString(),
      temperature: typeof temperature === 'number' ? temperature.toFixed(1) : temperature,
      humanPresent: humanPresent,
      bodyState: bodyState,
      movementAmplitude: movementAmplitude.toString(),
      sleepDuration: sleepDuration.toString(),
      sleepQuality: sleepQuality,
      heartAlarm: heartAlarm,
      tempAlarm: tempAlarm,
      alarmMessage: alarmMessage,
      showAlarmCard: showAlarmCard,
      lastUpdateTime: this.formatTime(new Date())
    });
    
    // 如果有报警,震动提示(仅首次报警时震动)
    if (showAlarmCard && !this.data.showAlarmCard) {
      wx.vibrateShort({
        type: 'medium'
      });
    }
  },

  /**
   * 刷新Token(示例:调用自己的后端接口)
   */
  refreshTokenAndRetry() {
    const that = this;
    wx.request({
      url: 'https://YOUR_BACKEND_API/auth/huawei-token',
      method: 'POST',
      success(res) {
        if (res.statusCode === 200 && res.data.token) {
          wx.setStorageSync('huawei_token', res.data.token);
          that.fetchDeviceData();
        } else {
          that.showToast('认证失败,请重新登录', 'none');
        }
      },
      fail() {
        that.showToast('网络错误', 'none');
      }
    });
  },

  /**
   * 查询历史数据
   */
  onQueryHistory() {
    wx.showActionSheet({
      itemList: ['最近1小时', '最近6小时', '最近24小时', '最近7天'],
      success: (res) => {
        const ranges = [1, 6, 24, 168]; // 小时单位
        const hours = ranges[res.tapIndex];
        this.fetchHistoryData(hours);
      }
    });
  },

  /**
   * 获取历史数据
   */
  fetchHistoryData(hours) {
    wx.showLoading({ title: '加载中...' });
    
    const endTime = Date.now();
    const startTime = endTime - hours * 3600 * 1000;
    
    // 调用华为云历史数据接口(设备属性历史)
    const token = wx.getStorageSync('huawei_token') || 'YOUR_ACCESS_TOKEN';
    
    wx.request({
      url: `https://YOUR_HUAWEI_IOT_ENDPOINT/v5/iot/${app.globalData.projectId}/devices/${this.data.deviceId}/properties-history`,
      method: 'GET',
      data: {
        start_time: startTime,
        end_time: endTime,
        limit: 100
      },
      header: {
        'Authorization': `Bearer ${token}`
      },
      success: (res) => {
        wx.hideLoading();
        if (res.statusCode === 200 && res.data.data) {
          this.processHistoryData(res.data.data);
        } else {
          this.showToast('获取历史数据失败', 'none');
        }
      },
      fail: () => {
        wx.hideLoading();
        this.showToast('网络错误', 'none');
      }
    });
  },

  /**
   * 处理历史数据并跳转详情页
   */
  processHistoryData(dataList) {
    // 处理数据格式
    const processed = dataList.map(item => {
      const props = item.services?.properties || item;
      return {
        time: this.formatTime(new Date(item.timestamp || item.time)),
        heartRate: props.heart_rate || '--',
        breathRate: props.breath_rate || '--',
        temperature: props.temperature ? props.temperature.toFixed(1) : '--',
        sleepQuality: props.sleep_quality || '--'
      };
    });
    
    // 跳转到历史数据页面(需要创建对应page)
    wx.navigateTo({
      url: `/pages/history/history?data=${encodeURIComponent(JSON.stringify(processed))}`
    });
  },

  /**
   * 手动刷新数据
   */
  onManualRefresh() {
    this.fetchDeviceData();
    this.showToast('刷新中', 'loading');
    setTimeout(() => {
      this.showToast('刷新完成', 'success');
    }, 1000);
  },

  /**
   * 清除报警提示
   */
  onClearAlarm() {
    this.setData({
      showAlarmCard: false,
      heartAlarm: false,
      tempAlarm: false
    });
  },

  /**
   * 显示健康建议(基于当前数据)
   */
  onShowAdvice() {
    let advice = '';
    const hr = parseFloat(this.data.heartRate);
    const temp = parseFloat(this.data.temperature);
    
    if (!isNaN(hr)) {
      if (hr > 100) {
        advice += '心率偏高,建议放松休息、避免剧烈运动。n';
      } else if (hr < 60 && hr > 0) {
        advice += '心率偏低,如无不适可继续观察。n';
      } else {
        advice += '心率正常,继续保持良好作息。n';
      }
    }
    
    if (!isNaN(temp)) {
      if (temp > 37.3) {
        advice += '体温偏高,请注意监测,多喝水。n';
      } else if (temp < 36.0 && temp > 0) {
        advice += '体温偏低,注意保暖。n';
      }
    }
    
    if (advice === '') {
      advice = '所有指标正常,祝您睡眠健康!';
    }
    
    wx.showModal({
      title: '健康建议',
      content: advice,
      showCancel: false
    });
  },

  /**
   * 格式化时间
   */
  formatTime(date) {
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    const hour = date.getHours().toString().padStart(2, '0');
    const minute = date.getMinutes().toString().padStart(2, '0');
    const second = date.getSeconds().toString().padStart(2, '0');
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
  },

  /**
   * 显示提示
   */
  showToast(title, icon = 'none') {
    wx.showToast({
      title: title,
      icon: icon,
      duration: 1500
    });
  }
});

六、总结

本项目围绕基于ESP32与60GHz毫米波生物感知雷达的睡眠监控系统,完成了从硬件选型、功能设计到软件开发与多端部署的完整方案。通过系统的设计与实现,成功构建了一套非接触式、全天候、多端联动的智能睡眠健康监测平台。

在硬件层面,系统以ESP32为主控核心,集成了R60ABD1 60GHz毫米波雷达模块、MLX90614红外体温传感器、SPI接口LCD显示屏及蜂鸣器报警模块。60GHz毫米波雷达凭借其波长更短、对微动信号高度敏感的特点,实现了对人体呼吸频率、心率、体动幅度、睡眠分期等生理参数的非接触式精准测量,解决了传统接触式传感器易脱落、用户依从性差的问题。MLX90614红外传感器进一步补充了体温这一关键健康指标,形成了多维度的生理数据采集体系。USB供电方式保证了设备的易用性和部署灵活性。

在软件开发方面,系统实现了设备端、云端、移动端与桌面端的全链路覆盖。设备端基于ESP-IDF框架采用C语言开发,通过UART、I2C、SPI等接口协调各外设工作,利用WiFi网络与MQTT协议将采集的数据实时上报至华为云IoT物联网平台。针对心率异常情况,系统设计了本地蜂鸣器报警与云端联动报警机制,确保异常事件能够被及时发现与响应。本地LCD屏幕则为用户提供了无需打开手机或电脑即可查看睡眠数据的便捷途径。

本项目成功融合了毫米波雷达感知技术、嵌入式系统开发、物联网通信与跨平台应用软件设计,形成了一套完整的睡眠监控解决方案。系统具有非接触、多参数、实时性、可扩展等特点,技术路线清晰,实现路径可行。未来的工作可以在现有基础上进一步优化雷达信号处理算法以提高心率提取精度,引入睡眠呼吸暂停综合征的自动识别功能,同时探索利用云端大数据进行睡眠质量趋势分析与个性化健康建议推送,使系统从“监测”向“智能健康管理”方向持续演进。

相关推荐