Instantly share code, notes, and snippets.

@erica /miband.swift
Last active Mar 29, 2018

What would you like to do?
import Cocoa
import CoreBluetooth
import PlaygroundSupport
public class BTHelper: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
// BLE Access
var centralManager: CBCentralManager
override init() {
self.centralManager = CBCentralManager(delegate: nil, queue: nil)
self.centralManager.delegate = self
// Required. Invoked when the central manager’s state is updated.
public func centralManagerDidUpdateState(_ manager: CBCentralManager) {
switch manager.state {
case .poweredOff:
print("BLE has powered off")
case .poweredOn:
print("BLE is now powered on")
centralManager.scanForPeripherals(withServices: nil, options: nil)
case .resetting: print("BLE is resetting")
case .unauthorized: print("Unauthorized BLE state")
case .unknown: print("Unknown BLE state")
case .unsupported: print("This platform does not support BLE")
var targetPeripheral: CBPeripheral? = nil
// Invoked when the central manager discovers a peripheral while scanning.
public func centralManager(_ manager: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData advertisement: [String : Any], rssi: NSNumber) {
guard let name =
else { print("Skipping unnamed peripheral"); return }
guard name.hasPrefix("MI")
else { print("Skipping \"\(name)\" service"); return }
// RSSI is Received Signal Strength Indicator
print("Found \"\(name)\" peripheral (RSSI: \(rssi))")
print("Advertisement data:", advertisement, "\n")
// Attempt connection and service scan
print("Scan stopped.\n")
print("Attempting to connect to \(name)\n")
targetPeripheral = peripheral
targetPeripheral?.delegate = self
centralManager.connect(peripheral, options: nil)
// Invoked when a connection is successfully created with a peripheral.
public func centralManager(_ manager: CBCentralManager, didConnect peripheral: CBPeripheral) {
if let name = {
print("\"\(name)\" has connected.")
print("Requesting service discovery.\n")
// Invoked when you discover the peripheral’s available services.
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = {
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
var vibrationCharacteristic: CBCharacteristic? = nil
// Invoked when you discover the characteristics of a specified service.
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics
else { print("Unable to retrieve service characteristics"); return }
for characteristic in characteristics {
let name = characteristic.uuid.uuidString
switch name {
case "2A06":
print("Available: Vibration")
vibrationCharacteristic = characteristic
let note = Notification(name: Notification.Name(rawValue: name))
// Invoked when you write data to a characteristic’s value.
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("Peripheral did write characteristic value for " + characteristic.uuid.uuidString)
// Invoked when you retrieve a specified characteristic’s value, or when the peripheral device notifies your app that the characteristic’s value has changed.
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("Peripheral did update characteristic value for " + characteristic.uuid.uuidString)
guard let data = characteristic.value
else { print("missing updated value"); return }
print(data.count, "bytes received")
switch characteristic.uuid.uuidString {
case "FF0F":
func evaluatePairing(_ data: Data) {
let result = data.withUnsafeBytes({ (ptr: UnsafePointer<Int16>) in
ptr.withMemoryRebound(to: Int16.self, capacity: 1) { pointer in
return pointer.pointee
print("Pairing result:", result)
// External vibration consumption
/// Available vibration patterns
// - SeeAlso:
public enum Pattern { case low, high }
public func vibrate(degree: Pattern) {
guard let characteristic = vibrationCharacteristic
else { return }
switch degree {
case .low:
targetPeripheral?.writeValue(Data([0x1]), for: characteristic, type: .withoutResponse)
case .high:
targetPeripheral?.writeValue(Data([0x2]), for: characteristic, type: .withoutResponse)
/// Executes closure on global queue (not main) after a delay of *seconds* seconds
public func performActions(after seconds: Double, _ action: @escaping () -> Void) {
let rawDelay = + dispatch_time_t(seconds * Double(NSEC_PER_SEC))
let delay = DispatchTime(uptimeNanoseconds: rawDelay)
.global(qos: .default)
.asyncAfter(deadline: delay, execute: action)
/// Remaining vibration iterations, public access
public var count = Int.max
/// Public request: vibrations start
public func startVibrating(degree: Pattern, delay: Double) {
if count > 0 {
vibrate(degree: degree)
count -= 1
performActions(after: delay) { self.startVibrating(degree: degree, delay: delay) }
// Public request: vibration stop
public func stopVibrating() {
count = 0
let bt = BTHelper()
PlaygroundPage.current.needsIndefiniteExecution = true
// Listen for vibration characteristic, then start pattern
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "2A06"), object: nil, queue: OperationQueue.main) { note in
print("Starting vibration pattern")
bt.startVibrating(degree: .low, delay: 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment