传感器与可信数据 / 传感器实验发布 内容更新

花盆缺水提醒:先校准土壤探头,再决定报警阈值

用指定SEN0193记录同土干端、排水湿端和中间样本,生成自己的相对指数;完整程序保留原始值、迟滞与异常状态,不把网络百分比当含水量。

读完能做什么
用指定SEN0193记录同土干端、排水湿端和中间样本,生成自己的相对指数;完整程序保留原始值、迟滞与异常状态,不把网络百分比当含水量。
开始之前
已做按钮消抖,能按GPIO丝印接线、识别电源/共地和LED极性。
所需器材
官方DevKitC-1 v1.1 N8R8、桥USB数据线、DFRobot SEN0193 / 原厂Gravity PH2.0-3P线、WP7113ID红LED、330Ω±5%0.25W、短导线、同种土样和试杯、电脑Python3.9+标准库;无水泵/继电器
版本组合
开发板:官方DevKitC-1 v1.1 / WROOM-1-N8R8;桥UART0/115200,CDC关闭软件:Arduino CLI1.5.1 / esp32:esp32 core3.3.12;完整8MB/QIO/OPI FQBN示例依赖:Arduino内置API;无第三方库;完整sketch.yaml与MIT
适用范围

只适用所列S3板、器件和锁定软件。真实编译与同源主机逻辑已执行;没有传感器、接线、物理动作或长期实验结果。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/sensors-root.md;固定官方/厂家来源与输入SHA验证日期:
构建 / 配置已通过
docs/esp32-evidence/sensors-root-builds.json;实际编译Exit0对象:完整S3 N8R8 FQBN版本:CLI1.5.1 / core3.3.12验证日期:
仿真已通过
docs/esp32-evidence/sensors-root-host.json;同源纯逻辑与合成输入检查,非GPIO/物理模拟验证日期:
本站实板待验证
没有本套板/器件,未接线、上传、观察电压/磁体/土壤或长期运行

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 传感器实验

先得到可解释的提醒,再谈显示百分比

电容探头感受介质变化,ADC只把输出电压变成raw。用两个参考值映射到0..100,得到的是你这套板、土壤与插入条件的相对指数;它不是空气%RH,也没有完成土壤体积含水率标定。换土、施肥/盐分、深度与压实程度,都应重新比较参考记录。

本篇固定SEN0193,不混用廉价同形探头或厂家UNO的520/260。完整程序出厂常量为0,先只采样、灯关;只有你记录自己的端点并重编后才提醒。没有实板时,本站保留空观察表,已做的是实际编译和合成逻辑检查。

做到这里,你应该看到
能说清相对指数、真实含水率和厂家示例值的区别。

参考:[1] DFRobot SEN0193规格:3.3–5.5V、输出0–3.0V、PH2.0-3P[2] 厂家校准:空气/浅水、禁止浸湿顶部、插入与范围[3] 厂家土壤示例:UNO常量、深度与压实影响

02 / 传感器实验

三根探头线接指定ADC,顶部保持干燥

断电把VCC/+接3V3、GND/-接GND、Signal/S接GPIO6/J1-6/ADC1_CH5。原厂Gravity红/黑/黄只是本款线索,先核实物端子标签;板由自己的桥USB供电。传感器官网输出0–3.0V,本例设置12位ADC_11db,不能据此宣称ADC是理想线性电压表。

SEN0193的耐腐蚀探测区不代表顶部元件和插座防水;不越厂家红线,不让水到板、USB或电脑。探头先断电插入土壤,保持固定深度。GPIO4经330Ω到WP7113ID阳极、阴极回地,只有DRY时亮;板载GPIO38 RGB和水泵都不参与。

对象连接限制
SEN0193 VCC/+板3V3只按本组合3.3V供电
SEN0193 GND/-板GND共地
SEN0193 SGPIO6 / J1-6模拟输入,不接5V
外LEDGPIO4→330Ω→阳极;阴极GND可选,只表示软件提示

参考:[1] DFRobot SEN0193规格:3.3–5.5V、输出0–3.0V、PH2.0-3P[2] 厂家校准:空气/浅水、禁止浸湿顶部、插入与范围[6] 固定官方板引脚与N8R8限制[7] WP7113ID厂家极性与电气条件

03 / 传感器实验

先上传零默认,保存三种以上条件的原始样本

下载包的soil_config.h中CAL_DRY与CAL_WET均为0,输出UNCALIBRATED是预期。固定FQBN、桥端口与115200,完整编译/上传命令在README;安装core3.3.12后保留相同板、供电、ADC衰减和分辨率。每20ms取一值,16值四舍五入均值后输出;约320ms只是规则推算,调度和串口会改变实际时序。

