1. 项目概述与核心思路

作为一个喜欢折腾硬件和软件的老玩家,我一直对如何用最少的成本实现最实用的功能感兴趣。这次分享的,是一个我自己捣鼓了挺久,并且已经稳定运行了小半年的DIY安防系统。它的核心目标很简单:当有人经过你设定的区域(比如家门口、楼梯口、或者你不想让宠物进入的房间门口)时,你的电脑能立刻发出警报,并且把这次事件的时间、传感器读数都记录下来。

市面上成熟的安防产品很多,但要么太贵,要么不够灵活,没法按照我的想法去定制触发逻辑和报警方式。我这个方案的亮点在于“传感器融合”的思路。听起来很高大上,其实原理很朴素: 只用单一传感器(比如超声波)容易误报 ,一阵风、一只猫经过,或者传感器自身偶尔的读数漂移,都可能让它“神经紧张”。而所谓融合,在这里我们可以理解为 用软件算法给单一的硬件数据“加上一层保险” 。我们不是增加更多硬件(那会提高成本和复杂度),而是通过Python脚本,对Arduino发来的一连串超声波测距数据进行智能分析,比如检查距离的连续变化模式、过滤掉瞬间的异常跳变,从而更准确地判断是不是真的有人经过。

整个系统的骨架是这样的:一个Arduino Nano(或者其他型号也行)负责读取HC-SR04超声波传感器的数据;一个HC-05蓝牙模块负责把数据无线发送到你的电脑;电脑上运行一个Python脚本,这个脚本是真正的大脑,它接收数据、分析判断、发出警报(比如在Mac上弹个通知,在Windows上可以播放声音),同时把原始数据和时间戳保存到文件里,方便你事后查看。整个硬件成本不到一百块,软件全部开源,你可以随意修改逻辑来适应你的窗户、走廊或者储物间。

2. 硬件选型、电路设计与搭建细节

2.1 核心元器件解析与选型考量

选择这些元件,我主要基于几个原则:低成本、易获取、低功耗(毕竟可能要用电池供电),以及足够的可靠性。

  1. 主控:Arduino Nano

    • 为什么是Nano? 相比于Uno,Nano体积更小,更适合嵌入到最终可能放在一个小盒子里的项目中。它的核心芯片(ATmega328P)性能与Uno完全一致,有足够的GPIO口和算力来处理超声波传感器和蓝牙通信。当然,如果你手头只有Uno或Mega,完全可以平替,代码几乎不用改。
    • 供电注意 :Nano可以通过Mini-USB口供电,也可以通过VIN引脚输入7-12V电压。在本项目中,我们使用移动电源通过USB供电,非常方便。
  2. 感知核心:HC-SR04超声波传感器

    • 工作原理 :它通过发射40kHz的超声波并接收回波,根据时间差计算距离。测量范围约2cm-400cm,精度对于人体检测(几十厘米到几米范围)完全够用。
    • 关键参数 :触发信号(Trig)需要至少10微秒的高电平,然后模块会自动发射8个40kHz脉冲。回声引脚(Echo)会输出一个高电平脉冲,其宽度与距离成正比。计算距离的公式为: 距离(厘米) = (高电平时间(微秒) * 0.034) / 2 。除以2是因为声音走了来回两趟。
    • 避坑点 :HC-SR04对供电电压比较敏感。虽然它标称5V工作,但最好确保供电稳定。如果距离读数飘忽不定,首先检查电源。
  3. 无线桥梁:HC-05蓝牙串口模块

    • 角色 :它本质上是一个串口转蓝牙的适配器。Arduino通过TX/RX引脚以串口协议发送数据,HC-05将其转换为蓝牙信号发送给电脑;反之亦然。
    • HC-05 vs HC-06 :HC-05功能更强大,支持主从模式切换和AT指令配置。HC-06只能作为从机。原项目提到连接名为“HC-06”的设备,这可能是模块出厂默认名称,或者是HC-05被配置为了从机模式。购买时确认一下即可,使用上区别不大。
    • 配对密码 :通常默认是“1234”或“0000”。
  4. 控制开关:OMRON轻触开关与10kΩ电阻

    • 作用 :用于手动开启或关闭整个传感器系统,避免在你不需要的时候误触发。
    • 电路原理 :这里使用的是经典的 上拉电阻 接法。Arduino的输入引脚通过一个10kΩ电阻连接到5V(上拉),同时该引脚也连接到开关的一端,开关另一端接地。当开关未按下时,引脚通过上拉电阻保持在高电平(1);当开关按下,引脚直接接地,变为低电平(0)。程序通过检测这个引脚的电平变化来判断开关状态。使用上拉电阻可以避免引脚悬空时产生不确定的杂讯。
  5. 其他 :面包板、杜邦线、移动电源。移动电源建议选择输出稳定、容量适中的(例如5000mAh),可以保证系统连续工作数天。

