Last active
March 2, 2021 17:08
-
-
Save erica/0f98d81ee17c17f50bb6d3ae1579fbd2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
super.init() | |
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") | |
centralManager.stopScan() | |
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 = peripheral.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") | |
centralManager.stopScan() | |
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 = peripheral.name { | |
print("\"\(name)\" has connected.") | |
print("Requesting service discovery.\n") | |
peripheral.discoverServices(nil) | |
} | |
} | |
// Invoked when you discover the peripheral’s available services. | |
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { | |
if let services = peripheral.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)) | |
NotificationCenter.default.post(note) | |
default: | |
break | |
} | |
} | |
} | |
// 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) | |
} | |
evaluatePairing(data) | |
default: | |
break | |
} | |
} | |
// External vibration consumption | |
/// Available vibration patterns | |
// - SeeAlso: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_level.xml | |
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 = DispatchTime.now().rawValue + dispatch_time_t(seconds * Double(NSEC_PER_SEC)) | |
let delay = DispatchTime(uptimeNanoseconds: rawDelay) | |
DispatchQueue | |
.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) | |
} |
mhv75
commented
Mar 2, 2021
via email
That sounds good.
Mine is under 10, might upgrade her later.
For now working on combining Brili app and having her complete her morning routines by making use of the media buttons. Which is a bit harder with iOS.
Nice challenge with a worthy goal.
Thank you for your code
… On 2 Mar 2021, at 16:54, Erica Sadun ***@***.***> wrote:
***@***.*** commented on this gist.
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.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment