GattClient
GattClient 定义了与远程 GattServer 交互所需的过程。
发现程序
GattServer 托管一组固定的服务。这些服务是可以被发现,读取或写入的特征的逻辑组合,并且可以将它们的状态广播到连接的客户端。这些特征还可能包含元信息和命名的特征描述符。特征描述符可以指示用于特征值的单位,以文本形式描述特征目的或允许客户端注册特征值的更新通知。
在与服务器特性进行任何交互之前,GattClient 会发现服务器上存在的服务和特征的布局。
特征描述符的布局也可以作为额外发现步骤发布。
属性操作
作为发现过程的结果,客户端可以开始与发现的特征进行交互。根据特征属性(在发现期间获取),客户端可以读取或写入给定特征的值。
Mbed BLE 抽象读取和写入操作,以提供可用于读取或写入特征值的单个 API。如果要传输的属性值不适合单个数据包,则应用程序代码不必处理必要的碎片或重组过程。
服务器发起事件
当服务器更新特征值时,它可以将新值转发给任何已注册的客户端。客户可以根据每个特征注册这些更新。服务器使用通知(没有来自客户端的确认)或指示(客户确认收到)发送更新。此机制通过避免轮询来最小化客户端和服务器之间的事务数。
客户端通过设置客户端特性配置描述符(CCCD)值来注册这些更新。这是一个属性,客户端需要发现它的描述符。如果设置了通知或指示属性,则它存在于特征中。
GattClient 类参考
公共类型 | |
enum | WriteOp_t { GATT_OP_WRITE_REQ = 0x01, GATT_OP_WRITE_CMD = 0x02, GATT_OP_SIGNED_WRITE_CMD = 0x03 } |
typedef FunctionPointerWithContext< const GattReadCallbackParams * > | ReadCallback_t |
typedef CallChainOfFunctionPointersWithContext< const GattReadCallbackParams * > | ReadCallbackChain_t |
typedef FunctionPointerWithContext< const GattWriteCallbackParams * > | WriteCallback_t |
typedef CallChainOfFunctionPointersWithContext< const GattWriteCallbackParams * > | WriteCallbackChain_t |
typedef FunctionPointerWithContext< const GattHVXCallbackParams * > | HVXCallback_t |
typedef CallChainOfFunctionPointersWithContext< const GattHVXCallbackParams * > | HVXCallbackChain_t |
typedef FunctionPointerWithContext< const GattClient * > | GattClientShutdownCallback_t |
typedef CallChainOfFunctionPointersWithContext< const GattClient * > | GattClientShutdownCallbackChain_t |
受保护的属性 | |
ReadCallbackChain_t | onDataReadCallbackChain |
WriteCallbackChain_t | onDataWriteCallbackChain |
HVXCallbackChain_t | onHVXCallbackChain |
GattClientShutdownCallbackChain_t | shutdownCallChain |
GattClient 示例
/* mbed Microcontroller Library
* Copyright (c) 2006-2017 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include <new>
#include <stdio.h>
#include "events/EventQueue.h"
#include "platform/NonCopyable.h"
#include "ble/BLE.h"
#include "ble/Gap.h"
#include "ble/GattClient.h"
#include "ble/GapAdvertisingParams.h"
#include "ble/GapAdvertisingData.h"
#include "ble/GattClient.h"
#include "ble/DiscoveredService.h"
#include "ble/DiscoveredCharacteristic.h"
#include "ble/CharacteristicDescriptorDiscovery.h"
#include "BLEProcess.h"
/**
* Handle discovery of the GATT server.
*
* First the GATT server is discovered in its entirety then each readable
* characteristic is read and the client register to characteristic
* notifications or indication when available. The client report server
* indications and notification until the connection end.
*/
class GattClientProcess : private mbed::NonCopyable<GattClientProcess> {
// Internal typedef to this class type.
// It is used as a shorthand to pass member function as callbacks.
typedef GattClientProcess Self;
typedef CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t
DiscoveryCallbackParams_t;
typedef CharacteristicDescriptorDiscovery::TerminationCallbackParams_t
TerminationCallbackParams_t;
typedef DiscoveredCharacteristic::Properties_t Properties_t;
public:
/**
* Construct an empty client process.
*
* The function start() shall be called to initiate the discovery process.
*/
GattClientProcess() :
_client(NULL),
_connection_handle(),
_characteristics(NULL),
_it(NULL),
_descriptor_handle(0),
_ble_interface(NULL),
_event_queue(NULL) {
}
~GattClientProcess()
{
stop();
}
void init(BLE &ble_interface, events::EventQueue &event_queue)
{
_ble_interface = &ble_interface;
_event_queue = &event_queue;
_client = &_ble_interface->gattClient();
_ble_interface->gap().onConnection(as_cb(&Self::on_connection));
_ble_interface->gap().onDisconnection(as_cb(&Self::on_disconnection));
}
/**
* Event handler invoked when a connection is established.
*
* This function setup the connection handle to operate on then start the
* discovery process.
*/
void on_connection(const Gap::ConnectionCallbackParams_t* connection_event)
{
_connection_handle = connection_event->handle;
_event_queue->call(mbed::callback(this, &Self::start));
}
/**
* Start the discovery process.
*
* @param[in] client The GattClient instance which will discover the distant
* GATT server.
* @param[in] connection_handle Reference of the connection to the GATT
* server which will be discovered.
*/
void start()
{
// setup the event handlers called during the process
_client->onDataWritten().add(as_cb(&Self::when_descriptor_written));
_client->onHVX().add(as_cb(&Self::when_characteristic_changed));
// The discovery process will invoke when_service_discovered when a
// service is discovered, when_characteristic_discovered when a
// characteristic is discovered and when_service_discovery_ends once the
// discovery process has ended.
_client->onServiceDiscoveryTermination(as_cb(&Self::when_service_discovery_ends));
ble_error_t error = _client->launchServiceDiscovery(
_connection_handle,
as_cb(&Self::when_service_discovered),
as_cb(&Self::when_characteristic_discovered)
);
if (error) {
printf("Error %u returned by _client->launchServiceDiscovery.\r\n", error);
return;
}
printf("Client process started: initiate service discovery.\r\n");
}
/**
* Stop the discovery process and clean the instance.
*/
void on_disconnection(const Gap::DisconnectionCallbackParams_t* disconnection_event)
{
if (!_client || disconnection_event->handle != _connection_handle) {
return;
}
stop();
}
/**
* Stop the discovery process and clean the instance.
*/
void stop()
{
if (!_client) {
return;
}
// unregister event handlers
_client->onDataWritten().detach(as_cb(&Self::when_descriptor_written));
_client->onHVX().detach(as_cb(&Self::when_characteristic_changed));
_client->onServiceDiscoveryTermination(NULL);
// remove discovered characteristics
clear_characteristics();
// clean up the instance
_connection_handle = 0;
_characteristics = NULL;
_it = NULL;
_descriptor_handle = 0;
printf("Client process stopped.\r\n");
}
private:
// Service and characteristic discovery process.
/**
* Handle services discovered.
*
* The GattClient invokes this function when a service has been discovered.
*
* @see GattClient::launchServiceDiscovery
*/
void when_service_discovered(const DiscoveredService *discovered_service)
{
// print information of the service discovered
printf("Service discovered: value = ");
print_uuid(discovered_service->getUUID());
printf(", start = %u, end = %u.\r\n",
discovered_service->getStartHandle(),
discovered_service->getEndHandle()
);
}
/**
* Handle characteristics discovered.
*
* The GattClient invoke this function when a characteristic has been
* discovered.
*
* @see GattClient::launchServiceDiscovery
*/
void when_characteristic_discovered(const DiscoveredCharacteristic *discovered_characteristic)
{
// print characteristics properties
printf("\tCharacteristic discovered: uuid = ");
print_uuid(discovered_characteristic->getUUID());
printf(", properties = ");
print_properties(discovered_characteristic->getProperties());
printf(
", decl handle = %u, value handle = %u, last handle = %u.\r\n",
discovered_characteristic->getDeclHandle(),
discovered_characteristic->getValueHandle(),
discovered_characteristic->getLastHandle()
);
// add the characteristic into the list of discovered characteristics
bool success = add_characteristic(discovered_characteristic);
if (!success) {
printf("Error: memory allocation failure while adding the discovered characteristic.\r\n");
_client->terminateServiceDiscovery();
stop();
return;
}
}
/**
* Handle termination of the service and characteristic discovery process.
*
* The GattClient invokes this function when the service and characteristic
* discovery process ends.
*
* @see GattClient::onServiceDiscoveryTermination
*/
void when_service_discovery_ends(Gap::Handle_t connection_handle)
{
if (!_characteristics) {
printf("No characteristics discovered, end of the process.\r\n");
return;
}
printf("All services and characteristics discovered, process them.\r\n");
// reset iterator and start processing characteristics in order
_it = NULL;
_event_queue->call(mbed::callback(this, &Self::process_next_characteristic));
}
// Processing of characteristics based on their properties.
/**
* Process the characteristics discovered.
*
* - If the characteristic is readable then read its value and print it. Then
* - If the characteristic can emit notification or indication then discover
* the characteristic CCCD and subscribe to the server initiated event.
* - Otherwise skip the characteristic processing.
*/
void process_next_characteristic(void)
{
if (!_it) {
_it = _characteristics;
} else {
_it = _it->next;
}
while (_it) {
Properties_t properties = _it->value.getProperties();
if (properties.read()) {
read_characteristic(_it->value);
return;
} else if(properties.notify() || properties.indicate()) {
discover_descriptors(_it->value);
return;
} else {
printf(
"Skip processing of characteristic %u\r\n",
_it->value.getValueHandle()
);
_it = _it->next;
}
}
printf("All characteristics discovered have been processed.\r\n");
}
/**
* Initate the read of the characteristic in input.
*
* The completion of the operation will happens in when_characteristic_read()
*/
void read_characteristic(const DiscoveredCharacteristic &characteristic)
{
printf("Initiating read at %u.\r\n", characteristic.getValueHandle());
ble_error_t error = characteristic.read(
0, as_cb(&Self::when_characteristic_read)
);
if (error) {
printf(
"Error: cannot initiate read at %u due to %u\r\n",
characteristic.getValueHandle(), error
);
stop();
}
}
/**
* Handle the reception of a read response.
*
* If the characteristic can emit notification or indication then start the
* discovery of the the characteristic descriptors then subscribe to the
* server initiated event by writing the CCCD discovered. Otherwise start
* the processing of the next characteristic discovered in the server.
*/
void when_characteristic_read(const GattReadCallbackParams *read_event)
{
printf("\tCharacteristic value at %u equal to: ", read_event->handle);
for (size_t i = 0; i < read_event->len; ++i) {
printf("0x%02X ", read_event->data[i]);
}
printf(".\r\n");
Properties_t properties = _it->value.getProperties();
if(properties.notify() || properties.indicate()) {
discover_descriptors(_it->value);
} else {
process_next_characteristic();
}
}
/**
* Initiate the discovery of the descriptors of the characteristic in input.
*
* When a descriptor is discovered, the function when_descriptor_discovered
* is invoked.
*/
void discover_descriptors(const DiscoveredCharacteristic &characteristic)
{
printf("Initiating descriptor discovery of %u.\r\n", characteristic.getValueHandle());
_descriptor_handle = 0;
ble_error_t error = characteristic.discoverDescriptors(
as_cb(&Self::when_descriptor_discovered),
as_cb(&Self::when_descriptor_discovery_ends)
);
if (error) {
printf(
"Error: cannot initiate discovery of %04X due to %u.\r\n",
characteristic.getValueHandle(), error
);
stop();
}
}
/**
* Handle the discovery of the characteristic descriptors.
*
* If the descriptor found is a CCCD then stop the discovery. Once the
* process has ended subscribe to server initiated events by writing the
* value of the CCCD.
*/
void when_descriptor_discovered(const DiscoveryCallbackParams_t* event)
{
printf("\tDescriptor discovered at %u, UUID: ", event->descriptor.getAttributeHandle());
print_uuid(event->descriptor.getUUID());
printf(".\r\n");
if (event->descriptor.getUUID() == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG) {
_descriptor_handle = event->descriptor.getAttributeHandle();
_client->terminateCharacteristicDescriptorDiscovery(
event->characteristic
);
}
}
/**
* If a CCCD has been found subscribe to server initiated events by writing
* its value.
*/
void when_descriptor_discovery_ends(const TerminationCallbackParams_t *event) {
// shall never happen but happen with android devices ...
// process the next charateristic
if (!_descriptor_handle) {
printf("\tWarning: characteristic with notify or indicate attribute without CCCD.\r\n");
process_next_characteristic();
return;
}
Properties_t properties = _it->value.getProperties();
uint16_t cccd_value =
(properties.notify() << 0) | (properties.indicate() << 1);
ble_error_t error = _client->write(
GattClient::GATT_OP_WRITE_REQ,
_connection_handle,
_descriptor_handle,
sizeof(cccd_value),
reinterpret_cast<uint8_t*>(&cccd_value)
);
if (error) {
printf(
"Error: cannot initiate write of CCCD %u due to %u.\r\n",
_descriptor_handle, error
);
stop();
}
}
/**
* Called when the CCCD has been written.
*/
void when_descriptor_written(const GattWriteCallbackParams* event)
{
// should never happen
if (!_descriptor_handle) {
printf("\tError: received write response to unsolicited request.\r\n");
stop();
return;
}
printf("\tCCCD at %u written.\r\n", _descriptor_handle);
_descriptor_handle = 0;
process_next_characteristic();
}
/**
* Print the updated value of the characteristic.
*
* This function is called when the server emits a notification or an
* indication of a characteristic value the client has subscribed to.
*
* @see GattClient::onHVX()
*/
void when_characteristic_changed(const GattHVXCallbackParams* event)
{
printf("Change on attribute %u: new value = ", event->handle);
for (size_t i = 0; i < event->len; ++i) {
printf("0x%02X ", event->data[i]);
}
printf(".\r\n");
}
struct DiscoveredCharacteristicNode {
DiscoveredCharacteristicNode(const DiscoveredCharacteristic &c) :
value(c), next(NULL) { }
DiscoveredCharacteristic value;
DiscoveredCharacteristicNode *next;
};
/**
* Add a discovered characteristic into the list of discovered characteristics.
*/
bool add_characteristic(const DiscoveredCharacteristic *characteristic)
{
DiscoveredCharacteristicNode* new_node =
new(std::nothrow) DiscoveredCharacteristicNode(*characteristic);
if (new_node == false) {
printf("Error while allocating a new characteristic.\r\n");
return false;
}
if (_characteristics == NULL) {
_characteristics = new_node;
} else {
DiscoveredCharacteristicNode* c = _characteristics;
while(c->next) {
c = c->next;
}
c->next = new_node;
}
return true;
}
/**
* Clear the list of discovered characteristics.
*/
void clear_characteristics(void)
{
DiscoveredCharacteristicNode *c= _characteristics;
while (c) {
DiscoveredCharacteristicNode *n = c->next;
delete c;
c = n;
}
}
/**
* Helper to construct an event handler from a member function of this
* instance.
*/
template<typename ContextType>
FunctionPointerWithContext<ContextType> as_cb(
void (Self::*member)(ContextType context)
) {
return makeFunctionPointer(this, member);
}
/**
* Print the value of a UUID.
*/
static void print_uuid(const UUID &uuid)
{
const uint8_t *uuid_value = uuid.getBaseUUID();
// UUIDs are in little endian, print them in big endian
for (size_t i = 0; i < uuid.getLen(); ++i) {
printf("%02X", uuid_value[(uuid.getLen() - 1) - i]);
}
}
/**
* Print the value of a characteristic properties.
*/
static void print_properties(const Properties_t &properties)
{
const struct {
bool (Properties_t::*fn)() const;
const char* str;
} prop_to_str[] = {
{ &Properties_t::broadcast, "broadcast" },
{ &Properties_t::read, "read" },
{ &Properties_t::writeWoResp, "writeWoResp" },
{ &Properties_t::write, "write" },
{ &Properties_t::notify, "notify" },
{ &Properties_t::indicate, "indicate" },
{ &Properties_t::authSignedWrite, "authSignedWrite" }
};
printf("[");
for (size_t i = 0; i < (sizeof(prop_to_str) / sizeof(prop_to_str[0])); ++i) {
if ((properties.*(prop_to_str[i].fn))()) {
printf(" %s", prop_to_str[i].str);
}
}
printf(" ]");
}
GattClient *_client;
Gap::Handle_t _connection_handle;
DiscoveredCharacteristicNode *_characteristics;
DiscoveredCharacteristicNode *_it;
GattAttribute::Handle_t _descriptor_handle;
BLE *_ble_interface;
events::EventQueue *_event_queue;
};
int main() {
BLE &ble_interface = BLE::Instance();
events::EventQueue event_queue;
BLEProcess ble_process(event_queue, ble_interface);
GattClientProcess gatt_client_process;
// Register GattClientProcess::init in the ble_process; this function will
// be called once the ble_interface is initialized.
ble_process.on_init(
mbed::callback(&gatt_client_process, &GattClientProcess::init)
);
// bind the event queue to the ble interface, initialize the interface
// and start advertising
ble_process.start();
// Process the event queue.
event_queue.dispatch_forever();
return 0;
}