mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-02-20 18:40:31 +00:00
markdown version of btstack manual
This commit is contained in:
parent
e50ff87cfe
commit
a11d132691
891
docs/manual/markdown/docs/appendix/apis.md
Normal file
891
docs/manual/markdown/docs/appendix/apis.md
Normal file
@ -0,0 +1,891 @@
|
||||
|
||||
|
||||
## Run Loop API
|
||||
<a name ="appendix:api_run_loop"></a>
|
||||
|
||||
/**
|
||||
* @brief Set timer based on current time in milliseconds.
|
||||
*/
|
||||
void run_loop_set_timer(timer_source_t *a, uint32_t timeout_in_ms);
|
||||
|
||||
/**
|
||||
* @brief Set callback that will be executed when timer expires.
|
||||
*/
|
||||
void run_loop_set_timer_handler(timer_source_t *ts, void (*process)(timer_source_t *_ts));
|
||||
|
||||
/**
|
||||
* @brief Add/Remove timer source.
|
||||
*/
|
||||
void run_loop_add_timer(timer_source_t *timer);
|
||||
int run_loop_remove_timer(timer_source_t *timer);
|
||||
|
||||
/**
|
||||
* @brief Init must be called before any other run_loop call. Use RUN_LOOP_EMBEDDED for embedded devices.
|
||||
*/
|
||||
void run_loop_init(RUN_LOOP_TYPE type);
|
||||
|
||||
/**
|
||||
* @brief Set data source callback.
|
||||
*/
|
||||
void run_loop_set_data_source_handler(data_source_t *ds, int (*process)(data_source_t *_ds));
|
||||
|
||||
/**
|
||||
* @brief Add/Remove data source.
|
||||
*/
|
||||
void run_loop_add_data_source(data_source_t *dataSource);
|
||||
int run_loop_remove_data_source(data_source_t *dataSource);
|
||||
|
||||
/**
|
||||
* @brief Execute configured run loop. This function does not return.
|
||||
*/
|
||||
void run_loop_execute(void);
|
||||
|
||||
// hack to fix HCI timer handling
|
||||
#ifdef HAVE_TICK
|
||||
/**
|
||||
* @brief Sets how many milliseconds has one tick.
|
||||
*/
|
||||
uint32_t embedded_ticks_for_ms(uint32_t time_in_ms);
|
||||
/**
|
||||
* @brief Queries the current time in ticks.
|
||||
*/
|
||||
uint32_t embedded_get_ticks(void);
|
||||
/**
|
||||
* @brief Queries the current time in ms
|
||||
*/
|
||||
uint32_t embedded_get_time_ms(void);
|
||||
/**
|
||||
* @brief Allows to update BTstack system ticks based on another already existing clock.
|
||||
*/
|
||||
void embedded_set_ticks(uint32_t ticks);
|
||||
#endif
|
||||
#ifdef EMBEDDED
|
||||
/**
|
||||
* @brief Sets an internal flag that is checked in the critical section just before entering sleep mode. Has to be called by the interrupt handler of a data source to signal the run loop that a new data is available.
|
||||
*/
|
||||
void embedded_trigger(void);
|
||||
/**
|
||||
* @brief Execute run_loop once. It can be used to integrate BTstack's timer and data source processing into a foreign run loop (it is not recommended).
|
||||
*/
|
||||
void embedded_execute_once(void);
|
||||
#endif
|
||||
|
||||
|
||||
## HCI API
|
||||
<a name ="appendix:api_hci"></a>
|
||||
|
||||
le_connection_parameter_range_t gap_le_get_connection_parameter_range();
|
||||
void gap_le_set_connection_parameter_range(le_connection_parameter_range_t range);
|
||||
|
||||
/* LE Client Start */
|
||||
|
||||
le_command_status_t le_central_start_scan(void);
|
||||
le_command_status_t le_central_stop_scan(void);
|
||||
le_command_status_t le_central_connect(bd_addr_t addr, bd_addr_type_t addr_type);
|
||||
le_command_status_t le_central_connect_cancel(void);
|
||||
le_command_status_t gap_disconnect(hci_con_handle_t handle);
|
||||
void le_central_set_scan_parameters(uint8_t scan_type, uint16_t scan_interval, uint16_t scan_window);
|
||||
|
||||
/* LE Client End */
|
||||
|
||||
void hci_connectable_control(uint8_t enable);
|
||||
void hci_close(void);
|
||||
|
||||
/**
|
||||
* @note New functions replacing: hci_can_send_packet_now[_using_packet_buffer]
|
||||
*/
|
||||
int hci_can_send_command_packet_now(void);
|
||||
|
||||
/**
|
||||
* @brief Gets local address.
|
||||
*/
|
||||
void hci_local_bd_addr(bd_addr_t address_buffer);
|
||||
|
||||
/**
|
||||
* @brief Set up HCI. Needs to be called before any other function.
|
||||
*/
|
||||
void hci_init(hci_transport_t *transport, void *config, bt_control_t *control, remote_device_db_t const* remote_device_db);
|
||||
|
||||
/**
|
||||
* @brief Set class of device that will be set during Bluetooth init.
|
||||
*/
|
||||
void hci_set_class_of_device(uint32_t class_of_device);
|
||||
|
||||
/**
|
||||
* @brief Set Public BD ADDR - passed on to Bluetooth chipset if supported in bt_control_h
|
||||
*/
|
||||
void hci_set_bd_addr(bd_addr_t addr);
|
||||
|
||||
/**
|
||||
* @brief Registers a packet handler. Used if L2CAP is not used (rarely).
|
||||
*/
|
||||
void hci_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size));
|
||||
|
||||
/**
|
||||
* @brief Requests the change of BTstack power mode.
|
||||
*/
|
||||
int hci_power_control(HCI_POWER_MODE mode);
|
||||
|
||||
/**
|
||||
* @brief Allows to control if device is discoverable. OFF by default.
|
||||
*/
|
||||
void hci_discoverable_control(uint8_t enable);
|
||||
|
||||
/**
|
||||
* @brief Creates and sends HCI command packets based on a template and a list of parameters. Will return error if outgoing data buffer is occupied.
|
||||
*/
|
||||
int hci_send_cmd(const hci_cmd_t *cmd, ...);
|
||||
|
||||
/**
|
||||
* @brief Deletes link key for remote device with baseband address.
|
||||
*/
|
||||
void hci_drop_link_key_for_bd_addr(bd_addr_t addr);
|
||||
|
||||
/* Configure Secure Simple Pairing */
|
||||
|
||||
/**
|
||||
* @brief Enable will enable SSP during init.
|
||||
*/
|
||||
void hci_ssp_set_enable(int enable);
|
||||
|
||||
/**
|
||||
* @brief If set, BTstack will respond to io capability request using authentication requirement.
|
||||
*/
|
||||
void hci_ssp_set_io_capability(int ssp_io_capability);
|
||||
void hci_ssp_set_authentication_requirement(int authentication_requirement);
|
||||
|
||||
/**
|
||||
* @brief If set, BTstack will confirm a numeric comparison and enter '000000' if requested.
|
||||
*/
|
||||
void hci_ssp_set_auto_accept(int auto_accept);
|
||||
|
||||
/**
|
||||
* @brief Get addr type and address used in advertisement packets.
|
||||
*/
|
||||
void hci_le_advertisement_address(uint8_t * addr_type, bd_addr_t addr);
|
||||
|
||||
|
||||
## L2CAP API
|
||||
<a name ="appendix:api_l2cap"></a>
|
||||
|
||||
/**
|
||||
* @brief Set up L2CAP and register L2CAP with HCI layer.
|
||||
*/
|
||||
void l2cap_init(void);
|
||||
|
||||
/**
|
||||
* @brief Registers a packet handler that handles HCI and general BTstack events.
|
||||
*/
|
||||
void l2cap_register_packet_handler(void (*handler)(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size));
|
||||
|
||||
/**
|
||||
* @brief Creates L2CAP channel to the PSM of a remote device with baseband address. A new baseband connection will be initiated if necessary.
|
||||
*/
|
||||
void l2cap_create_channel_internal(void * connection, btstack_packet_handler_t packet_handler, bd_addr_t address, uint16_t psm, uint16_t mtu);
|
||||
|
||||
/**
|
||||
* @brief Disconnects L2CAP channel with given identifier.
|
||||
*/
|
||||
void l2cap_disconnect_internal(uint16_t local_cid, uint8_t reason);
|
||||
|
||||
/**
|
||||
* @brief Queries the maximal transfer unit (MTU) for L2CAP channel with given identifier.
|
||||
*/
|
||||
uint16_t l2cap_get_remote_mtu_for_local_cid(uint16_t local_cid);
|
||||
|
||||
/**
|
||||
* @brief Sends L2CAP data packet to the channel with given identifier.
|
||||
*/
|
||||
int l2cap_send_internal(uint16_t local_cid, uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Registers L2CAP service with given PSM and MTU, and assigns a packet handler. On embedded systems, use NULL for connection parameter.
|
||||
*/
|
||||
void l2cap_register_service_internal(void *connection, btstack_packet_handler_t packet_handler, uint16_t psm, uint16_t mtu, gap_security_level_t security_level);
|
||||
|
||||
/**
|
||||
* @brief Unregisters L2CAP service with given PSM. On embedded systems, use NULL for connection parameter.
|
||||
*/
|
||||
void l2cap_unregister_service_internal(void *connection, uint16_t psm);
|
||||
|
||||
/**
|
||||
* @brief Accepts/Deny incoming L2CAP connection.
|
||||
*/
|
||||
void l2cap_accept_connection_internal(uint16_t local_cid);
|
||||
void l2cap_decline_connection_internal(uint16_t local_cid, uint8_t reason);
|
||||
|
||||
/**
|
||||
* @brief Request LE connection parameter update
|
||||
*/
|
||||
int l2cap_le_request_connection_parameter_update(uint16_t handle, uint16_t interval_min, uint16_t interval_max, uint16_t slave_latency, uint16_t timeout_multiplier);
|
||||
|
||||
/**
|
||||
* @brief Non-blocking UART write
|
||||
*/
|
||||
int l2cap_can_send_packet_now(uint16_t local_cid);
|
||||
int l2cap_reserve_packet_buffer(void);
|
||||
void l2cap_release_packet_buffer(void);
|
||||
|
||||
/**
|
||||
* @brief Get outgoing buffer and prepare data.
|
||||
*/
|
||||
uint8_t *l2cap_get_outgoing_buffer(void);
|
||||
|
||||
int l2cap_send_prepared(uint16_t local_cid, uint16_t len);
|
||||
|
||||
int l2cap_send_prepared_connectionless(uint16_t handle, uint16_t cid, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Bluetooth 4.0 - allows to register handler for Attribute Protocol and Security Manager Protocol.
|
||||
*/
|
||||
void l2cap_register_fixed_channel(btstack_packet_handler_t packet_handler, uint16_t channel_id);
|
||||
|
||||
uint16_t l2cap_max_mtu(void);
|
||||
uint16_t l2cap_max_le_mtu(void);
|
||||
|
||||
int l2cap_send_connectionless(uint16_t handle, uint16_t cid, uint8_t *data, uint16_t len);
|
||||
|
||||
|
||||
## RFCOMM API
|
||||
<a name ="appendix:api_rfcomm"></a>
|
||||
|
||||
/**
|
||||
* @brief Set up RFCOMM.
|
||||
*/
|
||||
void rfcomm_init(void);
|
||||
|
||||
/**
|
||||
* @brief Set security level required for incoming connections, need to be called before registering services.
|
||||
*/
|
||||
void rfcomm_set_required_security_level(gap_security_level_t security_level);
|
||||
|
||||
/**
|
||||
* @brief Register packet handler.
|
||||
*/
|
||||
void rfcomm_register_packet_handler(void (*handler)(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size));
|
||||
|
||||
/**
|
||||
* @brief Creates RFCOMM connection (channel) to a given server channel on a remote device with baseband address. A new baseband connection will be initiated if necessary. This channel will automatically provide enough credits to the remote side
|
||||
*/
|
||||
void rfcomm_create_channel_internal(void * connection, bd_addr_t addr, uint8_t channel);
|
||||
|
||||
/**
|
||||
* @brief Creates RFCOMM connection (channel) to a given server channel on a remote device with baseband address. new baseband connection will be initiated if necessary. This channel will use explicit credit management. During channel establishment, an initial amount of credits is provided.
|
||||
*/
|
||||
void rfcomm_create_channel_with_initial_credits_internal(void * connection, bd_addr_t addr, uint8_t server_channel, uint8_t initial_credits);
|
||||
|
||||
/**
|
||||
* @brief Disconnects RFCOMM channel with given identifier.
|
||||
*/
|
||||
void rfcomm_disconnect_internal(uint16_t rfcomm_cid);
|
||||
|
||||
/**
|
||||
* @brief Registers RFCOMM service for a server channel and a maximum frame size, and assigns a packet handler. On embedded systems, use NULL for connection parameter. This channel provides automatically enough credits to the remote side.
|
||||
*/
|
||||
void rfcomm_register_service_internal(void * connection, uint8_t channel, uint16_t max_frame_size);
|
||||
|
||||
/**
|
||||
* @brief Registers RFCOMM service for a server channel and a maximum frame size, and assigns a packet handler. On embedded systems, use NULL for connection parameter. This channel will use explicit credit management. During channel establishment, an initial amount of credits is provided.
|
||||
*/
|
||||
void rfcomm_register_service_with_initial_credits_internal(void * connection, uint8_t channel, uint16_t max_frame_size, uint8_t initial_credits);
|
||||
|
||||
/**
|
||||
* @brief Unregister RFCOMM service.
|
||||
*/
|
||||
void rfcomm_unregister_service_internal(uint8_t service_channel);
|
||||
|
||||
/**
|
||||
* @brief Accepts/Deny incoming RFCOMM connection.
|
||||
*/
|
||||
void rfcomm_accept_connection_internal(uint16_t rfcomm_cid);
|
||||
void rfcomm_decline_connection_internal(uint16_t rfcomm_cid);
|
||||
|
||||
/**
|
||||
* @brief Grant more incoming credits to the remote side for the given RFCOMM channel identifier.
|
||||
*/
|
||||
void rfcomm_grant_credits(uint16_t rfcomm_cid, uint8_t credits);
|
||||
|
||||
/**
|
||||
* @brief Checks if RFCOMM can send packet. Returns yes if packet can be sent.
|
||||
*/
|
||||
int rfcomm_can_send_packet_now(uint16_t rfcomm_cid);
|
||||
|
||||
/**
|
||||
* @brief Sends RFCOMM data packet to the RFCOMM channel with given identifier.
|
||||
*/
|
||||
int rfcomm_send_internal(uint16_t rfcomm_cid, uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Sends Local Line Status, see LINE_STATUS_..
|
||||
*/
|
||||
int rfcomm_send_local_line_status(uint16_t rfcomm_cid, uint8_t line_status);
|
||||
|
||||
/**
|
||||
* @brief Send local modem status. see MODEM_STAUS_..
|
||||
*/
|
||||
int rfcomm_send_modem_status(uint16_t rfcomm_cid, uint8_t modem_status);
|
||||
|
||||
/**
|
||||
* @brief Configure remote port
|
||||
*/
|
||||
int rfcomm_send_port_configuration(uint16_t rfcomm_cid, rpn_baud_t baud_rate, rpn_data_bits_t data_bits, rpn_stop_bits_t stop_bits, rpn_parity_t parity, rpn_flow_control_t flow_control);
|
||||
|
||||
/**
|
||||
* @brief Query remote port
|
||||
*/
|
||||
int rfcomm_query_port_configuration(uint16_t rfcomm_cid);
|
||||
|
||||
/**
|
||||
* @brief Allow to create RFCOMM packet in outgoing buffer.
|
||||
*/
|
||||
int rfcomm_reserve_packet_buffer(void);
|
||||
void rfcomm_release_packet_buffer(void);
|
||||
uint8_t * rfcomm_get_outgoing_buffer(void);
|
||||
uint16_t rfcomm_get_max_frame_size(uint16_t rfcomm_cid);
|
||||
int rfcomm_send_prepared(uint16_t rfcomm_cid, uint16_t len);
|
||||
|
||||
|
||||
## SDP API
|
||||
<a name ="appendix:api_sdp"></a>
|
||||
|
||||
/**
|
||||
* @brief Set up SDP.
|
||||
*/
|
||||
void sdp_init(void);
|
||||
|
||||
void sdp_register_packet_handler(void (*handler)(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size));
|
||||
|
||||
#ifdef EMBEDDED
|
||||
/**
|
||||
* @brief Register service record internally - this version doesn't copy the record therefore it must be forever accessible. Preconditions:
|
||||
- AttributeIDs are in ascending order;
|
||||
- ServiceRecordHandle is first attribute and valid.
|
||||
* @return ServiceRecordHandle or 0 if registration failed.
|
||||
*/
|
||||
uint32_t sdp_register_service_internal(void *connection, service_record_item_t * record_item);
|
||||
#endif
|
||||
|
||||
#ifndef EMBEDDED
|
||||
/**
|
||||
* @brief Register service record internally - this version creates a copy of the record precondition: AttributeIDs are in ascending order => ServiceRecordHandle is first attribute if present.
|
||||
* @return ServiceRecordHandle or 0 if registration failed
|
||||
*/
|
||||
uint32_t sdp_register_service_internal(void *connection, uint8_t * service_record);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Unregister service record internally.
|
||||
*/
|
||||
void sdp_unregister_service_internal(void *connection, uint32_t service_record_handle);
|
||||
|
||||
|
||||
## SDP Client API
|
||||
<a name ="appendix:api_sdp_client"></a>
|
||||
|
||||
|
||||
/**
|
||||
* @brief Queries the SDP service of the remote device given a service search pattern and a list of attribute IDs. The remote data is handled by the SDP parser. The SDP parser delivers attribute values and done event via a registered callback.
|
||||
*/
|
||||
void sdp_client_query(bd_addr_t remote, uint8_t * des_serviceSearchPattern, uint8_t * des_attributeIDList);
|
||||
|
||||
#ifdef HAVE_SDP_EXTRA_QUERIES
|
||||
void sdp_client_service_attribute_search(bd_addr_t remote, uint32_t search_serviceRecordHandle, uint8_t * des_attributeIDList);
|
||||
void sdp_client_service_search(bd_addr_t remote, uint8_t * des_serviceSearchPattern);
|
||||
#endif
|
||||
|
||||
|
||||
## SDP RFCOMM Query API
|
||||
<a name ="appendix:api_sdp_queries"></a>
|
||||
|
||||
/**
|
||||
* @brief SDP Query RFCOMM event to deliver channel number and service name byte by byte.
|
||||
*/
|
||||
typedef struct sdp_query_rfcomm_service_event {
|
||||
uint8_t type;
|
||||
uint8_t channel_nr;
|
||||
uint8_t * service_name;
|
||||
} sdp_query_rfcomm_service_event_t;
|
||||
|
||||
/**
|
||||
* @brief Registers a callback to receive RFCOMM service and query complete event.
|
||||
*/
|
||||
void sdp_query_rfcomm_register_callback(void(*sdp_app_callback)(sdp_query_event_t * event, void * context), void * context);
|
||||
|
||||
void sdp_query_rfcomm_deregister_callback(void);
|
||||
|
||||
/**
|
||||
* @brief Searches SDP records on a remote device for RFCOMM services with a given UUID.
|
||||
*/
|
||||
void sdp_query_rfcomm_channel_and_name_for_uuid(bd_addr_t remote, uint16_t uuid);
|
||||
|
||||
/**
|
||||
* @brief Searches SDP records on a remote device for RFCOMM services with a given service search pattern.
|
||||
*/
|
||||
void sdp_query_rfcomm_channel_and_name_for_search_pattern(bd_addr_t remote, uint8_t * des_serviceSearchPattern);
|
||||
|
||||
|
||||
## GATT Client API
|
||||
<a name ="appendix:api_gatt_client"></a>
|
||||
|
||||
typedef struct gatt_complete_event{
|
||||
uint8_t type;
|
||||
uint16_t handle;
|
||||
uint16_t attribute_handle;
|
||||
uint8_t status;
|
||||
} gatt_complete_event_t;
|
||||
|
||||
typedef struct le_service{
|
||||
uint16_t start_group_handle;
|
||||
uint16_t end_group_handle;
|
||||
uint16_t uuid16;
|
||||
uint8_t uuid128[16];
|
||||
} le_service_t;
|
||||
|
||||
typedef struct le_service_event{
|
||||
uint8_t type;
|
||||
uint16_t handle;
|
||||
le_service_t service;
|
||||
} le_service_event_t;
|
||||
|
||||
typedef struct le_characteristic{
|
||||
uint16_t start_handle;
|
||||
uint16_t value_handle;
|
||||
uint16_t end_handle;
|
||||
uint16_t properties;
|
||||
uint16_t uuid16;
|
||||
uint8_t uuid128[16];
|
||||
} le_characteristic_t;
|
||||
|
||||
typedef struct le_characteristic_event{
|
||||
uint8_t type;
|
||||
uint16_t handle;
|
||||
le_characteristic_t characteristic;
|
||||
} le_characteristic_event_t;
|
||||
|
||||
typedef struct le_characteristic_value_event{
|
||||
uint8_t type;
|
||||
uint16_t handle;
|
||||
uint16_t value_handle;
|
||||
uint16_t value_offset;
|
||||
uint16_t blob_length;
|
||||
uint8_t * blob;
|
||||
} le_characteristic_value_event_t;
|
||||
|
||||
typedef struct le_characteristic_descriptor{
|
||||
uint16_t handle;
|
||||
uint16_t uuid16;
|
||||
uint8_t uuid128[16];
|
||||
} le_characteristic_descriptor_t;
|
||||
|
||||
typedef struct le_characteristic_descriptor_event{
|
||||
uint8_t type;
|
||||
uint16_t handle;
|
||||
le_characteristic_descriptor_t characteristic_descriptor;
|
||||
uint16_t value_length;
|
||||
uint16_t value_offset;
|
||||
uint8_t * value;
|
||||
} le_characteristic_descriptor_event_t;
|
||||
|
||||
/**
|
||||
* @brief Set up GATT client.
|
||||
*/
|
||||
void gatt_client_init(void);
|
||||
|
||||
/**
|
||||
* @brief Register callback (packet handler) for GATT client. Returns GATT client ID.
|
||||
*/
|
||||
uint16_t gatt_client_register_packet_handler (gatt_client_callback_t callback);
|
||||
|
||||
/**
|
||||
* @brief Unregister callback (packet handler) for GATT client.
|
||||
*/
|
||||
void gatt_client_unregister_packet_handler(uint16_t gatt_client_id);
|
||||
|
||||
/**
|
||||
* @brief MTU is available after the first query has completed. If status is equal to BLE_PERIPHERAL_OK, it returns the real value, otherwise the default value of 23.
|
||||
*/
|
||||
le_command_status_t gatt_client_get_mtu(uint16_t handle, uint16_t * mtu);
|
||||
|
||||
/**
|
||||
* @brief Returns if the GATT client is ready to receive a query. It is used with daemon.
|
||||
*/
|
||||
int gatt_client_is_ready(uint16_t handle);
|
||||
|
||||
/**
|
||||
* @brief Discovers all primary services. For each found service, an le_service_event_t with type set to GATT_SERVICE_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t, with type set to GATT_QUERY_COMPLETE, marks the end of discovery.
|
||||
*/
|
||||
le_command_status_t gatt_client_discover_primary_services(uint16_t gatt_client_id, uint16_t con_handle);
|
||||
|
||||
/**
|
||||
* @brief Discovers a specific primary service given its UUID. This service may exist multiple times. For each found service, an le_service_event_t with type set to GATT_SERVICE_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t, with type set to GATT_QUERY_COMPLETE, marks the end of discovery.
|
||||
*/
|
||||
le_command_status_t gatt_client_discover_primary_services_by_uuid16(uint16_t gatt_client_id, uint16_t con_handle, uint16_t uuid16);
|
||||
le_command_status_t gatt_client_discover_primary_services_by_uuid128(uint16_t gatt_client_id, uint16_t con_handle, const uint8_t * uuid);
|
||||
|
||||
/**
|
||||
* @brief Finds included services within the specified service. For each found included service, an le_service_event_t with type set to GATT_INCLUDED_SERVICE_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of discovery. Information about included service type (primary/secondary) can be retrieved either by sending an ATT find information request for the returned start group handle (returning the handle and the UUID for primary or secondary service) or by comparing the service to the list of all primary services.
|
||||
*/
|
||||
le_command_status_t gatt_client_find_included_services_for_service(uint16_t gatt_client_id, uint16_t con_handle, le_service_t *service);
|
||||
|
||||
/**
|
||||
* @brief Discovers all characteristics within the specified service. For each found characteristic, an le_characteristics_event_t with type set to GATT_CHARACTERISTIC_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of discovery.
|
||||
*/
|
||||
le_command_status_t gatt_client_discover_characteristics_for_service(uint16_t gatt_client_id, uint16_t con_handle, le_service_t *service);
|
||||
|
||||
/**
|
||||
* @brief The following four functions are used to discover all characteristics within the specified service or handle range, and return those that match the given UUID. For each found characteristic, an le_characteristic_event_t with type set to GATT_CHARACTERISTIC_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of discovery.
|
||||
*/
|
||||
le_command_status_t gatt_client_discover_characteristics_for_handle_range_by_uuid16(uint16_t gatt_client_id, uint16_t con_handle, uint16_t start_handle, uint16_t end_handle, uint16_t uuid16);
|
||||
le_command_status_t gatt_client_discover_characteristics_for_handle_range_by_uuid128(uint16_t gatt_client_id, uint16_t con_handle, uint16_t start_handle, uint16_t end_handle, uint8_t * uuid);
|
||||
le_command_status_t gatt_client_discover_characteristics_for_service_by_uuid16 (uint16_t gatt_client_id, uint16_t con_handle, le_service_t *service, uint16_t uuid16);
|
||||
le_command_status_t gatt_client_discover_characteristics_for_service_by_uuid128(uint16_t gatt_client_id, uint16_t con_handle, le_service_t *service, uint8_t * uuid128);
|
||||
|
||||
/**
|
||||
* @brief Discovers attribute handle and UUID of a characteristic descriptor within the specified characteristic. For each found descriptor, an le_characteristic_descriptor_event_t with type set to GATT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of discovery.
|
||||
*/
|
||||
le_command_status_t gatt_client_discover_characteristic_descriptors(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_t *characteristic);
|
||||
|
||||
/**
|
||||
* @brief Reads the characteristic value using the characteristic's value handle. If the characteristic value is found, an le_characteristic_value_event_t with type set to GATT_CHARACTERISTIC_VALUE_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of read.
|
||||
*/
|
||||
le_command_status_t gatt_client_read_value_of_characteristic(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_t *characteristic);
|
||||
le_command_status_t gatt_client_read_value_of_characteristic_using_value_handle(uint16_t gatt_client_id, uint16_t con_handle, uint16_t characteristic_value_handle);
|
||||
|
||||
/**
|
||||
* @brief Reads the long characteristic value using the characteristic's value handle. The value will be returned in several blobs. For each blob, an le_characteristic_value_event_t with type set to GATT_CHARACTERISTIC_VALUE_QUERY_RESULT and updated value offset will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, mark the end of read.
|
||||
*/
|
||||
le_command_status_t gatt_client_read_long_value_of_characteristic(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_t *characteristic);
|
||||
le_command_status_t gatt_client_read_long_value_of_characteristic_using_value_handle(uint16_t gatt_client_id, uint16_t con_handle, uint16_t characteristic_value_handle);
|
||||
|
||||
/**
|
||||
* @brief Writes the characteristic value using the characteristic's value handle without an acknowledgment that the write was successfully performed.
|
||||
*/
|
||||
le_command_status_t gatt_client_write_value_of_characteristic_without_response(uint16_t gatt_client_id, uint16_t con_handle, uint16_t characteristic_value_handle, uint16_t length, uint8_t * data);
|
||||
|
||||
/**
|
||||
* @brief Writes the authenticated characteristic value using the characteristic's value handle without an acknowledgment that the write was successfully performed.
|
||||
*/
|
||||
le_command_status_t gatt_client_signed_write_without_response(uint16_t gatt_client_id, uint16_t con_handle, uint16_t handle, uint16_t message_len, uint8_t * message);
|
||||
|
||||
/**
|
||||
* @brief Writes the characteristic value using the characteristic's value handle. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of write. The write is successfully performed, if the event's status field is set to 0.
|
||||
*/
|
||||
le_command_status_t gatt_client_write_value_of_characteristic(uint16_t gatt_client_id, uint16_t con_handle, uint16_t characteristic_value_handle, uint16_t length, uint8_t * data);
|
||||
le_command_status_t gatt_client_write_long_value_of_characteristic(uint16_t gatt_client_id, uint16_t con_handle, uint16_t characteristic_value_handle, uint16_t length, uint8_t * data);
|
||||
|
||||
/**
|
||||
* @brief Writes of the long characteristic value using the characteristic's value handle. It uses server response to validate that the write was correctly received. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE marks the end of write. The write is successfully performed, if the event's status field is set to 0.
|
||||
*/
|
||||
le_command_status_t gatt_client_reliable_write_long_value_of_characteristic(uint16_t gatt_client_id, uint16_t con_handle, uint16_t characteristic_value_handle, uint16_t length, uint8_t * data);
|
||||
|
||||
/**
|
||||
* @brief Reads the characteristic descriptor using its handle. If the characteristic descriptor is found, an le_characteristic_descriptor_event_t with type set to GATT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of read.
|
||||
*/
|
||||
le_command_status_t gatt_client_read_characteristic_descriptor(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_descriptor_t * descriptor);
|
||||
|
||||
/**
|
||||
* @brief Reads the long characteristic descriptor using its handle. It will be returned in several blobs. For each blob, an le_characteristic_descriptor_event_t with type set to GATT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT will be generated and passed to the registered callback. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of read.
|
||||
*/
|
||||
le_command_status_t gatt_client_read_long_characteristic_descriptor(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_descriptor_t * descriptor);
|
||||
|
||||
/**
|
||||
* @brief Writes the characteristic descriptor using its handle. The gatt_complete_event_t with type set to GATT_QUERY_COMPLETE, marks the end of write. The write is successfully performed, if the event's status field is set to 0.
|
||||
*/
|
||||
le_command_status_t gatt_client_write_characteristic_descriptor(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_descriptor_t * descriptor, uint16_t length, uint8_t * data);
|
||||
le_command_status_t gatt_client_write_long_characteristic_descriptor(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_descriptor_t * descriptor, uint16_t length, uint8_t * data);
|
||||
|
||||
/**
|
||||
* @brief Writes the client characteristic configuration of the specified characteristic. It is used to subscribe for notifications or indications of the characteristic value. For notifications or indications specify: GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION resp. GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_INDICATION as configuration value.
|
||||
*/
|
||||
le_command_status_t gatt_client_write_client_characteristic_configuration(uint16_t gatt_client_id, uint16_t con_handle, le_characteristic_t * characteristic, uint16_t configuration);
|
||||
|
||||
|
||||
## PAN API
|
||||
<a name ="appendix:api_pan"></a>
|
||||
|
||||
/**
|
||||
* @brief Creates SDP record for PANU BNEP service in provided empty buffer.
|
||||
* @note Make sure the buffer is big enough.
|
||||
*
|
||||
* @param service is an empty buffer to store service record
|
||||
* @param security_desc
|
||||
* @param name if NULL, the default service name will be assigned
|
||||
* @param description if NULL, the default service description will be assigned
|
||||
*/
|
||||
void pan_create_panu_service(uint8_t *service, const char *name, const char *description, security_description_t security_desc);
|
||||
|
||||
/**
|
||||
* @brief Creates SDP record for GN BNEP service in provided empty buffer.
|
||||
* @note Make sure the buffer is big enough.
|
||||
*
|
||||
* @param service is an empty buffer to store service record
|
||||
* @param security_desc
|
||||
* @param name if NULL, the default service name will be assigned
|
||||
* @param description if NULL, the default service description will be assigned
|
||||
* @param IPv4Subnet is optional subnet definition, e.g. "10.0.0.0/8"
|
||||
* @param IPv6Subnet is optional subnet definition given in the standard IETF format with the absolute attribute IDs
|
||||
*/
|
||||
void pan_create_gn_service(uint8_t *service, const char *name, const char *description, security_description_t security_desc,
|
||||
const char *IPv4Subnet, const char *IPv6Subnet);
|
||||
|
||||
/**
|
||||
* @brief Creates SDP record for NAP BNEP service in provided empty buffer.
|
||||
* @note Make sure the buffer is big enough.
|
||||
*
|
||||
* @param service is an empty buffer to store service record
|
||||
* @param name if NULL, the default service name will be assigned
|
||||
* @param security_desc
|
||||
* @param description if NULL, the default service description will be assigned
|
||||
* @param net_access_type type of available network access
|
||||
* @param max_net_access_rate based on net_access_type measured in byte/s
|
||||
* @param IPv4Subnet is optional subnet definition, e.g. "10.0.0.0/8"
|
||||
* @param IPv6Subnet is optional subnet definition given in the standard IETF format with the absolute attribute IDs
|
||||
*/
|
||||
void pan_create_nap_service(uint8_t *service, const char *name, const char *description, security_description_t security_desc,
|
||||
net_access_type_t net_access_type, uint32_t max_net_access_rate, const char *IPv4Subnet, const char *IPv6Subnet);
|
||||
|
||||
|
||||
## BNEP API
|
||||
<a name ="appendix:api_bnep"></a>
|
||||
|
||||
/**
|
||||
* @brief Set up BNEP.
|
||||
*/
|
||||
void bnep_init(void);
|
||||
|
||||
/**
|
||||
* @brief Check if a data packet can be send out.
|
||||
*/
|
||||
int bnep_can_send_packet_now(uint16_t bnep_cid);
|
||||
|
||||
/**
|
||||
* @brief Send a data packet.
|
||||
*/
|
||||
int bnep_send(uint16_t bnep_cid, uint8_t *packet, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Set the network protocol filter.
|
||||
*/
|
||||
int bnep_set_net_type_filter(uint16_t bnep_cid, bnep_net_filter_t *filter, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Set the multicast address filter.
|
||||
*/
|
||||
int bnep_set_multicast_filter(uint16_t bnep_cid, bnep_multi_filter_t *filter, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Set security level required for incoming connections, need to be called before registering services.
|
||||
*/
|
||||
void bnep_set_required_security_level(gap_security_level_t security_level);
|
||||
|
||||
/**
|
||||
* @brief Register packet handler.
|
||||
*/
|
||||
void bnep_register_packet_handler(void (*handler)(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size));
|
||||
|
||||
/**
|
||||
* @brief Creates BNEP connection (channel) to a given server on a remote device with baseband address. A new baseband connection will be initiated if necessary.
|
||||
*/
|
||||
int bnep_connect(void * connection, bd_addr_t addr, uint16_t l2cap_psm, uint16_t uuid_dest);
|
||||
|
||||
/**
|
||||
* @brief Disconnects BNEP channel with given identifier.
|
||||
*/
|
||||
void bnep_disconnect(bd_addr_t addr);
|
||||
|
||||
/**
|
||||
* @brief Registers BNEP service, set a maximum frame size and assigns a packet handler. On embedded systems, use NULL for connection parameter.
|
||||
*/
|
||||
void bnep_register_service(void * connection, uint16_t service_uuid, uint16_t max_frame_size);
|
||||
|
||||
/**
|
||||
* @brief Unregister BNEP service.
|
||||
*/
|
||||
void bnep_unregister_service(uint16_t service_uuid);
|
||||
|
||||
|
||||
## GAP API
|
||||
<a name ="appendix:api_gap"></a>
|
||||
|
||||
/**
|
||||
* @brief Enable/disable bonding. Default is enabled.
|
||||
* @param enabled
|
||||
*/
|
||||
void gap_set_bondable_mode(int enabled);
|
||||
|
||||
/**
|
||||
* @brief Start dedicated bonding with device. Disconnect after bonding.
|
||||
* @param device
|
||||
* @param request MITM protection
|
||||
* @return error, if max num acl connections active
|
||||
* @result GAP_DEDICATED_BONDING_COMPLETE
|
||||
*/
|
||||
int gap_dedicated_bonding(bd_addr_t device, int mitm_protection_required);
|
||||
|
||||
gap_security_level_t gap_security_level_for_link_key_type(link_key_type_t link_key_type);
|
||||
gap_security_level_t gap_security_level(hci_con_handle_t con_handle);
|
||||
|
||||
void gap_request_security_level(hci_con_handle_t con_handle, gap_security_level_t level);
|
||||
int gap_mitm_protection_required_for_security_level(gap_security_level_t level);
|
||||
|
||||
/**
|
||||
* @brief Sets local name.
|
||||
* @note has to be done before stack starts up
|
||||
* @param name is not copied, make sure memory is accessible during stack startup
|
||||
*/
|
||||
void gap_set_local_name(const char * local_name);
|
||||
|
||||
|
||||
## SM API
|
||||
<a name ="appendix:api_sm"></a>
|
||||
|
||||
/**
|
||||
* @brief Security Manager event
|
||||
*/
|
||||
typedef struct sm_event {
|
||||
uint8_t type; ///< See <btstack/hci_cmds.h> SM_...
|
||||
uint8_t addr_type;
|
||||
bd_addr_t address;
|
||||
uint32_t passkey; ///< only used for SM_PASSKEY_DISPLAY_NUMBER
|
||||
uint16_t le_device_db_index; ///< only used for SM_IDENTITY_RESOLVING_..
|
||||
uint8_t authorization_result; ///< only use for SM_AUTHORIZATION_RESULT
|
||||
} sm_event_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes the Security Manager, connects to L2CAP
|
||||
*/
|
||||
void sm_init(void);
|
||||
|
||||
/**
|
||||
* @brief Set secret ER key for key generation as described in Core V4.0, Vol 3, Part G, 5.2.2
|
||||
* @param er
|
||||
*/
|
||||
void sm_set_er(sm_key_t er);
|
||||
|
||||
/**
|
||||
* @brief Set secret IR key for key generation as described in Core V4.0, Vol 3, Part G, 5.2.2
|
||||
*/
|
||||
void sm_set_ir(sm_key_t ir);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Registers OOB Data Callback. The callback should set the oob_data and return 1 if OOB data is availble
|
||||
* @param get_oob_data_callback
|
||||
*/
|
||||
void sm_register_oob_data_callback( int (*get_oob_data_callback)(uint8_t addres_type, bd_addr_t addr, uint8_t * oob_data));
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Registers packet handler. Called by att_server.c
|
||||
*/
|
||||
void sm_register_packet_handler(btstack_packet_handler_t handler);
|
||||
|
||||
/**
|
||||
* @brief Limit the STK generation methods. Bonding is stopped if the resulting one isn't in the list
|
||||
* @param OR combination of SM_STK_GENERATION_METHOD_
|
||||
*/
|
||||
void sm_set_accepted_stk_generation_methods(uint8_t accepted_stk_generation_methods);
|
||||
|
||||
/**
|
||||
* @brief Set the accepted encryption key size range. Bonding is stopped if the result isn't within the range
|
||||
* @param min_size (default 7)
|
||||
* @param max_size (default 16)
|
||||
*/
|
||||
void sm_set_encryption_key_size_range(uint8_t min_size, uint8_t max_size);
|
||||
|
||||
/**
|
||||
* @brief Sets the requested authentication requirements, bonding yes/no, MITM yes/no
|
||||
* @param OR combination of SM_AUTHREQ_ flags
|
||||
*/
|
||||
void sm_set_authentication_requirements(uint8_t auth_req);
|
||||
|
||||
/**
|
||||
* @brief Sets the available IO Capabilities
|
||||
* @param IO_CAPABILITY_
|
||||
*/
|
||||
void sm_set_io_capabilities(io_capability_t io_capability);
|
||||
|
||||
/**
|
||||
* @brief Let Peripheral request an encrypted connection right after connecting
|
||||
* @note Not used normally. Bonding is triggered by access to protected attributes in ATT Server
|
||||
*/
|
||||
void sm_set_request_security(int enable);
|
||||
|
||||
/**
|
||||
* @brief Trigger Security Request
|
||||
* @note Not used normally. Bonding is triggered by access to protected attributes in ATT Server
|
||||
*/
|
||||
void sm_send_security_request(uint16_t handle);
|
||||
|
||||
/**
|
||||
* @brief Decline bonding triggered by event before
|
||||
* @param addr_type and address
|
||||
*/
|
||||
void sm_bonding_decline(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Confirm Just Works bonding
|
||||
* @param addr_type and address
|
||||
*/
|
||||
void sm_just_works_confirm(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Reports passkey input by user
|
||||
* @param addr_type and address
|
||||
* @param passkey in [0..999999]
|
||||
*/
|
||||
void sm_passkey_input(uint8_t addr_type, bd_addr_t address, uint32_t passkey);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Get encryption key size.
|
||||
* @param addr_type and address
|
||||
* @return 0 if not encrypted, 7-16 otherwise
|
||||
*/
|
||||
int sm_encryption_key_size(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Get authentication property.
|
||||
* @param addr_type and address
|
||||
* @return 1 if bonded with OOB/Passkey (AND MITM protection)
|
||||
*/
|
||||
int sm_authenticated(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Queries authorization state.
|
||||
* @param addr_type and address
|
||||
* @return authorization_state for the current session
|
||||
*/
|
||||
authorization_state_t sm_authorization_state(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Used by att_server.c to request user authorization.
|
||||
* @param addr_type and address
|
||||
*/
|
||||
void sm_request_authorization(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Report user authorization decline.
|
||||
* @param addr_type and address
|
||||
*/
|
||||
void sm_authorization_decline(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Report user authorization grant.
|
||||
* @param addr_type and address
|
||||
*/
|
||||
void sm_authorization_grant(uint8_t addr_type, bd_addr_t address);
|
||||
|
||||
/**
|
||||
* @brief Support for signed writes, used by att_server.
|
||||
* @note Message and result are in little endian to allows passing in ATT PDU without flipping them first.
|
||||
*/
|
||||
int sm_cmac_ready(void);
|
||||
void sm_cmac_start(sm_key_t k, uint16_t message_len, uint8_t * message, uint32_t sign_counter, void (*done_handler)(uint8_t hash[8]));
|
||||
|
||||
/**
|
||||
* @brief Identify device in LE Device DB.
|
||||
* @param handle
|
||||
* @return index from le_device_db or -1 if not found/identified
|
||||
*/
|
||||
int sm_le_device_index(uint16_t handle );
|
105
docs/manual/markdown/docs/appendix/events_errors.md
Normal file
105
docs/manual/markdown/docs/appendix/events_errors.md
Normal file
@ -0,0 +1,105 @@
|
||||
|
||||
## L2CAP Events
|
||||
<a name="appendix:events_and_errors"></a>
|
||||
|
||||
L2CAP events and data packets are delivered to the packet handler
|
||||
specified by *l2cap_register_service* resp.
|
||||
*l2cap_create_channel_internal*. Data packets have the
|
||||
L2CAP_DATA_PACKET packet type. L2CAP provides the following events:
|
||||
|
||||
- L2CAP_EVENT_CHANNEL_OPENED - sent if channel establishment is
|
||||
done. Status not equal zero indicates an error. Possible errors: out
|
||||
of memory; connection terminated by local host, when the connection
|
||||
to remote device fails.
|
||||
|
||||
- L2CAP_EVENT_CHANNEL_CLOSED - emitted when channel is closed. No
|
||||
status information is provided.
|
||||
|
||||
- L2CAP_EVENT_INCOMING_CONNECTION - received when the connection is
|
||||
requested by remote. Connection accept and decline are performed
|
||||
with *l2cap_accept_connection_internal* and
|
||||
*l2cap_decline_connecti-on_internal* respectively.
|
||||
|
||||
- L2CAP_EVENT_CREDITS - emitted when there is a chance to send a new
|
||||
L2CAP packet. BTstack does not buffer packets. Instead, it requires
|
||||
the application to retry sending if BTstack cannot deliver a packet
|
||||
to the Bluetooth module. In this case, the l2cap_send_internal
|
||||
will return an error.
|
||||
|
||||
- L2CAP_EVENT_SERVICE_REGISTERED - Status not equal zero indicates
|
||||
an error. Possible errors: service is already registered;
|
||||
MAX_NO_L2CAP_SERVICES (defined in config.h) already registered.
|
||||
|
||||
|
||||
|
||||
Event Code |Event / Event Parameters (size in bits)
|
||||
-----------|----------------------------------------
|
||||
0x70 | L2CAP_EVENT_CHANNEL_OPENED<br/> *event(8), len(8), status(8), address(48), handle(16), psm(16), local_cid(16), remote_cid(16), local_mtu(16), remote_mtu(16)*
|
||||
0x71 | L2CAP_EVENT_CHANNEL_CLOSED<br/> *event (8), len(8), channel(16)*
|
||||
0x72 | L2CAP_EVENT_INCOMING_CONNECTION<br/> *event(8), len(8), address(48), handle(16), psm (16), local_cid(16), remote_cid (16)*
|
||||
0x74 | L2CAP_EVENT_CREDITS<br/> *event(8), len(8), local_cid(16), credits(8)*
|
||||
0x75 | L2CAP_EVENT_SERVICE_REGISTERED</br> *event(8), len(8), status(8), psm(16)*
|
||||
|
||||
|
||||
## RFCOMM Events
|
||||
|
||||
All RFCOMM events and data packets are currently delivered to the packet
|
||||
handler specified by *rfcomm_register_packet_handler*. Data packets
|
||||
have the _DATA_PACKET packet type. Here is the list of events provided
|
||||
by RFCOMM:
|
||||
|
||||
- RFCOMM_EVENT_INCOMING_CONNECTION - received when the connection
|
||||
is requested by remote. Connection accept and decline are performed
|
||||
with *rfcomm_accept_connection_internal* and
|
||||
*rfcomm_decline_con-nection_internal* respectively.
|
||||
|
||||
- RFCOMM_EVENT_CHANNEL_CLOSED - emitted when channel is closed. No
|
||||
status information is provided.
|
||||
|
||||
- RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE - sent if channel
|
||||
establishment is done. Status not equal zero indicates an error.
|
||||
Possible errors: an L2CAP error, out of memory.
|
||||
|
||||
- RFCOMM_EVENT_CREDITS - The application can resume sending when
|
||||
this even is received. See Section [section:flowcontrol] for more on
|
||||
RFCOMM credit-based flow-control.
|
||||
|
||||
- RFCOMM_EVENT_SERVICE_REGISTERED - Status not equal zero indicates
|
||||
an error. Possible errors: service is already registered;
|
||||
MAX_NO_RFCOMM_SERVICES (defined in config.h) already registered.
|
||||
|
||||
|
||||
Event Code |Event / Event Parameters (size in bits)
|
||||
-----------|----------------------------------------
|
||||
0x80 | RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE<br/> *event(8), len(8), status(8), address(48), handle(16), server_channel(8), rfcomm_cid(16), max_frame_size(16)*
|
||||
0x81 | RFCOMM_EVENT_CHANNEL_CLOSED<br/> *event(8), len(8), rfcomm_cid(16)*
|
||||
0x82 | RFCOMM_EVENT_INCOMING_CONNECTION<br/> *event(8), len(8), address(48), channel (8), rfcomm_cid(16)*
|
||||
0x84 | RFCOMM_EVENT_CREDITS<br/> *event(8), len(8), rfcomm_cid(16), credits(8)*
|
||||
0x85 | RFCOMM_EVENT_SERVICE_REGISTERED<br/> *event(8), len(8), status(8), rfcomm server channel_id(8)*
|
||||
|
||||
|
||||
## Errors
|
||||
|
||||
Error | Error Code
|
||||
------------------------------------------------------------------------|-------------------
|
||||
BTSTACK_MEMORY_ALLOC_FAILED | 0x56
|
||||
BTSTACK_ACL_BUFFERS_FULL | 0x57
|
||||
L2CAP_COMMAND_REJECT_REASON_COMMAND_NOT_UNDERSTOOD | 0x60
|
||||
L2CAP_COMMAND_REJECT_REASON_SIGNALING_MTU_EXCEEDED | 0x61
|
||||
L2CAP_COMMAND_REJECT_REASON_INVALID_CID_IN_REQUEST | 0x62
|
||||
L2CAP_CONNECTION_RESPONSE_RESULT_SUCCESSFUL | 0x63
|
||||
L2CAP_CONNECTION_RESPONSE_RESULT_PENDING | 0x64
|
||||
L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_PSM | 0x65
|
||||
L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_SECURITY | 0x66
|
||||
L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_RESOURCES | 0x65
|
||||
L2CAP_CONFIG_RESPONSE_RESULT_SUCCESSFUL | 0x66
|
||||
L2CAP_CONFIG_RESPONSE_RESULT_UNACCEPTABLE_PARAMS | 0x67
|
||||
L2CAP_CONFIG_RESPONSE_RESULT_REJECTED | 0x68
|
||||
L2CAP_CONFIG_RESPONSE_RESULT_UNKNOWN_OPTIONS | 0x69
|
||||
L2CAP_SERVICE_ALREADY_REGISTERED | 0x6a
|
||||
RFCOMM_MULTIPLEXER_STOPPED | 0x70
|
||||
RFCOMM_CHANNEL_ALREADY_REGISTERED | 0x71
|
||||
RFCOMM_NO_OUTGOING_CREDITS | 0x72
|
||||
SDP_HANDLE_ALREADY_REGISTERED | 0x80
|
||||
|
||||
|
83
docs/manual/markdown/docs/architecture.md
Normal file
83
docs/manual/markdown/docs/architecture.md
Normal file
@ -0,0 +1,83 @@
|
||||
As well as any other communication stack, BTstack is a collection of
|
||||
state machines that interact with each other. There is one or more state
|
||||
machines for each protocol and service that it implements. The rest of
|
||||
the architecture follows these fundamental design guidelines:
|
||||
|
||||
- *Single threaded design* - BTstack does not use or require
|
||||
multi-threading to handle data sources and timers. Instead, it uses
|
||||
a single run loop.
|
||||
|
||||
- *No blocking anywhere* - If Bluetooth processing is required, its
|
||||
result will be delivered as an event via registered packet handlers.
|
||||
|
||||
- *No artificially limited buffers/pools* - Incoming and outgoing data
|
||||
packets are not queued.
|
||||
|
||||
- *Statically bounded memory (optionally)* - The number of maximum
|
||||
connections/channels/services can be configured.
|
||||
|
||||
Figure [below](#fig:BTstackArchitecture) shows the general architecture of a
|
||||
BTstack-based single-threaded application that includes the BTstack run loop.
|
||||
The Main Application contains the application logic, e.g., reading a sensor value and
|
||||
providing it via the Communication Logic as a SPP Server. The
|
||||
Communication Logic is often modeled as a finite state machine with
|
||||
events and data coming from either the Main Application or from BTstack
|
||||
via registered packet handlers (PH). BTstack’s Run Loop is responsible
|
||||
for providing timers and processing incoming data.
|
||||
|
||||
<a name="fig:BTstackArchitecture"></a>
|
||||
|
||||
Single threaded design
|
||||
----------------------
|
||||
|
||||
BTstack does not use or require multi-threading. It uses a single run
|
||||
loop to handle data sources and timers. Data sources represent
|
||||
communication interfaces like an UART or an USB driver. Timers are used
|
||||
by BTstack to implement various Bluetooth-related timeouts. For example,
|
||||
to disconnect a Bluetooth baseband channel without an active L2CAP
|
||||
channel after 20 seconds. They can also be used to handle periodic
|
||||
events. During a run loop cycle, the callback functions of all
|
||||
registered data sources are called. Then, the callback functions of
|
||||
timers that are ready are executed.
|
||||
|
||||
For adapting BTstack to multi-threaded environments check [here](integration/#sec:multithreading).
|
||||
|
||||
No blocking anywhere
|
||||
--------------------
|
||||
|
||||
Bluetooth logic is event-driven. Therefore, all BTstack functions are
|
||||
non-blocking, i.e., all functions that cannot return immediately
|
||||
implement an asynchronous pattern. If the arguments of a function are
|
||||
valid, the necessary commands are sent to the Bluetooth chipset and the
|
||||
function returns with a success value. The actual result is delivered
|
||||
later as an asynchronous event via registered packet handlers.
|
||||
|
||||
If a Bluetooth event triggers longer processing by the application, the
|
||||
processing should be split into smaller chunks. The packet handler could
|
||||
then schedule a timer that manages the sequential execution of the
|
||||
chunks.
|
||||
|
||||
No artificially limited buffers/pools
|
||||
-------------------------------------
|
||||
|
||||
Incoming and outgoing data packets are not queued. BTstack delivers an
|
||||
incoming data packet to the application before it receives the next one
|
||||
from the Bluetooth chipset. Therefore, it relies on the link layer of
|
||||
the Bluetooth chipset to slow down the remote sender when needed.
|
||||
|
||||
Similarly, the application has to adapt its packet generation to the
|
||||
remote receiver for outgoing data. L2CAP relies on ACL flow control
|
||||
between sender and receiver. If there are no free ACL buffers in the
|
||||
Bluetooth module, the application cannot send. For RFCOMM, the mandatory
|
||||
credit-based flow-control limits the data sending rate additionally. The
|
||||
application can only send an RFCOMM packet if it has RFCOMM credits.
|
||||
|
||||
Statically bounded memory
|
||||
-------------------------
|
||||
|
||||
BTstack has to keep track of services and active connections on the
|
||||
various protocol layers. The number of maximum
|
||||
connections/channels/services can be configured. In addition, the
|
||||
non-persistent database for remote device names and link keys needs
|
||||
memory and can be be configured, too. These numbers determine the amount
|
||||
of static memory allocation.
|
1312
docs/manual/markdown/docs/examples/generated.md
Normal file
1312
docs/manual/markdown/docs/examples/generated.md
Normal file
File diff suppressed because it is too large
Load Diff
56
docs/manual/markdown/docs/examples/intro.md
Normal file
56
docs/manual/markdown/docs/examples/intro.md
Normal file
@ -0,0 +1,56 @@
|
||||
In this section, we will describe a number of examples from the
|
||||
*example/embedded* folder. To allow code-reuse with different platforms
|
||||
as well as with new ports, the low-level initialization of BTstack and
|
||||
the hardware configuration has been extracted to the various
|
||||
*platforms/PLATFORM/main.c* files. The examples only contain the
|
||||
platform-independent Bluetooth logic. But let’s have a look at the
|
||||
common init code.
|
||||
|
||||
Listing [below](#lst:btstackInit) shows a minimal platform setup for an
|
||||
embedded system with a Bluetooth chipset connected via UART.
|
||||
|
||||
<a name="lst:btstackInit"></a>
|
||||
|
||||
int main(){
|
||||
// ... hardware init: watchdoch, IOs, timers, etc...
|
||||
|
||||
// setup BTstack memory pools
|
||||
btstack_memory_init();
|
||||
|
||||
// select embedded run loop
|
||||
run_loop_init(RUN_LOOP_EMBEDDED);
|
||||
|
||||
// use logger: format HCI_DUMP_PACKETLOGGER, HCI_DUMP_BLUEZ or HCI_DUMP_STDOUT
|
||||
hci_dump_open(NULL, HCI_DUMP_STDOUT);
|
||||
|
||||
// init HCI
|
||||
hci_transport_t * transport = hci_transport_h4_dma_instance();
|
||||
remote_device_db_t * remote_db = (remote_device_db_t *) &remote_device_db_memory;
|
||||
hci_init(transport, NULL, NULL, remote_db);
|
||||
|
||||
// setup example
|
||||
btstack_main(argc, argv);
|
||||
|
||||
// go
|
||||
run_loop_execute();
|
||||
}
|
||||
|
||||
First, BTstack’s memory pools are setup up. Then, the standard run loop
|
||||
implementation for embedded systems is selected.
|
||||
|
||||
The call to *hci_dump_open* configures BTstack to output all Bluetooth
|
||||
packets and it’s own debug and error message via printf. The Python
|
||||
script *tools/create_packet_log.py* can be used to convert the console
|
||||
output into a Bluetooth PacketLogger format that can be opened by the OS
|
||||
X PacketLogger tool as well as by Wireshark for further inspection. When
|
||||
asking for help, please always include a log created with HCI dump.
|
||||
|
||||
The *hci_init* function sets up HCI to use the HCI H4 Transport
|
||||
implementation. It doesn’t provide a special transport configuration nor
|
||||
a special implementation for a particular Bluetooth chipset. It makes
|
||||
use of the *remote_device_db_memory* implementation that allows for
|
||||
re-connects without a new pairing but doesn’t persist the bonding
|
||||
information.
|
||||
|
||||
Finally, it calls *btstack_main()* of the actual example before
|
||||
executing the run loop.
|
276
docs/manual/markdown/docs/how_to.md
Normal file
276
docs/manual/markdown/docs/how_to.md
Normal file
@ -0,0 +1,276 @@
|
||||
BTstack implements a set of basic Bluetooth protocols. To make use of
|
||||
these to connect to other devices or to provide own services, BTstack
|
||||
has to be properly configured during application startup.
|
||||
|
||||
In the following, we provide an overview of the memory management, the
|
||||
run loop, and services that are necessary to setup BTstack. From the
|
||||
point when the run loop is executed, the application runs as a finite
|
||||
state machine, which processes events received from BTstack. BTstack
|
||||
groups events logically and provides them over packet handlers, of which
|
||||
an overview is provided here. Finally, we describe the RFCOMM
|
||||
credit-based flow-control, which may be necessary for
|
||||
resource-constraint devices.
|
||||
|
||||
Memory configuration <a name ="sec:memory_configuration"></a>
|
||||
------------------------------------------------------------
|
||||
|
||||
The structs for services, active connections and remote devices can be
|
||||
allocated in two different manners:
|
||||
|
||||
- statically from an individual memory pool, whose maximal number of
|
||||
elements is defined in the config file. To initialize the static
|
||||
pools, you need to call *btstack_memory_init* function. An example
|
||||
of memory configuration for a single SPP service with a minimal
|
||||
L2CAP MTU is shown in Listing [below](#lst:memoryConfigurationSPP).
|
||||
|
||||
- dynamically using the *malloc/free* functions, if HAVE_MALLOC is
|
||||
defined in config file.
|
||||
|
||||
<a name "lst:memoryConfigurationSPP"></a>
|
||||
<!-- -->
|
||||
|
||||
#define HCI_ACL_PAYLOAD_SIZE 52
|
||||
#define MAX_SPP_CONNECTIONS 1
|
||||
#define MAX_NO_HCI_CONNECTIONS MAX_SPP_CONNECTIONS
|
||||
#define MAX_NO_L2CAP_SERVICES 2
|
||||
#define MAX_NO_L2CAP_CHANNELS (1+MAX_SPP_CONNECTIONS)
|
||||
#define MAX_NO_RFCOMM_MULTIPLEXERS MAX_SPP_CONNECTIONS
|
||||
#define MAX_NO_RFCOMM_SERVICES 1
|
||||
#define MAX_NO_RFCOMM_CHANNELS MAX_SPP_CONNECTIONS
|
||||
#define MAX_NO_DB_MEM_DEVICE_NAMES 0
|
||||
#define MAX_NO_DB_MEM_LINK_KEYS 3
|
||||
#define MAX_NO_DB_MEM_SERVICES 1
|
||||
|
||||
If both HAVE_MALLOC and maximal size of a pool are defined in the
|
||||
config file, the statical allocation will take precedence. In case that
|
||||
both are omitted, an error will be raised.
|
||||
|
||||
The memory is set up by calling *btstack_memory_init* function:
|
||||
|
||||
<!-- -->
|
||||
|
||||
btstack_memory_init();
|
||||
|
||||
Run loop <a name"sec:run_loop"></a>
|
||||
-----------------------------------
|
||||
|
||||
BTstack uses a run loop to handle incoming data and to schedule work.
|
||||
The run loop handles events from two different types of sources: data
|
||||
sources and timers. Data sources represent communication interfaces like
|
||||
an UART or an USB driver. Timers are used by BTstack to implement
|
||||
various Bluetooth-related timeouts. They can also be used to handle
|
||||
periodic events.
|
||||
|
||||
Data sources and timers are represented by the *data_source_t* and
|
||||
*timer_source_t* structs respectively. Each of these structs contain a
|
||||
linked list node and a pointer to a callback function. All active timers
|
||||
and data sources are kept in link lists. While the list of data sources
|
||||
is unsorted, the timers are sorted by expiration timeout for efficient
|
||||
processing.
|
||||
|
||||
The complete run loop cycle looks like this: first, the callback
|
||||
function of all registered data sources are called in a round robin way.
|
||||
Then, the callback functions of timers that are ready are executed.
|
||||
Finally, it will be checked if another run loop iteration has been
|
||||
requested by an interrupt handler. If not, the run loop will put the MCU
|
||||
into sleep mode.
|
||||
|
||||
Incoming data over the UART, USB, or timer ticks will generate an
|
||||
interrupt and wake up the microcontroller. In order to avoid the
|
||||
situation where a data source becomes ready just before the run loop
|
||||
enters sleep mode, an interrupt-driven data source has to call the
|
||||
*embedded_trigger* function. The call to *embedded_trigger* sets an
|
||||
internal flag that is checked in the critical section just before
|
||||
entering sleep mode.
|
||||
|
||||
Timers are single shot: a timer will be removed from the timer list
|
||||
before its event handler callback is executed. If you need a periodic
|
||||
timer, you can re-register the same timer source in the callback
|
||||
function, as shown in Listing [PeriodicTimerHandler]. Note that BTstack
|
||||
expects to get called periodically to keep its time, see Section
|
||||
[section:timeAbstraction] for more on the tick hardware abstraction.
|
||||
|
||||
The run loop is set up by calling *run_loop_init* function for
|
||||
embedded systems:
|
||||
|
||||
<!-- -->
|
||||
|
||||
run_loop_init(RUN_LOOP_EMBEDDED);
|
||||
|
||||
The Run loop API is provided in Appendix [appendix:api~r~un~l~oop]. To
|
||||
enable the use of timers, make sure that you defined HAVE_TICK in the
|
||||
config file.
|
||||
|
||||
In your code, you’ll have to configure the run loop before you start it
|
||||
as shown in Listing [listing:btstackInit]. The application can register
|
||||
data sources as well as timers, e.g., periodical sampling of sensors, or
|
||||
communication over the UART.
|
||||
|
||||
BTstack initialization <a nam="sec:btstack_initialization"></a>
|
||||
----------------------
|
||||
|
||||
To initialize BTstack you need to initialize the memory and the run loop
|
||||
as explained in Sections [section:memory~c~onfiguration] and
|
||||
[section:run~l~oop] respectively, then setup HCI and all needed higher
|
||||
level protocols.
|
||||
|
||||
The HCI initialization has to adapt BTstack to the used platform and
|
||||
requires four arguments. These are:
|
||||
|
||||
- *Bluetooth hardware control*: The Bluetooth hardware control API can
|
||||
provide the HCI layer with a custom initialization script, a
|
||||
vendor-specific baud rate change command, and system power
|
||||
notifications. It is also used to control the power mode of the
|
||||
Bluetooth module, i.e., turning it on/off and putting to sleep. In
|
||||
addition, it provides an error handler *hw_error* that is called
|
||||
when a Hardware Error is reported by the Bluetooth module. The
|
||||
callback allows for persistent logging or signaling of this failure.
|
||||
|
||||
Overall, the struct *bt_control_t* encapsulates common
|
||||
functionality that is not covered by the Bluetooth specification. As
|
||||
an example, the *bt_con-trol_cc256x_in-stance* function returns a
|
||||
pointer to a control struct suitable for the CC256x chipset.
|
||||
|
||||
<!-- -->
|
||||
|
||||
bt_control_t * control = bt_control_cc256x_instance();
|
||||
|
||||
- *HCI Transport implementation*: On embedded systems, a Bluetooth
|
||||
module can be connected via USB or an UART port. BTstack implements
|
||||
two UART based protocols: HCI UART Transport Layer (H4) and H4 with
|
||||
eHCILL support, a lightweight low-power variant by Texas
|
||||
Instruments. These are accessed by linking the appropriate file (
|
||||
resp. and then getting a pointer to HCI Transport implementation.
|
||||
For more information on adapting HCI Transport to different
|
||||
environments, see Section [section:hci~t~ransport].
|
||||
|
||||
<!-- -->
|
||||
|
||||
hci_transport_t * transport = hci_transport_h4_dma_instance();
|
||||
|
||||
- *HCI Transport configuration*: As the configuration of the UART used
|
||||
in the H4 transport interface are not standardized, it has to be
|
||||
provided by the main application to BTstack. In addition to the
|
||||
initial UART baud rate, the main baud rate can be specified. The HCI
|
||||
layer of BTstack will change the init baud rate to the main one
|
||||
after the basic setup of the Bluetooth module. A baud rate change
|
||||
has to be done in a coordinated way at both HCI and hardware level.
|
||||
First, the HCI command to change the baud rate is sent, then it is
|
||||
necessary to wait for the confirmation event from the Bluetooth
|
||||
module. Only now, can the UART baud rate changed. As an example, the
|
||||
CC256x has to be initialized at 115200 and can then be used at
|
||||
higher speeds.
|
||||
|
||||
<!-- -->
|
||||
|
||||
hci_uart_config_t* config = hci_uart_config_cc256x_instance();
|
||||
|
||||
- *Persistent storage* - specifies where to persist data like link
|
||||
keys or remote device names. This commonly requires platform
|
||||
specific code to access the MCU’s EEPROM of Flash storage. For the
|
||||
first steps, BTstack provides a (non) persistent store in memory.
|
||||
For more see [here](#sec:persistent_storage).
|
||||
|
||||
<!-- -->
|
||||
|
||||
remote_device_db_t * remote_db = &remote_device_db_memory;
|
||||
|
||||
After these are ready, HCI is initialized like this:
|
||||
|
||||
<!-- -->
|
||||
hci_init(transport, config, control, remote_db);
|
||||
|
||||
The higher layers only rely on BTstack and are initialized by calling
|
||||
the respective *\*_init* function. These init functions register
|
||||
themselves with the underlying layer. In addition, the application can
|
||||
register packet handlers to get events and data as explained in the
|
||||
following section.
|
||||
|
||||
Services
|
||||
--------
|
||||
|
||||
One important construct of BTstack is *service*. A service represents a
|
||||
server side component that handles incoming connections. So far, BTstack
|
||||
provides L2CAP and RFCOMM services. An L2CAP service handles incoming
|
||||
connections for an L2CAP channel and is registered with its protocol
|
||||
service multiplexer ID (PSM). Similarly, an RFCOMM service handles
|
||||
incoming RFCOMM connections and is registered with the RFCOMM channel
|
||||
ID. Outgoing connections require no special registration, they are
|
||||
created by the application when needed.
|
||||
|
||||
|
||||
|
||||
Where to get data - packet handlers <a name="sec:packetHandlers"></a>
|
||||
-----------------------------------
|
||||
|
||||
After the hardware and BTstack are set up, the run loop is entered. From
|
||||
now on everything is event driven. The application calls BTstack
|
||||
functions, which in turn may send commands to the Bluetooth module. The
|
||||
resulting events are delivered back to the application. Instead of
|
||||
writing a single callback handler for each possible event (as it is done
|
||||
in some other Bluetooth stacks), BTstack groups events logically and
|
||||
provides them over a single generic interface. Appendix
|
||||
[Events and Errors](generated/appendix/#events_and_errors)
|
||||
summarizes the parameters and event
|
||||
codes of L2CAP and RFCOMM events, as well as possible errors and the
|
||||
corresponding error codes.
|
||||
|
||||
Here is summarized list of packet handlers that an application might
|
||||
use:
|
||||
|
||||
- HCI packet handler - handles HCI and general BTstack events if L2CAP
|
||||
is not used (rare case).
|
||||
|
||||
- L2CAP packet handler - handles HCI and general BTstack events.
|
||||
|
||||
- L2CAP service packet handler - handles incoming L2CAP connections,
|
||||
i.e., channels initiated by the remote.
|
||||
|
||||
- L2CAP channel packet handler - handles outgoing L2CAP connections,
|
||||
i.e., channels initiated internally.
|
||||
|
||||
- RFCOMM packet handler - handles RFCOMM incoming/outgoing events and
|
||||
data.
|
||||
|
||||
------------------------------ --------------------------------------
|
||||
Packet Handler Registering Function
|
||||
HCI packet handler *hci_register_packet_handler*
|
||||
L2CAP packet handler *l2cap_register_packet_handler*
|
||||
L2CAP service packet handler *l2cap_register_service_internal*
|
||||
L2CAP channel packet handler *l2cap_create_channel_internal*
|
||||
RFCOMM packet handler *rfcomm_register_packet_handler*
|
||||
[table:registeringFunction]
|
||||
------------------------------ --------------------------------------
|
||||
|
||||
These handlers are registered with the functions listed in Table
|
||||
[table:registeringFunction].
|
||||
|
||||
HCI and general BTstack events are delivered to the packet handler
|
||||
specified by *l2cap_register_packet_handler* function, or
|
||||
*hci_register_packet_handler*, if L2CAP is not used. In L2CAP,
|
||||
BTstack discriminates incoming and outgoing connections, i.e., event and
|
||||
data packets are delivered to different packet handlers. Outgoing
|
||||
connections are used access remote services, incoming connections are
|
||||
used to provide services. For incoming connections, the packet handler
|
||||
specified by *l2cap_register_service* is used. For outgoing
|
||||
connections, the handler provided by *l2cap_create_channel_internal*
|
||||
is used. Currently, RFCOMM provides only a single packet handler
|
||||
specified by *rfcomm_register_packet_handler* for all RFCOMM
|
||||
connections, but this will be fixed in the next API overhaul.
|
||||
|
||||
The application can register a single shared packet handler for all
|
||||
protocols and services, or use separate packet handlers for each
|
||||
protocol layer and service. A shared packet handler is often used for
|
||||
stack initialization and connection management.
|
||||
|
||||
Separate packet handlers can be used for each L2CAP service and outgoing
|
||||
connection. For example, to connect with a Bluetooth HID keyboard, your
|
||||
application could use three packet handlers: one to handle HCI events
|
||||
during discovery of a keyboard registered by
|
||||
*l2cap_register_packet_handler*; one that will be registered to an
|
||||
outgoing L2CAP channel to connect to keyboard and to receive keyboard
|
||||
data registered by *l2cap_create_channel_internal*; after that
|
||||
keyboard can reconnect by itself. For this, you need to register L2CAP
|
||||
services for the HID Control and HID Interrupt PSMs using
|
||||
*l2cap_register_service_internal*. In this call, you’ll also specify
|
||||
a packet handler to accept and receive keyboard data.
|
3
docs/manual/markdown/docs/index.md
Normal file
3
docs/manual/markdown/docs/index.md
Normal file
@ -0,0 +1,3 @@
|
||||
Thanks for checking out BTstack! In this manual, we first provide a 'quick starter guide' for common platforms before highlighting BTstack's main design choices and go over all implemented protocols and profiles. A series of examples show how BTstack can be used to implement common
|
||||
use cases. Finally, we outline the basic steps when integrating BTstack into existing single-threaded or even multi-threaded environments. The changes in the documentation are listed in the [Revision History](revision_history).
|
||||
|
68
docs/manual/markdown/docs/integration.md
Normal file
68
docs/manual/markdown/docs/integration.md
Normal file
@ -0,0 +1,68 @@
|
||||
While the run loop provided by BTstack is sufficient for new designs,
|
||||
BTstack is often used with or added to existing projects. In this case,
|
||||
the run loop, data sources, and timers may need to be adapted. The
|
||||
following two sections provides a guideline for single and
|
||||
multi-threaded environments.
|
||||
|
||||
To simplify the discussion, we’ll consider an application split into
|
||||
“Main ”, “Communication Logic”, and “BTstack”. The Communication Logic
|
||||
contains the packet handler (PH) that handles all asynchronous events
|
||||
and data packets from BTstack. The Main Application makes use of the
|
||||
Communication Logic for its Bluetooth communication.
|
||||
|
||||
Adapting BTstack for Single-Threaded Environments
|
||||
-------------------------------------------------
|
||||
<a name="sec:singlethreading"></a>
|
||||
|
||||
In a single-threaded environment, all application components run on the
|
||||
same (single) thread and use direct function calls as shown in Figure [below](#fig:BTstackSingle).
|
||||
|
||||
<a name="fig:BTstackSingle"></a>
|
||||

|
||||
|
||||
BTstack provides a basic run loop that supports the concept of data
|
||||
sources and timers, which can be registered centrally. This works well
|
||||
when working with a small MCU and without an operating system. To adapt
|
||||
to a basic operating system or a different scheduler, BTstack’s run loop
|
||||
can be implemented based on the functions and mechanism of the existing
|
||||
system.
|
||||
|
||||
Currently, we have two examples for this:
|
||||
|
||||
- *run_loop_cocoa.c* is an implementation for the CoreFoundation
|
||||
Framework used in OS X and iOS. All run loop functions are
|
||||
implemented in terms of CoreFoundation calls, data sources and
|
||||
timers are modeled as CFSockets and CFRunLoopTimer respectively.
|
||||
|
||||
- *run_loop_posix.c* is an implementation for POSIX compliant
|
||||
systems. The data sources are modeled as file descriptors and
|
||||
managed in a linked list. Then, the*select* function is used to wait
|
||||
for the next file descriptor to become ready or timer to expire.
|
||||
|
||||
Adapting BTstack for Multi-Threaded Environments
|
||||
------------------------------------------------
|
||||
<a name="sec:multithreading"></a>
|
||||
|
||||
|
||||
The basic execution model of BTstack is a general while loop. Aside from
|
||||
interrupt-driven UART and timers, everything happens in sequence. When
|
||||
using BTstack in a multi-threaded environment, this assumption has to
|
||||
stay valid - at least with respect to BTstack. For this, there are two
|
||||
common options:
|
||||
|
||||
|
||||
- The Communication Logic is implemented on a dedicated BTstack
|
||||
thread, and the Main Application communicates with the BTstack
|
||||
thread via application-specific messages over an Interprocess
|
||||
Communication (IPC). This option results in less code and quick
|
||||
adaption.
|
||||
|
||||

|
||||
|
||||
- BTstack must be extended to run standalone, i.e, as a Daemon, on a
|
||||
dedicated thread and the Main Application controls this daemon via
|
||||
BTstack extended HCI command over IPC - this is used for the
|
||||
non-embedded version of BTstack e.g., on the iPhone.
|
||||
This option requires more code but provides more flexibility.
|
||||
|
||||

|
BIN
docs/manual/markdown/docs/picts/bklogo.png
Normal file
BIN
docs/manual/markdown/docs/picts/bklogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
docs/manual/markdown/docs/picts/btstack-architecture.png
Normal file
BIN
docs/manual/markdown/docs/picts/btstack-architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
BIN
docs/manual/markdown/docs/picts/btstack-protocols.png
Normal file
BIN
docs/manual/markdown/docs/picts/btstack-protocols.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
BIN
docs/manual/markdown/docs/picts/multithreading-btdaemon.png
Normal file
BIN
docs/manual/markdown/docs/picts/multithreading-btdaemon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
docs/manual/markdown/docs/picts/multithreading-monolithic.png
Normal file
BIN
docs/manual/markdown/docs/picts/multithreading-monolithic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
docs/manual/markdown/docs/picts/singlethreading-btstack.png
Normal file
BIN
docs/manual/markdown/docs/picts/singlethreading-btstack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
166
docs/manual/markdown/docs/porting.md
Normal file
166
docs/manual/markdown/docs/porting.md
Normal file
@ -0,0 +1,166 @@
|
||||
In this section, we highlight the BTstack components that need to be
|
||||
adjusted for different hardware platforms.
|
||||
|
||||
Time Abstraction Layer
|
||||
----------------------
|
||||
<a name="sec:timeAbstraction"></a>
|
||||
|
||||
BTstack requires a way to learn about passing time.
|
||||
*run_loop_embedded.c* supports two different modes: system ticks or a
|
||||
system clock with millisecond resolution. BTstack’s timing requirements
|
||||
are quite low as only Bluetooth timeouts in the second range need to be
|
||||
handled.
|
||||
|
||||
### Tick Hardware Abstraction
|
||||
<a name="sec:tickAbstraction"></a>
|
||||
|
||||
If your platform doesn’t require a system clock or if you already have a
|
||||
system tick (as it is the default with CMSIS on ARM Cortex devices), you
|
||||
can use that to implement BTstack’s time abstraction in
|
||||
*include/btstack/hal_tick.h\>*.
|
||||
|
||||
For this, you need to define *HAVE_TICK* in *btstack-config.h*:
|
||||
|
||||
#define HAVE_TICK
|
||||
|
||||
Then, you need to implement the functions *hal_tick_init* and
|
||||
*hal_tick_set_handler*, which will be called during the
|
||||
initialization of the run loop.
|
||||
|
||||
void hal_tick_init(void);
|
||||
void hal_tick_set_handler(void (*tick_handler)(void));
|
||||
int hal_tick_get_tick_period_in_ms(void);
|
||||
|
||||
After BTstack calls *hal_tick_init()* and
|
||||
*hal_tick_set_handler(tick_handler)*, it expects that the
|
||||
*tick_handler* gets called every
|
||||
*hal_tick_get_tick_period_in_ms()* ms.
|
||||
|
||||
### Time MS Hardware Abstraction
|
||||
<a name="sec:timeMSAbstraction"></a>
|
||||
|
||||
If your platform already has a system clock or it is more convenient to
|
||||
provide such a clock, you can use the Time MS Hardware Abstraction in
|
||||
*include/btstack/hal_time_ms.h*.
|
||||
|
||||
For this, you need to define *HAVE_TIME_MS* in *btstack-config.h*:
|
||||
|
||||
#define HAVE_TIME_MS
|
||||
|
||||
Then, you need to implement the function *hal_time_ms()*, which will
|
||||
be called from BTstack’s run loop and when setting a timer for the
|
||||
future. It has to return the time in milliseconds.
|
||||
|
||||
uint32_t hal_time_ms(void);
|
||||
|
||||
|
||||
Bluetooth Hardware Control API
|
||||
------------------------------
|
||||
<a name="sec:bt_hw_control"></a>
|
||||
|
||||
The Bluetooth hardware control API can provide the HCI layer with a
|
||||
custom initialization script, a vendor-specific baud rate change
|
||||
command, and system power notifications. It is also used to control the
|
||||
power mode of the Bluetooth module, i.e., turning it on/off and putting
|
||||
to sleep. In addition, it provides an error handler *hw_error* that is
|
||||
called when a Hardware Error is reported by the Bluetooth module. The
|
||||
callback allows for persistent logging or signaling of this failure.
|
||||
|
||||
Overall, the struct *bt_control_t* encapsulates common functionality
|
||||
that is not covered by the Bluetooth specification. As an example, the
|
||||
*bt_control_cc256x_in-stance* function returns a pointer to a control
|
||||
struct suitable for the CC256x chipset.
|
||||
|
||||
|
||||
HCI Transport Implementation
|
||||
----------------------------
|
||||
<a name="sec:hci_transport"></a>
|
||||
|
||||
On embedded systems, a Bluetooth module can be connected via USB or an
|
||||
UART port. BTstack implements two UART based protocols for carrying HCI
|
||||
commands, events and data between a host and a Bluetooth module: HCI
|
||||
UART Transport Layer (H4) and H4 with eHCILL support, a lightweight
|
||||
low-power variant by Texas Instruments.
|
||||
|
||||
### HCI UART Transport Layer (H4)
|
||||
<a name="sec:hciUART"></a>
|
||||
|
||||
Most embedded UART interfaces operate on the byte level and generate a
|
||||
processor interrupt when a byte was received. In the interrupt handler,
|
||||
common UART drivers then place the received data in a ring buffer and
|
||||
set a flag for further processing or notify the higher-level code, i.e.,
|
||||
in our case the Bluetooth stack.
|
||||
|
||||
Bluetooth communication is packet-based and a single packet may contain
|
||||
up to 1021 bytes. Calling a data received handler of the Bluetooth stack
|
||||
for every byte creates an unnecessary overhead. To avoid that, a
|
||||
Bluetooth packet can be read as multiple blocks where the amount of
|
||||
bytes to read is known in advance. Even better would be the use of
|
||||
on-chip DMA modules for these block reads, if available.
|
||||
|
||||
The BTstack UART Hardware Abstraction Layer API reflects this design
|
||||
approach and the underlying UART driver has to implement the following
|
||||
API:
|
||||
|
||||
void hal_uart_dma_init(void);
|
||||
void hal_uart_dma_set_block_received(void (*block_handler)(void));
|
||||
void hal_uart_dma_set_block_sent(void (*block_handler)(void));
|
||||
int hal_uart_dma_set_baud(uint32_t baud);
|
||||
void hal_uart_dma_send_block(const uint8_t *buffer, uint16_t len);
|
||||
void hal_uart_dma_receive_block(uint8_t *buffer, uint16_t len);
|
||||
|
||||
|
||||
The main HCI H4 implementations for embedded system is
|
||||
*hci_h4_transport-_dma* function. This function calls the following
|
||||
sequence: *hal_uart_dma_init*, *hal_uart_dma_set_block_received*
|
||||
and *hal_uart_dma_set_block_sent* functions. this sequence, the HCI
|
||||
layer will start packet processing by calling
|
||||
*hal_uart-_dma_receive_block* function. The HAL implementation is
|
||||
responsible for reading the requested amount of bytes, stopping incoming
|
||||
data via the RTS line when the requested amount of data was received and
|
||||
has to call the handler. By this, the HAL implementation can stay
|
||||
generic, while requiring only three callbacks per HCI packet.
|
||||
|
||||
### H4 with eHCILL support
|
||||
|
||||
With the standard H4 protocol interface, it is not possible for either
|
||||
the host nor the baseband controller to enter a sleep mode. Besides the
|
||||
official H5 protocol, various chip vendors came up with proprietary
|
||||
solutions to this. The eHCILL support by Texas Instruments allows both
|
||||
the host and the baseband controller to independently enter sleep mode
|
||||
without loosing their synchronization with the HCI H4 Transport Layer.
|
||||
In addition to the IRQ-driven block-wise RX and TX, eHCILL requires a
|
||||
callback for CTS interrupts.
|
||||
|
||||
void hal_uart_dma_set_cts_irq_handler(void(*cts_irq_handler)(void));
|
||||
void hal_uart_dma_set_sleep(uint8_t sleep);
|
||||
|
||||
Persistent Storage API
|
||||
----------------------
|
||||
<a name="sec:persistent_storage"></a>
|
||||
|
||||
On embedded systems there is no generic way to persist data like link
|
||||
keys or remote device names, as every type of a device has its own
|
||||
capabilities, particularities and limitations. The persistent storage
|
||||
API provides an interface to implement concrete drivers for a particular
|
||||
system. As an example and for testing purposes, BTstack provides the
|
||||
memory-only implementation *remote_device_db_memory*. An
|
||||
implementation has to conform to the interface in Listing [below](#lst:persistentDB).
|
||||
|
||||
<a name="lst:persistentDB"></a>
|
||||
|
||||
typedef struct {
|
||||
// management
|
||||
void (*open)();
|
||||
void (*close)();
|
||||
|
||||
// link key
|
||||
int (*get_link_key)(bd_addr_t bd_addr, link_key_t link_key);
|
||||
void (*put_link_key)(bd_addr_t bd_addr, link_key_t key);
|
||||
void (*delete_link_key)(bd_addr_t bd_addr);
|
||||
|
||||
// remote name
|
||||
int (*get_name)(bd_addr_t bd_addr, device_name_t *device_name);
|
||||
void(*put_name)(bd_addr_t bd_addr, device_name_t *device_name);
|
||||
void(*delete_name)(bd_addr_t bd_addr);
|
||||
} remote_device_db_t;
|
400
docs/manual/markdown/docs/profiles.md
Normal file
400
docs/manual/markdown/docs/profiles.md
Normal file
@ -0,0 +1,400 @@
|
||||
In the following, we explain how the various Bluetooth profiles are used
|
||||
in BTstack.
|
||||
|
||||
GAP - Generic Access Profile: Classic
|
||||
-------------------------------------
|
||||
|
||||
The GAP profile defines how devices find each other and establish a
|
||||
secure connection for other profiles. As mentioned before, the GAP
|
||||
functionality is split between and . Please check both.
|
||||
|
||||
### Become discoverable
|
||||
|
||||
A remote unconnected Bluetooth device must be set as “discoverable” in
|
||||
order to be seen by a device performing the inquiry scan. To become
|
||||
discoverable, an application can call *hci_discoverable_control* with
|
||||
input parameter 1. If you want to provide a helpful name for your
|
||||
device, the application can set its local name by calling
|
||||
$gap_set_local_name$. To save energy, you may set the device as
|
||||
undiscoverable again, once a connection is established. See Listing
|
||||
[below](#lst:Discoverable) for an example.
|
||||
|
||||
<a name="lst:Discoverable"></a>
|
||||
|
||||
int main(void){
|
||||
...
|
||||
// make discoverable
|
||||
hci_discoverable_control(1);
|
||||
run_loop_execute();
|
||||
return 0;
|
||||
}
|
||||
void packet_handler (uint8_t packet_type, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
switch(state){
|
||||
case W4_CHANNEL_COMPLETE:
|
||||
// if connection is successful, make device undiscoverable
|
||||
hci_discoverable_control(0);
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
### Discover remote devices {#section:DiscoverRemoteDevices}
|
||||
|
||||
To scan for remote devices, the *hci_inquiry* command is used. Found
|
||||
remote devices are reported as a part of HCI_EVENT_INQUIRY_RESULT,
|
||||
HCI_EVENT-_INQUIRY_RESULT_WITH_RSSI, or
|
||||
HCI_EVENT_EXTENDED_INQUIRY_RESPONSE events. Each response contains
|
||||
at least the Bluetooth address, the class of device, the page scan
|
||||
repetition mode, and the clock offset of found device. The latter events
|
||||
add information about the received signal strength or provide the
|
||||
Extended Inquiry Result (EIR). A code snippet is shown in Listing
|
||||
[below](#lst:DiscoverDevices).
|
||||
|
||||
<a name="lst:DiscoverDevices"></a>
|
||||
|
||||
void print_inquiry_results(uint8_t *packet){
|
||||
int event = packet[0];
|
||||
int numResponses = packet[2];
|
||||
uint16_t classOfDevice, clockOffset;
|
||||
uint8_t rssi, pageScanRepetitionMode;
|
||||
for (i=0; i<numResponses; i++){
|
||||
bt_flip_addr(addr, &packet[3+i*6]);
|
||||
pageScanRepetitionMode = packet [3 + numResponses*6 + i];
|
||||
if (event == HCI_EVENT_INQUIRY_RESULT){
|
||||
classOfDevice = READ_BT_24(packet, 3 + numResponses*(6+1+1+1) + i*3);
|
||||
clockOffset = READ_BT_16(packet, 3 + numResponses*(6+1+1+1+3) + i*2) & 0x7fff;
|
||||
rssi = 0;
|
||||
} else {
|
||||
classOfDevice = READ_BT_24(packet, 3 + numResponses*(6+1+1) + i*3);
|
||||
clockOffset = READ_BT_16(packet, 3 + numResponses*(6+1+1+3) + i*2) & 0x7fff;
|
||||
rssi = packet [3 + numResponses*(6+1+1+3+2) + i*1];
|
||||
}
|
||||
printf("Device found: %s with COD: 0x%06x, pageScan %u, clock offset 0x%04x, rssi 0x%02x\n", bd_addr_to_str(addr), classOfDevice, pageScanRepetitionMode, clockOffset, rssi);
|
||||
}
|
||||
}
|
||||
|
||||
void packet_handler (uint8_t packet_type, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
switch (event) {
|
||||
case HCI_STATE_WORKING:
|
||||
hci_send_cmd(&hci_write_inquiry_mode, 0x01); // with RSSI
|
||||
break;
|
||||
case HCI_EVENT_COMMAND_COMPLETE:
|
||||
if (COMMAND_COMPLETE_EVENT(packet, hci_write_inquiry_mode) ) {
|
||||
start_scan();
|
||||
}
|
||||
case HCI_EVENT_COMMAND_STATUS:
|
||||
if (COMMAND_STATUS_EVENT(packet, hci_write_inquiry_mode) ) {
|
||||
printf("Ignoring error (0x%x) from hci_write_inquiry_mode.\n", packet[2]);
|
||||
hci_send_cmd(&hci_inquiry, HCI_INQUIRY_LAP, INQUIRY_INTERVAL, 0);
|
||||
}
|
||||
break;
|
||||
case HCI_EVENT_INQUIRY_RESULT:
|
||||
case HCI_EVENT_INQUIRY_RESULT_WITH_RSSI:
|
||||
print_inquiry_results(packet);
|
||||
break;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
By default, neither RSSI values nor EIR are reported. If the Bluetooth
|
||||
device implements Bluetooth Specification 2.1 or higher, the
|
||||
*hci_write_inquiry_mode* command enables reporting of this advanced
|
||||
features (0 for standard results, 1 for RSSI, 2 for RSSI and EIR).
|
||||
|
||||
A complete GAP inquiry example is provided [here](examples/#gapinquiry).
|
||||
|
||||
### Pairing of Devices
|
||||
|
||||
By default, Bluetooth communication is not authenticated, and any device
|
||||
can talk to any other device. A Bluetooth device (for example, cellular
|
||||
phone) may choose to require authentication to provide a particular
|
||||
service (for example, a Dial-Up service). The process of establishing
|
||||
authentication is called pairing. Bluetooth provides two mechanism for
|
||||
this.
|
||||
|
||||
On Bluetooth devices that conform to the Bluetooth v2.0 or older
|
||||
specification, a PIN code (up to 16 bytes ASCII) has to be entered on
|
||||
both sides. This isn’t optimal for embedded systems that do not have
|
||||
full I/O capabilities. To support pairing with older devices using a
|
||||
PIN, see Listing [below](#lst:PinCodeRequest).
|
||||
|
||||
<a name="lst:PinCodeRequest"></a>
|
||||
|
||||
void packet_handler (uint8_t packet_type, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
switch (event) {
|
||||
case HCI_EVENT_PIN_CODE_REQUEST:
|
||||
// inform about pin code request
|
||||
printf("Pin code request - using '0000'\n\r");
|
||||
bt_flip_addr(bd_addr, &packet[2]);
|
||||
|
||||
// baseband address, pin length, PIN: c-string
|
||||
hci_send_cmd(&hci_pin_code_request_reply, &bd_addr, 4, "0000");
|
||||
break;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
The Bluetooth v2.1 specification introduces Secure Simple Pairing (SSP),
|
||||
which is a better approach as it both improves security and is better
|
||||
adapted to embedded systems. With SSP, the devices first exchange their
|
||||
IO Capabilities and then settle on one of several ways to verify that
|
||||
the pairing is legitimate. If the Bluetooth device supports SSP, BTstack
|
||||
enables it by default and even automatically accepts SSP pairing
|
||||
requests. Depending on the product in which BTstack is used, this may
|
||||
not be desired and should be replaced with code to interact with the
|
||||
user.
|
||||
|
||||
Regardless of the authentication mechanism (PIN/SSP), on success, both
|
||||
devices will generate a link key. The link key can be stored either in
|
||||
the Bluetooth module itself or in a persistent storage, see [here](#examples/#persistent_storage).
|
||||
The next time the device connects and
|
||||
requests an authenticated connection, both devices can use the
|
||||
previously generated link key. Please note that the pairing must be
|
||||
repeated if the link key is lost by one device.
|
||||
|
||||
### Dedicated Bonding
|
||||
|
||||
Aside from the regular bonding, Bluetooth also provides the concept of
|
||||
“dedicated bonding”, where a connection is established for the sole
|
||||
purpose of bonding the device. After the bonding process is over, the
|
||||
connection will be automatically terminated. BTstack supports dedicated
|
||||
bonding via the *gap_dedicated_bonding* function.
|
||||
|
||||
SPP - Serial Port Profile
|
||||
-------------------------
|
||||
|
||||
The SPP profile defines how to set up virtual serial ports and connect
|
||||
two Bluetooth enabled devices.
|
||||
|
||||
### Accessing an SPP Server on a remote device
|
||||
|
||||
To access a remote SPP server, you first need to query the remote device
|
||||
for its SPP services. Section [subsection:querysdp] shows how to query
|
||||
for all RFCOMM channels. For SPP, you can do the same but use the SPP
|
||||
UUID 0x1101 for the query. After you have identified the correct RFCOMM
|
||||
channel, you can create an RFCOMM connection as shown in Section
|
||||
[subsubsection:rfcommlient]
|
||||
|
||||
### Providing an SPP Server
|
||||
|
||||
To provide an SPP Server, you need to provide an RFCOMM service with a
|
||||
specific RFCOMM channel number as explained in Section
|
||||
[section:rfcomm~s~ervice]. Then, you need to create an SDP record for it
|
||||
and publish it with the SDP server by calling
|
||||
*sdp_register_service_internal*. BTstack provides the
|
||||
*sdp_create_spp_service* function in that requires an empty buffer of
|
||||
approximately 200 bytes, the service channel number, and a service name.
|
||||
Have a look at the SPP Counter example in Section [example:sppcounter].
|
||||
|
||||
PAN - Personal Area Networking Profile {#section:pan_profile}
|
||||
--------------------------------------
|
||||
|
||||
The PAN profile uses BNEP to provide on-demand networking capabilities
|
||||
between Bluetooth devices. The PAN profile defines the following roles:
|
||||
|
||||
- PAN User (PANU)
|
||||
|
||||
- Network Access Point (NAP)
|
||||
|
||||
- Group Ad-hoc Network (GN)
|
||||
|
||||
PANU is a Bluetooth device that communicates as a client with GN, or
|
||||
NAP, or with another PANU Bluetooth device, through a point-to-point
|
||||
connection. Either the PANU or the other Bluetooth device may terminate
|
||||
the connection at anytime.
|
||||
|
||||
NAP is a Bluetooth device that provides the service of routing network
|
||||
packets between PANU by using BNEP and the IP routing mechanism. A NAP
|
||||
can also act as a bridge between Bluetooth networks and other network
|
||||
technologies by using the Ethernet packets.
|
||||
|
||||
The GN role enables two or more PANUs to interact with each other
|
||||
through a wireless network without using additional networking hardware.
|
||||
The devices are connected in a piconet where the GN acts as a master and
|
||||
communicates either point-to-point or a point-to-multipoint with a
|
||||
maximum of seven PANU slaves by using BNEP.
|
||||
|
||||
Currently, BTstack supports only PANU.
|
||||
|
||||
### Accessing a remote PANU service
|
||||
|
||||
To access a remote PANU service, you first need perform an SDP query to
|
||||
get the L2CAP PSM for the requested PANU UUID. With these two pieces of
|
||||
information, you can connect BNEP to the remote PANU service with the
|
||||
*bnep_connect* function. The PANU Demo example in Section
|
||||
[example:panudemo] shows how this is accomplished.
|
||||
|
||||
### Providing a PANU service
|
||||
|
||||
To provide a PANU service, you need to provide a BNEP service with the
|
||||
service UUID, e.g. the PANU UUID, and a a maximal ethernet frame size,
|
||||
as explained in Section [subsubsection:bnepserver]. Then, you need to
|
||||
create an SDP record for it and publish it with the SDP server by
|
||||
calling *sdp_register_service_internal*. BTstack provides the
|
||||
*pan_create_panu_service* function in *src/pan.c* that requires an
|
||||
empty buffer of approximately 200 bytes, a description, and a security
|
||||
description.
|
||||
|
||||
GAP LE - Generic Access Profile for Low Energy
|
||||
----------------------------------------------
|
||||
|
||||
As with GAP for Classic, the GAP LE profile defines how to discover and
|
||||
how to connect to a Bluetooth Low Energy device. There are several GAP
|
||||
roles that a Bluetooth device can take, but the most important ones are
|
||||
the Central and the Peripheral role. Peripheral devices are those that
|
||||
provide information or can be controlled. Central devices are those that
|
||||
consume information or control the peripherals. Before the connection
|
||||
can be established, devices are first going through an advertising
|
||||
process.
|
||||
|
||||
### Private addresses.
|
||||
|
||||
To better protect privacy, an LE device can choose to use a private i.e.
|
||||
random Bluetooth address. This address changes at a user-specified rate.
|
||||
To allow for later reconnection, the central and peripheral devices will
|
||||
exchange their Identity Resolving Keys (IRKs) during bonding. The IRK is
|
||||
used to verify if a new address belongs to a previously bonded device.
|
||||
|
||||
To toggle privacy mode using private addresses, call the
|
||||
*gap_random_address_set_mode* function. The update period can be set
|
||||
with *gap_random_address_set_update_period*.
|
||||
|
||||
After a connection is established, the Security Manager will try to
|
||||
resolve the peer Bluetooth address as explained in Section
|
||||
[section:smp].
|
||||
|
||||
### Advertising and Discovery
|
||||
|
||||
An LE device is discoverable and connectable, only if it periodically
|
||||
sends out Advertisements. An advertisement contains up to 31 bytes of
|
||||
data. To configure and enable advertisement broadcast, the following HCI
|
||||
commands can be used:
|
||||
|
||||
- *hci_le_set_advertising_data*
|
||||
|
||||
- *hci_le_set_advertising_parameters*
|
||||
|
||||
- *hci_le_set_advertise_enable*
|
||||
|
||||
As these are direct HCI commands, please refer to Section
|
||||
[subsubsection:sendinghci] for details and have a look at the SPP and LE
|
||||
Counter example in Section [example:sppandlecounter].
|
||||
|
||||
In addition to the Advertisement data, a device in the peripheral role
|
||||
can also provide Scan Response data, which has to be explicitly queried
|
||||
by the central device. It can be provided with the
|
||||
*hci_le_set_scan_response_data*.
|
||||
|
||||
The scan parameters can be set with
|
||||
*le_central_set_scan_parameters*. The scan can be started/stopped
|
||||
with *le_central_start_scan*/*le_central_stop_scan*.
|
||||
|
||||
Finally, if a suitable device is found, a connection can be initiated by
|
||||
calling *le_central_connect*. In contrast to Bluetooth classic, there
|
||||
is no timeout for an LE connection establishment. To cancel such an
|
||||
attempt, *le_central_connect_cancel* has be be called.
|
||||
|
||||
By default, a Bluetooth device stops sending Advertisements when it gets
|
||||
into the Connected state. However, it does not start broadcasting
|
||||
advertisements on disconnect again. To re-enable it, please send the
|
||||
*hci_le_set_advertise_enable* again .
|
||||
|
||||
GATT - Generic Attribute Profile
|
||||
--------------------------------
|
||||
|
||||
The GATT profile uses ATT Attributes to represent a hierarchical
|
||||
structure of GATT Services and GATT Characteristics. Each Service has
|
||||
one or more Characteristics. Each Characteristic has meta data attached
|
||||
like its type or its properties. This hierarchy of Characteristics and
|
||||
Services are queried and modified via ATT operations.
|
||||
|
||||
GATT defines both a server and a client role. A device can implement one
|
||||
or both GATT roles.
|
||||
|
||||
### GATT Client {#section:GATTClient}
|
||||
|
||||
The GATT Client is used to discover services, and their characteristics
|
||||
and descriptors on a peer device. It can also subscribe for
|
||||
notifications or indications that the characteristic on the GATT server
|
||||
has changed its value.
|
||||
|
||||
To perform GATT queries, provides a rich interface. Before calling
|
||||
queries, the GATT client must be initialized with *gatt_client_init*
|
||||
once.
|
||||
|
||||
To allow for modular profile implementations, GATT client can be used
|
||||
independently by multiple entities.
|
||||
|
||||
To use it by a GATT client, you register a packet handler with
|
||||
*gatt_client_register_packet_ handler*. The return value of that is
|
||||
a GATT client ID which has to be provided in all queries.
|
||||
|
||||
After an LE connection was created using the GAP LE API, you can query
|
||||
for the connection MTU with *gatt_client_get_mtu*.
|
||||
|
||||
GATT queries cannot be interleaved. Therefore, you can check if you can
|
||||
perform a GATT query on a particular connection using
|
||||
*gatt_client_is_ready*. As a result to a GATT query, zero to many
|
||||
*le_event*s are returned before a *GATT_QUERY_COMPLETE* event
|
||||
completes the query.
|
||||
|
||||
For more details on the available GATT queries, please consult Appendix
|
||||
[appendix:api~g~att~c~lient].
|
||||
|
||||
### GATT Server {#section:GATTServer}
|
||||
|
||||
The GATT server stores data and accepts GATT client requests, commands
|
||||
and confirmations. The GATT server sends responses to requests and when
|
||||
configured, sends indication and notifications asynchronously to the
|
||||
GATT client.
|
||||
|
||||
To save on both code space and memory, BTstack does not provide a GATT
|
||||
Server implementation. Instead, a textual description of the GATT
|
||||
profile is directly converted into a compact internal ATT Attribute
|
||||
database by a GATT profile compiler. The ATT protocol server -
|
||||
implemented by and - answers incoming ATT requests based on information
|
||||
provided in the compiled database and provides read- and write-callbacks
|
||||
for dynamic attributes.
|
||||
|
||||
GATT profiles are defined by a simple textual comma separated value
|
||||
(.csv) representation. While the description is easy to read and edit,
|
||||
it is compact and can be placed in ROM.
|
||||
|
||||
The current format is:
|
||||
|
||||
PRIMARY_SERVICE, {SERVICE_UUID}
|
||||
CHARACTERISTIC, {ATTRIBUTE_TYPE_UUID}, {PROPERTIES}, {VALUE}
|
||||
CHARACTERISTIC, {ATTRIBUTE_TYPE_UUID}, {PROPERTIES}, {VALUE}
|
||||
...
|
||||
PRIMARY_SERVICE, {SERVICE_UUID}
|
||||
CHARACTERISTIC, {ATTRIBUTE_TYPE_UUID}, {PROPERTIES}, {VALUE}
|
||||
...
|
||||
|
||||
Properties can be a list of READ $|$ WRITE $|$ WRITE_WITHOUT_RESPONSE
|
||||
$|$ NOTIFY $|$ INDICATE $|$ DYNAMIC.
|
||||
|
||||
Value can either be a string (“this is a string”), or, a sequence of hex
|
||||
bytes (e.g. 01 02 03).
|
||||
|
||||
UUIDs are either 16 bit (1800) or 128 bit
|
||||
(00001234-0000-1000-8000-00805F9B34FB).
|
||||
|
||||
Reads/writes to a Characteristic that is defined with the DYNAMIC flag,
|
||||
are forwarded to the application via callback. Otherwise, the
|
||||
Characteristics cannot be written and it will return the specified
|
||||
constant value.
|
||||
|
||||
Adding NOTIFY and/or INDICATE automatically creates an addition Client
|
||||
Configuration Characteristic.
|
||||
|
||||
To require encryption or authentication before a Characteristic can be
|
||||
accessed, you can add ENCRYPTION_KEY_SIZE_X - with $X \in [7..16]$ -
|
||||
or AUTHENTICATION_REQUIRED.
|
||||
|
||||
BTstack only provides an ATT Server, while the GATT Server logic is
|
||||
mainly provided by the GATT compiler. While GATT identifies
|
||||
Characteristics by UUIDs, ATT uses Handles (16 bit values). To allow to
|
||||
identify a Characteristic without hard-coding the attribute ID, the GATT
|
||||
compiler creates a list of defines in the generated \*.h file.
|
891
docs/manual/markdown/docs/protocols.md
Normal file
891
docs/manual/markdown/docs/protocols.md
Normal file
@ -0,0 +1,891 @@
|
||||
BTstack is a modular dual-mode Bluetooth stack, supporting both
|
||||
Bluetooth Basic Rate/Enhanced Date Rate (BR/EDR) as well as Bluetooth
|
||||
Low Energy (LE). The BR/EDR technology, also known as Classic Bluetooth,
|
||||
provides a robust wireless connection between devices designed for high
|
||||
data rates. In contrast, the LE technology has a lower throughput but
|
||||
also lower energy consumption, faster connection setup, and the ability
|
||||
to connect to more devices in parallel.
|
||||
|
||||
Whether Classic or LE, a Bluetooth device implements one or more
|
||||
Bluetooth profiles. A Bluetooth profile specifies how one or more
|
||||
Bluetooth protocols are used to achieve its goals. For example, every
|
||||
Bluetooth device must implement the Generic Access Profile (GAP), which
|
||||
defines how devices find each other and how they establish a connection.
|
||||
This profile mainly make use of the Host Controller Interface (HCI)
|
||||
protocol, the lowest protocol in the stack hierarchy which implements a
|
||||
command interface to the Bluetooth chipset.
|
||||
|
||||
In addition to GAP, a popular Classic Bluetooth example would be a
|
||||
peripheral devices that can be connected via the Serial Port Profile
|
||||
(SPP). SPP basically specifies that a compatible device should provide a
|
||||
Service Discovery Protocol (SDP) record containing an RFCOMM channel
|
||||
number, which will be used for the actual communication.
|
||||
|
||||
Similarly, for every LE device, the Generic Attribute Profile (GATT)
|
||||
profile must be implemented in addition to GAP. GATT is built on top of
|
||||
the Attribute Protocol (ATT), and defines how one device can interact
|
||||
with GATT Services on a remote device.
|
||||
|
||||
So far, the most popular use of BTstack is in peripheral devices that
|
||||
can be connected via SPP (Android 2.0 or higher) and GATT (Android 4.3
|
||||
or higher, and iOS 5 or higher). If higher data rates are required
|
||||
between a peripheral and iOS device, the iAP1 and iAP2 protocols of the
|
||||
Made for iPhone program can be used instead of GATT. Please contact us
|
||||
directly for information on BTstack and MFi.
|
||||
|
||||
In the following, we first explain how the various Bluetooth protocols
|
||||
are used in BTstack. In the next chapter, we go over the profiles.
|
||||
|
||||
<a name="fig:BTstackProtocolArchitecture"></a>
|
||||

|
||||
|
||||
|
||||
## HCI - Host Controller Interface
|
||||
|
||||
The HCI protocol provides a command interface to the Bluetooth chipset.
|
||||
In BTstack, the HCI implementation also keeps track of all active
|
||||
connections and handles the fragmentation and re-assembly of higher
|
||||
layer (L2CAP) packets.
|
||||
|
||||
Please note, that an application rarely has to send HCI commands on its
|
||||
own. Instead, BTstack provides convenience functions in GAP and higher
|
||||
level protocols use HCI automatically. E.g. to set the name, you can
|
||||
call *gap_set_local_name()* before powering up. The main use of HCI
|
||||
commands in application is during the startup phase to configure special
|
||||
features that are not available via the GAP API yet.
|
||||
|
||||
However, as many features of the GAP Classic can be achieved by sending
|
||||
a single HCI command, not all GAP convenience functions are listed in .
|
||||
If there’s no special GAP function, please consider sending the HCI
|
||||
command directly, as explained in the following.
|
||||
|
||||
### Defining custom HCI command templates
|
||||
|
||||
Each HCI command is assigned a 2-byte OpCode used to uniquely identify
|
||||
different types of commands. The OpCode parameter is divided into two
|
||||
fields, called the OpCode Group Field (OGF) and OpCode Command Field
|
||||
(OCF), see [Bluetooth Specification](https://www.bluetooth.org/Technical/Specifications/adopted.htm) -
|
||||
Core Version 4.0, Volume 2, Part E, Chapter 5.4.
|
||||
|
||||
Listing [below](#lst:hciOGFs) shows the OGFs provided by BTstack in file:
|
||||
|
||||
<a name "lst:hciOGFs"></a>
|
||||
<!-- -->
|
||||
|
||||
#define OGF_LINK_CONTROL 0x01
|
||||
#define OGF_LINK_POLICY 0x02
|
||||
#define OGF_CONTROLLER_BASEBAND 0x03
|
||||
#define OGF_INFORMATIONAL_PARAMETERS 0x04
|
||||
#define OGF_LE_CONTROLLER 0x08
|
||||
#define OGF_BTSTACK 0x3d
|
||||
#define OGF_VENDOR 0x3f
|
||||
|
||||
For all existing Bluetooth
|
||||
commands and their OCFs see [Bluetooth Specification](https://www.bluetooth.org/Technical/Specifications/adopted.htm) -
|
||||
Core Version 4.0, Volume 2, Part E, Chapter 7.
|
||||
|
||||
In a HCI command packet, the OpCode is followed by parameter total
|
||||
length, and the actual parameters. The OpCode of a command can be
|
||||
calculated using the OPCODE macro. BTstack provides the *hci_cmd_t*
|
||||
struct as a compact format to define HCI command packets, see
|
||||
Listing [below](#lst:HCIcmdTemplate):
|
||||
|
||||
<a name "lst:HCIcmdTemplate"></a>
|
||||
<!-- -->
|
||||
|
||||
// Calculate combined ogf/ocf value.
|
||||
#define OPCODE(ogf, ocf) (ocf | ogf << 10)
|
||||
|
||||
// Compact HCI Command packet description.
|
||||
typedef struct {
|
||||
uint16_t opcode;
|
||||
const char *format;
|
||||
} hci_cmd_t;
|
||||
|
||||
|
||||
Listing [below](#lst:HCIcmdExample) illustrates the *hci_write_local_name* HCI
|
||||
command template from library:
|
||||
|
||||
<a name "lst:HCIcmdExample"></a>
|
||||
<!-- -->
|
||||
|
||||
// Sets local Bluetooth name
|
||||
const hci_cmd_t hci_write_local_name = {
|
||||
OPCODE(OGF_CONTROLLER_BASEBAND, 0x13), "N"
|
||||
// Local name (UTF-8, Null Terminated, max 248 octets)
|
||||
};
|
||||
|
||||
It uses OGF_CONTROLLER_BASEBAND as OGF,
|
||||
0x13 as OCF, and has one parameter with format “N” indicating a null
|
||||
terminated UTF-8 string. Table [table:hciformat] lists the format
|
||||
specifiers supported by BTstack. Check for other predefined HCI commands
|
||||
and info on their parameters.
|
||||
|
||||
------------------- ----------------------------------------------------
|
||||
Format Specifier Description
|
||||
1,2,3,4 one to four byte value
|
||||
A 31 bytes advertising data
|
||||
B Bluetooth Baseband Address
|
||||
D 8 byte data block
|
||||
E Extended Inquiry Information 240 octets
|
||||
H HCI connection handle
|
||||
N Name up to 248 chars, UTF8 string, null terminated
|
||||
P 16 byte Pairing code, e.g. PIN code or link key
|
||||
S Service Record (Data Element Sequence)
|
||||
------------------- ----------------------------------------------------
|
||||
|
||||
### Sending HCI command based on a template
|
||||
<a name ="sec:sendinghci"></a>
|
||||
|
||||
You can use the *hci_send_cmd* function to send HCI command based on a
|
||||
template and a list of parameters. However, it is necessary to check
|
||||
that the outgoing packet buffer is empty and that the Bluetooth module
|
||||
is ready to receive the next command - most modern Bluetooth modules
|
||||
only allow to send a single HCI command. This can be done by calling
|
||||
*hci_can_send_command_packet_now()* function, which returns true,
|
||||
if it is ok to send.
|
||||
|
||||
Listing [below](#lst:HCIcmdExampleLocalName) illustrates how to manually set the
|
||||
device name with the HCI Write Local Name command.
|
||||
|
||||
<a name "lst:HCIcmdExampleLocalName"></a>
|
||||
<!-- -->
|
||||
if (hci_can_send_packet_now(HCI_COMMAND_DATA_PACKET)){
|
||||
hci_send_cmd(&hci_write_local_name, "BTstack Demo");
|
||||
}
|
||||
|
||||
|
||||
Please note, that an application rarely has to send HCI commands on its
|
||||
own. Instead, BTstack provides convenience functions in GAP and higher
|
||||
level protocols use HCI automatically.
|
||||
|
||||
L2CAP - Logical Link Control and Adaptation Protocol
|
||||
----------------------------------------------------
|
||||
|
||||
The L2CAP protocol supports higher level protocol multiplexing and
|
||||
packet fragmentation. It provides the base for the RFCOMM and BNEP
|
||||
protocols. For all profiles that are officially supported by BTstack,
|
||||
L2CAP does not need to be used directly. For testing or the development
|
||||
of custom protocols, it’s helpful to be able to access and provide L2CAP
|
||||
services however.
|
||||
|
||||
### Access an L2CAP service on a remote device
|
||||
|
||||
L2CAP is based around the concept of channels. A channel is a logical
|
||||
connection on top of a baseband connection. Each channel is bound to a
|
||||
single protocol in a many-to-one fashion. Multiple channels can be bound
|
||||
to the same protocol, but a channel cannot be bound to multiple
|
||||
protocols. Multiple channels can share the same baseband connection.
|
||||
|
||||
To communicate with an L2CAP service on a remote device, the application
|
||||
on a local Bluetooth device initiates the L2CAP layer using the
|
||||
*l2cap_init* function, and then creates an outgoing L2CAP channel to
|
||||
the PSM of a remote device using the *l2cap_create_channel_internal*
|
||||
function. The *l2cap_-create_channel_internal* function will initiate
|
||||
a new baseband connection if it does not already exist. The packet
|
||||
handler that is given as an input parameter of the L2CAP create channel
|
||||
function will be assigned to the new outgoing L2CAP channel. This
|
||||
handler receives the L2CAP_EVENT_CHANNEL_OPENED and
|
||||
L2CAP_EVENT_CHANNEL_CLOSED events and L2CAP data packets, as shown
|
||||
in Listing [below](#lst:L2CAPremoteService).
|
||||
|
||||
<a name "lst:L2CAPremoteService"></a>
|
||||
<!-- -->
|
||||
|
||||
btstack_packet_handler_t l2cap_packet_handler;
|
||||
|
||||
void btstack_setup(){
|
||||
...
|
||||
l2cap_init();
|
||||
}
|
||||
|
||||
void create_outgoing_l2cap_channel(bd_addr_t address, uint16_t psm, uint16_t mtu){
|
||||
l2cap_create_channel_internal(NULL, l2cap_packet_handler, remote_bd_addr, psm, mtu);
|
||||
}
|
||||
|
||||
void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
if (packet_type == HCI_EVENT_PACKET &&
|
||||
packet[0] == L2CAP_EVENT_CHANNEL_OPENED){
|
||||
if (packet[2]) {
|
||||
printf("Connection failed\n\r");
|
||||
return;
|
||||
}
|
||||
printf("Connected\n\r");
|
||||
}
|
||||
if (packet_type == L2CAP_DATA_PACKET){
|
||||
// handle L2CAP data packet
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
### Provide an L2CAP service
|
||||
|
||||
To provide an L2CAP service, the application on a local Bluetooth device
|
||||
must init the L2CAP layer and register the service with
|
||||
*l2cap_register_service_internal*. From there on, it can wait for
|
||||
incoming L2CAP connections. The application can accept or deny an
|
||||
incoming connection by calling the *l2cap_accept_connection_internal*
|
||||
and *l2cap_deny_connection_internal* functions respectively. If a
|
||||
connection is accepted and the incoming L2CAP channel gets successfully
|
||||
opened, the L2CAP service can send L2CAP data packets to the connected
|
||||
device with *l2cap_send_internal*.
|
||||
|
||||
Sending of L2CAP data packets may fail due to a full internal BTstack
|
||||
outgoing packet buffer, or if the ACL buffers in the Bluetooth module
|
||||
become full, i.e., if the application is sending faster than the packets
|
||||
can be transferred over the air. In such case, the application can try
|
||||
sending again upon reception of DAEMON_EVENT_HCI_PACKET_SENT or
|
||||
L2CAP_EVENT_CREDITS event. The first event signals that the internal
|
||||
BTstack outgoing buffer became free again, the second one signals the
|
||||
same for ACL buffers in the Bluetooth chipset. Listing [below](#lst:L2CAPService)
|
||||
provides L2CAP service example code.
|
||||
|
||||
<a name "lst:L2CAPService"></a>
|
||||
<!-- -->
|
||||
|
||||
void btstack_setup(){
|
||||
...
|
||||
l2cap_init();
|
||||
l2cap_register_service_internal(NULL, packet_handler, 0x11,100);
|
||||
}
|
||||
|
||||
void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
if (packet_type == L2CAP_DATA_PACKET){
|
||||
// handle L2CAP data packet
|
||||
return;
|
||||
}
|
||||
switch(event){
|
||||
...
|
||||
case L2CAP_EVENT_INCOMING_CONNECTION:
|
||||
bt_flip_addr(event_addr, &packet[2]);
|
||||
handle = READ_BT_16(packet, 8);
|
||||
psm = READ_BT_16(packet, 10);
|
||||
local_cid = READ_BT_16(packet, 12);
|
||||
printf("L2CAP incoming connection requested.");
|
||||
l2cap_accept_connection_internal(local_cid);
|
||||
break;
|
||||
case L2CAP_EVENT_CHANNEL_OPENED:
|
||||
bt_flip_addr(event_addr, &packet[3]);
|
||||
psm = READ_BT_16(packet, 11);
|
||||
local_cid = READ_BT_16(packet, 13);
|
||||
handle = READ_BT_16(packet, 9);
|
||||
if (packet[2] == 0) {
|
||||
printf("Channel successfully opened.");
|
||||
} else {
|
||||
printf("L2CAP connection failed. status code.");
|
||||
}
|
||||
break;
|
||||
case L2CAP_EVENT_CREDITS:
|
||||
case DAEMON_EVENT_HCI_PACKET_SENT:
|
||||
tryToSend();
|
||||
break;
|
||||
case L2CAP_EVENT_CHANNEL_CLOSED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
### L2CAP LE - L2CAP Low Energy Protocol
|
||||
|
||||
In addition to the full L2CAP implementation in the *src* folder,
|
||||
BTstack provides an optimized *l2cap_le* implementation in the *ble*
|
||||
folder. This L2CAP LE variant can be used for single-mode devices and
|
||||
provides the base for the ATT and SMP protocols.
|
||||
|
||||
RFCOMM - Radio Frequency Communication Protocol
|
||||
-----------------------------------------------
|
||||
|
||||
The Radio frequency communication (RFCOMM) protocol provides emulation
|
||||
of serial ports over the L2CAP protocol. and reassembly. It is the base
|
||||
for the Serial Port Profile and other profiles used for
|
||||
telecommunication like Head-Set Profile, Hands-Free Profile, Object
|
||||
Exchange (OBEX) etc.
|
||||
|
||||
### RFCOMM flow control.
|
||||
<a name="sec:flowcontrol"></a>
|
||||
|
||||
RFCOMM has a mandatory credit-based flow-control. This means that two
|
||||
devices that established RFCOMM connection, use credits to keep track of
|
||||
how many more RFCOMM data packets can be sent to each. If a device has
|
||||
no (outgoing) credits left, it cannot send another RFCOMM packet, the
|
||||
transmission must be paused. During the connection establishment,
|
||||
initial credits are provided. BTstack tracks the number of credits in
|
||||
both directions. If no outgoing credits are available, the RFCOMM send
|
||||
function will return an error, and you can try later. For incoming data,
|
||||
BTstack provides channels and services with and without automatic credit
|
||||
management via different functions to create/register them respectively.
|
||||
If the management of credits is automatic, the new credits are provided
|
||||
when needed relying on ACL flow control - this is only useful if there
|
||||
is not much data transmitted and/or only one physical connection is
|
||||
used. If the management of credits is manual, credits are provided by
|
||||
the application such that it can manage its receive buffers explicitly.
|
||||
|
||||
### Access an RFCOMM service on a remote device
|
||||
<a name="sec:rfcommlient"></a>
|
||||
|
||||
To communicate with an RFCOMM service on a remote device, the
|
||||
application on a local Bluetooth device initiates the RFCOMM layer using
|
||||
the *rfcomm_init* function, and then creates an outgoing RFCOMM channel
|
||||
to a given server channel on a remote device using the
|
||||
*rfcomm_create_channel_internal* function. The
|
||||
*rfcomm_create_channel_intern-al* function will initiate a new L2CAP
|
||||
connection for the RFCOMM multiplexer, if it does not already exist. The
|
||||
channel will automatically provide enough credits to the remote side. To
|
||||
provide credits manually, you have to create the RFCOMM connection by
|
||||
calling *rfcomm_create_channel_with_initial_credits_internal* -
|
||||
see Section [on manual credit assignement](#sec:manualCredits).
|
||||
|
||||
The packet handler that is given as an input parameter of the RFCOMM
|
||||
create channel function will be assigned to the new outgoing channel.
|
||||
This handler receives the RFCOMM_EVENT_OPEN_CHAN-NEL_COMPLETE and
|
||||
RFCOMM_EVENT_CHANNEL_CLOSED events, and RFCOMM data packets, as shown in
|
||||
Listing [below](#lst:RFCOMMremoteService).
|
||||
|
||||
<a name "lst:RFCOMMremoteService"></a>
|
||||
<!-- -->
|
||||
|
||||
void init_rfcomm(){
|
||||
...
|
||||
rfcomm_init();
|
||||
rfcomm_register_packet_handler(packet_handler);
|
||||
}
|
||||
|
||||
void create_rfcomm_channel(uint8_t packet_type, uint8_t *packet, uint16_t size){
|
||||
rfcomm_create_channel_internal(connection, addr, rfcomm_channel);
|
||||
}
|
||||
|
||||
void rfcomm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
if (packet_type == HCI_EVENT_PACKET && packet[0] == RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE){
|
||||
if (packet[2]) {
|
||||
printf("Connection failed\n\r");
|
||||
return;
|
||||
}
|
||||
printf("Connected\n\r");
|
||||
}
|
||||
|
||||
if (packet_type == RFCOMM_DATA_PACKET){
|
||||
// handle RFCOMM data packets
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
### Provide an RFCOMM service
|
||||
<a name="sec:rfcomm_service"></a>
|
||||
|
||||
To provide an RFCOMM service, the application on a local Bluetooth
|
||||
device must first init the L2CAP and RFCOMM layers and then register the
|
||||
service with *rfcomm_register_service_internal*. From there on, it
|
||||
can wait for incoming RFCOMM connections. The application can accept or
|
||||
deny an incoming connection by calling the
|
||||
*rfcomm_accept_connection_internal* and
|
||||
*rfcomm_deny_connection_internal* functions respectively. If a
|
||||
connection is accepted and the incoming RFCOMM channel gets successfully
|
||||
opened, the RFCOMM service can send RFCOMM data packets to the connected
|
||||
device with *rfcomm_send_internal* and receive data packets by the
|
||||
packet handler provided by the *rfcomm_register_service_internal*
|
||||
call.
|
||||
|
||||
Sending of RFCOMM data packets may fail due to a full internal BTstack
|
||||
outgoing packet buffer, or if the ACL buffers in the Bluetooth module
|
||||
become full, i.e., if the application is sending faster than the packets
|
||||
can be transferred over the air. In such case, the application can try
|
||||
sending again upon reception of DAEMON_EVENT_HCI_PACKET_SENT or
|
||||
RFCOMM_EVENT_CREDITS event. The first event signals that the internal
|
||||
BTstack outgoing buffer became free again, the second one signals that
|
||||
the remote side allowed to send another packet. Listing [below](#lst:RFCOMMService)
|
||||
provides the RFCOMM service example code.
|
||||
|
||||
<a name "lst:RFCOMMService"></a>
|
||||
<!-- -->
|
||||
|
||||
void btstack_setup(){
|
||||
...
|
||||
rfcomm_init();
|
||||
rfcomm_register_service_internal(NULL, rfcomm_channel_nr, mtu);
|
||||
}
|
||||
|
||||
void packet_handler(uint8_t packet_type, uint8_t *packet, uint16_t size){
|
||||
if (packet_type == RFCOMM_DATA_PACKET){
|
||||
// handle RFCOMM data packets
|
||||
return;
|
||||
}
|
||||
...
|
||||
switch (event) {
|
||||
...
|
||||
case RFCOMM_EVENT_INCOMING_CONNECTION:
|
||||
//data: event(8), len(8), address(48), channel(8), rfcomm_cid(16)
|
||||
bt_flip_addr(event_addr, &packet[2]);
|
||||
rfcomm_channel_nr = packet[8];
|
||||
rfcomm_channel_id = READ_BT_16(packet, 9);
|
||||
rfcomm_accept_connection_internal(rfcomm_channel_id);
|
||||
break;
|
||||
case RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE:
|
||||
// data: event(8), len(8), status (8), address (48), handle(16), server channel(8), rfcomm_cid(16), max frame size(16)
|
||||
if (packet[2]) {
|
||||
printf("RFCOMM channel open failed.");
|
||||
break;
|
||||
}
|
||||
// data: event(8), len(8), status (8), address (48), handle (16), server channel(8), rfcomm_cid(16), max frame size(16)
|
||||
rfcomm_channel_id = READ_BT_16(packet, 12);
|
||||
mtu = READ_BT_16(packet, 14);
|
||||
printf("RFCOMM channel open succeeded.");
|
||||
break;
|
||||
case RFCOMM_EVENT_CREDITS:
|
||||
case DAEMON_EVENT_HCI_PACKET_SENT:
|
||||
tryToSend();
|
||||
break;
|
||||
|
||||
case RFCOMM_EVENT_CHANNEL_CLOSED:
|
||||
printf("Channel closed.");
|
||||
rfcomm_channel_id = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
### Living with a single output buffer
|
||||
<a name="sec:single_buffer"></a>
|
||||
Outgoing packets, both commands and data, are not queued in BTstack.
|
||||
This section explains the consequences of this design decision for
|
||||
sending data and why it is not as bad as it sounds.
|
||||
|
||||
Independent from the number of output buffers, packet generation has to
|
||||
be adapted to the remote receiver and/or maximal link speed. Therefore,
|
||||
a packet can only be generated when it can get sent. With this
|
||||
assumption, the single output buffer design does not impose additional
|
||||
restrictions. In the following, we show how this is used for adapting
|
||||
the RFCOMM send rate.
|
||||
|
||||
BTstack returns BTSTACK_ACL_BUFFERS_FULL, if the outgoing buffer is
|
||||
full and RFCOMM_NO_OUTGOING_CREDITS, if no outgoing credits are
|
||||
available. In Listing [below](#lst:SingleOutputBufferTryToSend), we show how to
|
||||
resend data packets when credits or outgoing buffers become available.
|
||||
|
||||
<a name "lst:SingleOutputBufferTryToSend"></a>
|
||||
<!-- -->
|
||||
|
||||
void prepareData(void){
|
||||
...
|
||||
}
|
||||
|
||||
void tryToSend(void){
|
||||
if (!dataLen) return;
|
||||
if (!rfcomm_channel_id) return;
|
||||
|
||||
int err = rfcomm_send_internal(rfcomm_channel_id, dataBuffer, dataLen);
|
||||
switch (err){
|
||||
case 0:
|
||||
// packet is sent prepare next one
|
||||
prepareData();
|
||||
break;
|
||||
case RFCOMM_NO_OUTGOING_CREDITS:
|
||||
case BTSTACK_ACL_BUFFERS_FULL:
|
||||
break;
|
||||
default:
|
||||
printf("rfcomm_send_internal() -> err %d\n\r", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RFCOMM’s mandatory credit-based flow-control imposes an additional
|
||||
constraint on sending a data packet - at least one new RFCOMM credit
|
||||
must be available. BTstack signals the availability of a credit by
|
||||
sending an RFCOMM credit (RFCOMM_EVENT_CREDITS) event.
|
||||
|
||||
These two events represent two orthogonal mechanisms that deal with flow
|
||||
control. Taking these mechanisms in account, the application should try
|
||||
to send data packets when one of these two events is received. For a RFCOMM example see
|
||||
Listing [below](#lst:SingleOutputBufferTryPH).
|
||||
|
||||
|
||||
<a name "lst:SingleOutputBufferTryPH"></a>
|
||||
<!-- -->
|
||||
|
||||
void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
switch(event){
|
||||
case RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE:
|
||||
if (status) {
|
||||
printf("RFCOMM channel open failed.");
|
||||
} else {
|
||||
rfcomm_channel_id = READ_BT_16(packet, 12);
|
||||
rfcomm_mtu = READ_BT_16(packet, 14);
|
||||
printf("RFCOMM channel opened, mtu = %u.", rfcomm_mtu);
|
||||
}
|
||||
break;
|
||||
case RFCOMM_EVENT_CREDITS:
|
||||
case DAEMON_EVENT_HCI_PACKET_SENT:
|
||||
tryToSend();
|
||||
break;
|
||||
case RFCOMM_EVENT_CHANNEL_CLOSED:
|
||||
rfcomm_channel_id = 0;
|
||||
break;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
If the management of credits is manual, credits are provided by the
|
||||
application such that it can manage its receive buffers explicitly, see
|
||||
Listing [below].
|
||||
|
||||
<a name "lst:explicitFlowControl"></a>
|
||||
<!-- -->
|
||||
|
||||
void btstack_setup(void){
|
||||
...
|
||||
// init RFCOMM
|
||||
rfcomm_init();
|
||||
rfcomm_register_packet_handler(packet_handler);
|
||||
// reserved channel, mtu=100, 1 credit
|
||||
rfcomm_register_service_with_initial_credits_internal(NULL, rfcomm_channel_nr, 100, 1);
|
||||
}
|
||||
|
||||
|
||||
Manual credit management is recommended when received RFCOMM data cannot
|
||||
be processed immediately. In the [SPP flow control example](examples/#sec:sppflowcontrol),
|
||||
delayed processing of received data is
|
||||
simulated with the help of a periodic timer. To provide new credits, you
|
||||
call the *rfcomm_grant_credits* function with the RFCOMM channel ID
|
||||
and the number of credits as shown in Listing [below](#lst:NewCredits).
|
||||
|
||||
<a name "lst:NewCredits"></a>
|
||||
<!-- -->
|
||||
|
||||
void processing(){
|
||||
// process incoming data packet
|
||||
...
|
||||
// provide new credit
|
||||
rfcomm_grant_credits(rfcomm_channel_id, 1);
|
||||
}
|
||||
|
||||
Please note that providing single credits effectively reduces the credit-based
|
||||
(sliding window) flow control to a stop-and-wait flow-control that
|
||||
limits the data throughput substantially. On the plus side, it allows
|
||||
for a minimal memory footprint. If possible, multiple RFCOMM buffers
|
||||
should be used to avoid pauses while the sender has to wait for a new
|
||||
credit.
|
||||
|
||||
### Slowing down RFCOMM data reception
|
||||
<a name="sec:manualCredits"></a>
|
||||
|
||||
RFCOMM’s credit-based flow-control can be used to adapt, i.e., slow down
|
||||
the RFCOMM data to your processing speed. For incoming data, BTstack
|
||||
provides channels and services with and without automatic credit
|
||||
management. If the management of credits is automatic, new credits
|
||||
are provided when needed relying on ACL flow control. This is only
|
||||
useful if there is not much data transmitted and/or only one physical
|
||||
connection is used. See Listing [below](#lst:automaticFlowControl).
|
||||
|
||||
<a name "lst:automaticFlowControl"></a>
|
||||
<!-- -->
|
||||
|
||||
void btstack_setup(void){
|
||||
...
|
||||
// init RFCOMM
|
||||
rfcomm_init();
|
||||
rfcomm_register_packet_handler(packet_handler);
|
||||
rfcomm_register_service_internal(NULL, rfcomm_channel_nr, 100);
|
||||
}
|
||||
|
||||
|
||||
SDP - Service Discovery Protocol
|
||||
--------------------------------
|
||||
|
||||
The SDP protocol allows to announce services and discover services
|
||||
provided by a remote Bluetooth device.
|
||||
|
||||
### Create and announce SDP records
|
||||
|
||||
BTstack contains a complete SDP server and allows to register SDP
|
||||
records. An SDP record is a list of SDP Attribute *{ID, Value}* pairs
|
||||
that are stored in a Data Element Sequence (DES). The Attribute ID is a
|
||||
16-bit number, the value can be of other simple types like integers or
|
||||
strings or can itselff contain other DES.
|
||||
|
||||
To create an SDP record for an SPP service, you can call
|
||||
*sdp_create_spp_service* from with a pointer to a buffer to store the
|
||||
record, the server channel number, and a record name.
|
||||
|
||||
For other types of records, you can use the other functions in , using
|
||||
the data element *de_* functions. Listing [sdpCreate] shows how an SDP
|
||||
record containing two SDP attributes can be created. First, a DES is
|
||||
created and then the Service Record Handle and Service Class ID List
|
||||
attributes are added to it. The Service Record Handle attribute is added
|
||||
by calling the *de_add_number* function twice: the first time to add
|
||||
0x0000 as attribute ID, and the second time to add the actual record
|
||||
handle (here 0x1000) as attribute value. The Service Class ID List
|
||||
attribute has ID 0x0001, and it requires a list of UUIDs as attribute
|
||||
value. To create the list, *de_push_sequence* is called, which “opens”
|
||||
a sub-DES. The returned pointer is used to add elements to this sub-DES.
|
||||
After adding all UUIDs, the sub-DES is “closed” with
|
||||
*de_pop_sequence*.
|
||||
|
||||
### Query remote SDP service
|
||||
<a name="sec:querysdp"></a>
|
||||
|
||||
BTstack provides an SDP client to query SDP services of a remote device.
|
||||
The SDP Client API is shown in Appendix [appendix:api~s~dp~c~lient]. The
|
||||
*sdp_client_query* function initiates an L2CAP connection to the
|
||||
remote SDP server. Upon connect, a *Service Search Attribute* request
|
||||
with a *Service Search Pattern* and a *Attribute ID List* is sent. The
|
||||
result of the *Service Search Attribute* query contains a list of
|
||||
*Service Records*, and each of them contains the requested attributes.
|
||||
These records are handled by the SDP parser. The parser delivers
|
||||
SDP_PARSER_ATTRIBUTE_VALUE and SDP_PARSER_COMPLETE events via a
|
||||
registered callback. The SDP_PARSER_ATTRIBUTE_VALUE event delivers
|
||||
the attribute value byte by byte.
|
||||
|
||||
On top of this, you can implement specific SDP queries. For example,
|
||||
BTstack provides a query for RFCOMM service name and channel number.
|
||||
This information is needed, e.g., if you want to connect to a remote SPP
|
||||
service. The query delivers all matching RFCOMM services, including its
|
||||
name and the channel number, as well as a query complete event via a
|
||||
registered callback, as shown in Listing [below](#lst:SDPClientRFCOMM).
|
||||
|
||||
<a name "lst:SDPClientRFCOMM"></a>
|
||||
<!-- -->
|
||||
|
||||
bd_addr_t remote = {0x04,0x0C,0xCE,0xE4,0x85,0xD3};
|
||||
|
||||
void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
if (packet_type != HCI_EVENT_PACKET) return;
|
||||
|
||||
uint8_t event = packet[0];
|
||||
switch (event) {
|
||||
case BTSTACK_EVENT_STATE:
|
||||
// bt stack activated, get started
|
||||
if (packet[2] == HCI_STATE_WORKING){
|
||||
sdp_query_rfcomm_channel_and_name_for_uuid(remote, 0x0003);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void btstack_setup(){
|
||||
...
|
||||
// init L2CAP
|
||||
l2cap_init();
|
||||
l2cap_register_packet_handler(packet_handler);
|
||||
}
|
||||
|
||||
void handle_query_rfcomm_event(sdp_query_event_t * event, void * context){
|
||||
sdp_query_rfcomm_service_event_t * ve;
|
||||
|
||||
switch (event->type){
|
||||
case SDP_QUERY_RFCOMM_SERVICE:
|
||||
ve = (sdp_query_rfcomm_service_event_t*) event;
|
||||
printf("Service name: '%s', RFCOMM port %u\n", ve->service_name, ve->channel_nr);
|
||||
break;
|
||||
case SDP_QUERY_COMPLETE:
|
||||
report_found_services();
|
||||
printf("Client query response done with status %d. \n", ce->status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void){
|
||||
hw_setup();
|
||||
btstack_setup();
|
||||
|
||||
// register callback to receive matching RFCOMM Services and
|
||||
// query complete event
|
||||
sdp_query_rfcomm_register_callback(handle_query_rfcomm_event, NULL);
|
||||
|
||||
// turn on!
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
// go!
|
||||
run_loop_execute();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t des_buffer[200];
|
||||
uint8_t* attribute;
|
||||
de_create_sequence(service);
|
||||
|
||||
// 0x0000 "Service Record Handle"
|
||||
de_add_number(des_buffer, DE_UINT, DE_SIZE_16, SDP_ServiceRecordHandle);
|
||||
de_add_number(des_buffer, DE_UINT, DE_SIZE_32, 0x10001);
|
||||
|
||||
// 0x0001 "Service Class ID List"
|
||||
de_add_number(des_buffer, DE_UINT, DE_SIZE_16, SDP_ServiceClassIDList);
|
||||
attribute = de_push_sequence(des_buffer);
|
||||
{
|
||||
de_add_number(attribute, DE_UUID, DE_SIZE_16, 0x1101 );
|
||||
}
|
||||
de_pop_sequence(des_buffer, attribute);
|
||||
|
||||
|
||||
BNEP - Bluetooth Network Encapsulation Protocol
|
||||
-----------------------------------------------
|
||||
|
||||
The BNEP protocol is used to transport control and data packets over
|
||||
standard network protocols such as TCP, IPv4 or IPv6. It is built on top
|
||||
of L2CAP, and it specifies a minimum L2CAP MTU of 1691 bytes.
|
||||
|
||||
### Receive BNEP events
|
||||
|
||||
To receive BNEP events, please register a packet handler with
|
||||
*bnep_register_packet_handler*.
|
||||
|
||||
### Access a BNEP service on a remote device {#subsubsection:bnepclient}
|
||||
|
||||
To connect to a remote BNEP service, you need to know its UUID. The set
|
||||
of available UUIDs can be queried by a SDP query for the PAN profile.
|
||||
Please see Section [section:pan~p~rofile] for details. With the remote
|
||||
UUID, you can create a connection using the *bnep_connect* function.
|
||||
You’ll receive a *BNEP_EVENT_OPEN_CHANNEL_COMPLETE* on success or
|
||||
failure.
|
||||
|
||||
After the connection was opened successfully, you can send and receive
|
||||
Ethernet packets. Before sending an Ethernet frame with *bnep_send*,
|
||||
*bnep_can_send_packet_now* needs to return true. Ethernet frames
|
||||
are received via the registered packet handler with packet type
|
||||
*BNEP_DATA_PACKET*.
|
||||
|
||||
BTstack BNEP implementation supports both network protocol filter and
|
||||
multicast filters with *bnep_set_net_type_filter* and
|
||||
*bnep_set_multicast_filter* respectively.
|
||||
|
||||
Finally, to close a BNEP connection, you can call *bnep_disconnect*.
|
||||
|
||||
### Provide BNEP service {#subsubsection:bnepserver}
|
||||
|
||||
To provide a BNEP service, call *bnep_register_service* with the
|
||||
provided service UUID and a max frame size.
|
||||
|
||||
A *BNEP_EVENT_INCOMING_CONNECTION* event will mark that an incoming
|
||||
connection is established. At this point you can start sending and
|
||||
receiving Ethernet packets as described in the previous section.
|
||||
|
||||
ATT - Attribute Protocol
|
||||
------------------------
|
||||
|
||||
The ATT protocol is used by an ATT client to read and write attribute
|
||||
values stored on an ATT server. In addition, the ATT server can notify
|
||||
the client about attribute value changes. An attribute has a handle, a
|
||||
type, and a set of properties, see Section [section:GATTServer].
|
||||
|
||||
The Generic Attribute (GATT) profile is built upon ATT and provides
|
||||
higher level organization of the ATT attributes into GATT Services and
|
||||
GATT Characteristics. In BTstack, the complete ATT client functionality
|
||||
is included within the GATT Client. On the server side, one ore more
|
||||
GATT profiles are converted ahead of time into the corresponding ATT
|
||||
attribute database and provided by the *att_server* implementation. The
|
||||
constant data are automatically served by the ATT server upon client
|
||||
request. To receive the dynamic data, such is characteristic value, the
|
||||
application needs to register read and/or write callback. In addition,
|
||||
notifications and indications can be sent. Please see Section
|
||||
[section:GATTClient] for more.
|
||||
|
||||
SMP - Security Manager Protocol {#section:smp}
|
||||
--------------------------------
|
||||
|
||||
The SMP protocol allows to setup authenticated and encrypted LE
|
||||
connection. After initialization and configuration, SMP handles security
|
||||
related functions on it’s own but emits events when feedback from the
|
||||
main app or the user is required. The two main tasks of the SMP protocol
|
||||
are: bonding and identity resolving.
|
||||
|
||||
### Initialization
|
||||
|
||||
To activate the security manager, call *sm_init()*.
|
||||
|
||||
If you’re creating a product, you should also call *sm_set_ir()* and
|
||||
*sm_set_er()* with a fixed random 16 byte number to create the IR and
|
||||
ER key seeds. If possible use a unique random number per device instead
|
||||
of deriving it from the product serial number or something similar. The
|
||||
encryption key generated by the BLE peripheral will be ultimately
|
||||
derived from the ER key seed. See [Bluetooth Specification](https://www.bluetooth.org/Technical/Specifications/adopted.htm) -
|
||||
Bluetooth Core V4.0, Vol 3, Part G, 5.2.2 for more details on deriving
|
||||
the different keys. The IR key is used to identify a device if private,
|
||||
resolvable Bluetooth addresses are used.
|
||||
|
||||
### Configuration
|
||||
|
||||
To receive events from the Security Manager, a callback is necessary.
|
||||
How to register this packet handler depends on your application
|
||||
configuration.
|
||||
|
||||
When *att_server* is used to provide a GATT/ATT service, *att_server*
|
||||
registers itself as the Security Manager packet handler. Security
|
||||
Manager events are then received by the application via the
|
||||
*att_server* packet handler.
|
||||
|
||||
If *att_server* is not used, you can directly register your packet
|
||||
handler with the security manager by calling
|
||||
*sm_register_packet_handler*.
|
||||
|
||||
The default SMP configuration in BTstack is to be as open as possible:
|
||||
|
||||
- accept all Short Term Key (STK) Generation methods,
|
||||
|
||||
- accept encryption key size from 7..16 bytes,
|
||||
|
||||
- expect no authentication requirements, and
|
||||
|
||||
- IO Capabilities set to *IO_CAPABILITY_NO_INPUT_NO_OUTPUT*.
|
||||
|
||||
You can configure these items by calling following functions
|
||||
respectively:
|
||||
|
||||
- *sm_set_accepted_stk_generation_methods*
|
||||
|
||||
- *sm_set_encryption_key_size_range*
|
||||
|
||||
- *sm_set_authentication_requirements*
|
||||
|
||||
- *sm_set_io_capabilities*
|
||||
|
||||
### Identity Resolving
|
||||
|
||||
Identity resolving is the process of matching a private, resolvable
|
||||
Bluetooth address to a previously paired device using its Identity
|
||||
Resolving (IR) key. After an LE connection gets established, BTstack
|
||||
automatically tries to resolve the address of this device. During this
|
||||
lookup, BTstack will emit the following events:
|
||||
|
||||
- *SM_IDENTITY_RESOLVING_STARTED* to mark the start of a lookup,
|
||||
|
||||
and later:
|
||||
|
||||
- *SM_IDENTITY_RESOLVING_SUCCEEDED* on lookup success, or
|
||||
|
||||
- *SM_IDENTITY_RESOLVING_FAILED* on lookup failure.
|
||||
|
||||
### Bonding process
|
||||
|
||||
In Bluetooth LE, there are three main methods of establishing an
|
||||
encrypted connection. From the most to the least secure, these are:
|
||||
Out-of-Band (OOB) Data , Passkey, and Just Works.
|
||||
|
||||
With OOB data, there needs to be a pre-shared secret 16 byte key. In
|
||||
most cases, this is not an option, especially since popular OS like iOS
|
||||
don’t provide a way to specify it. It some applications, where both
|
||||
sides of a Bluetooth link are developed together, this could provide a
|
||||
viable option.
|
||||
|
||||
To provide OOB data, you can register an OOB data callback with
|
||||
*sm_register_oob_data_callback*.
|
||||
|
||||
Depending on the authentication requirements, available OOB data, and
|
||||
the enabled STK generation methods, BTstack will request feedback from
|
||||
the app in the form of an event:
|
||||
|
||||
- *SM_PASSKEY_INPUT_NUMBER*: request user to input a passkey
|
||||
|
||||
- *SM_PASSKEY_DISPLAY_NUMBER*: show a passkey to the user
|
||||
|
||||
- *SM_JUST_WORKS_REQUEST*: request a user to accept a Just Works
|
||||
pairing
|
||||
|
||||
To stop the bonding process, *sm_bonding_decline* should be called.
|
||||
Otherwise, *sm_just_works_confirm* or *sm_passkey_input* can be
|
||||
called.
|
||||
|
||||
After the bonding process, *SM_PASSKEY_DISPLAY_CANCEL* is emitted to
|
||||
update the user interface.
|
209
docs/manual/markdown/docs/quick_start.md
Normal file
209
docs/manual/markdown/docs/quick_start.md
Normal file
@ -0,0 +1,209 @@
|
||||
General Tools
|
||||
-------------
|
||||
|
||||
On Unix-based systems, git, make, and Python are usually installed. If
|
||||
not, use the system’s packet manager to install them.
|
||||
|
||||
On Windows, you need to manually install and configure GNU Make, Python,
|
||||
and optionally git :
|
||||
|
||||
- [GNU Make](http://gnuwin32.sourceforge.net/packages/make.htm)
|
||||
for Windows: Add its bin folder to the Windows Path in Environment
|
||||
Variables. The bin folder is where make.exe resides, and it’s
|
||||
usually located in .
|
||||
|
||||
- [Python](http://www.python.org/getit/) for
|
||||
Windows: Add Python installation folder to the Windows Path in
|
||||
Environment Variables.
|
||||
|
||||
Adding paths to the Windows Path variable <a name="sec:windowsPath"></a>:
|
||||
|
||||
- Go to: Control Panel->System->Advanced
|
||||
tab->Environment Variables.
|
||||
|
||||
- The top part contains a list of User variables.
|
||||
|
||||
- Click on the Path variable and then click edit.
|
||||
|
||||
- Go to the end of the line, then append the path to the list., for
|
||||
example, for GNU Make.
|
||||
|
||||
- Ensure that there is a semicolon before and after .
|
||||
|
||||
Getting BTstack from GitHub
|
||||
---------------------------
|
||||
|
||||
Use git to clone the latest version:
|
||||
|
||||
git clone https://github.com/bluekitchen/btstack.git
|
||||
|
||||
|
||||
Alternatively, you can download it as a ZIP archive from
|
||||
[BTstack’s page](https://github.com/bluekitchen/btstack/archive/master.zip) on
|
||||
GitHub.
|
||||
|
||||
Compiling the examples and loading firmware
|
||||
-------------------------------------------
|
||||
|
||||
This step is platform specific. To compile and run the examples, you
|
||||
need to download and install the platform specific toolchain and a flash
|
||||
tool. For TI’s CC256x chipsets, you also need the correct init script,
|
||||
or “Service Pack” in TI nomenclature. Assuming that these are provided,
|
||||
go to folder in command prompt and run make. If all the paths are
|
||||
correct, it will generate several firmware files. These firmware files
|
||||
can be loaded onto the device using platform specific flash programmer.
|
||||
For the PIC32-Harmony platform, a project file for the MPLAB X IDE is
|
||||
provided, too.
|
||||
|
||||
Run the Example
|
||||
---------------
|
||||
|
||||
As a first test, we recommend the SPP Counter example (see Section
|
||||
[example:sppcounter]). During the startup, for TI chipsets, the init
|
||||
script is transferred, and the Bluetooth stack brought up. After that,
|
||||
the development board is discoverable as “BTstack SPP Counter” and
|
||||
provides a single virtual serial port. When you connect to it, you’ll
|
||||
receive a counter value as text every second.
|
||||
|
||||
Platform specifics
|
||||
------------------
|
||||
|
||||
In the following, we provide more information on specific platform
|
||||
setups, toolchains, programmers, and init scripts.
|
||||
|
||||
### libusb
|
||||
|
||||
The quickest way to try BTstack is on a Linux or OS X system with an
|
||||
additional USB Bluetooth module. The Makefile in requires
|
||||
[pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/)
|
||||
and [libusb-1.0](www.libusb.org) or higher to be
|
||||
installed.
|
||||
|
||||
On Linux, it’s usually necessary to run the examples as root as the
|
||||
kernel needs to detach from the USB module.
|
||||
|
||||
On OS X, it’s necessary to tell the OS to only use the internal
|
||||
Bluetooth. For this, execute:
|
||||
|
||||
sudo nvram bluetoothHostControllerSwitchBehavior=never
|
||||
|
||||
It’s also possible to run the examples on Win32 systems. For this:
|
||||
|
||||
- Install [MSYS](www.mingw.org/wiki/msys) and
|
||||
[MINGW32](www.mingw.org) using the MINGW installer
|
||||
|
||||
- Compile and install libusb-1.0.19 to /usr/local/ in msys command
|
||||
shell
|
||||
|
||||
- Setup a USB Bluetooth dongle for use with libusb-1.0:
|
||||
|
||||
- Start [Zadig](http://zadig.akeo.ie)
|
||||
|
||||
- Select Options -> “List all devices”
|
||||
|
||||
- Select USB Bluetooth dongle in the big pull down list
|
||||
|
||||
- Select WinUSB (libusb) in the right pull pull down list
|
||||
|
||||
- Select “Replace Driver”
|
||||
|
||||
Now, you can run the examples from the *msys* shell the same way as on
|
||||
Linux/OS X.
|
||||
|
||||
### Texas Instruments MSP430-based boards
|
||||
|
||||
**Compiler Setup.** The MSP430 port of BTstack is developed using the
|
||||
Long Term Support (LTS) version of mspgcc. General information about it
|
||||
and installation instructions are provided on the
|
||||
[MSPGCC Wiki](http://sourceforge.net/apps/mediawiki/mspgcc/index.php?title=MSPGCC_Wiki).
|
||||
On Windows, you need to download and extract
|
||||
[mspgcc](http://sourceforge.net/projects/mspgcc/files/Windows/mingw32/)
|
||||
to ??. Add folder to the Windows Path in Environment variable as explained
|
||||
[here](#sec:windowsPath).
|
||||
|
||||
**Loading Firmware.** To load firmware files onto the MSP430 MCU for the
|
||||
MSP-EXP430F5438 Experimeneter board, you need a programmer like the
|
||||
MSP430 MSP-FET430UIF debugger or something similar. The eZ430-RF2560 and
|
||||
MSP430F5529LP contain a basic debugger. Now, you can use one of
|
||||
following software tools:
|
||||
|
||||
- [MSP430Flasher](http://processors.wiki.ti.com/index.php/MSP430_Flasher_-_Command_Line_Programmer)
|
||||
(windows-only):
|
||||
|
||||
- Use the following command, where you need to replace the with
|
||||
the name of your application:
|
||||
|
||||
<!-- -->
|
||||
|
||||
MSP430Flasher.exe -n MSP430F5438A -w "BINARY_FILE_NAME.hex" -v -g -z [VCC]
|
||||
|
||||
|
||||
- [MSPDebug](http://mspdebug.sourceforge.net/):
|
||||
An example session with the MSP-FET430UIF connected on OS X is given
|
||||
in following listing:
|
||||
|
||||
<!-- -->
|
||||
|
||||
mspdebug -j -d /dev/tty.FET430UIFfd130 uif
|
||||
...
|
||||
prog blink.hex
|
||||
run
|
||||
|
||||
### Texas Instruments CC256x-based chipsets
|
||||
|
||||
**CC256x Init Scripts.** In order to use the CC256x chipset on the
|
||||
PAN13xx modules and others, an initialization script must be obtained.
|
||||
Due to licensing restrictions, this initialization script must be
|
||||
obtained separately as follows:
|
||||
|
||||
- Download the [BTS file](http://processors.wiki.ti.com/index.php/CC256x_Downloads)
|
||||
for your CC256x-based module.
|
||||
|
||||
- Copy the included .bts file into
|
||||
|
||||
- In ??, run the Python script:
|
||||
|
||||
<!-- -->
|
||||
|
||||
./convert_bts_init_scripts.py
|
||||
|
||||
The common code for all CC256x chipsets is provided by
|
||||
*bt_control_cc256x.c*. During the setup,
|
||||
*bt_control_cc256x_instance* function is used to get a
|
||||
*bt_control_t* instance and passed to *hci_init* function.
|
||||
|
||||
**Note:** Depending on the CC256x-based module you’re using, you’ll need
|
||||
to update the reference in the Makefile to match the downloaded file.
|
||||
|
||||
**Update:** For the latest revision of the CC256x chipsets, the CC2560B
|
||||
and CC2564B, TI decided to split the init script into a main part and
|
||||
the BLE part. The conversion script has been updated to detect
|
||||
*bluetooth_init_cc256x_1.2.bts* and adds *BLE_init_cc256x_1.2.bts*
|
||||
if present and merges them into a single .c file.
|
||||
|
||||
### MSP-EXP430F5438 + CC256x Platform {#platform:msp430}
|
||||
|
||||
**Hardware Setup.** We assume that a PAN1315, PAN1317, or PAN1323 module
|
||||
is plugged into RF1 and RF2 of the MSP-EXP430F5438 board and the “RF3
|
||||
Adapter board” is used or at least simulated. See [User
|
||||
Guide](http://processors.wiki.ti.com/index.php/PAN1315EMK_User_Guide#RF3_Connector).
|
||||
|
||||
### STM32F103RB Nucleo + CC256x Platform
|
||||
|
||||
To try BTstack on this platform, you’ll need a simple adaptor board. For
|
||||
details, please read the documentation in ??.
|
||||
|
||||
### PIC32 Bluetooth Audio Development Kit
|
||||
|
||||
The PIC32 Bluetooth Audio Development Kit comes with the CSR8811-based
|
||||
BTM805 Bluetooth module. In the port, the UART on the DAC daughter board
|
||||
was used for the debug output. Please remove the DAC board and connect a
|
||||
3.3V USB-2-UART converter to GND and TX to get the debug output.
|
||||
|
||||
In ??, a project file for the MPLAB X IDE is provided as well as a regular
|
||||
Makefile. Both assume that the MPLAB XC32 compiler is installed. The
|
||||
project is set to use -Os optimization which will cause warnings if you
|
||||
only have the Free version. It will still compile a working example. For
|
||||
this platform, we only provide the SPP and LE Counter example directly.
|
||||
Other examples can be run by replacing the spp_and_le_counter.c file
|
||||
with one of the other example files.
|
6
docs/manual/markdown/docs/revision_history.md
Normal file
6
docs/manual/markdown/docs/revision_history.md
Normal file
@ -0,0 +1,6 @@
|
||||
Rev | Date | Comments
|
||||
----|------|---------
|
||||
1.x | April 27, 2015 | Added more platforms. Replaced Recipes with Protocols and Profiles. Added more examples.
|
||||
1.3 | November 6, 2014 | Introducing GATT client and server. Work in progress.
|
||||
1.2 | November 1, 2013 | Explained Secure Simple Pairing in "Pairing of devices".
|
||||
1.1 | August 30, 2013 | Introduced SDP client. Updated Quick Recipe on "Query remote SDP service".
|
16
docs/manual/markdown/mkdocs.yml
Normal file
16
docs/manual/markdown/mkdocs.yml
Normal file
@ -0,0 +1,16 @@
|
||||
site_name: BTstack Manual
|
||||
pages:
|
||||
- [index.md, Wellcome]
|
||||
- [quick_start.md, Quick Start]
|
||||
- [architecture.md, BTstack Architecture]
|
||||
- [how_to.md, How to use BTstack]
|
||||
- [protocols.md, Protocols]
|
||||
- [profiles.md, Profiles]
|
||||
- [examples/intro.md, Examples]
|
||||
- [examples/generated.md, List of Examples]
|
||||
- [porting.md, Porting to Other Platforms]
|
||||
- [integration.md, Integrating with Existing Systems]
|
||||
- [appendix/apis.md, APIs]
|
||||
- [appendix/events_errors.md, Events and Errors]
|
||||
- [revision_history.md, Revision History]
|
||||
theme: readthedocs
|
89
docs/manual/markdown/update_apis.py
Normal file
89
docs/manual/markdown/update_apis.py
Normal file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
class State:
|
||||
SearchStartAPI = 0
|
||||
RemoveEmptyLinesAfterAPIStart = 1
|
||||
SearchEndAPI = 2
|
||||
DoneAPI = 3
|
||||
|
||||
docs_folder = "docs/appendix/"
|
||||
appendix_file = docs_folder + "apis.md"
|
||||
btstack_folder = "../../../"
|
||||
code_identation = " "
|
||||
|
||||
api_header = """
|
||||
|
||||
## API_TITLE API
|
||||
<a name ="appendix:API_LABLE"></a>
|
||||
|
||||
"""
|
||||
|
||||
api_ending = """
|
||||
"""
|
||||
|
||||
# [file_name, api_title, api_lable]
|
||||
list_of_apis = [
|
||||
[btstack_folder+"include/btstack/run_loop.h", "Run Loop", "api_run_loop"],
|
||||
[btstack_folder+"src/hci.h", "HCI", "api_hci"],
|
||||
[btstack_folder+"src/l2cap.h", "L2CAP", "api_l2cap"],
|
||||
[btstack_folder+"src/rfcomm.h", "RFCOMM", "api_rfcomm"],
|
||||
[btstack_folder+"src/sdp.h", "SDP", "api_sdp"],
|
||||
[btstack_folder+"src/sdp_client.h", "SDP Client", "api_sdp_client"],
|
||||
[btstack_folder+"src/sdp_query_rfcomm.h", "SDP RFCOMM Query", "api_sdp_queries"],
|
||||
[btstack_folder+"ble/gatt_client.h", "GATT Client", "api_gatt_client"],
|
||||
[btstack_folder+"src/pan.h", "PAN", "api_pan"],
|
||||
[btstack_folder+"src/bnep.h", "BNEP", "api_bnep"],
|
||||
[btstack_folder+"src/gap.h", "GAP", "api_gap"],
|
||||
[btstack_folder+"ble/sm.h", "SM", "api_sm"]
|
||||
]
|
||||
|
||||
def replacePlaceholder(template, title, lable):
|
||||
api_title = title + " API"
|
||||
snippet = template.replace("API_TITLE", title).replace("API_LABLE", lable)
|
||||
return snippet
|
||||
|
||||
def writeAPI(fout, infile_name):
|
||||
global code_identation
|
||||
state = State.SearchStartAPI
|
||||
with open(infile_name, 'rb') as fin:
|
||||
for line in fin:
|
||||
if state == State.SearchStartAPI:
|
||||
parts = re.match('\s*(/\*).*API_START.*(\*/)',line)
|
||||
if parts:
|
||||
state = State.RemoveEmptyLinesAfterAPIStart
|
||||
continue
|
||||
|
||||
if state == State.RemoveEmptyLinesAfterAPIStart:
|
||||
if line == "" or line == "\n":
|
||||
continue
|
||||
state = State.SearchEndAPI
|
||||
|
||||
if state == State.SearchEndAPI:
|
||||
parts = re.match('\s*(/\*).*API_END.*(\*/)',line)
|
||||
if parts:
|
||||
state = State.DoneAPI
|
||||
return
|
||||
fout.write(code_identation + line)
|
||||
|
||||
def process_and_write_api(fout, api_tuple):
|
||||
infile_name = api_tuple[0]
|
||||
if not infile_name:
|
||||
return
|
||||
|
||||
api_title = api_tuple[1]
|
||||
api_lable = api_tuple[2]
|
||||
|
||||
fout.write(replacePlaceholder(api_header, api_title, api_lable))
|
||||
writeAPI(fout, infile_name)
|
||||
|
||||
|
||||
with open(appendix_file, 'w') as aout:
|
||||
for api_tuple in list_of_apis:
|
||||
infile_name = api_tuple[0]
|
||||
if not infile_name:
|
||||
continue
|
||||
process_and_write_api(aout, api_tuple)
|
||||
|
329
docs/manual/markdown/update_listings.py
Executable file
329
docs/manual/markdown/update_listings.py
Executable file
@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import sys, getopt
|
||||
|
||||
# Defines the names of example groups. Preserves the order in which the example groups will be parsed.
|
||||
list_of_groups = ["Hello World", "GAP", "SDP Queries", "SPP Server", "BNEP/PAN", "Low Energy", "Dual Mode"]
|
||||
|
||||
# Defines which examples belong to a group. Example is defined as [example file, example title].
|
||||
list_of_examples = {
|
||||
"Hello World" : [["led_counter"]],
|
||||
"GAP" : [["gap_inquiry"]],
|
||||
"SDP Queries" :[["sdp_general_query"],
|
||||
["sdp_bnep_query"]
|
||||
],
|
||||
"SPP Server" : [["spp_counter"],
|
||||
["spp_flowcontrol"]],
|
||||
"BNEP/PAN" : [["panu_demo"]],
|
||||
"Low Energy" : [["gatt_browser"],
|
||||
["le_counter"]],
|
||||
"Dual Mode" : [["spp_and_le_counter"]],
|
||||
}
|
||||
|
||||
lst_header = """
|
||||
"""
|
||||
|
||||
lst_ending = """
|
||||
"""
|
||||
|
||||
examples_header = """
|
||||
"""
|
||||
|
||||
example_item = """
|
||||
- [EXAMPLE_TITLE](#section:EXAMPLE_LABEL): EXAMPLE_DESC.
|
||||
"""
|
||||
example_section = """
|
||||
|
||||
## EXAMPLE_TITLE: EXAMPLE_DESC
|
||||
<a name="section:EXAMPLE_LABEL"></a>
|
||||
|
||||
"""
|
||||
example_subsection = """
|
||||
### SECTION_TITLE
|
||||
"""
|
||||
|
||||
listing_start = """
|
||||
|
||||
<a name="FILE_NAME:LISTING_LABEL"></a>
|
||||
<!-- -->
|
||||
|
||||
"""
|
||||
|
||||
listing_ending = """
|
||||
|
||||
"""
|
||||
|
||||
def replacePlaceholder(template, title, lable):
|
||||
snippet = template.replace("API_TITLE", title).replace("API_LABEL", lable)
|
||||
return snippet
|
||||
|
||||
def latexText(text, ref_prefix):
|
||||
if not text:
|
||||
return ""
|
||||
brief = text.replace(" in the BTstack manual","")
|
||||
|
||||
refs = re.match('.*(Listing\s+)(\w+).*',brief)
|
||||
if refs:
|
||||
brief = brief.replace(refs.group(1), "[code snippet below]")
|
||||
brief = brief.replace(refs.group(2), "(#"+ref_prefix+":" + refs.group(2)+")")
|
||||
|
||||
refs = re.match('.*(Section\s+)(\w+).*',brief)
|
||||
if refs:
|
||||
brief = brief.replace(refs.group(1), "[here]")
|
||||
brief = brief.replace(refs.group(2), "(#section:"+refs.group(2)+")")
|
||||
|
||||
return brief
|
||||
|
||||
def isEmptyCommentLine(line):
|
||||
return re.match('(\s*\*\s*)\n',line)
|
||||
|
||||
def isCommentLine(line):
|
||||
return re.match('(\s*\*\s*).*',line)
|
||||
|
||||
def isEndOfComment(line):
|
||||
return re.match('\s*\*/.*', line)
|
||||
|
||||
def isNewItem(line):
|
||||
return re.match('(\s*\*\s*\-\s*)(.*)',line)
|
||||
|
||||
def isTextTag(line):
|
||||
return re.match('.*(@text).*', line)
|
||||
|
||||
def isItemizeTag(line):
|
||||
return re.match("(\s+\*\s+)(-\s)(.*)", line)
|
||||
|
||||
def processTextLine(line, ref_prefix):
|
||||
if isTextTag(line):
|
||||
text_line_parts = re.match(".*(@text)(.*)", line)
|
||||
return " " + latexText(text_line_parts.group(2), ref_prefix)
|
||||
|
||||
if isItemizeTag(line):
|
||||
text_line_parts = re.match("(\s*\*\s*\-\s*)(.*)", line)
|
||||
return "\n- " + latexText(text_line_parts.group(2), ref_prefix)
|
||||
|
||||
text_line_parts = re.match("(\s+\*\s+)(.*)", line)
|
||||
if text_line_parts:
|
||||
return " " + latexText(text_line_parts.group(2), ref_prefix)
|
||||
return ""
|
||||
|
||||
def getExampleTitle(example_path):
|
||||
example_title = ''
|
||||
with open(example_path, 'rb') as fin:
|
||||
for line in fin:
|
||||
parts = re.match('.*(EXAMPLE_START)\((.*)\):\s*(.*)(\*/)?\n',line)
|
||||
if parts:
|
||||
example_title = parts.group(3).replace("_","\_")
|
||||
continue
|
||||
return example_title
|
||||
|
||||
class State:
|
||||
SearchExampleStart = 0
|
||||
SearchListingStart = 1
|
||||
SearchListingPause = 2
|
||||
SearchListingResume = 3
|
||||
SearchListingEnd = 4
|
||||
SearchItemizeEnd = 5
|
||||
ReachedExampleEnd = 6
|
||||
|
||||
text_block = ''
|
||||
itemize_block = ''
|
||||
|
||||
def writeTextBlock(aout, lstStarted):
|
||||
global text_block
|
||||
if text_block and not lstStarted:
|
||||
aout.write(text_block)
|
||||
text_block = ''
|
||||
|
||||
def writeItemizeBlock(aout, lstStarted):
|
||||
global itemize_block
|
||||
if itemize_block and not lstStarted:
|
||||
aout.write(itemize_block + "\n\n")
|
||||
itemize_block = ''
|
||||
|
||||
def writeListings(aout, infile_name, ref_prefix):
|
||||
global text_block, itemize_block
|
||||
itemText = None
|
||||
state = State.SearchExampleStart
|
||||
code_in_listing = ""
|
||||
code_identation = " "
|
||||
skip_code = 0
|
||||
|
||||
with open(infile_name, 'rb') as fin:
|
||||
for line in fin:
|
||||
if state == State.SearchExampleStart:
|
||||
parts = re.match('.*(EXAMPLE_START)\((.*)\):\s*(.*)(\*/)?\n',line)
|
||||
if parts:
|
||||
lable = parts.group(2).replace("_","")
|
||||
title = latexText(parts.group(2), ref_prefix)
|
||||
desc = latexText(parts.group(3), ref_prefix)
|
||||
aout.write(example_section.replace("EXAMPLE_TITLE", title).replace("EXAMPLE_DESC", desc).replace("EXAMPLE_LABEL", lable))
|
||||
state = State.SearchListingStart
|
||||
continue
|
||||
|
||||
# detect @section
|
||||
section_parts = re.match('.*(@section)\s*(.*)(:?\s*.?)\*?/?\n',line)
|
||||
if section_parts:
|
||||
aout.write("\n" + example_subsection.replace("SECTION_TITLE", section_parts.group(2)))
|
||||
continue
|
||||
|
||||
# detect @subsection
|
||||
subsection_parts = re.match('.*(@section)\s*(.*)(:?\s*.?)\*?/?\n',line)
|
||||
if section_parts:
|
||||
subsubsection = example_subsection.replace("SECTION_TITLE", section_parts.group(2)).replace('section', 'subsection')
|
||||
aout.write("\n" + subsubsection)
|
||||
continue
|
||||
|
||||
if isTextTag(line):
|
||||
text_block = text_block + "\n\n" + processTextLine(line, ref_prefix)
|
||||
continue
|
||||
|
||||
skip_code = 0
|
||||
lstStarted = state != State.SearchListingStart
|
||||
if text_block or itemize_block:
|
||||
if isEndOfComment(line) or isEmptyCommentLine(line):
|
||||
skip_code = 1
|
||||
if itemize_block:
|
||||
# finish itemize
|
||||
writeItemizeBlock(aout, lstStarted)
|
||||
else:
|
||||
if isEmptyCommentLine(line):
|
||||
text_block = text_block + "\n\n"
|
||||
|
||||
else:
|
||||
writeTextBlock(aout, lstStarted)
|
||||
|
||||
|
||||
else:
|
||||
if isNewItem(line) and not itemize_block:
|
||||
skip_code = 1
|
||||
# finish text, start itemize
|
||||
writeTextBlock(aout, lstStarted)
|
||||
itemize_block = "\n " + processTextLine(line, ref_prefix)
|
||||
continue
|
||||
if itemize_block:
|
||||
skip_code = 1
|
||||
itemize_block = itemize_block + processTextLine(line, ref_prefix)
|
||||
elif isCommentLine(line):
|
||||
# append text
|
||||
skip_code = 1
|
||||
text_block = text_block + processTextLine(line, ref_prefix)
|
||||
else:
|
||||
skip_code = 0
|
||||
#continue
|
||||
|
||||
if state == State.SearchListingStart:
|
||||
parts = re.match('.*(LISTING_START)\((.*)\):\s*(.*)(\s+\*/).*',line)
|
||||
|
||||
if parts:
|
||||
lst_lable = parts.group(2).replace("_","")
|
||||
lst_caption = latexText(parts.group(3), ref_prefix)
|
||||
listing = listing_start.replace("LISTING_CAPTION", lst_caption).replace("FILE_NAME", ref_prefix).replace("LISTING_LABEL", lst_lable)
|
||||
if listing:
|
||||
aout.write("\n" + listing)
|
||||
state = State.SearchListingEnd
|
||||
continue
|
||||
|
||||
if state == State.SearchListingEnd:
|
||||
parts_end = re.match('.*(LISTING_END).*',line)
|
||||
parts_pause = re.match('.*(LISTING_PAUSE).*',line)
|
||||
end_comment_parts = re.match('.*(\*/)\s*\n', line);
|
||||
|
||||
if parts_end:
|
||||
aout.write(code_in_listing)
|
||||
code_in_listing = ""
|
||||
aout.write(listing_ending)
|
||||
state = State.SearchListingStart
|
||||
writeItemizeBlock(aout, 0)
|
||||
writeTextBlock(aout, 0)
|
||||
elif parts_pause:
|
||||
code_in_listing = code_in_listing + code_identation + "...\n"
|
||||
state = State.SearchListingResume
|
||||
elif not end_comment_parts:
|
||||
# aout.write(line)
|
||||
if not skip_code:
|
||||
code_in_listing = code_in_listing + code_identation + line.replace(" ", " ")
|
||||
continue
|
||||
|
||||
if state == State.SearchListingResume:
|
||||
parts = re.match('.*(LISTING_RESUME).*',line)
|
||||
if parts:
|
||||
state = State.SearchListingEnd
|
||||
continue
|
||||
|
||||
parts = re.match('.*(EXAMPLE_END).*',line)
|
||||
if parts:
|
||||
if state != State.SearchListingStart:
|
||||
print "Formating error detected"
|
||||
writeItemizeBlock(aout, 0)
|
||||
writeTextBlock(aout, 0)
|
||||
state = State.ReachedExampleEnd
|
||||
print "Reached end of the example"
|
||||
|
||||
|
||||
# write list of examples
|
||||
def processExamples(examples_folder, examples_ofile):
|
||||
with open(examples_ofile, 'w') as aout:
|
||||
for group_title in list_of_groups:
|
||||
if not list_of_examples.has_key(group_title): continue
|
||||
examples = list_of_examples[group_title]
|
||||
for example in examples:
|
||||
example_path = examples_folder + example[0] + ".c"
|
||||
example_title = getExampleTitle(example_path)
|
||||
example.append(example_title)
|
||||
|
||||
aout.write(examples_header)
|
||||
aout.write("\n\n");
|
||||
|
||||
for group_title in list_of_groups:
|
||||
if not list_of_examples.has_key(group_title): continue
|
||||
examples = list_of_examples[group_title]
|
||||
|
||||
group_title = group_title + " example"
|
||||
if len(examples) > 1:
|
||||
group_title = group_title + "s"
|
||||
group_title = group_title + ":"
|
||||
|
||||
aout.write("- " + group_title + "\n");
|
||||
for example in examples:
|
||||
ref_prefix = example[0].replace("_", "")
|
||||
title = latexText(example[0], ref_prefix)
|
||||
desc = latexText(example[1], ref_prefix)
|
||||
aout.write(example_item.replace("EXAMPLE_TITLE", title).replace("EXAMPLE_DESC", desc).replace("EXAMPLE_LABEL", ref_prefix))
|
||||
aout.write("\n")
|
||||
aout.write("\n")
|
||||
|
||||
for group_title in list_of_groups:
|
||||
if not list_of_examples.has_key(group_title): continue
|
||||
examples = list_of_examples[group_title]
|
||||
|
||||
for example in examples:
|
||||
file_name = examples_folder + example[0] + ".c"
|
||||
writeListings(aout, file_name, example[0].replace("_",""))
|
||||
|
||||
|
||||
def main(argv):
|
||||
btstack_folder = "../../../"
|
||||
docs_folder = "docs/examples/"
|
||||
inputfolder = btstack_folder + "example/embedded/"
|
||||
outputfile = docs_folder + "generated.md"
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv,"hiso:",["ifolder=","ofile="])
|
||||
except getopt.GetoptError:
|
||||
print 'update_listings.py [-i <inputfolder>] [-o <outputfile>]'
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print 'update_listings.py [-i <inputfolder>] [-s] [-o <outputfile>]'
|
||||
sys.exit()
|
||||
elif opt in ("-i", "--ifolder"):
|
||||
inputfolder = arg
|
||||
elif opt in ("-o", "--ofile"):
|
||||
outputfile = arg
|
||||
print 'Input folder is ', inputfolder
|
||||
print 'Output file is ', outputfile
|
||||
processExamples(inputfolder, outputfile)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
Loading…
x
Reference in New Issue
Block a user