1. 项目概述从“洗衣焦虑”到数据驱动的智能洞察你有没有过这样的经历把衣服扔进洗衣机转头就去忙别的事等想起来的时候已经过去了好几个小时湿漉漉的衣服在滚筒里闷着不得不重新再洗一遍。或者你根本不确定这缸衣服到底洗了多久还剩多少时间只能时不时地去听一听、看一看。这种“洗衣焦虑”和低效的等待正是我启动“Lane Tech PCL - 洗衣机监控器”这个个人项目的初衷。它不是一个复杂的商业产品而是一个极客范儿十足的家庭自动化解决方案核心目标只有一个让洗衣过程变得透明、可预测并最终实现自动化提醒。所谓“PCL”在这里可以理解为“Personal Cloud Logger”个人云记录器的缩写它点明了这个项目的技术内核数据采集、云端记录与可视化。整个系统的思路非常清晰通过一个微小的、低功耗的硬件设备非侵入式地监测洗衣机的运行状态主要是振动和噪音将这些物理信号转化为数字数据上传到你自己掌控的云端服务器最终在一个简洁的网页仪表盘上实时展示洗衣机的“工作日志”——当前状态、本次运行时长、历史记录乃至能耗估算。这个项目适合谁呢首先当然是热爱动手的硬件爱好者、Home Assistant或物联网IoT玩家。其次任何对家庭能耗管理、生活习惯数据化感兴趣的朋友都能从中获得启发。它不要求你精通所有技术栈但需要你愿意折腾享受从无到有搭建一个完整系统的乐趣。通过这个项目你不仅能解决一个具体的家庭痛点更能深入理解传感器应用、信号处理、物联网数据流以及前后端协同工作的完整链条。接下来我将拆解整个系统的设计思路、硬件选型、软件实现以及那些只有亲手做过才会知道的“坑”。2. 系统整体设计与核心思路拆解在动手之前明确设计目标至关重要。我们希望这个监控器是无感的、可靠的和自给自足的。2.1 核心需求与设计原则非侵入式安装绝对不能修改洗衣机的任何电路或结构。理想的方式是直接将监控设备贴在洗衣机外壳上利用振动和声音来判断状态。这决定了我们的核心传感器是加速度计用于振动和麦克风用于声音。低功耗与无线连接设备需要长期放置在洗衣机附近接市电虽然简单但布线不美观。因此优先考虑电池供电这就要求主控芯片和无线模块必须具备超低功耗特性。ESP32系列芯片成为了不二之选它集成了Wi-Fi和蓝牙拥有出色的低功耗管理能力。边缘计算与数据精简如果将所有原始的振动和音频数据都上传到云端会迅速耗尽电池和网络带宽。因此必须在设备端边缘进行初步的信号处理和状态识别。例如实时计算振动幅度RMS值、分析声音频谱特征只将处理后的结果如“状态脱水”、“强度85”和关键摘要数据上传。私有化部署与数据安全数据是我自己的我希望它们存储在我自己的服务器上。这排除了直接使用公有云IoT平台虽然它们更方便的选项。我们将自建一个轻量级的后端服务来接收和存储数据。清晰的可视化数据需要被直观地理解。一个Web仪表盘是最佳选择它能展示实时状态、历史曲线并能在洗衣结束时发送浏览器通知或集成到Home Assistant中实现自动化提醒。基于以上原则我设计了如下系统架构感知层由ESP32开发板如ESP32-S3或ESP32-C3、MPU6050六轴加速度计/陀螺仪和一颗驻极体麦克风模块组成。边缘处理层在ESP32上运行的固件负责读取传感器数据进行滤波、特征提取和状态机判断。网络传输层通过Wi-Fi将处理后的JSON格式数据通过HTTP POST或MQTT协议发送到后端服务器。云端服务层一台轻量级服务器可以是家里的树莓派、旧电脑或VPS运行着用PythonFlask/FastAPI或Node.js编写的API服务以及一个时序数据库如InfluxDB或关系型数据库如SQLite/PostgreSQL用于存储数据。应用展示层一个由HTML/CSS/JavaScript构建的前端页面通过图表库如Chart.js或ECharts从后端API获取数据并动态展示。2.2 技术栈选型背后的考量主控为什么是ESP32而不是Arduino UnoArduino Uno简单易用但缺乏集成的无线功能和强大的低功耗模式。ESP32不仅内置Wi-Fi/蓝牙其双核处理器也能更好地胜任实时的信号处理任务。此外ESP32的深度睡眠模式电流可低至10μA以下这对于电池供电设备至关重要。我们可以让设备在大部分时间深度睡眠每隔几秒唤醒一次进行采样和判断从而极大延长续航。为什么选择MPU6050加速度计MPU6050是一款经典且廉价的运动处理传感器。它能同时测量三轴加速度和三轴角速度。对于洗衣机监控我们主要关心的是振动强度加速度幅度。MPU6050通过I2C接口与ESP32通信程序简单社区支持丰富。它的精度和量程通常可达±16g完全足够检测洗衣机从轻柔洗涤到高速脱水所产生的剧烈振动。数据传输协议MQTT vs HTTPHTTP实现简单符合RESTful风格适合低频次、非实时的数据上报。对于几分钟上报一次状态数据的场景HTTP完全够用。MQTT一种轻量级的发布/订阅消息协议专为物联网设计。它更节省带宽和电力支持实时双向通信。如果你未来想扩展为“远程控制”或需要服务器主动下发指令MQTT是更好的选择。在本项目中我选择了MQTT因为它更“物联网”与Home Assistant等平台的集成也更原生。重要提示无论选择哪种协议务必在通信中启用加密如MQTT over TLS/SSL HTTPS。虽然家庭网络相对安全但防止数据被窃听或篡改是好习惯。数据库选型时序数据库的优势洗衣机的状态数据是典型的时间序列数据每个数据点都带有时间戳。InfluxDB是专门为处理时间序列数据而设计的数据库它在写入效率、存储压缩和时间范围查询方面比传统的SQL数据库有巨大优势。例如查询“过去24小时内洗衣机的振动强度曲线”在InfluxDB中会非常高效和简单。如果追求极简SQLite也能胜任初期开发。3. 硬件搭建与传感器数据采集实操理论清晰后我们开始动手组装硬件。这部分需要一些基础的焊接和接线能力。3.1 物料清单与电路连接你需要准备以下核心部件ESP32开发板一块推荐带电池管理芯片的型号如ESP32-S3或TTGO T-Display。MPU6050模块一个。驻极体麦克风模块一个注意选择输出为模拟量的型号以便ESP32的ADC读取。锂离子电池一块如18650容量建议2000mAh以上。电池充电/保护板一块如果ESP32板载没有。杜邦线若干洞洞板或定制PCB可选。3D打印外壳或防水盒一个用于保护和固定。接线示意图仅供参考具体引脚请以你的开发板手册为准ESP32 GPIO21 (SDA) --- MPU6050 SDA ESP32 GPIO22 (SCL) --- MPU6050 SCL ESP32 3.3V --- MPU6050 VCC, 麦克风模块 VCC ESP32 GND --- MPU6050 GND, 麦克风模块 GND ESP32 GPIO34 (ADC1_CH6 仅输入引脚) --- 麦克风模块 AUDIO_OUT 电池正极 --- 充电板B 充电板OUT --- ESP32 VIN/VBAT 电池负极 --- 充电板B- 充电板OUT- --- ESP32 GND注意ESP32的ADC引脚如GPIO34是仅输入引脚不能用于输出。确保麦克风模块的输出信号电压范围在0-3.3V之间超过可能会损坏ESP32。有些麦克风模块输出带有直流偏置可能需要串联一个电容进行隔直处理。3.2 固件开发状态识别是核心逻辑硬件连接好后我们需要为ESP32编写固件。这里使用Arduino IDE或PlatformIO进行开发。核心逻辑是一个状态机它根据传感器数据判断洗衣机处于“空闲”、“洗涤”、“漂洗”、“脱水”或“结束”中的哪一种状态。关键代码逻辑拆解初始化与连接#include WiFi.h #include PubSubClient.h // MQTT客户端库 #include MPU6050_tockn.h // MPU6050库 #include Wire.h MPU6050 mpu6050(Wire); WiFiClient espClient; PubSubClient mqttClient(espClient); const char* ssid 你的Wi-Fi名称; const char* password 你的Wi-Fi密码; const char* mqtt_server 你的MQTT服务器IP; const int mqtt_port 1883; // 如果启用TLS则为8883 void setup() { Serial.begin(115200); Wire.begin(); mpu6050.begin(); mpu6050.calcGyroOffsets(true); // 校准陀螺仪 connectToWiFi(); connectToMQTT(); }数据采集与特征提取void loop() { // 1. 读取传感器数据 mpu6050.update(); float ax mpu6050.getAccX(); float ay mpu6050.getAccY(); float az mpu6050.getAccZ(); // 计算合成加速度振动强度 float vibration sqrt(ax*ax ay*ay az*az); int micValue analogRead(MIC_PIN); // 读取麦克风ADC值 // 简单的音频幅度计算可改进为RMS int soundLevel abs(micValue - 512); // 假设静态偏置为512 // 2. 低通滤波去除高频噪声 static float filteredVib 0; float alpha 0.1; // 滤波系数越小越平滑 filteredVib alpha * vibration (1 - alpha) * filteredVib; // 3. 状态判断 String currentState determineState(filteredVib, soundLevel); // 4. 如果状态发生变化或定时上报则通过MQTT发布消息 if (stateChanged || millis() - lastReportTime REPORT_INTERVAL) { publishData(currentState, filteredVib); } // 5. 进入轻度睡眠以省电非深度睡眠以保持Wi-Fi连接 esp_sleep_enable_timer_wakeup(1000000); // 睡眠1秒 esp_light_sleep_start(); } String determineState(float vib, int sound) { // 这是核心算法需要根据你的洗衣机特性调整阈值 if (vib 2.0 sound 50) { // 阈值需要实测校准 return IDLE; } else if (vib 2.0 vib 8.0 sound 50) { return WASHING; } else if (vib 8.0) { // 脱水时振动非常剧烈 return SPINNING; } else { return UNKNOWN; } }功耗优化实战技巧Wi-Fi功耗是大头连接Wi-Fi后即使不发送数据功耗也有几十毫安。我们的策略是仅在需要发送数据时保持Wi-Fi连接发送完毕后立即断开然后让ESP32进入深度睡眠。但这会丢失实时性。折中方案是使用esp_light_sleep它能在保持Wi-Fi连接的同时降低一些功耗唤醒速度也更快。传感器供电管理如果可能通过一个MOSFET或三极管控制MPU6050和麦克风模块的电源在采样间隙彻底关闭它们。调整上报频率状态稳定时如长时间脱水降低上报频率如每30秒一次状态变化时立即上报。4. 后端服务搭建与数据持久化方案设备端的数据需要有个地方接收和存储。我选择在家庭服务器一台常年开机的旧笔记本装了Ubuntu上搭建服务。4.1 MQTT Broker选择与部署MQTT代理Broker是消息的中转站。Mosquitto是一个轻量级、开源且流行的选择。在Ubuntu服务器上安装和配置Mosquittosudo apt update sudo apt install mosquitto mosquitto-clients # 安装后Mosquitto服务会自动启动。检查状态 sudo systemctl status mosquitto # 设置密码认证强烈建议 sudo mosquitto_passwd -c /etc/mosquitto/passwd lane_tech_user # 然后会提示你输入密码 # 编辑配置文件 sudo nano /etc/mosquitto/conf.d/default.conf在配置文件中添加allow_anonymous false # 禁止匿名连接 password_file /etc/mosquitto/passwd listener 1883 0.0.0.0 # 监听所有网卡的1883端口 # 如果需要TLS加密配置更复杂此处略重启服务sudo systemctl restart mosquitto现在你的ESP32就可以用设置的用户名密码连接到mqtt://你的服务器IP:1883了。4.2 数据订阅与存储服务Python示例我们需要一个常驻程序订阅ESP32发布的主题并将数据写入数据库。这里用Python的paho-mqtt库和influxdb客户端库。import paho.mqtt.client as mqtt from influxdb_client import InfluxDBClient, Point from influxdb_client.client.write_api import SYNCHRONOUS import json from datetime import datetime # InfluxDB 配置 INFLUXDB_URL http://localhost:8086 INFLUXDB_TOKEN 你的管理员Token INFLUXDB_ORG 你的组织名 INFLUXDB_BUCKET washing_machine client InfluxDBClient(urlINFLUXDB_URL, tokenINFLUXDB_TOKEN, orgINFLUXDB_ORG) write_api client.write_api(write_optionsSYNCHRONOUS) # MQTT回调函数 def on_connect(client, userdata, flags, rc): print(Connected with result code str(rc)) client.subscribe(lane_tech/washer/status) # 订阅主题 def on_message(client, userdata, msg): payload msg.payload.decode() print(fReceived {payload} from {msg.topic} topic) try: data json.loads(payload) # 构造InfluxDB数据点 point Point(washer_status)\ .tag(device_id, data.get(device_id, esp32_01))\ .field(state, data.get(state))\ .field(vibration, float(data.get(vibration, 0)))\ .field(sound_level, int(data.get(sound, 0)))\ .time(datetime.utcnow()) # 使用服务器时间 # 写入InfluxDB write_api.write(bucketINFLUXDB_BUCKET, recordpoint) print(Data written to InfluxDB) except Exception as e: print(fError processing message: {e}) # 设置MQTT客户端 mqtt_client mqtt.Client() mqtt_client.username_pw_set(lane_tech_user, 你的密码) # 对应Mosquitto设置 mqtt_client.on_connect on_connect mqtt_client.on_message on_message mqtt_client.connect(你的服务器IP, 1883, 60) mqtt_client.loop_forever() # 阻塞式循环保持连接将这个Python脚本设置为系统服务使用systemd它就会在后台一直运行忠实地充当数据桥梁。4.3 数据库设计与查询优化在InfluxDB中我们创建了一个名为washing_machine的Bucket。数据模型很简单Measurement表washer_statusTags索引标签device_id(用于区分多个设备)Fields数据字段state(字符串),vibration(浮点数),sound_level(整数)Time 自动生成的时间戳。这种结构非常适合做时间序列查询。例如在前端我们需要查询最近一次洗衣的数据来绘图可以使用InfluxDB的Flux查询语言from(bucket: washing_machine) | range(start: -12h) // 查询过去12小时 | filter(fn: (r) r[_measurement] washer_status) | filter(fn: (r) r[device_id] esp32_01) | filter(fn: (r) r[_field] vibration or r[_field] state) | aggregateWindow(every: 10s, fn: mean, createEmpty: false) // 每10秒聚合一次这个查询会返回振动数据和状态变化的时间序列非常适合用图表展示。5. 前端可视化仪表盘开发数据存好了最后一步是让人能看懂。我们开发一个简单的单页面应用SPA。5.1 技术选型与页面布局为了轻量化和快速开发我选择了前端框架 Vue.js 3 (使用Composition API) 或 React。它们的数据绑定和组件化特性能让开发变得简单。这里以Vue为例。图表库 Chart.js。它足够轻量图形美观且与Vue有很好的集成库如vue-chartjs。HTTP客户端 Axios用于从后端API获取数据。UI组件 使用纯CSS或轻量级UI库如Tailwind CSS来快速构建界面。页面布局可以设计为上下结构顶部状态栏显示洗衣机当前实时状态一个大图标和文字描述、本次运行时长、预估剩余时间需算法学习。中部图表区一个时间序列折线图展示过去几小时内的振动强度曲线并用不同颜色的背景区域高亮标出“洗涤”、“脱水”等不同阶段。底部历史记录一个表格或列表展示最近几次的洗衣记录包括开始时间、结束时间、总时长、主要阶段耗时等。5.2 核心功能实现实时数据更新与图表绘制前端需要做两件事轮询获取最新数据和动态更新图表。Vue组件示例简化版template div h1Lane Tech 洗衣机监控面板/h1 div classstatus-board div :classstatus-icon ${currentState}/div p当前状态: strong{{ currentStateLabel }}/strong/p p运行时长: {{ formatDuration(runningTime) }}/p /div div classchart-container canvas refvibrationChart/canvas /div /div /template script setup import { ref, onMounted, onUnmounted } from vue; import Chart from chart.js/auto; import axios from axios; const currentState ref(idle); const currentStateLabel ref(空闲); const vibrationChart ref(null); let chartInstance null; // 1. 轮询后端API获取最新状态和数据 const fetchLatestStatus async () { try { const response await axios.get(/api/washer/status/latest); currentState.value response.data.state; // 根据state更新label const stateMap { IDLE: 空闲, WASHING: 洗涤中, SPINNING: 脱水中, END: 结束 }; currentStateLabel.value stateMap[currentState.value] || 未知; } catch (error) { console.error(Failed to fetch status:, error); } }; // 2. 获取历史数据并渲染图表 const renderChart async () { try { const response await axios.get(/api/washer/data/history?hours6); const data response.data; // 假设返回 {times: [], vibrations: []} if (chartInstance) { chartInstance.destroy(); } const ctx vibrationChart.value.getContext(2d); chartInstance new Chart(ctx, { type: line, data: { labels: data.times, datasets: [{ label: 振动强度, data: data.vibrations, borderColor: rgb(75, 192, 192), tension: 0.1, fill: true }] }, options: { responsive: true, scales: { x: { type: time, time: { unit: hour } }, y: { beginAtZero: true, title: { display: true, text: 振动 (g) } } } } }); } catch (error) { console.error(Failed to fetch chart data:, error); } }; onMounted(() { // 初始化时加载一次 fetchLatestStatus(); renderChart(); // 设置定时器每10秒更新一次状态每1分钟更新一次图表 const statusInterval setInterval(fetchLatestStatus, 10000); const chartInterval setInterval(renderChart, 60000); onUnmounted(() { clearInterval(statusInterval); clearInterval(chartInterval); }); }); /script后端需要提供两个API端点/api/washer/status/latest返回最新状态和/api/washer/data/history返回历史时间序列数据。这些API由PythonFlask/FastAPI或Node.js服务提供它们的工作就是从InfluxDB中执行查询并将结果格式化为JSON。5.3 通知功能集成纯粹的网页需要你主动打开才能看到状态。为了实现“洗衣结束自动提醒”我们可以利用浏览器的Notification API需要HTTPS和用户授权或更通用的方案集成到Home Assistant。Home Assistant集成方案更推荐在Home Assistant中安装MQTT集成。我们的ESP32设备通过MQTT发布状态。Home Assistant会自动发现这个设备如果配置了MQTT发现。在Home Assistant中洗衣机状态会作为一个传感器实体出现例如sensor.washing_machine_state。然后你就可以在Home Assistant的自动化中轻松创建规则“当sensor.washing_machine_state从spinning变为idle时向我的手机发送通知通过Home Assistant App”。 这样你就获得了一个完全私有化、高度可定制的智能洗衣提醒系统。6. 部署、调试与长期维护心得项目开发完成部署上线并稳定运行才是真正的挑战。6.1 设备安装与阈值校准这是最需要耐心的一步。用3M双面胶或纳米胶将封装好的设备牢固地贴在洗衣机侧面或顶部盖板中心位置避免贴在边缘振动模式复杂的地方。校准流程空闲状态基线让洗衣机完全静止运行设备记录下vibration和sound_level的数值范围。这个值就是你的“空闲阈值”上限。分阶段采样运行一次标准洗衣程序。通过串口监视器或SD卡日志实时观察并记录不同阶段注水、洗涤、排水、脱水的传感器读数。重点关注振动数据。洗涤时是规律性、中等强度的振动脱水时是持续、高强度的振动。找到能明确区分这几个阶段的数值门槛。更新代码将实测得到的阈值更新到ESP32固件的determineState函数中。例如if (filteredVib 1.5) return “IDLE”; // 实测空闲时振动小于1.5g else if (filteredVib 1.5 filteredVib 5.0) return “WASHING”; else if (filteredVib 5.0) return “SPINNING”;注意不同品牌、型号的洗衣机甚至不同地面平整度振动特性都不同。必须针对你自己的洗衣机进行校准。6.2 常见问题与排查实录在开发和运行过程中我遇到了不少问题这里总结成排查表问题现象可能原因排查步骤与解决方案ESP32无法连接Wi-Fi1. SSID/密码错误2. 路由器设置了MAC过滤3. 信号太弱1. 检查代码中的凭证。2. 查看路由器后台将ESP32的MAC地址加入白名单。3. 用手机测试位置信号强度考虑加装Wi-Fi中继。MQTT连接失败1. 服务器IP/端口错误2. 防火墙未开放端口3. 用户名密码错误1.ping服务器IP用telnet IP 1883测试端口。2. 检查服务器防火墙设置sudo ufw allow 1883。3. 使用mosquitto_sub命令行工具测试Broker本身是否正常。数据波动大状态误判1. 传感器受外界干扰如人走过2. 未进行软件滤波3. 阈值设置不合理1. 尝试将设备安装位置更隐蔽或增加物理减震。2. 实现更稳定的滤波算法如移动平均滤波或卡尔曼滤波。3. 重新进行详细的阈值校准并加入“状态保持时间”逻辑避免瞬时波动导致状态跳变。例如连续5个采样周期都判定为“脱水”才正式切换状态。电池续航极短1. Wi-Fi常开2. 未启用深度睡眠3. 传感器模块未断电1. 优化连接策略发送后断开Wi-Fi并深度睡眠。2. 检查代码确保调用了esp_deep_sleep_start()。3. 为传感器模块增加电源开关电路。实测采用“工作2秒采样发送深度睡眠58秒”的策略2000mAh电池可续航数月。前端图表不更新1. 后端API故障2. 前端JS错误3. 浏览器缓存1. 打开浏览器开发者工具F12查看“网络”标签页中API请求是否返回错误。2. 查看“控制台”标签页是否有JS报错。3. 尝试硬刷新CtrlF5或禁用缓存进行调试。6.3 项目优化与扩展方向这个基础版本运行稳定后你可以考虑以下优化和扩展能耗估算通过监测不同阶段的持续时间和已知的洗衣机额定功率在铭牌上可以粗略估算每次洗衣的耗电量。能耗(kWh) 功率(kW) × 时间(h)。将估算结果展示在前端是家庭能源管理的有趣补充。机器学习优化状态识别手动设置阈值终究不够灵活。可以采集大量不同程序、不同负载下的传感器数据打上标签在电脑上训练一个简单的分类模型如决策树、SVM然后将模型部署到ESP32上实现更精准、自适应的状态识别。多设备管理与对比将后端和前端扩展支持同时监控家中多台洗衣机或烘干机。在仪表盘上进行对比看看哪台机器更费电、用时更长。预测性维护长期监测振动数据。如果发现脱水时的高频振动幅度随时间逐渐异常增大可能意味着配重块松动或轴承磨损可以在问题严重前给出提醒。这个“Lane Tech PCL - 洗衣机监控器”项目从构思到实现贯穿了硬件、嵌入式、后端、前端乃至数据分析等多个领域。它带给我的最大收获不是解决了洗衣提醒这个小问题而是完整地体验了一个物联网产品从原型到落地的全过程。每一个环节的调试、每一个坑的填平都加深了对系统协同工作的理解。如果你正想找一个项目来练手将碎片化的知识串联起来那么这个项目会是一个绝佳的起点。它足够小可以快速看到成果它也足够深可以让你根据兴趣无限挖掘。最重要的是当你的手机在衣服洗好的那一刻准时响起通知时那种创造带来的满足感是无与伦比的。