可先按厂家做空气与不越红线的浅水方向检查,确认湿润时raw下降;操作后保持顶部干燥。真正花盆端点另取同种土的可接受偏干样本和充分湿润后自由排水样本,避免为取干端损害活植株。相同深度、压实与方向,等连续稳定后各收至少30个raw_mean,再给至少三个中间条件各留30行。

  1. 把串口CSV头和数据另存dry.csv/drained-wet.csv,不含启动文字行。
  2. 每个条件记录土种、供电、深度、状态、时间与日志路径;观察格不以预期填充。
  3. 改变条件后先等稳定,再拿中间样本检查方向和重复插入差异。
做到这里,你应该看到
两端与至少三个中间条件有真实样本;本站当前仍没有这些数据。

参考:[1] DFRobot SEN0193规格:3.3–5.5V、输出0–3.0V、PH2.0-3P[2] 厂家校准:空气/浅水、禁止浸湿顶部、插入与范围[3] 厂家土壤示例:UNO常量、深度与压实影响[4] core3.3.12固定ADC文档:raw/毫伏/分辨率/衰减

04 / 传感器实验

用自己的记录计算端点,再重编配置

电脑执行python3 calibrate.py dry.csv drained-wet.csv。脚本以每组median取整给出CAL_DRY/CAL_WET,并打印n/min/max;不足30样本、近rail、湿端方向反、跨度不足100或两端范围重叠都会拒绝。100 raw只是防退化护栏,不是探头精度。中间样本不单调、范围漂移或噪声大时先排查接线和放置,不能靠改映射掩盖。

把脚本输出的个人实测常量写回soil_config.h,保留原CSV,重新编译并上传。相对指数=100×(dry−raw)/(dry−wet),按代码整数除法向下取整并截到0..100;超端点同时clamped=1且原raw_mean保留。它没有让未经标定的土壤变成体积含水率仪表,也没有自动适配所有植物。

代码 / 配置
python3 calibrate.py dry.csv drained-wet.csv
# 将你自己的输出常量写入soil_config.h,再按README完整FQBN重编/上传
做到这里,你应该看到
配置来自自己的原始数据,源码中的0与主机测试的3000/1000都不当作校准结果。

参考:[2] 厂家校准:空气/浅水、禁止浸湿顶部、插入与范围[3] 厂家土壤示例:UNO常量、深度与压实影响[4] core3.3.12固定ADC文档:raw/毫伏/分辨率/衰减

05 / 传感器实验

迟滞与连续三批,避免灯在阈值附近抖动

本例index≤30时进入DRY、≥40回OK,30<index<40时保持已有状态,正常变化须连续3个16值均值满足条件。首次合法均值和rail后恢复会立即建立初始状态。阈值只是可修改实验规则;应拿实际“需要提醒”的土样记录判断,再观察连续运行,不把它写成植物灌溉建议或测得的响应时延。

校准有效时,均值≤8或≥4087输出SUSPECT_RAIL并关灯,提示检查输入/供电/量程;这不等于完整坏件诊断,真实极端值也可能触发。断开信号后ADC可能浮在正常范围,所以这里不保证断线检测。灯关还可能代表未校准或正常,必须一起读state、raw_mean和clamped。

参考:[4] core3.3.12固定ADC文档:raw/毫伏/分辨率/衰减[5] core3.3.12 ADC实际实现

06 / 传感器实验

完整程序与可重建配置

下载包包括SoilAlert.h、soil_config.h、同名.ino、校准脚本/其测试、同源C++测试、sketch.yaml、README、MIT和空观察表。下方每个文件名必须分开保存;不能把多个文件合成一个.ino,也不要只拷入口遗漏头文件。

主机与固件共用SoilAlert.h,避免测试复刻另一套规则。程序使用有界计数/uint32时间差,没有网络、泵或继电器;低频轮询不承诺任何安全控制能力。

代码 / 配置
// ---- SoilAlert.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
class SoilAlert {
 public:
  enum State { UNCALIBRATED, SUSPECT_RAIL, OK, DRY };
  SoilAlert(uint16_t dry, uint16_t wet):dry_(dry),wet_(wet) {}
  State update(uint16_t raw) {
    if (!calibrated()) { reset(); return state_=UNCALIBRATED; }
    if (raw<=8 || raw>=4087) { reset(); return state_=SUSPECT_RAIL; }
    index_ = raw>=dry_ ? 0 : raw<=wet_ ? 100 : uint16_t((uint32_t(dry_-raw)*100)/(dry_-wet_));
    clamped_=raw>dry_ || raw<wet_;
    if (state_!=OK && state_!=DRY) state_=index_<=30 ? DRY : OK;
    const bool change = state_==OK ? index_<=30 : index_>=40;
    if (!change) { qualifying_=0; return state_; }
    if (++qualifying_>=3) { state_=state_==OK?DRY:OK; qualifying_=0; }
    return state_;
  }
  bool calibrated() const { return dry_<=4086 && wet_>=9 && dry_>wet_ && dry_-wet_>=100; }
  uint16_t index() const { return index_; }
  bool clamped() const { return clamped_; }
 private:
  void reset() {qualifying_=0;index_=0;clamped_=false;}
  uint16_t dry_,wet_,index_=0;uint8_t qualifying_=0;bool clamped_=false;State state_=UNCALIBRATED;
};