2.2 电路连接详解与实操陷阱

按照电路图连接并不难,但有几个细节决定了你是一次成功还是陷入调试地狱。

接线清单(务必对照):

  • Arduino Nano & 电源

    • 5V -> 面包板正极(红线区域)
    • GND -> 面包板负极(蓝线区域)
    • 移动电源USB口连接Nano的USB口供电。
  • HC-SR04超声波传感器

    • VCC -> 面包板正极 (5V)
    • GND -> 面包板负极
    • Trig -> Arduino 数字引脚 D2
    • Echo -> Arduino 数字引脚 D3
  • HC-05蓝牙模块

    • VCC -> 面包板正极 (5V) (注意:有些HC-05模块是3.3V逻辑电平,但供电需要5V,请查看模块背面说明。若为3.3V逻辑,则其RX引脚不能直接接Arduino的5V TX,需分压)
    • GND -> 面包板负极
    • TX -> Arduino 数字引脚 D0 (RX) (重要:上传代码时必须断开此线)
    • RX -> Arduino 数字引脚 D1 (TX) (重要:上传代码时必须断开此线)
  • OMRON开关与10kΩ电阻

    • 将开关跨接在面包板中间凹槽的两侧。
    • 开关一侧引脚连接至面包板负极(GND)。
    • 开关另一侧引脚连接至 Arduino 数字引脚 D4
    • 在 Arduino D4 引脚和面包板正极(5V)之间,连接一个10kΩ电阻(这就是上拉电阻)。

核心避坑指南1:蓝牙模块的TX/RX连接 这是新手最容易出错的地方。Arduino的 D0 (RX)和 D1 (TX)同时也是其与电脑进行串口通信(用于上传程序)的引脚。如果在这两个引脚连接着其他设备(如HC-05)的情况下尝试上传程序,会产生信号冲突,导致上传失败,并报“avrdude: stk500_getsync()”之类的错误。 正确操作流程 :1. 编写好Arduino代码。2. 断开HC-05模块与D0、D1连接的所有线 。3. 将Arduino通过USB连接电脑,选择正确端口和板型,上传程序。4. 上传成功后,拔掉USB线, 先接好HC-05的线 ,再重新插上USB线上电。这个顺序务必牢记。

核心避坑指南2:电源噪声与共地 所有元件的 GND (地线)必须最终连接到一起(都接到面包板的负极总线),形成一个共同的参考零电位点,这是电路正常工作的基础。如果超声波传感器读数乱跳,除了检查代码,一定要确认电源正负极连接牢固,且移动电源输出稳定。有条件的话,可以在Arduino的5V和GND之间加一个100uF的电解电容,用于滤波稳压。

3. Arduino固件:数据采集与串口发送

Arduino端的任务很纯粹:循环读取超声波传感器的距离,读取开关状态,然后将这些数据打包成一个格式化的字符串,通过软串口发送给蓝牙模块。

