1. 项目概述:一个更聪明的家庭药箱

家里有需要长期服药的老人,或者自己就是个“健忘星人”的朋友,大概都经历过这种场景:药到底吃了没?是今天早上吃的还是昨天?这瓶药还剩多少,是不是该买了?更让人担心的是,万一不小心多吃了一次,会不会有风险?传统的分格药盒解决了分装问题,但解决不了“记忆”和“监督”的问题。这正是我动手做这个“IoT智能药盒”的初衷——它不止是个盒子,更是一个守在身边的用药小管家。

这个项目的核心,是给一个普通的药盒装上“大脑”和“感官”。通过物联网技术,让它能定时提醒吃药,记录每一次的服药动作,并且最关键的是,它能基于处方设定,智能地管理药品发放,从物理结构上防止过量服用。想象一下,它就像一个尽职的护士,到点会轻声提醒你,你取药的动作会被默默记下,形成一份清晰的用药日志供你或家人查阅。而当今天的药量已经取完后,药盒会“锁死”,拒绝再提供药品,直到下一个用药周期。这对于需要服用多种药物、记忆力减退的用户,或者需要远程关注家人用药情况的子女来说,实用性非常强。

整个系统可以分为三层:最底层是药盒本身的硬件,包括机械结构、传感器和控制器;中间层是负责逻辑处理和通信的“大脑”;最上层则是你手机上的App或者网页界面,用于设置和查看。我会带你从零开始,一步步拆解设计思路、硬件选型、代码编写,直到最后组装调试,并分享我在这个过程中踩过的所有坑和收获的经验。无论你是物联网爱好者、嵌入式开发者,还是仅仅想为家人做点什么的动手达人,相信都能从中获得可以直接复现的完整方案。

2. 核心设计思路与方案选型

做一个智能药盒,听起来简单,但细想下来涉及的选择点很多。是做一个大药箱管理所有药瓶,还是做成便携式分格药盒?提醒方式用声音、灯光还是震动?防过量的机制是电子锁还是机械限位?通信方式用Wi-Fi直接连家里路由器,还是用蓝牙连手机做中转?每一个选择都关系到成本、复杂度、可靠性和用户体验。

2.1 产品形态与功能定义

我最终决定设计一个 桌面式、多药格(以7格为例,对应一周七天)、带物理锁闭机构 的药盒。选择桌面式而非便携式,主要基于供电和通信稳定性的考虑:它可以常接电源,并使用家庭Wi-Fi,确保提醒和上报永不掉线。一周七格的设计最符合日常用药习惯,方便每周一次性备药。核心功能明确为四点:

  1. 定时提醒 :在预设的服药时间点,通过声光(蜂鸣器+LED)进行本地提醒,并同步推送手机通知。
  2. 服药记录 :自动检测并记录用户打开药格取药的动作,生成不可篡改的日志。
  3. 防过量机制 :在当天该药格的药品已被取走后,自动锁死该药格,直至次日零点重置。
  4. 存量预警 :根据处方用量和当前剩余药量,在药品即将用完前(如剩余3天量时)发送预警通知。

这个定义避免了华而不实的功能,聚焦于最核心的安全与依从性痛点。防过量是安全底线,必须通过硬件机制实现,而不能仅仅依赖软件提示。

2.2 硬件平台选型解析

硬件是项目的骨架,选型决定了项目的上限和难度。

主控制器 :在ESP32、树莓派Pico、STM32之间,我选择了 ESP32 。原因很直接:它集成了Wi-Fi和蓝牙,双核处理器性能对于本项目绰绰有余,且有庞大的Arduino和MicroPython生态支持,开发效率极高。型号上,ESP32-S3或ESP32-C3都是不错的选择,我用的是一款常见的ESP32-DevKitC开发板,引脚丰富,便于调试。

传感器与执行器

  • 药格状态检测 :这是关键。方案有红外对管、霍尔传感器+磁铁、微型限位开关等。红外对管(发射管与接收管分开)容易受灰尘和摆放位置影响;霍尔传感器非接触,但需要每个药格安装磁铁,结构稍复杂。我最终选择了 微型轻触开关 ,成本极低,可靠性高,物理接触的检测方式非常直接。当药格关闭时,开关被压下(导通);打开时,开关弹起(断开)。通过读取GPIO的高低电平即可判断状态。
  • 锁闭机构 :这是防过量功能的核心。需要一个小型、低功耗的锁具。舵机驱动的插销锁是一个选择,但舵机待机有电流,且体积较大。我选择了更优雅的方案: 微型电磁锁(推拉式电磁铁) 。它只在通电瞬间产生磁力拉动锁舌,锁住后断电即可依靠机械结构保持锁死状态,解锁时反向通电即可。功耗低,控制简单。
  • 提醒模块 :由一个有源蜂鸣器(声音提醒)和一颗RGB LED(灯光提醒)组成。RGB LED可以通过颜色变化指示不同状态(如待机绿色、提醒闪烁蓝色、错误红色)。
  • 用户交互 :增加一个实体按键,用于手动触发“我已服药”确认,或进入配网模式。

