手把手超详细教程:如何制作一款多功能智能药盒(三)

手把手超详细教程:如何制作一款多功能智能药盒(二)

  • 功能需求

    功能需求描述
    服药闹钟支持周定时。
    支持使用 App 设置服药时间。
    错过提醒定时时间到达后,蜂鸣器开始鸣叫。
    若未检测到药盒打开,则三分钟后继续提醒。
    服药提醒定时时间到达后蜂鸣器开始鸣叫。
    设备上报该 DP 给 App,提醒用户服药。用户可手动关闭提醒。
    药盒查找当按下“药盒查找”按钮时,蜂鸣器间隔发声。
    低电量报警检测到电量低于 10% 时,药盒开始报警。
    药仓模式药盒支持大仓、小仓两种不同模式,用户可根据实际需求手动切换。

    环境搭建

    产品开发

    1. 登录 涂鸦 IoT 开发平台产品创建页

    2. 在左侧 标准类目 中,选择 传感 > 时钟气象站 > 温湿度时钟

      说明:由于用到周定时功能,在 IoT 开发平台现有智能药盒品类下面没有周定时功能面板,因此选择支持周定时面板的品类。

    3. 完善产品信息。

      填写好产品名称,选择通讯协议为蓝牙低功耗,功耗类型为低功耗。
      关于创建产品,详情可查看 按品类创建产品

    4. 添加产品功能。

      1. 选择 DP ID 为 1、2、4、8、14、17、24、26、27 的标准功能并单击 确定 添加标准功能。

      2. 在 标准功能 列表中,单击右侧 操作 栏中的 编辑,将 DP ID 为 17、24、26、27 的功能点分别修改为药仓模式、低电量报警、错过提醒、服药提醒。
        关于添加标准功能,详情可查看 添加标准功能

        注意:温湿度 DP 为必选项,否则面板无法正常打开。

    5. 选择设备面板。

      单击上方 设备面板 页签,任选一个公版面板。
      关于选择设备面板,详情可查看 配置 App 界面

    6. 完成硬件开发。

      1. 选择云端接入方式为 TuyaOS

      2. 选择云端接入硬件为 BT3L Bluetooth 模组

      3. 在硬件的右方的操作栏下,单击 免费领取 2 个激活码,根据页面指导提交订单,免费领取激活码。
        关于硬件开发,详情可查看 硬件开发

        说明:此步骤仅用于选择模组拿到 Lisence 用于联网,实际使用的的模组为 TYBN1。
        单击 TYBN1 模组规格书 ,查看模组详情。

    获取 SDK

    1. 单击 Downloads 选择版本,获取 nRF52832 SDK。

    2. 本次用到的 SDK 版本为 nRF5 SDK 15.3.0。因此,选择 15.3.0 nRF5 SDK

    3. 下拉页面至末端,默认勾选所有选项,单击 Download files (.zip)

      注意:不要将下载的 SDK 放在 Linux 共享文件夹或者中文路径下,否则在编译时会出现 No such file or directory 等的未知错误。

    4. 下载完成后找到对应文件夹进行文件解压。解压完成后,打开 DeviceDownload 文件夹,解压nRF5SDK153059ac345 文件夹。

    5. 获取 TYBN1 SDK

    6. 下载完成后找到对应文件夹进行文件解压。解压完成后,将tuya-ble-sdk-demo-project-nrf52832-V2.1.0 文件夹复制到原厂 SDK 的 DeviceDownload\nRF5_SDK_15.3.\examples\ble_peripheral 文件夹下。

    7. 获取 ARM CMSIS4.5.0

    8. 下载安装完成后,打开 tuya-ble-sdk-demo-project-nrf52832-V2.1.0\pca10040\s132\arm5_no_packs 文件夹下的 Keil 工程。

    下载芯片对应安装包

    1. 第一次打开工程会自动下载 nRF52832 芯片对应的安装包。

    2. (可选)安装包下载完成后,如果第一次开发,会显示以下报错信息,可忽略该报错,继续完成安装。

      Cannot execute external request (Install Pack, "NordicSemiconductor:nRF_DeviceFamilyPack"): Pack not found
      Cannot execute external request (Install Pack, "NordicSemiconductor:nRF_DeviceFamilyPack:8.24.1"): Pack not found
      
    3. 按下图所示继续安装。

    4. 安装完成后重启 Keil 即可编译。

    5. (可选)由于 Keil 版本问题,编译时可能会出现以下报错信息 :

      RTE\Device\nRF52832_xxAA\system_nrf52.c(30): error:  #5: cannot open source input file "nrf52_erratas.h": No such file or directory
      

      只需用 DeviceDownload\nRF5SDK153059ac345\nRF5_SDK_15.3.0_59ac345\modules\nrfx\mdk 文件夹下的 system_nrf52.c 替换掉 DeviceDownload\nRF5SDK153059ac345\examples\ble_peripheral\tuya-ble-sdk-demo-project-nrf52832-V2.1.0\pca10040\s132\arm5_no_packs\RTE\Device\nRF52832_xxAA 文件夹下的 system_nrf52.c文件,然后关闭 Keil 工程,再重新打开该工程即可编译通过。

    修改 PID、UUID、Auth_key、MAC 地址

    1. 在 Keil 工程中,打开 tuya_ble_sdk_demo 文件夹找到 tuya_ble_sdk_demo.h 文件。

    2. 回到涂鸦 IoT 开发平台,进入采购订单的调试商品&样品订单,在右侧 操作 栏中,单击 下载授权码清单

    3. 打开授权码清单文件,将授权码清单中的 UUID、Auth_key、MAC 地址以及创建产品的 PID 填入下图所示位置。

      说明:如果您不知道产品 PID,可到 产品开发 列表中,找到产品,在产品下方可看到产品 ID。

    4. tuya_ble_sdk_demo.c文件中将 use_ext_license_key、device_id_len 的值与DEVICE_ID_LEN 的值分别改为 1 与 16,否则上步修改的 UUID、Auth_key、MAC、PID 信息不会生效。

    设备连线

    编译成功后将 J-Link 烧录器连接到开发板,连线如下。

    模组对应引脚串口对应引脚
    VCCVCC (3.3V)
    模组对应引脚JLINK 对应引脚
    SWDIOSWDIO
    SWCSWCLK
    GNDGND

    日志查看

    • 烧录 Nodric 协议栈固件

      1. 在开始菜单中搜索 J-Flash Vx.xxb (x.xx 版本号)并打开,单击 File > New Project,在 Target device 中选择 Nordic Semi nRF52832_xxAA ,确认无误单击 OK

      2. 单击 File > Open data file,选中 tuya-ble-sdk-demo-project-nrf52832-V2.1.0\pca10040\s132\arm5_no_packs\hex\material 文件夹下的 s132_nrf52_6.1.1_softdevice.hex文件进行烧录。

        说明:Softdevice 是 Nordic 蓝牙协议栈的名称,整个开发过程中只需下载一次。

      3. 单击 Target > Connect,等待连接成功。

      4. 单击 Target > Production Programming 开始下载协议栈固件。

      5. 下载成功后,下方日志口显示 Data file opened successfully 字样。

      6. 单击 Target > Disconnect 断开连接。

    • 使能日志

      Log 默认是关闭的,通过修改宏来使能日志。

      1. 找到 tuya-ble-sdk-demo-project-nrf52832-V2.1.0\tuya_ble_sdk_demo\board\nRF52832\tuya_ble_port文件夹下的 custom_tuya_ble_config.h 文件,将第 113 行 TUYA_APP_LOG_ENABLE 日志输出使能。
      2. 找到 ble_peripheral\tuya-ble-sdk-demo-project-nrf52832-V2.1.0\tuya_ble_sdk_demo\board 文件夹下的 board.h 文件,将第 38 行 TY_LOG_ENABLE 日志输出使能。
      3. 修改、编译完成后将 hex 文件烧录到模组。
    • 查看日志

      1. 打开 J-link RTT Viewer Vx.xxb 后自动弹出以下对话框:

      2. 选择 USB,在 Specify Target Device 中单击出现下面的弹窗。

        在这里插入图片描述

      3. 在红框内输入nRF52832_xxAA ,按回车后双击选定。

      4. 依次选择上图红框选项,单击 OK 完成设置。

      5. 当界面内出现以下内容说明连接成功,可以正常查看日志。

    • 修改测试宏

      打开工程目录 tuya_ble_sdk_demo 下的 tuya_ble_sdk_test.h 文件,将第 29 行宏定义 TUYA_BLE_SDK_TEST 关闭。

      由于该宏为测试模式的开关,打开会导致测试功能模块占用相关 I/O 口资源使部分 I/O 口无法正常使用。

      关于 nRF52832 SDK 的使用教程,可查看 学习笔记

    功能实现

    药盒查找

    在手机与药盒配网的情况下,当用户忘记药盒的位置时,可以点按 App 上的 药盒查找 功能,蜂鸣器间隔发声。

    当检测到药盒被打开或者手动关闭 App 上的 药盒查找开关时,停止蜂鸣器发声。

    部分功能代码如下:

    // APP send find common
    case DP_BOX_FIND:
    	if (1 == dp_data[4]) {
    	   tuya_remind_start_find();
    	} else if (0 == dp_data[4]) {
    	   tuya_beep_stop_play();
    	}           
    break;
    
    // Buzzer interval warning
    void tuya_remind_start_find(void)
    {
        find_handle = 1;
    
        return ;
    }
    
    int tuya_remind_box_find(void)
    {
        static uint32_t sn = 0;
    
        while (find_handle) {   // wait for the DP_BOX_FIND command to send from the APP
            tuya_beep_box_find_play(BEEP_HZ, 50);
            tuya_ble_device_delay_ms(200);
            tuya_beep_stop_play();
            tuya_ble_device_delay_ms(1000);
            
            if (BOX_OPEN == nrf_gpio_pin_read(KEY_OPEN) ) {
                tuya_beep_stop_play();
                find_buf[4] = 0;
                tuya_ble_dp_data_send(sn++, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITHOUT_RESPONSE, find_buf, BUf_LEN);
    
            break;
            }
        }
        find_handle = 0;
    
        return 0;
    }
    

    低电量报警

    智能药盒采用 3.7V 锂电池供电,电池在充满电的情况下,输出电压为 4.2V。没电时的输出电压 3.7V(满电和没电的实际输出电压都可能有偏差)。

    电量采集代码:

    static uint16_t tuya_batmon_batval_get(uint16_t vref, uint16_t sample, float ratio)
    {
        int i = 0;
        uint16_t Bat_val = 0;
        uint16_t adc_Avg = 0;
        uint16_t voltage_val = 0;
        nrf_saadc_value_t val_bat = 0;
    
        for (i = 0; i < 5; i++) {
            nrf_drv_saadc_sample_convert(ADC_CHANNEL0, &val_bat);
            adc_Avg += val_bat;
        }
        adc_Avg /= 5;
        voltage_val = (adc_Avg * vref) / sample;   // ADC Sample 10bit Ref voltage 3.6V
        Bat_val = voltage_val * ratio;  // Voltage divider The actual battery voltage value is 2 times the voltage value taken by the adc  unit:mv
        // TUYA_APP_LOG_INFO("battery_val:%dmv", Bat_val);
    
        return Bat_val;
    }
    
    int tuya_batmon_bat_level_report(void)
    {
        uint8_t i = 0;
        uint8_t op_ret;
        uint16_t Bat_val = 0;
        static uint32_t sn = 0;
    
        for (i = 0; i < 5; i++) {
            Bat_val = tuya_batmon_batval_get(3600, 1024, 2);
            if (Bat_val <= pet_10) {
                battery_buf[4] = ALARM;
                nrf_gpio_pin_write(LED_SWITCH, 1);  // low power, red light
                op_ret = tuya_ble_dp_data_send(sn++, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITHOUT_RESPONSE, battery_buf, DP_BUF_LEN(battery_buf));
                if (op_ret != TUYA_BLE_SUCCESS) {
                    TUYA_APP_LOG_ERROR("dp data send failed, error code:%d", op_ret);
                }
                
            } else {
                battery_buf[4] = NORMAL;
                nrf_gpio_pin_write(LED_SWITCH, 0);  // enough power, green light
                op_ret = tuya_ble_dp_data_send(sn++, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITHOUT_RESPONSE, battery_buf, DP_BUF_LEN(battery_buf));
                if (op_ret != TUYA_BLE_SUCCESS) {
                    TUYA_APP_LOG_ERROR("dp data send failed, error code:%d", op_ret);
                }
            }
            tuya_ble_device_delay_ms(1000);
        }
    
        return 0;
    }
    

    经过实际测量,万用表测的电压为 4.15V,ADC 采集电压为 4154mV。

    ![](https://images.tuyacn.com/smart/huanling_zone/nrf_adc1.jpg)
    

    周定时

    产品的 DP ID 14 为周定时功能,数据类型为 RAW,具体的协议格式如下:
    下发方式: 全量、16 进制下发和上报,即任何 1 个闹钟上报、下发、删除、更新,都全部上报。按序列拼接即可,无顺序、无编辑等操作。

    字节说明字节长度说明
    #1 协议版本号10x00:初始版本
    #2 开关+通道号1
    • bit7: 开/关 ,表示此条闹钟开启和关闭,默认为开启。
    • bit0~bit6: 表示通道 0~127(通道是从 0 开始,无需关心)。
      该值为 128 则闹钟开启,为 0 时则闹钟关闭。
    #3 星期1
    • 如果全为 0,表示单次模式,只生效一次,否则为循环模式。
    • 判断相应位是否置 1,置 1 表示当天生效。
      如为 0x42,表示任务在星期六和星期一生效。
      注意:必须保证相应的任务开关是处于开启状态)。bit0~bit6 对应周天到周六,bit7 保留为 0。
    #4 时间-小时1范围允许值为 0~23
    #5 时间-分钟1范围允许值为 0~59
    #6 执行动作10x00 无此功能,不展示,作为未来预留。
    #710x00:无此功能,不展示,作为未来预留。
    #810x00:无此功能,不展示,作为未来预留。
    #9 服药数量18
    • 仓位模式为小仓时(3 个仓),1、2、3 号仓服用量分别为 3,1,5,则输入格式为 103201305;
    • 仓位模式为大仓时(2 个仓),1、2 号仓服用量分别为 7,14,则输入格式为 107214。
      说明:必须按照 ”药盒号 + 服用量”两个字节表示,服药量为个位数时也用两位数表示,中间不能有空格,不能出现汉字。
    #10 预留 1【新增】10x00:无此功能,不展示,作为未来预留。
    #11 预留 2【新增】10x00:无此功能,不展示,作为未来预留。
    • RTC 设计思路:

      由于药盒只有在使用状态下才是正常功耗,其他时刻均处于低功耗,因此周定时采用 RTC 实现计数。
      药盒定时的触发时间精度为分钟级别,唤醒事件处理函数依据 RTC 中断每隔 60 秒查询一次 RTC 时间,然后将当前时间与定时时间做比对。
      如果时间吻合,判断当天是否有定时任务。如果有定时任务则退出低功耗,各外设开始正常工作,开始蓝牙广播。
      OLED 屏幕显示每个仓位的服药数量,蜂鸣器报警、电池电量检测等任务。
      当一个完整的服药动作完成后,药盒再次进入低功耗,等待下一个服药闹钟的到来将其唤醒。

    • RTC 唤醒事件处理部分代码:

      if (1 == rtc_flag) {    // 1s
          rtc_flag = 0;
          ty_rtc_get_time(&p_timestamp);	//	gets the timestamp once per 60s
          utc_sec = p_timestamp + EAST_8_ZONE;	
          tuya_ble_utc_sec_2_mytime(utc_sec, &rtc_time, 0);   // convert Beijing timestamp
      } else {
          return 0;
      }
      for (i = 0; i < medic_pro->num; i++) {  // query each timing info
          if (medic_pro->clock[i].hour == rtc_time.nHour && medic_pro->clock[i].min == rtc_time.nMin) {
              if (0x00 == medic_pro->clock[i].sw) {   // on_off
                  continue;
              }
              if (1 == (medic_pro->clock[i].week & (1 << rtc_time.DayIndex))) {    // repeat timing
                  /* wake up */
                  tuya_remind_wake_up();
                  tuya_remind_dose_show(medic_pro->clock[i].box1, medic_pro->clock[i].box2, medic_pro->clock[i].box3);
                  tuya_rtc_wakeup_process();
              } else if (0 == medic_pro->clock[i].week) { // once timing
                  // tuya_remind_wake_up();
                  tuya_remind_dose_show(medic_pro->clock[i].box1, medic_pro->clock[i].box2, medic_pro->clock[i].box3);
                  tuya_rtc_wakeup_process();
                  medic_pro->clock[i].sw = 0;
              }
          }
      }
      
    • 周定时协议解析部分代码:

      int tuya_local_time_parse(uint8_t *dp_data, uint8_t len)
      {
          if (NULL == dp_data) {
              TUYA_APP_LOG_ERROR("invalid parse dp_data!!!");
              return TUYA_BLE_ERR_INVALID_PARAM;
          }
      
          #if 0   //if want to see dp data from APP send open this macro when debug
          for (int a = 0; a < len; a++) {
              TUYA_APP_LOG_INFO("time_data[%d] = %d", a, dp_data[a]);
          }
          #endif
      
          int i = 0;
          int time_cnt = 0;
          TY_TIME_PRO_T *time_pro = NULL;
      
          time_pro = (TY_TIME_PRO_T *)dp_data;
          time_cnt = (len - 1) / sizeof(TY_SIGEL_TIME_PRO_T);   // (len - 1) -> (clock data) - (1 byte clock version)
          if (time_cnt > 5) {     // The number of alarm clocks exceeds the upper limit
              TUYA_APP_LOG_ERROR("More than 5 alarm clocks");
              return TUYA_BLE_ERR_INVALID_PARAM;
          } else if (0 == time_cnt) { // alarm clock num is 0
              TUYA_APP_LOG_INFO("No alarm clock is available");
              return 0;
          }
          /* parse version number */
          if (time_pro->ver != 0) {
              TUYA_APP_LOG_ERROR("Invalid timing version!");
              return TUYA_BLE_ERR_INTERNAL;
          }
          /* parse 'on_off'、week、hour、minute、dose */
          medic_info.num = time_cnt;
          for (i = 0; i < medic_info.num; i++) {
              medic_info.clock[i].sw = time_pro->singel_time[i].sw;
              medic_info.clock[i].week = time_pro->singel_time[i].week;
              medic_info.clock[i].hour = time_pro->singel_time[i].hour;
              medic_info.clock[i].min = time_pro->singel_time[i].min;
              medic_info.clock[i].box1 = PER_BOX_MEDICINE(time_pro->singel_time[i].box_dose[1], (time_pro->singel_time[i].box_dose[2]));
              medic_info.clock[i].box2 = PER_BOX_MEDICINE(time_pro->singel_time[i].box_dose[4], (time_pro->singel_time[i].box_dose[5]));
              medic_info.clock[i].box3 = PER_BOX_MEDICINE(time_pro->singel_time[i].box_dose[7], (time_pro->singel_time[i].box_dose[8]));
          }
              
          return TUYA_BLE_SUCCESS;
      }
      

    服药提醒、错过提醒

    当定时时间到达后上报一条 DP,App 显示服药提醒。第一次响铃 1 分钟后检测到没有打开药盒,三分钟后再次提醒。

    服药提醒与错过提醒部分代码:

    if (1 == medic_pro->clock[n].week & (1 << rtc_time.DayIndex) ) {
        /* wake up */
        tuya_remind_wake_up();
        tuya_remind_dose_show(medic_pro->clock[n].box1, medic_pro->clock[n].box2, medic_pro->clock[n].box3);
        tuya_beep_medicine_alarm(BEEP_HZ, 50);  // about ring 1min
        tuya_remind_ble_connect(); 
        tuya_remind_key_scan();
        ty_oled_clear();
        tuya_remind_miss_alarm();   //  after 3mins ring 1min, check box is not open enter sleep
        n++;
        if (n == medic_pro->num) {
            n = 0;
        }
        if (3 == tuya_ble_connect_status_get()) {
            remind_buf[4] = 1;
            tuya_ble_dp_data_send(0, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL,\
                                  DP_SEND_WITHOUT_RESPONSE, remind_buf, 5);
            tuya_batmon_bat_level_report(); // battery value check
        }
        tuya_ble_timer_create(&sec_sleep_timer, 2000, TUYA_BLE_TIMER_SINGLE_SHOT, sec_enter_sleep_cb);
        tuya_ble_timer_start(sec_sleep_timer);
    }
    

    药仓模式

    根据不同的药仓模式,打开不同的指示灯,指示指定药仓,达到提醒服用指定药物的功能。

    药仓指示功能的部分代码:

    case DP_BOX_MODE:// 药仓模式选择
    	ty_flash_write(0x66000, &dp_data[4], 1);    // write box_mode to flash
    break;
    
    
    int tuya_remind_box_mode_led_play(uint8_t mode)
    {
        if (mode > 1) {
            TUYA_APP_LOG_ERROR("box mode select error");
    
            return TUYA_BLE_ERR_INVALID_PARAM;
        }
    
        switch (mode) {
            case SMALL_BOX:
                tuya_remind_small_mode_led();
            break;
            case LARGE_BOX:
                tuya_remind_large_mode_led();
            break;
            default:
            break;
        }
    
        return 0;
    }
    

小结

智能药盒的 DIY 分享到到此结束。您还可以参考本教程,开发改造更多更有意思的智能药盒方案,比如将蜂鸣器换成振动马达,OLED 屏改成断码显示屏,充电锂电池换成纽扣电池方案等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT砖家涂拉拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值