夏天来了,人要多喝水,家里的宠物也要多喝水,但存在几个问题。
例如:
1、 我家的猫不喜欢喝固定盛水器里的水,有时偷喝水盆里水,有时喝地面的水。
2、买几 1-2 的饮水机,有时喝有时不喝,有时还喜欢扒拉机子,滤芯还要买指定的。
3、针对宠物喜欢喝流动、新鲜水的特性,网上又买了个 20 左右的饮水机,但水泵是通电后一直工作没有定时开关功能。

基础功能需求:
1、具有定时换水提示功能,保证水的新鲜度。
2、饮水机的水泵,自动模式,识别到宠物自动打开,否则自动关闭。
BOM 硬件元料表(总计:55 元)
如果不做后期功能扩展,目前功能使用 EPS32-C3 开发板(10 元)完全满足需求。
|--------|---------------------|--------|
| 序号 | 硬件型号 | 价格 |
| 1 | 饮水机 | 15 |
| 2 | ESP32-S3-N16R8 开发板 | 25 |
| 3 | 毫米波 LD02402 | 5 |
| 4 | 继电器模块 | 2 |
| 5 | 0.91寸OLED液晶屏SSD1306 | 8 |
后期可扩展功能:
1、Web 界面: web 设置管理界面,手机访问管理
2、数据持久化:饮水数据统计与分析,通过毫米波统计时间段喝水次数,通过水位传感器统计喝水量
3、水温管理:通过温度传感器冬天进行水温测量,添加加热棒控制水温
4、水质管理:TDS 传感器实时测量饮用水水质显示,超标提醒。
5、自动加换水:通过另外大容量纯净水桶与水泵,实现自动加水、换水。
6、摄像头: ESP32-CAM 实现摄像头功能,宠物识别高级功能。
改装硬件的外壳使用 3D 打印机,自己设计打印。

