@mentra/bluetooth-sdk 0.1.2 → 0.1.4

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.
Files changed (85) hide show
  1. package/README.md +75 -19
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +13 -13
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +42 -18
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +19 -21
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -4
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +118 -103
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +110 -38
  8. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -4
  9. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -2
  10. package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +6 -6
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -3
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +18 -18
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -4
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +2 -2
  18. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +6 -6
  19. package/app.plugin.js +3 -1
  20. package/build/BluetoothSdk.types.d.ts +126 -27
  21. package/build/BluetoothSdk.types.d.ts.map +1 -1
  22. package/build/BluetoothSdk.types.js.map +1 -1
  23. package/build/_internal.d.ts +12 -0
  24. package/build/_internal.d.ts.map +1 -0
  25. package/build/_internal.js +11 -0
  26. package/build/_internal.js.map +1 -0
  27. package/build/{BluetoothSdkModule.d.ts → _private/BluetoothSdkModule.d.ts} +19 -8
  28. package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
  29. package/build/{BluetoothSdkModule.js → _private/BluetoothSdkModule.js} +41 -26
  30. package/build/_private/BluetoothSdkModule.js.map +1 -0
  31. package/build/index.d.ts +4 -2
  32. package/build/index.d.ts.map +1 -1
  33. package/build/index.js +3 -4
  34. package/build/index.js.map +1 -1
  35. package/build/react/index.d.ts +5 -0
  36. package/build/react/index.d.ts.map +1 -0
  37. package/build/react/index.js +5 -0
  38. package/build/react/index.js.map +1 -0
  39. package/build/react/useBluetoothEvent.d.ts +6 -0
  40. package/build/react/useBluetoothEvent.d.ts.map +1 -0
  41. package/build/react/useBluetoothEvent.js +21 -0
  42. package/build/react/useBluetoothEvent.js.map +1 -0
  43. package/build/react/useBluetoothScan.d.ts +22 -0
  44. package/build/react/useBluetoothScan.d.ts.map +1 -0
  45. package/build/react/useBluetoothScan.js +135 -0
  46. package/build/react/useBluetoothScan.js.map +1 -0
  47. package/build/react/useBluetoothStatus.d.ts +16 -0
  48. package/build/react/useBluetoothStatus.d.ts.map +1 -0
  49. package/build/react/useBluetoothStatus.js +137 -0
  50. package/build/react/useBluetoothStatus.js.map +1 -0
  51. package/build/react/useGlassesConnection.d.ts +29 -0
  52. package/build/react/useGlassesConnection.d.ts.map +1 -0
  53. package/build/react/useGlassesConnection.js +140 -0
  54. package/build/react/useGlassesConnection.js.map +1 -0
  55. package/ios/BluetoothSdkModule.swift +14 -9
  56. package/ios/Source/Bridge.swift +44 -12
  57. package/ios/Source/DeviceManager.swift +27 -26
  58. package/ios/Source/DeviceStore.swift +4 -4
  59. package/ios/Source/MentraBluetoothSDK.swift +250 -132
  60. package/ios/Source/controllers/ControllerManager.swift +5 -5
  61. package/ios/Source/controllers/R1.swift +3 -3
  62. package/ios/Source/services/PhoneMic.swift +5 -5
  63. package/ios/Source/sgcs/Frame.swift +1 -1
  64. package/ios/Source/sgcs/G1.swift +1 -1
  65. package/ios/Source/sgcs/G2.swift +3 -3
  66. package/ios/Source/sgcs/Mach1.swift +1 -1
  67. package/ios/Source/sgcs/MentraLive.swift +31 -33
  68. package/ios/Source/sgcs/MentraNex.swift +2 -2
  69. package/ios/Source/sgcs/SGCManager.swift +5 -5
  70. package/ios/Source/sgcs/Simulated.swift +3 -3
  71. package/ios/Source/utils/Constants.swift +6 -6
  72. package/ios/Source/utils/JSCExperiment.swift +8 -8
  73. package/ios/Source/utils/TarBz2Extractor.swift +2 -2
  74. package/package.json +19 -1
  75. package/src/BluetoothSdk.types.ts +186 -28
  76. package/src/_internal.ts +11 -0
  77. package/src/{BluetoothSdkModule.ts → _private/BluetoothSdkModule.ts} +71 -38
  78. package/src/index.ts +80 -4
  79. package/src/react/index.ts +22 -0
  80. package/src/react/useBluetoothEvent.ts +38 -0
  81. package/src/react/useBluetoothScan.ts +173 -0
  82. package/src/react/useBluetoothStatus.ts +180 -0
  83. package/src/react/useGlassesConnection.ts +194 -0
  84. package/build/BluetoothSdkModule.d.ts.map +0 -1
  85. package/build/BluetoothSdkModule.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothStatus.js","sourceRoot":"","sources":["../../src/react/useBluetoothStatus.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useRef, useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport {\n createDisconnectedGlassesStatus,\n isConnectedGlassesConnectionStatus,\n isReadyGlassesConnectionStatus,\n} from \"../BluetoothSdk.types\"\nimport type {\n BatteryStatusEvent,\n HotspotStatus,\n HotspotStatusChangeEvent,\n PublicBluetoothStatus,\n PublicGlassesStatus,\n WifiStatus,\n WifiStatusChangeEvent,\n} from \"../BluetoothSdk.types\"\n\nexport type UseBluetoothStatusOptions = {\n enabled?: boolean\n onError?: (error: unknown) => void\n}\n\nexport type BluetoothStatusHookResult = {\n bluetoothStatus: Partial<PublicBluetoothStatus>\n connected: boolean\n error: unknown | null\n glassesStatus: Partial<PublicGlassesStatus>\n loading: boolean\n ready: boolean\n refresh: () => Promise<void>\n}\n\nfunction wifiStatusFromEvent(event: WifiStatusChangeEvent): WifiStatus {\n switch (event.state) {\n case \"connected\":\n return {state: \"connected\", ssid: event.ssid, localIp: event.localIp}\n case \"disconnected\":\n return {state: \"disconnected\"}\n }\n}\n\nfunction hotspotStatusFromEvent(event: HotspotStatusChangeEvent): HotspotStatus {\n if (event.state === \"enabled\") {\n return {\n state: \"enabled\",\n ssid: event.ssid,\n password: event.password,\n localIp: event.localIp,\n }\n }\n return {state: event.state}\n}\n\nfunction mergeGlassesStatus(\n current: Partial<PublicGlassesStatus>,\n changed: Partial<PublicGlassesStatus>,\n): Partial<PublicGlassesStatus> {\n if (changed.connection?.state === \"disconnected\") {\n return {...createDisconnectedGlassesStatus(), ...changed}\n }\n return {...current, ...changed}\n}\n\nexport function useBluetoothStatus(options: UseBluetoothStatusOptions = {}): BluetoothStatusHookResult {\n const enabled = options.enabled ?? true\n const onErrorRef = useRef(options.onError)\n const [bluetoothStatus, setBluetoothStatus] = useState<Partial<PublicBluetoothStatus>>({})\n const [error, setError] = useState<unknown | null>(null)\n const [glassesStatus, setGlassesStatus] = useState<Partial<PublicGlassesStatus>>(() =>\n createDisconnectedGlassesStatus(),\n )\n const [loading, setLoading] = useState(enabled)\n\n useEffect(() => {\n onErrorRef.current = options.onError\n }, [options.onError])\n\n async function refresh() {\n setLoading(true)\n try {\n const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([\n BluetoothSdk.getGlassesStatus(),\n BluetoothSdk.getBluetoothStatus(),\n ])\n setGlassesStatus(nextGlassesStatus)\n setBluetoothStatus(nextBluetoothStatus)\n setError(null)\n } catch (nextError) {\n setError(nextError)\n onErrorRef.current?.(nextError)\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false)\n return undefined\n }\n\n let mounted = true\n\n const loadInitialStatus = async () => {\n setLoading(true)\n try {\n const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([\n BluetoothSdk.getGlassesStatus(),\n BluetoothSdk.getBluetoothStatus(),\n ])\n if (!mounted) {\n return\n }\n setGlassesStatus(nextGlassesStatus)\n setBluetoothStatus(nextBluetoothStatus)\n setError(null)\n } catch (nextError) {\n if (!mounted) {\n return\n }\n setError(nextError)\n onErrorRef.current?.(nextError)\n } finally {\n if (mounted) {\n setLoading(false)\n }\n }\n }\n\n void loadInitialStatus()\n\n const removeGlasses = BluetoothSdk.onGlassesStatus((changed) => {\n setGlassesStatus((current) => mergeGlassesStatus(current, changed))\n })\n const removeBluetooth = BluetoothSdk.onBluetoothStatus((changed) => {\n setBluetoothStatus((current) => ({...current, ...changed}))\n })\n const batterySubscription = BluetoothSdk.addListener(\"battery_status\", (event: BatteryStatusEvent) => {\n setGlassesStatus((current) => ({\n ...current,\n batteryLevel: event.level,\n charging: event.charging,\n }))\n })\n const wifiSubscription = BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n setGlassesStatus((current) => ({...current, wifi: wifiStatusFromEvent(event)}))\n })\n const hotspotSubscription = BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n setGlassesStatus((current) => ({...current, hotspot: hotspotStatusFromEvent(event)}))\n })\n const hotspotErrorSubscription = BluetoothSdk.addListener(\"hotspot_error\", () => {\n setGlassesStatus((current) => ({...current, hotspot: {state: \"disabled\"}}))\n })\n\n return () => {\n mounted = false\n removeGlasses()\n removeBluetooth()\n batterySubscription.remove()\n wifiSubscription.remove()\n hotspotSubscription.remove()\n hotspotErrorSubscription.remove()\n }\n }, [enabled])\n\n const connection = glassesStatus.connection\n const connected = connection ? isConnectedGlassesConnectionStatus(connection) : false\n const ready = connection ? isReadyGlassesConnectionStatus(connection) : false\n\n return {\n bluetoothStatus,\n connected,\n error,\n glassesStatus,\n loading,\n ready,\n refresh,\n }\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import type { ConnectOptions, Device, DeviceModel } from "../BluetoothSdk.types";
