2017年2月6日 星期一

BLE Gatt Date Time Service Implimentation in nRF51/52


    There are current time client library(components\ble\ble_services\ble_cts_c) and example code(examples\ble_peripheral\ble_app_cts_c) in the nRF SDK : the nRF device would adjust its real timer while the phone be connected. It is client mode : phone is real-time server, and device is real time client.
    But for some application, it is necessary that there is a real-time server in the BLE device. for example, it could make the phone to sure if the time adjusting is requisite or not.

    The post give a library code which follows GATT date time and GATT day of week.

The library code be :

ble_dts.h :



/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

/** @file
 *
 * @defgroup ble_sdk_srv_time current time Service
 * @{
 * @ingroup ble_sdk_srv
 * @brief current timer module.
 *
 * @details This module implements the current timer Service with the current time 
 *    , local time information(option) and reference time information(option) characteristic.
 *          During initialization it adds the current time Service and current time characteristic
 *          to the BLE stack database. Optionally it can also add local time,reference time descriptor
 *          to the current time server.
 *
 *          If specified, the module will support notification of the current time characteristic
 *          through the ble_time_current_time_update() function.
 *          If an event handler is supplied by the application, the current time Service will
 *          generate current timer Service events to the application.
 *
 * @note The application must propagate BLE stack events to the current timer Service module by calling
 *       ble_time_on_ble_evt() from the from the @ref ble_stack_handler callback.
 */

#ifndef _BLE_DTS_H_
#define _BLE_DTS_H_

#include <stdint.h>
#include <stdbool.h>
#include "ble.h"
#include "ble_srv_common.h"

#include "ble_date_time.h"

/**@brief current timer Service event type. */
typedef enum
{
    BLE_TIME_EVT_NOTIFICATION_ENABLED,                             /**< current time notification enabled event. */
    BLE_TIME_EVT_NOTIFICATION_DISABLED                             /**< current time notification disabled event. */
} ble_dts_evt_type_t;


/**@brief time Service event. */
typedef struct
{
    ble_dts_evt_type_t evt_type;                                  /**< Type of event. */
} ble_dts_evt_t;

// Forward declaration of the ble_time_t type. 
typedef struct ble_dts_s ble_dts_t;


/**@brief current time Service event handler type. */
typedef void (*ble_time_evt_handler_t) (ble_dts_t *p_time, ble_dts_evt_t * p_evt);



/**@brief current time Service init structure. This contains all options and data needed for
 *        initialization of the service.*/
typedef struct
{
    ble_time_evt_handler_t   evt_handler;     /**< Event handler to be called for handling events in the current time Service. */
    bool       is_notification_supported;    /**< TRUE if notification of current time measurement is supported. */
 ble_date_time_t     init_date_time;
} ble_dts_init_t;


/**@brief current time Service structure. This contains various status information for the service. */
struct ble_dts_s
{
    ble_time_evt_handler_t        evt_handler;                    /**< Event handler to be called for handling events in the current time Service. */
    uint16_t                      service_handle;                 /**< Handle of current time Service (as provided by the BLE stack). */
    ble_gatts_char_handles_t      date_time_handles;            /**< Handles related to the current time characteristic. */
 ble_gatts_char_handles_t      day_of_week_handles;    /**< Handles related to the day of week characteristic. */
    uint16_t                      conn_handle;                    /**< Handle of the current connection (as provided by the BLE stack, is BLE_CONN_HANDLE_INVALID if not in a connection). */
    bool                          is_notification_supported;      /**< TRUE if notification of current time is supported. */
 ble_date_time_t      current_date_time;             /**< Last current time passed to the current time Service. */
};


uint32_t ble_dts_init(ble_dts_t *p_dts, const ble_dts_init_t *p_dts_init);

void ble_dts_on_ble_evt(ble_dts_t *p_dts, ble_evt_t *p_ble_evt);

uint32_t ble_dts_update(ble_dts_t *p_dts, ble_date_time_t *p_current_time);

