Core Bluetooth in watchOS Tutorial

Learn how to communicate with external BLE devices from within watchOS in this Core Bluetooth tutorial! By Audrey Tam.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Central Manager

A central manager’s main jobs are:

  • If Bluetooth LE is available and turned on, the central manager scans for peripherals.
  • If a peripheral’s signal is in range, it connects to the peripheral. It also discovers services and characteristics, which it may display to the user to select from, subscribes to characteristics, or requests to read or write a characteristic’s value.

Central Manager Methods & Properties:

  • Initialize with delegate, queue and optional options.
  • Connect to a peripheral, with options,
  • Retrieve known peripherals (array of UUIDs) or connected peripherals (array of service UUIDs).
  • Scan for peripherals with services and options, or stop scanning.
  • Properties: delegate, isScanning

Peripheral Manager

A peripheral manager’s main jobs are to manage and advertise the services in the GATT database of the peripheral device. You would implement this for an Apple device acting as a peripheral. Non-Apple accessories have their own manager APIs. Most of the sample BLE apps you can find online use non-Apple accessories like Arduino.

If Bluetooth LE is available and turned on, the peripheral manager sets up characteristics and services. And it can respond when a central device subscribes to a characteristic, requests to read or write a characteristic value, or unsubscribes from a characteristic.

Peripheral Manager Methods & Properties:

  • Initialize with delegate, queue and optional options.
  • Start or stop advertising peripheral manager data.
  • updateValue(_:for:onSubscribedCentrals:)
  • respond(_:withResult:)
  • Add or remove services.
  • setDesiredConnectionLatency(_:for:)
  • Properties: delegate, isAdvertising

Central Manager Delegate Protocol

Methods in this protocol indicate availability of the central manager, and monitor discovering and connecting to peripherals. Follow along in the CBCentralManagerDelegate extension of CentralViewController.swift, as you work through this list:

  • centralManagerDidUpdateState(_:) is the only required method. If the central is poweredOn — Bluetooth LE is available and turned on — you should start scanning for peripherals. You can also handle the cases poweredOff, resetting, unauthorized, unknown and unsupported, but you must not issue commands to the central manager when it isn’t powered on.
  • When the central manager discovers a peripheral, centralManager(_:didDiscover:advertisementData:rssi:) should save a local copy of the peripheral. Check the received signal strength indicator (RSSI) to see if the peripheral’s signal is strong enough: -22dB is good, but two iOS devices placed right next to each other produce a much lower RSSI, often below -35dB. If the peripheral’s RSSI is acceptable, try to connect to it with the central manager’s connect(_:options:) method.
  • If the connection attempt fails, you can check the error in the delegate method centralManager(_:didFailToConnect:error:). If the error is something transient, you can call connect(_:options:) again.
  • When the connection attempt succeeds, implement centralManager(_:didConnect:) to stop scanning, reset characteristic values, set the peripheral’s delegate property, then call the peripheral’s discoverServices(_:) method. The argument is an array of service UUIDs that your app is interested in. After this, it’s up to the peripheral delegate protocol to discover characteristics of the services.
  • In centralManager(_:didDisconnectPeripheral:error:), you can clean up, then start scanning again.

Peripheral Manager Delegate Protocol

Methods in this protocol indicate availability of the peripheral manager, verify advertising, and monitor read, write and subcription requests from central devices. Follow along in the CBPeripheralManagerDelegate extension of PeripheralViewController.swift, as you work through this list:

  • peripheralManagerDidUpdateState(_:) is the only required method. You handle the same cases as the corresponding centralManagerDidUpdateState(_:). If the peripheral is poweredOn, you should create the peripheral’s services, and their characteristics.
  • peripheralManagerDidStartAdvertising(_:error:) is called when the peripheral manager starts advertising the peripheral’s data.
  • When the central subscribes to a characteristic, by enabling notifications, peripheralManager(_:central:didSubscribeTo:) should start sending the characteristic’s value.
  • When the central disables notifications for a characteristic, you can implement peripheralManager(_:central:didUnsubscribeFrom:) to stop sending updates of the characteristic’s value.
  • To send a characteristic’s value, sendData() uses the peripheral manager method updateValue(_:for:onSubscribedCentrals:). This method returns false if the transmit queue is full. When the transmit queue has space, the peripheral manager calls peripheralManagerIsReady(toUpdateSubscribers:). You should implement this delegate method to resend the value.
  • The central can send read or write requests, which the peripheral handles with peripheralManager(_:didReceiveRead:) or peripheralManager(_:didReceiveWrite:). When implementing these methods, you should call the peripheral manager method peripheral.respond(to:withResult:) exactly once. The sample app implements only peripheralManager(_:didReceiveWrite:); reading the text data is accomplished by subscribing to textCharacteristic.

Peripheral Delegate Protocol

A peripheral delegate can respond when a central device discovers its services or characteristics, or requests to read a characteristic, or when a characteristic’s value is updated. It can also respond when a central device writes a characteristic’s value, or disconnects a peripheral. Follow along in the CBPeripheralDelegate extension of CentralViewController.swift, as you work through this list:

  • The sample app just checks the error in peripheral(_:didDiscoverServices:), but some apps might present a list of peripheral.services for the user to select from.
  • And similarly for peripheral(_:didDiscoverCharacteristicsFor:error:).
  • When the peripheral manager updates a value that the central subscribed to, or requested to read, implement peripheral(_:didUpdateValueFor:error:) to use that value in your app. The sample app collects the chunks, then displays the complete text in the view controller’s text view.
  • Implement peripheral(_:didUpdateNotificationStateFor:error:) to handle the central device enabling or disabling notifications for a characteristic. The sample app just logs the information.
  • There’s a runtime warning if you don’t implement peripheral(_:didModifyServices:), so I added this stub.

watchOS vs iOS

iOS apps can be central or peripheral, and can continue using CoreBluetooth in the background.

watchOS and tvOS both rely on Bluetooth as their main system input, so Core Bluetooth has restrictions, to ensure system activities can run. Both can be only the central device, and can use at most two peripherals at a time. Peripherals are disconnected when the app is suspended.And the minimum interval between connections is 30ms, instead of 15ms for iOS and macOS.

Now finally, you’re going to build the Watch app!

Building the Watch App

As you’ve done many times already, select Xcode\File\New\Target… and choose watchOS\WatchKit App:

Name the product BT_WatchKit_App, uncheck Include Notification Scene, and select Finish:

There are now three targets: TextMeMapMe, BT_WatchKit_App and BT_WatchKit_App Extension. Check that all three have the same team.

Creating the Interface

Open BT_WatchKit_App/Interface.storyboard, and drag two buttons, two labels, and a menu onto the scene. Set the background color of the buttons to different colors, and set their titles to wait…:

Select the two buttons, then select Editor\Embed in\Horizontal Group. Set each button’s width to 0.5 Relative to Container, and leave Height Size To Fit Content:

Set each label’s Font to Footnote, and Lines to 0, and set the second label’s Text to Transferred text appears here:

Set the Menu Item‘s Title to Reset, with Image Repeat:

Open the assistant editor, and create outlets (textButton, mapButton, statusLabel, textLabel) and actions (textMe, mapMe, resetCentral) in InterfaceController.swift.