2
+ import { type BluetoothScanHookResult } from "./useBluetoothScan";
3
+ import { type BluetoothStatusHookResult } from "./useBluetoothStatus";
4
+ export type DefaultDeviceStorage = {
5
+ load: () => Promise<Device | null>;
6
+ save: (device: Device | null) => Promise<void>;
7
+ };
8
+ export type GlassesConnectionAction = "idle" | "scanning" | "connecting" | "disconnecting" | "forgetting";
9
+ export type UseGlassesConnectionOptions = {
10
+ autoConnectDefault?: boolean;
11
+ defaultDeviceStorage?: DefaultDeviceStorage;
12
+ onError?: (error: unknown) => void;
13
+ scanModel?: DeviceModel;
14
+ scanTimeoutMs?: number;
15
+ };
16
+ export type GlassesConnectionHookResult = BluetoothStatusHookResult & {
17
+ action: GlassesConnectionAction;
18
+ busy: boolean;
19
+ clearDefaultDevice: () => Promise<void>;
20
+ connect: (device?: Device, options?: ConnectOptions) => Promise<void>;
21
+ connectDefault: (options?: ConnectOptions) => Promise<void>;
22
+ defaultDevice: Device | null;
23
+ disconnect: () => Promise<void>;
24
+ forget: () => Promise<void>;
25
+ scan: BluetoothScanHookResult;
26
+ setDefaultDevice: (device: Device | null) => Promise<void>;
27
+ };
28
+ export declare function useGlassesConnection(options?: UseGlassesConnectionOptions): GlassesConnectionHookResult;
29
+ //# sourceMappingURL=useGlassesConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGlassesConnection.d.ts","sourceRoot":"","sources":["../../src/react/useGlassesConnection.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAC,MAAM,uBAAuB,CAAA;AAE9E,OAAO,EAAmB,KAAK,uBAAuB,EAAC,MAAM,oBAAoB,CAAA;AACjF,OAAO,EAAqB,KAAK,yBAAyB,EAAC,MAAM,sBAAsB,CAAA;AAEvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAClC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/C,CAAA;AAED,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GACN,UAAU,GACV,YAAY,GACZ,eAAe,GACf,YAAY,CAAA;AAEhB,MAAM,MAAM,2BAA2B,GAAG;IACxC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,yBAAyB,GAAG;IACpE,MAAM,EAAE,uBAAuB,CAAA;IAC/B,IAAI,EAAE,OAAO,CAAA;IACb,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrE,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,EAAE,uBAAuB,CAAA;IAC7B,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D,CAAA;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,2BAA2B,CAsJ7B"}
@@ -0,0 +1,140 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import BluetoothSdk from "../index";
3
+ import { useBluetoothScan } from "./useBluetoothScan";
4
+ import { useBluetoothStatus } from "./useBluetoothStatus";
5
+ export function useGlassesConnection(options = {}) {
6
+ const status = useBluetoothStatus({ onError: options.onError });
7
+ const scan = useBluetoothScan({
8
+ model: options.scanModel,
9
+ onError: options.onError,
10
+ timeoutMs: options.scanTimeoutMs,
11
+ });
12
+ const [action, setAction] = useState("idle");
13
+ const [defaultDevice, setDefaultDeviceState] = useState(null);
14
+ const [operationError, setOperationError] = useState(null);
15
+ const autoConnectAttemptedRef = useRef(false);
16
+ const defaultDeviceStorageRef = useRef(options.defaultDeviceStorage);
17
+ const onErrorRef = useRef(options.onError);
18
+ useEffect(() => {
19
+ defaultDeviceStorageRef.current = options.defaultDeviceStorage;
20
+ onErrorRef.current = options.onError;
21
+ }, [options.defaultDeviceStorage, options.onError]);
22
+ useEffect(() => {
23
+ let mounted = true;
24
+ const loadDefaultDevice = async () => {
25
+ try {
26
+ const [nativeDefaultDevice, storedDefaultDevice] = await Promise.all([
27
+ BluetoothSdk.getDefaultDevice(),
28
+ defaultDeviceStorageRef.current?.load() ?? Promise.resolve(null),
29
+ ]);
30
+ if (!mounted) {
31
+ return;
32
+ }
33
+ const nextDefaultDevice = storedDefaultDevice ?? nativeDefaultDevice;
34
+ if (nextDefaultDevice) {
35
+ await BluetoothSdk.setDefaultDevice(nextDefaultDevice);
36
+ }
37
+ if (mounted) {
38
+ setDefaultDeviceState(nextDefaultDevice);
39
+ setOperationError(null);
40
+ }
41
+ }
42
+ catch (nextError) {
43
+ if (!mounted) {
44
+ return;
45
+ }
46
+ setOperationError(nextError);
47
+ onErrorRef.current?.(nextError);
48
+ }
49
+ };
50
+ void loadDefaultDevice();
51
+ return () => {
52
+ mounted = false;
53
+ };
54
+ }, []);
55
+ useEffect(() => {
56
+ if (!options.autoConnectDefault || autoConnectAttemptedRef.current || status.connected || !defaultDevice) {
57
+ return;
58
+ }
59
+ autoConnectAttemptedRef.current = true;
60
+ void connectDefault().catch(() => undefined);
61
+ }, [defaultDevice, options.autoConnectDefault, status.connected]);
62
+ async function runConnectionAction(nextAction, operation) {
63
+ setAction(nextAction);
64
+ setOperationError(null);
65
+ try {
66
+ const result = await operation();
67
+ setOperationError(null);
68
+ return result;
69
+ }
70
+ catch (nextError) {
71
+ setOperationError(nextError);
72
+ onErrorRef.current?.(nextError);
73
+ throw nextError;
74
+ }
75
+ finally {
76
+ setAction("idle");
77
+ }
78
+ }
79
+ async function setDefaultDevice(device) {
80
+ await runConnectionAction("connecting", async () => {
81
+ await BluetoothSdk.setDefaultDevice(device);
82
+ await defaultDeviceStorageRef.current?.save(device);
83
+ setDefaultDeviceState(device);
84
+ });
85
+ }
86
+ async function clearDefaultDevice() {
87
+ await runConnectionAction("forgetting", async () => {
88
+ await BluetoothSdk.clearDefaultDevice();
89
+ await defaultDeviceStorageRef.current?.save(null);
90
+ setDefaultDeviceState(null);
91
+ scan.selectDevice(null);
92
+ });
93
+ }
94
+ async function connect(device, connectOptions) {
95
+ await runConnectionAction("connecting", async () => {
96
+ const targetDevice = device ?? scan.selectedDevice;
97
+ if (targetDevice) {
98
+ await BluetoothSdk.connect(targetDevice, connectOptions);
99
+ if (connectOptions?.saveAsDefault !== false) {
100
+ await defaultDeviceStorageRef.current?.save(targetDevice);
101
+ setDefaultDeviceState(targetDevice);
102
+ }
103
+ return;
104
+ }
105
+ await BluetoothSdk.connectDefault(connectOptions);
106
+ });
107
+ }
108
+ async function connectDefault(connectOptions) {
109
+ await runConnectionAction("connecting", async () => {
110
+ await BluetoothSdk.connectDefault(connectOptions);
111
+ });
112
+ }
113
+ async function disconnect() {
114
+ await runConnectionAction("disconnecting", async () => {
115
+ await BluetoothSdk.disconnect();
116
+ });
117
+ }
118
+ async function forget() {
119
+ await runConnectionAction("forgetting", async () => {
120
+ await BluetoothSdk.forget();
121
+ scan.clearResults();
122
+ });
123
+ }
124
+ const busy = action !== "idle" || scan.scanning || status.loading;
125
+ return {
126
+ ...status,
127
+ action: scan.scanning ? "scanning" : action,
128
+ busy,
129
+ clearDefaultDevice,
130
+ connect,
131
+ connectDefault,
132
+ defaultDevice,
133
+ disconnect,
134
+ error: operationError ?? scan.error ?? status.error,
135
+ forget,
136
+ scan,
137
+ setDefaultDevice,
138
+ };
139
+ }
140
+ //# sourceMappingURL=useGlassesConnection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGlassesConnection.js","sourceRoot":"","sources":["../../src/react/useGlassesConnection.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useRef, useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport type {ConnectOptions, Device, DeviceModel} from \"../BluetoothSdk.types\"\n\nimport {useBluetoothScan, type BluetoothScanHookResult} from \"./useBluetoothScan\"\nimport {useBluetoothStatus, type BluetoothStatusHookResult} from \"./useBluetoothStatus\"\n\nexport type DefaultDeviceStorage = {\n load: () => Promise<Device | null>\n save: (device: Device | null) => Promise<void>\n}\n\nexport type GlassesConnectionAction =\n | \"idle\"\n | \"scanning\"\n | \"connecting\"\n | \"disconnecting\"\n | \"forgetting\"\n\nexport type UseGlassesConnectionOptions = {\n autoConnectDefault?: boolean\n defaultDeviceStorage?: DefaultDeviceStorage\n onError?: (error: unknown) => void\n scanModel?: DeviceModel\n scanTimeoutMs?: number\n}\n\nexport type GlassesConnectionHookResult = BluetoothStatusHookResult & {\n action: GlassesConnectionAction\n busy: boolean\n clearDefaultDevice: () => Promise<void>\n connect: (device?: Device, options?: ConnectOptions) => Promise<void>\n connectDefault: (options?: ConnectOptions) => Promise<void>\n defaultDevice: Device | null\n disconnect: () => Promise<void>\n forget: () => Promise<void>\n scan: BluetoothScanHookResult\n setDefaultDevice: (device: Device | null) => Promise<void>\n}\n\nexport function useGlassesConnection(\n options: UseGlassesConnectionOptions = {},\n): GlassesConnectionHookResult {\n const status = useBluetoothStatus({onError: options.onError})\n const scan = useBluetoothScan({\n model: options.scanModel,\n onError: options.onError,\n timeoutMs: options.scanTimeoutMs,\n })\n const [action, setAction] = useState<GlassesConnectionAction>(\"idle\")\n const [defaultDevice, setDefaultDeviceState] = useState<Device | null>(null)\n const [operationError, setOperationError] = useState<unknown | null>(null)\n const autoConnectAttemptedRef = useRef(false)\n const defaultDeviceStorageRef = useRef(options.defaultDeviceStorage)\n const onErrorRef = useRef(options.onError)\n\n useEffect(() => {\n defaultDeviceStorageRef.current = options.defaultDeviceStorage\n onErrorRef.current = options.onError\n }, [options.defaultDeviceStorage, options.onError])\n\n useEffect(() => {\n let mounted = true\n\n const loadDefaultDevice = async () => {\n try {\n const [nativeDefaultDevice, storedDefaultDevice] = await Promise.all([\n BluetoothSdk.getDefaultDevice(),\n defaultDeviceStorageRef.current?.load() ?? Promise.resolve(null),\n ])\n if (!mounted) {\n return\n }\n const nextDefaultDevice = storedDefaultDevice ?? nativeDefaultDevice\n if (nextDefaultDevice) {\n await BluetoothSdk.setDefaultDevice(nextDefaultDevice)\n }\n if (mounted) {\n setDefaultDeviceState(nextDefaultDevice)\n setOperationError(null)\n }\n } catch (nextError) {\n if (!mounted) {\n return\n }\n setOperationError(nextError)\n onErrorRef.current?.(nextError)\n }\n }\n\n void loadDefaultDevice()\n\n return () => {\n mounted = false\n }\n }, [])\n\n useEffect(() => {\n if (!options.autoConnectDefault || autoConnectAttemptedRef.current || status.connected || !defaultDevice) {\n return\n }\n\n autoConnectAttemptedRef.current = true\n void connectDefault().catch(() => undefined)\n }, [defaultDevice, options.autoConnectDefault, status.connected])\n\n async function runConnectionAction<T>(\n nextAction: Exclude<GlassesConnectionAction, \"idle\" | \"scanning\">,\n operation: () => Promise<T>,\n ): Promise<T> {\n setAction(nextAction)\n setOperationError(null)\n try {\n const result = await operation()\n setOperationError(null)\n return result\n } catch (nextError) {\n setOperationError(nextError)\n onErrorRef.current?.(nextError)\n throw nextError\n } finally {\n setAction(\"idle\")\n }\n }\n\n async function setDefaultDevice(device: Device | null) {\n await runConnectionAction(\"connecting\", async () => {\n await BluetoothSdk.setDefaultDevice(device)\n await defaultDeviceStorageRef.current?.save(device)\n setDefaultDeviceState(device)\n })\n }\n\n async function clearDefaultDevice() {\n await runConnectionAction(\"forgetting\", async () => {\n await BluetoothSdk.clearDefaultDevice()\n await defaultDeviceStorageRef.current?.save(null)\n setDefaultDeviceState(null)\n scan.selectDevice(null)\n })\n }\n\n async function connect(device?: Device, connectOptions?: ConnectOptions) {\n await runConnectionAction(\"connecting\", async () => {\n const targetDevice = device ?? scan.selectedDevice\n if (targetDevice) {\n await BluetoothSdk.connect(targetDevice, connectOptions)\n if (connectOptions?.saveAsDefault !== false) {\n await defaultDeviceStorageRef.current?.save(targetDevice)\n setDefaultDeviceState(targetDevice)\n }\n return\n }\n await BluetoothSdk.connectDefault(connectOptions)\n })\n }\n\n async function connectDefault(connectOptions?: ConnectOptions) {\n await runConnectionAction(\"connecting\", async () => {\n await BluetoothSdk.connectDefault(connectOptions)\n })\n }\n\n async function disconnect() {\n await runConnectionAction(\"disconnecting\", async () => {\n await BluetoothSdk.disconnect()\n })\n }\n\n async function forget() {\n await runConnectionAction(\"forgetting\", async () => {\n await BluetoothSdk.forget()\n scan.clearResults()\n })\n }\n\n const busy = action !== \"idle\" || scan.scanning || status.loading\n\n return {\n ...status,\n action: scan.scanning ? \"scanning\" : action,\n busy,\n clearDefaultDevice,\n connect,\n connectDefault,\n defaultDevice,\n disconnect,\n error: operationError ?? scan.error ?? status.error,\n forget,\n scan,\n setDefaultDevice,\n }\n}\n"]}
@@ -193,6 +193,12 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
193
193
  }