电源管理 :虽然接市电,但考虑到偶尔移动的可能,我增加了一块18650锂电池作为备用电源,并搭配TP4056充电模块和升压模块,实现充放电管理,保证断电后仍能正常工作一段时间。

2.3 通信与云端方案

物联网项目,连接是灵魂。我采用了经典的 “设备端 - 云平台 - 客户端” 三层架构。

  • 设备端(ESP32) :运行Arduino框架编写的固件,负责所有硬件控制和本地逻辑。
  • 云平台 :选用 阿里云物联网平台 。它提供了完善的设备管理、消息路由、数据存储和规则引擎服务。ESP32通过MQTT协议与平台通信,上报状态(开关事件)、接收指令(远程控制、定时设置)。选择它的理由是其稳定性、丰富的文档以及免费的额度对本项目完全足够。
  • 客户端 :为了快速验证,我最初使用了 钉钉群机器人 Server酱 这类工具来接收报警消息。后期可以开发一个简单的微信小程序或Flutter App,通过调用云平台API来获取数据、设置定时,体验会更完整。

注意 :在选择云平台时,务必考虑数据隐私和合规性。用药数据属于敏感个人信息,确保你选择的平台有相应的安全措施,或者对于高阶玩家,可以考虑使用开源方案(如EMQX + PostgreSQL)自建服务,将数据完全掌握在自己手中。

3. 硬件设计与组装要点

有了方案,接下来就是把想法变成实物。硬件部分是最需要耐心和动手能力的环节。

3.1 药盒本体改造与机械结构

我购买了一个现成的透明亚克力一周分格药盒作为基础。改造的核心是在每个药格的盖子上安装检测开关,并在盒子主体内部设计锁闭机构。

  1. 开关安装 :在每个药格盖子内侧的合适位置,开一个小孔,用于固定微型轻触开关。开关的按钮部分应略微凸出,确保盖子盖紧时能被完全压下。开关的两根引线(常开触点)连接到ESP32的GPIO口,并配置为上拉输入模式。这样,盖子关闭时GPIO读到低电平,打开时读到高电平。
  2. 电磁锁安装 :这是最精巧的部分。需要在每个药格的下方或侧面,设计一个锁舌孔。我使用3D打印了一个锁舌固定座,将微型电磁锁(通常是5V或12V规格)固定在内。锁舌的行程要精确计算,确保能恰好伸入药格底部预留的锁孔中,阻止药格被拉开。7个药格需要7把电磁锁,分别由ESP32的GPIO口通过三极管或MOS管驱动。
  3. 主控板安装 :在药盒背面或底部开辟一个空间,固定ESP32开发板、电源模块、蜂鸣器和LED。确保走线整洁,并用热熔胶或螺丝固定,避免运输或移动时松脱。

3.2 电路连接与电源管理

电路原理并不复杂,但布局和焊接需要细心。下面是一个简化的连接表:

组件 连接至ESP32引脚 备注
轻触开关 x7 GPIO 15, 2, 4, 16, 17, 5, 18 配置为 INPUT_PULLUP
电磁锁 x7 GPIO 23, 22, 21, 19, 13, 12, 14 通过NPN三极管(如S8050)驱动,基极串1k电阻
有源蜂鸣器 GPIO 27 高电平触发
RGB LED (共阳) GPIO 25 (R), 26 (G), 33 (B) 通过220Ω限流电阻
功能按键 GPIO 0 配置为 INPUT_PULLUP ,注意GPIO0有特殊启动功能
TP4056充电模块 接18650电池 电池正负极接B+/B-
升压模块 (至5V) 接TP4056输出端 为ESP32及整个系统供电

实操心得 :驱动电磁锁这类感性负载时,务必在每个电磁锁两端并联一个 续流二极管 (如1N4007),阴极接电源正极,阳极接锁的负极。否则,在断电瞬间产生的反向电动势极易击穿驱动三极管或ESP32的GPIO口。这是硬件设计中的一个经典“坑”。