#endif // _BLE_DTS_H_

/** @} */



ble_dts.c :

/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

#include "ble_dts.h"
#include <string.h>
#include "nordic_common.h"
//#include "app_util.h"


static void on_connect(ble_dts_t *p_dts, ble_evt_t * p_ble_evt)
{
    p_dts->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
}/*on_connect*/


static void on_disconnect(ble_dts_t *p_dts, ble_evt_t * p_ble_evt)
{
    UNUSED_PARAMETER(p_ble_evt);
    p_dts->conn_handle = BLE_CONN_HANDLE_INVALID;
}/*on_disconnect*/


static void on_write(ble_dts_t *p_dts, ble_evt_t * p_ble_evt)
{
    
        ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
        
        if (p_evt_write->handle == p_dts->date_time_handles.cccd_handle)
        {
            // CCCD written, call application event handler
            if (p_dts->evt_handler != NULL)
            {
                ble_dts_evt_t evt;
                
                if (ble_srv_is_notification_enabled(p_evt_write->data))
                {
                    evt.evt_type = BLE_TIME_EVT_NOTIFICATION_ENABLED;
                }
                else
                {
                    evt.evt_type = BLE_TIME_EVT_NOTIFICATION_DISABLED;
                }

                p_dts->evt_handler(p_dts, &evt);
            }
        }/*if */
}/*on_write*/


void ble_dts_on_ble_evt(ble_dts_t *p_date_time_svc, ble_evt_t * p_ble_evt)
{
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            on_connect(p_date_time_svc, p_ble_evt);
            break;
            
        case BLE_GAP_EVT_DISCONNECTED:
            on_disconnect(p_date_time_svc, p_ble_evt);
            break;
            
        case BLE_GATTS_EVT_WRITE:
            on_write(p_date_time_svc, p_ble_evt);
            break;
            
        default:
            break;
    }
}


/**@brief Add current time characteristic.
 *
 * @param[in]   p_dts     Date and time Service structure.
 * @param[in]   p_time_init   Information needed to initialize the service.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t date_and_time_char_add(ble_dts_t *p_dts, 
 const ble_dts_init_t * p_dts_init)
{
    uint32_t            err_code;
 ble_uuid_t          ble_uuid;
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_gatts_attr_md_t attr_md;

    
    // Add date and time characteristic
    if (false != p_dts->is_notification_supported)
    {
        memset(&cccd_md, 0, sizeof(cccd_md));
  
        BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
  BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
        cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    }
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read   = 1;
 char_md.char_props.write = 0;
    char_md.char_props.notify = (p_dts->is_notification_supported) ? 1 : 0;
    char_md.p_char_user_desc  = NULL;
    char_md.p_char_pf         = NULL;
    char_md.p_user_desc_md    = NULL;
    char_md.p_cccd_md         = (p_dts->is_notification_supported) ? &cccd_md : NULL;
    char_md.p_sccd_md         = NULL;
    
 
 
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_DATE_TIME_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); 
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
 
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));

    attr_char_value.p_uuid       = &ble_uuid;
    attr_char_value.p_attr_md    = &attr_md;
    attr_char_value.init_len     = sizeof(ble_date_time_t);
    attr_char_value.init_offs    = 0;
    attr_char_value.max_len      = sizeof(ble_date_time_t);
    attr_char_value.p_value      = (uint8_t*)&p_dts_init->init_date_time;
 
 
    err_code = sd_ble_gatts_characteristic_add(p_dts->service_handle, &char_md,
                                               &attr_char_value,
                                               &p_dts->date_time_handles);
    if (err_code != NRF_SUCCESS)
  return err_code;
        
    return NRF_SUCCESS;
}/*current_time_char_add*/


