搬家完之后,大门锁是一个德斯曼门锁,型号是 T700,指纹识别特别拉胯,加上自己的手换季就有点脱皮,所以门锁一直用官方 app 开门,每次开门,都需要打开 App,靠近门锁,点击一下一键开门。
用了几个月之后,突然想到为啥这个门锁不支持像 Tesla 一样,靠近自动解锁呢?于是,我就在 app 上提了一个意见反馈,希望可以像 Tesla 一样,只要手机靠近,拉一下门把手就开门的功能。可能这个锁太小众,或者开发人员是外包?提交了需求,三个月过去了,后台一直都是待回复。
于是,自己写一个吧,虽然,没做过 iOS 开发,不过也不用做界面,就做蓝牙调用就好了。
分析协议 网络请求 iOS 使用 stream 抓包,获取网络 Http 请求,很容易就能找到 2 个关键请求。
/lock/command/secret
获取 command_secret ,这个值测试下来是固定的。
/lock/open/new
开锁
所有的请求使用 sessionId
进行用户验证,亲测一个 sessionId 基本可以用很久,几个月都不会过期。
蓝牙请求 使用 https://github.com/jnross/Bluetility 这个工具,即可扫描蓝牙设备,获取广播信息。可以找到锁的蓝牙特征值
1 central.scanForPeripherals(withServices: [CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"),], options: [CBCentralManagerScanOptionAllowDuplicatesKey:**true**])
做个 App 拿到这些所有数据,就可以开始做个 app 了。步骤 1、蓝牙扫描周围设备,如果发现设备名称为 “LOCK_1234”的设备,即是自己的锁,1234 为锁的蓝牙 Mac 地址后 4 位拼接。 2、连接这个设备。 3、处理 notifyService 和 writeService。 4、将 Http 请求获取到的 command_secret 使用 hexWrite 写入蓝牙锁,锁会返回一个 secret 值。 5、使用锁返回的 secret 值,调用 /lock/open/new
即可获取开锁的 command 6、使用 hexWrite 写入 command ,锁就开了。
通过上面的流程,就可以完成一次开锁了,如何像 Tesla 一样,靠近,拉门把手,就开锁呢? 处理 deviceDidDisconnect
方法就可以了。断联后,自动等待下次连接,这个指令有 iOS 调度,所以也不用担心 App 后台的问题。
最终效果,还挺好,每天开门都不用掏手机了。
核心代码如下,基本就是 github 上的代码,改一改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 // ContentViewModel.swift import Foundation import CoreBluetooth import LogsSheetKit class ContentViewModel:ObservableObject,ScannerDelegate,DeviceDelegate{ let scanner = Scanner() let door = OpenDoor() let notifyServiceId="FFE0" let notifyCharacteristicId="FFE4" let writeServiceId="FFE5" let writeCharacteristicId="FFE9" var lockDevice:Device? = nil var notifyService:CBService? = nil var writeService:CBService? = nil var notifyCharacteristic:CBCharacteristic? = nil var writeCharacteristic:CBCharacteristic? = nil init(){ scanner.delegate = self scanner.start() } func scanner(_ scanner: Scanner, didUpdateDevices: [Device]) { for device in scanner.devices { if let device_name = device.peripheral.name { print("device_name",device_name) LogsSheetManager.shared.log(message: "device_name \(device_name)") if device_name == "LOCK_1234" { //1234 为mac地址后四位拼接 print(device_name,device.advertisingData) LogsSheetManager.shared.log(message: "advertisingData \(device.advertisingData)") scanner.stop() device.delegate = self device.connect() lockDevice = device } } } } func deviceDidConnect(_ device: Device) { } func deviceDidDisconnect(_ device: Device) { LogsSheetManager.shared.log(message: "waitint for connect...") self.lockDevice!.connect() } func deviceDidUpdateName(_ device: Device){ } func device(_ device: Device, updated services: [CBService]) { for service in services { if service.uuid.uuidString == notifyServiceId && device == lockDevice{ notifyService = service }else if service.uuid.uuidString == writeServiceId && device == lockDevice{ writeService = service } device.discoverCharacteristics(for: service) } } func device(_ device: Device, updated characteristics: [CBCharacteristic], for service: CBService) { for characteristic in characteristics{ if characteristic.uuid.uuidString == notifyCharacteristicId && characteristic.properties.contains(.notify) && device == lockDevice { device.setNotify(true, for: characteristic) LogsSheetManager.shared.log(message: "setNotify true") } else if characteristic.uuid.uuidString == writeCharacteristicId && characteristic.properties.contains(.write) && device == lockDevice { writeCharacteristic = characteristic door.get_secret(finishedCallback: {result in self.hexWrite(result) }) LogsSheetManager.shared.log(message: "write secret") } } } func device(_ device: Device, updatedValueFor characteristic: CBCharacteristic) { if characteristic.uuid.uuidString == notifyCharacteristicId && device == lockDevice { let a = characteristic.value!.hexString print("notifyCharacteristic_value",a) if String(a.prefix(6)) == "FE093A" { let startIndex = a.index(a.startIndex, offsetBy: 12) let endIndex = a.index(a.endIndex, offsetBy: -4) let result = String(a[startIndex..<endIndex]) door.open_door(result,finishedCallback: {result in for command in result{ self.hexWrite(command) } LogsSheetManager.shared.log(message: "write openDoor command") }) }else if String(a.prefix(6)) == "FE0939" { LogsSheetManager.shared.log(message: "openDoor success") } } } func hexWrite(_ text:String) { var bytes = [UInt8]() var i = text.startIndex while i < text.endIndex { let nextIndex = text.index(i, offsetBy: 2) let hexByte = text[i ..< nextIndex] if let byte:UInt8 = UInt8(hexByte, radix:16) { bytes.append(byte) } else { return } i = nextIndex } let data = Data(bytes) let writeType = CBCharacteristicWriteType.withResponse lockDevice!.write(data: data, for: self.writeCharacteristic!, type: writeType) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 // // Scanner.swift import CoreBluetooth import LogsSheetKit protocol ScannerDelegate: AnyObject { func scanner(_ scanner: Scanner, didUpdateDevices: [Device]) } class Scanner: NSObject { var central:CBCentralManager weak var delegate:ScannerDelegate? = nil var devices:[Device] = [] var started:Bool = false override init() { central = CBCentralManager(delegate: nil, queue: nil) super.init() central.delegate = self } func start() { started = true devices = [] startIfReady() } private func startIfReady() { if central.state == .poweredOn && started { central.scanForPeripherals(withServices: [CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"),], options: [CBCentralManagerScanOptionAllowDuplicatesKey:true]) // LOCK } } func stop() { started = false central.stopScan() } func restart() { stop() start() } } extension Scanner : CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { startIfReady() } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { LogsSheetManager.shared.log(message: "Name : \(peripheral.name ?? "(No name)")") guard let existingDevice = devices.first(where: { $0.peripheral == peripheral } ) else { let newDevice = Device(scanner: self, peripheral: peripheral, advertisingData: advertisementData, rssi: RSSI.intValue) devices.append(newDevice) return } existingDevice.rssi = RSSI.intValue existingDevice.advertisingData += advertisementData delegate?.scanner(self, didUpdateDevices: devices) } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { guard let device = devices.first(where:{ $0.peripheral == peripheral }) else { return } device.peripheralDidConnect() } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { guard let device = devices.first(where:{ $0.peripheral == peripheral }) else { return } device.peripheralDidDisconnect(error: error) } }