@glassly/bluetooth-sdk 0.1.0-dev.62 → 0.1.0-dev.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/android/src/main/assets/G2Firmware/README.md +3 -3
- package/android/src/main/assets/G2Firmware/g2-glassly-cfw-2.2.9.22.evenota.bin +0 -0
- package/android/src/main/assets/G2Firmware/g2-stock-2.2.9.22.evenota.bin +0 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +0 -4
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +0 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +30 -86
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +1 -2
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +3 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +171 -51
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +23 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +18 -6
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +3 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +8 -8
- package/build/BluetoothSdk.types.d.ts +37 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +0 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +0 -1
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +0 -6
- package/ios/Source/BluetoothSDK.swift +0 -4
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +35 -88
- package/ios/Source/DeviceStore.swift +16 -2
- package/ios/Source/GeneratedReleaseMetadata.swift +3 -3
- package/ios/Source/Resources/G2Firmware/README.md +3 -3
- package/ios/Source/Resources/G2Firmware/g2-glassly-cfw-2.2.9.22.evenota.bin +0 -0
- package/ios/Source/Resources/G2Firmware/g2-stock-2.2.9.22.evenota.bin +0 -0
- package/ios/Source/controllers/R1.swift +145 -49
- package/ios/Source/sgcs/G2.swift +311 -54
- package/ios/Source/sgcs/G2CfwRenderer.swift +24 -8
- package/ios/Source/sgcs/G2FlashProtocol.swift +2 -2
- package/ios/Source/sgcs/G2Flasher.swift +8 -8
- package/ios/Source/sgcs/SGCManager.swift +13 -0
- package/ios/Source/utils/G2NotifyFile.swift +116 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +2 -2
- package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +91 -0
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +37 -1
- package/src/_private/BluetoothSdkModule.ts +0 -1
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +0 -1
|
@@ -31,22 +31,97 @@ private enum R1BLE {
|
|
|
31
31
|
/// Name filters for scanning
|
|
32
32
|
static let NAME_FILTERS = ["EVEN R1", "BCL60"]
|
|
33
33
|
|
|
34
|
-
// Init sequence config writes
|
|
35
|
-
static let CONFIG_FC = Data([0xFC])
|
|
36
|
-
static let CONFIG_11 = Data([0x11])
|
|
37
|
-
|
|
38
|
-
// BleRing1 command header (cmd, module, subCmd) for advStart
|
|
39
|
-
// From RE: BleRing1Cmd_system=0, BleRing1Module_system=0, BleRing1SubCmd_advStart=9
|
|
40
|
-
static let CMD_SYSTEM: UInt8 = 0x00
|
|
41
|
-
static let MODULE_SYSTEM: UInt8 = 0x00
|
|
42
|
-
static let SUBCMD_ADV_START: UInt8 = 0x09
|
|
43
|
-
|
|
44
34
|
/// Gesture protocol marker
|
|
45
35
|
static let GESTURE_MARKER: UInt8 = 0xFF
|
|
46
36
|
|
|
47
37
|
static let SCAN_TIMEOUT: TimeInterval = 15.0
|
|
48
38
|
}
|
|
49
39
|
|
|
40
|
+
// MARK: - EUS transport encoder (phone → ring, channel 2)
|
|
41
|
+
|
|
42
|
+
/// Recovered firmware contract (openCFW r1/ research): every phone→ring packet
|
|
43
|
+
/// is a 12-byte "model" header
|
|
44
|
+
/// [version, module, moduleVersion, serial LE16, status, command, subcommand,
|
|
45
|
+
/// totalLength LE16, crc16 LE16]
|
|
46
|
+
/// followed by the payload, where the phone-direction CRC-16 (CCITT rounds,
|
|
47
|
+
/// init 0xFFFF) runs over the compact bytes [0,1,2,3,5,6,7,8,0x00] and then
|
|
48
|
+
/// continues over the payload bytes. The model is carried on channel 2 inside
|
|
49
|
+
/// a fragment train [sequence, crc32 LE32, ≤239 bytes]; the CRC-32 (poly
|
|
50
|
+
/// 0x1EDC6F41, MSB-first, zero seed, no final xor) covers the whole model and
|
|
51
|
+
/// sequence 0 terminates the train — all our packets fit a single fragment.
|
|
52
|
+
private enum R1Eus {
|
|
53
|
+
static let VERSION: UInt8 = 100
|
|
54
|
+
static let MODULE_SYSTEM: UInt8 = 0x01
|
|
55
|
+
static let MODULE_VERSION: UInt8 = 100
|
|
56
|
+
/// status bit 0x02 marks a SET; advStart requires it
|
|
57
|
+
static let STATUS_SET: UInt8 = 0x02
|
|
58
|
+
|
|
59
|
+
/// system subcommand 0x08: payload [0x01] claims the phone role; the ring
|
|
60
|
+
/// requests link encryption/bonding in response
|
|
61
|
+
static let SUBCMD_PAIR_AUTH: UInt8 = 0x08
|
|
62
|
+
/// system subcommand 0x0A: 12-byte payload = two 6-byte glasses arm MACs;
|
|
63
|
+
/// the ring stores both targets and fast-advertises for them
|
|
64
|
+
static let SUBCMD_ADV_START: UInt8 = 0x0A
|
|
65
|
+
|
|
66
|
+
private static func crc16Step(_ crc: UInt16, _ byte: UInt8) -> UInt16 {
|
|
67
|
+
var c = (crc >> 8) | (crc << 8)
|
|
68
|
+
c ^= UInt16(byte)
|
|
69
|
+
c ^= (c & 0x00FF) >> 4
|
|
70
|
+
c ^= c << 12
|
|
71
|
+
c ^= (c & 0x00FF) << 5
|
|
72
|
+
return c
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private static func phoneCrc16(header: [UInt8], payload: Data) -> UInt16 {
|
|
76
|
+
var crc: UInt16 = 0xFFFF
|
|
77
|
+
// compact header: bytes 0-3 and 5-8, then a zero placeholder
|
|
78
|
+
for b in [header[0], header[1], header[2], header[3], header[5], header[6], header[7], header[8], 0] {
|
|
79
|
+
crc = crc16Step(crc, b)
|
|
80
|
+
}
|
|
81
|
+
for b in payload {
|
|
82
|
+
crc = crc16Step(crc, b)
|
|
83
|
+
}
|
|
84
|
+
return crc
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static func crc32(_ data: Data) -> UInt32 {
|
|
88
|
+
var crc: UInt32 = 0
|
|
89
|
+
for byte in data {
|
|
90
|
+
crc ^= UInt32(byte) << 24
|
|
91
|
+
for _ in 0 ..< 8 {
|
|
92
|
+
crc = (crc & 0x8000_0000) != 0 ? (crc << 1) ^ 0x1EDC_6F41 : crc << 1
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return crc
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Build one single-fragment system-module command packet.
|
|
99
|
+
static func frame(subcommand: UInt8, status: UInt8, serial: UInt16, payload: Data) -> Data {
|
|
100
|
+
let length = UInt16(12 + payload.count)
|
|
101
|
+
var header: [UInt8] = [
|
|
102
|
+
VERSION, MODULE_SYSTEM, MODULE_VERSION,
|
|
103
|
+
UInt8(serial & 0xFF), UInt8(serial >> 8),
|
|
104
|
+
status, 0x00, subcommand,
|
|
105
|
+
UInt8(length & 0xFF), UInt8(length >> 8),
|
|
106
|
+
0, 0,
|
|
107
|
+
]
|
|
108
|
+
let crc16 = phoneCrc16(header: header, payload: payload)
|
|
109
|
+
header[10] = UInt8(crc16 & 0xFF)
|
|
110
|
+
header[11] = UInt8(crc16 >> 8)
|
|
111
|
+
var model = Data(header)
|
|
112
|
+
model.append(payload)
|
|
113
|
+
|
|
114
|
+
let outer = crc32(model)
|
|
115
|
+
var packet = Data([0x00]) // sequence 0: single-fragment train
|
|
116
|
+
packet.append(UInt8(outer & 0xFF))
|
|
117
|
+
packet.append(UInt8((outer >> 8) & 0xFF))
|
|
118
|
+
packet.append(UInt8((outer >> 16) & 0xFF))
|
|
119
|
+
packet.append(UInt8((outer >> 24) & 0xFF))
|
|
120
|
+
packet.append(model)
|
|
121
|
+
return packet
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
50
125
|
// MARK: - R1 Gesture Types
|
|
51
126
|
|
|
52
127
|
private enum R1Gesture: String {
|
|
@@ -92,6 +167,7 @@ class R1: NSObject, ControllerManager {
|
|
|
92
167
|
private var batteryLevelChar: CBCharacteristic?
|
|
93
168
|
private var notifySubscriptionCount = 0
|
|
94
169
|
private var initSequenceRun = false
|
|
170
|
+
private var eusSerial: UInt16 = 0
|
|
95
171
|
|
|
96
172
|
/// Device search
|
|
97
173
|
var DEVICE_SEARCH_ID = "NOT_SET"
|
|
@@ -223,67 +299,83 @@ class R1: NSObject, ControllerManager {
|
|
|
223
299
|
return nil
|
|
224
300
|
}
|
|
225
301
|
|
|
302
|
+
// MARK: - EUS commands
|
|
303
|
+
|
|
304
|
+
private func nextEusSerial() -> UInt16 {
|
|
305
|
+
eusSerial &+= 1
|
|
306
|
+
if eusSerial == 0 { eusSerial = 1 }
|
|
307
|
+
return eusSerial
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// EUS system commands go to WRITE_CHAR_2 only — channel 2 is the framed
|
|
311
|
+
/// transport; channel 1 speaks the separate legacy protocol.
|
|
312
|
+
private func sendEusCommand(_ subcommand: UInt8, payload: Data, log: String) {
|
|
313
|
+
guard let wc = writeChar2 else {
|
|
314
|
+
Bridge.log("R1: \(log): no channel-2 write characteristic")
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
let packet = R1Eus.frame(
|
|
318
|
+
subcommand: subcommand, status: R1Eus.STATUS_SET,
|
|
319
|
+
serial: nextEusSerial(), payload: payload
|
|
320
|
+
)
|
|
321
|
+
ringPeripheral?.writeValue(packet, for: wc, type: .withoutResponse)
|
|
322
|
+
Bridge.log("R1: \(log) sent (\(packet.count) bytes)")
|
|
323
|
+
}
|
|
324
|
+
|
|
226
325
|
// MARK: - Init Sequence
|
|
227
326
|
|
|
228
|
-
/// Runs after notify subscriptions are confirmed
|
|
229
|
-
///
|
|
327
|
+
/// Runs after notify subscriptions are confirmed: claim the phone role,
|
|
328
|
+
/// then mark the phone link ready.
|
|
230
329
|
private func runInitSequence() {
|
|
231
330
|
guard !initSequenceRun else { return }
|
|
232
331
|
initSequenceRun = true
|
|
233
332
|
|
|
234
333
|
Bridge.log("R1: Running init sequence")
|
|
235
334
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
guard !writeChars.isEmpty else {
|
|
239
|
-
Bridge.log("R1: No write characteristics found, skipping init")
|
|
335
|
+
guard writeChar2 != nil else {
|
|
336
|
+
Bridge.log("R1: No channel-2 write characteristic, skipping init")
|
|
240
337
|
markConnected()
|
|
241
338
|
return
|
|
242
339
|
}
|
|
243
340
|
|
|
244
|
-
|
|
245
|
-
ringPeripheral?.writeValue(R1BLE.CONFIG_FC, for: wc, type: .withoutResponse)
|
|
246
|
-
}
|
|
341
|
+
sendEusCommand(R1Eus.SUBCMD_PAIR_AUTH, payload: Data([0x01]), log: "pairAuth")
|
|
247
342
|
|
|
248
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.
|
|
249
|
-
|
|
250
|
-
for wc in writeChars {
|
|
251
|
-
self.ringPeripheral?.writeValue(R1BLE.CONFIG_11, for: wc, type: .withoutResponse)
|
|
252
|
-
}
|
|
253
|
-
self.markConnected()
|
|
343
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
344
|
+
self?.markConnected()
|
|
254
345
|
}
|
|
255
346
|
}
|
|
256
347
|
|
|
257
|
-
/// Tells the ring
|
|
258
|
-
///
|
|
259
|
-
///
|
|
260
|
-
///
|
|
348
|
+
/// Tells the ring which glasses may connect to it: EUS advStart whose
|
|
349
|
+
/// 12-byte payload is the two glasses arm MACs. The ring stores both
|
|
350
|
+
/// targets, fast-advertises while a glasses role is open, and accepts a
|
|
351
|
+
/// connection from either arm. With only one arm MAC known it is sent for
|
|
352
|
+
/// both targets — the ring matches against either slot.
|
|
261
353
|
private func connectToGlasses() {
|
|
262
|
-
|
|
354
|
+
func stored(_ key: String) -> String? {
|
|
355
|
+
guard let value = DeviceStore.shared.get("glasses", key) as? String, !value.isEmpty else { return nil }
|
|
356
|
+
return value
|
|
357
|
+
}
|
|
358
|
+
let left = stored("leftMacAddress")
|
|
359
|
+
?? stored("bluetoothMacAddress")
|
|
263
360
|
?? UserDefaults.standard.string(forKey: "glasses_btMacAddress")
|
|
361
|
+
let right = stored("rightMacAddress")
|
|
362
|
+
?? UserDefaults.standard.string(forKey: "glasses_btMacAddressRight")
|
|
264
363
|
|
|
265
|
-
guard let
|
|
364
|
+
guard let left, let leftBytes = parseMac(left) else {
|
|
266
365
|
Bridge.log("R1: connectToGlasses: no glasses MAC")
|
|
267
366
|
return
|
|
268
367
|
}
|
|
368
|
+
let rightBytes = right.flatMap(parseMac) ?? leftBytes
|
|
269
369
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// Cache so we can reconnect even before the glasses are scanned.
|
|
276
|
-
UserDefaults.standard.set(glassesMac, forKey: "glasses_btMacAddress")
|
|
277
|
-
|
|
278
|
-
guard let wc = writeChar2 ?? writeChar1 else {
|
|
279
|
-
Bridge.log("R1: connectToGlasses: no write characteristic")
|
|
280
|
-
return
|
|
370
|
+
// Cache so we can re-send even before the glasses are next scanned.
|
|
371
|
+
UserDefaults.standard.set(left, forKey: "glasses_btMacAddress")
|
|
372
|
+
if let right {
|
|
373
|
+
UserDefaults.standard.set(right, forKey: "glasses_btMacAddressRight")
|
|
281
374
|
}
|
|
282
375
|
|
|
283
|
-
var payload =
|
|
284
|
-
payload.append(
|
|
285
|
-
|
|
286
|
-
ringPeripheral?.writeValue(payload, for: wc, type: .withoutResponse)
|
|
376
|
+
var payload = leftBytes
|
|
377
|
+
payload.append(rightBytes)
|
|
378
|
+
sendEusCommand(R1Eus.SUBCMD_ADV_START, payload: payload, log: "advStart")
|
|
287
379
|
}
|
|
288
380
|
|
|
289
381
|
/// Parse a MAC string like "AA:BB:CC:DD:EE:FF" or "AABBCCDDEEFF" into 6 raw bytes.
|
|
@@ -317,7 +409,10 @@ class R1: NSObject, ControllerManager {
|
|
|
317
409
|
DeviceStore.shared.apply("glasses", "controllerMacAddress", mac)
|
|
318
410
|
|
|
319
411
|
DeviceStore.shared.apply("glasses", "controllerConnected", true)
|
|
320
|
-
//
|
|
412
|
+
// The phone link owns these flags: a ready phone link IS pairing
|
|
413
|
+
// success (fires handleControllerReady). The glasses link is layered
|
|
414
|
+
// on afterwards via advStart + RING_CONNECT_INFO.
|
|
415
|
+
DeviceStore.shared.apply("glasses", "controllerFullyBooted", true)
|
|
321
416
|
|
|
322
417
|
// tell the ring to connect to the glasses if we have it's mac address:
|
|
323
418
|
connectToGlasses()
|
|
@@ -420,7 +515,8 @@ class R1: NSObject, ControllerManager {
|
|
|
420
515
|
batteryLevelChar = nil
|
|
421
516
|
notifySubscriptionCount = 0
|
|
422
517
|
initSequenceRun = false
|
|
423
|
-
ringMacAddress
|
|
518
|
+
// ringMacAddress/ringUUID persist across disconnects so reconnection
|
|
519
|
+
// can find the ring again; only forget() clears them.
|
|
424
520
|
DeviceStore.shared.apply("glasses", "controllerConnected", false)
|
|
425
521
|
DeviceStore.shared.apply("glasses", "controllerFullyBooted", false)
|
|
426
522
|
}
|
|
@@ -666,7 +762,7 @@ extension R1: CBCentralManagerDelegate {
|
|
|
666
762
|
if self.isDisconnecting { return }
|
|
667
763
|
|
|
668
764
|
self.resetConnectionState()
|
|
669
|
-
|
|
765
|
+
self.startReconnectionTimer()
|
|
670
766
|
}
|
|
671
767
|
}
|
|
672
768
|
}
|