Peripheral Manager
Acting as a Peripheral
In the last section, you learned how to perform the most common types of Bluetooth low energy tasks from the central side. Here we will go through how to perform the common Bluetooth low energy tasks from the peripheral side. The code-based examples that follow will assist you in developing your app to implement the peripheral role on your local device. Specifically, you will learn how to:
- Set Up Services and Characteristics
- Publish Services and Characteristics
- Advertise Services
- Read and Write Requests
- Update Subscribed Centrals
Set Up Services and Characteristics
The first step is to create an array of Service
's, each with an array of available
Characteristic
's. You can consider a Service
as a group of data values or
Characteristic
s. Each Service
and Characteristic
is uniquely defined by a UUID
.
Though not all UUIDs that identify a service or characteristic are predefined by the Bluetooth Special Interest Group (SIG), Bluetooth SIG has defined and published a number of commonly used UUIDs that have been shortened to 16-bits for convenience. For example, Bluetooth SIG has predefined the 16-bit UUID that identifies a heart rate service as 180D. This UUID is shortened from its equivalent 128-bit UUID, 0000180D-0000-1000-8000-00805F9B34FB, which is based on the Bluetooth base UUID that is defined in the Bluetooth 4.0 specification, Volume 3, Part F, Section 3.2.1. When you use a predefined 16-bit UUID, the extension prefills the rest of 128-bit UUID with the Bluetooth base UUID
You may have services and characteristics that are not identified by predefined Bluetooth UUIDs. If you do, you need to generate your own 128-bit UUIDs to identify them.
Use the command-line utility uuidgen to easily generate 128-bit UUIDs. To get started, open a window in Terminal. Next, for each service and characteristic that you need to identify with a UUID, type uuidgen on the command line to receive a unique 128-bit value in the form of an ASCII string that is punctuated by hyphens, as in the following example:
$ uuidgen
90753043-6E40-4590-AFF6-5B48F6412E2F
You can then use this UUID to create your own Service and Characteristic objects.
var characteristic:Characteristic = new Characteristic( "E95E787B-9C88-4A8D-9A33-BA63EBC4A3A3" );
var service:Service = new Service();
service.uuid = "90753043-6E40-4590-AFF6-5B48F6412E2F";
service.characteristics.push( characteristic );
Publish Services and Characteristics
Once you have created your Service
's and Characteristic
's you will then need to
publish these services to add them to the device's database of services and
characteristics. This task involves calling the addService
function of the peripheralManager
.
At this time it is also important to note the removeAllServices
function which can
be used to ensure that you don't add duplicate services at each launch of your application.
var characteristic:Characteristic = new Characteristic( "E95E787B-9C88-4A8D-9A33-BA63EBC4A3A3" );
var service:Service = new Service();
service.uuid = "90753043-6E40-4590-AFF6-5B48F6412E2F";
service.characteristics.push( characteristic );
BluetoothLE.service.peripheralManager.addEventListener( PeripheralManagerEvent.SERVICE_ADD, peripheral_serviceAddHandler );
BluetoothLE.service.peripheralManager.addEventListener( PeripheralManagerEvent.SERVICE_ADD_ERROR, peripheral_serviceAddErrorHandler );
// Remove all to prevent duplicate services (this only removes services added by this application)
BluetoothLE.service.peripheralManager.removeAllServices();
BluetoothLE.service.peripheralManager.addService( service );
You will receive a success or failure event from the addService call:
private function peripheral_serviceAddHandler( event:PeripheralManagerEvent ):void
{
trace( "peripheral manager service added" );
}
private function peripheral_serviceAddErrorHandler( event:PeripheralManagerEvent ):void
{
trace( "peripheral manager service add error" );
}
Note: After you publish a service and any of its associated characteristics to the peripheral's database, the service is cached and you can no longer make changes to it.
Advertise Services
When you have published your services and characteristics to your device's database
of services and characteristics, you are ready to start advertising some of them
to any centrals that may be listening. To start advertising call the
startAdvertising
function on the peripheralManager
. The first parameter is a
local name to advertise for the device and the second parameter is a vector array
of Service
objects to advertise.
var service:Service = ...; // A previously created and published service
BluetoothLE.service.peripheralManager.addEventListener( PeripheralManagerEvent.START_ADVERTISING, peripheral_startAdvertisingHandler );
BluetoothLE.service.peripheralManager.startAdvertising( "distriqt", new <Service>[ service ] );
When you start advertising some of the data on your local peripheral, the peripheral
manager dispatches the PeripheralManagerEvent.START_ADVERTISING
event. Advertising
behavior is also affected when your app is in the background. You must make sure you
have correctly added the application descriptor additions to have permission to
advertise when in the background.
private function peripheral_startAdvertisingHandler( event:PeripheralManagerEvent ):void
{
trace( "peripheral manager start advertising" );
}
Once you begin advertising data, remote centrals can discover and initiate a connection with you.
Read and Write Requests
After you are connected to one or more remote centrals, you may begin receiving read or write requests from them. When you do, be sure to respond to those requests in an appropriate manner. The following examples describe how to handle such requests.
When a connected central requests to read the value of one of your characteristics,
the peripheral manager dispatches the RequestEvent.READ
event. The RequestEvent
has
a number of properties that you can use to fulfill the request.
When you receive a read request you should make sure that the characteristic in
your device's database matches the one that the remote central specified in the
original read request. If the characteristics match the next step is to make sure
that the read request isn't asking to read from an index position that is outside
the bounds of your characteristic's value. If the offset is invalid you should
respond with the Request.INVALID_OFFSET
flag as in the example below.
Assuming the request's offset is verified you can use the respondToRequest
function
on the peripheralManager
to send the data to the central. You should respect the
read offset and send the Request.SUCCESS
flag and the requested data, as in the
example below.
Firstly add a listener for read requests:
var characteristic:Characteristic = ...; // Some characteristic you have created and added to a published service
BluetoothLE.service.peripheralManager.addEventListener( RequestEvent.READ, peripheral_readRequestHandler );
Then process these read requests when received:
private function peripheral_readRequestHandler( event:RequestEvent ):void
{
//
// Read requests will only have one object in the requests event
var request:Request = event.requests[0];
trace( "peripheral manager: read request: " + request.characteristic.uuid );
//
// Handle the read request by first checking the UUID and then responding with the requested data.
// You need to make sure you correctly handle the offset variable as illustrated below
if (request.characteristic.uuid == characteristic.uuid)
{
if (request.offset > characteristic.value.length)
{
BluetoothLE.service.peripheralManager.respondToRequest( request, Request.INVALID_OFFSET );
}
else
{
characteristic.value.position = 0;
var data:ByteArray = new ByteArray();
data.writeBytes( characteristic.value, request.offset, (characteristic.value.length - request.offset) );
BluetoothLE.service.peripheralManager.respondToRequest( request, Request.SUCCESS, data );
}
}
}
Note: If the characteristics' UUIDs do not match, or if the read can not be completed for any other reason, you should not attempt to fulfill the request. Instead, you should call the
respondToRequest
function immediately with the appropriate result flag from theRequest
class.
Handling write requests from a connected central is also straightforward.
When a connected central sends a request to write the value of one or more
of your characteristics, the peripheral manager dispatches the
RequestEvent.WRITE
event. With a write event the event may contain more
than one requests and you must process each request and call
respondToRequest
only once. Be sure to take into account the request's
offset property when writing the value of your characteristic
Add the write request listener:
var characteristic:Characteristic = ...; // Some characteristic you have created and added to a published service
BluetoothLE.service.peripheralManager.addEventListener( RequestEvent.WRITE, peripheral_writeRequestHandler );
Then handle the request:
private function peripheral_writeRequestHandler( event:RequestEvent ):void
{
trace( "peripheral manager: write request: " + event.requests.length );
//
// Write requests may have several requests and you should process each one
// before calling respondToRequest. You only need to call respondToRequest once
// with the first request in the array
for each (var request:Request in event.requests)
{
trace( "peripheral manager: write request: " + request.characteristic.uuid +" :: [" + request.offset +"] "+ request.value.readUTFBytes( request.value.length ) );
if (request.characteristic.uuid == characteristic.uuid)
{
if (request.offset + request.value.length > MAX_VALUE_LENGTH)
{
BluetoothLE.service.peripheralManager.respondToRequest( request, Request.INVALID_OFFSET );
return;
}
else
{
if (characteristic.value == null) characteristic.value = new ByteArray();
characteristic.value.position = 0;
characteristic.value.writeBytes( request.value, request.offset );
}
}
}
BluetoothLE.service.peripheralManager.respondToRequest( event.requests[0], Request.SUCCESS );
}
You need to call
respondToRequest
exactly once per dispatchedRequestEvent
no matter how many requests the event contains.
Update Subscribed Centrals
Often, connected centrals will subscribe to one or more of your characteristic values. When they do, you are responsible for sending them notifications when the value of characteristic they subscribed to changes.
When a connected central subscribes to the value of one of your characteristics,
the peripheral manager dispatches the CentralEvent.SUBSCRIBE
event and
similarly the CentralEvent.UNSUBSCRIBE
when the central unsubscribes.
You should use these events as a cue to start/stop sending the updated values
to centrals.
Make sure you are listening for the events:
var characteristic:Characteristic = ...; // Some characteristic you have created and added to a published service
BluetoothLE.service.peripheralManager.addEventListener( CentralEvent.SUBSCRIBE, peripheral_central_subscribeHandler );
BluetoothLE.service.peripheralManager.addEventListener( CentralEvent.UNSUBSCRIBE, peripheral_central_unsubscribeHandler );
Then process the subscription and unsubscription:
private function peripheral_central_subscribeHandler( event:CentralEvent ):void
{
trace( "peripheral manager: central subscribe: " + event.central.uuid );
// You should use this to track whether you should be periodically sending updates
// For the moment we'll just show as an example sending an update immediately
var value:ByteArray = new ByteArray();
value.writeUTFBytes( "some_updated_value" );
BluetoothLE.service.peripheralManager.updateValue( characteristic, value );
}
private function peripheral_central_unsubscribeHandler( event:CentralEvent ):void
{
trace( "peripheral manager: central unsubscribe: " + event.central.uuid );
// If there are no other centrals subscribed you can stop periodically sending updates
}
The updateValue
function returns a Boolean value that indicates whether
the update was successfully sent to the subscribed centrals. If the underlying
queue that is used to transmit the updated value is full, the method returns
false
and you should wait and try again later.