用微波雷达+激光测距实现宠物饮水机按需出水
1. 为什么普通宠物饮水机不够用从一直出水到按需出水我家猫对活水执念很深静止的水盆它不碰必须看到水流才肯喝。最开始我也跟大多数人一样直接买那种24小时循环过滤的宠物饮水机泵不停转、水不停流。用了一阵子之后发现问题越来越多才决定自己做一台带宠物存在检测和靠近感应出水的机器。这个项目的核心逻辑其实很简单宠物靠近时才出水宠物走了就停而不是让水泵永远空转。先说普通循环饮水机的三个隐性成本这也是我决定自己动手的直接原因。第一是滤芯消耗速度。循环泵只要开机水就一直在滤芯里过不管有没有宠物来喝。我家猫一天真正喝水的时间加起来不到二十分钟但滤芯是24小时在工作的原本标称能用三十天的滤芯实际半个月就发酸发滑。按需出水之后泵每天实际运行时间可能就一两个小时滤芯损耗直接降了一个量级。第二是噪音和震动。循环泵的噪音在白天不明显到了晚上就非常突兀尤其是水泵老化之后那种低频嗡嗡声贴着地板传过来浅睡的人很容易被吵醒。宠物靠近感应出水的方案里泵大部分时间处于断电状态夜里几乎零噪音猫咪去喝水的时候泵才启动声音反而变成一种水来了的提示。第三是水质本身。水流一直在循环水里的食物残渣、猫毛、灰尘会被反复搅动反而加速细菌滋生。按需出水时水是相对静止的每次出水相当于一次换水动作加上外置水箱的密封设计水质维持时间明显更长。这个逻辑很像人用的即热饮水机——用多少、加热多少而不是一大桶水反复保温。当然市面上也不是没有带感应功能的宠物饮水机但大部分用的还是红外对射或者震动感应放在真实家庭环境里误报率很高。猫从旁边路过但没想喝水机器哗哗出半天水或者猫已经低头凑到出水口了传感器却迟迟不触发。真正好用的方案需要把存在检测和靠近感应分开设计前者解决宠物在不在附近后者解决宠物是不是真的要喝水。这两个逻辑听起来差不多实际实现起来差别很大。所以这个项目的目标定得很明确做一台能区分路过和来喝水的饮水机宠物靠近出水口一定范围内才启动水泵离开后自动停止同时保留持续出水模式方便手动控制。整套系统不需要很复杂的机械结构主要工作量在传感器选型、泵驱动电路和检测逻辑上。项目整体难度属于中级适合有一定Arduino或ESP32基础、想亲手做智能家居设备的玩家参考。2. 传感器选型先搞清楚宠物存在检测和人体感应的本质区别做这个项目最容易踩的坑就是把人体感应方案直接套到宠物身上。市面上大量所谓智能感应产品用的都是人体红外传感器也就是PIR这种方案对宠物基本失效原因下面细说。我一开始也图省事买了PIR模块实测下来发现完全不是那么回事后来才换成微波雷达加激光测距的组合。这块值得单独写一段帮后面做同类项目的朋友少走弯路。2.1 四种常用存在检测方案横向对比我整理了一下目前市面上比较常见的几种存在检测方案各有各的脾气适配的场景完全不一样。检测方案工作原理宠物适用性优点主要问题PIR红外热释电检测人体/动物发出的红外线变化较弱便宜、功耗极低、方案成熟宠物体型小、红外辐射弱且需要移动才能触发猫趴着喝水大概率检测不到微波雷达多普勒发射微波并检测反射波的频率变化强对微小动作敏感可穿透塑料外壳不受温度影响穿透力强导致易受外部运动干扰调试时需要仔细调节灵敏度和方向激光测距ToF通过飞行时间计算物体到传感器的距离强精确测量距离可设置距离阈值需要开口对准检测区域水花可能造成干扰成本稍高超声波测距发射声波并测量回波时间中成本低、不涉及隐私对软质物体宠物毛发反射效果差且饮水机水泵的超声噪声可能干扰自身这四种方案单独拿出来都能做检测但用在宠物饮水机这个具体场景里各有各的短板。PIR的问题是猫趴着不动就不触发但实际情况是猫喝水时头会轻微晃动理论上也能触发只是距离稍微远一点或者猫体型小一点触发概率就大幅下降。我实测过PIR对十斤以上的成年猫大概在0.5米内才能稳定触发而且猫必须是在走动或者明显摇头的状态一旦低头喝水的动作幅度变小信号就断了。超声波方案的问题在于小型潜水泵在运转时本身就会产生一定频率的噪声同在水箱这个封闭腔体里很容易干扰超声波传感器的回波信号。我试过用普通HC-SR04超声波模块做靠近检测泵一启动读数就开始乱跳根本没法做可靠的逻辑判断。2.2 为什么我最终选了微波雷达加激光测距组合最终我采用的方案是RCWL-0516微波雷达模块做主检测VL53L0X激光测距模块做二次确认。这两个模块现在都很便宜加起来不到二十块钱但配合起来使用效果相当理想。微波雷达的核心是多普勒效应原理是发射特定频率的微波遇到运动物体后反射波的频率会发生变化模块检测到这个变化就输出高电平。它的好处是对微小运动非常敏感哪怕是猫呼吸时身体的轻微起伏都能检测到而且微波可以穿透饮水机的外壳传感器可以完全藏在机身内部外观上不会露出任何检测窗口。RCWL-0516的检测距离默认在3到5米左右这个距离明显太远了放在家里会把人在客厅走动也当成宠物靠近所以必须通过调节模块上的灵敏度旋钮来收窄检测范围。但微波雷达只能检测动检测不了距离它不会告诉你宠物是在半米外还是已经在出水口旁边。这时候就需要激光测距模块来做二次确认。VL53L0X是ToF方案通过发射激光并测量光子往返时间来计算距离精度能到毫米级。我在出水口前方斜向下安装了一个VL53L0X实时检测宠物头部到出水口的距离只有当微波雷达检测到运动并且激光测距读到的距离小于设定阈值时系统才判定宠物真的要喝水了。这个组合的逻辑就是雷达负责粗检测激光负责精定位。雷达先发现有东西在附近动了激光再确认这东西已经到了喝水位置两个条件同时满足才出水误触发率大大降低。从实际使用来看我家猫从两米外走过来雷达先触发等它走到出水口约15厘米范围内激光测距确认水泵启动出水整个响应时间不到一秒钟。3. 硬件搭建从主控选型到泵驱动的完整链路传感器方案定了之后剩下的就是硬件电路的搭建。很多人做这类小项目喜欢直接买集成好的模块往杜邦线上一插就完事但宠物饮水机涉及水泵这种感性负载供电和驱动电路如果处理不好轻则系统重启重则烧主控这块必须单独拿出来说说。3.1 硬件清单与采购注意事项我用的核心硬件清单如下基本都是常见型号采购没有难度主控ESP32-C3 SuperMini开发板成本十几块钱自带WiFi和蓝牙后续想接米家、Home Assistant或者手机App都留有余地。选ESP32-C3而不是Arduino Uno是因为封装小、功耗低、IO口够用而且USB直接供电方便调试。存在检测RCWL-0516微波雷达模块距离确认VL53L0X激光测距模块I2C接口水泵3V到5V小型潜水泵我选的是额定电压5V、流量约120L/H的型号扬程比较低但用在饮水机上足够泵驱动AO3400型N沟道MOS管加一个10K下拉电阻、一个1N5819肖特基二极管液位检测非接触式电容液位传感器贴在透明水箱外壁底部供电5V/2A USB电源适配器水泵和主控共用一路电源有几个采购时容易忽略的点提醒一下。RCWL-0516市面上有两种常见版本一种带灵敏度可调电位器一种是固定灵敏度的一定要买带可调电阻的版本不然检测距离完全不可控。VL53L0X模块有1V8和2V8两种电平版本选的时候注意看模块是否自带电平转换ESP32-C3的IO口是3.3V电平直接接2V8的模块会存在电平不匹配的隐患。水泵最好选那种底部带滤网罩的防止杂质吸入卡死叶轮。3.2 泵驱动电路不是接个IO口那么简单水泵看起来就是个直流小电机很多人会想当然地把它直接接在主控的GPIO上这是个大坑。水泵启动瞬间的电流冲击很大实测我用的这个5V水泵堵转电流可以达到800毫安以上而ESP32-C3的GPIO最大输出电流只有40毫安直接接上去的结果就是主控复位或者GPIO烧毁。正确的驱动方式是用MOS管做低边开关GPIO只负责控制系统通断水泵的电流从电源直接走MOS管到地。具体连接方式GPIO引脚通过一个100欧姆电阻接入MOS管的栅极同时在栅极和地之间接一个10K下拉电阻防止GPIO悬空时MOS管误导通MOS管的源极接地漏极接水泵负极水泵正极接5V电源在水泵两端反向并联一个1N5819肖特基二极管也就是二极管的阴极接5V、阳极接水泵负极这个二极管是续流作用。水泵是感性负载断电瞬间会产生反向电动势如果没有续流二极管这个反向尖峰很容易击穿MOS管或干扰主控整个电路我后来还做了个小优化就是MOS管栅极驱动电阻不用100欧姆而用1K。阻值调大一点会让开关沿稍微变缓牺牲一点点开关速度但能明显减小水泵启停瞬间对电源轨的冲击。对于饮水机这种低频开关场景开关速度完全不是瓶颈电源稳定性反而更重要。3.3 整机供电与防干烧设计供电方面水泵和主控共用同一个5V电源是没有问题的但要加一个大容量的滤波电容。我在电源输入两端并联了一个470微法的电解电容和一个0.1微法的陶瓷电容前者稳定低频波动后者滤除高频噪声。有了这两个电容之后水泵启动瞬间的电压跌落从原来的0.8伏降低到0.3伏以内ESP32-C3不会再因为欠压重启。液位传感器在这个项目里不是可有可无的装饰是保命的组件。潜水泵最怕空转水温升高加上机械磨损时间长了水泵很容易损坏甚至会有塑料焦糊味。非接触式电容液位传感器贴在透明水箱外壁的底部通过检测水与空气的介电常数差异来判断有没有水。当液位低于传感器位置时主控会强制禁止启动水泵同时通过板载LED闪烁提示需要加水。4. 软件逻辑状态机实现存在检测→靠近感应→按需出水硬件搭好只是地基这块项目的灵魂其实在软件状态机里。如果用最简单的写法就是检测到靠近就开泵检测不到就关泵但实际跑起来你会发现这个简单逻辑问题很多主要在于水泵频繁启停对硬件寿命的影响以及猫在喝水过程中头会偶尔抬起来又低下去这时候如果泵跟着停再跟着开体验非常差。4.1 状态机设计与关键时间参数我设计了一个包含四个状态的简单状态机待机状态雷达持续检测激光测距进入低功耗轮询模式水泵断电唤醒状态雷达检测到运动激光测距开始高频采样确认距离是否低于阈值出水状态水泵启动持续出水保持状态水泵停止但系统仍在检测如果短时间内再次确认宠物靠近直接回到出水状态不用重新走唤醒流程这几个状态之间切换的核心在于时间参数的控制。我最初设定出水3秒之后如果检测不到宠物就直接停机结果猫喝一口水抬头喘口气水泵就停了等它再低头又要重新启动一来一回猫都烦了。后来把参数调整为连续出水8秒后暂停2秒暂停期间仍以高频采样检测宠物是否还在目标区域如果还在就继续出水如果2秒内没有检测到才进入真正的待机。另外还加了一个冷却时间的概念。因为微波雷达检测的是运动宠物从旁边路过也会触发唤醒状态即使激光测距确认距离不够、系统不会出水但这个事件会被记录下来。如果猫频繁在饮水机附近走动系统就会频繁被唤醒虽然不出水但功耗会上升。我设置了一个30秒的冷却计时器唤醒事件发生后30秒内不再响应雷达信号除非激光测距直接检测到近距离目标。这样既能过滤掉路过场景又能保证来喝水可以随时触发。4.2 Arduino/ESP32示例代码这里给出一份可以直接跑通的Arduino代码骨架基于ESP32-C3开发板和Arduino IDE 2.x环境核心逻辑如下#include Wire.h #include VL53L0X.h #define PIN_RADAR 2 // RCWL-0516 微波雷达输出引脚 #define PIN_PUMP 3 // 控制水泵的MOS管栅极引脚 #define PIN_LED 4 // 板载指示灯引脚 #define PIN_LEVEL 5 // 非接触式液位传感器引脚 // 距离阈值单位毫米根据实际安装位置调整 #define DIST_THRESHOLD 150 // 时间参数单位毫秒 #define PUMP_RUN_TIME 8000 // 单次持续出水时长 #define PUMP_PAUSE_TIME 2000 // 出水暂停检测时长 #define COOLDOWN_TIME 30000 // 触发事件后的冷却时长 VL53L0X sensor; enum State { STATE_IDLE, STATE_WAKEUP, STATE_PUMPING, STATE_HOLDING }; State currentState STATE_IDLE; unsigned long stateTimer 0; unsigned long lastTriggerTime 0; int lastDistance 999; void setup() { Serial.begin(115200); pinMode(PIN_RADAR, INPUT); pinMode(PIN_PUMP, OUTPUT); pinMode(PIN_LED, OUTPUT); pinMode(PIN_LEVEL, INPUT_PULLUP); Wire.begin(); sensor.setTimeout(500); if (!sensor.init()) { Serial.println(VL53L0X init failed!); } sensor.setMeasurementTimingBudget(50000); // 50ms一帧兼顾响应速度和功耗 } void loop() { int radarTriggered digitalRead(PIN_RADAR); int waterLevelOk digitalRead(PIN_LEVEL); int distance readDistance(); unsigned long now millis(); bool recentlyTriggered (now - lastTriggerTime) COOLDOWN_TIME; switch (currentState) { case STATE_IDLE: // 只有水够且雷达触发或距离已经非常近时才进入唤醒状态 if (waterLevelOk (radarTriggered || distance DIST_THRESHOLD)) { if (!recentlyTriggered || distance DIST_THRESHOLD) { currentState STATE_WAKEUP; stateTimer now; digitalWrite(PIN_LED, HIGH); } } break; case STATE_WAKEUP: // 唤醒状态持续确认距离连续3帧低于阈值才认定真的要喝水 if (distance DIST_THRESHOLD) { int confirmCount 0; for (int i 0; i 3; i) { delay(30); if (readDistance() DIST_THRESHOLD) confirmCount; } if (confirmCount 2) { startPump(); currentState STATE_PUMPING; stateTimer now; } else { currentState STATE_IDLE; lastTriggerTime now; digitalWrite(PIN_LED, LOW); } } else { currentState STATE_IDLE; lastTriggerTime now; digitalWrite(PIN_LED, LOW); } break; case STATE_PUMPING: // 出水阶段先运行PUMP_RUN_TIME时长然后进入保持状态 if (now - stateTimer PUMP_RUN_TIME) { stopPump(); currentState STATE_HOLDING; stateTimer now; } // 中途水位不足立即停机 if (!waterLevelOk) { stopPump(); currentState STATE_IDLE; lastTriggerTime now; digitalWrite(PIN_LED, LOW); } break; case STATE_HOLDING: // 保持状态暂停出水但仍在检测2秒内如果还有近距离目标则继续出水 if (now - stateTimer PUMP_PAUSE_TIME) { if (distance DIST_THRESHOLD) { startPump(); currentState STATE_PUMPING; stateTimer now; } else { currentState STATE_IDLE; lastTriggerTime now; digitalWrite(PIN_LED, LOW); } } break; } } int readDistance() { uint16_t mm sensor.readRangeSingleMillimeters(); if (sensor.timeoutOccurred()) { return 999; } // 简单滤波连续读数变化过大时取更保守的较大值防止水花干扰 if (mm lastDistance 50 || mm lastDistance - 50) { mm lastDistance; } lastDistance mm; return mm; } void startPump() { digitalWrite(PIN_PUMP, HIGH); Serial.println(Pump ON); } void stopPump() { digitalWrite(PIN_PUMP, LOW); Serial.println(Pump OFF); }代码的逻辑其实不复杂就是把这四个状态串起来。有一个细节值得说在唤醒状态里我做了连续3帧确认而不是单帧距离低于阈值就立刻开泵。原因是VL53L0X在近距离测量时偶尔会冒出一个异常值单帧判定很容易被这个噪声干扰连续多帧确认可以把误判概率降到极低。4.3 低功耗与滤芯寿命之间的平衡这个项目如果用电池供电低功耗设计会是另一个大话题但宠物饮水机通常是持续插电使用的所以功耗不是最核心的指标。不过我仍然在软件里做了一些降低水泵运行时间的处理目的不是省电而是延长水泵和滤芯的寿命。水泵这种直流电机最怕的不是持续运行而是频繁启停。每次启动瞬间的电流冲击和机械冲击都会加速磨损所以在状态机里我特意设计了保持状态让暂定出水和继续出水之间的切换不需要水泵完全停机重启而是先停2秒再决定是继续泵水还是彻底休息。这个2秒的设计也是实测下来的最优值太短的话滤芯里的水还没流回去就会产生水锤效应太长的话猫可能等不及就走了。滤芯寿命方面按需出水的优势是巨大的。我做了个粗略统计以前24小时循环的机器泵每天运行1440分钟现在的按需出水方案猫一天来喝五六次每次出水大概十几秒到半分钟泵的实际运行时间加起来不到30分钟不到原来的百分之三。同样一片滤芯以前两周就变味现在用了将近两个月还基本正常。这个差距完全值得为迁就所谓水流动更干净的传统观念多花一点心思。5. 实测中的三个坑误触发、抽空空转、传感器互相干扰做完这台饮水机我实际在家里跑了一个多月期间遇到不少问题有些是调试参数就能解决的有些则是硬件布局的问题。挑三个最有代表性的踩坑经历说说这些问题在绝大多数传感器项目里都会遇到不只是饮水机场景。5.1 微波雷达被水泵震动带跑偏第一次整机联调的时候我遇到一个特别诡异的现象水泵一开始运转微波雷达就持续输出触发信号水泵停了之后反而恢复正常。一开始我以为是雷达模块的灵敏度调太高了把水泵的震动也当作运动检测到了但把灵敏度调到最低之后问题依然存在。后来排查到根因水泵运转时产生的是宽频振动这种机械振动会通过机身传导到雷达模块导致模块PCB本身在微微震动。微波雷达对空间中的微小运动极其敏感模块自身震动就相当于检测到持续运动自然就一直输出高电平。这个问题的解法不是调灵敏度而是给雷达模块做机械隔振。我用一小块双面泡棉胶把雷达模块固定在外壳上让它和泵体之间有柔性隔离震动传导大幅减弱问题才彻底消失。这也是做非接触式传感器项目最容易忽视的地方传感器自身的物理稳定性和电路设计、软件逻辑一样重要。5.2 激光测距被水花反射干扰第二个坑是VL53L0X在出水过程中读数飘忽。水泵运转时水流从出水口落下会砸出很多水花这些细小的水珠会对激光产生漫反射导致ToF测量值时不时跳变到非常近或者非常远的区间。我在代码里加了一个基于变化幅度限制的简单滤波就是上面示例代码里的那几行——当新读数和上一次读数相差超过50毫米时默认采用上一次读数不做跳变。这个方法在宠物饮水机这种变化缓慢的场景里足够用了不需要引入复杂的卡尔曼滤波。同时我在物理安装上也做了调整把激光测距模块的安装位置从正对出水口改成斜向下45度装在出水口的侧后方。这样激光束不会直接穿过水流和水花区域从源头减少了反射干扰。从实际效果看物理上的规避比软件滤波更有效。软件滤波只是把异常值吃掉但没法阻止激光测量掉帧斜向安装之后激光测距的输出稳定度明显提升出水过程中的距离读数误差控制在10毫米以内。5.3 宠物体积小检测距离缩水一半第三个坑是关于检测覆盖范围的。微波雷达的标称检测距离是5米但实际上这是针对人体这种大体型目标的对于猫这种体型小得多的目标有效检测距离会明显缩水。我实测下来RCWL-0516对成年猫的运动检测稳定距离大概在2米左右比标称的5米少了一半还多。如果你的宠物是小型犬或者幼猫检测距离还会进一步缩短。这个需要明确接受微波雷达能检测到一个信号本身就已经是成功了后续的判断距离是否够近是激光测距模块的工作。所以不要试图用一个雷达模块去完成所有事情雷达只管有没有动距离检测交给专业选手。我把雷达模块安装在了饮水机底部靠近前方的位置略微朝上倾斜一个角度让检测波束尽量覆盖宠物头部区域而不是水平向外发散这样能把有限的检测距离用在刀刃上。6. 整体调试经验与后续玩法这套系统从开始动手到稳定运行前后大概花了两个周末主要的调试时间都花在传感器参数上而不是代码或者机械结构上。调试环节有几个经验值得分享。先做分模块测试不要一上来就整机联调。我第一步单独测试微波雷达的输出逻辑先用手在传感器前晃确认触发距离符合预期第二步单独测VL53L0X用一个固定距离的纸板确认读数稳定第三步才把两个传感器的信号接入状态机先用模拟信号跑状态切换逻辑实际接上水泵已经是最后一个环节了。分模块测试的好处是出现问题能快速定位不至于面对一锅粥毫无思路。再说说时间参数怎么调。我建议先打印详细的串口日志记录状态切换时间、距离读数和触发事件然后观察宠物几天的行为模式再回头调整出水时长和冷却时间。每个人家里的宠物习惯不一样喝水频率、单次喝水时长差别很大这些参数必须按自己宠物的情况来不能照抄别人的设置。最后是整机的结构设计。如果完全自己做外壳建议把电子部分和水路部分在物理上隔开。我用了一个小号防水盒装电路板和传感器盒子外侧只引出电源线和水泵线水泵放在水箱里中间用软管连接出水口。这样即使宠物喝水时把水溅得到处都是也不会影响电路部分的安全性。另外VL53L0X的检测窗口和饮水机外壳之间留了大约3毫米的间隙既不影响测量又能防止水渍堆积在镜头上影响读数。这台机器现在还在每天早上服役整体运行非常稳定。在此基础上如果想继续扩展有几个方向可以考虑一是给ESP32-C3接上WiFi把出水次数、单次出水时长通过MQTT传到Home Assistant能直观看到宠物的喝水习惯二是加一个重量传感器在水盆下面同时检测饮水量这对关注宠物健康的用户来说是个很实用的功能三是在激光测距被确认有效的基础上可以用它来检测宠物逗留时长如果猫在饮水机前站了很长时间却没怎么喝水通过App推送提醒这已经可以算是一个基础的宠物健康监测设备了。硬件成本全部算下来不到六十块钱花在传感器调试上的时间才是这个项目里最值钱的部分。