194
194
  }
195
195
 
196
+ AsyncFunction("stopScan") {
197
+ await MainActor.run {
198
+ self.bluetoothSdk().stopScan()
199
+ }
200
+ }
201
+
196
202
  AsyncFunction("cancelConnectionAttempt") {
197
203
  await MainActor.run {
198
204
  self.bluetoothSdk().cancelConnectionAttempt()
@@ -308,10 +314,10 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
308
314
  }
309
315
  }
310
316
 
311
- AsyncFunction("photoRequest") {
317
+ AsyncFunction("requestPhoto") {
312
318
  (
313
319
  requestId: String, appId: String, size: String, webhookUrl: String?,
314
- authToken: String?, compress: String?, flash: Bool, sound: Bool
320
+ authToken: String?, compress: String?, sound: Bool
315
321
  ) in
316
322
  await MainActor.run {
317
323
  self.bluetoothSdk().requestPhoto(
@@ -322,7 +328,6 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
322
328
  webhookUrl: webhookUrl,
323
329
  authToken: authToken,
324
330
  compress: compress.flatMap(PhotoCompression.init(rawValue:)),
325
- flash: flash,
326
331
  sound: sound
327
332
  )
328
333
  )
@@ -367,10 +372,10 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
367
372
 
368
373
  // MARK: - Video Recording Commands
369
374
 
370
- AsyncFunction("startVideoRecording") { (requestId: String, save: Bool, flash: Bool, sound: Bool) in
375
+ AsyncFunction("startVideoRecording") { (requestId: String, save: Bool, sound: Bool) in
371
376
  await MainActor.run {
372
377
  self.bluetoothSdk().startVideoRecording(
373
- VideoRecordingRequest(requestId: requestId, save: save, flash: flash, sound: sound)
378
+ VideoRecordingRequest(requestId: requestId, save: save, sound: sound)
374
379
  )
375
380
  }
376
381
  }
@@ -422,7 +427,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
422
427
  AsyncFunction("rgbLedControl") {
423
428
  (
424
429
  requestId: String, packageName: String?, action: String, color: String?,
425
- ontime: Int, offtime: Int, count: Int
430
+ onDurationMs: Int, offDurationMs: Int, count: Int
426
431
  ) in
427
432
  await MainActor.run {
428
433
  self.bluetoothSdk().rgbLedControl(
@@ -431,8 +436,8 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
431
436
  packageName: packageName,
432
437
  action: RgbLedAction(rawValue: action) ?? .off,
433
438
  color: color.flatMap(RgbLedColor.init(rawValue:)),
434
- ontime: ontime,
435
- offtime: offtime,
439
+ onDurationMs: onDurationMs,
440
+ offDurationMs: offDurationMs,
436
441
  count: count
437
442
  )
438
443
  )
@@ -545,7 +550,7 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
545
550
  "compatible_glasses_search_stop",
546
551
  [
547
552
  "type": "compatible_glasses_search_stop",
548
- "device_model": deviceModel,
553
+ "deviceModel": deviceModel,
549
554
  ]
550
555
  )
551
556
  }
@@ -126,18 +126,48 @@ class Bridge {
126
126
  if let rssi {
127
127
  newResult["rssi"] = rssi
128
128
  }
129
- let allResults = searchResults + [newResult]
130
- var seen = Set<String>()
131
- let uniqueResults = allResults.reversed().filter {
132
- let model = $0["model"] as? String ?? $0["deviceModel"] as? String ?? deviceModel
133
- guard let name = $0["name"] as? String ?? $0["deviceName"] as? String else { return false }
134
- return seen.insert("\(model):\(name)").inserted
135
- }.reversed()
136
- DeviceStore.shared.set("bluetooth", "searchResults", Array(uniqueResults))
129
+ // Keep the public searchResults array stable as glasses are added or removed.
130
+ // Duplicate discoveries refresh their existing row; only new glasses append.
131
+ let uniqueResults = mergeStableSearchResults(
132
+ searchResults,
133
+ newResult: newResult,
134
+ fallbackModel: deviceModel
135
+ )
136
+ DeviceStore.shared.set("bluetooth", "searchResults", uniqueResults)
137
137
  }
138
138
  }
139
139
  }
