@glassly/bluetooth-sdk 0.3.1-dev.540 → 0.3.1-dev.548
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/android/build.gradle +2 -2
- package/android/lc3Lib/build.gradle +2 -2
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +3 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +61 -16
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +3 -3
- package/ios/Source/controllers/R1.swift +55 -16
- package/package.json +2 -2
- package/src/generated/releaseMetadata.ts +3 -3
package/android/build.gradle
CHANGED
|
@@ -364,8 +364,8 @@ afterEvaluate {
|
|
|
364
364
|
url = 'https://github.com/tetramo-labs/glassly'
|
|
365
365
|
licenses {
|
|
366
366
|
license {
|
|
367
|
-
name = '
|
|
368
|
-
url = 'https://
|
|
367
|
+
name = 'MIT License'
|
|
368
|
+
url = 'https://opensource.org/licenses/MIT'
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
developers {
|
|
@@ -159,8 +159,8 @@ afterEvaluate {
|
|
|
159
159
|
url = 'https://github.com/tetramo-labs/glassly'
|
|
160
160
|
licenses {
|
|
161
161
|
license {
|
|
162
|
-
name = '
|
|
163
|
-
url = 'https://
|
|
162
|
+
name = 'MIT License'
|
|
163
|
+
url = 'https://opensource.org/licenses/MIT'
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
developers {
|
|
@@ -3,9 +3,9 @@ package com.glassly.bluetoothsdk
|
|
|
3
3
|
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
4
|
internal object GeneratedReleaseMetadata {
|
|
5
5
|
const val FAMILY_BASE_VERSION: String = "0.3.1"
|
|
6
|
-
const val RELEASE_IDENTITY: String = "0.3.1-dev.
|
|
7
|
-
const val RELEASE_SET_ID: String = "glassly-0.3.1-dev.
|
|
8
|
-
const val SOURCE_COMMIT: String = "
|
|
6
|
+
const val RELEASE_IDENTITY: String = "0.3.1-dev.548"
|
|
7
|
+
const val RELEASE_SET_ID: String = "glassly-0.3.1-dev.548"
|
|
8
|
+
const val SOURCE_COMMIT: String = "8de673988aed75061b39b243d324fcf0b5050224"
|
|
9
9
|
const val OTA_MANIFEST_URL: String = ""
|
|
10
10
|
const val OTA_MANIFEST_SHA256: String = ""
|
|
11
11
|
}
|
|
@@ -54,10 +54,11 @@ private object R1BLE {
|
|
|
54
54
|
* Scan-response manufacturer data (openCFW: `0x5245` company id, the six
|
|
55
55
|
* address bytes in SoftDevice order, then an optional 15-byte serial).
|
|
56
56
|
* Android hands us the payload with the company id already stripped. The
|
|
57
|
-
* GAP name is `EVEN R1_` +
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* GAP name is `EVEN R1_` + the first three address bytes reversed
|
|
58
|
+
* (`R1_26081B` ↔ `1B 08 26 …`), which pins the byte order; the returned
|
|
59
|
+
* string keeps SoftDevice order, the form the glasses have accepted in
|
|
60
|
+
* RING_CONNECT_INFO. Taking the LAST six bytes was wrong for rings with a
|
|
61
|
+
* serial (it returned ASCII digits).
|
|
61
62
|
*/
|
|
62
63
|
fun macAddress(mfgBytes: ByteArray, ringId: String?): String? {
|
|
63
64
|
if (mfgBytes.size < 6) return null
|
|
@@ -66,14 +67,22 @@ private object R1BLE {
|
|
|
66
67
|
val id = ringId?.uppercase()
|
|
67
68
|
val chosen = when {
|
|
68
69
|
id.isNullOrEmpty() -> addr
|
|
69
|
-
hex(byteArrayOf(addr[
|
|
70
|
+
hex(byteArrayOf(addr[2], addr[1], addr[0])) == id -> addr
|
|
70
71
|
else -> {
|
|
71
72
|
val flipped = addr.reversedArray()
|
|
72
|
-
if (hex(byteArrayOf(flipped[
|
|
73
|
+
if (hex(byteArrayOf(flipped[2], flipped[1], flipped[0])) == id) flipped else return null
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
return chosen.joinToString(":") { String.format("%02X", it) }
|
|
76
77
|
}
|
|
78
|
+
|
|
79
|
+
/** A stored "AA:BB:…" address is only trusted when it still agrees with the
|
|
80
|
+
* ring's name; older builds persisted serial digits as the address. */
|
|
81
|
+
fun macMatches(mac: String, ringId: String): Boolean {
|
|
82
|
+
val parts = mac.split(":")
|
|
83
|
+
if (parts.size != 6) return false
|
|
84
|
+
return (parts[2] + parts[1] + parts[0]).uppercase() == ringId.uppercase()
|
|
85
|
+
}
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
// MARK: - Ring → phone touch report (channel 1)
|
|
@@ -140,12 +149,20 @@ private object R1Eus {
|
|
|
140
149
|
* the ring stores both targets and fast-advertises for them */
|
|
141
150
|
const val SUBCMD_ADV_START = 0x0A
|
|
142
151
|
/** system subcommand 0x82 (removeRingNotify): one-byte SET erases both
|
|
143
|
-
* stored glasses targets and the "glasses paired" flag
|
|
144
|
-
*
|
|
145
|
-
* phone link carries telemetry only, so this is what puts input on the
|
|
146
|
-
* phone link. */
|
|
152
|
+
* stored glasses targets and the "glasses paired" flag — the stock
|
|
153
|
+
* "unpair" state, in which the ring accepts ANY glasses that connect. */
|
|
147
154
|
const val SUBCMD_REMOVE_RING = 0x82
|
|
148
155
|
|
|
156
|
+
/** Two targets no glasses will ever present. The ring's advStart consumer
|
|
157
|
+
* compares the connected glasses peer with the supplied targets, drops a
|
|
158
|
+
* mismatch (after a ~20 s firmware delay), stores both targets, and from
|
|
159
|
+
* then on admits a glasses peer only when it equals one of them. That is
|
|
160
|
+
* the only ring-side lever that makes it refuse the glasses: with the
|
|
161
|
+
* targets erased (removeRingNotify) it accepts every glasses peer, and
|
|
162
|
+
* the glasses reconnect on their own. Non-zero and non-FF, which the
|
|
163
|
+
* firmware treats as "configured". */
|
|
164
|
+
val DECOY_TARGETS = byteArrayOf(0x02, 0, 0, 0, 0, 0x01, 0x02, 0, 0, 0, 0, 0x02)
|
|
165
|
+
|
|
149
166
|
private fun crc16Step(crc: Int, byte: Int): Int {
|
|
150
167
|
var c = ((crc shr 8) or (crc shl 8)) and 0xFFFF
|
|
151
168
|
c = c xor (byte and 0xFF)
|
|
@@ -321,6 +338,11 @@ class R1 : ControllerManager() {
|
|
|
321
338
|
|
|
322
339
|
private fun getRingMacFromMap(name: String): String? = loadRingMacAddressMap()[name]
|
|
323
340
|
|
|
341
|
+
private fun removeRingMacFromMap(name: String) {
|
|
342
|
+
val map = loadRingMacAddressMap()
|
|
343
|
+
if (map.remove(name) != null) saveRingMacAddressMap(map)
|
|
344
|
+
}
|
|
345
|
+
|
|
324
346
|
// Reconnection
|
|
325
347
|
private val reconnectionManager = R1ReconnectionManager()
|
|
326
348
|
|
|
@@ -639,10 +661,10 @@ class R1 : ControllerManager() {
|
|
|
639
661
|
get() = DeviceStore.get("bluetooth", "ring_via_glasses") as? Boolean ?: false
|
|
640
662
|
|
|
641
663
|
/**
|
|
642
|
-
* Phone route (default):
|
|
643
|
-
*
|
|
644
|
-
* link
|
|
645
|
-
* MACs, then ask the glasses to connect.
|
|
664
|
+
* Phone route (default): point the ring at decoy glasses targets so it
|
|
665
|
+
* drops and then refuses the real glasses, which puts its touch reports
|
|
666
|
+
* on this link; also ask the glasses to let go. Glasses route (debug):
|
|
667
|
+
* hand the ring the glasses MACs, then ask the glasses to connect.
|
|
646
668
|
*/
|
|
647
669
|
private fun applyRoute(viaGlasses: Boolean) {
|
|
648
670
|
if (viaGlasses) {
|
|
@@ -653,7 +675,7 @@ class R1 : ControllerManager() {
|
|
|
653
675
|
DeviceManager.getInstance().sgc?.connectController()
|
|
654
676
|
}
|
|
655
677
|
} else {
|
|
656
|
-
sendEusCommand(R1Eus.
|
|
678
|
+
sendEusCommand(R1Eus.SUBCMD_ADV_START, R1Eus.DECOY_TARGETS, "advStart(decoy targets)")
|
|
657
679
|
DeviceManager.getInstance().sgc?.releaseController()
|
|
658
680
|
}
|
|
659
681
|
}
|
|
@@ -761,6 +783,17 @@ class R1 : ControllerManager() {
|
|
|
761
783
|
|
|
762
784
|
val timestamp = System.currentTimeMillis()
|
|
763
785
|
|
|
786
|
+
// EUS reply on channel 2: [seq][crc32][12-byte model header][payload].
|
|
787
|
+
// The model status byte is 0x03 | (result << 2); result 0 = success,
|
|
788
|
+
// 1 = error, 2 = refused (needs the bonded phone role), 3 = unsupported.
|
|
789
|
+
if (characteristic.uuid == R1BLE.NOTIFY_CHAR_2 && data.size >= 17 && data[0] == 0.toByte()) {
|
|
790
|
+
val subcmd = data[12].toInt() and 0xFF
|
|
791
|
+
val result = (data[10].toInt() and 0xFF) shr 2
|
|
792
|
+
val len = (data[13].toInt() and 0xFF) or ((data[14].toInt() and 0xFF) shl 8)
|
|
793
|
+
Bridge.log("R1: EUS reply subcmd=0x%02X result=%d len=%d".format(subcmd, result, len))
|
|
794
|
+
return
|
|
795
|
+
}
|
|
796
|
+
|
|
764
797
|
// Touch report: [00 09 61 00 wireType aux speed 00 tick4]
|
|
765
798
|
R1TouchReport.parse(data)?.let { (gesture, wireType) ->
|
|
766
799
|
if (gesture != null) {
|
|
@@ -850,7 +883,13 @@ class R1 : ControllerManager() {
|
|
|
850
883
|
// peripheral isn't in the map we don't have its true public MAC — drop the
|
|
851
884
|
// connection and rescan rather than publishing a stale/wrong MAC. Mirrors
|
|
852
885
|
// iOS R1.swift didConnect.
|
|
853
|
-
|
|
886
|
+
var mappedMac = name?.let { getRingMacFromMap(it) }
|
|
887
|
+
val ringId = name?.let { extractRingId(it) }
|
|
888
|
+
if (mappedMac != null && ringId != null && !R1BLE.macMatches(mappedMac, ringId)) {
|
|
889
|
+
Bridge.log("R1: Stored MAC for $name does not match its name; rescanning for the advertisement")
|
|
890
|
+
removeRingMacFromMap(name)
|
|
891
|
+
mappedMac = null
|
|
892
|
+
}
|
|
854
893
|
if (mappedMac == null) {
|
|
855
894
|
Bridge.log("R1: No MAC stored in map found for ${name ?: "ring"}")
|
|
856
895
|
mainHandler.post {
|
|
@@ -1056,6 +1095,12 @@ class R1 : ControllerManager() {
|
|
|
1056
1095
|
}
|
|
1057
1096
|
|
|
1058
1097
|
override fun forget() {
|
|
1098
|
+
// Hand the ring back in its stock unpaired state: erase the glasses
|
|
1099
|
+
// targets (the phone route left decoys behind) so the Even app, or a
|
|
1100
|
+
// later pairing here, starts clean.
|
|
1101
|
+
if (DeviceStore.get("glasses", "controllerConnected") as? Boolean == true) {
|
|
1102
|
+
sendEusCommand(R1Eus.SUBCMD_REMOVE_RING, byteArrayOf(0x01), "removeRingNotify")
|
|
1103
|
+
}
|
|
1059
1104
|
disconnect()
|
|
1060
1105
|
ringMacAddress = null
|
|
1061
1106
|
ringBleAddress = null
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const BLUETOOTH_SDK_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "0.3.1",
|
|
4
|
-
"releaseIdentity": "0.3.1-dev.
|
|
5
|
-
"releaseSetId": "glassly-0.3.1-dev.
|
|
6
|
-
"sourceCommit": "
|
|
4
|
+
"releaseIdentity": "0.3.1-dev.548",
|
|
5
|
+
"releaseSetId": "glassly-0.3.1-dev.548",
|
|
6
|
+
"sourceCommit": "8de673988aed75061b39b243d324fcf0b5050224",
|
|
7
7
|
"otaManifestUrl": null,
|
|
8
8
|
"otaManifestSha256": null
|
|
9
9
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.1\",\n \"releaseIdentity\": \"0.3.1-dev.
|
|
1
|
+
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.1\",\n \"releaseIdentity\": \"0.3.1-dev.548\",\n \"releaseSetId\": \"glassly-0.3.1-dev.548\",\n \"sourceCommit\": \"8de673988aed75061b39b243d324fcf0b5050224\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
|
|
@@ -11,7 +11,7 @@ enum BluetoothSdkDefaults {
|
|
|
11
11
|
static let voiceActivityDetectionEnabled = false
|
|
12
12
|
static let loudnessGateEnabled = true
|
|
13
13
|
private static let infoSdkVersionKey = "BluetoothSdkVersion"
|
|
14
|
-
private static let swiftPackageSdkVersion = "0.3.1-dev.
|
|
14
|
+
private static let swiftPackageSdkVersion = "0.3.1-dev.548"
|
|
15
15
|
private static let swiftPackageSdkVersionPlaceholder = "__GLASSLY" + "_BLUETOOTH_SDK_VERSION__"
|
|
16
16
|
|
|
17
17
|
private static func normalizedSdkVersion(_ value: String?) -> String? {
|
|
@@ -3,9 +3,9 @@ import Foundation
|
|
|
3
3
|
/// Generated by release CI. Do not edit in a release checkout.
|
|
4
4
|
enum GeneratedReleaseMetadata {
|
|
5
5
|
static let familyBaseVersion = "0.3.1"
|
|
6
|
-
static let releaseIdentity = "0.3.1-dev.
|
|
7
|
-
static let releaseSetId = "glassly-0.3.1-dev.
|
|
8
|
-
static let sourceCommit = "
|
|
6
|
+
static let releaseIdentity = "0.3.1-dev.548"
|
|
7
|
+
static let releaseSetId = "glassly-0.3.1-dev.548"
|
|
8
|
+
static let sourceCommit = "8de673988aed75061b39b243d324fcf0b5050224"
|
|
9
9
|
static let otaManifestUrl = ""
|
|
10
10
|
static let otaManifestSha256 = ""
|
|
11
11
|
}
|
|
@@ -14,22 +14,31 @@ import Foundation
|
|
|
14
14
|
private enum R1BLE {
|
|
15
15
|
/// Scan-response manufacturer data (openCFW: `0x5245` company id, the six
|
|
16
16
|
/// address bytes in SoftDevice order, then an optional 15-byte serial).
|
|
17
|
-
/// The GAP name is `EVEN R1_` +
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
///
|
|
17
|
+
/// The GAP name is `EVEN R1_` + the first three address bytes reversed
|
|
18
|
+
/// (`R1_26081B` ↔ `1B 08 26 …`), which pins the byte order; the stored
|
|
19
|
+
/// string keeps SoftDevice order, the form the glasses have accepted in
|
|
20
|
+
/// RING_CONNECT_INFO. Taking the LAST six bytes was wrong for rings with
|
|
21
|
+
/// a serial (it returned ASCII digits).
|
|
21
22
|
static func macAddress(mfgData: Data, ringId: String?) -> Data? {
|
|
22
23
|
let bytes = [UInt8](mfgData)
|
|
23
24
|
guard bytes.count >= 8, bytes[0] == 0x45, bytes[1] == 0x52 else { return nil }
|
|
24
25
|
let addr = Array(bytes[2 ..< 8])
|
|
25
26
|
guard let ringId, !ringId.isEmpty else { return Data(addr) }
|
|
26
27
|
let hex = { (b: [UInt8]) in b.map { String(format: "%02X", $0) }.joined() }
|
|
27
|
-
if hex([addr[
|
|
28
|
+
if hex([addr[2], addr[1], addr[0]]) == ringId.uppercased() { return Data(addr) }
|
|
28
29
|
let flipped = Array(addr.reversed())
|
|
29
|
-
if hex([flipped[
|
|
30
|
+
if hex([flipped[2], flipped[1], flipped[0]]) == ringId.uppercased() { return Data(flipped) }
|
|
30
31
|
return nil
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/// A stored address is only trusted when it still agrees with the ring's
|
|
35
|
+
/// name; older builds persisted serial digits as the address.
|
|
36
|
+
static func macMatches(_ mac: Data, ringId: String) -> Bool {
|
|
37
|
+
let b = [UInt8](mac)
|
|
38
|
+
guard b.count == 6 else { return false }
|
|
39
|
+
return [b[2], b[1], b[0]].map { String(format: "%02X", $0) }.joined() == ringId.uppercased()
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
/// Ring custom service
|
|
34
43
|
static let SERVICE_UUID = CBUUID(string: "BAE80001-4F05-4503-8E65-3AF1F7329D1F")
|
|
35
44
|
|
|
@@ -117,12 +126,20 @@ private enum R1Eus {
|
|
|
117
126
|
/// the ring stores both targets and fast-advertises for them
|
|
118
127
|
static let SUBCMD_ADV_START: UInt8 = 0x0A
|
|
119
128
|
/// system subcommand 0x82 (removeRingNotify): one-byte SET erases both
|
|
120
|
-
/// stored glasses targets and the "glasses paired" flag
|
|
121
|
-
///
|
|
122
|
-
/// phone link carries telemetry only, so this is what puts input on the
|
|
123
|
-
/// phone link.
|
|
129
|
+
/// stored glasses targets and the "glasses paired" flag — the stock
|
|
130
|
+
/// "unpair" state, in which the ring accepts ANY glasses that connect.
|
|
124
131
|
static let SUBCMD_REMOVE_RING: UInt8 = 0x82
|
|
125
132
|
|
|
133
|
+
/// Two targets no glasses will ever present. The ring's advStart consumer
|
|
134
|
+
/// compares the connected glasses peer with the supplied targets, drops a
|
|
135
|
+
/// mismatch (after a ~20 s firmware delay), stores both targets, and from
|
|
136
|
+
/// then on admits a glasses peer only when it equals one of them. That is
|
|
137
|
+
/// the only ring-side lever that makes it refuse the glasses: with the
|
|
138
|
+
/// targets erased (removeRingNotify) it accepts every glasses peer, and
|
|
139
|
+
/// the glasses reconnect on their own. Non-zero and non-FF, which the
|
|
140
|
+
/// firmware treats as "configured".
|
|
141
|
+
static let DECOY_TARGETS = Data([0x02, 0, 0, 0, 0, 0x01, 0x02, 0, 0, 0, 0, 0x02])
|
|
142
|
+
|
|
126
143
|
private static func crc16Step(_ crc: UInt16, _ byte: UInt8) -> UInt16 {
|
|
127
144
|
var c = (crc >> 8) | (crc << 8)
|
|
128
145
|
c ^= UInt16(byte)
|
|
@@ -485,10 +502,10 @@ class R1: NSObject, ControllerManager {
|
|
|
485
502
|
startHeartbeat()
|
|
486
503
|
}
|
|
487
504
|
|
|
488
|
-
/// Phone route (default):
|
|
489
|
-
///
|
|
490
|
-
/// link
|
|
491
|
-
/// MACs, then ask the glasses to connect.
|
|
505
|
+
/// Phone route (default): point the ring at decoy glasses targets so it
|
|
506
|
+
/// drops and then refuses the real glasses, which puts its touch reports
|
|
507
|
+
/// on this link; also ask the glasses to let go. Glasses route (debug):
|
|
508
|
+
/// hand the ring the glasses MACs, then ask the glasses to connect.
|
|
492
509
|
private func applyRoute(viaGlasses: Bool) {
|
|
493
510
|
if viaGlasses {
|
|
494
511
|
connectToGlasses()
|
|
@@ -498,7 +515,7 @@ class R1: NSObject, ControllerManager {
|
|
|
498
515
|
await DeviceManager.shared.sgc?.connectController()
|
|
499
516
|
}
|
|
500
517
|
} else {
|
|
501
|
-
sendEusCommand(R1Eus.
|
|
518
|
+
sendEusCommand(R1Eus.SUBCMD_ADV_START, payload: R1Eus.DECOY_TARGETS, log: "advStart(decoy targets)")
|
|
502
519
|
Task { await DeviceManager.shared.sgc?.releaseController() }
|
|
503
520
|
}
|
|
504
521
|
}
|
|
@@ -552,6 +569,15 @@ class R1: NSObject, ControllerManager {
|
|
|
552
569
|
|
|
553
570
|
let timestamp = Int64(Date().timeIntervalSince1970 * 1000)
|
|
554
571
|
|
|
572
|
+
// EUS reply on channel 2: [seq][crc32][12-byte model header][payload].
|
|
573
|
+
// The model status byte is 0x03 | (result << 2); result 0 = success,
|
|
574
|
+
// 1 = error, 2 = refused (needs the bonded phone role), 3 = unsupported.
|
|
575
|
+
if characteristic.uuid == R1BLE.NOTIFY_CHAR_2, data.count >= 17, data[0] == 0 {
|
|
576
|
+
let b = [UInt8](data)
|
|
577
|
+
Bridge.log("R1: EUS reply subcmd=0x\(String(format: "%02X", b[12])) result=\(b[10] >> 2) len=\(Int(b[13]) | Int(b[14]) << 8)")
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
|
|
555
581
|
// Touch report: [00 09 61 00 wireType aux speed 00 tick4]
|
|
556
582
|
if let report = R1TouchReport.parse(data) {
|
|
557
583
|
if let gesture = report.gesture {
|
|
@@ -660,6 +686,12 @@ class R1: NSObject, ControllerManager {
|
|
|
660
686
|
}
|
|
661
687
|
|
|
662
688
|
func forget() {
|
|
689
|
+
// Hand the ring back in its stock unpaired state: erase the glasses
|
|
690
|
+
// targets (the phone route left decoys behind) so the Even app, or a
|
|
691
|
+
// later pairing here, starts clean.
|
|
692
|
+
if DeviceStore.shared.get("glasses", "controllerConnected") as? Bool ?? false {
|
|
693
|
+
sendEusCommand(R1Eus.SUBCMD_REMOVE_RING, payload: Data([0x01]), log: "removeRingNotify")
|
|
694
|
+
}
|
|
663
695
|
disconnect()
|
|
664
696
|
ringUUID = nil
|
|
665
697
|
ringMacAddress = nil
|
|
@@ -816,7 +848,14 @@ extension R1: CBCentralManagerDelegate {
|
|
|
816
848
|
|
|
817
849
|
self.ringUUID = peripheral.identifier
|
|
818
850
|
|
|
819
|
-
|
|
851
|
+
let storedMac = peripheral.name.flatMap { name -> Data? in
|
|
852
|
+
guard let mac = self.ringMacAddressMap[name] else { return nil }
|
|
853
|
+
guard let id = self.extractRingId(name), !R1BLE.macMatches(mac, ringId: id) else { return mac }
|
|
854
|
+
Bridge.log("R1: Stored MAC for \(name) does not match its name; rescanning for the advertisement")
|
|
855
|
+
self.ringMacAddressMap[name] = nil
|
|
856
|
+
return nil
|
|
857
|
+
}
|
|
858
|
+
guard let name = peripheral.name, let mac = storedMac else {
|
|
820
859
|
Bridge.log("R1: No MAC stored in map found for \(peripheral.name ?? "ring")")
|
|
821
860
|
// stop the scan, disconnect, remove the uuid, and try again as we need the mac address to connect:
|
|
822
861
|
self.disconnect()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/bluetooth-sdk",
|
|
3
|
-
"version": "0.3.1-dev.
|
|
3
|
+
"version": "0.3.1-dev.548",
|
|
4
4
|
"description": "SDK for communicating with smart glasses",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"url": "https://github.com/tetramo-labs/glassly/issues"
|
|
108
108
|
},
|
|
109
109
|
"author": "Glassly",
|
|
110
|
-
"license": "
|
|
110
|
+
"license": "MIT",
|
|
111
111
|
"homepage": "https://github.com/tetramo-labs/glassly/tree/main/mobile/modules/bluetooth-sdk#readme",
|
|
112
112
|
"publishConfig": {
|
|
113
113
|
"access": "public",
|
|
@@ -12,9 +12,9 @@ export interface BluetoothSdkReleaseMetadata {
|
|
|
12
12
|
export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.3.1",
|
|
15
|
-
"releaseIdentity": "0.3.1-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.3.1-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.1-dev.548",
|
|
16
|
+
"releaseSetId": "glassly-0.3.1-dev.548",
|
|
17
|
+
"sourceCommit": "8de673988aed75061b39b243d324fcf0b5050224",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|