一、宠物感知传感器选型分析
对猫、狗的感应,毫米波雷达通常比普通 PIR 更灵敏;但 PIR 可通过安装与调参实现 "宠物免疫",而雷达很难彻底忽略小动物。
首先排除使用 HC-SR04 超声波,HC-SR04 测试时人体接近距离是有数据,宠物例如猫,测试时传感器是没有数据。原因如下:
- 猫毛发吸声波猫蓬松绒毛会直接吸收超声波,反射回波极弱,传感器收不到信号,直接测距失效。
- 体型小、曲面多猫身体弧度大,声波四散反射,没法原路回弹。
- 姿态低矮猫贴地、低头、蜷缩,超出超声波常规探测角度。
- 移动微动猫咪慢走、趴卧微动,测距逻辑容易判定无目标。
1、毫米波与 PIR原理差异(决定灵敏度)
- PIR(被动红外) :只检测移动的热源(8--14μm 红外),不发热 / 不动就无感。
-
- 猫狗体温≈38℃,和人体接近,但体表面积小、热信号弱。
- 依赖横向移动 + 温差;低矮、慢速、靠近地面的目标易被过滤。
- 毫米波雷达(24GHz/60GHz) :发射电磁波,靠多普勒效应 检测任何移动 / 微动 (包括呼吸、心跳),不依赖热量。
-
- 对微小动作、慢速移动、近距离贴近地面的目标都敏感。
- 能穿透薄布 / 玻璃,不受温度、光照、粉尘影响。
2、实际灵敏度对比(猫狗)
1. 普通 PIR(如 HC‑SR501)
- 猫(3--7kg):近距离(1--2m)快速走过 易触发;慢速 / 远处 / 蹲卧常无感。
- 小型狗(<10kg):类似猫;** 中型以上(>15kg)** 接近人体灵敏度。
- 可 "防宠物":调高安装高度(2.1--2.4m)+ 调低灵敏度,可忽略 10kg 以下宠物。
2. 毫米波雷达(24GHz 主流)
- 猫:极强敏感 ------ 哪怕缓慢走动、蹲卧微动、呼吸 都能触发;0.1m 起即可检测。
- 狗:全体型高敏感 ------ 从小型吉娃娃到大型犬,几乎无法过滤;调低灵敏度也难完全忽略。
- 微动检测:能感知0.1mm 级微动,猫狗呼吸 / 轻微晃动都逃不过。
3、关键区别与选型建议
- 灵敏度排序:毫米波雷达(极高) > 普通 PIR(中) > 防宠物 PIR(低)。
- 能否区分人与宠物:
-
- PIR:可通过安装 + 调参区分(过滤小宠物)。
- 毫米波雷达:基本不能区分,对人和猫狗一视同仁;高端雷达可粗略按体型过滤,但效果有限。
- 适用场景:
-
- 要防宠物误触发 (如智能家居、安防):选防宠物 PIR。
- 要精准监测宠物活动 (如喂食器、猫砂盆):选毫米波雷达。
- 要人体 + 宠物全感知 (如无人存在检测):选毫米波雷达。
二、 Trae SOLO 全自动 AI 开发平台
在进行物联网小型项目开发时,使用 SOLO 平台进行开发是非常好的应用场景。
Trae IDE 是 "你主导、AI 辅助" 的传统编辑器环境;Trae SOLO 是 "AI 主导、你提需求" 的全自动 AI 开发工作台,两者可以在同一个 Trae 客户端里切换。
1、核心定位:谁来主导?
- IDE 模式(IDE Mode)
-
- 角色:你是司机,AI 是副驾
- 流程:你写代码、操作文件、调 Git、运行调试;AI 做补全、问答、改片段
- 类比:VS Code + 强 AI 插件
- SOLO 模式(SOLO Mode)
-
- 角色:AI 是开发团队,你是产品 / 老板
- 流程:你用自然语言说需求 → AI 自己拆任务、写代码、建目录、装依赖、运行、调试、出预览Trae
- 类比:给一个高级 AI 工程师全权负责项目
2、界面布局完全不一样
- IDE 模式界面(经典编辑器)
-
- 左侧:文件树
- 中间:代码编辑器
- 右侧:AI 聊天 / 面板
- 下方:终端、调试、问题
- SOLO 模式界面(AI 任务工作台)
-
- 左侧:任务列表 / Plan
- 中间:对话 + 执行日志 + 预览
- 右侧:工具 / 模型 / 配置
3、工作流差异
IDE 模式:传统开发 + AI 增强
- 自己新建 / 打开项目
- 手动创建文件、写代码
- AI 帮补全、解释、改代码片段
- 自己运行、调试、提交 Git
- 适合:精细控制、长期迭代、复杂业务逻辑
SOLO 模式:需求驱动,AI 全自动
- 输入一句话需求(可语音 / 截图)
- AI 生成项目结构、选技术栈、写所有代码
- 自动安装依赖、运行、打开预览
- 你只需要:提需求、确认、验收、微调
- 适合:快速原型、全栈小项目、不会写代码也能做
4、可用智能体(Agent)不同
- IDE 模式可用
-
- Chat:问答、改片段
- Builder:生成项目 / 页面(轻量)
- SOLO 模式可用
-
- SOLO Coder:复杂项目全流程(架构→编码→调试→部署)
- SOLO Builder:专门做 Web 应用,自动出 PRD + 代码 + 预览
- 支持 Plan:AI 先出详细计划,你确认再执行
5、适合人群怎么选
- 选 IDE 模式,如果你:
-
- 是程序员,习惯 VS Code 工作流
- 需要精细控制代码、调试、Git
- 做大型 / 长期迭代项目
- 要深度定制、二次开发
- 选 SOLO 模式,如果你:
-
- 产品 / 设计 / 运营,不会写代码
- 想快速出原型、MVP、小应用
- 不想管细节,只想 "说需求→要结果"
- 做全栈项目、网站、工具类应用
6、总结
|--------|--------------|-------------------------|
| 维度 | IDE 模式 | SOLO 模式 |
| 主导者 | 开发者主导 | AI 主导 |
| 界面 | VS Code 经典布局 | AI 任务工作台 |
| 工作流 | 手动编码 + AI 辅助 | 自然语言需求 → AI 全流程 |
| 智能体 | Chat、Builder | SOLO Coder、SOLO Builder |
| 控制力 | 强(你说了算) | 弱(AI 说了算) |
| 上手难度 | 中等(需会基本编码) | 极低(会说话就行) |
| 典型场景 | 日常开发、大型项目 | 快速原型、全栈小应用 |
三、Trae SOLO 开发宠物饮水系统

