Original URL: http://southpeak.github.io/blog/2014/07/29/core-bluetoothkuang-jia-zhi-%5B%3F%5D-:centralyu-peripheral/
iOS and Mac apps use the core Bluetooth framework to communicate with BLE (low power Bluetooth) devices. Our programs can discover, search, and communicate with low-power peripheral (peripheral) Bluetooth devices such as heartbeat monitors, digital thermostats, and even other iOS devices. This framework abstracts the basic operation of the Bluetooth 4.0 standard low power device, hides the 4.0 standard underlying implementation details, so that we can easily use the BLE device.
Roles in Bluetooth communication
In BLE communication, there are two main roles: Central and peripheral. Similar to the traditional client-server architecture, a peripheral side is the one that provides the data (equivalent to the server), while Central is the party that uses the data provided by the peripheral to accomplish a specific task (equivalent to the client). For example, the heartbeat listener shows a schema like this:
The peripheral side broadcasts some data in the form of AD packets. An ad package (advertising packet) is a small bundle of related data that may contain useful information peripheral provides, such as peripheral name or key functionality. Under BLE, advertising is the main form of peripheral device performance.
The central side can scan and listen for any broadcast information that it is interested in peripheral devices.
The broadcast and reception of data need to be represented by a certain data structure. And service is such a data structure. The peripheral end may contain one or more services or provide useful information about the strength of the connection signal. A service is a collection of data for a device and a data-related operation.
The service itself is made up of features or services included. A feature provides more detailed information about the service. Shows the various data structures in the heart rate listener
After a successful connection between the central and peripheral ends, central can discover the complete set of services and features provided by the peripheral side. A central can also read and write the value of the service attribute on the peripheral side. We will describe it in detail below.
Representation of Central, peripherals, and peripheral data
When we interact with the peripheral side using local central, we perform operations on the central side of the BLE communication. Unless we set up a local peripheral device, most Bluetooth interactions are done on the central side. (the basic operation of peripheral is also described below)
On the central side, the local central device is represented by a Cbcentralmanager object. This object is used to manage operations that discover and connect peripheral devices (Cbperipheral objects), including scanning, lookups, and connections. Local central end and peripheral objects
When interacting with peripheral devices, we are primarily dealing with its services and features. In the core Bluetooth framework, the service is a Cbservice object, which is a Cbcharacteristic object that demonstrates the basic structure of the services and features of the Central side:
After OS X 10.9 and iOS 6, Apple provides a ble peripheral (peripheral) feature that can be processed as a peripheral. On the peripheral side, the local peripheral device is represented as a Cbperipheralmanager object. These objects are used to manage the publishing of services and features to the local peripheral device database, and advertise these services to central devices. The peripheral manager is also used to respond to read and write requests from the central side. As shown in a peripheral-side role:
When setting up data on a local peripheral device, we are actually dealing with a variable version of the service and features. In the core Bluetooth framework, the local peripheral service is represented by the Cbmutableservice object, and the attribute is represented by the Cbmutablecharacteristic object. Shows the basic structure of local peripheral services and features:
Peripheral (Server)-side operations
A peripheral-side operation mainly has the following steps:
- Start a peripheral Management object
- Setting up services and features in local peripheral
- Publishing services and features to a local database of devices
- Advertise Our Services
- Responds to read and write requests from the central side of the connection
- Send updated attribute value to the central end of the subscription
We'll explain each step in combination with the code below
Start a peripheral manager
To implement a peripheral side on a local device, we need to allocate and initialize a peripheral manager instance, as shown in the following code
// 创建一个Peripheral管理器// 我们将当前类作为peripheralManager,因此必须实现CBPeripheralManagerDelegate// 第二个参数如果指定为nil,则默认使用主队列peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
After the peripheral manager is created, the peripheral manager invokes the Peripheralmanagerdidupdatestate: Method of the proxy object. We need to implement this method to ensure that the local device supports BLE.
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{ NSLog(@"Peripheral Manager Did Update State"); switch (peripheral.state) { case CBPeripheralManagerStatePoweredOn: NSLog(@"CBPeripheralManagerStatePoweredOn"); break; case CBPeripheralManagerStatePoweredOff: NSLog(@"CBPeripheralManagerStatePoweredOff"); break; case CBPeripheralManagerStateUnsupported: NSLog(@"CBPeripheralManagerStateUnsupported"); break; default: break; }}
Setting up services and features
A local peripheral database organizes services and features in a tree-like structure. So, when setting up services and features, we organize them into tree structures.
A peripheral service and feature is identified by a 128-bit Bluetooth-specified UUID, which is a Cbuuid object. Although the SIG organization does not pre-define all of the services and features of the UUID, the SIG has defined and released some of the passed UUID, which is simplified to 16-bit for ease of use. For example, the SIG defines a 16-bit UUID as the identity of the Heartbeat Service (180D).
The Cbuuid class provides methods to generate a Cbuuid object from a string. When the note string is using a predefined 16-bit UUID, the Core Bluetooth uses it to pre-complete the 128-bit identity in advance.
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString:@"180D"];
Of course we can also generate a 128-bit UUID ourselves to identify our services and features. Using the Uuidgen command at the command line generates a 128-bit UUID string, which we can then use to generate a Cbuuid object.
After generating the UUID object, we can use this object to create our services and features, and then organize them into a tree-like structure.
The code for creating the attribute is as follows
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:@"C22D1ECA-0F78-463B-8C21-688A517D7D2B"];CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:@"632FB3C9-2078-419B-83AA-DBC64B5B685A"];CBMutableCharacteristic *character1 = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID1 properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];CBMutableCharacteristic *character2 = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID2 properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteable];
We need to set attributes, values, and permissions for attributes. The attribute and permission values determine whether the attribute value is readable or writable, and the central side of the connection can subscribe to the value of the attribute. In addition, if we specify the value of the attribute, the value is cached and its properties and permissions are set to readable. A value of nil must be specified if we want the value of the attribute to be writable, or if the value can be modified in the lifetime of the service to which the attribute is expected to belong.
After creating the attribute, we can create a service related to the attribute and then associate the attribute to the service, as shown in the following code:
CBUUID *serviceUUID = [CBUUID UUIDWithString:@"3655296F-96CE-44D4-912D-CD83F06E7E7E"];CBMutableService *service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];service.characteristics = @[character1, character2]; // 组织成树状结构
The primary parameter in the previous example passed yes, indicating that this is a primary service that describes the primary functionality of a device and can be referenced by other services. The opposite is the secondary service (secondary services), which describes a service only in the context of another service that references it.
Publishing services and Features
Create services and attributes after you have organized them into a tree structure, we need to publish these services on the device's local database. We can use Cbperipheralmanager's AddService: method to do this work. As shown in the following code:
[peripheralManager addService:service];
When a method is called to publish a service, the Cbperipheralmanager object calls its proxy's peripheralManager:didAddService:error: method. If an error occurs during the publishing process that prevents the fabric from being used, you can implement the Proxy method to handle the error, as shown in the following code:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{ NSLog(@"Add Service"); if (error) { NSLog(@"Error publishing service: %@", [error localizedDescription]); }}
After the service and features are published to the device database, the service will be cached and we can no longer modify the service.
Advertising Services
After processing the above steps, we can advertise these services to the central side of the service that is of interest. We can do this by invoking the Startadvertising: method of the Cbperipheralmanager instance, as shown in the following code:
[peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey: @[service.UUID]}];
Startadvertising: The argument is a dictionary, Peripheral Manager supports and supports only two key values: Cbadvertisementdatalocalnamekey and Cbadvertisementdataserviceuuidskey. These two values describe the details of the data. The value expected for the key is an array representing multiple services.
When advertising services, the Cbperipheralmanager object invokes the Peripheralmanagerdidstartadvertising:error: Method of the Code object, and we can do the appropriate processing here, as shown in the following code:
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{ NSLog(@"Start Advertising"); if (error) { NSLog(@"Error advertising: %@", [error localizedDescription]); }}
After the ad service, the central side can discover the device and initialize a connection.
Responds to read and write requests on the central side
After connecting to the central end, you may need to receive read-write requests from them, and we need to respond in the appropriate manner.
When the central side of the connection requests the value of the read attribute, the Cbperipheralmanager object invokes the proxy object's Peripheralmanager:didreceivereadrequest: method, The proxy method provides a Cbattrequest object to represent the central side of the request, and we can use its properties to populate the request. The following code shows a simple procedure:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{ // 查看请求的特性是否是指定的特性 if ([request.characteristic.UUID isEqual:cha1.UUID]) { NSLog(@"Request character 1"); // 确保读请求所请求的偏移量没有超出我们的特性的值的长度范围 // offset属性指定的请求所要读取值的偏移位置 if (request.offset > cha1.value.length) { [peripheralManager respondToRequest:request withResult:CBATTErrorInvalidOffset]; return; } // 如果读取位置未越界,则将特性中的值的指定范围赋给请求的value属性。 request.value = [cha1.value subdataWithRange:(NSRange){request.offset, cha1.value.length - request.offset}]; // 对请求作出成功响应 [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess]; }}
Call the Respondtorequest:withresult: method to respond to the request each time the proxy object's peripheralmanager:didreceivereadrequest: is called.
Processing a write request is similar to the above procedure, where the peripheralmanager:didreceivewriterequests: method of the proxy object is called. The difference is that the proxy method will give us an array containing one or more Cbattrequest objects, each representing a write request. We can use the Value property of the Request object to assign a value to our attribute property, as shown in the following code:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{ CBATTRequest *request = requests[0]; cha1.value = request.value; [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];}
Response processing is similar to a request.
Sends the updated attribute value to the central side of the subscription
If one or more of the central ports subscribe to the features of our service, we need to notify these central ports when the features change. To do this, the proxy object needs to implement the PeripheralManager:central:didSubscribeToCharacteristic: method. As shown below:
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{ NSLog(@"Central subscribed to characteristic %@", characteristic); NSData *updatedData = characteristic.value; // 获取属性更新的值并调用以下方法将其发送到Central端 // 最后一个参数指定我们想将修改发送给哪个Central端,如果传nil,则会发送给所有连接的Central // 将方法返回一个BOOL值,表示修改是否被成功发送,如果用于传送更新值的队列被填充满,则方法返回NO BOOL didSendValue = [peripheralManager updateValue:updatedData forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:nil]; NSLog(@"Send Success ? %@", (didSendValue ? @"YES" : @"NO"));}
In the preceding code, when the transport queue has space available, the Cbperipheralmanager object invokes the Peripheralmanagerisreadytoupdatesubscribers: Method of the Code object. We can call UpdateValue:forCharacteristic:onSubscribedCentrals in this method: to resend the value.
We use notifications to packets individual data to central for the subscription. When we update central for a subscription, we should place the entire updated value in a notification by calling the UpdateValue:forCharacteristic:onSubscribedCentrals: method.
Because the value of the attribute varies in size, not all values are notified for transmission. If this happens, you need to call the Readvalueforcharacteristic: method of the Cbperipheral instance on the central side to handle it, which can get the entire value.
Central (Client) side operations
A central side mainly includes the following operations:
- Start a central Manager object
- Search for and connect to peripheral devices that are advertising
- Querying data after connecting to the peripheral end
- Sends a read-write request to an attribute value to the peripheral
- Receive notifications when the peripheral-side attribute value changes
We'll explain each step in combination with the code below
Start a central manager
The Cbcentralmanager object represents a local central device in core Bluetooth, and we must allocate and initialize a central Manager object when performing any ble interaction. The creation code looks like this:
// 指定当前类为代理对象,所以其需要实现CBCentralManagerDelegate协议// 如果queue为nil,则Central管理器使用主队列来发送事件centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
When you create the central manager, the manager object invokes the Centralmanagerdidupdatestate: Method of the proxy object. We need to implement this method to ensure that the local device supports BLE.
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{ NSLog(@"Central Update State"); switch (central.state) { case CBCentralManagerStatePoweredOn: NSLog(@"CBCentralManagerStatePoweredOn"); break; case CBCentralManagerStatePoweredOff: NSLog(@"CBCentralManagerStatePoweredOff"); break; case CBCentralManagerStateUnsupported: NSLog(@"CBCentralManagerStateUnsupported"); break; default: break; }}
Discover the peripheral device being advertised
The primary task of the central side is to discover the peripheral device being advertised for subsequent connections. We can invoke the Scanforperipheralswithservices:options: method of the Cbcentralmanager instance to discover the peripheral device that is advertising. As shown in the following code:
// 查找Peripheral设备// 如果第一个参数传递nil,则管理器会返回所有发现的Peripheral设备。// 通常我们会指定一个UUID对象的数组,来查找特定的设备[centralManager scanForPeripheralsWithServices:nil options:nil];
After calling the above method, the Cbcentralmanager object invokes the CentralManager:didDiscoverPeripheral:advertisementData:RSSI: Method of the proxy object each time the device is discovered.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ NSLog(@"Discover name : %@", peripheral.name); // 当我们查找到Peripheral端时,我们可以停止查找其它设备,以节省电量 [centralManager stopScan]; NSLog(@"Scanning stop");}
Connecting peripheral devices
After finding the peripheral device, we can call the Connectperipheral:options: method of the Cbcentralmanager instance to connect the peripheral device. As shown in the following code
[centralManager connectPeripheral:peripheral options:nil];
If the connection succeeds, the Centralmanager:didconnectperipheral: Method of the Code object is called, and we can implement the method to handle it accordingly. In addition, before we start interacting with the peripheral device, we need to set the proxy for the peripheral object to ensure that the appropriate callback is received.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@"Peripheral Connected"); peripheral.delegate = self;}
Find services for connected peripheral devices
Once the connection to the peripheral device is established, we can start querying the data. First we need to find the services available in the peripheral device. Because peripheral devices can advertise limited data, the actual service of the peripheral device may be more than the service it advertises. We can call the peripheral object's Discoverservices: method to find all the services. As shown in the following code:
[peripheral discoverServices:nil];
Parameter passing nil can find all the services, but in general we will specify the services of interest.
When the above method is called, peripheral invokes the Peripheral:diddiscoverservices: Method of the proxy object. Core Bluetooth creates an array of Cbservice objects, and the elements in the array are the services found in peripheral.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ NSLog(@"Discover Service"); for (CBService *service in peripheral.services) { NSLog(@"Discovered service %@", service); }}
Find attributes in a service
Assuming that we have found the service of interest, the next step is to query the features in the service. In order to find the attributes in the service, we only need to call the Discovercharacteristics:forservice: Method of the Cbperipheral class as follows:
NSLog(@"Discovering characteristics for service %@", service);[peripheral discoverCharacteristics:nil forService:service];
When an attribute of a particular service is discovered, the peripheral object invokes the Peripheral:didDiscoverCharacteristicsForService:error: Method of the proxy object. In this method, Core Bluetooth creates an array of Cbcharacteristic objects, each of which represents a found attribute object. As shown in the following code:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ NSLog(@"Discover Characteristics"); for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"Discovered characteristic %@", characteristic); }}
Get the value of an attribute
An attribute contains a single value that contains information about the peripheral service. After acquiring the attribute, we can get the value from the attribute. You only need to invoke the Readvalueforcharacteristic: method of the Cbperipheral instance. As shown below:
NSLog(@"Reading value for characteristic %@", characteristic);[peripheral readValueForCharacteristic:characteristic];
When we read the value in the attribute, the peripheral object invokes the Peripheral:didUpdateValueForCharacteristic:error: Method of the proxy object to get the value. If it succeeds, we can access it through the attribute's Value property, as follows:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ NSData *data = characteristic.value; NSLog(@"Data = %@", data);}
The value of the subscription attribute
Although using the Readvalueforcharacteristic: method to read an attribute value is very effective for some usage scenarios, it is less effective for getting the changed value. For most change values, we need to get them through a subscription. When we subscribe to the value of the attribute, we receive a notification from the peripheral object when the value changes.
We can call the Cbperipheral class's setnotifyvalue:forcharacteristic: method to subscribe to the values of the attributes of interest. As shown below:
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
When we try to subscribe to the value of the attribute, the Peripheral:didUpdateNotificationStateForCharacteristic:error: Method of the proxy object of the peripheral object is called. If the subscription fails, we can implement the Proxy method to access the error as follows:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ ... if (error) { NSLog(@"Error changing notification state: %@", [error localizedDescription]); }}
When the value of the attribute is successfully subscribed, the peripheral device notifies our app when the attribute value changes.
The value of the Write attribute
In some scenarios, we need to write the value of the attribute. For example, when we need to interact with a BLE digital thermostat, we may need to provide a value for the thermostat to set the temperature of the room. If the value of the attribute is writable, we can write the value by calling the WriteValue:forCharacteristic:type: method of the Cbperipheral instance.
NSData *data = [NSData dataWithBytes:[@"test" UTF8String] length:@"test".length];[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
When attempting to write an attribute value, we need to specify the type of write you want to perform. The previous example specifies that the write type is cbcharacteristicwritewithresponse, which means that peripheral let our app know if the write was successful.
Specifies that a peripheral object of type Cbcharacteristicwritewithresponse is written, and the peripheral:didwritevalueforcharacteristic of the proxy object is invoked in response to the request: Error: Method. If the write fails, we can handle the error message in this method.
Summary
The Core Bluetooth framework has encapsulated the underlying implementation of Bluetooth communication, and we only need to do simple processing to implement Bluetooth-based communication in the program. In the game, however, you typically use the Bluetooth processing feature that comes with the game kit to enable communication of large data volumes. The Core Bluetooth framework is also more suitable for small data traffic.
"Go" Core Bluetooth Framework One: Central and peripheral