@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.
- package/README.md +75 -19
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +13 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +42 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +19 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +118 -103
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +110 -38
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +18 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +6 -6
- package/app.plugin.js +3 -1
- package/build/BluetoothSdk.types.d.ts +126 -27
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +12 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +11 -0
- package/build/_internal.js.map +1 -0
- package/build/{BluetoothSdkModule.d.ts → _private/BluetoothSdkModule.d.ts} +19 -8
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/{BluetoothSdkModule.js → _private/BluetoothSdkModule.js} +41 -26
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/index.d.ts +4 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -4
- 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 +5 -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 +140 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/ios/BluetoothSdkModule.swift +14 -9
- package/ios/Source/Bridge.swift +44 -12
- package/ios/Source/DeviceManager.swift +27 -26
- package/ios/Source/DeviceStore.swift +4 -4
- package/ios/Source/MentraBluetoothSDK.swift +250 -132
- package/ios/Source/controllers/ControllerManager.swift +5 -5
- package/ios/Source/controllers/R1.swift +3 -3
- package/ios/Source/services/PhoneMic.swift +5 -5
- package/ios/Source/sgcs/Frame.swift +1 -1
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +3 -3
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +31 -33
- package/ios/Source/sgcs/MentraNex.swift +2 -2
- package/ios/Source/sgcs/SGCManager.swift +5 -5
- package/ios/Source/sgcs/Simulated.swift +3 -3
- package/ios/Source/utils/Constants.swift +6 -6
- package/ios/Source/utils/JSCExperiment.swift +8 -8
- package/ios/Source/utils/TarBz2Extractor.swift +2 -2
- package/package.json +19 -1
- package/src/BluetoothSdk.types.ts +186 -28
- package/src/_internal.ts +11 -0
- package/src/{BluetoothSdkModule.ts → _private/BluetoothSdkModule.ts} +71 -38
- package/src/index.ts +80 -4
- package/src/react/index.ts +22 -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 +194 -0
- package/build/BluetoothSdkModule.d.ts.map +0 -1
- 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("
|
|
317
|
+
AsyncFunction("requestPhoto") {
|
|
312
318
|
(
|
|
313
319
|
requestId: String, appId: String, size: String, webhookUrl: String?,
|
|
314
|
-
authToken: String?, compress: String?,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
435
|
-
|
|
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
|
-
"
|
|
553
|
+
"deviceModel": deviceModel,
|
|
549
554
|
]
|
|
550
555
|
)
|
|
551
556
|
}
|
package/ios/Source/Bridge.swift
CHANGED
|
@@ -126,18 +126,48 @@ class Bridge {
|
|
|
126
126
|
if let rssi {
|
|
127
127
|
newResult["rssi"] = rssi
|
|
128
128
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
let uniqueResults =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
DeviceStore.shared.set("bluetooth", "searchResults",
|
|
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
|
-
"
|
|
157
|
-
"
|
|
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
|
-
"
|
|
177
|
-
"
|
|
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
|
-
|
|
69
|
+
glassesBluetoothClassicConnected = true
|
|
70
70
|
} else {
|
|
71
|
-
|
|
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.
|
|
82
|
+
self.glassesBluetoothClassicConnected = true
|
|
83
83
|
} else {
|
|
84
84
|
Bridge.log("Audio: Device disconnected")
|
|
85
|
-
self.
|
|
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
|
|
218
|
-
get { DeviceStore.shared.get("glasses", "
|
|
219
|
-
set { DeviceStore.shared.apply("glasses", "
|
|
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.
|
|
510
|
-
|| micMode == MicTypes.
|
|
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.
|
|
576
|
-
|| micMode == MicTypes.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
883
|
+
glassesBluetoothClassicConnected = true
|
|
884
884
|
} else {
|
|
885
|
-
|
|
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, _
|
|
1204
|
+
func startVideoRecording(_ requestId: String, _ save: Bool, _ sound: Bool) {
|
|
1203
1205
|
Bridge.log(
|
|
1204
|
-
"MAN: onStartVideoRecording: requestId=\(requestId), save=\(save), flash
|
|
1206
|
+
"MAN: onStartVideoRecording: requestId=\(requestId), save=\(save), flash=true, sound=\(sound)"
|
|
1205
1207
|
)
|
|
1206
|
-
sgc?.startVideoRecording(requestId: requestId, save: save, flash:
|
|
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
|
-
|
|
1227
|
-
|
|
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
|
-
|
|
1236
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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", "
|
|
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", "
|
|
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, "
|
|
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", "
|
|
217
|
+
case ("bluetooth", "galleryModeAuto"):
|
|
218
218
|
DeviceManager.shared.sgc?.sendGalleryMode()
|
|
219
219
|
|
|
220
220
|
case ("bluetooth", "screen_disabled"):
|