[经验分享] 【基于人脸识别的自动打卡健走计时系统】BLE自动校时及扫描穿戴手表/扫描

alanlan86   2022-10-23 17:31 楼主

 得捷2022电子创新设计大赛的比赛的作品贴已经提交,但是考虑到前后设计完整性,还是考虑单独开一个贴,分享一下在BLE设计上面的这个功能模块。

(一)通过蓝牙自动获取时间

实现原理


- 考虑到K210的主板并没有按键交互,如果需要配置一个本地时间非常的不方便,因此考虑是否可以通过蓝牙自动获取到手机上面的时间?

- 基于此想法,通过了解BLE里面的CTS(current time service)就非常合适!

- Service本身是存在与数据提供方,因此,手机必须要支持CTS(通过了解iOS的手机,以及部分的安卓手机是有此功能),那蓝牙芯片nRF52832需要具有client客户端功能。

- 另外,需要注意,这里的service和client和central和peripheral是没有必然关联的~~~ 也就是,我们需要在nRF52832上面实现peripheral(即可以进行广播,被手机连接),同时,需要支持配对加密和一个服务发现功能,nRF52832发现手机支持此服务后,即可从手机端读取得到时间信息。

实现过程

- 假定大家以及对于nRF52832的SDK有一定的经验基础

- 添加peer_mananger模块

#if (PEER_MANAGER_ENABLED == 1)
static void advertising_start(bool erase_bonds);
  
/**[url=home.php?mod=space&uid=159083]@brief[/url] Fetch the list of peer manager peer IDs.
 *
 * @param[inout] p_peers   The buffer where to store the list of peer IDs.
 * @param[inout] p_size    In: The size of the @p p_peers buffer.
 *                         Out: The number of peers copied in the buffer.
 */
static void peer_list_get(pm_peer_id_t * p_peers, uint32_t * p_size)
{
    pm_peer_id_t peer_id;
    uint32_t     peers_to_copy;

    peers_to_copy = (*p_size < BLE_GAP_WHITELIST_ADDR_MAX_COUNT) ?
                     *p_size : BLE_GAP_WHITELIST_ADDR_MAX_COUNT;

    peer_id = pm_next_peer_id_get(PM_PEER_ID_INVALID);
    *p_size = 0;

    while ((peer_id != PM_PEER_ID_INVALID) && (peers_to_copy--))
    {
        p_peers[(*p_size)++] = peer_id;
        peer_id = pm_next_peer_id_get(peer_id);
    }
}


/**@brief Clear bond information from persistent storage.
 */
static void delete_bonds(void)
{
    ret_code_t err_code;

    NRF_LOG_INFO("Erase bonds!");

    err_code = pm_peers_delete();
    APP_ERROR_CHECK(err_code);
}

/**@brief Function for handling Peer Manager events.
 *
 * @param[in] p_evt  Peer Manager event.
 */
