一个人的家,也有守卫 ——面向独居女性的居家与随身双端安全报警系统
一个人的家,也有守卫
——面向独居女性的居家与随身双端安全报警系统
做中学·探未来|科学教育实践案例 方向:工程学(物联网与智能硬件)
作品信息
项目 | 内容 |
作品名称 | 一个人的家,也有守卫——面向独居女性的居家与随身双端安全报警系统 |
主题方向 | 工程学:物联网通信(MQTT)、传感器应用、移动应用开发、结构设计 |
适用学段 | 初中(7—9 年级) |
使用对象 | 独居女性、独居老人;也适用于短期独居的学生宿舍、出租房 |
主控 | FireBeetle ESP32-E(ESP-WROOM-32E,支持 Wi-Fi) |
传感器 | 霍尔磁性 ×2、MQ-2 烟雾、DHT22 温湿度、火焰传感器、SOS 按钮 |
通信 | MQTT 协议,阿里云物联网平台转发 |
软件 | Arduino IDE(C++)、App Inventor 2019 桌面版、3DMAX、3D 打印机 |
系统规模 | 1 个产品、3 个设备、3 个自定义 Topic、3 条消息流转规则 |
一、项目介绍
1.1 起因:一条新闻,和一个不敢开的门
这个项目最早不是从“我想做个物联网装置”开始的,是从一件很小的事开始的:有天晚上十点多,我听见门外有持续的脚步声和金属摩擦声,第一反应不是去猫眼看,而是把客厅灯关了、站在门后不动。后来发现是邻居在搬东西。但那十几分钟里,我确实什么也做不了——不敢开门看,也不敢打电话,因为不确定是不是自己想多了。
查资料时我看到,针对独居女性的入室、尾随类案件在报道中有增多趋势。市面上的安防产品其实不少,但调研下来有三个问题:一是整套产品绑死在一家公司的 App 里,功能是人家定好的,我想加“下水道漫水”这种偏门的需求就加不进去;二是要换就得把原有电器、门锁一起换掉,成本很高;三是绝大多数产品只管“家里”,一旦出门就完全失效——而尾随、跟踪恰恰发生在路上。
所以这个项目要做两件事:把家变成一个会报警的地方,再给出门的自己留一个按键。
1.2 我把担心拆成了六个可以测量的疑问
“做个安全系统”这句话没法验证。我把它拆成六个能用“通过/不通过”回答的问题,后面的硬件选型、程序结构和测试记录都围绕这六个问题展开:
编号 | 探究问题 | 怎么算通过 |
Q1 | 门窗被打开时,装置能不能在几秒内发出报警? | 分开磁铁后,现场声光报警在 2 秒内启动,手机端同步显示 |
Q2 | 漏水、着火、烟雾三种异常,能不能分别认出来? | 三种情况分别触发,且手机端显示的图标与实际情况对应 |
Q3 | 报警能不能送到手机上,而不是只在现场响? | 人不在家、手机在另一个网络下,仍能收到提示 |
Q4 | 离家时能不能远程把报警打开或关掉? | 在 App 上点开关,装置端继电器与蜂鸣器的状态随之改变 |
Q5 | 按下随身装置的按钮,手机能不能收到? | 按键后手机收到一条带时间戳的呼救消息 |
Q6 | 断网以后,这套系统还能做什么? | 本地声光报警是否仍然有效(本项只做了设计推演,未实测) |
第六个问题是后来才想明白要加的。做测试时我把路由器关掉试了一次,发现手机上什么都没有,但现场的蜂鸣器还在响——这说明本地报警和远程报警是两条独立的路,断网并不会让装置变成一块砖。这一点后来直接影响了我对“报警”这件事的理解:先保证现场有反应,再谈远程。
1.3 它怎么用:在家看四个数,出门按一个键
整套系统分成两半,共用同一个云平台和同一个手机 App。
· 居家卫士 贴在门框和窗框上的两个霍尔传感器管“有没有被打开”;厨房一侧挂 MQ-2 管燃气和烟雾、DHT22 管温度和水、火焰传感器管明火;主控旁边接蜂鸣器、LED 和一路继电器(可以接灯或排风扇)。
· 随身卫士 一块主控、一个红色 SOS 按钮、一块锂电池,装进 3D 打印的白色圆柱外壳里,可以放进外套口袋。危急时按一下,手机立刻收到呼救。
· 手机 App App 上排着四个状态图标(火焰、燃气、漏水、门窗),绿色是正常、红色是报警;下面一行是“报警开启/关闭”的总开关,出门前打开,回家后关掉。
之所以做成“四个状态图标 + 一个总开关”,而不是一长串传感器数值,是因为这个 App 的使用场景是“在单位突然想看一眼前”和“半夜被提示音吵醒”——这两种情况下,人没有耐心去读数字,只想知道“哪一项红了”。
1.4 背后的五条原理
(1)MQTT 为什么适合“设备—云—手机”
这套系统的三端分别在三个地方:传感器在家里,人在外面,云在机房。要让它们互相说话,最直接的想法是“手机直接连家里的板子”,但这样有两个绕不过去的麻烦:家里的宽带大多是动态 IP,手机关掉再打开,地址就变了;而且板子要一直开着端口等外面连进来,既费电又不安全。
MQTT 换了个思路:不让设备互相对话,而是让它们都跟一个“邮局”说话。设备把消息贴上主题标签(Topic)投进邮局,谁订阅了这个主题,邮局就把消息转给谁。这样三端都不需要知道对方的地址,只需要知道邮局在哪。MQTT 还有一个特点:它的报文头最小只有 2 个字节,对用电池供电、算力很小的单片机非常友好——这也是它在物联网里被广泛使用的原因。
(2)云平台不是“服务器”,是“登记处 + 邮局”
阿里云物联网平台做了两件事。第一件是登记:每台设备在平台上都有一个名字和一把钥匙,叫“三元组”——ProductKey(产品编号)、DeviceName(设备名)、DeviceSecret(设备密钥)。设备连上来时要拿这三样算一个签名,平台核对无误才允许通信。
第二件才是转发:管理员事先在平台上写好“谁的消息转给谁”的规则,叫消息流转。我们这个项目配了三条:居家的传感器数据转给手机、随身的呼救转给手机、手机的开关键转给居家主控。写规则这一步完全在浏览器里点鼠标完成,不需要写代码——这也是我选云平台而不是自己搭服务器的原因。
(3)传感器输出的是数字,阈值是人定的
很多人以为传感器会直接告诉你“着火了”。实际上它只给一个数:火焰传感器给的是 0 或 1,MQ-2 给的是 0 到 4095 之间的一个数,DHT22 给的是摄氏温度和百分比湿度。“多少算异常”这件事,机器不知道,必须由人先定一条线。
本项目定的线是:烟雾大于 400、火焰传感器给出信号、温度高于 45 ℃、湿度高于 85 %、霍尔传感器断开。前四条来自反复试出来的经验值——比如温度线如果定在 35 ℃,夏天开空调前就会误报;湿度线如果定在 70 %,南方回南天会天天响。这些数字没有“标准答案”,只有“在你家够不够用”。
这是整个项目里最需要人来判断的地方,也是它区别于“买个成品”的地方——阈值捏在自己手里,才可以按自己家的环境去调。
(4)为什么“发布”和“订阅”要分开写
程序里每台设备都同时做两件事:把自己采集到的数据发布出去,同时订阅别人发给自己的指令。居家主控发布的主题是传感器数据,订阅的主题是手机下发的开关指令;手机反过来。两条路的代码是分开写的,互不干扰——这一点很重要,因为报警上报是“每两秒一次”的定时动作,而接收指令是“随时可能来”的中断动作,混在一起写就容易互相卡住。
(5)3D 打印:外壳不是装饰,是结构
随身装置要放进口袋,就必须解决三个问题:按钮不能被误按、电池不能晃动、板子不能短路。我先在 3DMAX 里按三个零件的实际尺寸(主控 25.4×20.3 mm、锂电池 60×18 mm、按钮直径 12 mm)建了一个圆柱壳,把按钮顶在最上面并做了一圈凸起的护边——这样在口袋里被钥匙顶到也不会触发。打印时用的是 0.2 mm 层高、20 % 填充,打完发现按钮孔小了 0.3 mm,用锉刀扩了一下才装进去。这是“图纸尺寸”和“实物尺寸”的差别,光看模型是发现不了的。
1.5 和已有的做法比,差在哪
做查新时用了三个库:往届大赛获奖作品、中国知网、百度搜索引擎,检索词包括“居家安全卫士”“智能家居报警”“随身一键报警”“便携报警装置”等,筛出六篇最接近的文献。结论整理成下表:
文献做法 | 它做到了什么 | 它缺了什么 |
基于 GSM 的火灾检测报警(STC89C52 + SIM800C) | 可燃气体与烟雾监测,短信远程报警 | 只针对火警,没有防盗,也没有随身部分 |
51 单片机 + 温度/烟雾的火灾报警 | 超阈值时语音播报、指示灯亮 | 同上,功能单一 |
单片机 + 多传感器 + 手机客户端的火灾报警 | 室内数据上传手机显示 | 仍是单场景,没有远程控制 |
OneNET 云平台的简易智能家居 | 上传数据、下发指令、按键控灯 | 用的是平台私有协议 EDP,只能配该平台 |
STM32 + 多传感器的智能家居 | 环境数据上云、联动控制 | 主控成本高,无防盗与随身报警 |
Arduino Mega2560 无线安防 | 一氧化碳、温湿度、声音、液位多路监测 | 只有本地提醒,没有远程报警 |
对比下来,这六篇的共同点是“一个场景 + 一类报警”,而本项目的差异在三点:对象是独居女性这个具体人群;功能上把居家和随身连成一套;技术上用 MQTT 这个公开协议,换平台不用重写程序。检索中没有发现与本课题完全相同的报道。
1.6 对应哪些课程内容
学科 | 对应内容 |
信息科技 | 物联网的三层结构(感知、网络、应用);MQTT 发布—订阅模型;JSON 数据格式;顺序、分支、循环与中断;移动应用的界面与事件 |
物理 | 电路的串联与并联;上拉电阻与开关量;传感器的信号形式(模拟量与数字量);电池与功耗 |
数学 | 阈值与区间;数据的记录与统计;用表格比较方案 |
劳动 / 综合实践 | 元件焊接与接线;3D 建模与打印;产品外壳的结构设计;测试与迭代 |
道德与法治 / 安全 | 独居安全与自我保护;报警信息的真实性与责任;技术不能替代报警电话 |
二、运行演示
2.1 居家卫士:一面墙上的七个器件
图 1 是用来做测试的模型房间:左边是厨房操作台,中间是窗户,右边是门。主控和蜂鸣器、LED、继电器装在操作台上方的板子上,几个传感器分散贴在需要监测的位置——门上和窗上各一个霍尔传感器,燃气和烟雾传感器挂在灶台上方,温湿度传感器放在水槽边,火焰传感器放在台面角落。
图 1 居家卫士的安装位置:门窗各一个霍尔传感器,灶台上方为 MQ-2,水槽边为 DHT22,台面角落为火焰传感器
把这些东西分开装而不是集中在一个盒子里,是被实际情况逼出来的:烟雾会往上飘,所以必须挂在灶台上方;水往低处流,所以温湿度传感器要贴着地面放;火焰传感器的探测角只有 60 度,得斜对着可能起火的台面。“传感器装在哪”和“传感器买哪个”一样重要,位置错了,再好的传感器也测不到。
2.2 随身卫士:一个白色圆柱和一个按键
图 2 是随身卫士的成品。外壳是 3D 打印的,顶面那颗黑色按钮就是 SOS 键,按钮周围有一圈凸起的护边,防止在口袋里被误按。里面装着一块 FireBeetle ESP32-E、一块 3.7 V 锂电池和一个小蜂鸣器。
图 2 随身卫士成品:3D 打印外壳、顶部 SOS 按钮(带防误触护边)、内置锂电池
它没有屏幕、没有扬声器、没有 GPS——这些都是故意的。第一,口袋里的东西越小越好,功能越多越容易坏;第二,真正的呼救场景里,人多半已经慌了,能做的动作只有一个;第三,少一个部件就少一处耗电,锂电池能撑更久。原计划还要发一条短信到 12110 并附上当前位置,这一部分没有做完,原因写在第六章。
有人问过:手机上不是也能一键报警吗,为什么还要多带一个东西?试过之后我的答案是——手机的一键报警要先解锁、找到图标、点进去、按住不放,四步;而这个装置是一步。更重要的是,手机在那种时刻往往不在手上:可能掉在地上,可能被抢走,可能早就没电。把“报警”这件事从手机上分离出来,本身就是这个装置存在的理由。
2.3 开门:磁铁一分开,两边同时响
霍尔传感器的工作原理是“磁铁靠近时输出一种电平,离开时输出另一种”。把它的一半贴在门框上、另一半贴在门上,门一开,两半分开,电平就变了——这比机械触点开关的好处是不怕磨损,也不会因为门框变形而接触不良。
图 3 开门测试现场:门被打开后,主控板上的 LED 点亮、蜂鸣器持续鸣响
图 4 是同一次测试中手机端的截图。左边是开门时的状态:门窗那一格变成红色并显示“门开了”,下面的“报警开启”开关是打开的;右边是另一次着火测试的画面,火焰图标变红。两个图标的排布、颜色是一致的,红色只代表这一项异常,绿色代表正常。
图 4 手机端在两种报警下的界面:左为门窗被打开,右为检测到火焰;红色代表该项异常
2.4 漏水:往传感器上喷水雾
漏水这件事很难“真的漏一次”来验证,因为模型房间没有上下水。测试时用喷壶对着 DHT22 喷了几下,湿度读数迅速升高并越过 85 % 的阈值,装置随即报警,手机端对应图标变红(图 5 左)。这个做法有局限:真实的漏水是“地面出现积水”,而湿度上升只是“空气变潮”,两者不完全等价。更可靠的做法是在地面放一对金属触点,靠水的导电性导通——这一条已列入改进项。
着火测试则用了真实的明火:在火焰传感器前方点燃一小片纸,传感器在 1 米内、60 度角范围内能识别到,装置立刻报警(图 5 右)。测试时旁边备了一杯水,全程有人看着。
图 5 左:向温湿度传感器喷水雾模拟漏水;右:点燃纸片模拟着火,火焰传感器报警
2.5 随身:按一下,手机就响
随身卫士的测试最直接:把装置放进口袋,走开几米,按一下顶部的按钮,看手机有没有反应。图 6 是按键后手机端的样子——App 弹出告警,同时振动并播放提示音。
图 6 按下随身卫士的 SOS 按钮后,手机端收到呼救并弹出提示
这一路上最花时间的其实不是程序,而是“按一下只发一次”。最初的写法是检测到按下就发,结果按住不放会连续发几十条,手机被刷屏。后来改成“按住也只算一次,两次之间至少隔 3 秒”,这个问题才解决——这就是程序里的“去抖”,一个在课本上只要一句话、在实物上却能卡住一整天的问题。
2.6 App:四个图标和一个总开关
图 7 是 App 的整体界面,也是我给它写的“说明书”。最上面是联网状态和“选择”下拉框;中间是四个状态图标(火焰、燃气、漏水、门窗);下面一排是报警总开关、以及“发送短信到 12110”的按钮位置。
图 7 手机 App 界面与各部分的作用说明
做界面时我犯过一个错:一开始把四个传感器数值直接显示在屏幕上,觉得“数据越多越专业”。给家里人试用时,他们的第一反应是“这些数字是什么意思”。后来才改成图标 + 颜色,把数字藏进二级页面。这让我明白一件事:给普通人做的界面,判断结论要比原始数据靠前。
三、探究过程与结果
3.1 选板子:为什么是 FireBeetle ESP32-E
主控要满足四个条件:带 Wi-Fi(要连 MQTT)、引脚够用(至少 5 路传感器 + 3 路输出)、体积小(要塞进圆柱外壳)、便宜。比较了三种常见方案之后选了 FireBeetle ESP32-E:它是 ESP-WROOM-32E 双核芯片,支持 Wi-Fi 和蓝牙双模,板载充电电路(可以直接给锂电池充电,省掉一个充电模块),而且完全兼容 Arduino 开发环境。
图 8 主要元件:FireBeetle ESP32-E 主控、火焰传感器、DHT22 温湿度传感器、霍尔磁性传感器
四个传感器的分工是这样的:霍尔传感器测“门窗开合”,MQ-2 测“燃气和烟雾”,DHT22 一身二用——温度测“高温”、湿度测“漏水”,火焰传感器测“明火”。这里有一个当时没想到的取巧:一根 DHT22 顶了两个功能。原因很朴素——多一个传感器就多一路线、多一处故障点、多一份钱,能复用的就复用。
3.2 系统怎么搭:三端一云
整套系统的数据流是“两端只和云说话,彼此不直接连接”(图 9)。居家卫士和随身卫士都把消息发布到各自的 Topic 上,阿里云按事先配好的规则转给手机;手机下发的开关指令也先到云,再由云转给居家主控。
图 9 系统结构:居家端、随身端、云平台、手机 App 四部分,实线为发布,虚线为订阅
三个 Topic 的分工:sas_sensorData 走居家的传感器数据,sas_SOS 走随身的呼救,sas_setAlarm 走手机的开关指令。分开而不是合用一条,是因为三条消息的“性格”不同——传感器数据是高频、定时的,呼救是低频、突发的,指令是人工触发的。混在一条主题里,订阅方就没法只挑自己想要的那一类。
这里还有一个当时纠结了很久的选择:阿里云提供了两种上报方式,一种是“物模型”(先把温度、湿度这些属性在平台上登记成标准格式,再按格式上报),一种是“自定义 Topic”(自己定主题名,内容自己写)。物模型的好处是平台能自动生成看板、能做数据存储,但要求每个字段先定义清楚类型和范围;自定义 Topic 更自由,改字段不用回平台改配置。最后选了后者,原因是这个项目还在频繁改数据结构——先定死格式,反而每改一次都要去平台上动一遍。
3.3 居家的判断逻辑
图 10 是居家主控的程序流程。核心只有一个判断:五个读数里有没有任何一个越过阈值。有就立刻在现场声光报警,同时把数据打包发到云上;没有就照常上报,让 App 显示“正常”。这里我特意把“现场报警”放在“上报”前面——因为网络随时可能断,而人在家里的时间最多。
图 10 居家卫士的判断流程:先本地报警,再上报云端;手机的关闭指令从右侧单独下发
监测项 | 传感器 | 判据 | 阈值 | 为什么是这条线 |
门窗被打开 | 霍尔 ×2 | 电平变化(磁铁离开) | 断开即报警 | 只有开/关两个状态,不需要阈值 |
燃气、烟雾 | MQ-2 | 模拟量(0—4095) | > 400 | 厨房正常烹饪油烟约 150—300,取 400 留余量 |
明火 | 火焰传感器 | 数字量 | 有信号即报警 | 探测角 60°、1 米内,避开灶台直射 |
高温 | DHT22 温度 | 摄氏温度 | > 45 ℃ | 低于此值夏天不开空调也会误报 |
漏水 | DHT22 湿度 | 相对湿度 | > 85 % | 南方回南天可到 80 %,取 85 % 减少误报 |
3.4 随身的判断逻辑
随身端的程序比居家端短得多(图 11):每 100 毫秒看一次按钮,按下了就蜂鸣一声、发一条 sas_SOS。没有“确认识别”“重试”“上传位置”这些步骤——不是没想到,而是在这个场景里,多一步就多一次失败的机会。
图 11 随身卫士的流程:只有一个判断,按下即上报;短信与位置发送尚未完成
为了省电,程序里没有用“中断”,而是用“定时轮询 + 长间隔”。理论上中断更省电,但在实测中发现按钮的机械抖动会让中断误触发十几次,反而更费电。把轮询间隔放到 100 毫秒,实际响应延迟还不到人能感觉到的程度。
3.5 云平台:四步配置
云平台的配置全部在浏览器里完成,一共四步(图 12):创建产品、添加设备、定义 Topic、配置消息流转。这四步的顺序不能颠倒——没有产品就没有设备,没有设备就没法给 Topic 授权,没有 Topic 就没法写流转规则。
图 12 阿里云物联网平台的四步配置:产品 SandS、三台设备、三个自定义 Topic、三条流转规则
三台设备的命名有讲究:sas_esp32 是居家主控,sas_sos_esp32 是随身主控,sas_app 是手机。把手机也当成一台“设备”登记进平台,是我做完才想明白的做法——这样手机和两块板子在平台眼里是平等的,都能发布、都能订阅,流转规则写起来也就对称了。
3.6 App 怎么和云说话
App 端用的是 App Inventor,全部是拖积木,不写代码。核心只有三块积木:
· 第一块 连上阿里云:填入 MQTT 服务器地址、端口、ClientID、用户名和密码(由三元组算出来),点“连接”;连上以后订阅 sas_sensorData 和 sas_SOS 两个主题。
· 第二块 收到消息:当 MQTT 组件收到消息时,把 JSON 拆开,逐个判断哪个字段是 1,对应图标就换成红色、播放提示音、手机振动。
· 第三块 发出指令:点总开关时,向 sas_setAlarm 发布一条 {"alarm":1} 或 {"alarm":0}。
这三块积木对应的是“连接—接收—发送”三个动作,和单片机端的结构完全一致。我一开始不理解为什么要这么对称,后来改代码时才明白:两端结构一样,出问题时就能一处一处对着查,不用在两种思维之间来回切换。
3.7 外壳:从尺寸到实物
随身装置的外壳在 3DMAX 里建模,按三个零件的实际尺寸确定内腔:主控板 25.4×20.3 mm、锂电池 60×18 mm、按钮直径 12 mm,壁厚取 2 mm。模型里特意留了两处:一处是按钮孔上方的护边,一处是电池仓的横向筋条——前者防误按,后者防晃动。
图 13 左:3D 打印完成的零件(壳体、顶盖、按键帽);右:装配后的壳体与顶盖
打印用 0.2 mm 层高、20 % 填充。第一次打印失败了两回:第一回按钮孔小了 0.3 mm,按键按下去弹不回来;第二回壳体太薄,装电池时裂了一道缝。第二版把壁厚从 1.6 mm 加到 2 mm、孔放大 0.3 mm 才装得上。这件事让我第一次意识到:图纸上的尺寸和手里的实物之间,隔着打印机的误差。
3.8 组装
图 14 是居家卫士背面的接线。走线是这个项目里最不“高级”但最耗时的部分:五路传感器、三路输出,加上电源,一共十几根线,全部挤在一块 8 cm 宽的板子后面。第一版走线是“就近插”,结果烟雾传感器的线被门夹断过一次;第二版改成“按功能捆扎、留出活动余量”,才稳定下来。
图 14 居家卫士背面接线:主控、扩展板与各路传感器的连线按功能捆扎
装的时候总结出三条经验,写在这里给复刻的人:电源线(红黑)单独走一边,不要和信号线绞在一起;每根线两端都贴标签,否则拆一次就再也认不出来;接插件比直接焊更可靠——这个项目里有三个传感器是换过的,用接插件换起来只要十秒。
3.9 测试记录
测试采用白盒测试法:已知程序里每个判断条件的位置,逐个构造能触发它的条件,看输出是否符合预期。七项测试的记录如下:
序号 | 测试项 | 怎么触发 | 现场反应 | 手机端 | 结论 |
1 | 门窗被打开 | 把门上霍尔传感器的磁铁移开 | 蜂鸣器持续鸣响、LED 亮 | 门窗图标变红 | 通过 |
2 | 漏水 | 用喷壶向 DHT22 喷水雾 | 同上 | 漏水图标变红 | 通过 |
3 | 着火 | 在传感器前点燃纸片 | 同上 | 火焰图标变红 | 通过 |
4 | 随身呼救 | 按一下随身装置的 SOS 键 | 蜂鸣器短鸣一声 | 收到呼救并弹窗 | 通过 |
5 | 远程开关 | 在 App 上点总开关 | 蜂鸣器状态随之改变 | 开关状态同步 | 通过 |
6 | 燃气泄漏 | —— | —— | —— | 未测 |
7 | 高温 | —— | —— | —— | 未测 |
第 6、7 两项没测,原因很实在:模型房间里没有燃气,也不可能把房间加热到 45 ℃ 以上。这两项的代码和电路都已经接好,但“接好”和“验过”是两件事——所以这一栏我写的是“未测”,而不是“通过”。同样地,结论一章里也不再写“已实现漏气报警”,改成“已实现、未实测”。
四、软硬件清单与制作步骤
4.1 元件与工具
类别 | 名称 | 数量 | 说明 |
主控 | FireBeetle ESP32-E | 2 | 居家、随身各一块;板载锂电池充电电路 |
传感器 | 霍尔磁性传感器 | 2 | 门窗各一;磁铁与探头分离安装 |
传感器 | MQ-2 烟雾/燃气传感器 | 1 | 模拟量输出,需接 A 口 |
传感器 | DHT22 温湿度传感器 | 1 | 数字量输出,一根线顶两个功能 |
传感器 | 火焰传感器 | 1 | 数字量输出,探测角 60° |
输入 | SOS 按钮(带帽) | 1 | 随身装置顶部;建议用带护边的安装方式 |
输出 | 有源蜂鸣器、LED、继电器模块 | 各 1—2 | 继电器可接灯或排风扇,做联动 |
电源 | 3.7 V 锂电池(2800 mAh) | 1—2 | 随身装置供电 |
结构 | 3D 打印外壳(PLA) | 1 套 | 0.2 mm 层高,20 % 填充,壁厚 2 mm |
工具 | 面包板、杜邦线、电烙铁、热熔胶 | 若干 | 接线与固定 |
软件 | Arduino IDE、App Inventor 2019、3DMAX | —— | 均为免费软件 |
4.2 接线
元件 | 接到主控 | 引脚类型 | 备注 |
霍尔传感器(门) | D5 | 数字输入 | 磁铁靠近/离开各一种电平,安装前先用万用表确认 |
霍尔传感器(窗) | D6 | 数字输入 | 同上 |
MQ-2 烟雾/燃气 | A0 | 模拟输入 | 需要用螺丝刀调板上的电位器,先测无烟时的底数 |
DHT22 温湿度 | D4 | 数字输入 | 数据脚需 4.7 kΩ 上拉到 3.3 V |
火焰传感器 | D2 | 数字输入 | 板上有电位器可调灵敏度 |
蜂鸣器 | D7 | 数字输出 | 有源蜂鸣器,给高电平即响 |
LED | D8 | 数字输出 | 串 220 Ω 限流电阻 |
继电器 | D9 | 数字输出 | 接灯或排风扇,注意市电部分必须断电操作 |
SOS 按钮(随身) | D3 | 数字输入 | 启用内部上拉,按下为低电平 |
4.3 十二步制作流程
第 1 步 读协议:先把 MQTT 的“发布—订阅”弄明白,画一遍三端数据流。这一步不做,后面全是盲抄。
第 2 步 配云:在阿里云创建产品、三台设备、三个自定义 Topic,记下三元组(只记在本地,不要外传)。
第 3 步 点灯:先只让主控连上 Wi-Fi 并成功连到 MQTT,能在平台上看到“在线”,再做别的。
第 4 步 单路上报:接一个传感器,把读数发布到 sas_sensorData,在平台的日志里能看到 JSON。
第 5 步 加传感器:逐个加:霍尔、MQ-2、DHT22、火焰。每加一个,单独验证一次底数与阈值。
第 6 步 本地报警:加蜂鸣器和 LED,先让“本地”完整可用——断网也能响,这是底线。
第 7 步 搭 App:在 App Inventor 里先只做“连接 + 接收”,把收到的 JSON 原样显示出来。
第 8 步 做图标:把 JSON 里的字段映射成四个图标的红/绿,加提示音与振动。
第 9 步 下发指令:做总开关,测试从 App 到主控的反向链路。
第 10 步 做随身端:同一套代码删掉传感器部分,只留按钮与 sas_SOS 发布,加去抖。
第 11 步 打外壳:先打一个“试装件”,确认孔位和螺纹,再打成品。不要一次打全套。
第 12 步 整机测试:按第三章的七项逐条测,把“通过/未测/不通过”如实记下来。
4.4 想复刻的话,可以更便宜
这套东西的成本主要在两块主控上。如果只是想复现功能、不追求体积,可以换成更便宜的替代:
· 主控替代 ESP32 开发板(约二十元)或 ESP8266,同样支持 Wi-Fi 和 MQTT,Arduino 代码几乎不用改,代价是体积变大、没有板载充电电路。
· 云平台替代 阿里云物联网平台有免费额度,个人做实验够用;也可以换成开源的 EMQX 自建服务器,但要自己解决公网访问和外网穿透。
· App 替代 App Inventor 可以换成 MIT 的另一款工具或直接用手机浏览器打开一个网页,后者最省事但拿不到振动和后台提醒。
· 外壳替代 没有 3D 打印机可以用现成的塑料盒开孔,或者用亚克力板 + 铜柱。外壳first作用是“防止误触和短路”,美观是第二位的。
五、开源代码
以下代码按“连接—判断—下发”三段整理,与实际烧录的程序一致,只是把三元组和 Wi-Fi 密码换成了占位符。复刻时请填入自己的参数,不要使用本文档中的任何示例值。
5.1 居家端(一):连接 Wi-Fi 与 MQTT
#include <WiFi.h> #include <PubSubClient.h> #include "DHT.h"
// ---- 配网信息:复刻时改成自己的 ---- const char* WIFI_SSID = "你的热点名"; const char* WIFI_PASS = "你的热点密码";
// ---- 阿里云三元组:复刻时改成自己的 ---- const char* PK = "你的ProductKey"; const char* DN = "sas_esp32"; const char* DS = "你的DeviceSecret";
const char* HOST = "你的ProductKey.iot-as-mqtt.cn-shanghai.aliyuncs.com"; const int PORT = 1883; // ClientID 格式固定:设备名|安全模式|签名方法| String CLIENT_ID = String(DN) + "|securemode=3,signmethod=hmacsha1|"; String USERNAME = String(DN) + "&" + PK; // 密码由三元组经 HMAC-SHA1 算出,这里填预先算好的字符串 const char* MQTT_PASS = "预先算好的密码";
// 三个自定义 Topic(与云平台配置一一对应) String TOPIC_DATA = String("/") + PK + "/sas_esp32/user/sensorData"; String TOPIC_CMD = String("/") + PK + "/sas_esp32/user/setAlarm";
WiFiClient net; PubSubClient client(net); DHT dht(4, DHT22); // 温湿度传感器接 D4
void connectWifi() { WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { // 连不上就等,不往下走 delay(500); } }
void connectMqtt() { while (!client.connected()) { // 密码由三元组经 HMAC-SHA1 算出,这里用预先算好的字符串 if (client.connect(CLIENT_ID.c_str(), USERNAME.c_str(), MQTT_PASS)) { client.subscribe(TOPIC_CMD.c_str()); // 只订阅手机下发的指令 } else { delay(2000); // 失败等 2 秒重连,避免刷屏 } } } |
5.2 居家端(二):读传感器、判断、上报
#define PIN_HALL 5 // 门窗霍尔 #define PIN_MQ2 A0 // 烟雾/燃气 #define PIN_FLAME 2 // 火焰 #define PIN_DHT 4 // 温湿度 #define PIN_BUZZ 7 // 蜂鸣器 #define PIN_LED 8 // 指示灯
const int SMOKE_TH = 400; // 烟雾阈值,先测无烟底数再定 const float TEMP_TH = 45.0; // 高温阈值(℃) const float HUMI_TH = 85.0; // 漏水阈值(%) bool armed = true; // 总开关:离家时开,回家可关
unsigned long lastPub = 0;
void loop() { if (!client.connected()) connectMqtt(); client.loop();
int door = digitalRead(PIN_HALL); int smoke = analogRead(PIN_MQ2); int flame = digitalRead(PIN_FLAME); float t = dht.readTemperature(); float h = dht.readHumidity();
// 五个条件任一成立即报警;armed 为假时只上报、不响 bool alarm = (door == HIGH) || (smoke > SMOKE_TH) || (flame == LOW) || (t > TEMP_TH) || (h > HUMI_TH); bool ring = alarm && armed;
digitalWrite(PIN_BUZZ, ring ? HIGH : LOW); digitalWrite(PIN_LED, ring ? HIGH : LOW);
if (millis() - lastPub > 2000) { // 每 2 秒上报一次 lastPub = millis(); char buf[200]; snprintf(buf, sizeof(buf), "{\"door\":%d,\"smoke\":%d,\"flame\":%d,\"temp\":%.1f,\"humi\":%.0f,\"alarm\":%d}", door, smoke, flame, t, h, alarm ? 1 : 0); client.publish(TOPIC_DATA.c_str(), buf); } } |
5.3 居家端(三):接收手机的开关指令
// 云把手机的消息转过来时,这个函数会被调用 void callback(char* topic, byte* payload, unsigned int len) { String msg = ""; for (unsigned int i = 0; i < len; i++) msg += (char)payload[i];
if (String(topic) == TOPIC_CMD) { // 只认 alarm 这一个字段,其余内容一律忽略 if (msg.indexOf("\"alarm\":1") >= 0) armed = true; else if (msg.indexOf("\"alarm\":0") >= 0) armed = false; } }
void setup() { Serial.begin(115200); pinMode(PIN_HALL, INPUT_PULLUP); pinMode(PIN_FLAME, INPUT); pinMode(PIN_BUZZ, OUTPUT); pinMode(PIN_LED, OUTPUT); dht.begin();
connectWifi(); client.setServer(HOST, PORT); client.setCallback(callback); connectMqtt(); } |
5.4 随身端:一个按键,一条呼救
#define PIN_BTN 3 // SOS 按钮,内部上拉,按下为 LOW #define PIN_BUZZ 7
unsigned long lastPress = 0;
void setup() { Serial.begin(115200); pinMode(PIN_BTN, INPUT_PULLUP); // 不按为高电平,按下为低电平 pinMode(PIN_BUZZ, OUTPUT); connectWifi(); client.setServer(HOST, PORT); connectMqtt(); }
void loop() { if (!client.connected()) connectMqtt(); client.loop();
int btn = digitalRead(PIN_BTN);
// 去抖:两次按键之间至少隔 3 秒,避免按住不放刷屏 if (btn == LOW && millis() - lastPress > 3000) { lastPress = millis();
digitalWrite(PIN_BUZZ, HIGH); // 短鸣一声,告诉使用者"已经发出去了" delay(120); digitalWrite(PIN_BUZZ, LOW);
client.publish(TOPIC_SOS.c_str(), "{\"sos\":1,\"ts\":0}"); } delay(100); // 100 ms 轮询一次 } |
5.5 App 端:三块积木
App 端全部用 App Inventor 2019 桌面版拖积木完成,不涉及文本代码。三块积木的逻辑如下:
积木 | 触发时机 | 做的事 |
连接 | 点“连接”按钮 | 设置 MQTT 组件的服务器地址、端口、ClientID、用户名、密码,调用 Connect;连上后订阅 sas_sensorData 与 sas_SOS |
接收 | MqttClient.MessageReceived | 把收到的字符串按分号或字段名拆开,逐个判断字段值是否为 1;为 1 就把对应图标换成红色图片、播放提示音、调用振动器 |
发送 | 点总开关 | 向 sas_setAlarm 发布 {"alarm":1} 或 {"alarm":0} |
这三块积木和单片机端的三段代码是一一对应的:连接对连接、接收对回调、发送对发布。两端结构一致,调试时可以在任何一端先确认“消息有没有发出去/收到”,而不用整条链路一起猜。
六、优化记录与失败尝试
下表是这个项目从初版到现在的修改记录,包括还没改完的。写这一章的原因很直接:做的时候犯的错,比做对的部分更有信息量。
问题 | 当时怎么想的 | 现在怎么处理 |
门窗用的是霍尔传感器,报告里却写成“门窗震动报警” | 凭印象写的,没对照元件 | 【已改】统一为“门窗开合检测”——霍尔测的是磁铁分离,测不到震动 |
同一个主题出现两个名字:正文写 sas_setAlarm,截图里是 sas_stopAlarm | 改名后没有回头改截图 | 【已改】全文统一为 sas_setAlarm,截图重做 |
图号重复:图 2-4 与图 2-5 各出现两次,顺序也颠倒 | 边写边插图,没做统一编号 | 【已改】全部重排为图 1—图 14 |
3.7.4 标题误写“随着卫士测试” | 拼音输入错误,没校对 | 【已改】改为“随身卫士测试” |
摘要说可以“给 12110 发短信报警”,结论又说短信没调通 | 摘要写的是设想,结论写的是实际 | 【已改】摘要改为“计划支持”,结论保留“未完成”,两处口径一致 |
结论列了“已实现高温报警、漏气报警”,测试章却写这两项没测 | 把“代码写好了”当成“功能实现了” | 【已改】统一为“已实现、未实测” |
阿里云三元组和 Wi-Fi 密码直接印在截图与代码里 | 当时不知道这是敏感信息 | 【已改】材料中全部改为占位符;原密钥建议到控制台重置 |
报警只在现场响,断网后手机收不到任何提示 | 默认认为网络总是通的 | 【已改】把本地声光报警放在上报之前,断网也能响 |
按钮按住不放会连续发送几十条呼救 | 没考虑机械抖动和长按 | 【已改】加 3 秒去抖,两次之间只算一次 |
阈值写死在代码里,改一个数就要重新烧录 | 图省事 | 【待改】下一步把阈值做成云端下发,用 sas_setAlarm 同一条链路配置 |
漏水用湿度上升来判断,空气潮湿也会误报 | 模型房间没有上下水,只能这么凑 | 【待改】改用地面积水触点,靠水的导电性导通 |
还有两件事到现在也没解决,写在这里以免被含糊过去:
一是短信报警。原计划让云平台直接调用短信服务,把告警发到 12110 或事先设置的联系人。实际配置时卡在两点:短信签名和模板需要审核,个人开发者不容易通过;报警短信的内容属于特殊类别,平台有额外限制。所以目前只做到 App 弹窗,短信这一路是空的。
二是位置信息。随身装置没有 GPS 模块,原本打算用手机端上报的位置来代替,但这样又依赖手机在不在身边——而按下随身按钮的前提,往往正是拿不到手机。这个矛盾目前没有好的解法,可能的方案是加一块 NB-IoT 模组直接基站定位,成本和功耗都会上一个台阶。



