☰
温湿度监控APP开发:从传感器到手机的全链路500行代码
2026/10/20 17:30:09 网站建设 项目流程

简介:温湿度监控手机APP开发代码,面向Android与物联网开发者,围绕DHT11/DHT22传感器数据采集、服务端交互与远程控制等环节,提供一套可直接分析的工程源码。资源共143个文件,压缩后约3.98MB,主要文件类型包括Java源码与class编译文件、xml界面布局与工程配置、jar依赖库、png图片资源以及apk安装包,能同时看到前端页面、业务逻辑、资源文件和可安装App,便于对照学习。已有1617人学习下载。代码包涵盖传感器读取、网络通信、数据展示、LED开关控制等典型模块,可帮助开发者梳理温湿度监控App从数据采集到指令下发的完整链路,并理解Android应用与云端设备协同开发的常见模式,适合需要动手实践、参考工程结构并深入理解物联网终端应用的初中级开发者。

1. 温湿度监控的手机 APP 开发代码:为什么这 500 行代码值得你动手

如果你在育苗棚、机房或者酒窖里待过一晚,就会明白一个朴素的需求:不能时刻守在温湿度计面前,但又怕环境悄悄越界。温湿度监控的手机 APP 开发代码,解决的就是这件事——让传感器采集到的数据穿过网络,在手机屏幕上变成一眼能看懂的数值、曲线和告警。这套方案的价值在于链路完整:从传感器、MCU、消息协议到手机端解析、存储和推送,全部可以自己写代码跑通。我接过的模拟项目X,就靠着这套思路在两天内做出了第一个可演示的版本。

适合谁动手做这件事?一类是物联网方向的学生,想把嵌入式、网络协议和移动端串成一个完整项目;另一类是小团队或个人开发者,做环境监测原型时不愿被现成云平台绑定,想自己掌握数据链路。这篇文章把链路里最容易踩坑的几个环节单独拿出来讲清楚,照着做,你也能从零搭出一个能用的温湿度监控 APP。

2. 温湿度监控链路:传感器到手机的四个环节

2.1 选型:DHT22 还是 SHT30?手机端真正在意的是稳定读数

写 APP 之前,得先决定传感器的牌子和接法。常见做法是 DHT11、DHT22、SHT30 三选一。很多人觉得 DHT11 便宜够用,实际上它的精度标称只有 ±2℃,联网之后数据会整体偏移,手机上看到的数值「稳定但不对」。DHT22 精度到 ±0.5℃,价格适中,是大多数入门项目的平衡点。SHT30 则是 I2C 接口,自带 CRC 校验,精度标称 ±0.3℃,适合对读数有进一步要求、或者一条 I2C 总线上要挂多个传感器的场景。

传感器接口常见精度采样特点手机端体验
DHT11单总线±2℃ / ±5%RH数据抖动明显曲线波动大,需要滤波
DHT22单总线±0.5℃ / ±2%RH相对稳定基础监控够用
SHT30I2C±0.3℃(典型)带 CRC,抗干扰强曲线平滑,少出现跳变

选型时不要只看传感器本身,还要看接线距离。某开发者第一次布线时把 DHT22 用了一米多的杜邦线,读数在 26.2℃ 和 27.1℃ 之间来回跳,排查了很久才是线材压降和干扰问题。后来换成 SHT30,供电从 3.3V 单独稳压,I2C 线控制在 20 厘米以内,手机端曲线立刻安静下来。所以我的建议是:如果做原型、验证链路,DHT22 完全够用;如果做长期部署,SHT30 省掉后半夜的玄学跳数。

2.2 数据上报:MQTT 还是 HTTP?手机 APP 的实时性由这里决定

传感器数据上报,常见选择是 MQTT 或 HTTP。HTTP 简单直接,传感器定时 POST 一段 JSON 到后端,APP 再定时 GET 拉取。缺点是"拉"这个动作天然有延迟,而且手机端为了省电不能频繁轮询,实时性做不到秒级。MQTT 是长连接发布-订阅模型,传感器端发布温湿度,APP 订阅同一个主题,数据一到就推送过来,延迟通常在一秒以内。监控场景最怕的就是「温度已经超了,APP 还显示半分钟前的值」,所以我一般推荐 MQTT。

