自制智能门锁(NFC+小程序远程开锁)


这是一次真实可复现的智能门锁改造记录。起因很简单:传统门锁太麻烦,而市面上的智能门锁要么太贵、要么不开放。

所以我决定:

  • 用 ESP8266 自己做一把
  • 支持 NFC 本地刷卡
  • 支持 微信小程序远程开锁
  • 成本可控、逻辑透明、代码完全可读

一、这个项目能做什么?

在开始之前,先明确目标功能:

  • 📡 刷 IC 卡开锁(NFC)

  • 📱 微信小程序远程开锁(MQTT)

  • 🔁 自动回位关锁(防止舵机空转)

  • ⚙️ 非阻塞网络设计,刷卡与联网互不影响

适用场景:老旧门锁、宿舍门、出租屋、工作室内门等。


二、整体方案与架构

先看整体结构,这一步能帮你快速判断这个方案是否适合你。


┌────────┐        NFC         ┌──────────┐
   IC卡     ────────────────▶    RC522    
└────────┘                    └────┬─────┘
                                   │ SPI
┌──────────────┐   WiFi + MQTT ┌───▼────────┐      PWM        ┌─────────┐
  微信小程序      ◀────────────▶   ESP8266      ─────────────▶   MG90S    
└──────────────┘                 D1 UNO R3                      舵机     
                               └────────────┘                 └─────────┘

核心思想只有一句话:

ESP8266 作为中枢,NFC 与 MQTT 都只是“开门信号源”。

三、准备

1.硬件

硬件说明                  
D1 UNO R3ESP8266 主控(自带 WiFi)图示
MFRC-522  RFID IC 卡模块(⚠️3.3V)图示
MG90S    180° 金属齿轮舵机        图示
杜邦线公对母若干图示

⚠️ RC522 只能接 3.3V,接 5V 很容易直接烧模块。

2.硬件连接

这是最容易出问题的一步,直接给出完整对照表。

D1 UNO R3舵机      RC522    
GND      黑线      GND      
5V        红线      —        
D10      黄线(PWM)—        
3.3V      —      VCC      
D15      —      RST      
D14      —      SDA / SS
D11      —      MOSI    
D12      —      MISO    
D13      —      SCK      

舵机颜色不统一,以商品说明为准(常见:红=VCC,黑=GND,黄=信号)。 IC卡感应模块的IRQ不用连接

3.软件 IDE

安装IDE

Arduino IDE 下载地址 ,用于将控制代码写入开发板

安装ESP8266程序

参考# Arduino IDE 离线安装ESP8266教程

安装库

安装串口驱动

开发板与IDE通讯,IDE写入程序到开发板,如果是买的和我同款开发板,可以下载以下地址驱动,如果是其他开发板,一般购买详情页有相关资料的下载地址,可以下载。

通过网盘分享的文件:ch340驱动 链接: https://pan.baidu.com/s/1jEzqaJpXa2Sio2-H7DcaVA?pwd=ngbr 提取码: ngbr

创建远程控制账号
  1. 打开小程序

  2. 创建一个锁,然后点击【使用教程】

  3. 在教程页面复制账号和密码

  4. 修改代码

    const char* mqtt_broker = "mqtt.zkphp.com";
    const int mqtt_port = 2883;	
    const char* mqtt_user = "小程序获取的用户名";	
    const char* mqtt_password = "小程序获取密码";

写入程序

必须完成以上所有步骤。然后用数据线将开发板连接到电脑上。

  1. 选择开发板

  2. 选择串口

  3. 打开串口监视器,然后设置比特率为9600

  4. 复制完整代码 点击查看完整代码

  5. 修改代码中WiFi密码

// WiFi 配置

const char* ssid = "WiFi账号";

const char* password = "密码";
  1. 上传代码

    如果在串口监视器中见到以下信息,则表示WiFi和mqtt远程控制连接成功。

    点击微信小程序解锁按键,串口监视器中见到以下信息,则表示mqtt远程控制成功。 ic卡贴近感应模块,串口监视器中见到以下信息,则表示IC卡感应模块功能正常 可以将uid添加到代码中,将IC卡设置为授权卡片。代码如下:

    const uint64_t ALLOWED_IDS[] = {0x03C09EEE, 0x1D193DE0};