static void pm_evt_handler(pm_evt_t const * p_evt)
{
    ret_code_t err_code;

    pm_handler_on_pm_evt(p_evt);
    pm_handler_disconnect_on_sec_failure(p_evt);
    pm_handler_flash_clean(p_evt);

    switch (p_evt->evt_id)
    {
        case PM_EVT_CONN_SEC_SUCCEEDED:
        {
#if ( PM_WHITE_LIST_EN == 1)          
            m_peer_id = p_evt->peer_id;
#endif
            // Discover peer's services.
            err_code  = ble_db_discovery_start(&m_ble_db_discovery, p_evt->conn_handle);
            APP_ERROR_CHECK(err_code);
        } break;

        case PM_EVT_PEERS_DELETE_SUCCEEDED:
        {
            advertising_start(false);
        } break;
        
        case PM_EVT_CONN_SEC_CONFIG_REQ:
        {
					  NRF_LOG_INFO("Allow Re-pairing\r\n");
					
            // Allow Re-pairing request from an already bonded peer.
            pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true};
            pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
        } break;

        case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
        {
            // Note: You should check on what kind of white list policy your application should use.
            if (     p_evt->params.peer_data_update_succeeded.flash_changed
                 && (p_evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING))
            {
                NRF_LOG_DEBUG("New Bond, add the peer to the whitelist if possible");
#if ( PM_WHITE_LIST_EN == 1)              
                NRF_LOG_DEBUG("\tm_whitelist_peer_cnt %d, MAX_PEERS_WLIST %d",
                               m_whitelist_peer_cnt + 1,
                               BLE_GAP_WHITELIST_ADDR_MAX_COUNT);

                if (m_whitelist_peer_cnt < BLE_GAP_WHITELIST_ADDR_MAX_COUNT)
                {
                    // Bonded to a new peer, add it to the whitelist.
                    m_whitelist_peers[m_whitelist_peer_cnt++] = m_peer_id;

                    // The whitelist has been modified, update it in the Peer Manager.
                    err_code = pm_device_identities_list_set(m_whitelist_peers, m_whitelist_peer_cnt);
                    if (err_code != NRF_ERROR_NOT_SUPPORTED)
                    {
                        APP_ERROR_CHECK(err_code);
                    }

                    err_code = pm_whitelist_set(m_whitelist_peers, m_whitelist_peer_cnt);
                    APP_ERROR_CHECK(err_code);
                }
#endif                
            }
        } break;

        default:
            break;
    }
}

/**@brief Function for the Peer Manager initialization.
 */
static void peer_manager_init(void)
{
    ble_gap_sec_params_t sec_param;
    ret_code_t           err_code;

    err_code = pm_init();
    APP_ERROR_CHECK(err_code);

    memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));

    // Security parameters to be used for all security procedures.
    sec_param.bond           = SEC_PARAM_BOND;
    sec_param.mitm           = SEC_PARAM_MITM;
    sec_param.lesc           = SEC_PARAM_LESC;
    sec_param.keypress       = SEC_PARAM_KEYPRESS;
    sec_param.io_caps        = SEC_PARAM_IO_CAPABILITIES;
    sec_param.oob            = SEC_PARAM_OOB;
    sec_param.min_key_size   = SEC_PARAM_MIN_KEY_SIZE;
    sec_param.max_key_size   = SEC_PARAM_MAX_KEY_SIZE;
    sec_param.kdist_own.enc  = 1;
    sec_param.kdist_own.id   = 1;
    sec_param.kdist_peer.enc = 1;
    sec_param.kdist_peer.id  = 1;

    err_code = pm_sec_params_set(&sec_param);
    APP_ERROR_CHECK(err_code);

    err_code = pm_register(pm_evt_handler);
    APP_ERROR_CHECK(err_code);
}

#endif
  • 添加CTS_C相关代码
#if (BLE_CTS_C_ENABLED == 1)
/** CTS read current time **/
void cts_read_current_time(void *pdata, uint16_t size)
{
    ret_code_t err_code;
  
    if (m_cts_c.conn_handle != BLE_CONN_HANDLE_INVALID)
    {
        err_code = ble_cts_c_current_time_read(&m_cts_c);
        if (err_code == NRF_ERROR_NOT_FOUND)
        {
            NRF_LOG_INFO("Current Time Service is not discovered.");
        }
      }
}

/**@brief Function for handling the Current Time Service errors.
 *
 * @param[in]  nrf_error  Error code containing information about what went wrong.
 */
static void current_time_error_handler(uint32_t nrf_error)
{
    APP_ERROR_HANDLER(nrf_error);
}


/**@brief Function for handling the Current Time Service errors.
 *
 * @param[in] p_evt  Event received from the Current Time Service client.
 */