电源路径 :市电USB适配器(5V/2A)作为主电源,同时给TP4056模块充电。TP4056的输出(电池电压)接入升压模块,升压至稳定的5V。然后,通过一个双路电源切换电路(可以用两个肖特基二极管实现)或简单的MOS管切换电路,优先使用市电,市电断开时自动切换到电池供电。确保系统不间断运行。

4. 固件开发与核心逻辑实现

硬件搭好了,接下来是赋予它灵魂的软件部分。我们将在Arduino IDE中为ESP32编写固件。

4.1 开发环境与库依赖

首先确保已安装ESP32的Arduino开发板支持。然后,我们需要引入几个关键库:

  • WiFi.h WiFiClientSecure.h :用于连接Wi-Fi和建立TLS加密连接。
  • PubSubClient.h :一个非常流行的MQTT客户端库,用于和阿里云IoT平台通信。
  • ArduinoJson.h :处理JSON格式的数据,这是与云平台交互的通用语言。
  • NTPClient.h :通过网络获取精确时间,用于定时任务和日志时间戳。

在代码开头,需要配置你的Wi-Fi凭证、阿里云设备的“三元组”(ProductKey, DeviceName, DeviceSecret)以及MQTT连接参数。

4.2 状态机与主循环设计

智能药盒是一个典型的事件驱动系统。我采用 状态机(State Machine) 模型来组织主程序逻辑,这比一堆 if-else 语句要清晰健壮得多。每个药格可以定义几个状态: LOCKED (锁定)、 UNLOCKED_READY (解锁待取药)、 OPENED (已打开)、 TAKEN (已服药)。

主循环 ( loop() ) 里只做几件事:

  1. 维持网络心跳 :调用 mqttClient.loop() 维持MQTT连接,处理订阅消息。
  2. 扫描输入 :快速扫描7个药格的开关状态和功能按键状态。
  3. 状态机驱动 :根据当前时间、定时设置和扫描到的事件(如“盖子打开”),驱动每个药格的状态转移。
  4. 执行输出 :根据状态,控制电磁锁、蜂鸣器、LED。例如,状态为 UNLOCKED_READY 时,电磁锁断电(解锁),LED闪烁蓝光;状态变为 TAKEN 后,电磁锁通电(锁定),LED常绿。

这种结构使得程序逻辑清晰,易于调试和扩展。

4.3 定时提醒与防过量逻辑

这是业务逻辑的核心。我们需要在ESP32上维护一个定时任务列表。例如,用户通过App设置了每天上午8点和晚上8点服药。

// 伪代码示例
struct MedicationSchedule {
  int hour;
  int minute;
  bool takenToday; // 今天该次药是否已服用
};

MedicationSchedule schedule[2] = {{8, 0, false}, {20, 0, false}};

void checkSchedule() {
  int currentHour = getCurrentHour(); // 从NTP获取
  int currentMinute = getCurrentMinute();
  
  for (auto &s : schedule) {
    if (!s.takenToday && currentHour == s.hour && currentMinute == s.minute) {
      // 触发提醒!
      triggerAlert();
      // 解锁相关药格(假设第一个药格)
      setCompartmentState(0, UNLOCKED_READY);
    }
  }
}

防过量逻辑 就体现在 takenToday 这个标志位上。当系统检测到药格从 UNLOCKED_READY 状态变为 OPENED (开关状态变化),并持续打开超过2秒(防止误触),则认为一次服药动作完成。此时,除了记录日志、上报云端外,最关键的一步是将对应定时任务的 takenToday 设为 true ,并立即 锁定该药格 。这样,在下次定时任务触发前,用户无法再次打开这个药格。 takenToday 标志会在每天凌晨0点由系统自动重置。

4.4 与阿里云IoT平台通信

通信部分主要工作是实现MQTT协议的连接、订阅和发布。阿里云IoT有特定的Topic格式和认证方式。

  1. 连接与认证 :使用设备三元组,按照阿里云文档计算MQTT连接的 clientId , username , password 。使用 WiFiClientSecure 建立TLS加密连接,端口8883。
  2. 上报属性与事件
    • 属性 :定时上报设备状态,如 {"Compartment_1_Status": "locked"} 。这对应于平台“物模型”中的属性。
    • 事件 :当关键动作发生时上报,如服药事件 {"event": "medication_taken", "compartment": 1, "time": "2023-10-27T08:01:23Z"} 。平台可以配置规则,将事件转发到钉钉或你的App。
  3. 接收指令 :订阅云端下发的Topic。例如,App设置新的服药时间后,云端会下发一条消息到设备。设备解析后,更新本地的定时任务列表。
