Hello all,
This email will be long and dry. If you are not interested in the
nimble host (bluetooth stack), then you might want to skip it. If you
are a user of the nimble host, the email will be no less dry, but I
would really appreciate it if you could expend the time to read it when
you can. All feedback is welcome.
With the core host functionality mostly stable, I think it is time to
revisit the API. Some crucial functionality is missing from the host
API; other functionality is available, but the interfaces will cause
much scratching of application developers' heads. Since Mynewt is
nearing the end of its beta phase, now is the time to get the API right
once and for all. In this email I will propose some changes that I wish
to make to the nimble host API.
As I said, all feedback is welcome, whether it concerns one of the
proposed changes or not. If there is anything you don't like about the
nimble host API, please speak up. Let me be clear: no criticism is too
derisive :).
****** GATT
**** Characteristic and descriptor access
* Separate "read" and "write" structs in the ble_gatt_access_ctxt.
Currently, "chr_access" is overloaded so that it applies to both reads
and writes. I think this is confusing and error-prone.
* For reads, provide a buffer for the application to fill with response
data if needed. Currently, the application needs to manage its own
memory for dynamically-generated characteristic values.
* Characteristic callback access function gets executed when a peer
subscribes to notifications or indications. Currently there is no way
of knowing when this happens.
* Add "const" to pointers that are meant to be read-only.
The proposed API is below.
/**
* Context for reads of characteristics and descriptors. An instance of this
* gets passed to an application callback whenever a local characteristic or
* descriptor is read.
*/
struct ble_gatt_read_ctxt {
/**
* (stack --> app)
* Maximum number of data bytes the stack can send in the read response.
* This is based on the connection's ATT MTU.
*/
uint16_t max_data_len;
/**
* (stack --> app)
* A buffer that the app can use to write the characteristic response value
* to. This buffer can hold up to max_data_len bytes.
*/
uint8_t *buf;
/**
* (app --> stack)
* App points this at the characteristic data to respond with. Initially
* this points to "buf".
*/
const void *data;
/**
* (app --> stack)
* App fills this with the number of data bytes contained in characteristic
* response.
*/
uint16_t len;
};
/**
* Context for writes of characteristics and descriptors. An instance of this
* gets passed to an application callback whenever a local characteristic or
* descriptor is written.
*/
struct ble_gatt_write_ctxt {
/**
* (stack --> app)
* The data that the peer is writing to the characteristic.
*/
const void *data;
/**
* (stack --> app)
* The number of bytes of characteristic data being written.
*/
int len;
};
union ble_gatt_access_ctxt {
struct {
/**
* Points to the characteristic defintion corresponding to the
* characteristic being accessed. This is what the app registered at
* startup.
*/
const struct ble_gatt_chr_def *def;
union {
struct ble_gatt_read_ctxt read;
struct ble_gatt_write_ctxt write;
};
} chr;
struct {
/**
* Points to the descriptor defintion corresponding to the descriptor
* being accessed. This is what the app registered at startup.
*/
const struct ble_gatt_dsc_def *def;
union {
struct ble_gatt_read_ctxt read;
struct ble_gatt_write_ctxt write;
};
} dsc;
};
**** Characteristic registration
* In the GATT registration context struct (ble_gatt_register_ctxt),
rename some fields. Sorry, I know renames are annoying, but I think
the new names are more intuitive. See the proposed
ble_gatt_register_ctxt struct below.
union ble_gatt_register_ctxt {
struct {
uint16_t handle;
const struct ble_gatt_svc_def *svc_def;
} svc;
struct {
uint16_t def_handle;
uint16_t val_handle;
const struct ble_gatt_svc_def *svc_def;
const struct ble_gatt_chr_def *chr_def;
} chr;
struct {
uint16_t dsc_handle;
const struct ble_gatt_dsc_def *dsc_def;
uint16_t chr_def_handle;
uint16_t chr_val_handle;
const struct ble_gatt_chr_def *chr_def;
} dsc;
};
* The characteristic registration context (ble_gatt_register_ctxt.chr)
now contains a pointer to the parent service definition. This is
necessary for identifying the characteristic being registered in case
multiple characteristics have identical UUIDs.
* The descriptor registration context (ble_gatt_register_ctxt.dsc) now
contains the parent characteristic value handle (in addition to the
definition handle that it always contained).
* Ability to look up handles of registered services, characteristics,
and descriptors:
/** All "out" arguments can be null if you don't need the value. */
int
ble_gatt_find_svc(const uint8_t *svc_uuid128,
uint16_t *out_svc_handle);
int
ble_gatt_find_chr(const uint8_t *svc_uuid128, const uint8_t *chr_uuid128,
uint16_t *out_svc_handle,
uint16_t *out_chr_def_handle,
uint16_t *out_chr_val_handle);
int
ble_gatt_find_dsc(const uint8_t *svc_uuid128, const uint8_t *chr_uuid128,
const uint8_t *dsc_uuid128,
uint16_t *out_svc_handle,
uint16_t *out_chr_def_handle,
uint16_t *out_chr_val_handle,
uint16_t *out_dsc_handle);
**** Multi-response procedures
* Indicate that the procedure has successfully completed by passing a
status of BLE_HS_EDONE to the application callback. Currently,
successful completion is indicated with a status of 0 and a null
attribute pointer.
***** GAP
**** Advertising
* Add a duration_ms parameter. 0 means use a suitable default;
BLE_GAP_FOREVER means never expire. When an advertising procedure
times out, the application callback is executed with a status of
BLE_HS_ETIMEOUT.
* Move own_addr_type from params into a separate parameter. The intent
is that the advertising parameters struct can be reused for subsequent
advertisement procedures. The properties that are likely to change
among different advertising procedures (own_addr_type, peer_addr_type,
and peer_addr) are passed in separately. Maybe this distinction is
not very useful, and all parameters should just go into the struct...
* Add "high_duty_cycle" field to ble_gap_adv_params. Currently,
advertising is always low duty cycle!
* Make read-only parameters const.
* Remove support for default parameters when a null ble_gap_adv_params
pointer is passed. I think there is enough variation among
applications that the concept of a default set of parameters doesn't
make much sense. Instead, the application must pass a valid
ble_gap_adv_params struct to the ble_gap_adv_start() function. The
fields which may seem obscure to some application developers are
optional; if you assign zero to them, the stack fills them in for you.
Here is the proposed advertising API:
struct ble_gap_adv_params {
/*** Mandatory fields. */
uint8_t conn_mode;
uint8_t disc_mode;
uint8_t adv_filter_policy;
uint8_t high_duty_cycle:1;
/*** Optional fields; assign 0 to make the stack calculate them. */
uint16_t adv_itvl_min;
uint16_t adv_itvl_max;
uint8_t adv_channel_map;
};
int ble_gap_adv_start(uint8_t own_addr_type, uint8_t peer_addr_type,
const uint8_t *peer_addr, uint32_t duration_ms,
const struct ble_gap_adv_params *adv_params,
ble_gap_event_fn *cb, void *cb_arg);
**** Advertising data
* Allow app to explicitly specify the flags and tx power fields.
Currently:
* Flags:
The host always inserted the flags field into undirected
advertisements.
* TX Power Level:
The application could indicate whether the tx power level field
should be included in an advertisement, but it could not specify the
value; the stack always filled in the value.
Proposed change:
* Flags:
The application has three options concerning the flags field:
1. Don't include it in advertisements (!flags_is_present).
2. Explicitly specify the value (flags_is_present && flags != 0).
3. Let stack calculate the value (flags_is_present && flags == 0).
For option 3, the calculation is delayed until advertising is
enabled. The delay is necessary because the flags value depends on
the type of advertising being performed which is not known at this
time.
Note: The CSS prohibits advertising a flags value of 0, so this
method of specifying option 2 vs. 3 is sound.
* TX Power Level:
The application has three options concerning the TX power level
field:
1. Don't include it in advertisements (!tx_pwr_lvl_is_present).
2. Explicitly specify the value
(tx_pwr_lvl_is_present &&
tx_pwr_lvl != BLE_HS_ADV_TX_PWR_LVL_AUTO).
3. Let stack calculate the value
(tx_pwr_lvl_is_present &&
tx_pwr_lvl == BLE_HS_ADV_TX_PWR_LVL_AUTO).
**** Connecting
* Rename ble_gap_conn_initiate() to ble_gap_connect().
* Move own_addr_type from params into a separate parameter.
* Add a duration_ms parameter. 0 means use a suitable default;
BLE_GAP_FOREVER means never expire.
* Add role and master_clock_accuracy fields to the ble_gap_conn_desc
struct.
Proposed connect API:
struct ble_gap_conn_params {
uint16_t scan_itvl;
uint16_t scan_window;
uint16_t itvl_min;
uint16_t itvl_max;
uint16_t latency;
uint16_t supervision_timeout;
uint16_t min_ce_len;
uint16_t max_ce_len;
};
int ble_gap_connect(uint8_t own_addr_type,
uint8_t peer_addr_type, const uint8_t *peer_addr,
const struct ble_gap_conn_params *params,
ble_gap_event_fn *cb, void *cb_arg);
**** Discovery
* Add direct_address_type and direct_address fields to the
ble_gap_disc_desc struct. These fields are only reported when an "LE
Direct Advertising Report" event has been received from the
controller. These fields are necessary when you are using privacy;
you need to know what address type to use if you initiate a connection
to the advertiser.
What else?
Thanks,
Chris