完整代码

#include <Servo.h>

#include <ESP8266WiFi.h>

#include <PubSubClient.h>

#include <SPI.h>

#include <MFRC522.h>

  

// ========== 配置 ==========

const uint64_t ALLOWED_IDS[] = {0x03C09E22};//写死的IC卡

const size_t NUM_ALLOWED = sizeof(ALLOWED_IDS) / sizeof(ALLOWED_IDS[0]);

  

// WiFi 配置

const char* ssid = "你的WiFi名";

const char* password = "WiFi密码";

  

// MQTT 配置

const char* mqtt_broker = "mqtt.zkphp.com";

const int mqtt_port = 2883;

const char* mqtt_user = "小程序获取的用户名";

const char* mqtt_password = "小程序获取密码";

  

// ========== 全局对象 ==========

WiFiClient espClient;

PubSubClient client(espClient);

  

MFRC522 mfrc522(D14, D15);

  

Servo doorServo;

const int SERVO_PIN = D10;

  

char command_topic[128];

  

// ========== 状态定义 ==========

enum DoorState {

  DOOR_IDLE,

  DOOR_OPEN,

  DOOR_UNAUTHORIZED

};

  

DoorState doorState = DOOR_UNAUTHORIZED;

unsigned long doorOpenTime = 0;

bool needOpen = false;

  

#define SERVO_CLOSE 1020

#define SERVO_OPEN  1500

  

// ========== 计时器变量 (用于非阻塞) ==========

unsigned long lastMqttRetry = 0;

const unsigned long MQTT_RETRY_INTERVAL = 5000; // MQTT重连间隔5秒

  

unsigned long lastWifiCheck = 0;

const unsigned long WIFI_CHECK_INTERVAL = 1000; // WiFi状态检查间隔1秒

  

// ========== 函数声明 ==========

void openDoor();

void closeDoor();

void doorServoInit();

void mqttCallback(char* topic, byte* payload, unsigned int length);

void handleNetwork(); // 统一的网络处理函数

void readCard();

  

// ========== MQTT 回调 ==========

void mqttCallback(char* topic, byte* payload, unsigned int length) {

  Serial.print("收到消息 [");

  Serial.print(topic);

  Serial.print("] : ");

  

  String msg = "";

  for (int i = 0; i < length; i++) {

    msg += (char)payload[i];

  }

  Serial.println(msg);

  

  if (msg == "ON" && doorState != DOOR_OPEN) {

    needOpen = true;

  }

}

  

// ========== 网络管理 (核心修改部分) ==========

void handleNetwork() {

  unsigned long now = millis();

  

  // 1. 检查 WiFi 连接状态

  if (now - lastWifiCheck >= WIFI_CHECK_INTERVAL) {

    lastWifiCheck = now;

    // 如果 WiFi 未连接,只输出状态,不阻塞。

    // ESP8266 在 STATION 模式下会自动在后台尝试重连,不需要在 loop 中频繁调用 begin

    if (WiFi.status() != WL_CONNECTED) {

      // 可以在这里加一个长时间未连接的判断,例如超过30秒重置WiFi,目前保持简单

      // Serial.println("WiFi 连接中...");

      return; // WiFi 没好,就不用试 MQTT 了

    }

  }

  

  // 如果 WiFi 没连上,直接退出,不执行后面 MQTT 逻辑

  if (WiFi.status() != WL_CONNECTED) return;

  

  // 2. 检查 MQTT 连接状态

  if (!client.connected()) {

    // 使用非阻塞计时器,每隔 5 秒尝试一次连接

    if (now - lastMqttRetry > MQTT_RETRY_INTERVAL) {

      lastMqttRetry = now;

      Serial.println("尝试连接 MQTT Broker...");

  

      // client.connect 可能会有短暂阻塞 (约1-2秒超时),但在重试间隔内不会影响 NFC

      if (client.connect("door-hook", mqtt_user, mqtt_password)) {

        Serial.println("✅ MQTT 连接成功");

        client.subscribe(command_topic);

      } else {

        Serial.print("❌ MQTT 连接失败, rc=");

        Serial.print(client.state());

        Serial.println(" (将在5秒后重试)");

      }

    }

  } else {

    // 只有连接成功时才调用 loop,处理接收到的消息

    client.loop();

  }

}

  