输入项目开发的提示词,尽量详细与明确,特别是硬件模块的型号与接线。
开发基于 ESP32 S3 开发板宠物饮水机控制系统的 Arduino 程序:
1、硬件环境:ESP32S3开发板、继电器模块、LD2402 毫米波模块、SSD1306显示屏模块、WS2812LED灯
2、引脚说明:继电器(GPIO6)。HCLD2402(Tx--GPIO17,Rx--GPIO18)使用串口通信,接收字符串。SSD1306(SCL--GPIO15,SDA--GPIO16)IIC接口,分辨率128x32。WS2812LED灯(GPIO48)。
3、LD2402 毫米波模块,检测宠物接近饮水机时通过继电器打开送水开关,否则关闭送水开发。
4、WS2812LED灯,做为换水提示灯,用户每次断电后认为换了一次水,24小时内显示绿色,48小时内黄色,72小时内显示红色,超过72小时报警模式,红色闪烁。
5、SSD1306显示屏,第一行显示换水时间和继电器状态,第二行显示实时LD2402 数据。字符大小要适配分辨率,每行前面添加简化的示意图标。
完成!我已经为 ESP32 S3 开发板创建了完整的宠物饮水机控制系统 Arduino 程序。

1. 硬件通信架构
- 继电器控制:GPIO6 控制送水开关
- LD2402 毫米波:GPIO17/18 串口通信,接收检测数据
- SSD1306 显示屏:GPIO15/16 I2C 接口,128x32 分辨率
- WS2812 LED:GPIO48 控制状态指示灯
2. 核心功能实现
🔄 LD2402 毫米波检测
- 实时接收串口数据并解析
- 检测到宠物(数据>0)时自动开启继电器
- 未检测到时自动关闭继电器
💧 WS2812 LED 换水提示
|----------|------------|--------|
| 时间范围 | LED 颜色 | 状态 |
| 0-24小时 | 🟢 绿色 | 正常 |
| 24-48小时 | 🟡 黄色 | 提醒 |
| 48-72小时 | 🔴 红色 | 警告 |
| >72小时 | 🔴 红色闪烁 | 报警模式 |
📺 SSD1306 显示屏界面
第一行:
- 图标 + 换水时间(小时:分钟:秒)
- 继电器 ON/OFF 状态
第二行:
- 图标 + LD2402 实时数据
3. 数据持久化
- 使用 ESP32 Preferences 库存储换水时间
- 系统每次上电自动检查存储状态
- 支持
manualWaterChange()函数手动标记换水
4. 代码特点
- ✅ 完整的中文注释
- ✅ 模块化设计,易于维护
- ✅ 串口调试输出(115200波特率)
- ✅ 自动状态管理和LED闪烁控制
- ✅ 适配 128x32 分辨率的优化图标绘制
📁 文件位置
代码已保存至:pet_water_fountain.ino
🔧 需要的库
- Adafruit_GFX
- Adafruit_SSD1306
- Adafruit_NeoPixel
- Preferences (ESP32内置)
如需添加手动换水按钮或串口命令支持,可以进一步完善!
#include <Arduino.h>
#include <HardwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>
#include <Preferences.h>
#define RELAY_PIN 6
#define LD2402_TX 17
#define LD2402_RX 18
#define LED_PIN 48
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_SDA 16
#define OLED_SCL 15
#define LED_COUNT 1
HardwareSerial ld2402Serial(2);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
Preferences preferences;
enum LEDState {
LED_GREEN,
LED_YELLOW,
LED_RED,
LED_RED_BLINK
};
enum RelayState {
RELAY_OFF,
RELAY_ON
};
struct SystemState {
unsigned long lastWaterChangeTime;
bool petDetected;
RelayState relayState;
String ld2402Data;
LEDState ledState;
};
SystemState systemState;
unsigned long getCurrentTimeMillis() {
return millis();
}
unsigned long getTimeSinceWaterChange() {
unsigned long lastChange = preferences.getULong("lastWaterChange", 0);
if (lastChange == 0) {
lastChange = getCurrentTimeMillis();
preferences.putULong("lastWaterChange", lastChange);
}
return getCurrentTimeMillis() - lastChange;
}
void updateWaterChangeTime() {
unsigned long current = getCurrentTimeMillis();
preferences.putULong("lastWaterChange", current);
systemState.lastWaterChangeTime = current;
}
void initLED() {
strip.begin();
strip.show();
strip.setBrightness(255);
}
void setLEDState(LEDState state) {
systemState.ledState = state;
}
void updateLED() {
unsigned long hoursSinceChange = getTimeSinceWaterChange() / 3600000;
if (hoursSinceChange < 24) {
setLEDState(LED_GREEN);
strip.setPixelColor(0, strip.Color(0, 255, 0));
} else if (hoursSinceChange < 48) {
setLEDState(LED_YELLOW);
strip.setPixelColor(0, strip.Color(255, 255, 0));
} else if (hoursSinceChange < 72) {
setLEDState(LED_RED);
strip.setPixelColor(0, strip.Color(255, 0, 0));
} else {
setLEDState(LED_RED_BLINK);
static bool blinkState = false;
blinkState = !blinkState;
if (blinkState) {
strip.setPixelColor(0, strip.Color(255, 0, 0));
} else {
strip.setPixelColor(0, strip.Color(0, 0, 0));
}
}
strip.show();
}
void initRelay() {
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
systemState.relayState = RELAY_OFF;
}
void setRelayState(RelayState state) {
systemState.relayState = state;
digitalWrite(RELAY_PIN, (state == RELAY_ON) ? HIGH : LOW);
}
void controlRelay(bool petDetected) {
if (petDetected) {
setRelayState(RELAY_ON);
} else {
setRelayState(RELAY_OFF);
}
}
void initLD2402() {
ld2402Serial.begin(115200, SERIAL_8N1, LD2402_RX, LD2402_TX);
}
void updateLD2402() {
if (ld2402Serial.available()) {
String line = ld2402Serial.readStringUntil('\n');
line.trim();
if (line.length() == 0) return;
if (line == "OFF") {
systemState.ld2402Data = "OFF";
systemState.petDetected = false;
}
else if (line.startsWith("distance:")) {
systemState.ld2402Data = line.substring(9).toFloat();
systemState.petDetected = true;
}
controlRelay(systemState.petDetected);
}
}
void initDisplay() {
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
return;
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.display();
}
void drawWaterIcon(int x, int y) {
display.fillRect(x, y + 2, 10, 12, SSD1306_WHITE);
display.fillRect(x + 2, y, 6, 2, SSD1306_WHITE);
}
void drawRulerIcon(int x, int y) {
display.fillRect(x, y, 12, 14, SSD1306_WHITE);
display.drawLine(x + 2, y + 2, x + 2, y + 12, SSD1306_BLACK);
display.drawLine(x + 6, y + 2, x + 6, y + 12, SSD1306_BLACK);
display.drawLine(x + 10, y + 2, x + 10, y + 12, SSD1306_BLACK);
}
void updateDisplay() {
unsigned long timeSinceChange = getTimeSinceWaterChange();
unsigned long hours = timeSinceChange / 3600000;
unsigned long minutes = (timeSinceChange % 3600000) / 60000;
unsigned long seconds = (timeSinceChange % (1000 * 60)) / 1000;
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
drawWaterIcon(0, 0);
display.setCursor(16, 0);
char timeStr[16];
sprintf(timeStr, "%02luH-%02luM-%02luS", hours, minutes, seconds);
display.print(timeStr);
display.setCursor(100, 0);
if (systemState.relayState == RELAY_ON) {
display.print("ON ");
} else {
display.print("OFF");
}
drawRulerIcon(0, 16);
display.setTextSize(2);
display.setCursor(16, 16);
if (systemState.ld2402Data.length() > 0) {
if (systemState.ld2402Data == "OFF") {
display.print("OFF");
} else {
display.print("D:");
display.print(systemState.ld2402Data);
}
} else {
display.print("NODATA");
}
display.display();
}
void checkPowerOnReset() {
unsigned long lastChange = preferences.getULong("lastWaterChange", 0);
if (lastChange == 0 || (getCurrentTimeMillis() - lastChange) > 86400000 * 10) {
updateWaterChangeTime();
}
systemState.lastWaterChangeTime = lastChange;
}
void setup() {
Serial.begin(115200);
preferences.begin("petWater", false);
checkPowerOnReset();
initRelay();
initLED();
initLD2402();
initDisplay();
systemState.petDetected = false;
systemState.ld2402Data = "";
Serial.println("Pet Water Fountain System Initialized");
}
void loop() {
unsigned long currentMillis = getCurrentTimeMillis();
updateLD2402();
static unsigned long lastLEDUpdate = 0;
if (currentMillis - lastLEDUpdate >= 500) {
lastLEDUpdate = currentMillis;
updateLED();
}
static unsigned long lastDisplayUpdate = 0;
if (currentMillis - lastDisplayUpdate >= 100) {
lastDisplayUpdate = currentMillis;
updateDisplay();
}
static unsigned long lastSerialUpdate = 0;
if (currentMillis - lastSerialUpdate >= 500) {
lastSerialUpdate = currentMillis;
Serial.print("LD2402: ");
Serial.print(systemState.ld2402Data);
Serial.print(" | Detected: ");
Serial.print(systemState.petDetected ? "Yes" : "No");
Serial.print(" | Relay: ");
Serial.println(systemState.relayState == RELAY_ON ? "ON" : "OFF");
}
}
void manualWaterChange() {
updateWaterChangeTime();
Serial.println("Water changed manually!");
}