Skip to content

Instantly share code, notes, and snippets.

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)
Copy link

mhv75 commented Mar 2, 2021

I think we’re trying to do the same thing! Daughter with adhd, figuring out how to best implement miband for gently pulling her back to attention without me having to do it, or her having to carry a phone. ;-)

Copy link

erica commented Mar 2, 2021

After a year or two we upgraded her to an older Apple Watch (g3 I think), and it has built in a tiny vibrate on quarter hours that works well for her.

Copy link

mhv75 commented Mar 2, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment