封闭阳台20盆观叶自动浇花方案(ESP32离线闭环,350元)

mgm3000 🌱 破土(0) | 版块: 休闲娱乐 | 发布时间: 2026-09-29 09:26:44.118009 | 浏览: 8 | 回复: 1

家里一个封闭式生活阳台,三层移动花架12~13盆+地面6~7盆,全是龟背竹、蔓绿绒、合果芋、彩叶芋这类天南星科观叶,之前人工浇水顾不过来,顶层中层都有缺光烂叶的。最近做了套自动浇花系统,全程离线闭环,不依赖Wi-Fi和云,断网照样跑,预算350元。把方案发出来,同款阳台的可以抄。

现场约束(决定布管方式)

  • 头顶有晾衣杆 → 管线一律不上顶
  • 花架带脚轮会挪 → 架侧用快接三通,挪架前拔管
  • 左侧有跑步机 → 作业通道在花架左侧+盆间窄缝,主管贴地走这条道

水路

[12L储水桶] 放跑步机旁角落
     │ 5V潜水泵(1L/min)
     ▼
[单向阀+40目过滤器]
     │ 4/7主管,沿左侧走道贴地走
     ├── 花架左侧 → 快接三通×3 → 三路立管上三层
     │     顶层4滴头 / 中层4滴头 / 底层4滴头
     └── 沿窗边地面走 → 快接三通×2
           地面左3滴头 / 地面右4滴头
  • 滴头用 2L/h 可调式,塞土面1cm
  • 12L桶满水够全阳台浇2~3轮,3~4天补一次
  • 快接头(对插式,2元/个)是核心:花架随时挪,拔了插回就行

电路(全在一个防水盒里)

  • ESP32-DevKitC(25元)
  • 1路5V继电器控水泵
  • 土壤湿度传感器×2(模拟量版):
  • 1号插顶层小盆(干得快,代表上限)
  • 2号插地面大龟背竹(干得慢,代表下限)
  • 蜂鸣器+LED报警,浮球水位开关防干抽
  • 12V电源给泵,5V给ESP32

控制逻辑(烧进ESP32)

每30分钟采样两路湿度:
if 湿度1 < 25% 且 距上次浇水 > 6h:
    开泵90秒(20滴头全开,约3L,全阳台一轮)
elif 湿度1 < 35% 且 湿度2 < 30%:
    开泵45秒(半轮)
浮球低水位 → 停泵+蜂鸣器响3次
连续3次开泵湿度不下降 → 判定泵堵/断水 → 报警

白天阈值放宽5%、阴天收紧(加个光照电阻就能做,不加也行)。阈值先跑一周再微调。

采购清单(约350元)

件 规格 数量 价
ESP32-DevKitC v4 带天线 1 25
5V潜水泵 1L/min带出水嘴 1 18
12L储水桶 食品级 1 20
4/7主管 PE管15m 1 15
4mm支管 20m 1 10
2L/h可调滴头 20 20
快接三通4/7 10 10
快接头(对插) 12 12
单向阀4/7 1 5
40目过滤器 内螺纹 1 6
土壤湿度传感器 模拟量 2 20
5V继电器模块 1路 1 10
12V 2A电源 给泵 1 15
5V 3A电源 给ESP32 1 12
蜂鸣器+LED+按键 1套 8
PVC防水接线盒 120mm 1 10
杜邦线/扎带/卡扣 1套 25
浮球开关 25mm 1 8
余量 备用滴头补管 ~100

淘宝搜"滴灌套装 家用 20滴头"有整包80~120元,比单买便宜,电子件另买。

安装顺序(一个下午)

  1. 桶放跑步机旁,泵装桶里,出水接主管
  2. 主管沿左走道贴地走到底,折回窗边
  3. 花架侧3个快接三通+立管上三层,滴头插土
  4. 地面5个滴头
  5. 接线盒里接好ESP32,烧固件
  6. 手动触发一轮,检查每个滴头出水是否均匀
  7. 跑3天,调阈值

几个踩坑提醒

  • 湿度传感器模拟量版精度一般,够用但两路对照比单路可靠
  • 滴头选2L/h别选4L/h,观叶植物要慢灌,快灌容易冲土
  • 40目过滤器别省,阳台水里有灰尘藻类,一个月就能堵滴头
  • 快接头要买同规格(4/7配4/7),混4mm和4/7对插会渗水
  • 天南星科统一逻辑没问题,但如果混了多肉,多肉要单独分区,阈值和频率都不一样

固件逻辑都是现成的,需要代码的在底下扣1,我整理一份发出来。同款封闭阳台+观叶为主的基本都能直接抄。

💬 回复列表(1)
M
mgm3000 2026-09-29 13:34:08.592305

扣1的来了,固件 v1.0 如下。Arduino IDE 直接编译烧录,板子选 ESP32 Dev Module,端口选 COM 口即可,不需要改库(只用标准库)。

接线速查: - 湿度传感器1号(顶层小盆)→ A34 - 湿度传感器2号(地面大盆)→ A35 - 光照电阻(可选,不接恒按白天)→ A36 - 继电器 IN(水泵12V)→ GPIO26 - 蜂鸣器 → GPIO27,LED → GPIO28 - 浮球水位开关 → GPIO5(接GND,内部上拉)

两个校准项(烧录后串口看 boot 行): 1. 把两个传感器分别插湿土和悬空,把 ADC 值填进 soilCal1/soilCal2(格式 [湿土, 空气]) 2. 光照传感器白天/黑夜各读一次,取中间值填 LIGHT_DAY

