mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-29 13:20:39 +00:00
manual: include autogenerated examples
This commit is contained in:
parent
4bcb3e9249
commit
e078b98954
@ -434,618 +434,7 @@ Separate packet handlers can be used for each L2CAP service and outgoing connect
|
||||
\newcommand{\BluetoothSpecificationURL}{\href{https://www.bluetooth.org/Technical/Specifications/adopted.htm}{\color{blue} Bluetooth Specification}}
|
||||
|
||||
\input{protocols_profiles}
|
||||
|
||||
\pagebreak
|
||||
\section{Examples}
|
||||
|
||||
\label{examples}
|
||||
The \path{MSP-EXP430F5438-CC256x} folder in BTstack repository currently includes the following examples for the MSP430F5438 Experimenter Board:
|
||||
\begin{itemize}
|
||||
\item UART example:
|
||||
\begin{itemize}
|
||||
\item \emph{led\_counter}: provides UART and timer interrupt without Bluetooth.
|
||||
\end{itemize}
|
||||
\item GAP example:
|
||||
\begin{itemize}
|
||||
\item \emph{gap\_inquiry}: uses GAP to discover surrounding Bluetooth devices and then requests their remote name.
|
||||
\end{itemize}
|
||||
\item SPP Server examples :
|
||||
\begin{itemize}
|
||||
\item \emph{spp\_counter}: provides a virtual serial port via SPP and a periodic timer over RFCOMM.
|
||||
\item \emph{spp\_accel}: provides a virtual serial port via SPP. On connect, it sends the current accelerometer values as fast as possible.
|
||||
\item \emph{spp\_flowcontrol}: provides a virtual serial port via SPP with manual RFCOMM credit management. Delayed processing of received data is simulated with the help of a periodic timer.
|
||||
\end{itemize}
|
||||
% \item SPP Client example: requires an SDP client to query SPP RFCOMM channel number. It is not yet provided by BTstack. Outgoing RFCOMM connections are fully supported otherwise.
|
||||
\item HID Host example:
|
||||
\begin{itemize}
|
||||
\item \emph{hid\_demo}: on start, the device does a device discovery and connects to the first Bluetooth keyboard it finds, pairs, and allows to type on the little LCD screen.
|
||||
% \item \emph{mouse}: on start, the device does a device discovery and connects to the first Bluetooth keyboard it finds, paris, and allows move cursor on the screen. It is not yet provided by BTstack, but it can be created by combining existing parts.
|
||||
\end{itemize}
|
||||
%\item HID Device examples:
|
||||
% \begin{itemize}
|
||||
% \item \emph{keyboard}: a keyboard device is simulated. Pressing the two buttons on the EXP430 board are mapped to Space and Enter. It is not yet provided by BTstack. %-- version for different board exists, needs to handle button IRQs
|
||||
% \end{itemize}
|
||||
\item Low Energy example:
|
||||
\begin{itemize}
|
||||
\item \emph{ble\_server}: provides a ready-to-run example for a test Peripheral device. It assumes that a PAN1323 or 1326 module with a CC2564 chipset is used.
|
||||
\item \emph{gatt\_browser}: shows how to use the GATT Client API to discover primary services and their characteristics.
|
||||
\end{itemize}
|
||||
\item Dual mode example:
|
||||
\begin{itemize}
|
||||
\item \emph{spp\_and\_ble\_counter}: \todo{TODO}
|
||||
\end{itemize}
|
||||
|
||||
\end{itemize}
|
||||
|
||||
In all examples the debug UART port is configured at 57600 bps speed.
|
||||
|
||||
\subsection{led\_counter: UART and timer interrupt without Bluetooth}
|
||||
The example demonstrates how to setup hardware, initialize BTstack without Bluetooth, provide a periodic timer to toggle an LED and print number of toggles as a minimal BTstack test.
|
||||
|
||||
\subsubsection{Periodic Timer Setup}
|
||||
$ $
|
||||
\begin{lstlisting}[float, caption=Periodic counter, label=LEDToggler]
|
||||
void heartbeat_handler(timer_source_t *ts){
|
||||
// increment counter
|
||||
char lineBuffer[30];
|
||||
sprintf(lineBuffer, "BTstack counter %04u\n\r", ++counter);
|
||||
printf(lineBuffer);
|
||||
|
||||
// toggle LED
|
||||
LED_PORT_OUT = LED_PORT_OUT ^ LED_2;
|
||||
|
||||
// re-register timer
|
||||
run_loop_register_timer(ts, HEARTBEAT_PERIOD_MS);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
As timers in BTstack are single shot, the periodic counter is implemented by re-registering the \emph{timer\_source} in the \emph{heartbeat\_handler} callback function. The general setup is shown in Listing \ref{PeriodicTimerHandler}. Listing \ref{LEDToggler} shows \emph{heartbeat\_handler} adapted to periodically toggle an LED and print number of toggles.
|
||||
|
||||
|
||||
\subsubsection{Turn On and Go}
|
||||
|
||||
Listing \ref{RunLoopExecution} shows how to setup and start the run loop. For hardware and BTstack setup, please check the source code.
|
||||
|
||||
\begin{lstlisting}[float, caption= Run loop execution., label=RunLoopExecution]
|
||||
void timer_setup(){
|
||||
// set one-shot timer
|
||||
heartbeat.process = &timer_handler;
|
||||
run_loop_register_timer(&heartbeat, HEARTBEAT_PERIOD_MS);
|
||||
}
|
||||
|
||||
int main(void){
|
||||
hw_setup();
|
||||
btstack_setup();
|
||||
timer_setup();
|
||||
|
||||
// go!
|
||||
run_loop_execute();
|
||||
|
||||
return 0;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\subsection{gap\_inquiry: GAP Inquiry Example}
|
||||
\label{example:GapInquiry}
|
||||
|
||||
The Generic Access Profile (GAP) defines how Bluetooth devices discover and establish a connection with each other. In this example, the application discovers surrounding Bluetooth devices and collects their Class of Device (CoD), page scan mode, clock offset, and RSSI. After that, the remote name of each device is requested. In the following section we outline the Bluetooth logic part, i.e., how the packet handler handles the asynchronous events and data packets.
|
||||
|
||||
\subsubsection{Bluetooth Logic}
|
||||
The Bluetooth logic is implemented as a state machine within the packet handler. In this example, the following states are passed sequentially: INIT, W4\_INQUIRY\_MODE\_COMPLETE, and ACTIVE.
|
||||
|
||||
In INIT, the application enables the extended inquiry mode, which includes RSSI values, and transits to W4\_INQUIRY\_MODE\_COMPLETE state.
|
||||
|
||||
In W4\_INQUIRY\_MODE\_COMPLETE, after the inquiry mode was set, an inquiry scan is started, and the application transits to ACTIVE state.
|
||||
|
||||
IN ACTIVE, the following events are processed:
|
||||
\begin{itemize}
|
||||
\item Inquiry result event: the list of discovered devices is processed and the Class of Device (CoD), page scan mode, clock offset, and RSSI are stored in a table.
|
||||
\item Inquiry complete event: the remote name is requested for devices without a fetched name. The state of a remote name can be one of the following: REMOTE\_NAME\_REQUEST, REMOTE\_NAME\_INQUIRED, or REMOTE\_NAME\_FETCHED.
|
||||
\item Remote name cached event: prints cached remote names provided by BTstack - if persistent storage is provided.
|
||||
\item Remote name request complete event: the remote name is stored in the table and the state is updated to REMOTE\_NAME\_FETCHED. The query of remote names is continued.
|
||||
\end{itemize}
|
||||
|
||||
For more details please check Section \ref{section:DiscoverDevices} and the source code.
|
||||
|
||||
%*************************************
|
||||
|
||||
\begin{lstlisting}[float, caption=SPP service setup, label=SPPSetup]
|
||||
void btstack_setup(void){
|
||||
btstack_memory_init();
|
||||
run_loop_init(RUN_LOOP_EMBEDDED);
|
||||
|
||||
// init HCI
|
||||
hci_transport_t * transport = hci_transport_h4_dma_instance();
|
||||
bt_control_t * control = bt_control_cc256x_instance();
|
||||
hci_uart_config_t * config = hci_uart_config_cc256x_instance();
|
||||
remote_device_db_t * remote_db = (remote_device_db_t *) &remote_device_db_memory;
|
||||
hci_init(transport, config, control, remote_db);
|
||||
hci_register_packet_handler(packet_handler);
|
||||
|
||||
// init L2CAP
|
||||
l2cap_init();
|
||||
l2cap_register_packet_handler(packet_handler);
|
||||
|
||||
// init RFCOMM
|
||||
rfcomm_init();
|
||||
rfcomm_register_packet_handler(packet_handler);
|
||||
rfcomm_register_service_internal(NULL, rfcomm_channel_nr, 100);
|
||||
|
||||
// init SDP, create record for SPP and register with SDP
|
||||
sdp_init();
|
||||
memset(spp_service_buffer, 0, sizeof(spp_service_buffer));
|
||||
service_record_item_t * service_record_item = (service_record_item_t *) spp_service_buffer;
|
||||
sdp_create_spp_service( (uint8_t*) &service_record_item->service_record, 1, "SPP Counter");
|
||||
sdp_register_service_internal(NULL, service_record_item);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection {spp\_counter: SPP Server - Heartbeat Counter over RFCOMM}
|
||||
\label{section:sppcounter}
|
||||
The Serial port profile (SPP) is widely used as it provides a serial port over Bluetooth. The SPP counter example demonstrates how to setup an SPP service, and provide a periodic timer over RFCOMM.
|
||||
|
||||
\subsubsection{SPP Service Setup}
|
||||
|
||||
SPP is based on RFCOMM, a Bluetooth protocol that emulates RS-232 serial ports. To access an RFCOMM serial port on a remote device, a client has to query its Service Discovery Protocol (SDP) server. The SDP response for an SPP service contains the RFCOMM channel number. To provide an SPP service, you need to initialize memory (Section \ref{section:memory_configuration}) and the run loop (Section \ref{section:run_loop}), setup HCI (Section \ref{section:btstack_initialization}) and L2CAP, then register an RFCOMM service and provide its RFCOMM channel number as part of the Protocol List attribute of the SDP record . Example code for SPP service setup is provided in Listing \ref{SPPSetup}. The SDP record created by $sdp\_create\_spp\_service$ consists of a basic SPP definition that uses provided RFCOMM channel ID and service name. For more details, please have a look at it in \path{include/btstack/sdp_util.c}. The SDP record is created on the fly in RAM and is deterministic. To preserve valuable RAM, the result can be stored as constant data inside the ROM.
|
||||
|
||||
|
||||
\subsubsection{Periodic Timer Setup}
|
||||
|
||||
The heartbeat handler increases the real counter every second, as shown in Listing \ref{PeriodicCounter}. The general setup is shown in Listing \ref{PeriodicTimerHandler}.
|
||||
|
||||
\begin{lstlisting}[float, caption=Periodic counter, label=PeriodicCounter]
|
||||
#define HEARTBEAT_PERIOD_MS 1000
|
||||
|
||||
void theartbeat_handler(timer_source_t *ts){
|
||||
real_counter++;
|
||||
// re-register timer
|
||||
run_loop_register_timer(ts, HEARTBEAT_PERIOD_MS);
|
||||
}
|
||||
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Bluetooth logic}
|
||||
The Bluetooth logic is implemented as a state machine within the packet handler, see Listing \ref{SppServerPacketHandler}. In this example, the following states are passed sequentially: INIT, W4\_CONNECTION, W4\_CHANNEL\_COMPLETE, and ACTIVE.
|
||||
|
||||
\begin{lstlisting}[float, caption=SPP Server - Heartbeat Counter over RFCOMM., label=SppServerPacketHandler]
|
||||
void prepareData(void){
|
||||
counter_to_send++;
|
||||
}
|
||||
|
||||
void tryToSend(void){
|
||||
// see Quick Recipe 5.4, Listing 8
|
||||
}
|
||||
|
||||
void packet_handler (uint8_t packet_type, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
switch(state){
|
||||
case INIT:
|
||||
if (packet[2] == HCI_STATE_WORKING) {
|
||||
state = W4_CONNECTION;
|
||||
}
|
||||
break;
|
||||
|
||||
case W4_CONNECTION:
|
||||
switch (event) {
|
||||
case HCI_EVENT_PIN_CODE_REQUEST:
|
||||
// see Quick Recipe 5.7
|
||||
break;
|
||||
case RFCOMM_EVENT_INCOMING_CONNECTION:
|
||||
// see Quick Recipe 5.11
|
||||
state = W4_CHANNEL_COMPLETE;
|
||||
break;
|
||||
}
|
||||
|
||||
case W4_CHANNEL_COMPLETE:
|
||||
if (event != RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE) break;
|
||||
// see Quick Recipe 5.11
|
||||
// state: W4_CONNECTION on failure, otherwise ACTIVE
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
// see Quick Recipe 5.4, Listing 9
|
||||
// state: W4_CONNECTION on channel closed
|
||||
break;
|
||||
...
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
In INIT, upon successful startup of BTstack, the local Bluetooth name is set, and the state machine transits to W4\_CONNECTION.
|
||||
|
||||
The W4\_CONNECTION state handles authentication and accepts incoming RFCOMM connections. It uses a fixed PIN code "0000" for authentication. An incoming RFCOMM connection is accepted, and the state machine progresses to W4\_CHANNEL\_COMPLETE. More logic is need, if you want to handle connections from multiple clients. The incoming RFCOMM connection event contains the RFCOMM channel number used during the SPP setup phase and the newly assigned RFCOMM channel ID that is used by all BTstack commands and events.
|
||||
|
||||
In W4\_CHANNEL\_COMPLETE state, an error in the channel establishment fails (rare case, e.g., client crashes), the application returns to the W4\_CONNE-CTION state. On successful connection, the RFCOMM channel ID and MTU for this channel are made available to the heartbeat counter and the state machine transits to ACTIVE.
|
||||
|
||||
While in the ACTIVE state, the communication between client and the application takes place. In this example, the timer handler increases the real counter every second. The packet handler tries to send this information when an RFCOMM credit (RFCOMM\_EVENT\_CREDITS) or an HCI packet sent event (DAEMON\_EVENT\_HCI\_PACKET\_SENT) are received. These two events represent two orthogonal mechanisms that deal with flow control. A packet can only be sent when an RFCOMM credit is available and the internal BTstack outgoing packet buffer is free.
|
||||
|
||||
|
||||
%*************************************
|
||||
\subsection{spp\_accel: SPP Server - Accelerator Values}
|
||||
In this example, the server tries to send the current accelerometer values. It does not use a periodic timer, instead, it sends the data as fast as possible.
|
||||
|
||||
|
||||
%*************************************
|
||||
\subsection {spp\_flowcontrol: SPP Server - Flow Control}
|
||||
\label{example:spp_flow_control}
|
||||
This example adds explicit flow control for incoming RFCOMM data to the SPP heartbeat counter example. We will highlight the changes compared to the SPP counter example.
|
||||
|
||||
\subsubsection{SPP Service Setup}
|
||||
|
||||
Listing \ref{explicitFlowControl} shows how to provide one initial credit during RFCOMM service initialization. Please note that providing a single credit effectively reduces the credit-based (sliding window) flow control to a stop-and-wait flow control that limits the data throughput substantially.
|
||||
|
||||
\begin{lstlisting}[float, caption= Heartbeat handler with manual credit management. , label=hbhManual]
|
||||
void heartbeat_handler(struct timer *ts){
|
||||
if (rfcomm_send_credit){
|
||||
rfcomm_grant_credits(rfcomm_channel_id, 1);
|
||||
rfcomm_send_credit = 0;
|
||||
}
|
||||
run_loop_register_timer(ts, HEARTBEAT_PERIOD_MS);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}[float, caption= Packet handler with manual credit management. , label=phManual]
|
||||
void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
...
|
||||
if (packet_type == RFCOMM_DATA_PACKET){
|
||||
packet[size] = 0;
|
||||
puts( (const char *) packet);
|
||||
rfcomm_send_credit = 1;
|
||||
return;
|
||||
}
|
||||
...
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\subsubsection{Periodic Timer Setup}
|
||||
|
||||
Explicit credit management is recommended when received RFCOMM data cannot be processed immediately. In this example, delayed processing of received data is simulated with the help of a periodic timer as follows. When the packet handler receives a data packet, it does not provide a new credit, it sets a flag instead. If the flag is set, a new credit will be granted by the heartbeat handler, introducing a delay of up to 1 second. The heartbeat handler code is shown in Listing \ref{hbhManual}. The general setup is shown in Listing \ref{PeriodicTimerHandler}.
|
||||
|
||||
%*************************************
|
||||
|
||||
\subsection {ble\_server: LE Peripheral}
|
||||
\label{example:ble_browser}
|
||||
|
||||
This example shows how to create an LE peripheral. The peripheral can be discovered by other devices and provides a GATT Server. The GATT server allows to discover the primary services, and read and write their characteristics. The BTstack setup code is written for a PAN1323 or 1326 module with a CC2564 chipset.
|
||||
|
||||
\begin{lstlisting}[float, caption=ATT Database ., label=code:lePeripheralDatabase]
|
||||
PRIMARY_SERVICE, GAP_SERVICE
|
||||
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "BTstack LE Peripheral"
|
||||
CHARACTERISTIC, GAP_APPEARANCE, READ, 00 00
|
||||
|
||||
PRIMARY_SERVICE, GATT_SERVICE
|
||||
CHARACTERISTIC, GATT_SERVICE_CHANGED, READ,
|
||||
|
||||
PRIMARY_SERVICE, FFF0
|
||||
CHARACTERISTIC, FFF1, READ | WRITE | DYNAMIC,
|
||||
CHARACTERISTIC, FFF2, READ | WRITE | DYNAMIC,
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
|
||||
\begin{lstlisting}[float, caption= Setting up LE peripheral., label=code:lePeripheralSetup]
|
||||
void setup(void){
|
||||
...
|
||||
// set up l2cap_le
|
||||
l2cap_init();
|
||||
|
||||
// setup le device db
|
||||
le_device_db_init();
|
||||
|
||||
// setup SM: Display only
|
||||
sm_init();
|
||||
sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
|
||||
sm_set_authentication_requirements( SM_AUTHREQ_BONDING | SM_AUTHREQ_MITM_PROTECTION);
|
||||
|
||||
// setup ATT server
|
||||
att_server_init(profile_data, NULL, att_write_callback);
|
||||
att_server_register_packet_handler(app_packet_handler);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\begin{lstlisting}[float, caption= Read callback ., label=code:lePeripheralReadCallback]
|
||||
uint16_t chr01_value_length = 0;
|
||||
uint16_t max_chr01_value_length = 40;
|
||||
char chr01_value[max_chr01_value_length];
|
||||
char chr02_value = 0;
|
||||
|
||||
uint16_t get_read_att_value_len(uint16_t att_handle);
|
||||
uint16_t get_write_att_value_len(uint16_t att_handle);
|
||||
uint16_t get_bytes_to_copy(uint16_t value_len, uint16_t offset);
|
||||
|
||||
uint16_t att_read_callback(uint16_t con_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
printf("READ Callback, handle %04x\n", att_handle);
|
||||
uint16_t value_len = get_att_read_value_len(att_handle);
|
||||
if (!buffer) return value_len;
|
||||
|
||||
uint16_t bytes_to_copy = get_bytes_to_copy(value_len, offset);
|
||||
if (!bytes_to_copy) return 0;
|
||||
|
||||
switch(att_handle){
|
||||
case ATT_CHARACTERISTIC_FFF1_01_HANDLE:
|
||||
memcpy(buffer, &chr01_value[offset], bytes_to_copy);
|
||||
break;
|
||||
case ATT_CHARACTERISTIC_FFF2_01_VALUE_HANDLE:
|
||||
buffer[offset] = chr02_value;
|
||||
break;
|
||||
}
|
||||
return bytes_to_copy;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}[float, caption= Write callback ., label=code:lePeripheralWriteCallback]
|
||||
int att_write_callback(uint16_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
|
||||
printf("WRITE Callback, handle %04x\n", att_handle);
|
||||
|
||||
uint16_t value_len = get_write_att_value_len(att_handle);
|
||||
uint16_t bytes_to_copy = get_bytes_to_copy(value_len, offset);
|
||||
if (!bytes_to_copy) return ATT_ERROR_INVALID_OFFSET;
|
||||
|
||||
switch(att_handle){
|
||||
case ATT_CHARACTERISTIC_FFF1_01_HANDLE:
|
||||
buffer[buffer_size] = 0;
|
||||
memcpy(&chr01_value[offset], buffer, bytes_to_copy);
|
||||
chr01_value_length = bytes_to_copy + offset;
|
||||
|
||||
printf("New text: %s\n", buffer);
|
||||
overwriteLine(7, (char*)buffer);
|
||||
break;
|
||||
case ATT_CHARACTERISTIC_FFF2_01_VALUE_HANDLE:
|
||||
printf("New value: %u\n", buffer[offset]);
|
||||
if (buffer[offset])
|
||||
LED_PORT_OUT |= LED_2;
|
||||
} else {
|
||||
LED_PORT_OUT &= ~LED_2;
|
||||
}
|
||||
chr02_value = buffer[offset];
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}[float, caption= Write callback ., label=code:lePeripheralATTPacketHandler]
|
||||
static void app_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
if (packet_type != HCI_EVENT_PACKET) return;
|
||||
bd_addr_t addr;
|
||||
uint8_t adv_data[] = { 02, 01, 05, 03, 02, 0xf0, 0xff };
|
||||
switch (packet[0]) {
|
||||
case BTSTACK_EVENT_STATE:
|
||||
// bt stack activated, get started - set local name
|
||||
if (packet[2] == HCI_STATE_WORKING) {
|
||||
printf("Working!\n");
|
||||
hci_send_cmd(&hci_le_set_advertising_data, sizeof(adv_data), adv_data);
|
||||
}
|
||||
break;
|
||||
case BTSTACK_EVENT_NR_CONNECTIONS_CHANGED:
|
||||
if (packet[2]) {
|
||||
overwriteLine(4, "CONNECTED");
|
||||
} else {
|
||||
overwriteLine(4, "NOT CONNECTED");
|
||||
}
|
||||
break;
|
||||
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
||||
// restart advertising
|
||||
hci_send_cmd(&hci_le_set_advertise_enable, 1);
|
||||
break;
|
||||
case HCI_EVENT_COMMAND_COMPLETE:
|
||||
if (COMMAND_COMPLETE_EVENT(packet, hci_read_bd_addr)){
|
||||
bt_flip_addr(addr, &packet[6]);
|
||||
printf("BD ADDR: %s\n", bd_addr_to_str(addr));
|
||||
break;
|
||||
}
|
||||
if(COMMAND_COMPLETE_EVENT(packet, hci_le_set_advertising_data)){
|
||||
hci_send_cmd(&hci_le_set_scan_response_data,10,adv_data);
|
||||
break;
|
||||
}
|
||||
if(COMMAND_COMPLETE_EVENT(packet, hci_le_set_scan_response_data)){
|
||||
hci_send_cmd(&hci_le_set_advertise_enable, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
%*************************************
|
||||
\subsection {gatt\_browser: GATT Client - Discovering primary services and their characteristics}
|
||||
\label{example:gatt_browser}
|
||||
This example shows how to use the GATT Client API to discover primary services and their characteristics of the first found device that is advertising its services.
|
||||
|
||||
The logic is divided between the HCI and GATT client packet handlers. The HCI packet handler with its state machine is responsible for finding and connecting to a remote device, and for starting the first GATT client query. Then, the GATT client packet handler receives all primary services and requests the characteristics of the last one to keep the example short.
|
||||
|
||||
\subsubsection{Setting up GATT client}
|
||||
In setup phase, a GATT client must register the HCI and GATT client packet handlers, as shown in Listing \ref{code:gattClientSetup}. Additionally, the security manager can be setup, if signed writes, or encrypted or authenticated connection, are required to access the characteristics, as explained in Section \ref{subsection:smp}.
|
||||
|
||||
\begin{lstlisting}[float, caption= Setting up GATT client., label=code:gattClientSetup]
|
||||
typedef enum {
|
||||
IDLE,
|
||||
W4_SCAN_RESULT,
|
||||
W4_CONNECT,
|
||||
W4_SERVICE_RESULT,
|
||||
W4_CHARACTERISTIC_RESULT,
|
||||
W4_DISCONNECT
|
||||
} gc_state_t;
|
||||
|
||||
gc_state_t state = TC_IDLE;
|
||||
uint16_t gc_id;
|
||||
|
||||
// Handles connect, disconnect, and advertising report events,
|
||||
// starts the GATT client, and sends the first query.
|
||||
void handle_hci_event(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
// Handles GATT client query results, sends queries and the
|
||||
// GAP disconnect command when the querying is done.
|
||||
void handle_gatt_client_event(le_event_t * event);
|
||||
|
||||
void setup(void){
|
||||
...
|
||||
// Initialize L2CAP and register HCI event handler
|
||||
l2cap_init();
|
||||
l2cap_register_packet_handler(&handle_hci_event);
|
||||
|
||||
// Initialize GATT client and register handler for GATT client
|
||||
// events
|
||||
gatt_client_init();
|
||||
gc_id = gatt_client_register_packet_handler(&handle_gatt_client_event);
|
||||
|
||||
// Optionally, setup security manager for signed writes or
|
||||
// encrypted or authenticated connection
|
||||
sm_init();
|
||||
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Packet handlers}
|
||||
The GATT browser goes sequentially through the states: IDLE, W4\_SCAN\_RESULT, W4\_CONNECT, W4\_SERVICE\_RESULT,
|
||||
\\ W4\_CHARACTERISTIC\_RESULT, and W4\_DISCONNECT.
|
||||
|
||||
The W4\_SERVICE\_RESULT and W4\_CHARACTERISTIC\_RESULT states compose the state machine of the GATT client packet handler, as it reacts on GATT client events. The other states compose the state machine of the HCI packet handler.
|
||||
|
||||
In detail, the HCI packet handler has to start the scanning, to find the first advertising device, to stop scanning, to connect to and later to disconnect from it, to start the gatt client upon the connection is completed, and to send the first query - in this case the \emph{gatt\_client\_discover\_primary\_services} query is called, see Listing \ref{code:gattBrowserHCIPacketHandler}. A convenience function for filling advertising report struct from data packet is shown in Listing \ref{code:gattBrowserAdvReport}.
|
||||
|
||||
Query results and further queries are handled by the gatt client packet handler, as shown in Listing \ref{code:gattBrowserQueryHandler}. Here, upon receiving the primary services, the \emph{gatt\_client\_discover\_characteristics\_for\_service} query for the last received service is sent. After receiving the characteristics for the service, the \emph{gap\_disconnect} is called to terminate the connection. Upon disconnect, the HCI packet handler receives the disconnect complete event, and has to call \emph{gatt\_client\_stop} function to remove the disconnected device from the list of active GATT clients.
|
||||
|
||||
\begin{lstlisting}[float, caption= Advertising report handling., label=code:gattBrowserHCIPacketHandler]
|
||||
void handle_hci_event(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
if (packet_type != HCI_EVENT_PACKET) return;
|
||||
advertising_report_t report;
|
||||
|
||||
uint8_t event = packet[0];
|
||||
switch (event) {
|
||||
case BTSTACK_EVENT_STATE:
|
||||
// BTstack activated, get started
|
||||
if (packet[2] != HCI_STATE_WORKING) break;
|
||||
if (cmdline_addr_found){
|
||||
printf("Trying to connect to %s\n", bd_addr_to_str(cmdline_addr));
|
||||
state = TC_W4_CONNECT;
|
||||
le_central_connect(cmdline_addr, 0);
|
||||
break;
|
||||
}
|
||||
printf("BTstack activated, start scanning!\n");
|
||||
state = TC_W4_SCAN_RESULT;
|
||||
le_central_set_scan_parameters(0,0x0030, 0x0030);
|
||||
le_central_start_scan();
|
||||
break;
|
||||
case GAP_LE_ADVERTISING_REPORT:
|
||||
if (state != TC_W4_SCAN_RESULT) return;
|
||||
fill_advertising_report_from_packet(&report, packet);
|
||||
// stop scanning, and connect to the device
|
||||
state = TC_W4_CONNECT;
|
||||
le_central_stop_scan();
|
||||
le_central_connect(report.address,report.address_type);
|
||||
break;
|
||||
case HCI_EVENT_LE_META:
|
||||
// wait for connection complete
|
||||
if (packet[2] != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break;
|
||||
if (state != TC_W4_CONNECT) return;
|
||||
gc_handle = READ_BT_16(packet, 4);
|
||||
// query primary services
|
||||
state = TC_W4_SERVICE_RESULT;
|
||||
gatt_client_discover_primary_services(gc_id, gc_handle);
|
||||
break;
|
||||
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
||||
printf("DISCONNECTED\n");
|
||||
exit(0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}[float, caption=Convenience function for filling advertising report struct from data packet., label=code:gattBrowserAdvReport]
|
||||
typedef struct advertising_report {
|
||||
uint8_t type;
|
||||
uint8_t event_type;
|
||||
uint8_t address_type;
|
||||
bd_addr_t address;
|
||||
uint8_t rssi;
|
||||
uint8_t length;
|
||||
uint8_t * data;
|
||||
} advertising_report_t;
|
||||
|
||||
void fill_advertising_report_from_packet(advertising_report_t * report, uint8_t *packet){
|
||||
int pos = 2;
|
||||
report->event_type = packet[pos++];
|
||||
report->address_type = packet[pos++];
|
||||
memcpy(report->address, &packet[pos], 6);
|
||||
pos += 6;
|
||||
report->rssi = packet[pos++];
|
||||
report->length = packet[pos++];
|
||||
report->data = &packet[pos];
|
||||
pos += report->length;
|
||||
dump_advertising_report(report);
|
||||
|
||||
bd_addr_t found_device_addr;
|
||||
memcpy(found_device_addr, report->address, 6);
|
||||
swapX(found_device_addr, report->address, 6);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}[float, caption=GATT client queries handling., label=code:gattBrowserQueryHandler]
|
||||
void handle_gatt_client_event(le_event_t * event){
|
||||
le_service_t service;
|
||||
le_characteristic_t characteristic;
|
||||
switch(state){
|
||||
case TC_W4_SERVICE_RESULT:
|
||||
switch(event->type){
|
||||
case GATT_SERVICE_QUERY_RESULT:
|
||||
service = ((le_service_event_t *) event)->service;
|
||||
dump_service(&service);
|
||||
services[service_count++] = service;
|
||||
break;
|
||||
case GATT_QUERY_COMPLETE:
|
||||
state = TC_W4_CHARACTERISTIC_RESULT;
|
||||
service_index = 0;
|
||||
printf("\nCHARACTERISTIC for SERVICE ");
|
||||
printUUID128(service.uuid128); printf("\n");
|
||||
|
||||
gatt_client_discover_characteristics_for_service(gc_id, gc_handle, &services[service_index]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case TC_W4_CHARACTERISTIC_RESULT:
|
||||
switch(event->type){
|
||||
case GATT_CHARACTERISTIC_QUERY_RESULT:
|
||||
characteristic = ((le_characteristic_event_t *) event)->characteristic;
|
||||
dump_characteristic(&characteristic);
|
||||
break;
|
||||
case GATT_QUERY_COMPLETE:
|
||||
if (service_index < service_count) {
|
||||
state = TC_W4_CHARACTERISTIC_RESULT;
|
||||
service = services[service_index++];
|
||||
printf("\nCHARACTERISTIC for SERVICE ");
|
||||
printUUID128(service.uuid128);
|
||||
printf(", [0x%04x-0x%04x]\n", service.start_group_handle, service.end_group_handle);
|
||||
gatt_client_discover_characteristics_for_service(gc_id, gc_handle, &service);
|
||||
break;
|
||||
}
|
||||
state = TC_W4_DISCONNECT;
|
||||
service_index = 0;
|
||||
gap_disconnect(gc_handle);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\include{example_spp_le_counter}
|
||||
|
||||
\subsection{sdp\_bnep\_query}
|
||||
\label{subsection:panudemo}
|
||||
\input{examples}
|
||||
|
||||
% \section{Platforms}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import sys
|
||||
|
||||
docs_folder = ""
|
||||
appendix_file = docs_folder + "examples.tex"
|
||||
stand_alone_doc = 0
|
||||
|
||||
lst_header = """
|
||||
\\begin{lstlisting}
|
||||
@ -14,7 +15,7 @@ lst_ending = """
|
||||
\end{lstlisting}
|
||||
"""
|
||||
|
||||
example_header = """
|
||||
document_begin = """
|
||||
\documentclass[11pt, oneside]{article}
|
||||
\usepackage{geometry}
|
||||
\geometry{letterpaper}
|
||||
@ -25,13 +26,17 @@ example_header = """
|
||||
\usepackage{listings}
|
||||
\usepackage{hyperref}
|
||||
\\begin{document}
|
||||
\section{Examples}
|
||||
"""
|
||||
|
||||
example_ending = """
|
||||
document_end = """
|
||||
\end{document}
|
||||
"""
|
||||
|
||||
examples_header = """
|
||||
% !TEX root = btstack_gettingstarted.tex
|
||||
\section{Examples}
|
||||
"""
|
||||
|
||||
example_item = """
|
||||
\item \emph{EXAMPLE_TITLE}: EXAMPLE_DESC, see Section \\ref{example:EXAMPLE_LABLE}.
|
||||
"""
|
||||
@ -45,6 +50,7 @@ example_subsection = """
|
||||
"""
|
||||
|
||||
listing_start = """
|
||||
$ $
|
||||
\\begin{lstlisting}[caption= LISTING_CAPTION., label=listing:LISTING_LABLE]
|
||||
"""
|
||||
|
||||
@ -55,20 +61,19 @@ msp_folder = "../../platforms/msp-exp430f5438-cc2564b/example/"
|
||||
embedded_folder = "../../example/embedded/"
|
||||
# Example group title: [folder, example file, section title]
|
||||
list_of_examples = {
|
||||
#"UART" : [[msp_folder, "led_counter", "UART and timer interrupt without Bluetooth"]],
|
||||
#"GAP" : [[embedded_folder, "gap_inquiry", "GAP Inquiry Example"]],
|
||||
"SPP Server" : [[embedded_folder, "spp_counter", "SPP Server - Heartbeat Counter over RFCOMM"]]
|
||||
# [embedded_folder, "spp_accel", "SPP Server - Accelerator Values"],
|
||||
# [embedded_folder, "spp_flowcontrol", "SPP Server - Flow Control"]],
|
||||
#"HID Host" :[[embedded_folder, "hid_demo", "HID Demo"]],
|
||||
#"Low Energy" :[[embedded_folder, "gatt_browser", "GATT Client - Discovering primary services and their characteristics"],
|
||||
# [embedded_folder, "ble_server", "LE Peripheral"]],
|
||||
#"Dual Mode " :[[embedded_folder, "spp_and_le_counter", "Dual mode example"]],
|
||||
#"SDP BNEP Query" :[[embedded_folder, "sdp_bnep_query", "SDP BNEP Query"]],
|
||||
"UART" : [[embedded_folder, "led_counter", "UART and timer interrupt without Bluetooth"]],
|
||||
"GAP" : [[embedded_folder, "gap_inquiry", "GAP Inquiry Example"]],
|
||||
"SPP Server" : [[embedded_folder, "spp_counter", "SPP Server - Heartbeat Counter over RFCOMM"],
|
||||
[embedded_folder, "spp_flowcontrol", "SPP Server - Flow Control"]],
|
||||
"Low Energy" :[[embedded_folder, "gatt_browser", "GATT Client - Discovering primary services and their characteristics"],
|
||||
[embedded_folder, "ble_peripheral", "LE Peripheral"]],
|
||||
"Dual Mode " :[[embedded_folder, "spp_and_le_counter", "Dual mode example"]],
|
||||
"SDP BNEP Query" :[[embedded_folder, "sdp_bnep_query", "SDP BNEP Query"]]
|
||||
}
|
||||
|
||||
class State:
|
||||
SearchExampleStart = 0
|
||||
SearchBlockEnd = 1
|
||||
SearchListingStart = 2
|
||||
SearchListingPause = 4
|
||||
SearchListingResume = 5
|
||||
@ -108,9 +113,24 @@ def writeListings(fout, infile_name):
|
||||
title = latexText(parts.group(2))
|
||||
desc = latexText(parts.group(3))
|
||||
aout.write(example_section.replace("EXAMPLE_TITLE", title).replace("EXAMPLE_DESC", desc).replace("EXAMPLE_LABLE", lable))
|
||||
state = State.SearchListingStart
|
||||
state = State.SearchBlockEnd
|
||||
continue
|
||||
if state == State.SearchBlockEnd:
|
||||
comment_end = re.match('(.*)\s(\*/)\n',line)
|
||||
comment_continue = re.match('(\s?\*\s+)(.*)',line)
|
||||
brief_start = re.match('.*(@text)\s*(.*)',line)
|
||||
|
||||
if brief_start:
|
||||
brief_part = "\n\n" + latexText(brief_start.group(2))
|
||||
briefs_in_listings = briefs_in_listings + brief_part
|
||||
state = State.SearchBlockEnd
|
||||
continue
|
||||
|
||||
if comment_end:
|
||||
state = State.SearchListingStart
|
||||
elif comment_continue:
|
||||
aout.write(latexText(comment_continue.group(2)))
|
||||
continue
|
||||
|
||||
# detect @section
|
||||
section_parts = re.match('.*(@section)\s*(.*)\s*(\*?/?)\n',line)
|
||||
if section_parts:
|
||||
@ -129,6 +149,7 @@ def writeListings(fout, infile_name):
|
||||
if brief_start:
|
||||
brief_part = "\n\n" + latexText(brief_start.group(2))
|
||||
briefs_in_listings = briefs_in_listings + brief_part
|
||||
state = State.SearchBlockEnd
|
||||
continue
|
||||
|
||||
# detect subsequent items
|
||||
@ -216,8 +237,10 @@ def writeListings(fout, infile_name):
|
||||
|
||||
# write list of examples
|
||||
with open(appendix_file, 'w') as aout:
|
||||
aout.write(example_header)
|
||||
aout.write("\\begin{itemize}\n");
|
||||
if stand_alone_doc:
|
||||
aout.write(document_begin)
|
||||
aout.write(examples_header)
|
||||
aout.write("\n \\begin{itemize}\n");
|
||||
|
||||
for group_title, examples in list_of_examples.iteritems():
|
||||
group_title = group_title + " example"
|
||||
@ -240,5 +263,6 @@ with open(appendix_file, 'w') as aout:
|
||||
file_name = example[0] + example[1] + ".c"
|
||||
writeListings(aout, file_name)
|
||||
|
||||
aout.write(example_ending)
|
||||
if stand_alone_doc:
|
||||
aout.write(document_end)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user