门现在开着,与已经开过几次,是两个变量
门磁触点开不代表刚刚发生一次开门。若板上电时门本已开,应该先建立初始状态,再开始记录后续变化;否则每次重启都会伪造一个事件。本例用独立state/age/opens/closes日志,把INITIAL与OPENED/CLOSED分开。
这里只做桌面低压回路实验,固定PID375和短线,不将二线接点称作安防设备。本站没有实际磁体/板,30周期、开机状态和线缆注入均给出完整待执行方法;已完成的软件证据不能代替这些动作。
参考:[1] Adafruit PID375常开、磁体距离参考、2021线端修订[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead
确认这是无VCC的常开干接点
PID375一半是干簧开关,另一半是磁体。厂家描述磁体靠近时闭合;13mm描述与15mm技术栏只是厂家参考,实际安装方向、间隙和周边材料必须用通断档与本机读数确认,不能承诺这个距离内一定可靠。2021后线端易接面包板,未向其他有源模块外推。
断电把任意一线接GPIO5/J1-5,另一线GND,内部INPUT_PULLUP。触点开时HIGH、闭时LOW。可选GPIO4经330Ω到WP7113ID阳极、阴极GND,已接受CLOSED时亮。厂家触点额定200VDC不代表GPIO能接高压;全篇仅3.3V域,不给门磁另接5V/VCC。
| 连接 | 解释 |
|---|---|
| 门磁一线→GPIO5 | INPUT_PULLUP;按GPIO丝印 |
| 门磁另一线→GND | 两线无极性,共地 |
| GPIO4→330Ω→LED阳极,阴极GND | 可选闭合指示;GPIO38 RGB不用 |
参考:[1] Adafruit PID375常开、磁体距离参考、2021线端修订[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead[3] 固定官方S3板引脚:GPIO4/5和桥接口[4] Kingbright WP7113ID极性与电气条件
稳定30ms后先INITIAL,计数仍是零
首次采样仅建立候选值,UNKNOWN持续到它连续稳定至少30ms。若中途变化就重置稳定起点;第一次接受后输出INITIAL,无论开或闭都不增加opens/closes。之后只有接受到与当前状态不同的稳定值,才产生OPENED或CLOSED并增加对应一项。
每秒状态行给出电气状态和age_ms,年龄从最近一次被接受状态开始,并非门运动的精确时刻。delay(1)是让出执行机会,采样/串口不保证硬实时,极短变化可能漏采;uint32差跨一次millis回绕有效,长时间计数溢出也会回绕。RAM计数RESET/断电归零,不冒充持久历史。
参考:[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead[5] core3.3.12 millis实现与uint32返回
完整记录器:模型与入口各有一个文件
把DoorContact.h和同名.ino保存在下载包同一工程目录,保留sketch.yaml的完整板选项。没有第三方库。桥UART0/115200、CDC关闭,不等待串口连接才开始采样;即使不看日志,模型也会更新。
开机LED先LOW,首次确认闭合后才亮,避免UNKNOWN时给出已闭合的错觉。灯的锁存值只反映程序已接受状态,既非光学结果也不证明磁体位置;真正记录仍须查state与两项计数。
// ---- DoorContact.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
class DoorContact {
public:
enum Event { NONE, INITIAL, OPENED, CLOSED };
Event update(bool electricalOpen, uint32_t now) {
if (!started_) { started_=true; candidate_=electricalOpen; changed_=now; }
if (electricalOpen!=candidate_) { candidate_=electricalOpen; changed_=now; }
if (uint32_t(now-changed_)<30) return NONE;
if (!known_) { known_=true; open_=candidate_; since_=now; return INITIAL; }
if (candidate_==open_) return NONE;
open_=candidate_; since_=now;
if(open_) { ++opens_; return OPENED; }
++closes_; return CLOSED;
}
bool known() const { return known_; }
bool open() const { return open_; }
uint32_t opens() const { return opens_; }
uint32_t closes() const { return closes_; }
uint32_t age(uint32_t now) const { return known_?uint32_t(now-since_):0; }
private:
bool started_=false,known_=false,candidate_=true,open_=true;
uint32_t changed_=0,since_=0,opens_=0,closes_=0;
};
// ---- esp32-reed-switch-door-state.ino ----
// SPDX-License-Identifier: MIT
// S05: PID375 two-wire dry contact; GPIO5 pull-up, other wire GND.
#include <Arduino.h>
#include "DoorContact.h"
constexpr uint8_t CONTACT_PIN=5, LED_PIN=4;
DoorContact contact;
void setup() {
pinMode(CONTACT_PIN,INPUT_PULLUP);
pinMode(LED_PIN,OUTPUT); digitalWrite(LED_PIN,LOW);
Serial.begin(115200);
Serial.println("S05 core=3.3.12 PID375 GPIO5; HIGH means electrical open OR broken wire, not a security diagnosis");
}
void loop() {
static uint32_t reportedAt=0;
const uint32_t now=millis();
const auto event=contact.update(digitalRead(CONTACT_PIN)==HIGH,now);
if(event!=DoorContact::NONE) {
digitalWrite(LED_PIN,contact.open()?LOW:HIGH);
Serial.printf("event=%s electrical=%s opens=%lu closes=%lu at=%lu\n",
event==DoorContact::INITIAL?"INITIAL":event==DoorContact::OPENED?"OPENED":"CLOSED",
contact.open()?"OPEN":"CLOSED",(unsigned long)contact.opens(),
(unsigned long)contact.closes(),(unsigned long)now);
}
if(uint32_t(now-reportedAt)>=1000) {
reportedAt=now;
Serial.printf("state=%s age_ms=%lu opens=%lu closes=%lu; counters reset on reboot\n",
!contact.known()?"UNKNOWN":contact.open()?"CONTACT_OPEN":"CONTACT_CLOSED",
(unsigned long)contact.age(now),(unsigned long)contact.opens(),(unsigned long)contact.closes());
}
delay(1);
}
参考:[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead[3] 固定官方S3板引脚:GPIO4/5和桥接口[5] core3.3.12 millis实现与uint32返回
两次开机检查与30个完整周期
按README完整FQBN编译,替换真实桥端口上传,115200打开monitor。先磁体靠近、RESET,应在稳定窗口后得到INITIAL/CLOSED且计数0;再磁体移远、RESET,另存INITIAL/OPEN且计数0。不能把一次启动输出当两种情况都已验证。
重新以CLOSED初始状态开始,做30个“移远→靠近”完整周期,每个位置至少保持约0.2秒;预期opens=30/closes=30。开和闭两类边沿加起来60,不把周期数与事件数混写。每个位置另保持2秒应无新增;记录日期、初态、实际计数和日志路径,非实测表保持空白。
- 先通断档确认磁体靠近/移远确实改变触点。
- 分开保存初始开、初始闭两轮RESET日志。
- 从初始闭做30完整周期,并检查额外保持不增。
- 观察LED只作辅助,核对串口事件与独立计数。
- 做到这里,你应该看到
- INITIAL不计事件,30周期的两项计数均30;本站硬件结果仍待测。
参考:[1] Adafruit PID375常开、磁体距离参考、2021线端修订[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead[3] 固定官方S3板引脚:GPIO4/5和桥接口
信号断开实验:展示边界,不编造断线诊断
保持磁体靠近时断开信号回路,电平升HIGH,稳定后应只增加一次OPENED;恢复后一次CLOSED。建议先断电装低压支路开关,再上电操作,避免带电拔线误碰电源。若本来已是OPEN,断线没有新电平,不应期待额外事件。
这也说明二线限制:断线与磁体远离同样可读HIGH,短接地与磁体靠近同样LOW。当前代码无独立FAULT态,不能辨别所有导线松动、触点失效或短路。长线/安防需要另做受监测回路及电气设计;去抖只处理稳定时间,不能解决一切线缆问题。
- 如果卡住了
- 故障实验后断电恢复最小接线,保存每个条件实际日志。
参考:[1] Adafruit PID375常开、磁体距离参考、2021线端修订[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead
先测触点和地,再看模型
一直OPEN:先断电测门磁靠近能否连通,查GND、GPIO5丝印和面包板接点,再核磁体位置。一直CLOSED:查同一排误短接或GPIO5接地,移开磁体再读。计数过多先查安装振动、线接触与停留时间;真实反复开合不能靠加大消抖假装稳定。
上传失败回到桥USB数据线、实际端口、BOOT/RESET和固定FQBN,保留首错;看不到日志核115200及CDC配置,不先换库。恢复后先重跑初始状态,再重跑30周期,既有RAM计数不当旧历史。
参考:[1] Adafruit PID375常开、磁体距离参考、2021线端修订[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead[3] 固定官方S3板引脚:GPIO4/5和桥接口
同源逻辑已检查,物理可靠性仍需补记录
CLI1.5.1/core3.3.12、完整8MB/QIO/OPI S3 FQBN实际编译Exit0,原命令与源摘要在sensors-root-builds.json。主机直接用同一个DoorContact.h检查29/30ms边界、初始开/闭、反弹、保持、30周期、状态年龄与uint32回绕;没有复制一套测试模型。
主机合成电平不模拟磁场、GPIO、电气和轮询调度。本站observations.csv只含表头,实板与长期稳定性未测;不将函数正确推成“门磁永不丢事件”。下一步可继续串口分帧,把日志协议也做成有边界的输入。
clang++ -std=c++17 -Wall -Wextra -Werror tests/contact_test.cpp -o /tmp/s05-contact-test
/tmp/s05-contact-test参考:[2] core3.3.12 GPIO:INPUT_PULLUP与digitalRead[5] core3.3.12 millis实现与uint32返回
资料来源与版本说明
已实读固定core源码、板级引脚及具体厂家资料;物理实验与构建/合成输入检查分开记录。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。