```cpp / * 封闭阳台自动浇花固件 v1.0 * ESP32-DevKitC v4 · 离线闭环 · 无 Wi-Fi 依赖 * * 接线: * 土壤湿度传感器1号(顶层小盆) A0 (VCC 5V, GND) * 土壤湿度传感器2号(地面大盆) A1 * 光照电阻(可选) A2 (10kΩ分压, 接GND) * 继电器 IN GPIO26 (水泵, 12V电源独立) * 蜂鸣器 GPIO27 (有源, GND共地) * LED GPIO28 * 浮球水位开关 GPIO5 (接GND, 内部上拉; 低水位=断开=HIGH) * * 逻辑: * 每30分钟采样。 * 湿度1 < DRY_HARD 且 距上次>6h → 全轮 90s * 湿度1 < DRY_MID 且 湿度2<DRY_MID → 半轮 45s * 浮球低水位 / 泵堵 → 报警 /

// ===== 可调参数 ===== const uint8_t SOIL1_PIN = 34; // 顶层小盆(干得快) const uint8_t SOIL2_PIN = 35; // 地面大盆(干得慢) const uint8_t LIGHT_PIN = 36; // 光照(可选, 不接则恒白天) const uint8_t PUMP_PIN = 26; const uint8_t BUZZ_PIN = 27; const uint8_t LED_PIN = 28; const uint8_t WATER_PIN = 5;

const int DRY_HARD = 25; // 湿度百分比, 低于此 6h → 全轮 const int DRY_MID = 35; // 两路都低于此 → 半轮 const int DAY_ADJ = 5; // 白天阈值放宽(数字变大=更难触发) const int NIGHT_ADJ = 0; // 夜间阈值收紧 const int LIGHT_DAY = 3200; // ADC>此值判白天(10k分压, 实测调)

const uint32_t CHECK_MS = 30ULL601000; // 30分钟采样 const uint32_t MIN_GAP = 6ULL36001000; // 两次浇水最小间隔6h const uint16_t FULL_MS = 90; // 全轮秒数 const uint16_t HALF_MS = 45; // 半轮秒数

// ===== 状态 ===== uint32_t lastWater = 0; uint8_t pumpFailCount = 0; bool lowWater = false; int soilCal1[2] = {0, 4095}; // 传感器校准: [湿土, 空气] 首跑后改 int soilCal2[2] = {0, 4095};

// ===== 工具 ===== int readADC(uint8_t pin) { int s = 0; for (int i = 0; i < 8; i ) { s = analogRead(pin); delay(2); } return s / 8; }

// 湿度百分比: 校准区间内线性, 未校准时用默认 0~4095 int soilPct(int adc, int cal[2]) { int lo = cal[0], hi = cal[1]; if (hi - lo < 100) return 50; int p = (hi - adc) * 100 / (hi - lo); return constrain(p, 0, 100); }

void beep(uint16_t n, uint16_t ms = 200) { for (int i = 0; i < n; i ) { digitalWrite(BUZZ_PIN, HIGH); delay(ms); digitalWrite(BUZZ_PIN, LOW); delay(ms); } }

void pumpOn() { digitalWrite(PUMP_PIN, HIGH); } void pumpOff() { digitalWrite(PUMP_PIN, LOW); }

// 水泵运行 N 秒, 每5秒闪一次LED void runPump(uint16_t secs) { pumpOn(); uint32_t t0 = millis(); while (millis() - t0 < (uint32_t)secs * 1000) { digitalWrite(LED_PIN, (millis() / 500) % 2); if (digitalRead(WATER_PIN) == HIGH) { // 中途缺水 pumpOff(); beep(5); lowWater = true; return; } delay(50); } pumpOff(); digitalWrite(LED_PIN, LOW); }

void setup() { Serial.begin(115200); pinMode(PUMP_PIN, OUTPUT); pinMode(PUMP_PIN, LOW); pinMode(BUZZ_PIN, OUTPUT); pinMode(BUZZ_PIN, LOW); pinMode(LED_PIN, OUTPUT); pinMode(LED_PIN, LOW); pinMode(WATER_PIN, INPUT); digitalWrite(WATER_PIN, HIGH); // 上拉 analogReadResolution(12);

// 传感器预热(上电需稳定几秒) delay(2000); int a1 = soilPct(readADC(SOIL1_PIN), soilCal1); int a2 = soilPct(readADC(SOIL2_PIN), soilCal2); Serial.printf("boot: soil1=%d%% soil2=%d%%\n", a1, a2);

if (digitalRead(WATER_PIN) == HIGH) { lowWater = true; Serial.println("WARN: 储水低"); beep(3); } }

void loop() { static uint32_t next = millis() 60000; // 首采1分钟后 if (millis() < next) { delay(200); return; } next = millis() CHECK_MS;

int raw1 = readADC(SOIL1_PIN); int raw2 = readADC(SOIL2_PIN); int pct1 = soilPct(raw1, soilCal1); int pct2 = soilPct(raw2, soilCal2);

bool day = (readADC(LIGHT_PIN) > LIGHT_DAY); int adj = day ? DAY_ADJ : NIGHT_ADJ; int hard = constrain(DRY_HARD adj, 0, 100); int mid = constrain(DRY_MID adj, 0, 100);

Serial.printf("t=%lus soil1=%d%%(%d) soil2=%d%%(%d) %s gap=%lus\n", millis()/1000, pct1, raw1, pct2, raw2, day ? "day" : "night", (lastWater ? (millis()-lastWater)/1000 : 0));

// 缺水检查 if (digitalRead(WATER_PIN) == HIGH) { if (!lowWater) beep(3); lowWater = true; return; } lowWater = false;

uint32_t gap = lastWater ? (millis() - lastWater) : UINT32_MAX;

if (pct1 < hard

请登录后发表回复