@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.
- package/README.md +63 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +38 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +88 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +11 -5
- package/app.plugin.js +3 -1
- package/build/BluetoothSdk.types.d.ts +20 -7
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +1 -1
- package/build/_internal.js +1 -1
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +5 -4
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/index.d.ts +4 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +81 -2
- package/build/index.js.map +1 -1
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/react/useMentraBluetooth.d.ts +98 -0
- package/build/react/useMentraBluetooth.d.ts.map +1 -0
- package/build/react/useMentraBluetooth.js +156 -0
- package/build/react/useMentraBluetooth.js.map +1 -0
- package/ios/BluetoothSdkModule.swift +5 -5
- package/ios/Source/Bridge.swift +42 -8
- package/ios/Source/DeviceManager.swift +0 -1
- package/ios/Source/DeviceStore.swift +1 -1
- package/ios/Source/MentraBluetoothSDK.swift +117 -10
- package/package.json +7 -1
- package/src/BluetoothSdk.types.ts +20 -7
- package/src/_internal.ts +1 -1
- package/src/_private/BluetoothSdkModule.ts +7 -5
- package/src/index.ts +93 -4
- package/src/react/index.ts +24 -0
- package/src/react/useBluetoothEvent.ts +38 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +209 -0
- package/src/react/useMentraBluetooth.ts +307 -0
package/src/index.ts
CHANGED
|
@@ -1,10 +1,101 @@
|
|
|
1
|
-
import
|
|
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
|
+
}
|