// MQTT回调函数示例
void callback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (int i=0; i<length; i++) msg += (char)payload[i];
  
  DynamicJsonDocument doc(1024);
  deserializeJson(doc, msg);
  
  // 解析指令,例如设置时间
  if (String(topic).endsWith("service/set")) {
    int newHour = doc["params"]["hour"];
    int newMinute = doc["params"]["minute"];
    updateSchedule(newHour, newMinute);
    // ... 回复执行结果给云端
  }
}

注意事项 :网络通信是不稳定的。代码中必须加入重连机制。如果MQTT断开,应在 loop() 中尝试重连。所有重要的状态变更(如服药记录),最好能在本地进行缓存(如写入ESP32的SPIFFS文件系统),待网络恢复后重传,避免数据丢失。

5. 云端配置与客户端通知

设备端完成后,我们需要在云端搭建数据通道和业务逻辑。

5.1 阿里云IoT平台配置

  1. 创建产品与设备 :在物联网平台创建一个新产品,如“智能药盒”,并定义“物模型”。物模型就是设备的数字抽象,包括:
    • 属性 :药格1状态、药格2状态...电池电量、信号强度。
    • 服务 :设备可被调用的能力,如“设置服药时间”、“查询历史记录”。
    • 事件 :设备主动上报的信息,如“服药事件”、“低电量警报”。
  2. 配置规则引擎 :这是实现自动通知的关键。创建一个规则,监听“服药事件”。当事件触发时,规则引擎可以将数据 流转到函数计算(FC) ,也可以直接通过 云消息服务(MNS) HTTP推送 到一个Webhook地址。
  3. 数据存储 :为了长期查看用药历史,可以配置规则引擎将所有的“服药事件”自动存储到**表格存储(OTS) 时序数据库(TSDB)**中,方便日后按时间范围查询。

5.2 实现钉钉/App推送

对于快速原型,我推荐使用钉钉群机器人。

  1. 在钉钉群添加一个自定义机器人,获取它的Webhook地址。
  2. 在阿里云IoT规则引擎中,创建一个“数据转发”规则,将“服药事件”的消息内容,通过“HTTP推送”方式,发送到钉钉机器人的Webhook。
  3. 钉钉机器人接收到后,就能在群里发送一条格式化的消息:“【智能药盒提醒】爷爷已在08:00服用周一格药物。”

对于更专业的App,流程类似:

  1. 开发一个后端服务(可以用阿里云函数计算FC快速搭建),提供一个HTTP接口。
  2. 在IoT规则引擎中,将事件转发到这个HTTP接口。
  3. 后端服务收到事件后,一方面可以存入自己的数据库,另一方面可以通过 极光推送、个推 等第三方服务,或者苹果/谷歌的原生推送通道,将通知推送到已登录用户的手机App上。

5.3 用药历史查看与预警

历史数据存储在云端数据库后,我们可以开发一个简单的数据看板。

  • Web页面 :使用任何你熟悉的后端框架(如Flask, Express)搭配前端框架(如Vue, React),从数据库读取数据,展示成日历视图或折线图,一目了然地看到每天的服药情况。
  • 存量预警 :这个逻辑可以在云端实现。设备每次上报“服药事件”时,云端服务可以维护一个“药品剩余量”的计数器。当计数器低于预设的阈值(如3天用量)时,就触发一条“药品不足”的预警事件,并通过上述推送通道通知用户。这比在资源有限的设备端实现更灵活、可靠。

6. 系统调试与优化实录

把硬件、固件、云端都打通后,并不意味着大功告成。真实的家庭环境和使用场景会带来一系列挑战,调试和优化阶段往往比开发更花时间。

6.1 硬件调试常见问题

  1. 开关误触发 :轻触开关因为机械振动或轻微位移,可能产生抖动信号,导致程序误判为“开盖”。解决方法是在软件中加入 消抖(Debounce) 逻辑。不是一检测到电平变化就立刻响应,而是等待一个短暂的时间(如50毫秒),如果状态稳定,才确认事件。
    // 简化的消抖逻辑
    if (digitalRead(pin) != lastStableState) {
      unsigned long now = millis();
      if (now - lastDebounceTime > debounceDelay) {
        // 状态稳定,确认变化
        lastStableState = !lastStableState;
        // ... 触发状态机事件
      }
    } else {
      lastDebounceTime = now;
    }
    
  2. 电磁锁力量不足 :选购的电磁锁拉力不够,导致锁舌无法可靠锁死药格。除了更换更大拉力的锁体外,可以在机械结构上优化,比如减小锁舌与锁孔之间的摩擦,或者使用杠杆原理放大锁舌的作用力。
  3. 功耗与发热 :虽然主要接市电,但备用电池下的功耗仍需关注。确保在待机时,ESP32进入轻量级睡眠模式(Light Sleep),仅靠定时器或外部中断(如按键唤醒)来工作。同时,检查所有未使用的GPIO口应设置为输入上拉或下拉,避免悬空引起漏电。