对比项MQTTHTTP
连接方式TCP 长连接短连接
实时性秒级推送取决于轮询频率
断线恢复协议自带重连需要自己设计补偿
APP 端耗电低,空闲时无请求高,轮询越多越耗电
服务器要求需要 Broker 常驻普通后端即可

MQTT 的关键参数有三个:QoS、keepAlive、Retain。QoS 0 用在温湿度这种允许偶发丢失的监控上完全够用,没必要上 QoS 1 或 2,因为传感器后半夜数据连续上报,丢一条不会影响整体曲线。keepAlive 常见配置是 60 秒,Broker 超过这个时间没收到请求就判定掉线。Retain 标志是给 APP 体验用的——设备端发一条 Retain 消息,新上线的 APP 订阅主题时会立刻收到最后一条数据,不需要干等下一次上报。

ESP8266 端上报这段代码,我一般这样写,用的是常见的 PubSubClient 库:

#include <ESP8266WiFi.h> #include <PubSubClient.h> WiFiClient espClient; PubSubClient mqttClient(espClient); const char* mqttServer = "你的MQTT服务器地址"; const int mqttPort = 1883; const char* clientId = "temp_humidity_node_01"; // 必须全局唯一 void sendSensorData() { float temp = readTemp(); // 读取传感器温度 float hum = readHumidity(); // 读取传感器湿度 char payload[128]; snprintf(payload, sizeof(payload), "{\"temp\":%.2f,\"hum\":%.2f}", temp, hum); // 发布到设备专属主题,开启 Retain 以便 APP 上线立刻拿到最新值 mqttClient.publish("v1/devices/temp_humidity_node_01/data", payload, true); Serial.println(payload); }

这段代码的逻辑是每次采集完成,就把带两位小数的 JSON 发布到该设备专属主题。clientId 不要写死成通用值,否则多个设备共用同一个 clientId 会互相踢掉线,这是 MQTT 里最容易翻车的位置。Retain 参数我特意传了 true,好处是 APP 端冷启动后订阅主题,立刻就能收到设备最近一次上报的数据,不用等一个上报周期。

3. 用 Flutter 写手机 APP:从订阅到渲染的最小工程

3.1 工程怎么建:mqtt_client 包的连接参数与回调

APP 端我常用 Flutter 写,因为一套代码能同时出 Android 和 iOS,省去双端维护。网络层用 mqtt_client 这个开源包,它内部封装了连接、订阅、重连的流程,但你要知道这些回调不是自动的——很多人连上就以为完事了,设备端一重启,APP 就再也收不到数据,根源是忘了处理断开后的重建。

看一下最小可跑的 MQTT 连接代码:

