@mentra/bluetooth-sdk 0.1.3 → 0.1.5

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 (61) hide show
  1. package/README.md +63 -18
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +5 -5
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +38 -12
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -1
  5. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +88 -3
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +11 -5
  7. package/app.plugin.js +3 -1
  8. package/build/BluetoothSdk.types.d.ts +20 -7
  9. package/build/BluetoothSdk.types.d.ts.map +1 -1
  10. package/build/BluetoothSdk.types.js.map +1 -1
  11. package/build/_internal.d.ts +1 -1
  12. package/build/_internal.js +1 -1
  13. package/build/_internal.js.map +1 -1
  14. package/build/_private/BluetoothSdkModule.d.ts +5 -4
  15. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  16. package/build/_private/BluetoothSdkModule.js +1 -1
  17. package/build/_private/BluetoothSdkModule.js.map +1 -1
  18. package/build/index.d.ts +4 -3
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +81 -2
  21. package/build/index.js.map +1 -1
  22. package/build/react/index.d.ts +5 -0
  23. package/build/react/index.d.ts.map +1 -0
  24. package/build/react/index.js +4 -0
  25. package/build/react/index.js.map +1 -0
  26. package/build/react/useBluetoothEvent.d.ts +6 -0
  27. package/build/react/useBluetoothEvent.d.ts.map +1 -0
  28. package/build/react/useBluetoothEvent.js +21 -0
  29. package/build/react/useBluetoothEvent.js.map +1 -0
  30. package/build/react/useBluetoothScan.d.ts +22 -0
  31. package/build/react/useBluetoothScan.d.ts.map +1 -0
  32. package/build/react/useBluetoothScan.js +135 -0
  33. package/build/react/useBluetoothScan.js.map +1 -0
  34. package/build/react/useBluetoothStatus.d.ts +16 -0
  35. package/build/react/useBluetoothStatus.d.ts.map +1 -0
  36. package/build/react/useBluetoothStatus.js +137 -0
  37. package/build/react/useBluetoothStatus.js.map +1 -0
  38. package/build/react/useGlassesConnection.d.ts +29 -0
  39. package/build/react/useGlassesConnection.d.ts.map +1 -0
  40. package/build/react/useGlassesConnection.js +153 -0
  41. package/build/react/useGlassesConnection.js.map +1 -0
  42. package/build/react/useMentraBluetooth.d.ts +98 -0
  43. package/build/react/useMentraBluetooth.d.ts.map +1 -0
  44. package/build/react/useMentraBluetooth.js +156 -0
  45. package/build/react/useMentraBluetooth.js.map +1 -0
  46. package/ios/BluetoothSdkModule.swift +5 -5
  47. package/ios/Source/Bridge.swift +42 -8
  48. package/ios/Source/DeviceManager.swift +0 -1
  49. package/ios/Source/DeviceStore.swift +1 -1
  50. package/ios/Source/MentraBluetoothSDK.swift +117 -10
  51. package/package.json +7 -1
  52. package/src/BluetoothSdk.types.ts +20 -7
  53. package/src/_internal.ts +1 -1
  54. package/src/_private/BluetoothSdkModule.ts +7 -5
  55. package/src/index.ts +93 -4
  56. package/src/react/index.ts +24 -0
  57. package/src/react/useBluetoothEvent.ts +38 -0
  58. package/src/react/useBluetoothScan.ts +173 -0
  59. package/src/react/useBluetoothStatus.ts +180 -0
  60. package/src/react/useGlassesConnection.ts +209 -0
  61. package/src/react/useMentraBluetooth.ts +307 -0
