[ Applause ]
Thank you and good morning.
This is Session 712, What's New In Core Bluetooth.
My name is Craig Dooley.
I'm a Bluetooth Engineer here at Apple.
I think we have some really good stuff to talk to you about today, so we'll start with a brief introduction of where Core Bluetooth is right now.
We'll talk about some of the enhanced reliability features that we built for this year.
Talk about platform support.
We announced watchOS support this year and we're really excited about that.
A new feature for this year is L2CAP channels.
Then we'll go over some of the best practices we've learned over the years, and finally we'll do a real world example of how to get the most out of Core Bluetooth.
So I'd like to start with a brief introduction.
Hopefully everybody knows but, if you don't, Core Bluetooth is our framework that we released in 2011 to interact with Bluetooth low energy peripherals and accessories.
And since we initially shipped the framework, we've just seen amazing creativity come out of accessory manufacturers and app developers.
We're so pleased to see what you've done with it.
So when we started off, we knew that health and fitness and were going to be big areas.
We've seen lots of things like smart scales, running sensors, fitness devices that you can wear all day.
But the creativity has been awesome to watch.
So things like toys and connected accessories for kids.
We were really excited to see the Swift Playground's announcements this year, and really making things more interactive for children and people who are first learning to code.
And we love seeing how easy it is for people to take things like sensors or buttons or switches and enable those through their apps and enable them wirelessly.
And then things have gone in directions that we couldn't have even imagined.
Things like connected goggles with head-mounted displays, just awesome creativity that's come out of this community.
So I want to start this talk just by saying thank you.
[ Applause ]
It's awesome for us to see what you've been able to build with this technology, and we can't wait to see how you build even better accessories going forward with these enhancements we want to talk to you about today.
And before we start, I just want to mention there's a lot of types of accessories that you don't have to write any software for.
So if you want to see things like what the currently playing music is on your device, you can implement the Apple Media Service and the device will take care of all of that for you.
Things like iBeacon or HID devices, you don't have to write any software for those.
And we've seen really good adoption for those as well.
But today is going to be about Core Bluetooth and how your device can talk to your accessory.
So stepping back to stage 1, Bluetooth Low Energy has two roles.
There's a central that can look for devices that are around your environment, and there are peripherals that can beacon to the world that they're there.
They can either send out data or just let their presence be known.
So if you have simple things that you want to send out like the current temperature or the time, you can do that and you can actually connect device to device as well.
iOS and Mac devices are allowed to be both the central and the peripheral side of the connection.
After you find this device that's around you, you can connect to your device and then you have bidirectional communication through what's called the GATT Protocol.
The GATT Protocol takes all of your data and it exposes them in a hierarchy called services and characteristics.
We represent these in Core Bluetooth as a CB service.
And a CB service can contain characteristics inside of it.
Most of the time, your Apple device is going to be the central side of this connection and you connect to your peripheral.
But you can also do this in reverse.
So one example of this is your phone has its own GATT database that exposes something like the current time service.
So if it's useful for your accessory to get the current time, you can actually read that off of your phone.
If you're looking to build these type of iDirectional communications on CB Central manager, you can either retrieve the connected peripherals if you know that your device is already connected by calling retrieveConnectedPeripherals or if you have an identifier for an accessory you can call retrievePeripherals (withidentifiers) and get handled back to your accessory.
So let's move onto something new for this year.
As I mentioned, the health and fitness space has really popped up since Core Bluetooth was first announced.
And we find there's a whole class of devices that want to be connected all day.
Users want access to their data as they're wearing it or throughout the night, and you want to make sure that your connection to your Accessories is reliable.
So on iOS we allow you to be a background application.
You can continue connecting out to your accessory or you can continue beaconing to the world that you have data that's available by using these two options in Xcode.
In you can either choose to use your Bluetooth LE accessories or act as a Bluetooth LE accessory if a peripheral is the role that you want to implement.
But we allow you to go even further than that, so if you're acting as a central, you can do things like Scan for new devices by specifying a service or Connect to an accessory you already known about and even if your application is Jetson from the system from memory reasons, Core Bluetooth will continue to look for those devices for you.
If we were to do this in code, at the time that you initialize a CBCentralManager, you pass this RestoreIdentifier.
This is just a string that identifies your session that you want to continue going.
And if we can complete those actions on your behalf, like connecting to your accessory, even if it's a week after your application was launched, we'll relaunch your application and give you this callback.
CentralManager will restore state.
This is when we'll tell you what the current state of the system is so you can reconcile where we are now versus the time that your application was killed.
We'll tell you about the currently connected peripherals and we'll also tell you what we are scanning for at the time that you're relaunched.
Now there will be times where these can be null as well, so you have to be able to be flexible with this and know that you can rebuild your state after you're relaunched.
Similarly, if you're acting as a peripheral we can keep doing things on your behalf even after your application is no longer and we'll relaunch you when that's interesting for your application.
So if you had a local service published in the GATT database or you're advertising a service, we can keep those things going and let you know when something interesting happens.
Again, you have to start with a restoration identifier, which needs to be unique on the system and we'll tell you the current state of the system after you come back.
We'll tell you either through the restored state services key what services are still published on the system for you and through the state advertising data key we'll tell you what we're advertising on your behalf.
So state preservation and restoration that has actually been in the system for a couple of years, but this year we've enhanced it to be even more reliable for your application.
So we're excited to tell you, even if there are Bluetooth system level events that previously would have stopped these things from occurring on your behalf, we'll keep doing them for you now.
And even if a user reboots the device, we can keep connecting to your accessory.
[ Applause ]
And one thing we just want to mention is that in order to keep the power profile of these features low, we actually do have to do a lot of things in Hardware.
So, we are limited in the number of things we can look for at a time, especially the number of services we can scan for.
So the less things you ask for, the better the chances that we can keep your actions running all the time.
The other thing about this is there's no UI to control what applications are allowed to do what in the background, so we will stop doing things on your app's behalf if the user either force quits the app through the task switcher or if the user turns off Bluetooth manually through Bluetooth settings we'll stop doing background activities on your behalf.
Another thing we've enhanced for this year is Write Without Response.
So previously you could call Write Without Response, and as the name implies you don't hear anything back from that.
And there are times when your writes would be dropped if you pushed too many into the system.
And we had no way to tell you that those packets were dropped so if you were trying to send a large amount of data you had to come up with schemes to try to make sure that that data was reliable.
For this year, we've enhanced CBPeripheral with a new property called canSendWriteWithoutResponse.
So if you call this before you do a write and it and it returns yes, that's our promise to you that your data will not be dropped in software before we get a chance to send it to the remote peripheral.
If that returns no, you'll also get a delegate callback when we're ready and we'll call back peripheralIsReady (toSendWritewithoutResponse.
[ Applause ]
Okay. So when we first launched in 2011 we support macOS 10.7 and iOS 5.
We supported those this whole time, and on tvOS 9 we added support for tvOS.
This is actually the first time we've been on stage to talk to you about that so we'd like to go over that as well.
And new for this year, we're supporting watchOS.
We think this opens a lot of cool opportunities, especially in the health and fitness space for places where it was unpractical to bring your phone previously.
So iOS and macOS were the first platforms that we supported.
They support both foreground and background applications, and both central and peripheral mode operation.
We allow you to go down to 15 ms starting this year for your connection interval.
So if you're trying to get the most throughput to your accessories or the lowest latency, these are the platforms that provide you the most opportunities there.
And as I mentioned, state preservation and restoration are supported on iOS so you can try maintain connections to your accessories throughout the day and even across major device events like a system reboot.
tvOS was supported as of tvOS 9, and it was interesting for us because it was the first that we wanted to support third party accessories on a platform that relies on Bluetooth as the main system input.
So we have to coexist between those two opportunities or those two activities.
So in this case we had to put some restrictions into the system to make sure that we don't interfere with core operating things like the Apple Siri Remote.
So we decided for accessory or for tvOS, you can only act as a Central device, and you can only connect to two simultaneous devices.
You can be paired with an unlimited amount but you can only use two at a time.
And we don't allow 15 ms connection intervals on tvOS, we set the minimum at 30 ms. So this still allows you to get low latency for your accessories and good user interaction but it leaves more system resources available.
And when your application is suspended or moved to the background, we're going to automatically disconnect those accessories.
And similarly, to watch OS, tvOS is a platform that relies on Bluetooth for its core system functionality.
So we are allowing access to Core Bluetooth whenever the system allows your application to be run.
So if a user is in your application and directly interacting with it, you can use Core Bluetooth.
You can connect your accessories, you can interact with them, but this also means that if you're allowed to run for other reasons, like complication updates or a workout session, you can stay connected to your accessories and continue getting that data.
Similarly, if the tvOS you can only act as a Central, meaning that you can connect out tier accessories but you can't act as a peripheral to other devices.
And the same two simultaneous connection limitation is in place on watchOS as on tvOS, and the same connection interval limitation.
Also, when the system decides that your application needs to be suspended, that's the indication to Core Bluetooth that the accessories connected on that application's behalf should be disconnected.
And this is supported on Apple Watch Series 2.
As I mentioned, we think this is a really cool opportunity to see accessories connected to Apple Watch and can be used all day.
You can be in places where you don't want to bring your phone or places where it's just more convenient to have data available on your wrist.
So if you want things like battery updates throughout the day, your complication can connect your accessory, get a quick bit of data, and always be up to date on the home screen.
New for this year, we're allowing access to L2CAP Channels.
So, an L2CAP channel at the lowest level is just this stream of data between two devices, and it's actually the protocol that we use under the covers for all communication between these devices.
So L2CAP Channels have been used on the platform.
Since day one of Bluetooth this is the first time that we're opening up for your applications to directly talk over these channels.
It actually stands for the Logical Link Control and Adaptation Protocol, and the support for dynamically allocated connection or into channels is new for Bluetooth Core Spec 4.1.
So unlike all the other interactions that you've done with your accessories before where you have to work through the GATT Database to get your data, L2CAP Channels allow you to open a side channel and directly read and write without any framing limitations, packet size limitations.
It's a direct way to talk between your device and your accessory.
So we think this API is very simple to use.
If you have already connected to a peripheral, all you have to do is call openL2CAPChannel and specify as the PSM, and you'll get a callback data openL2CAPChannel will hand you back the object that represents this channel.
So the PSM is a it's the Protocol Service Multiplexer but you should think of it as analogous to say TCP port.
It's just a number that you give to us that uniquely identifies the service you want to open on the peripheral side of the connection.
And what's interesting about this is that there are some profiles that are published by the Bluetooth SIG that have a hard coded PSM.
So if you're trying to do things like the Object Transfer Protocol, the PSM will be known to you even before you connect to the device.
But in every other case, the PSM is unique to the device you're talking to, which means it's locally assigned and can even be reused by other applications.
So it's important that you discover what PSM to connect to before you try to open it.
What we've done to make this a little bit simpler is we're publishing a UUID that you can use and put within your service to help you specify what PSM to open that's associated with your CB service.
We're also opening L2CAP Channels if you're acting as a peripheral.
So if you want to have a L2CAP Channel that's associated with your service, you can call publishL2CAPChannel and we will return you what PSM was assigned by the system in the callback peripheralManger didPublishL2CAPChannel.
Another thing we've allowed is we allow you to specify whether your L2CAP Channel requires encryption or not.
So most of the time, the safe bet is to say yes, I want encryption.
It protects you from things like man-in-the-middle attacks, it protects you from things like people eavesdropping on the packets that you're sending between your two devices.
But there are time when you might want to build more advanced types of authentication, like using public key cryptography you could build your own account system and protect your data that way.
So let's go through an example of how you would actually negotiate an L2CAP Channel between two devices.
If you're acting as a peripheral, you would ask the system to publish an L2CAP Channel.
Again you get to specify whether this channel requires encryption or not.
In this case we're going to say true.
And when that's successful, you'll get a callback peripheralManager didPublishL2CAPChannel.
And as I mentioned, we'll tell you what PSM has been locally assigned for your service.
So this is your opportunity to make the PSM known to incoming connections so that they can discover what channel to open that's associated with your service.
As a Central, you can read the PSM and that's all the information you need to actually open that channel.
So now you can call openL2CAPChannel with the PSM that you read from the remote device and you'll get the callback didOpenL2CAPChannel.
So CB L2CAP Channel encapsulates all the information you need to know who you're talking to and how you can talk to it.
In this case we tell you the peer, which is either the central or the peripheral on the remote side of the connection.
We tell you what PSM was connected in case you have multiple services that you've published.
And then we just give you an InputStream and OutputStream.
We didn't want to build our own API and make you have to learn how to use a new Read Write API.
We didn't want you to have to adapt your code to deal specifically with Bluetooth connections.
If you already know how to deal with the socket and an InputStream and OutputStream, you can deal with an L2CAP Channel.
So we use all the same stream events that you would get off of, say, a socket after you're connected, and there's bytes available to be read.
You'll get the callback hasBytesAvailable.
If you're writing data and you fill all those queues, you get the same callback you would on a socket of hasSpaceAvailable.
We think this is really powerful because if you already have code that knows how to talk over other network interfaces like Ethernet or Wi-Fi.
That code can be directly adapted to run on top of L2CAP Channels.
And when the channel has been closed, you get the same endEncountered.Event that you would get.
So after you've opened your connection, you can read data to that connection, you can write to it.
Eventually when it's closed you get the endEncounterd.Event, and it can be closed for a couple of reasons.
So if the overall Bluetooth link is lost we'll tell you that the L2CAP Channel has been closed.
As the Central side, you can manually ask to close that connection.
Now on the peripheral side, you can either un-publish the service or if you drop the object and allow it to be deallocated that's an implicit sign to us through Core Bluetooth that you're no longer interested.
That will also close the L2CAP Channel.
So when should you use L2CAP Channels?
In general, if you're already doing well with GATT and your data model fits well into a GATT database, you should continue using that.
GATT makes it very simple to discover your data, to get quick updates.
You don't have to frame any of your data.
So keep using that if that worked for you.
But if there are times where it was previously hard to make your data fit into the GATT database, either because you had more data that you could fit into a characteristic, or you're finding that GATT is kind of getting in the way, L2CAP Channels are a great opportunity to get the lowest overhead best performance when talking between your accessories.
And if you're doing large data transfers, say like a firmware update, L2CAP Channels are a good opportunity to help you get it there faster.
And if you have a streaming protocol that's already defined somewhere that you want to run between your two devices, L2CAP Channels make that a really natural fit.
I'd like to talk to you about some of the best practices that we've learned over the years.
So the first one I have, please follow the Bluetooth Accessory Design Guidelines.
We have a great set of guidelines that are published that give you lots of data to help you build the best accessories.
They're not mandatory but they are things we've learned over the year that help you build accessories without running into the same pitfalls and problems that we've seen across the years.
And if possible, use existing profiles and services.
We've defined our own protocols for things like Notification Center and Apple Media, and the Bluetooth site has a bunch of really good profiles for things like batter updates and device information.
So if there's already a profile that does what you need, implement that and it will help you get a head start on getting the data you want.
A common question we get is, "Why does it take so long to connect to my accessory?"
So before your two devices are connected, they're each running on their own timelines.
Your peripheral is going to be advertising things to the world, and your Central is going to be looking for devices that are around.
But each one of these uses small windows, and until those two events line up you can't discover your accessory.
So the easiest way you can get a quick connection between your two devices or a quick discovery when you're scanning is to use the shortest advertising interval possible.
That ensures that no matter what state your central is in, whether it's screen on, screen off, directly in the app, in the background, it will get you the shortest time to find.
Now using a short advertising interval takes extra battery for your accessory, so you can't do that all the time but what we strongly recommend is if you know through any means necessary, through the user touching the accessory, user picking it up, accelerometer, buttons, use that as an indication to start doing aggressive advertising and then you can go into a lower advertising interval later when you're trying to save battery.
If you're trying to save battery, you can go to the Bluetooth Accessory Design Guidelines.
We have a whole bunch of power optimized but also discovery time optimize advertising intervals you can use.
Another thing that we see is that if you've previously used an accessory and you want to reconnect to that accessory you don't have to scan every time.
If you know that the device is advertising or you just want to connect as quickly as possible, connect directly to the accessory.
If you have the identifier, you can call retrievePeripherals (WithIdentifiers) and directly get a CBPeripheral object that you can connect to.
If you were to scan for your accessory and then connect to it it's actually going to take us twice as long to do that operation as if we could have connected the first time we found it.
Something we get asked a lot is why does it take so long to discover the GATT database after I've connected to my accessory?
And in this case there's a couple of things you can do to speed that up.
The first recommendation we have is use as few services or characteristics as possible.
Every time you add a service to your GATT Database, that takes extra time over the air that we have to ask for those services and discover those.
So by getting rid of services that you don't need, or packing multiple characteristics into one service, that speeds up your service discovery every time your user connects to your accessory.
Another one that's a really simple thing you can do but that helps is group your services by the UUID size.
So if you have multiple 16 bit UUIDs and then a service with 128 bit UUID we can discover all the 16 bit services in one roundtrip instead of having to go back and forth for every service.
And then finally to speed up your service discovery, if we can cash your GATT database we actually will, so that on reconnect we don't have to discover anything.
If you're in an Accessory that requires pairing or bonding, that means that you have to have the service change characteristic in your GATT database.
The Service Change Characteristic is part of the Bluetooth Spec that allows you to tell us when the services and characteristics on your device have changed and when it's safe for us to reuse the last known version of the GATT Database.
And if you're an Accessory that does not allow pairing or bonding, this means that if you have the Service Changed characteristic we can't cache your database anymore.
So in those cases, if you do not have a Serviced Changed characteristic, we will cache your entire service database which gives you quick reconnections to your device.
But you have to be careful if you do not support pairing and you want to do things like firmware update.
Even if it's a rare event, there's no way for you tell us that the services have changed on your peripheral.
And then, finally, if you are building a new accessory we recommend that you always look and see the newest chipsets that are available to you.
Since we came out with Core Bluetooth, Bluetooth 4.2 and now 5.0 have been released.
All the features in those releases are backwards compatible.
So even if you build an accessory that's Bluetooth 5.0, it will work with all the Apple devices that you want to connect to that are 4.0 only.
And then, like I said, follow the Accessory Design Guidelines.
If you read through those upfront it can save you a lot of hassle later in your project.
Now I'd like to hand it to Duy, who's going to walk through how to get the most out of Core Bluetooth.
Thank you, Craig.
[ Applause ]
I am Duy.
Today I'll talk about getting the most out of Core Bluetooth.
Let's walk through a scenario where we have a lot of data to send from where updating a device.
Today it takes about 3,000 seconds to transfer 1MB of data at about 2.5kbps if we use Write With Response and all defaults.
So this is obviously very slow.
What are the problems?
There's two main problems.
The first is all of the protocol overhead in LE.
The Bluetooth Specification defines the LE maximum application datalink to be 27 Bytes, but we lose 7 of that because your data has to traverse from the application through GATT, through ATT and through L2CAP, so you lose about 25% of the packet, and in the end the useable data length is only 20 Bytes.
And in addition, once your data gets through the controller the hardware also adds link layer security and CRC which adds even more time to transmit a packet.
So in order to improve performance, we'll have to reduce both the software and hardware overhead.
The second problem is previously in GATT the only way to reliably write is to use Write With Response which take two intervals to complete, one to write and one to wait for the response.
So your writes are actually very sparse.
You're not fully utilizing the available bandwidth because, as you know, there's multiple opportunities to transmit per interval and we want to pack as many writes as we can into all of the connection events.
So how do we do this?
As Craig mentioned, we've improved Write Without Response.
You can now write and continue to write into Core Bluetooth sets can send holds write without response to false, in which case then your application can wait for a delegate to signal when it's safe to resume writing.
And when you follow the APIs the write will be reliable.
And you can use the Write Without Response to make sure that Core Bluetooth is sufficiently buffered so we can use all of the available connection events to send all of your application data.
And in iOS 10, we also enlarged the Connection Event Length so now you have even more room to write using Write For That Response.
And when you pack all of the connection events in all of the interval, your throughput will improve from 2.5 to 37 kbps.
And coming back to reducing software protocol overhead, all of the discussion thus far assumes an ATT MTU of 23 Bytes, this is why there are red portion overhead in each viewer packets but we can enlarge the MTU and enlarge the writes to align to the MTU.
This will improve your throughput because L2CAP will now fragment your data for you.
You only have to pay in overhead on the first fragment of your MTU.
The rest of the fragments can be as the full 27 Bytes, so when you do this your throughput should improve to 48 kbps.
And so how do you configure MTU in Core Bluetooth?
If you're running Core Bluetooth to Core Bluetooth, there's nothing much you have to do.
We will calculate the MTU for you based on the connection event length and other system configurations but in this example we're from we're updating an accessory.
So if you recall the ATT MTU exchange as a transaction between the client and the server, so the two minimum of the the minimum of the two proposed value is actually the ATT MTU, so your accessory should also use a large MTU to take advantage of this more optimized behavior.
And to use a large MTU that's aligned to the to use a larger attribute write that is aligned to the MTU, you can use the below interfaces to look at what is the maximum write length aligned to the MTU.
So thus far we've talked about how to pack as much writes as we can into the intervals and how to utilize fully utilize all of the bandwidth available.
We talked about how to reduce overhead, software overhead.
But the fact that LE is 27 Bytes per packet and we have to pay the hardware overhead per packet, this really limits how much further we can improve LE performance using just software.
So we've added extended data length support.
Extended data length is a 4.2 feature that increases the maximum application data length from 27 to 251.
This means that per packet now you can send 10 times the amount of data so you've eliminated all of the hardware overhead and software overhead previously and we can now use more of the available time to transmit your application data.
And in fact, we can send a full an entire maximum GATTs write of 512 Bytes in a single interval using Extended Data Length, and this should be about 3 times the throughput of your normal performance.
[ Applause ]
Extended Data Length is the new feature in Bluetooth 4.2.
It extends the maximum application data length from 27 to 251.
It's completely transparent to your application.
If you're running Core Bluetooth to Core Bluetooth we've done everything for you.
We'll negotiate the write data length and MTU appropriate to your hardware configuration, but in this example again we're updating a firmware device so remember you have to also add support for extended data length in the accessory for this to work.
And it has four times the throughput in the same amount of radio time so it's also very power efficient.
It's now available on iPhone 7 and Apple Watch Series 2, and the newly announced iPad Pro.
So please try it out and use this as a reference to develop your new Extended Data Length Accessory.
[ Applause ]
And because we're the example is from we're updating a device, this is a good example of where you should use L2CAP Connection Oriented Channel.
This will eliminate all of the overhead previously in GATT and ATTs, but more importantly it eliminates limitations and restriction in GATT like the maximum attribute size of 512, so we can now write much larger values and use much larger MTU.
When we do that the throughput will increase to almost 200 kbps.
This really shows how powerful Extended Data Length can be if it is not limited by software protocol.
As you know, another way to improve performance is to ask for a faster connection interval.
All of the discussion thus far assumes a connection interval of 30 ms, but in Core Bluetooth we made a change to lower the connection interval minimum for iOS to 15 ms, so when your firmware is updating your device you can ask for a parameter update request and set the interval min and max to 15 ms in which we will honor.
And when you do that, your throughput can double to 394, almost 400 kbps.
[ Applause ]
So this is a summary of where we were and where we are now.
We started with Write With Response at 2.5 kbps.
Then we did Write Without Response but writing only once per interval, which only doubles the performance.
But if you packed all of the opportunities to transmit with writes your throughput can improve to 37.
And if you reduce overhead using larger MTU, your performance can go up to 48 kbps.
And we made a major leap forward with Extended Data Length and tripled that which will yield 135 kbps.
And if we eliminate the software limitations using L2CAP plus Extended Data Length, it can improve to almost 200 kbps.
And we increase the made the connection interval faster which allows you to transmit more often, then the throughput will improve to 400 kbps.
So we started with about 3,000 seconds to download 1 MB of data and we end it with 20 seconds to download 1 MB of data.
[ Applause ]
So in summary, please request a shorter connection interval.
The new minimum is 15 MS on iOS if you need it.
Take advantage of all the GATT optimizations we have like Write Without Response.
Use L2CAP Channel for larger transfer.
All of this is free and all in software, and also update your hardware with the latest specification of Bluetooth Specification and Hardware for best performance and best battery life.
[ Applause ]
Thank you, Duy.
So we've talked about a lot of really exciting stuff here.
There's a lot to go over, but if you're the type of Application that wants to be connected throughout the day, look into the new optimizations we've put in for State Restoration.
Try to build more reliable experiences for your customers.
If it makes sense, you can now take your existing applications and make them available to people on tvOS and watchOS.
If you're trying to do things like streaming protocols or transfer a large amount of data, L2CAP Channels are a great fit for that and we think that's a really powerful feature that's going to help you build even better accessories.
And then if you're building new accessories, always look for the newest Bluetooth specifications and chipsets available and follow the Accessory Design Guidelines.
For more information, we have a website up here that will have links to the Core Bluetooth Developer Documentation Sample Code and other great resources to help you get started.
We also have two historical sessions that we've done at WWDC that are available online, so you can go back and watch the videos and get a bunch of great information about Core Bluetooth both as a Central and a Peripheral.
We can't wait to see build with all these new enhancements.
[ Applause ]