押题八:Touch 密码门禁

命中方向:Touch 自校准、按键中断、二符号密码、输错锁定、解锁计时、RGB、蜂鸣器、NVS。

模拟任务书

用 Touch 和按键构成二符号密码输入:Touch 表示 1,按键短按表示 0,按键长按确认。默认密码为 1011。连续三次错误后锁定 8 秒;正确后 GPIO21 输出高电平 5 秒并自动重新上锁。

  1. Touch 必须启动自校准并连续有效 50 ms 才接收。
  2. 密码长度允许 2–16 位,通过串口修改后写入 NVS。
  3. 锁定、解锁、输入和错误状态使用 RGB/蜂鸣器区分。
  4. 输入超过 16 位应清空并报错。

建议评分点

评分项分值
Touch 校准和抗误触15
按键中断、短按与长按15
二符号密码输入与验证20
连续三错锁定 8 秒15
解锁输出与自动上锁10
密码修改及 NVS 保存15
边界错误和状态提示10

总分:100

直接使用

工程:arduino_pio/mock_08_touch_access。GPIO4 接导电片,按键接 GPIO15 与 GND,GPIO21 可接 LED 或驱动电路。

ENTER 1011
PASS 11001
ENTER 11001
LOCK
CLEAR
TOUCH CAL
STATUS

主要修改:src/task_logic.cpp中的 appendBit()verifyEntry()tick()

高概率变题

  • 密码改为 Touch 短触/长触序列。
  • 门禁改为布防/撤防或模式选择。
  • 错误次数、锁定时间和解锁时间由串口设置。
  • 要求掉电保存最后锁定状态或开门次数。

现场修改指南:密码值和长度必须成对

当前输入规则:按键短按输入 0,Touch 输入 1,长按确认;默认密码 1011,三错锁 8 秒,正确解锁 5 秒。现场只是改密码时,优先使用 PASS命令,不必改源码。

题目一句话对应哪里

题目关键词函数/变量作用
密码文本是否合法parseBits()只接受 2..16 位的 0/1
默认密码/解锁时间beginTask()设置 user1/user2/user4默认值
输入一位appendBit()把 0/1 加到当前输入末尾
核对密码/失败锁定verifyEntry()正确解锁,错误累计,达到次数进入 LOCKOUT
按键/Touch 分别代表什么shortPress()touchChanged()当前分别输入 0 和 1
确认longPress()调用 verifyEntry()
自动上锁/锁定结束tick()比较截止时刻并控制输出
改密码command()PASS设置并立即保存

变量翻译

代码含义
user1打包成整数的密码值,例如 1011 是 0b1011
user2密码长度,例如 1011 长度是 4
user4正确后保持解锁的毫秒数
entered_bits本次尚未确认的输入值
entered_length本次已经输入多少位
failed_attempts连续错误次数
state_deadline_ms自动上锁或错误锁定结束的时刻

最安全:不改代码也能改密码

现场要求操作
密码改 11001PASS 11001,命令会立即保存
测试密码ENTER 11001
立即上锁LOCK
清除输到一半的内容CLEAR
Touch 环境变化无人触碰时执行 TOUCH CAL

前导 0 陷阱:密码 0011 和 11 的整数值相同,但长度不同,所以必须同时保存 user1user2

四种高概率变题:可直接照着改

示例 A:源码默认密码改为 11001

beginTask()中找到:

app.config().user1 = 0b1011;
app.config().user2 = 4;

改为:

app.config().user1 = 0b11001;
app.config().user2 = 5;

烧录后如果 NVS 已保存旧密码,新默认值不会覆盖有效配置。输入 FACTORY后,新默认值才会生效。

示例 B:交换输入,按键输入 1,Touch 输入 0

替换范围:完整替换三个输入回调;确认动作仍是长按。

void shortPress(contest::ContestApp &app) {
    appendBit(app, 1);
}

void longPress(contest::ContestApp &app) {
    verifyEntry(app);
}

void touchChanged(contest::ContestApp &app, bool active) {
    if (active) appendBit(app, 0);
}

同步修改:printHelp()中的 TOUCH=1、SHORT_BUTTON=0 也要交换,防止验收人员按错误规则输入。

示例 C:五次错误锁 15 秒,正确后解锁 10 秒

1. 在 beginTask()中把:

if (app.config().user4 == 0) app.config().user4 = 5000;

改为:

if (app.config().user4 == 0) app.config().user4 = 10000;

2. 在 verifyEntry()错误分支中把:

if (failed_attempts >= 3) {
    app.setMode(LOCKOUT);
    state_deadline_ms = millis() + 8000;
    app.emit("ACCESS LOCKOUT MS=8000");
}

改为:

if (failed_attempts >= 5) {
    app.setMode(LOCKOUT);
    state_deadline_ms = millis() + 15000;
    app.emit("ACCESS LOCKOUT MS=15000");
}

如果板子里已有旧的 user4=5000,修改默认值后要执行 FACTORY,否则仍使用旧保存值。

示例 D:正确后保持解锁,必须收到 LOCK 才重新上锁

修改范围:tick()开头删除下面整个自动上锁判断:

if (app.config().mode == UNLOCKED &&
    static_cast<int32_t>(now_ms - state_deadline_ms) >= 0) {
    app.setMode(LOCKED);
    app.emit("ACCESS AUTO_LOCK");
}

保留 LOCKOUT 到时恢复的第二个判断。串口 LOCK命令仍可强制上锁。

改完后的最短验收

  1. 先用 ENTER测试正确密码,再用实体 Touch/按键输入同一序列。
  2. 少一位、多一位和错误一位都必须拒绝,且每次确认后输入缓存清空。
  3. 连续输错到规定次数,锁定期间任何输入都应回复 ERR LOCKOUT
  4. 等待锁定结束后再次输入正确密码,GPIO21 应按规定时间拉高。
  5. 修改密码并断电重启,使用新密码仍能解锁。
  6. 输入超过 16 位,必须清空并报错,不能覆盖内存。

不要改:parseBits()的长度保护和带符号截止时间比较。除非题目明确允许更长密码,否则不要把 16 位上限随意增大。