static void current_time_print(ble_cts_c_evt_t * p_evt)
{
    uint8_t data[64];
    current_time_char_t current_time;
  
    NRF_LOG_INFO("\r\nCurrent Time:");
    NRF_LOG_INFO("\r\nDate:");

    NRF_LOG_INFO("\tDay of week   %s", (uint32_t)day_of_week[p_evt->
                                                         params.
                                                         current_time.
                                                         exact_time_256.
                                                         day_date_time.
                                                         day_of_week]);

    if (p_evt->params.current_time.exact_time_256.day_date_time.date_time.day == 0)
    {
        NRF_LOG_INFO("\tDay of month  Unknown");
    }
    else
    {
        NRF_LOG_INFO("\tDay of month  %i",
                       p_evt->params.current_time.exact_time_256.day_date_time.date_time.day);
    }

    NRF_LOG_INFO("\tMonth of year %s",
    (uint32_t)month_of_year[p_evt->params.current_time.exact_time_256.day_date_time.date_time.month]);
    if (p_evt->params.current_time.exact_time_256.day_date_time.date_time.year == 0)
    {
        NRF_LOG_INFO("\tYear          Unknown");
    }
    else
    {
        NRF_LOG_INFO("\tYear          %i",
                       p_evt->params.current_time.exact_time_256.day_date_time.date_time.year);
    }
    NRF_LOG_INFO("\r\nTime:");
    NRF_LOG_INFO("\tHours     %i",
                   p_evt->params.current_time.exact_time_256.day_date_time.date_time.hours);
    NRF_LOG_INFO("\tMinutes   %i",
                   p_evt->params.current_time.exact_time_256.day_date_time.date_time.minutes);
    NRF_LOG_INFO("\tSeconds   %i",
                   p_evt->params.current_time.exact_time_256.day_date_time.date_time.seconds);
    NRF_LOG_INFO("\tFractions %i/256 of a second",
                   p_evt->params.current_time.exact_time_256.fractions256);

    NRF_LOG_INFO("\r\nAdjust reason:\r");
    NRF_LOG_INFO("\tDaylight savings %x",
                   p_evt->params.current_time.adjust_reason.change_of_daylight_savings_time);
    NRF_LOG_INFO("\tTime zone        %x",
                   p_evt->params.current_time.adjust_reason.change_of_time_zone);
    NRF_LOG_INFO("\tExternal update  %x",
                   p_evt->params.current_time.adjust_reason.external_reference_time_update);
    NRF_LOG_INFO("\tManual update    %x",
                   p_evt->params.current_time.adjust_reason.manual_time_update);
    
    current_time = p_evt->params.current_time;
    app_uart_data_packet(COM_PACKET_TYPE_CMD, (uint8_t *)&current_time, sizeof(current_time), data);
}

/**@brief Function for handling the Current Time Service client events.
 *
 * @details This function will be called for all events in the Current Time Service client that
 *          are passed to the application.
 *
 * @param[in] p_evt Event received from the Current Time Service client.
 */