140
140
 
141
+ private static func mergeStableSearchResults(
142
+ _ currentResults: [[String: Any]],
143
+ newResult: [String: Any],
144
+ fallbackModel: String
145
+ ) -> [[String: Any]] {
146
+ guard let newKey = searchResultKey(newResult, fallbackModel: fallbackModel) else {
147
+ return currentResults
148
+ }
149
+ var nextResults = currentResults
150
+ if let existingIndex = nextResults.firstIndex(where: {
151
+ searchResultKey($0, fallbackModel: fallbackModel) == newKey
152
+ }) {
153
+ nextResults[existingIndex] = newResult
154
+ } else {
155
+ nextResults.append(newResult)
156
+ }
157
+ return nextResults
158
+ }
159
+
160
+ private static func searchResultKey(_ result: [String: Any], fallbackModel: String) -> String? {
161
+ if let id = result["id"] as? String, !id.isEmpty {
162
+ return id
163
+ }
164
+ let model = result["model"] as? String ?? fallbackModel
165
+ guard let name = result["name"] as? String else {
166
+ return nil
167
+ }
168
+ return "\(model):\(name)"
169
+ }
170
+
141
171
  // MARK: - Hardware Events
142
172
 
143
173
  static func sendButtonPress(buttonId: String, pressType: String) {
@@ -153,8 +183,9 @@ class Bridge {
153
183
 
154
184
  static func sendTouchEvent(deviceModel: String, gestureName: String, timestamp: Int64, source: Int32? = nil) {
155
185
  var body: [String: Any] = [
156
- "device_model": deviceModel,
157
- "gesture_name": gestureName,
186
+ "type": "touch_event",
187
+ "deviceModel": deviceModel,
188
+ "gestureName": gestureName,
158
189
  "timestamp": timestamp,
159
190
  ]
160
191
  if let source {
@@ -173,8 +204,8 @@ class Bridge {
173
204
 
174
205
  static func sendSwitchStatus(switchType: Int, value: Int, timestamp: Int64) {
175
206
  let body: [String: Any] = [
176
- "switch_type": switchType,
177
- "switch_value": value,
207
+ "switchType": switchType,
208
+ "switchValue": value,
178
209
  "timestamp": timestamp,
179
210
  ]
180
211
  Bridge.sendTypedMessage("switch_status", body: body)
@@ -183,6 +214,7 @@ class Bridge {
183
214
  static func sendRgbLedControlResponse(requestId: String, success: Bool, error: String?) {
184
215
  guard !requestId.isEmpty else { return }
185
216
  var body: [String: Any] = [
217
+ "type": "rgb_led_control_response",
186
218
  "requestId": requestId,
187
219
  "state": success ? "success" : "error",
188
220
  ]
@@ -66,9 +66,9 @@ struct ViewState {
66
66
  if isPaired {
67
67
  // Device is paired! Don't activate it - let PhoneMic.swift activate when recording starts
68
68
  Bridge.log("Audio: ✅ Mentra Live is paired (preserving A2DP for music)")
69
- glassesBtcConnected = true
69
+ glassesBluetoothClassicConnected = true
70
70
  } else {
71
- glassesBtcConnected = false
71
+ glassesBluetoothClassicConnected = false
72
72
  // Not found in availableInputs - not paired yet
73
73
 
74
74
  // Start monitoring for when user pairs manually
@@ -79,10 +79,10 @@ struct ViewState {
79
79
  if connected {
80
80
  Bridge.log("Audio: ✅ Device paired and connected")
81
81
  // Don't activate - let PhoneMic.swift handle that when recording starts
82
- self.glassesBtcConnected = true
82
+ self.glassesBluetoothClassicConnected = true
83
83
  } else {
84
84
  Bridge.log("Audio: Device disconnected")
85
- self.glassesBtcConnected = false
85
+ self.glassesBluetoothClassicConnected = false
86
86
  }
87
87
  }
88
88
  }
@@ -214,9 +214,9 @@ struct ViewState {
214
214
  set { DeviceStore.shared.apply("bluetooth", "searchingController", newValue) }
215
215
  }
216
216
 
217
- private var glassesBtcConnected: Bool {
218
- get { DeviceStore.shared.get("glasses", "btcConnected") as? Bool ?? false }
219
- set { DeviceStore.shared.apply("glasses", "btcConnected", newValue) }
217
+ private var glassesBluetoothClassicConnected: Bool {
218
+ get { DeviceStore.shared.get("glasses", "bluetoothClassicConnected") as? Bool ?? false }
219
+ set { DeviceStore.shared.apply("glasses", "bluetoothClassicConnected", newValue) }
220
220
  }
221
221
 
222
222
  private var micRanking: [String] {
@@ -506,8 +506,8 @@ struct ViewState {
506
506
 
507
507
  if micEnabled {
508
508
  for micMode in micRanking {
509
- if micMode == MicTypes.PHONE_INTERNAL || micMode == MicTypes.BT_CLASSIC
510
- || micMode == MicTypes.BT
509
+ if micMode == MicTypes.PHONE_INTERNAL || micMode == MicTypes.BLUETOOTH_CLASSIC
510
+ || micMode == MicTypes.BLUETOOTH
511
511
  {
512
512
  if PhoneMic.shared.isRecordingWithMode(micMode) {
513
513
  micUsed = micMode
@@ -572,8 +572,8 @@ struct ViewState {
572
572
  continue
573
573
  }
574
574
 
575
- if micMode == MicTypes.PHONE_INTERNAL || micMode == MicTypes.BT_CLASSIC
576
- || micMode == MicTypes.BT
575
+ if micMode == MicTypes.PHONE_INTERNAL || micMode == MicTypes.BLUETOOTH_CLASSIC
576
+ || micMode == MicTypes.BLUETOOTH
577
577
  {
578
578
  PhoneMic.shared.stopMode(micMode)
579
579
  }
@@ -849,7 +849,7 @@ struct ViewState {
849
849
  Bridge.log("MAN: checkCurrentAudioDevice: audioDevicePattern: \(audioDevicePattern)")
850
850
 
851
851
  if audioDevicePattern.isEmpty || audioDevicePattern == DeviceTypes.SIMULATED {
852
- glassesBtcConnected = false
852
+ glassesBluetoothClassicConnected = false
853
853
  Bridge.log("MAN: Audio device pattern is empty or simulated, returning")
854
854
  return
855
855
  }
@@ -861,7 +861,7 @@ struct ViewState {
861
861
 
862
862
  if !isConnected {
863
863
  Bridge.log("MAN: Device '\(deviceName)' disconnected")
864
- glassesBtcConnected = false
864
+ glassesBluetoothClassicConnected = false
865
865
 
866
866
  let isOtherDeviceConnected = AudioSessionMonitor.isOtherAudioDeviceConnected(
867
867
  devicePattern: audioDevicePattern
@@ -880,9 +880,9 @@ struct ViewState {
880
880
  $0.portName.localizedCaseInsensitiveContains(audioDevicePattern)
881
881
  })?.portName
882
882
  Bridge.log("MAN: ✅ Successfully detected newly paired device '\(deviceName)'")
883
- glassesBtcConnected = true
883
+ glassesBluetoothClassicConnected = true
884
884
  } else {
885
- glassesBtcConnected = false
885
+ glassesBluetoothClassicConnected = false
886
886
  }
887
887
  }
888
888
 
@@ -1120,6 +1120,8 @@ struct ViewState {
1120
1120
  func dbg2() {}
1121
1121
 
1122
1122
  func startStream(_ message: [String: Any]) {
1123
+ var message = message
1124
+ message["flash"] = true
1123
1125
  Bridge.log("MAN: startStream: \(message)")
1124
1126
  sgc?.startStream(message)
1125
1127
  }
@@ -1199,11 +1201,11 @@ struct ViewState {
1199
1201
  sgc?.sendReboot()
1200
1202
  }
1201
1203
 
1202
- func startVideoRecording(_ requestId: String, _ save: Bool, _ flash: Bool, _ sound: Bool) {
1204
+ func startVideoRecording(_ requestId: String, _ save: Bool, _ sound: Bool) {
1203
1205
  Bridge.log(
1204
- "MAN: onStartVideoRecording: requestId=\(requestId), save=\(save), flash=\(flash), sound=\(sound)"
1206
+ "MAN: onStartVideoRecording: requestId=\(requestId), save=\(save), flash=true, sound=\(sound)"
1205
1207
  )
1206
- sgc?.startVideoRecording(requestId: requestId, save: save, flash: flash, sound: sound)
1208
+ sgc?.startVideoRecording(requestId: requestId, save: save, flash: true, sound: sound)
1207
1209
  }
1208
1210
 
1209
1211
  func stopVideoRecording(_ requestId: String) {
@@ -1223,8 +1225,8 @@ struct ViewState {
1223
1225
  packageName: String?,
1224
1226
  action: String,
1225
1227
  color: String?,
1226
- ontime: Int,
1227
- offtime: Int,
1228
+ onDurationMs: Int,
1229
+ offDurationMs: Int,
1228
1230
  count: Int
1229
1231
  ) {
1230
1232
  sgc?.sendRgbLedControl(
@@ -1232,8 +1234,8 @@ struct ViewState {
1232
1234
  packageName: packageName,
1233
1235
  action: action,
1234
1236
  color: color,
1235
- ontime: ontime,
1236
- offtime: offtime,
1237
+ onDurationMs: onDurationMs,
1238
+ offDurationMs: offDurationMs,
1237
1239
  count: count
1238
1240
  )
1239
1241
  }
@@ -1272,22 +1274,21 @@ struct ViewState {
1272
1274
  }
1273
1275
  }
1274
1276
 
1275
- func photoRequest(
1277
+ func requestPhoto(
1276
1278
  _ requestId: String,
1277
1279
  _ appId: String,
1278
1280
  _ size: String,
1279
1281
  _ webhookUrl: String?,
1280
1282
  _ authToken: String?,
1281
1283
  _ compress: String?,
1282
- _ flash: Bool,
1283
1284
  _ sound: Bool
1284
1285
  ) {
1285
1286
  Bridge.log(
1286
- "MAN: onPhotoRequest: \(requestId), \(appId), \(webhookUrl), size=\(size), compress=\(compress ?? "none"), flash=\(flash), sound=\(sound)"
1287
+ "MAN: requestPhoto: \(requestId), \(appId), \(webhookUrl), size=\(size), compress=\(compress ?? "none"), flash=true, sound=\(sound)"
1287
1288
  )
1288
1289
  sgc?.requestPhoto(
1289
1290
  requestId, appId: appId, size: size, webhookUrl: webhookUrl, authToken: authToken,
1290
- compress: compress, flash: flash, sound: sound
1291
+ compress: compress, flash: true, sound: sound
1291
1292
  )
1292
1293
  }
1293
1294
 
@@ -28,7 +28,7 @@ class DeviceStore {
28
28
  store.set("glasses", "deviceModel", "")
29
29
  store.set("glasses", "firmwareVersion", "")
30
30
  store.set("glasses", "micEnabled", false)
31
- store.set("glasses", "btcConnected", false)
31
+ store.set("glasses", "bluetoothClassicConnected", false)
32
32
  store.set("glasses", "caseRemoved", true)
33
33
  store.set("glasses", "caseOpen", true)
34
34
  store.set("glasses", "caseCharging", false)
@@ -80,12 +80,12 @@ class DeviceStore {
80
80
  store.set("bluetooth", "dashboard_depth", 2)
81
81
  store.set("bluetooth", "head_up_angle", 30)
82
82
  store.set("bluetooth", "contextual_dashboard", true)
83
- store.set("bluetooth", "gallery_mode", true)
83
+ store.set("bluetooth", "galleryModeAuto", true)
84
84
  store.set("bluetooth", "screen_disabled", false)
85
85
  store.set("bluetooth", "button_photo_size", "medium")
86
86
  store.set("bluetooth", "button_camera_led", true)
87
87
  store.set("bluetooth", "button_max_recording_time", 10)
88
- store.set("bluetooth", "camera_fov", ["fov": 118, "roi_position": 0])
88
+ store.set("bluetooth", "camera_fov", ["fov": 118, "roiPosition": 0])
89
89
  store.set("bluetooth", "button_video_width", 1280)
90
90
  store.set("bluetooth", "button_video_height", 720)
91
91
  store.set("bluetooth", "button_video_fps", 30)
@@ -214,7 +214,7 @@ class DeviceStore {
214
214
  DeviceManager.shared.sgc?.setDashboardMenu(items)
215
215
  }
216
216
 
217
- case ("bluetooth", "gallery_mode"):
217
+ case ("bluetooth", "galleryModeAuto"):
218
218
  DeviceManager.shared.sgc?.sendGalleryMode()
219
219
 
220
220
  case ("bluetooth", "screen_disabled"):