搬家完之后,大门锁是一个德斯曼门锁,型号是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)
}
}