6.2 网络与通信稳定性优化

  1. Wi-Fi断连重连 :家庭Wi-Fi环境并非绝对稳定。固件中必须实现健壮的Wi-Fi重连逻辑。不要只在 setup() 里连接一次。可以在 loop() 中检查连接状态,如果断开,则尝试重连,并加入指数退避策略,避免频繁重试刷爆日志。
  2. MQTT保活与遗嘱 :配置合理的MQTT心跳间隔(Keep Alive)。同时,设置遗嘱消息(Last Will),主题为设备离线Topic,内容为离线状态。这样一旦设备异常断开,云端能立刻知道,并可以在App上显示“设备离线”的提示。
  3. 数据上报策略 :不是所有数据都需要实时上报。对于开关状态这种高频信号,可以适当聚合,比如每10秒上报一次状态快照,或者只在状态发生变化时上报。对于“服药事件”这种关键数据,则采用“至少一次(QoS1)”的MQTT服务质量,确保送达。

6.3 用户体验细节打磨

  1. 提醒策略 :单纯的蜂鸣器响声可能被忽略或令人烦躁。我设计了一个渐进式提醒:到达服药时间后,先闪烁LED 30秒;若未取药,则LED闪烁加快并加入短促蜂鸣;5分钟后若仍未取药,则触发强烈的声光提醒并发送手机推送。取药后,LED变为绿色常亮10秒作为确认反馈。
  2. 手动确认与纠错 :考虑到用户可能打开药盒查看但并未取药,或者取药时动作太快传感器未捕捉到,我们增加了手动确认按键。长按按键3秒,可以手动将当前解锁的药格标记为“已服药”,并锁定。这提供了容错机制。
  3. 本地时钟容错 :完全依赖NTP在网络不好时会有问题。我在ESP32上启用了 SNTP(简单网络时间协议) 定期同步,同时辅以硬件RTC(实时时钟)芯片(如DS3231)作为后备。当网络不可用时,使用RTC的时间,虽然可能有微小漂移,但足以维持日常定时功能。网络恢复后,再同步校准RTC。

7. 项目总结与扩展思考

经过从设计到调试的完整循环,这个IoT智能药盒已经从一个想法变成了一个可以稳定工作的原型。它确实能有效地提醒服药、记录历史,并通过物理锁闭提供了实实在在的防过量保障。在给我家人试用的几周里,最大的感受是“安心”——远程能看到记录,知道该吃的药都按时吃了,而药盒本身的锁闭功能也杜绝了重复服药的隐患。

回顾整个过程,有几个关键点决定了项目的成败:一是防过量机制的硬件实现必须可靠,软件层面的提醒再强也不如物理隔离让人放心;二是网络通信的稳定性需要层层加固,从Wi-Fi重连到MQTT保活,再到数据本地缓存,缺一不可;三是用户体验的细节,比如渐进提醒、手动确认,这些看似小的设计,在实际使用中极大地提升了产品的可用性和接受度。

这个项目还有很多可以深化和扩展的方向。例如,可以增加重量传感器(如微型称重模块)来更精确地监测每次取出的药片数量,甚至识别是否取错药格;可以集成语音模块,用更自然的人声进行提醒;可以加入蓝牙功能,在手机靠近时自动同步数据或快速配置;更进一步,可以尝试加入一些简单的AI,通过分析长期的服药记录数据,对用户的依从性进行评估和预测。当然,每增加一个功能,都需要在复杂度、成本和可靠性之间做出新的权衡。

对于想要复现或借鉴这个项目的朋友,我的建议是:先从核心功能做起,把提醒、检测、防过量这三点做稳做透。硬件上不必追求一次完美,可以先用开发板和洞洞板搭建功能原型,验证逻辑无误后,再考虑设计PCB和定制外壳。软件上,状态机的思维模式会让你的代码结构清晰很多。最重要的是,带着解决真实问题的热情去做,每一次调试和优化,都让你离一个可靠、有用的产品更近一步。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