package/src/index.ts CHANGED
@@ -1,10 +1,101 @@
1
- import {BluetoothSdk} from "./_private/BluetoothSdkModule"
1
+ import PrivateBluetoothSdkModule from "./_private/BluetoothSdkModule"
2
+ import type {
3
+ BluetoothSdkEventListener,
4
+ BluetoothSdkEventName,
5
+ BluetoothSdkPublicModule,
6
+ } from "./BluetoothSdk.types"
7
+
8
+ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
9
+ "log",
10
+ "device_discovered",
11
+ "default_device_changed",
12
+ "glasses_not_ready",
13
+ "button_press",
14
+ "touch_event",
15
+ "head_up",
16
+ "vad_status",
17
+ "battery_status",
18
+ "local_transcription",
19
+ "wifi_status_change",
20
+ "hotspot_status_change",
21
+ "hotspot_error",
22
+ "photo_response",
23
+ "gallery_status",
24
+ "compatible_glasses_search_stop",
25
+ "swipe_volume_status",
26
+ "switch_status",
27
+ "rgb_led_control_response",
28
+ "pair_failure",
29
+ "audio_pairing_needed",
30
+ "audio_connected",
31
+ "audio_disconnected",
32
+ "mic_pcm",
33
+ "mic_lc3",
34
+ "stream_status",
35
+ "keep_alive_ack",
36
+ ])
37
+
38
+ const addListener: BluetoothSdkPublicModule["addListener"] = (eventName, listener) => {
39
+ if (!PUBLIC_EVENT_NAMES.has(eventName)) {
40
+ throw new Error(
41
+ `Unsupported BluetoothSdk event "${eventName}". Use @mentra/bluetooth-sdk/react for status state.`,
42
+ )
43
+ }
44
+ return PrivateBluetoothSdkModule.addListener(
45
+ eventName,
46
+ listener as BluetoothSdkEventListener<BluetoothSdkEventName>,
47
+ )
48
+ }
49
+
50
+ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
51
+ addListener,
52
+ getDefaultDevice: PrivateBluetoothSdkModule.getDefaultDevice.bind(PrivateBluetoothSdkModule),
53
+ setDefaultDevice: PrivateBluetoothSdkModule.setDefaultDevice.bind(PrivateBluetoothSdkModule),
54
+ clearDefaultDevice: PrivateBluetoothSdkModule.clearDefaultDevice.bind(PrivateBluetoothSdkModule),
55
+ startScan: PrivateBluetoothSdkModule.startScan.bind(PrivateBluetoothSdkModule),
56
+ stopScan: PrivateBluetoothSdkModule.stopScan.bind(PrivateBluetoothSdkModule),
57
+ scan: PrivateBluetoothSdkModule.scan.bind(PrivateBluetoothSdkModule) as BluetoothSdkPublicModule["scan"],
58
+ connect: PrivateBluetoothSdkModule.connect.bind(PrivateBluetoothSdkModule),
59
+ connectDefault: PrivateBluetoothSdkModule.connectDefault.bind(PrivateBluetoothSdkModule),
60
+ cancelConnectionAttempt: PrivateBluetoothSdkModule.cancelConnectionAttempt.bind(PrivateBluetoothSdkModule),
61
+ disconnect: PrivateBluetoothSdkModule.disconnect.bind(PrivateBluetoothSdkModule),
62
+ forget: PrivateBluetoothSdkModule.forget.bind(PrivateBluetoothSdkModule),
63
+ displayText: PrivateBluetoothSdkModule.displayText.bind(PrivateBluetoothSdkModule),
64
+ clearDisplay: PrivateBluetoothSdkModule.clearDisplay.bind(PrivateBluetoothSdkModule),
65
+ showDashboard: PrivateBluetoothSdkModule.showDashboard.bind(PrivateBluetoothSdkModule),
66
+ setDashboardPosition: PrivateBluetoothSdkModule.setDashboardPosition.bind(PrivateBluetoothSdkModule),
67
+ setHeadUpAngle: PrivateBluetoothSdkModule.setHeadUpAngle.bind(PrivateBluetoothSdkModule),
68
+ setScreenDisabled: PrivateBluetoothSdkModule.setScreenDisabled.bind(PrivateBluetoothSdkModule),
69
+ requestWifiScan: PrivateBluetoothSdkModule.requestWifiScan.bind(PrivateBluetoothSdkModule),
70
+ sendWifiCredentials: PrivateBluetoothSdkModule.sendWifiCredentials.bind(PrivateBluetoothSdkModule),
71
+ forgetWifiNetwork: PrivateBluetoothSdkModule.forgetWifiNetwork.bind(PrivateBluetoothSdkModule),
72
+ setHotspotState: PrivateBluetoothSdkModule.setHotspotState.bind(PrivateBluetoothSdkModule),
73
+ setGalleryMode: PrivateBluetoothSdkModule.setGalleryMode.bind(PrivateBluetoothSdkModule),
74
+ setButtonPhotoSettings: PrivateBluetoothSdkModule.setButtonPhotoSettings.bind(PrivateBluetoothSdkModule),
75
+ setButtonVideoRecordingSettings: PrivateBluetoothSdkModule.setButtonVideoRecordingSettings.bind(PrivateBluetoothSdkModule),
76
+ setButtonCameraLed: PrivateBluetoothSdkModule.setButtonCameraLed.bind(PrivateBluetoothSdkModule),
77
+ setButtonMaxRecordingTime: PrivateBluetoothSdkModule.setButtonMaxRecordingTime.bind(PrivateBluetoothSdkModule),
78
+ setCameraFov: PrivateBluetoothSdkModule.setCameraFov.bind(PrivateBluetoothSdkModule),
79
+ queryGalleryStatus: PrivateBluetoothSdkModule.queryGalleryStatus.bind(PrivateBluetoothSdkModule),
80
+ requestPhoto: PrivateBluetoothSdkModule.requestPhoto.bind(PrivateBluetoothSdkModule),
81
+ startVideoRecording: PrivateBluetoothSdkModule.startVideoRecording.bind(PrivateBluetoothSdkModule),
82
+ stopVideoRecording: PrivateBluetoothSdkModule.stopVideoRecording.bind(PrivateBluetoothSdkModule),
83
+ startStream: PrivateBluetoothSdkModule.startStream.bind(PrivateBluetoothSdkModule),
84
+ stopStream: PrivateBluetoothSdkModule.stopStream.bind(PrivateBluetoothSdkModule),
85
+ keepStreamAlive: PrivateBluetoothSdkModule.keepStreamAlive.bind(PrivateBluetoothSdkModule),
86
+ setMicState: PrivateBluetoothSdkModule.setMicState.bind(PrivateBluetoothSdkModule),
87
+ setPreferredMic: PrivateBluetoothSdkModule.setPreferredMic.bind(PrivateBluetoothSdkModule),
88
+ setOwnAppAudioPlaying: PrivateBluetoothSdkModule.setOwnAppAudioPlaying.bind(PrivateBluetoothSdkModule),
89
+ getGlassesMediaVolume: PrivateBluetoothSdkModule.getGlassesMediaVolume.bind(PrivateBluetoothSdkModule),
90
+ setGlassesMediaVolume: PrivateBluetoothSdkModule.setGlassesMediaVolume.bind(PrivateBluetoothSdkModule),
91
+ rgbLedControl: PrivateBluetoothSdkModule.rgbLedControl.bind(PrivateBluetoothSdkModule),
92
+ requestVersionInfo: PrivateBluetoothSdkModule.requestVersionInfo.bind(PrivateBluetoothSdkModule),
93
+ })
2
94
 