static void on_cts_c_evt(ble_cts_c_t * p_cts, ble_cts_c_evt_t * p_evt)
{
    ret_code_t err_code;

    switch (p_evt->evt_type)
    {
        case BLE_CTS_C_EVT_DISCOVERY_COMPLETE:
            NRF_LOG_INFO("Current Time Service discovered on server.");
            err_code = ble_cts_c_handles_assign(&m_cts_c,
                                                p_evt->conn_handle,
                                                &p_evt->params.char_handles);
            APP_ERROR_CHECK(err_code);
         
            err_code = app_sched_event_put(NULL, 0, cts_read_current_time);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_CTS_C_EVT_DISCOVERY_FAILED:
            NRF_LOG_INFO("Current Time Service not found on server. ");
            // CTS not found in this case we just disconnect. There is no reason to stay
            // in the connection for this simple app since it all wants is to interact with CT
#if 0//Android Phone don't have CTS_C        
            if (p_evt->conn_handle != BLE_CONN_HANDLE_INVALID)
            {
                err_code = sd_ble_gap_disconnect(p_evt->conn_handle,
                                                 BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
                APP_ERROR_CHECK(err_code);
            }
#endif            
            break;

        case BLE_CTS_C_EVT_DISCONN_COMPLETE:
            NRF_LOG_INFO("Disconnect Complete.");
            break;

        case BLE_CTS_C_EVT_CURRENT_TIME:
            NRF_LOG_INFO("Current Time received.");
            current_time_print(p_evt);
            break;

        case BLE_CTS_C_EVT_INVALID_TIME:
            NRF_LOG_INFO("Invalid Time received.");
            break;

        default:
            break;
    }
}
#endif

/**@brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
    uint32_t           err_code;  
    ble_nus_init_t     nus_init;
#if (BLE_CTS_C_ENABLED == 1)  
    ble_cts_c_init_t   cts_init = {0};
#endif    
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    // Initialize NUS.
    memset(&nus_init, 0, sizeof(nus_init));

    nus_init.data_handler = nus_data_handler;

    err_code = ble_nus_init(&m_nus, &nus_init);
    APP_ERROR_CHECK(err_code);

#if (BLE_CTS_C_ENABLED == 1)    
    // Initialize CTS.
    cts_init.evt_handler   = on_cts_c_evt;
    cts_init.error_handler = current_time_error_handler;
    cts_init.p_gatt_queue  = &m_ble_gatt_queue;
    err_code               = ble_cts_c_init(&m_cts_c, &cts_init);
    APP_ERROR_CHECK(err_code);
#endif    
}
- 主函数部分
/**@brief Application main function.
 */
int main(void)
{
    // Initialize.
    uart_init();
    log_init();
    timers_init();
    scheduler_init();
    power_management_init();
    ble_stack_init();
    gap_params_init();
    gatt_init();
#if (BLE_CTS_C_ENABLED == 1)  
    db_discovery_init();
#endif  
    services_init();
    advertising_init();
#if (NRF_BLE_SCAN_ENABLED == 1)
    scan_init();
#endif  
    conn_params_init();
#if (PEER_MANAGER_ENABLED == 1)  
    peer_manager_init();
#endif  
    // Start execution.
    //printf("\r\nBLE-LoRa Init...\r\n");
		NRF_LOG_INFO("Debug logging for BLE-LoRa over RTT started.");

#if BLE_ENABLE    
		advertising_start(true);
#if (NRF_BLE_SCAN_ENABLED == 1)
    scan_start();
#endif 
#endif
	
    // Enter main loop.
    for (;;)
    {
					
        idle_state_handle();
    }
}

调试效果

当前时间自动获取.PNG

(二)通过MAC地址扫描到周边的穿戴手环或手表

实现原理

- 每个蓝牙设备都有一个MAC地址信息,采用随机静态地址的设备,它的MAC地址信息是不会变化的,因此我们可以通过扫描MAC地址的方式,了解穿戴设备是否在周边,通过RSSI,进行一个粗略距离的计算。

- 扫描得到的RSSI信息,最好是需要进行滤波处理,提高距离计算的稳定度。

实现代码

bool app_ble_scan_device_report(uint8_t mac[6], int8_t rssi)
{
  uint8_t txdata[32];
  double kf_rssi;
  
  kf_rssi = (int)KalmanFilter(&m_kalmaninfo[TARGET_DEVICE_A], (double)rssi);
  NRF_LOG_INFO("kalman rssi:%d\r\n", kf_rssi);
  
  if ((kf_rssi > 30)&&(ble_device_scan_report == false))
  {    
    ble_device_scan_report = true;
    ble_device_scan_counter = 50;
    app_uart_data_packet(COM_PACKET_TYPE_CMD, (uint8_t *)"BLE_SCAN_DEVICE", sizeof("BLE_SCAN_DEVICE"), txdata);
  }
  
  if (kf_rssi > 30)
  {
    ble_device_scan_counter  = 50;
  }
  
  return ble_device_scan_report;
}

/**@brief Function for handling BLE events.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 * @param[in]   p_context   Unused.
 */
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    uint32_t err_code = NRF_SUCCESS;
    ble_gap_evt_adv_report_t const * p_adv_report = &p_ble_evt->evt.gap_evt.params.adv_report;
  
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_ADV_REPORT:
        {
          if (memcmp(m_target_periph_mac_addr, p_adv_report->peer_addr.addr, 6) == 0)
          {
            NRF_LOG_INFO("MAC Addr:%02X:%02X:%02X:%02X:%02X:%02X\r\n", 
                          p_adv_report->peer_addr.addr[5],
                          p_adv_report->peer_addr.addr[4],
                          p_adv_report->peer_addr.addr[3],
                          p_adv_report->peer_addr.addr[2],
                          p_adv_report->peer_addr.addr[1],
                          p_adv_report->peer_addr.addr[0]);
            NRF_LOG_INFO("rssi:%d\r\n", p_adv_report->rssi);
            
            app_ble_scan_device_report((uint8_t *)p_adv_report->peer_addr.addr, p_adv_report->rssi);
          }
        }
          break;
        case BLE_GAP_EVT_CONNECTED:
            NRF_LOG_INFO("Connected");
//            err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
//            APP_ERROR_CHECK(err_code);
            m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
            APP_ERROR_CHECK(err_code);
        
            err_code = pm_conn_secure(p_ble_evt->evt.gap_evt.conn_handle, true);
            if (err_code != NRF_ERROR_BUSY)
            {
                APP_ERROR_CHECK(err_code);
            }
            break;

        case BLE_GAP_EVT_DISCONNECTED:
            NRF_LOG_INFO("Disconnected");
            // LED indication will be changed when advertising starts.
            m_conn_handle = BLE_CONN_HANDLE_INVALID;
            if (p_ble_evt->evt.gap_evt.conn_handle == m_cts_c.conn_handle)
            {
                m_cts_c.conn_handle = BLE_CONN_HANDLE_INVALID;
            }
            break;

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
            NRF_LOG_DEBUG("PHY update request.");
            ble_gap_phys_t const phys =
            {
                .rx_phys = BLE_GAP_PHY_AUTO,
                .tx_phys = BLE_GAP_PHY_AUTO,
            };
            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
            NRF_LOG_DEBUG("BLE_GAP_EVT_SEC_PARAMS_REQUEST");
            break;
        
        case BLE_GAP_EVT_AUTH_KEY_REQUEST:
            NRF_LOG_INFO("BLE_GAP_EVT_AUTH_KEY_REQUEST");
            break;

        case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
            NRF_LOG_INFO("BLE_GAP_EVT_LESC_DHKEY_REQUEST");
            break;

         case BLE_GAP_EVT_AUTH_STATUS:
             NRF_LOG_INFO("BLE_GAP_EVT_AUTH_STATUS: status=0x%x bond=0x%x lv4: %d kdist_own:0x%x kdist_peer:0x%x",
                          p_ble_evt->evt.gap_evt.params.auth_status.auth_status,
                          p_ble_evt->evt.gap_evt.params.auth_status.bonded,
                          p_ble_evt->evt.gap_evt.params.auth_status.sm1_levels.lv4,
                          *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_own),
                          *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_peer));
            if (p_ble_evt->evt.gap_evt.params.auth_status.auth_status == NRF_SUCCESS)
            {
              
            } else {
              
            }
            break;

        case BLE_GATTS_EVT_SYS_ATTR_MISSING:
            // No system attributes have been stored.
            err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTC_EVT_TIMEOUT:
            // Disconnect on GATT Client timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_TIMEOUT:
            // Disconnect on GATT Server timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        default:
            // No implementation needed.
            break;
    }
}

调试效果

rssi.png

至此,通过将手环靠近扫描的蓝牙附近,则可以观察到RSSI的状况,远离和靠近的RSSI变化关系。

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复