// ========== 舵机控制 ==========

void openDoor() {

  if (doorState == DOOR_OPEN) return;

  Serial.println("open door!!");

  doorServo.writeMicroseconds(SERVO_OPEN);

  doorOpenTime = millis();

  doorState = DOOR_OPEN;

  needOpen = false;

}

  

void closeDoor() {

  if (doorState == DOOR_IDLE) return;

  Serial.println("close door!!");

  doorServo.writeMicroseconds(SERVO_CLOSE);

  doorState = DOOR_IDLE;

}

  

void doorServoInit() {

  doorServo.attach(SERVO_PIN);

  closeDoor();

}

  

// ========== RFID 读卡 (保持高响应速度) ==========

void readCard() {

  // 如果门正开着或准备开,暂不读卡,避免逻辑冲突

  if (needOpen || doorState == DOOR_OPEN) return;

  

  // 这里的检查非常快,如果没有卡立即返回

  if (!mfrc522.PICC_IsNewCardPresent()) return;

  if (!mfrc522.PICC_ReadCardSerial()) return;

  

  Serial.println("检测到卡片...");

  

  if (mfrc522.uid.size > 8) {

    Serial.println("UID too long");

    mfrc522.PICC_HaltA();

    mfrc522.PCD_StopCrypto1();

    return;

  }

  

  uint64_t uidInt = 0;

  for (byte i = 0; i < mfrc522.uid.size; i++) {

    uidInt = (uidInt << 8) | mfrc522.uid.uidByte[i];

  }

  

  Serial.print("UID (hex): 0x");

  Serial.println(uidInt, HEX);

  

  bool authorized = false;

  for (size_t i = 0; i < NUM_ALLOWED; i++) {

    if (uidInt == ALLOWED_IDS[i]) {

      authorized = true;

      break;

    }

  }

  

  if (authorized) {

    Serial.println("✅ 授权通过");

    needOpen = true;

  } else {

    Serial.println("🚫 未授权卡片");

  }

  

  // 必须停止当前卡片操作,否则下次循环可能读不到新卡

  mfrc522.PICC_HaltA();

  mfrc522.PCD_StopCrypto1();

}

  

// ========== Setup ==========

void setup() {

  Serial.begin(9600);

  Serial.println("\n\n欢迎启动智能门禁系统!");

  

  // 1. 初始化硬件

  SPI.begin();

  mfrc522.PCD_Init();

  doorServoInit();

  

  // 2. 初始化网络配置

  snprintf(command_topic, sizeof(command_topic), "door/switch/%s/status", mqtt_user);

  client.setServer(mqtt_broker, mqtt_port);

  client.setCallback(mqttCallback);

  

  // 3. 启动 WiFi (只调用一次,交给 ESP 后台处理连接)

  // WiFi.mode(WIFI_STA); // 可选,强制为客户端模式

  WiFi.begin(ssid, password);

  Serial.println("WiFi 开始后台连接...");

}

  

// ========== 主循环 ==========

void loop() {

  // 1. 优先处理读卡 (最高优先级)

  readCard();

  

  // 2. 处理开门/关门逻辑

  if (needOpen) {

    openDoor();

  }

  

  if (doorState == DOOR_OPEN && millis() - doorOpenTime > 3000) {

    closeDoor();

  }

  

  // 3. 处理网络重连与消息 (非阻塞,低优先级)

  handleNetwork();

  

  // 4. 让出系统资源 (对 ESP8266 很重要)

  yield();

}

参考图

开发板 开发版

IC卡感应模块 RC-522

MG90S 舵机

杜邦线