3
95
  export default BluetoothSdk
4
96
 
5
97
  export {
6
98
  DeviceModels,
7
- createDisconnectedGlassesStatus,
8
99
  isBusyGlassesConnectionStatus,
9
100
  isConnectedGlassesConnectionStatus,
10
101
  isConnectedWifiStatus,
@@ -54,8 +145,6 @@ export type {
54
145
  PhotoCompression,
55
146
  PhotoResponseEvent,
56
147
  PhotoSize,
57
- PublicBluetoothStatus as BluetoothStatus,
58
- PublicGlassesStatus as GlassesStatus,
59
148
  RgbLedAction,
60
149
  RgbLedColor,
61
150
  RgbLedControlResponseEvent,
@@ -0,0 +1,24 @@
1
+ export {
2
+ useBluetoothEvent,
3
+ type UseBluetoothEventOptions,
4
+ } from "./useBluetoothEvent"
5
+ export {
6
+ useBluetoothScan,
7
+ type BluetoothScanDedupe,
8
+ type BluetoothScanHookResult,
9
+ type UseBluetoothScanOptions,
10
+ } from "./useBluetoothScan"
11
+ export type {DefaultDeviceStorage} from "./useGlassesConnection"
12
+ export {
13
+ useMentraBluetooth,
14
+ type BatteryState,
15
+ type ConnectedGlassesInfo,
16
+ type FirmwareInfo,
17
+ type GalleryModeState,
18
+ type GlassesRuntimeState,
19
+ type MentraBluetoothSession,
20
+ type PhoneSdkRuntimeState,
21
+ type ScanController,
22
+ type SignalState,
23
+ type UseMentraBluetoothOptions,
24
+ } from "./useMentraBluetooth"
@@ -0,0 +1,38 @@
1
+ import {useEffect, useRef} from "react"
2
+
3
+ import BluetoothSdk from "../index"
4
+ import type {
5
+ BluetoothSdkEventListener,
6
+ BluetoothSdkEventName,
7
+ } from "../BluetoothSdk.types"
8
+
9
+ export type UseBluetoothEventOptions = {
10
+ enabled?: boolean
11
+ }
12
+
13
+ export function useBluetoothEvent<EventName extends BluetoothSdkEventName>(
14
+ eventName: EventName,
15
+ listener: BluetoothSdkEventListener<EventName>,
16
+ options: UseBluetoothEventOptions = {},
17
+ ): void {
18
+ const enabled = options.enabled ?? true
19
+ const listenerRef = useRef(listener)
20
+
21
+ useEffect(() => {
22
+ listenerRef.current = listener
23
+ }, [listener])
24
+
25
+ useEffect(() => {
26
+ if (!enabled) {
27
+ return undefined
28
+ }
29
+
30
+ const subscription = BluetoothSdk.addListener(eventName, (event) => {
31
+ listenerRef.current(event)
32
+ })
33
+
34
+ return () => {
35
+ subscription.remove()
36
+ }
37
+ }, [enabled, eventName])
38
+ }
@@ -0,0 +1,173 @@
1
+ import {useEffect, useRef, useState} from "react"
2
+
3
+ import BluetoothSdk, {DeviceModels} from "../index"
4
+ import type {Device, DeviceModel} from "../BluetoothSdk.types"
5
+
6
+ export type BluetoothScanDedupe = "id" | "name" | ((device: Device) => string)
7
+
8
+ export type UseBluetoothScanOptions = {
9
+ dedupe?: BluetoothScanDedupe
10
+ model?: DeviceModel
11
+ onError?: (error: unknown) => void
12
+ timeoutMs?: number
13
+ }
14
+
15
+ export type BluetoothScanHookResult = {
16
+ clearResults: () => void
17
+ devices: Device[]
18
+ error: unknown | null
19
+ model: DeviceModel
20
+ scanning: boolean
21
+ selectedDevice: Device | null
22
+ selectDevice: (device: Device | null) => void
23
+ setModel: (model: DeviceModel) => void
24
+ startScan: (model?: DeviceModel) => Promise<Device[]>
25
+ stopScan: () => Promise<void>
26
+ }
27
+
28
+ function dedupeKey(device: Device, dedupe: BluetoothScanDedupe): string {
29
+ if (typeof dedupe === "function") {
30
+ return dedupe(device)
31
+ }
32
+ if (dedupe === "name") {
33
+ return `${device.model}:${device.name}`
34
+ }
35
+ return device.id
36
+ }
37
+
38
+ function dedupeDevices(devices: Device[], dedupe: BluetoothScanDedupe): Device[] {
39
+ const seen = new Set<string>()
40
+ const nextDevices: Device[] = []
41
+
42
+ devices.forEach((device) => {
43
+ const key = dedupeKey(device, dedupe)
44
+ if (seen.has(key)) {
45
+ return
46
+ }
47
+ seen.add(key)
48
+ nextDevices.push(device)
49
+ })
50
+
51
+ return nextDevices
52
+ }
53
+
54
+ function hasDevice(devices: Device[], selectedDevice: Device | null, dedupe: BluetoothScanDedupe): boolean {
55
+ if (!selectedDevice) {
56
+ return true
57
+ }
58
+ const selectedKey = dedupeKey(selectedDevice, dedupe)
59
+ return devices.some((device) => dedupeKey(device, dedupe) === selectedKey)
60
+ }
61
+
62
+ export function useBluetoothScan(options: UseBluetoothScanOptions = {}): BluetoothScanHookResult {
63
+ const [devices, setDevices] = useState<Device[]>([])
64
+ const [error, setError] = useState<unknown | null>(null)
65
+ const [model, setModelState] = useState<DeviceModel>(options.model ?? DeviceModels.MentraLive)
66
+ const [scanning, setScanning] = useState(false)
67
+ const [selectedDevice, selectDevice] = useState<Device | null>(null)
68
+ const activeScanRef = useRef(0)
69
+ const dedupeRef = useRef<BluetoothScanDedupe>(options.dedupe ?? "id")
70
+ const onErrorRef = useRef(options.onError)
71
+ const scanningRef = useRef(false)
72
+ const timeoutMsRef = useRef(options.timeoutMs)
73
+
74
+ useEffect(() => {
75
+ dedupeRef.current = options.dedupe ?? "id"
76
+ onErrorRef.current = options.onError
77
+ timeoutMsRef.current = options.timeoutMs
78
+ }, [options.dedupe, options.onError, options.timeoutMs])
79
+
80
+ useEffect(() => {
81
+ scanningRef.current = scanning
82
+ }, [scanning])
83
+
84
+ useEffect(() => {
85
+ if (!hasDevice(devices, selectedDevice, dedupeRef.current)) {
86
+ selectDevice(null)
87
+ }
88
+ }, [devices, selectedDevice])
89
+
90
+ useEffect(() => {
91
+ return () => {
92
+ if (!scanningRef.current) {
93
+ return
94
+ }
95
+ activeScanRef.current += 1
96
+ void BluetoothSdk.stopScan().catch(() => undefined)
97
+ }
98
+ }, [])
99
+
100
+ function clearResults() {
101
+ setDevices([])
102
+ selectDevice(null)
103
+ }
104
+
105
+ function setModel(nextModel: DeviceModel) {
106
+ setModelState(nextModel)
107
+ clearResults()
108
+ }
109
+
110
+ async function startScan(nextModel?: DeviceModel): Promise<Device[]> {
111
+ const scanId = activeScanRef.current + 1
112
+ activeScanRef.current = scanId
113
+ const scanModel = nextModel ?? model
114
+ if (nextModel && nextModel !== model) {
115
+ setModelState(nextModel)
116
+ }
117
+
118
+ setError(null)
119
+ setScanning(true)
120
+ scanningRef.current = true
121
+ setDevices([])
122
+ selectDevice(null)
123
+
124
+ try {
125
+ const nextDevices = await BluetoothSdk.scan(scanModel, {
126
+ ...(timeoutMsRef.current == null ? {} : {timeoutMs: timeoutMsRef.current}),
127
+ onResults: (results) => {
128
+ if (activeScanRef.current !== scanId) {
129
+ return
130
+ }
131
+ setDevices(dedupeDevices(results, dedupeRef.current))
132
+ },
133
+ })
134
+ const finalDevices = dedupeDevices(nextDevices, dedupeRef.current)
135
+ if (activeScanRef.current === scanId) {
136
+ setDevices(finalDevices)
137
+ setError(null)
138
+ }
139
+ return finalDevices
140
+ } catch (nextError) {
141
+ if (activeScanRef.current === scanId) {
142
+ setError(nextError)
143
+ onErrorRef.current?.(nextError)
144
+ }
145
+ throw nextError
146
+ } finally {
147
+ if (activeScanRef.current === scanId) {
148
+ setScanning(false)
149
+ scanningRef.current = false
150
+ }
151
+ }
152
+ }
153
+
154
+ async function stopScan() {
155
+ activeScanRef.current += 1
156
+ setScanning(false)
157
+ scanningRef.current = false
158
+ await BluetoothSdk.stopScan()
159
+ }
160
+
161
+ return {
162
+ clearResults,
163
+ devices,
164
+ error,
165
+ model,
166
+ scanning,
167
+ selectedDevice,
168
+ selectDevice,
169
+ setModel,
170
+ startScan,
171
+ stopScan,
172
+ }
173
+ }
@@ -0,0 +1,180 @@
1
+ import {useEffect, useRef, useState} from "react"
2
+
3
+ import BluetoothSdk from "../_private/BluetoothSdkModule"
4
+ import {
5
+ createDisconnectedGlassesStatus,
6
+ isConnectedGlassesConnectionStatus,
7
+ isReadyGlassesConnectionStatus,
8
+ } from "../BluetoothSdk.types"
9
+ import type {
10
+ BatteryStatusEvent,
11
+ HotspotStatus,
12
+ HotspotStatusChangeEvent,
13
+ PublicBluetoothStatus,
14
+ PublicGlassesStatus,
15
+ WifiStatus,
16
+ WifiStatusChangeEvent,
17
+ } from "../BluetoothSdk.types"
18
+
19
+ export type UseBluetoothStatusOptions = {
20
+ enabled?: boolean
21
+ onError?: (error: unknown) => void
22
+ }
23
+
24
+ export type BluetoothStatusHookResult = {
25
+ bluetoothStatus: Partial<PublicBluetoothStatus>
26
+ connected: boolean
27
+ error: unknown | null
28
+ glassesStatus: Partial<PublicGlassesStatus>
29
+ loading: boolean
30
+ ready: boolean
31
+ refresh: () => Promise<void>
32
+ }
33
+
34
+ function wifiStatusFromEvent(event: WifiStatusChangeEvent): WifiStatus {
35
+ switch (event.state) {
36
+ case "connected":
37
+ return {state: "connected", ssid: event.ssid, localIp: event.localIp}
38
+ case "disconnected":
39
+ return {state: "disconnected"}
40
+ }
41
+ }
42
+
43
+ function hotspotStatusFromEvent(event: HotspotStatusChangeEvent): HotspotStatus {
44
+ if (event.state === "enabled") {
45
+ return {
46
+ state: "enabled",
47
+ ssid: event.ssid,
48
+ password: event.password,
49
+ localIp: event.localIp,
50
+ }
51
+ }
52
+ return {state: event.state}
53
+ }
54
+
55
+ function mergeGlassesStatus(
56
+ current: Partial<PublicGlassesStatus>,
57
+ changed: Partial<PublicGlassesStatus>,
58
+ ): Partial<PublicGlassesStatus> {
59
+ if (changed.connection?.state === "disconnected") {
60
+ return {...createDisconnectedGlassesStatus(), ...changed}
61
+ }
62
+ return {...current, ...changed}
63
+ }
64
+
65
+ export function useBluetoothStatus(options: UseBluetoothStatusOptions = {}): BluetoothStatusHookResult {
66
+ const enabled = options.enabled ?? true
67
+ const onErrorRef = useRef(options.onError)
68
+ const [bluetoothStatus, setBluetoothStatus] = useState<Partial<PublicBluetoothStatus>>({})
69
+ const [error, setError] = useState<unknown | null>(null)
70
+ const [glassesStatus, setGlassesStatus] = useState<Partial<PublicGlassesStatus>>(() =>
71
+ createDisconnectedGlassesStatus(),
72
+ )
73
+ const [loading, setLoading] = useState(enabled)
74
+
75
+ useEffect(() => {
76
+ onErrorRef.current = options.onError
77
+ }, [options.onError])
78
+
79
+ async function refresh() {
80
+ setLoading(true)
81
+ try {
82
+ const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([
83
+ BluetoothSdk.getGlassesStatus(),
84
+ BluetoothSdk.getBluetoothStatus(),
85
+ ])
86
+ setGlassesStatus(nextGlassesStatus)
87
+ setBluetoothStatus(nextBluetoothStatus)
88
+ setError(null)
89
+ } catch (nextError) {
90
+ setError(nextError)
91
+ onErrorRef.current?.(nextError)
92
+ } finally {
93
+ setLoading(false)
94
+ }
95
+ }
96
+
97
+ useEffect(() => {
98
+ if (!enabled) {
99
+ setLoading(false)
100
+ return undefined
101
+ }
102
+
103
+ let mounted = true
104
+
105
+ const loadInitialStatus = async () => {
106
+ setLoading(true)
107
+ try {
108
+ const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([
109
+ BluetoothSdk.getGlassesStatus(),
110
+ BluetoothSdk.getBluetoothStatus(),
111
+ ])
112
+ if (!mounted) {
113
+ return
114
+ }
115
+ setGlassesStatus(nextGlassesStatus)
116
+ setBluetoothStatus(nextBluetoothStatus)
117
+ setError(null)
118
+ } catch (nextError) {
119
+ if (!mounted) {
120
+ return
121
+ }
122
+ setError(nextError)
123
+ onErrorRef.current?.(nextError)
124
+ } finally {
125
+ if (mounted) {
126
+ setLoading(false)
127
+ }
128
+ }
129
+ }
130
+
131
+ void loadInitialStatus()
132
+
133
+ const removeGlasses = BluetoothSdk.onGlassesStatus((changed) => {
134
+ setGlassesStatus((current) => mergeGlassesStatus(current, changed))
135
+ })
136
+ const removeBluetooth = BluetoothSdk.onBluetoothStatus((changed) => {
137
+ setBluetoothStatus((current) => ({...current, ...changed}))
138
+ })
139
+ const batterySubscription = BluetoothSdk.addListener("battery_status", (event: BatteryStatusEvent) => {
140
+ setGlassesStatus((current) => ({
141
+ ...current,
142
+ batteryLevel: event.level,
143
+ charging: event.charging,
144
+ }))
145
+ })
146
+ const wifiSubscription = BluetoothSdk.addListener("wifi_status_change", (event) => {
147
+ setGlassesStatus((current) => ({...current, wifi: wifiStatusFromEvent(event)}))
148
+ })
149
+ const hotspotSubscription = BluetoothSdk.addListener("hotspot_status_change", (event) => {
150
+ setGlassesStatus((current) => ({...current, hotspot: hotspotStatusFromEvent(event)}))
151
+ })
152
+ const hotspotErrorSubscription = BluetoothSdk.addListener("hotspot_error", () => {
153
+ setGlassesStatus((current) => ({...current, hotspot: {state: "disabled"}}))
154
+ })
155
+
156
+ return () => {
157
+ mounted = false
158
+ removeGlasses()
159
+ removeBluetooth()
160
+ batterySubscription.remove()
161
+ wifiSubscription.remove()
162
+ hotspotSubscription.remove()
163
+ hotspotErrorSubscription.remove()
164
+ }
165
+ }, [enabled])
166
+
167
+ const connection = glassesStatus.connection
168
+ const connected = connection ? isConnectedGlassesConnectionStatus(connection) : false
169
+ const ready = connection ? isReadyGlassesConnectionStatus(connection) : false
170
+
171
+ return {
172
+ bluetoothStatus,
173
+ connected,
174
+ error,
175
+ glassesStatus,
176
+ loading,
177
+ ready,
178
+ refresh,
179
+ }
180
+ }