// ---- soil_config.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
// Replace with YOUR measured 16-sample means in the same soil, depth, supply,
// ADC resolution and attenuation. Zero defaults intentionally disable alert.
constexpr uint16_t CAL_DRY=0;
constexpr uint16_t CAL_WET=0;

// ---- esp32-plant-moisture-alert.ino ----
// SPDX-License-Identifier: MIT
// SEN0193 @3V3; S->GPIO6/ADC1_CH5, GND->GND; GPIO4 external LED.
#include <Arduino.h>
#include "SoilAlert.h"
#include "soil_config.h"
constexpr uint8_t SOIL_PIN=6, LED_PIN=4;
SoilAlert alert(CAL_DRY,CAL_WET);
void setup() {
  pinMode(LED_PIN,OUTPUT);digitalWrite(LED_PIN,LOW);
  Serial.begin(115200);
  analogReadResolution(12);analogSetPinAttenuation(SOIL_PIN,ADC_11db);
  Serial.println("S04 SEN0193; raw=12bit; means of16; index is relative, not water-content percent");
  Serial.println("ms,raw_mean,index,clamped,state,led_latch");
}
void loop() {
  static uint32_t sampledAt=0,sum=0;
  static uint8_t count=0;
  uint32_t now=millis();
  if(uint32_t(now-sampledAt)>=20) {
    sampledAt=now;sum+=analogRead(SOIL_PIN);++count;
    if(count==16) {
      uint16_t mean=uint16_t((sum+8)/16);sum=0;count=0;
      auto state=alert.update(mean);
      bool led=state==SoilAlert::DRY;digitalWrite(LED_PIN,led?HIGH:LOW);
      Serial.printf("%lu,%u,%u,%d,%s,%d\n",(unsigned long)now,mean,alert.index(),alert.clamped(),
        state==SoilAlert::UNCALIBRATED?"UNCALIBRATED":state==SoilAlert::SUSPECT_RAIL?"SUSPECT_RAIL":state==SoilAlert::DRY?"DRY":"OK",led);
    }
  }
  delay(1);
}

参考:[4] core3.3.12固定ADC文档:raw/毫伏/分辨率/衰减[5] core3.3.12 ADC实际实现

07 / 传感器实验

从状态与raw定位,不先改百分比

一直UNCALIBRATED先查是否仍是零默认、端点方向/跨度及实际重编文件;SUSPECT_RAIL先断电查S/+/GND与GPIO6丝印,回到锁定配置。一直很干不能先当成饱和输入的真实干土。噪声大先固定位置、缩短悬线、检查供电与土壤接触;恢复后重新收样本,不用一次平均掩盖接触不良。

上部进水立即断电,按厂家边界处理并确认干燥,不能把涂层想象成整板防水。重插、换土或更换供电后重新比对端点。上传失败先回桥串口/BOOT-RESET/固定目标,不先更换一套库;保留首错与修复步骤。

如果卡住了
没有实物结果时,不填写observations.csv的observed/pass;先完成方法与软件交付。

参考:[1] DFRobot SEN0193规格:3.3–5.5V、输出0–3.0V、PH2.0-3P[2] 厂家校准:空气/浅水、禁止浸湿顶部、插入与范围[5] core3.3.12 ADC实际实现

08 / 传感器实验

编译、逻辑与土壤观察分别验收

本例CLI1.5.1/core3.3.12完整S3 FQBN实际编译Exit0,编译器报告程序占用319328字节、全局22912字节。主机直接运行同一SoilAlert.h,覆盖默认禁用、反向/窄范围、指数、迟滞/噪声、截断、rail恢复和初始干状态;校准脚本用临时合成CSV检查样本数和拒绝规则。

这些都没有模拟土壤、电压、探头或GPIO,不能当作真实缺水提醒准确度。实板要依次补原始端点/中间条件、重编版本、迟滞附近稳定性与恢复记录,保持原始值和时间。下一篇门磁会继续区分“当前状态”与“发生过几次”。

代码 / 配置
clang++ -std=c++17 -Wall -Wextra -Werror tests/soil_test.cpp -o /tmp/s04-soil-test
/tmp/s04-soil-test
python3 calibrate_test.py

参考:[4] core3.3.12固定ADC文档:raw/毫伏/分辨率/衰减[5] core3.3.12 ADC实际实现

BACK TO THE SOURCE

资料来源与版本说明

已实读固定core源码、板级引脚及具体厂家资料;物理实验与构建/合成输入检查分开记录。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. DFRobot SEN0193规格:3.3–5.5V、输出0–3.0V、PH2.0-3P
  2. 厂家校准:空气/浅水、禁止浸湿顶部、插入与范围
  3. 厂家土壤示例:UNO常量、深度与压实影响
  4. core3.3.12固定ADC文档:raw/毫伏/分辨率/衰减
  5. core3.3.12 ADC实际实现
  6. 固定官方板引脚与N8R8限制
  7. WP7113ID厂家极性与电气条件