Vetrivel07/Automated-Logistics-and-Access-Security-Gate-with-Monitoring-and-Behavioral-Analysis
GitHub: Vetrivel07/Automated-Logistics-and-Access-Security-Gate-with-Monitoring-and-Behavioral-Analysis
一个基于 RFID 与隔离森林的本地智能门禁监控系统,解决访客进出记录与异常行为检测问题。
Stars: 0 | Forks: 0
# 自动物流与访问安全门监控与行为分析系统
**作者:** Vetrivel Maheswaran
**课程:** ISTE730 — 物联网基础 | 2026年春季
**机构:** 罗彻斯特理工学院
---
## 📋 目录
- [概述](#overview)
- [系统架构](#system-architecture)
- [功能特性](#features)
- [硬件要求](#hardware-requirements)
- [硬件引脚映射](#hardware-pin-mapping)
- [软件要求](#software-requirements)
- [项目结构](#project-structure)
- [安装说明](#setup-instructions)
- [运行系统](#running-the-system)
- [仪表板](#dashboard)
- [API 端点](#api-endpoints)
- [异常检测](#anomaly-detection)
- [配置](#configuration)
- [串行消息格式](#serial-message-format)
---
## 概述
传统的门禁系统在入口处验证用户身份后就不再进行任何操作。本项目通过以下方式扩展基础门禁控制:
- **基于 RFID 的边缘认证**(Arduino)
- **实时事件日志记录**到本地 SQLite 数据库
- **实时 Web 仪表板**用于监控访问活动
- **每用户行为分析**(进入时间、停留时长、访问频率)
- **混合异常检测**,结合基于规则的逻辑与隔离森林机器学习模型
整个系统在本地运行——**无需云端依赖,也不需要互联网连接**。
---
## 系统架构
```
RFID Card Scan
↓
Arduino UNO R3 (Edge Layer)
├── RC522 reads UID
├── Authorization check (hardcoded UIDs)
├── IN/OUT toggle per user
├── Servo + LED + Buzzer + LCD feedback
└── JSON over USB Serial → COM3
↓
Python Backend (localhost)
├── SerialReader → background daemon thread
├── AccessService → validation + pipeline
├── AnomalyService → rule-based + ML detection
├── Repository → SQLAlchemy → SQLite
└── FastAPI → REST API + Dashboard
↓
http://localhost:8000
├── Live dashboard (auto-refresh 5s)
├── User detail pages
└── Anomaly alerts page
```
---
## 功能特性
### 硬件(边缘端)
- 通过 RC522(SPI)进行 RFID 卡扫描
- 伺服电机控制闸门(0° 关闭 / 90° 打开)
- 绿色 LED —— 授权通过
- 红色 LED —— 授权拒绝
- 蓝色 LED —— 空闲状态
- 蜂鸣器 —— 音频反馈(授权时短鸣,拒绝时警报)
- Grove LCD RGB —— 带背光的状态显示
- 每张卡片的进出切换逻辑
### 后端
- FastAPI REST API
- SQLAlchemy ORM 配合 SQLite
- 后台串行读取线程(独立于 API 层)
- 仓库模式 —— 所有数据库查询隔离
- 服务层 —— 所有业务逻辑隔离
- Pydantic 模式验证
- 本地时间戳存储
### 仪表板
- **实时扫描面板** —— 实时显示最近扫描(姓名、UID、状态、事件、时间戳、是否在场)
- **允许与拒绝对比图** —— 环形图(全时段)
- **今日统计** —— 总扫描数、允许、拒绝、异常、唯一用户(在午夜重置)
- **实时时钟** —— 每秒更新
- **用户面板** —— 所有用户及其进出状态徽章,可点击
- **异常警报** —— 最新警报及其原因与时间戳
- **日志筛选器** —— 按日期范围和时间窗口筛选
- **用户详情页** —— 每个用户的分析图表、停留时长、完整历史
- **完整异常页面** —— 所有标记事件
### 异常检测
- 离线时段访问检测(可配置时间窗口)
- 快速扫描检测(滑动窗口算法)
- 连续拒绝检测
- 隔离森林机器学习 —— 检测每个用户的异常访问时间模式
---
## 硬件要求

| 组件 | 型号 | 说明 |
|---|---|---|
| 微控制器 | Arduino UNO R3 | 主控板 |
| 基础扩展板 | Grove Base Shield | 用于 Grove 模块连接 |
| RFID 读卡器 | RC522 | 13.56 MHz,SPI 通信 |
| RFID 卡/钥匙 | MIFARE Classic | 必须为 13.56 MHz |
| 伺服电机 | Grove 模拟伺服电机 | 闸门驱动 |
| 绿色 LED | Grove LED | D4 |
| 红色 LED | Grove LED | D5 |
| 蓝色 LED | Grove LED | D6 |
| 蜂鸣器 | Grove 蜂鸣器 | D8 |
| LCD 显示屏 | Grove LCD RGB Backlight v4.0 | I2C |
| 杜邦线 | 母对公 | 用于 RC522 连接 |
| USB 线 | USB-A 转 USB-B | Arduino 连接至电脑 |
---
## 硬件引脚映射
### RC522 RFID(杜邦线 — SPI)
| RC522 引脚 | Arduino 引脚 | 说明 |
|---|---|---|
| SDA (SS) | D10 | SPI 片选 |
| SCK | D13 | SPI 时钟 |
| MOSI | D11 | SPI 数据输出 |
| MISO | D12 | SPI 数据输入 |
| RST | D7 | 复位 |
| GND | GND | 地 |
| 3.3V | 3.3V | **非 5V** |
### Grove 模块(基础扩展板)
| 组件 | Grove 端口 | Arduino 引脚 |
|---|---|---|
| 伺服电机 | D3 | D3(PWM) |
| 绿色 LED | D4 | D4 |
| 红色 LED | D5 | D5 |
| 蓝色 LED | D6 | D6 |
| 蜂鸣器 | D8 | D8 |
| LCD RGB | I2C | A4(SDA)、A5(SCL) |
---
## 软件要求
### Arduino 库
通过 Arduino IDE → 库管理器安装:
- `MFRC522`(由 GithubCommunity 提供)
- `Grove LCD RGB Backlight`(由 Seeed Studio 提供)
- `Servo`(内置,无需安装)
### Python 依赖
```
fastapi==0.115.0
uvicorn==0.30.6
sqlalchemy==2.0.35
pyserial==3.5
pydantic==2.9.2
pydantic-settings==2.5.2
scikit-learn==1.5.2
numpy==2.1.1
pandas==2.2.3
jinja2==3.1.4
```
### 系统要求
- Python 3.10+
- Windows(已在 Windows 上测试,使用 COM3)
- Arduino IDE 2.x
---
## 安装说明
### 步骤 1 — Arduino 设置
1. 按照上方引脚映射表连接所有硬件
2. 打开 Arduino IDE
3. 通过 **工具 → 管理库** 安装所需库
4. 打开 `arduino/SecurityGate.ino`
5. 将程序上传至 Arduino UNO
6. 打开 **串口监视器**,波特率设为 **9600**
7. 扫描每张 RFID 卡 —— 复制打印出的 UID(格式:`XX XX XX XX`)
8. 关闭串口监视器
9. 打开 `SecurityGate.ino` 并替换其中的虚拟 UID:
```
const char AUTHORIZED_UIDS[][12] = {
"XX XX XX XX", // replace with your real UID
"XX XX XX XX", // replace with your real UID
"XX XX XX XX" // replace with your real UID
};
```
10. 再次上传
### 步骤 2 — Python 环境设置
```
# 创建并激活虚拟环境
uv init
uv venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # Mac/Linux
# 安装依赖
uv pip install -r requirements.txt
```
### 步骤 3 — 配置用户名
编辑 `core/user_map.py` 将 UID 映射为显示名称:
```
USER_MAP = {
"XX XX XX XX": "Alice",
"XX XX XX XX": "Bob",
"XX XX XX XX": "Charlie",
}
```
### 步骤 4 — 配置设置(可选)
编辑 `core/config.py` 修改以下配置:
```
SERIAL_PORT: str = "COM3" # change to your Arduino port
SERIAL_BAUD_RATE: int = 9600 # must match Arduino Serial.begin()
ANOMALY_HOUR_START: int = 10 # start of normal operating hours
ANOMALY_HOUR_END: int = 15 # end of normal operating hours
ANOMALY_RAPID_SCAN_LIMIT: int = 5 # max scans before rapid scan alert
ANOMALY_RAPID_SCAN_WINDOW: int = 60 # seconds for rapid scan window
```
---
## 运行系统
### 重要规则
- ❌ 启动 Python 前必须**关闭**串口监视器
- ✅ Arduino 必须**通过 USB 连接**
- ✅ 虚拟环境必须**已激活**
### 启动系统
```
# 确保你在项目目录中
cd python
# 激活 venv(如果尚未激活)
venv\Scripts\activate
# 运行后端
uvicorn main:app --port 8000
```
### 打开仪表板
```
http://localhost:8000
```
### 预期启动终端输出
```
[Main] Starting Security Gate System v1.0.0
[Main] Database initialized.
[SerialReader] Thread started on COM3
[Main] Serial reader started on COM3
[SerialReader] Connected to COM3
[SerialReader] Non-scan message received: {'event': 'system_ready', ...}
```
### 预期卡片扫描终端输出
```
[SerialReader] Received → uid=C3 22 E0 56 status=allowed event=IN
[AccessService] Saved → id=1 uid=C3 22 E0 56 status=allowed event=IN
[AccessService] ✓ Pipeline complete → anomaly=False
```
### 全新启动(重置数据库)
```
# 先停止 Python(Ctrl+C)
# 删除数据库
del access_logs.db # Windows
# rm access_logs.db # Mac/Linux
# 重启
uvicorn main:app --port 8000
```
---
## 仪表板
| URL | 描述 |
|---|---|
| `http://localhost:8000` | 主仪表板 |
| `http://localhost:8000/dashboard/user.html?uid=XX XX XX XX` | 用户详情页 |
| `http://localhost:8000/dashboard/anomalies.html` | 所有异常警报 |
| `http://localhost:8000/docs` | 自动生成的 API 文档 |
---
## API 端点
| 方法 | 端点 描述 |
|---|---|---|
| GET | `/api/latest` | 最新扫描事件 |
| GET | `/api/logs` | 最近 50 条日志 |
| GET | `/api/logs/filter` | 按日期和时间范围筛选 |
| GET | `/api/logs/{id}` | 按 ID 查询单条日志 |
| GET | `/api/logs/user/{uid}` | 查询某 UID 的所有日志 |
| GET | `/api/anomalies` | 所有标记为异常的日志 |
| GET | `/api/stats` | 全时段统计 |
| GET | `/api/stats/today` | 今日统计(在午夜重置) |
| GET | `/api/users` | 所有唯一用户及其统计 |
| GET | `/api/behaviour/{uid}` | 某用户的分析行为 |
### 筛选端点参数
```
GET /api/logs/filter?date_from=2026-04-18&date_to=2026-04-18&time_from=10:00&time_to=15:00
```
| 参数 | 必填 | 格式 | 描述 |
|---|---|---|---|
| `date_from` | 是 | YYYY-MM-DD | 开始日期 |
| `date_to` | 是 | YYYY-MM-DD | 结束日期 |
| `time_from` | 否 | HH:MM | 当天开始时间 |
| `time_to` | 否 | HH:MM | 当天结束时间 |
---
## 异常检测
### 第一层 — 基于规则
| 规则 | 触发条件 | 配置键 |
|---|---|---|
| 离线时段 | 在 10:00 之前或 15:00 之后扫描 | `ANOMALY_HOUR_START`、`ANOMALY_HOUR_END` |
| 快速扫描 | 同一卡片在 60 秒内扫描 5 次以上 | `ANOMALY_RAPID_SCAN_LIMIT`、`ANOMALY_RAPID_SCAN_WINDOW` |
| 拒绝连续 | 同一 UID 连续拒绝 3 次以上 | 硬编码(3) |
### 第二层 — 隔离森林机器学习
- **算法:** `sklearn.ensemble.IsolationForest`
- **特征:** 小时(0–23)
- **训练:** 每次扫描后使用完整用户历史重新训练
- **激活条件:** 每个用户至少 10 条历史记录
- **污染率:** 0.1(预期约 10% 的训练数据为异常)
- **输出:** -1 表示异常,1 表示正常
### 检测流程
```
Every scan →
Rule 1: Off-hours check (fast, always runs)
Rule 2: Rapid scan check (DB query, sliding window)
Rule 3: Denied streak check (last 5 records)
ML: Isolation Forest (only if 10+ history records)
→ First match wins → is_anomaly=True, reason saved to DB
```
---
## 配置
所有配置均在 `core/config.py` 中:
```
# 串行
SERIAL_PORT: str = "COM3"
SERIAL_BAUD_RATE: int = 9600
SERIAL_TIMEOUT: int = 2
SERIAL_RECONNECT_DELAY: int = 5
# 数据库
DATABASE_URL: str = "sqlite:///./access_logs.db"
# 异常检测
ANOMALY_HOUR_START: int = 10 # scans before this hour = suspicious
ANOMALY_HOUR_END: int = 15 # scans after this hour = suspicious
ANOMALY_RAPID_SCAN_LIMIT: int = 5
ANOMALY_RAPID_SCAN_WINDOW: int = 60 # seconds
# 仪表板
DASHBOARD_RECENT_LOGS_LIMIT: int = 50
```
---
## 串行消息格式
Arduino 发送以起始/结束标记包裹的 JSON:
```
<{"uid":"C3 22 E0 56","status":"allowed","event":"IN"}>
<{"uid":"C3 22 E0 56","status":"allowed","event":"OUT"}>
<{"uid":"AE F3 10 06","status":"denied","event":"NONE"}>
```
| 字段 | 取值 | 说明 |
|---|---|---|
| `uid` | `XX XX XX XX` | RFID 卡的 UID(十六进制) |
| `status` | `allowed` / `denied` | 授权结果 |
| `event` | `IN` / `OUT` / `NONE` | 进出切换(拒绝时为 NONE) |
`<` 和 `>` 标记使 Python 串行读取器能够可靠地解析消息,即使传输过程中出现字节丢失。
---
## 数据库架构
**表:** `access_logs`
| 列名 | 类型 | 说明 |
|---|---|---|
| `id` | 整数 | 自增主键 |
| `uid` | 字符串(20) | RFID 卡的 UID |
| `status` | 字符串(10) | `allowed` 或 `denied` |
| `event` | 字符串(4) | `IN`、`OUT` 或 `NONE` |
| `timestamp` | 日期时间 | 本地扫描时间 |
| `is_anomaly` | 布尔值 | 是否标记为异常 |
| `anomaly_reason` | 字符串(200) | 异常原因描述 |
---
## 故障排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| `PermissionError: COM3` | 串口监视器处于打开状态 | 关闭 Arduino IDE 串口监视器 |
| `ModuleNotFoundError: serial` | 名为 `serial` 的文件夹与 pyserial 冲突 | 将文件夹重命名为 `serial_comm` |
| RC522 固件:`0x00` | 接线问题或电压错误 | 检查 3.3V 电源,验证 SPI 引脚连接 |
| 今日统计显示为 0 | 时区不匹配 | 确保使用 `datetime.now()`(而非 `utcnow()`) |
| 卡片无法读取 | 天线信号弱 | 调用 `rfid.PCD_SetAntennaGain(rfid.RxGain_max)` |
| 仪表板未更新 | 筛选器处于激活状态 | 点击筛选器上的重置按钮 |
---
## 技术栈
| 层级 | 技术 |
|---|---|
| 嵌入式 | Arduino C/C++、MFRC522、伺服电机、rgb_lcd |
| 通信 | USB 串行、JSON、基于标记的消息封装 |
| 后端 | Python 3.10+、FastAPI、Uvicorn |
| 数据库 | SQLite、SQLAlchemy ORM |
| 验证 | Pydantic v2 |
| 串行通信 | PySerial |
| 机器学习 | scikit-learn(IsolationForest)、NumPy |
| 前端 | HTML5、CSS3、JavaScript、Chart.js |
| 字体 | IBM Plex Sans、IBM Plex Mono |
标签:Apex, Arduino, AV绕过, FastAPI, ISTE730, Python, RC522, RFID, SQLAlchemy, SQLite, Streamlit, TCP/UDP协议, UNO R3, USB Serial, 串口通信, 云计算, 仪表盘, 停留时长, 入场时间, 孤立森林, 安全闸机, 异常检测, 无云依赖, 无后门, 智能门禁, 本地部署, 机器学习, 混合检测, 物联网, 罗彻斯特理工学院, 自动化物流, 规则引擎, 访问控制, 访问频率, 边缘计算, 逆向工具, 门禁系统