【基于人脸识别的自动打卡健走计时系统】BLE自动校时及扫描穿戴手表/扫描
[复制链接]
得捷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
#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 *)¤t_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();
}
}
调试效果
(二)通过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的状况,远离和靠近的RSSI变化关系。
|