import 'package:mqtt_client/mqtt_client.dart'; class MqttService { late MqttClient client; Future<bool> connect() async { client = MqttClient('你的MQTT服务器地址', 'app_monitor_001'); client.port = 1883; client.keepAlivePeriod = 60; client.connectTimeoutPeriod = 10; // 连接成功回调,此时再订阅主题,避免订阅动作过早执行失败 client.onConnected = () { client.subscribe('v1/devices/temp_humidity_node_01/data', MqttQos.atMostOnce); }; // 断开回调,先打印原因,再决定是否重连 client.onDisconnected = () { print('MQTT断开,尝试重连'); reconnect(); }; client.onAutoReconnect = (status) { print('自动重连状态: $status'); }; try { await client.connect(); return true; } catch (e) { print('连接失败: $e'); return false; } } }

这段代码里三个参数值得注意。keepAlivePeriod 设为 60 秒,含义是客户端每 60 秒向 Broker 发送一次心跳,维持连接有效;丢线检测不会立即触发,所以 fallback 到 60 秒感觉是灵敏和资源消耗的折中。connectTimeoutPeriod 设为 10 秒,超过则判定为失败。onConnected 回调里做订阅是最稳妥的——如果连接还没建立就提前订阅,会被 Broker 直接拒绝。mqtt_client 的自动重连并不是绝对可靠,断开回调里自己再加一层重连逻辑,是实战中避免「连上后断一次就永久离线」的关键。

3.2 把字符串解析成温湿度:报文协议与异常兜底

订阅拿到的是 MQTT 的字节流,通常需要转成字符串再解析成 JSON。这块看似简单,但有几个边界动作必须做。传感器偶发会上报无效数据,比如 DHT 在电源不稳时返回数值 255 或者负值,如果直接把 255 画到曲线上,整条曲线就会被拉出一个吓人的尖峰。

我习惯在解析层做一次清洗,把非法数据挡在 UI 外侧:

import 'dart:convert'; class SensorPayload { final double? temperature; final double? humidity; SensorPayload({this.temperature, this.humidity}); static SensorPayload? parse(String message) { try { final map = jsonDecode(message) as Map<String, dynamic>; final temp = (map['temp'] as num?)?.toDouble(); final hum = (map['hum'] as num?)?.toDouble(); // 温湿度物理范围检查 if (temp == null || hum == null) return null; if (temp < -40 || temp > 80) return null; if (hum < 0 || hum > 100) return null; return SensorPayload(temperature: temp, humidity: hum); } catch (e) { // 解析失败返回 null,调用方跳过这次数据,不直接崩溃 return null; } } }

读出有效值后做什么?两层判断:第一层是物理范围,温度超出 -40℃ 到 80℃、湿度超出 0% 到 100% 的报文直接丢弃;第二层是单位一致性,APP 端统一约定用摄氏度和百分比,传感器端上报时就用这两个单位。某开发者在项目里常见的问题是传感器一键刷新后报华氏度,APP 端没做转换,所有数值看起来都偏高三十多度。这种低级问题,最好的解法就是像上面代码一样做范围校验,异常值能直接筛掉一大部分。

3.3 刷新频率不是拍脑袋定的:三秒一次还是推送驱动

APP 展示数据有两种做法:定时轮询和 MQTT 推送驱动。用 Timer 每三秒去数据库查一次、每五秒去远程拉一次,是新手的常见姿势,结果就是手机电量掉得飞快,界面还时常卡顿。MQTT 本身就是推送模型,数据到了就会触发 onMessage 回调,完全不需要定时器反复查询。

正确的姿势是把「数据到达」和「UI 刷新」绑定。onMessage 回调里解析完数据,直接丢给状态管理对象,UI 层监听变化自动重建。传感器一般设置 5 到 30 秒上报一次,手机端收到的频率恰好匹配人的阅读节奏。如果你想手动刷新,只做一个下拉刷新的手势,让 APP 发一条 MQTT 请求让设备即刻上报,而不是开启高频轮询。刷新频率设计的本质是:能推就不要拉,能事件触发就不要定时跑。

4. 历史曲线与断网回补:APP 端存储的两种可靠做法

4.1 本地 SQLite 落库:采样与入库分离

APP 端除了实时显示,还要能看历史曲线。数据量不大,但你需要稳定的查询接口,SQLite 是最轻的选择。用 sqflite 包建一个简单的表,关键字段是温度、湿度和时间戳,再用时间索引保证范围查询不慢。

CREATE TABLE sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, temperature REAL NOT NULL, humidity REAL NOT NULL, ts INTEGER NOT NULL ); CREATE INDEX idx_ts ON sensor_data(ts);

这个表的作用有两个。一是 APP 离线时数据不丢,随时可以后续补绘曲线;二是做「采样与入库分离」,onMessage 收到数据先放进内存队列,由单独的写入任务批量落库,避免每条 MQTT 消息都去打开一次数据库,造成频繁 I/O。批量写入用事务包住,能明显减少卡顿。保留策略上我一般只留最近 30 天数据,超过的定期 DELETE,防止 APP 存储空间膨胀到让人肉疼。

4.2 远程历史:自建后端与云托管的选择

本地库只能服务这一台手机,真要跨设备同步或者想在电脑上查看历史曲线,就要把数据上报到远程。这一环节有两条路线,自建后端适合数据敏感、要自己掌控的场景;云托管数据库适合快速建原型、不想运维服务器的情况。我自己的习惯是先自建一个极简 HTTP API,收到传感器数据后写入数据库,APP 请求曲线时按时间段查询返回 JSON。

方案适用场景工作量成本
自建后端数据要自持、有基础服务器高,需要写接口和存储服务器费用
云托管数据库快速验证、个人项目低,免运维按量计费
混合方案数据双写、兼容离线中,两套逻辑中等

自建后端的最小接口逻辑如下,语言我习惯用 Python 的 FastAPI,因为它能十分钟搭出带文档的 REST 接口:

from fastapi import FastAPI from pydantic import BaseModel import sqlite3 app = FastAPI() class SensorPayload(BaseModel): device_id: str temp: float hum: float ts: int @app.post("/v1/data/report") def report_data(payload: SensorPayload): conn = sqlite3.connect("sensor.db") cur = conn.cursor() cur.execute( "INSERT INTO sensor_data(device_id, temperature, humidity, ts) VALUES (?,?,?,?)", (payload.device_id, payload.temp, payload.hum, payload.ts) ) conn.commit() conn.close() return {"status": "ok"}

POST 接口只做一件事:把 JSON 报文插进数据库。用 pydantic 做字段类型约束,如果设备上传来一个缺失 temp 的报文,接口会直接返回 422,而不是把脏数据写进库。注意这个接口没有做鉴权,真实部署时至少要在 Header 里带一个 token,否则任何知道地址的人都能往你的数据库灌数据。

4.3 断网后补数:时间戳要由设备定,不是 APP 本地时间

断网回补是温湿度监控里最隐蔽的坑。设备断网十分钟,这期间的数据没上报,恢复后传感器把缓存数据补发过来,APP 端如果按「收到数据的当前时间」打时间戳,曲线就会多出一段发生在未来的虚影。正确做法是设备端上报时携带采集时间戳,APP 和后端都无条件信任这个时间戳,只把它当作排序列。这样补数据时曲线依然按真实发生时间排列,不会因为网络延迟而错位。

{"device_id": "temp_humidity_node_01", "temp": 26.31, "hum": 58.2, "ts": 1712345678}

ts 字段用 Unix 秒级时间戳,跨设备、跨语言都不会有歧义。要注意设备端的时间基准,如果设备没有 RTC 模块,重启后时间会回到固件编译时间,补发数据会把整条曲线打乱。最省心的做法是让设备每次连接 Wi-Fi 后用 NTP 校时一次,再往后采集时间戳就可靠了。

5. 温湿度监控 APP 的四个真实踩坑点:现象、原因、解决

5.1 Wi-Fi 偶发断开导致曲线出现大缺口

现象:温度曲线每隔几个小时出现一段几小时的平线,平线期间完全无数据。原因不是传感器坏了,而是 MCU 的 Wi-Fi 在运行中断开,TCP 连接没有自动重建,上层的 MQTT 发布全部失败。很多示例代码只写了上电自动连接,从没处理运行中掉线的情况,所以这种问题被戏称为物理层玄学——明明信号满格,数据就是发不出来。

解决:在设备端加一个看门狗式的循环,每 30 秒检查一次连接状态,发现未连接就主动重连并重新订阅主题。代码片段我通常会写成这样:

void loop() { if (!mqttClient.connected()) { reconnect(); // 内部执行 WiFi 重连和 clientId 重新注册 } mqttClient.loop(); delay(30000); }

注意重新连接后必须检查订阅是否也恢复了。MQTT 的长连接一旦断裂,之前的订阅关系会全部消失,只重连不订阅,设备还是处于“能连不能发”的哑巴状态。调试这个方法时,手头最好有一个 MQTT 调试工具,监听主题看断线后能否收到恢复消息,比盯着 APP 等半天强得多。

5.2 APP 显示温度像心电图一样跳动

现象:传感器旁边地面走过一个人,温度值瞬间从 26℃ 跳到 27.5℃ 再跳回来,曲线完全没法看。原因有两层,传感器采样本身存在物理噪声,加上采集频率过快、没有滤波直接上报。每秒读一次 DHT22,读到的数值天然会有 ±0.3℃ 的随机抖动,在手机端放大后就是锯齿状曲线。

解决:在 APP 端做指数移动平均(EMA),把抖动消化在渲染之前。核心实现如下:

class EmaFilter { double? _last; final double alpha; EmaFilter({this.alpha = 0.4}); double add(double value) { if (_last == null) { _last = value; } else { _last = alpha * value + (1 - alpha) * _last!; } return _last!; } }

alpha 值决定平滑力度,alpha 越小曲线越平滑但越迟钝。监控场景我习惯用 0.3 到 0.4,能滤掉随机抖动又不至于让真实升温看起来慢了半拍。比较敏感的场景可以先把 alpha 降到 0.2 试效果。EMA 只适合做展示层平滑,不能替代传感器端的采样均值,两个环节各做各的,数据才是双保险。229

5.3 设备凌晨上报的数据变成 NaN

现象:凌晨两点开始,设备端上报的 JSON 里出现 null 或 NaN,APP 曲线直接断成一段空白。值班的人早上看到以为传感器坏了,重启后一切正常。原因大概率是传感器供电在夜间出现电压跌落,I2C 读取失败,固件代码把读取错误的值直接发了出来。很多示例代码只处理了正常读取分支,没处理读取失败分支,失败时变量仍然保留着上次的残留值。

解决:代码层面加一个明确的读取失败判定,失败时跳过这次上报而不是发垃圾值。读取函数里遇到错误标记位要区分处理,这是最容易被忽略的边界逻辑。还有一种边缘情况是传感器老化,存放 ADC 读数的寄存器返回全 1,温度读出来是 85℃。物理范围校验在这种场景反而能救命——超过 80℃ 直接丢弃,设备端和 APP 端都做这个校验,双保险。

5.4 设备端正常但手机收不到告警推送

现象:温度超过阈值,设备端在 MQTT 主题上正常发布了消息,服务器也能看到,但 Android 手机息屏一段时间后就收不到推送提醒。原因不是 MQTT 断了,而是手机厂商的省电策略把后台长连接进程挂起了。这个问题在 Android 上尤其突出,各大厂商的省电白名单管理五花八门,进程休眠后 MQTT 连接不发送心跳,Broker 判定掉线。

解决:不要在 APP 里依赖 MQTT 长连接来推送告警。折中方案是告警判断逻辑放云端,由后端服务判断数据超过阈值后,通过系统推送通道发通知到手机。这里的要点是「判断和触达分离」,MQTT 管实时曲线,系统推送管告警触达。如果只想做本地演示,就用 APP 内的本地通知方案,阈值判断在解析数据时同步执行,手机亮屏时也能有提醒效果。真正要全场景可靠推送,还是走服务端推送这条路,别跟厂商的省电策略较劲。

6. 从看到到可用:本地告警阈值与趋势判断的进阶技巧

把实时数值和曲线跑通,只是完成了「看到」这一步。一套真正可用的温湿度监控 APP,必须能在环境异常时做出反应。我后来在模拟项目X里加了两层逻辑:第一层是阈值告警,用户可以在 APP 里设置最高温度和最低温度;第二层是趋势预警,在温度连续十到十五分钟内持续上升超过阈值时,提前提示用户处理,而不是等到温度已经越界才报警。趋势判断的代码不复杂,用滑动窗口算线性回归的斜率就能做到:

double? calcTrend(List<double> values) { if (values.length < 5) return null; final n = values.length; double sx = 0, sy = 0, sxy = 0, sxx = 0; for (int i = 0; i < n; i++) { sx += i; sy += values[i]; sxy += i * values[i]; sxx += i * i; } final denominator = n * sxx - sx * sx; if (denominator == 0) return null; return (n * sxy - sx * sy) / denominator; }

这段代码计算的是一个窗口内数值的线性斜率,返回的是每个时间点温度变化的速率。如果最近十个数据点的斜率连续为正且超过 0.05,就说明温度正在持续爬升,这时候推一条「温度持续上升,注意通风」的提醒,比等到报警阈值触发才通知更有价值。验证这套逻辑有一个笨但有效的办法:把传感器放进冰箱几分钟,再拿出来放到室温环境,曲线会先下降再缓慢回升,趋势判断应该在这两个阶段产生正确的预警信号。手机开飞行模式再关掉,看 MQTT 连接能否自动恢复,这是练重连逻辑最好的压力测试。

某开发者做这个项目时只做了刷新和显示,没有做阈值逻辑,结果夏天机房温度升到 34℃ 是同事先发现的,那个体会我到现在都记得。从那以后我接手的监控项目,画完曲线第一件事就是写告警判断。环境监控的价值不在曲线多漂亮,而在异常发生时能不能及时被看见。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询