static uint8_t determine_day_of_week_by_gauss(ble_date_time_t *p_date)
{
 int16_t first_2_digits_of_year, last_2_digits_of_year;
 int16_t shift_month, shift_year;
 
 int16_t temp;
 
 int16_t day_of_week;
 
 shift_year = p_date->year;
 if(1== p_date->month || 2 == p_date->month)
  shift_year -= 1;
 
 first_2_digits_of_year = shift_year/100;
 last_2_digits_of_year = shift_year%100;
 
 shift_month = (p_date->month + 10)%12;
 if(0 == shift_month)
  shift_month = 12;
 
 temp = 2.6*shift_month - 0.2;
 
 day_of_week = (p_date->day + temp + last_2_digits_of_year 
  + (last_2_digits_of_year/4) + (first_2_digits_of_year/4) - 2*first_2_digits_of_year);
 day_of_week = day_of_week % 7;
 
 if(0 == day_of_week)
  day_of_week = 7;
 
 return (uint8_t)(day_of_week);
 
}/*determine_day_of_week_by_gauss*/


static uint32_t week_of_day_char_add(ble_dts_t *p_dts, 
 const ble_dts_init_t *p_time_init)
{
 uint32_t            err_code;
 ble_uuid_t          ble_uuid;
    ble_gatts_char_md_t char_md;
//    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_gatts_attr_md_t attr_md;

 
 BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_DAY_OF_WEEK_CHAR);
 
 memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read   = 1;
    char_md.char_props.notify = 0;
    char_md.p_char_user_desc  = NULL;
    char_md.p_char_pf         = NULL;
    char_md.p_user_desc_md    = NULL;
    char_md.p_cccd_md         = NULL;
    char_md.p_sccd_md         = NULL;
 
 
    memset(&attr_md, 0, sizeof(attr_md));

 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
 
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));

 uint8_t day_of_week;
 
 day_of_week = determine_day_of_week_by_gauss(&p_dts->current_date_time);
 
    attr_char_value.p_uuid       = &ble_uuid;
    attr_char_value.p_attr_md    = &attr_md;
    attr_char_value.init_len     = sizeof(uint8_t);
    attr_char_value.init_offs    = 0;
    attr_char_value.max_len      = sizeof(uint8_t);
    attr_char_value.p_value      = (uint8_t*)&day_of_week;
 
 
    err_code = sd_ble_gatts_characteristic_add(p_dts->service_handle, &char_md,
                                               &attr_char_value,
                                               &p_dts->day_of_week_handles);
    if (err_code != NRF_SUCCESS)
  return err_code;
 
 return NRF_SUCCESS;
}/*week_of_day_char_add*/


uint32_t ble_dts_init(ble_dts_t *p_dts, const ble_dts_init_t *p_dts_init)
{
    uint32_t   err_code;
    ble_uuid_t ble_uuid;

    // Initialize service structure
    p_dts->evt_handler                = p_dts_init->evt_handler;
    p_dts->conn_handle                = BLE_CONN_HANDLE_INVALID;
    p_dts->is_notification_supported = p_dts_init->is_notification_supported;
    p_dts->current_date_time    = p_dts_init->init_date_time;
 
    // Add service
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_CURRENT_TIME_SERVICE);

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, 
  &ble_uuid, &p_dts->service_handle);
    if (err_code != NRF_SUCCESS)
        return err_code;
    
    
    err_code = date_and_time_char_add(p_dts, p_dts_init);
 if(err_code != NRF_SUCCESS)
  return err_code;
 
 err_code = week_of_day_char_add(p_dts, p_dts_init);
 if(err_code != NRF_SUCCESS)
  return err_code;
 
 return NRF_SUCCESS;
}/*ble_time_init*/