3.1 代码逐行解析与关键参数设置

我们使用 SoftwareSerial 库来创建一组新的串口,专门用于和HC-05通信,这样就不会占用硬件串口(虽然我们上传程序后也用不到硬件串口了,但这是个好习惯)。

#include <SoftwareSerial.h>

// 定义引脚
const int trigPin = 2;
const int echoPin = 3;
const int switchPin = 4;

// 创建软串口对象,RX接D0, TX接D1 (对应HC-05的TX和RX)
SoftwareSerial bluetooth(0, 1); // RX, TX

// 定义一些变量
long duration;
int distance_cm;
bool lastSwitchState = HIGH; // 假设初始为上拉状态(开关未按下)
bool systemActive = false;

void setup() {
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(switchPin, INPUT_PULLUP); // 使用内部上拉电阻,这样外部10k电阻可以省略,但为了演示我们保留了外部电路。

  Serial.begin(9600); // 硬件串口,用于调试,可选择性开启
  bluetooth.begin(9600); // 蓝牙串口,必须与HC-05默认波特率一致(通常是9600或38400)

  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
}

void loop() {
  // 1. 读取开关状态
  bool currentSwitchState = digitalRead(switchPin);
  if (currentSwitchState == LOW && lastSwitchState == HIGH) {
    // 检测到下降沿(按下动作)
    delay(50); // 简单防抖
    if (digitalRead(switchPin) == LOW) { // 再次确认
      systemActive = !systemActive; // 切换系统状态
      // 可以通过蓝牙发送状态信息,例如:“SYS:ON”或“SYS:OFF”
      if(systemActive) {
        bluetooth.println("SYS:ON");
      } else {
        bluetooth.println("SYS:OFF");
      }
    }
  }
  lastSwitchState = currentSwitchState;

  // 2. 如果系统激活,则进行测距
  if (systemActive) {
    // 触发超声波传感器
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    // 读取回波脉冲时长
    duration = pulseIn(echoPin, HIGH, 30000); // 设置超时30ms,对应约5米距离

    // 计算距离(厘米)
    if (duration > 0) {
      distance_cm = duration * 0.034 / 2;
    } else {
      distance_cm = 999; // 超时或无效值,用一个很大的数表示
    }

    // 3. 通过蓝牙串口发送数据
    // 格式建议:”DIST:123,SW:1“ 表示距离123cm,开关状态为激活(1)
    bluetooth.print("DIST:");
    bluetooth.print(distance_cm);
    bluetooth.print(",SYS:");
    bluetooth.println(systemActive ? "1" : "0");

    // 可选:通过硬件串口打印到IDE的串口监视器,用于调试
    // Serial.print("Distance: ");
    // Serial.print(distance_cm);
    // Serial.println(" cm");
  } else {
    // 系统未激活,可以每隔一段时间发送一个休眠状态信息,或者不发送
    bluetooth.println("SYS:STANDBY");
  }

  // 控制采样频率,避免数据过快。200ms一次比较合适。
  delay(200);
}

关键点解析:

  • pulseIn 函数与超时 pulseIn(echoPin, HIGH, 30000) 中的 30000 是超时时间(微秒)。如果30ms内没有收到高电平回波,函数会返回0。这可以防止在传感器前方没有障碍物时,程序一直卡在这里等待。根据声速,30ms对应大约5米的测量距离,满足大部分室内场景。
  • 数据格式 :我们定义了简单的数据协议,如 “DIST:150,SYS:1” 。这样Python端可以用逗号分隔,再用冒号分割键值对,便于解析。 统一的格式是后续稳定通信的基础。
  • 开关防抖 :机械开关在按下瞬间会产生快速的电压抖动,可能导致程序误判为多次按下。我们采用简单的延时再检测的方法( delay(50) )来进行软件防抖。
  • 采样延迟 delay(200) 将循环周期控制在约200ms,即每秒采样5次。这个频率对于检测人的行走速度足够了,同时避免了数据洪流淹没蓝牙通道。