uint32_t ble_dts_update(ble_dts_t *p_dts, ble_date_time_t *p_current_date_time)
{
 uint32_t err_code;
 err_code = NRF_SUCCESS;
 ble_gatts_value_t gatts_value;
 
 if (0 == memcmp(&p_dts->current_date_time, p_current_date_time, sizeof(ble_date_time_t)))
  return NRF_SUCCESS;
 
 p_dts->current_date_time = *p_current_date_time;
 
 
 gatts_value.len     = sizeof(ble_date_time_t);
 gatts_value.offset  = 0;
 gatts_value.p_value = (uint8_t*)p_current_date_time;

  
 err_code = sd_ble_gatts_value_set(p_dts->conn_handle, 
  p_dts->date_time_handles.value_handle, &gatts_value);
 
 if (err_code != NRF_SUCCESS)
  return err_code;
 
 // Send value if connected and notifying
 if ((p_dts->conn_handle != BLE_CONN_HANDLE_INVALID) 
  && p_dts->is_notification_supported)
 {
  ble_gatts_hvx_params_t hvx_params;
  uint16_t len;
  
  memset(&hvx_params, 0, sizeof(hvx_params));
  len = sizeof(ble_date_time_t);
  
  hvx_params.handle   = p_dts->date_time_handles.value_handle;
  hvx_params.type     = BLE_GATT_HVX_NOTIFICATION;
  hvx_params.offset   = 0;
  hvx_params.p_len    = &len;
  hvx_params.p_data   = (uint8_t*)p_current_date_time;
  
  err_code = sd_ble_gatts_hvx(p_dts->conn_handle, &hvx_params);
 }
 else
 {
  err_code = NRF_ERROR_INVALID_STATE;
 }
 
 

 uint8_t day_of_week;
 
 day_of_week = determine_day_of_week_by_gauss(p_current_date_time);
 
 gatts_value.len     = sizeof(uint8_t);
 gatts_value.offset  = 0;
 gatts_value.p_value = (uint8_t*)&day_of_week;
 
 err_code = sd_ble_gatts_value_set(p_dts->conn_handle, 
  p_dts->day_of_week_handles.value_handle, &gatts_value);
 
 if (err_code != NRF_SUCCESS)
  return err_code; 
 
 return NRF_SUCCESS;
}/*ble_data_time_update*/



To use the library, as add others BLE service, you should add the code in function services_init of main.c :


#include "ble_dts.h"
static ble_dts_t  m_dts;
static void services_init(void)
{
//:
//:
ble_dts_init_t  bts_init_obj; 
 memset(&bts_init_obj, 0, sizeof(ble_dts_init_t)); 
 
 ble_date_time_t init_date_time;
 init_date_time.year = 2017;
 init_date_time.month = 1;
 init_date_time.day = 22;
 init_date_time.hours = 2;
 init_date_time.minutes = 3;
 init_date_time.seconds = 40;
 
 
 bts_init_obj.evt_handler          = NULL;
    bts_init_obj.is_notification_supported = true; 
 bts_init_obj.init_date_time = init_date_time;
    err_code = ble_dts_init(&m_dts, &bts_init_obj);
 APP_ERROR_CHECK(err_code);
//:
}



And add ble on event  function in fuction ble_evt_dispatch of main.c:


static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
//:
    ble_dts_on_ble_evt(&m_dts, p_ble_evt);
//:
}


While the time value needs to be update, call the function ble_dts_update. the most typical application is, create a app timer to update date time value periodically:


/* app periodic timer updates date time value*/
static void date_time_timeout_handler(void * p_context)
{
 UNUSED_PARAMETER(p_context);

 uint32_t err_code;
 
 ble_date_time_t new_date_time;
 memcpy(&new_date_time, &m_dts.current_date_time, sizeof(ble_date_time_t));
 
 /*
 TODO :
   implement the new_date_time value updating ..
 */
 err_code = ble_dts_update(&m_dts, &new_date_time);
 
 APP_ERROR_CHECK(err_code); 
 
}/*date_time_timeout_handler*/


How to read the sevice simplely :

  You could use the app, nRF Connect for this goal:




Connect to the nRF device, in here, it is DHT11_temp&Humi


It is the service and characteristics, you could just press the buttons.




The date time and day of week have been read and displayed successfully. It prove the library works.