3.2 上传代码与初步测试

  1. 在Arduino IDE中安装 SoftwareSerial 库(通常已内置)。
  2. 将上述代码复制到新项目中。
  3. 务必断开Arduino Nano上连接HC-05模块TX/RX的杜邦线。
  4. 选择正确的板型( Arduino Nano )和端口(如 COM3 /dev/cu.usbmodem14101 )。
  5. 点击上传。
  6. 上传成功后,先关闭Arduino IDE的串口监视器(如果打开了),然后 给Arduino断电 ,接上HC-05的TX/RX线,最后重新上电。

初步测试 :打开Arduino IDE的串口监视器,将波特率设置为9600,你应该能看到每隔200ms打印出的距离信息(前提是你在代码中取消了 Serial.print 那几行的注释)。这能验证超声波传感器和基础逻辑是否工作正常。同时,观察HC-05模块上的LED指示灯,通常连接成功后会有规律的慢闪(约每秒一次),表示已进入可配对状态。

4. Python大脑:数据接收、融合算法与警报触发

这是项目的智能核心。Python脚本需要完成几件事:建立蓝牙串口连接、持续读取数据、解析数据、应用算法判断是否有人经过、触发系统通知并记录日志。

4.1 环境搭建与依赖库说明

原项目提到在macOS上运行,在Windows上需要调整。这里我会分别说明。

# 使用pip安装所需库(Windows/macOS/Linux通用)
pip install pyserial
# 如果需要桌面通知,macOS需要安装`pyobjc`,但更推荐使用`plyer`库跨平台
pip install plyer
# 如果需要记录日志文件,可以使用Python内置的logging,但这里我们按原项目思路自己写文件
  • pyserial :这是与蓝牙串口通信的核心库。它把蓝牙虚拟成的COM口当作一个普通的串口来读写。
  • plyer :一个跨平台的库,可以很方便地调用操作系统的通知功能,在Mac上显示通知中心提醒,在Windows上显示Toast通知,在Linux上显示libnotify提示。
  • 注意事项 :在Windows上,蓝牙串口通常显示为 COMx (如 COM6 );在macOS上,则显示为 /dev/tty.HC-05-DevB 或类似名称。在Linux上可能是 /dev/rfcomm0 。连接前需要在系统蓝牙设置中完成配对。

4.2 核心脚本解析与传感器融合逻辑实现

下面是一个增强版的 readingValuesV2.py 脚本,包含了更健壮的融合逻辑和错误处理。

import serial
import time
from datetime import datetime
import os
from plyer import notification
import sys

class SecuritySensor:
    def __init__(self, port, baudrate=9600, wall_distance_cm=100):
        """
        初始化传感器
        :param port: 串口地址,如 'COM6' 或 '/dev/tty.HC-05-DevB'
        :param baudrate: 波特率,需与Arduino设置一致
        :param wall_distance_cm: 传感器到背景墙(或正常静止物体)的基准距离
        """
        self.port = port
        self.baudrate = baudrate
        self.wall_distance = wall_distance_cm
        self.ser = None
        self.system_active = False
        # 数据缓存,用于融合算法
        self.distance_buffer = []
        self.buffer_size = 5  # 缓存最近5次读数
        # 报警状态锁,防止短时间内重复报警
        self.alarm_cooldown = False
        self.cooldown_seconds = 10  # 报警后冷却10秒

    def connect(self):
        """建立蓝牙串口连接"""
        try:
            self.ser = serial.Serial(self.port, self.baudrate, timeout=1)
            print(f"成功连接到 {self.port}")
            # 清空可能存在的旧数据缓冲区
            self.ser.flushInput()
            time.sleep(2)  # 等待Arduino和模块稳定
            return True
        except serial.SerialException as e:
            print(f"连接失败: {e}")
            print("请检查:1. 蓝牙是否已配对并连接。2. 端口号是否正确。3. 是否有其他程序占用了该端口。")
            return False

    def parse_data(self, line):
        """解析从Arduino接收到的单行数据"""
        try:
            line = line.decode('utf-8').strip()
        except UnicodeDecodeError:
            return None

        data_dict = {}
        # 预期格式: "DIST:150,SYS:1" 或 "SYS:ON"
        parts = line.split(',')
        for part in parts:
            if ':' in part:
                key, value = part.split(':', 1)
                data_dict[key] = value
        return data_dict

    def sensor_fusion_logic(self, current_distance):
        """
        核心融合判断逻辑。
        当前实现:基于滑动平均和变化趋势的简单融合。
        """
        # 1. 填充缓存
        self.distance_buffer.append(current_distance)
        if len(self.distance_buffer) > self.buffer_size:
            self.distance_buffer.pop(0)  # 移除最旧的读数

        # 如果缓存还没满,不进行判断
        if len(self.distance_buffer) < self.buffer_size:
            return False

        # 2. 计算滑动平均值和瞬时波动
        avg_distance = sum(self.distance_buffer) / len(self.distance_buffer)
        # 计算最近一次读数与平均值的偏差
        immediate_deviation = abs(current_distance - avg_distance)

        # 3. 计算距离变化的趋势(最近几次读数的变化量)
        # 这里简单计算缓存中第一个和最后一个的差值
        trend = self.distance_buffer[-1] - self.distance_buffer[0]

        # 4. 判断条件(可调整的阈值)
        threshold_sudden = 20  # 厘米,单次读数突变的阈值
        threshold_sustained = 15  # 厘米,持续偏离基准的阈值
        trend_threshold = 10  # 厘米,趋势变化的阈值

        # 条件A:当前读数突然剧烈变化(可能是快速通过)
        condition_a = immediate_deviation > threshold_sudden and current_distance < self.wall_distance
        # 条件B:平均读数持续低于背景墙距离(有人停留)
        condition_b = avg_distance < (self.wall_distance - threshold_sustained)
        # 条件C:呈现明显的持续减小趋势(有人靠近)
        condition_c = trend < -trend_threshold

        # 满足任一条件,且当前距离有效(非超时值999)
        if current_distance != 999 and (condition_a or condition_b or condition_c):
            return True
        return False

    def send_notification(self, message):
        """发送系统通知"""
        try:
            notification.notify(
                title='安全传感器警报',
                message=message,
                app_name='DIY Security System',
                timeout=5  # 通知显示5秒
            )
            print(f"[警报] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")
        except Exception as e:
            print(f"发送通知失败: {e}")

    def log_data(self, distance, status, event=''):
        """记录数据到文件"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        log_line = f"{timestamp}, DIST:{distance}, SYS:{status}, EVENT:{event}\n"
        with open(self.log_file, 'a') as f:
            f.write(log_line)

    def run(self, log_file_prefix='sensor_log'):
        """主运行循环"""
        # 创建日志目录和文件
        log_dir = 'sensor_logs'
        os.makedirs(log_dir, exist_ok=True)
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        self.log_file = os.path.join(log_dir, f'{log_file_prefix}_{timestamp}.csv')
        # 写入CSV表头
        with open(self.log_file, 'w') as f:
            f.write("timestamp,distance_cm,system_status,event\n")
        print(f"日志文件已创建: {self.log_file}")

        if not self.connect():
            sys.exit(1)

        print("系统启动。等待数据... (按Ctrl+C停止)")
        try:
            while True:
                if self.ser.in_waiting > 0:
                    line = self.ser.readline()
                    data = self.parse_data(line)

                    if data:
                        # 处理系统状态
                        if 'SYS' in data:
                            if data['SYS'] in ['ON', '1']:
                                if not self.system_active:
                                    self.system_active = True
                                    print("系统状态:激活")
                                    self.send_notification("安防系统已激活")
                            elif data['SYS'] in ['OFF', '0', 'STANDBY']:
                                if self.system_active:
                                    self.system_active = False
                                    print("系统状态:待机")
                                    self.distance_buffer = []  # 清空缓存
                                    self.send_notification("安防系统已待机")

                        # 处理距离数据
                        if 'DIST' in data and self.system_active:
                            try:
                                dist = int(data['DIST'])
                                # 记录原始数据
                                self.log_data(dist, 'ACTIVE')

                                # 应用融合算法判断
                                if self.sensor_fusion_logic(dist):
                                    if not self.alarm_cooldown:
                                        alert_msg = f"检测到移动!当前距离: {dist}cm"
                                        self.send_notification(alert_msg)
                                        self.log_data(dist, 'ACTIVE', 'ALARM_TRIGGERED')
                                        self.alarm_cooldown = True
                                        # 启动冷却计时器
                                        def reset_cooldown():
                                            time.sleep(self.cooldown_seconds)
                                            self.alarm_cooldown = False
                                        import threading
                                        threading.Thread(target=reset_cooldown, daemon=True).start()
                                    else:
                                        print(f"[忽略] 检测到移动,但在冷却期内。距离: {dist}cm")
                                else:
                                    # 正常读数,可选打印
                                    # print(f"距离: {dist}cm")
                                    pass

                            except ValueError:
                                pass

                time.sleep(0.05)  # 短暂睡眠,降低CPU占用
        except KeyboardInterrupt:
            print("\n用户中断。")
        finally:
            if self.ser and self.ser.is_open:
                self.ser.close()
                print("串口连接已关闭。")

if __name__ == "__main__":
    # ====== 用户需要修改的部分 ======
    # Windows 用户:在设备管理器中查看端口(COMx)
    # macOS 用户:在终端输入 `ls /dev/tty.*` 查看
    SENSOR_PORT = '/dev/tty.HC-05-DevB'  # 例如:'COM6' 或 '/dev/tty.HC-05-DevB'
    # 测量并设置传感器到背景墙(或固定障碍物)的静止距离
    BASE_WALL_DISTANCE_CM = 120  # 单位:厘米
    # ===============================

    sensor = SecuritySensor(port=SENSOR_PORT, wall_distance_cm=BASE_WALL_DISTANCE_CM)
    sensor.run()

融合算法深度解读:

这个 sensor_fusion_logic 函数是实现“智能”的关键。它没有增加硬件,而是通过软件算法提升了判断可靠性。

  1. 数据缓存( distance_buffer :维护一个固定长度(如5)的最近距离读数列表。这相当于给系统一个“短期记忆”。
  2. 滑动平均( avg_distance :计算缓存内数据的平均值。这能有效平滑掉单次测量可能出现的随机噪声或毛刺。
  3. 瞬时偏差( immediate_deviation :计算最新一次读数与滑动平均值的差值。如果这个差值突然变得很大,可能意味着有物体快速进入或离开探测区。
  4. 变化趋势( trend :计算缓存中第一个和最后一个数据的差值。一个持续为负的趋势(值越来越小)表明物体正在持续靠近传感器。
  5. 多条件触发
    • condition_a :针对快速通过。比如有人快步走过,会导致某一次读数剧烈变化。
    • condition_b :针对长时间停留。比如有人站在传感器前,会导致滑动平均值持续低于背景距离。
    • condition_c :针对缓慢靠近。比如有人悄悄走近,距离会呈现稳定的减小趋势。 只有满足这些条件之一,才判定为有效入侵,触发警报。这大大降低了因传感器自身波动或小动物短暂经过引起的误报。

其他重要特性:

  • 报警冷却期 :触发一次警报后,会进入10秒的冷却期,期间即使再检测到移动也不会报警。这避免了人在警报区域内活动时,系统“喋喋不休”地连续报警。
  • 完整的日志记录 :所有数据(时间戳、距离、系统状态、报警事件)都以CSV格式保存,方便后续用Excel或Python分析,优化 wall_distance 和判断阈值。
  • 跨平台通知 :使用 plyer 库,一行代码就能实现Mac/Windows/Linux的通知。

4.3 脚本运行与配置要点

  1. 获取蓝牙串口号
    • Windows :配对HC-05后,打开“设备管理器”,在“端口(COM和LPT)”下找到类似“Standard Serial over Bluetooth link (COM6)”的设备,记下COM号。
    • macOS :配对后,打开终端,输入 ls /dev/tty.* ,你会看到类似 /dev/tty.HC-05-DevB 的名称。
  2. 修改脚本参数 :打开脚本,找到最后面的 SENSOR_PORT BASE_WALL_DISTANCE_CM 变量,根据你的实际情况修改。
  3. 测量基准距离 :将传感器固定在最终位置,前方无人的情况下,运行一个简单的测试脚本读取距离,取一个稳定值作为 BASE_WALL_DISTANCE_CM 。这是判断“有无物体”的基准线。
  4. 运行脚本 :在终端或命令行中,进入脚本所在目录,执行 python readingValuesV2.py 。首次运行会要求输入一个记录编号(原项目功能),你可以按回车跳过或输入任意数字。
  5. 测试 :在传感器前走动,观察终端输出和电脑是否弹出通知。查看生成的日志文件,确认数据被记录。

5. 系统集成、调试与优化实战

5.1 完整工作流程与部署

当硬件和软件分别测试通过后,就可以进行集成部署了:

  1. 硬件固定 :将面包板上的电路用热熔胶或螺丝固定在一个合适的小盒子内,为超声波传感器开孔。确保传感器表面清洁,无遮挡。
  2. 电源管理 :使用移动电源供电。测试待机功耗,估算续航时间。Arduino Nano和HC-05在非深度睡眠模式下,电流大约在30-50mA,一个10000mAh的移动电源理论上可以支持数百小时。
  3. 软件自启动 :如果你希望电脑开机后自动运行这个监控脚本:
    • macOS :可以创建一个 .plist 文件放入 ~/Library/LaunchAgents/ 目录。
    • Windows :可以将Python脚本的快捷方式放入“启动”文件夹。
    • 更优解 :使用树莓派Zero W这类微型电脑来运行Python脚本,功耗更低,可以完全脱离主机电脑,并通过网络发送警报(如Telegram消息、电子邮件),这是下一步升级的方向。
  4. 阈值调优 :这是降低误报率的关键。运行系统一段时间,让人在正常活动范围内行走,观察日志中的距离变化范围。然后调整Python脚本中的三个阈值:
    • threshold_sudden (瞬时突变阈值):通常设为10-30cm,取决于人通过的速度和你希望忽略的小幅度波动。
    • threshold_sustained (持续偏离阈值):通常设为5-20cm,比瞬时阈值小一些,用于检测缓慢移动或静止目标。
    • trend_threshold (趋势阈值):通常设为5-15cm,用于判断一个明确的靠近或远离趋势。
    • buffer_size (缓存大小):增大缓存(如到10)会让系统反应更“迟钝”但更稳定;减小缓存会让系统更“灵敏”但也更易受干扰。

5.2 高级调试与问题排查手册

即使按照步骤操作,也可能会遇到问题。下面是一个系统性的排查清单:

问题现象 可能原因 排查步骤与解决方案
Arduino代码上传失败 1. 端口或板型选择错误。
2. TX/RX引脚被占用。
3. 驱动问题(仅Windows)。
1. 确认IDE中选择的板型是 Arduino Nano ,端口正确。
2. 务必在上传前断开与HC-05连接的TX/RX线。
3. 尝试为Nano选择 ATmega328P (Old Bootloader)
Python脚本报错 [Errno 16] Resource busy SerialException 1. 蓝牙未配对/连接。
2. 端口被其他程序占用。
3. 端口号错误。
1. 去系统蓝牙设置中,确认已与 HC-05 HC-06 配对并连接。
2. 关闭Arduino IDE的串口监视器或其他可能占用端口的软件。
3. 重新检查并更正脚本中的 SENSOR_PORT 变量。
Python脚本连接成功,但收不到数据 1. Arduino未供电或未运行。
2. 蓝牙模块波特率不匹配。
3. Arduino代码中数据格式与Python解析格式不一致。
1. 检查Arduino电源指示灯是否亮起。
2. 尝试修改Python和Arduino代码中的波特率为 38400 115200 (需同时修改)。
3. 在Python脚本的 parse_data 函数里添加 print(“Raw:”, line) ,查看原始数据格式,确保与Arduino发送的格式匹配。
超声波传感器读数始终为0或非常大且不变 1. 接线错误(Trig/Echo接反)。
2. 供电不足。
3. 传感器损坏。
1. 仔细检查Trig和Echo引脚是否接对。
2. 用万用表测量传感器VCC引脚电压是否稳定在5V左右。
3. 使用Arduino IDE串口监视器运行一个最简单的超声波测距例程单独测试传感器。
系统频繁误报 1. 基准距离 wall_distance 设置不准确。
2. 算法阈值过于敏感。
3. 环境干扰(如空调风、窗帘飘动)。
1. 在无人环境下重新校准基准距离,并留出一定余量(比如减去5cm)。
2. 逐步提高 threshold_sudden threshold_sustained 等阈值。
3. 调整传感器角度,避免直接对着通风口或窗户。考虑增加 buffer_size
警报延迟很大 1. Arduino循环中的 delay 过长。
2. Python脚本数据处理耗时太长。
1. 减少Arduino代码中 loop() 末尾的 delay(200) ,比如改为 100 (需测试稳定性)。
2. 优化Python代码,确保主循环 time.sleep 很短,且 sensor_fusion_logic 计算高效。
移动电源耗电极快 1. 系统未进入低功耗模式。 1. 修改Arduino代码,当开关关闭时,让Arduino进入 低功耗 模式(需要更复杂的编程),或至少停止超声波传感器工作(设置Trig为LOW)和减少蓝牙发送频率。

5.3 扩展思路与进阶玩法

这个基础框架有巨大的可扩展性:

  1. 多传感器融合(真正的硬件融合) :增加一个 PIR(热释电红外)传感器 。PIR对移动的人体热源敏感,但对静止目标无效。超声波对静止目标有效,但可能被非生命体触发。让Arduino同时读取两者数据,只有 当PIR和超声波同时触发 时,才判定为“人经过”,这将误报率降到极低。
  2. 网络化与远程警报 :用 ESP8266 ESP32 替代Arduino Nano,它们自带Wi-Fi。检测到入侵后,可以通过网络请求(HTTP)或MQTT协议,将警报发送到手机App(如Blynk、IFTTT)、Telegram Bot或你自己的服务器。
  3. 本地声光报警 :在Arduino端连接一个蜂鸣器和一个LED。当Python脚本判定为入侵时,可以通过蓝牙反向发送一个指令(如“ALARM:ON”),让Arduino触发本地声光报警,这样即使电脑关机或蓝牙断开也能起作用。
  4. 数据可视化 :使用Python的 matplotlib 库,实时绘制距离随时间变化的曲线图,可以更直观地观察探测情况和调试算法阈值。
  5. 机器学习优化 :收集大量的“有人经过”和“无人经过”时的距离序列数据,使用简单的机器学习算法(如Scikit-learn)进行训练,让系统自己学习判断模式,这可能比手动设置阈值规则更有效。

更多推荐