@glassly/engine 0.3.5-dev.842 → 0.3.5-dev.844
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/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +34 -30
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +3 -2
- package/build/facades/pairing.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/ScreenshotFixture.d.ts +56 -0
- package/build/services/ScreenshotFixture.d.ts.map +1 -0
- package/build/services/ScreenshotFixture.js +72 -0
- package/build/services/ScreenshotFixture.js.map +1 -0
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +3 -0
- package/build/stores/settings.js.map +1 -1
- package/package.json +7 -7
- package/src/facades/glasses.ts +58 -42
- package/src/facades/pairing.ts +4 -2
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/ScreenshotFixture.ts +113 -0
- package/src/stores/settings.ts +3 -0
package/src/facades/glasses.ts
CHANGED
|
@@ -27,6 +27,13 @@ import {useSettingsStore, SETTINGS} from "../stores/settings"
|
|
|
27
27
|
import {hasDefaultDevice} from "../services/DeviceStoreHydration"
|
|
28
28
|
import micStateCoordinator from "../services/MicStateCoordinator"
|
|
29
29
|
import {projectPairingIdentity} from "../services/PairingIdentity"
|
|
30
|
+
import {
|
|
31
|
+
fixtureControllerList,
|
|
32
|
+
fixtureControllerStatuses,
|
|
33
|
+
fixtureInfo,
|
|
34
|
+
fixtureStatus,
|
|
35
|
+
isScreenshotMode,
|
|
36
|
+
} from "../services/ScreenshotFixture"
|
|
30
37
|
import {isGlassesConnected, isGlassesReady} from "../services/GlassesReadiness"
|
|
31
38
|
import {pushAllBluetoothSettings} from "../services/GlassesSettingsSync"
|
|
32
39
|
import {isControllerReady, pairedControllersFrom, readyControllers} from "../services/controllers"
|
|
@@ -43,25 +50,33 @@ import {glassesSettings} from "./glassesSettings"
|
|
|
43
50
|
|
|
44
51
|
let micLevelSubscriptions = 0
|
|
45
52
|
|
|
53
|
+
// The screenshot fixture (ScreenshotFixture.ts) dresses these read-models
|
|
54
|
+
// while the identity is the simulated device; it is a no-op outside that build.
|
|
46
55
|
function projectStatus() {
|
|
47
56
|
const s = useGlassesStore.getState()
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
return fixtureStatus(
|
|
58
|
+
{
|
|
59
|
+
state: s.connection.state,
|
|
60
|
+
fullyBooted: isGlassesReady(s.connection),
|
|
61
|
+
battery: s.batteryLevel,
|
|
62
|
+
charging: s.charging,
|
|
63
|
+
case: {battery: s.caseBatteryLevel, charging: s.caseCharging, open: s.caseOpen, removed: s.caseRemoved},
|
|
64
|
+
signal: s.signalStrength,
|
|
65
|
+
micEnabled: s.micEnabled,
|
|
66
|
+
vadEnabled: s.voiceActivityDetectionEnabled,
|
|
67
|
+
btClassic: s.bluetoothClassicConnected,
|
|
68
|
+
// iOS: glasses may read the phone's ANCS stream ("Share System Notifications").
|
|
69
|
+
ancsAuthorized: Boolean(s.ancsAuthorized),
|
|
70
|
+
},
|
|
71
|
+
projectPairingIdentity(),
|
|
72
|
+
)
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
function projectControllerList(): PairedController[] {
|
|
64
|
-
return
|
|
76
|
+
return fixtureControllerList(
|
|
77
|
+
pairedControllersFrom(useSettingsStore.getState().getSetting(SETTINGS.controllers.key)),
|
|
78
|
+
projectPairingIdentity(),
|
|
79
|
+
)
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
// Copies: snapshots must not hand callers mutable references into the store.
|
|
@@ -70,7 +85,27 @@ function projectControllerStatuses(): Record<string, ControllerStatus> {
|
|
|
70
85
|
for (const [id, status] of Object.entries(useGlassesStore.getState().controllers ?? {})) {
|
|
71
86
|
statuses[id] = {...status}
|
|
72
87
|
}
|
|
73
|
-
return statuses
|
|
88
|
+
return fixtureControllerStatuses(statuses, projectPairingIdentity())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Dedupe on the projected snapshot: the store updates on many fields; fire
|
|
92
|
+
// only when the projection changes. The fixture's read-models also follow the
|
|
93
|
+
// identity (a settings key), so that build watches the settings store too.
|
|
94
|
+
function subscribeProjection<T>(project: () => T, cb: (snapshot: T) => void): () => void {
|
|
95
|
+
let last = JSON.stringify(project())
|
|
96
|
+
const notify = () => {
|
|
97
|
+
const snap = project()
|
|
98
|
+
const key = JSON.stringify(snap)
|
|
99
|
+
if (key === last) return
|
|
100
|
+
last = key
|
|
101
|
+
cb(snap)
|
|
102
|
+
}
|
|
103
|
+
const unsubscribeGlasses = useGlassesStore.subscribe(notify)
|
|
104
|
+
const unsubscribeSettings = isScreenshotMode() ? useSettingsStore.subscribe(notify) : () => {}
|
|
105
|
+
return () => {
|
|
106
|
+
unsubscribeGlasses()
|
|
107
|
+
unsubscribeSettings()
|
|
108
|
+
}
|
|
74
109
|
}
|
|
75
110
|
|
|
76
111
|
function projectKeyboardStatus() {
|
|
@@ -89,6 +124,10 @@ function projectKeyboardStatus() {
|
|
|
89
124
|
|
|
90
125
|
function projectInfo() {
|
|
91
126
|
const s = useGlassesStore.getState()
|
|
127
|
+
return fixtureInfo(projectRawInfo(s), projectPairingIdentity())
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function projectRawInfo(s: ReturnType<typeof useGlassesStore.getState>) {
|
|
92
131
|
return {
|
|
93
132
|
model: s.deviceModel,
|
|
94
133
|
style: s.style,
|
|
@@ -191,29 +230,9 @@ export const glasses = {
|
|
|
191
230
|
|
|
192
231
|
// --- read-model (projected from the engine-owned glasses store) ---
|
|
193
232
|
status: (): GlassesStatusSnapshot => projectStatus(),
|
|
194
|
-
onStatus: (cb: (status: GlassesStatusSnapshot) => void): (() => void) =>
|
|
195
|
-
// Dedupe: the glasses store updates on many fields; only fire when the
|
|
196
|
-
// projected status snapshot actually changes.
|
|
197
|
-
let last = JSON.stringify(projectStatus())
|
|
198
|
-
return useGlassesStore.subscribe(() => {
|
|
199
|
-
const snap = projectStatus()
|
|
200
|
-
const key = JSON.stringify(snap)
|
|
201
|
-
if (key === last) return
|
|
202
|
-
last = key
|
|
203
|
-
cb(snap)
|
|
204
|
-
})
|
|
205
|
-
},
|
|
233
|
+
onStatus: (cb: (status: GlassesStatusSnapshot) => void): (() => void) => subscribeProjection(projectStatus, cb),
|
|
206
234
|
info: (): GlassesInfoSnapshot => projectInfo(),
|
|
207
|
-
onInfo: (cb: (info: GlassesInfoSnapshot) => void): (() => void) =>
|
|
208
|
-
let last = JSON.stringify(projectInfo())
|
|
209
|
-
return useGlassesStore.subscribe(() => {
|
|
210
|
-
const snap = projectInfo()
|
|
211
|
-
const key = JSON.stringify(snap)
|
|
212
|
-
if (key === last) return
|
|
213
|
-
last = key
|
|
214
|
-
cb(snap)
|
|
215
|
-
})
|
|
216
|
-
},
|
|
235
|
+
onInfo: (cb: (info: GlassesInfoSnapshot) => void): (() => void) => subscribeProjection(projectInfo, cb),
|
|
217
236
|
capabilities: () => getModelCapabilities(useGlassesStore.getState().deviceModel as DeviceTypes),
|
|
218
237
|
/** Ask the glasses to report fresh firmware/version info (updates the store). */
|
|
219
238
|
requestVersionInfo: () => BluetoothSdk.requestVersionInfo(),
|
|
@@ -284,14 +303,11 @@ export const glasses = {
|
|
|
284
303
|
statuses: (): Record<string, ControllerStatus> => projectControllerStatuses(),
|
|
285
304
|
status: (id: string): ControllerStatus | undefined => projectControllerStatuses()[id],
|
|
286
305
|
onStatus: (cb: (statuses: Record<string, ControllerStatus>) => void): (() => void) =>
|
|
287
|
-
|
|
288
|
-
(s) => s.controllers,
|
|
289
|
-
() => cb(projectControllerStatuses()),
|
|
290
|
-
),
|
|
306
|
+
subscribeProjection(projectControllerStatuses, cb),
|
|
291
307
|
/** Controllers delivering input (linked, route or claim done). */
|
|
292
308
|
connected: (): ControllerStatus[] => readyControllers(projectControllerStatuses()),
|
|
293
309
|
isConnected: (model?: string): boolean =>
|
|
294
|
-
Object.values(
|
|
310
|
+
Object.values(projectControllerStatuses()).some(
|
|
295
311
|
(status) => isControllerReady(status) && (model === undefined || status.model === model),
|
|
296
312
|
),
|
|
297
313
|
connect: (id: string): Promise<void> => BluetoothSdk.connectController(id),
|
package/src/facades/pairing.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
subscribePairingIdentity,
|
|
22
22
|
type IdentitySnapshot,
|
|
23
23
|
} from "../services/PairingIdentity"
|
|
24
|
+
import {fixtureIdentity} from "../services/ScreenshotFixture"
|
|
24
25
|
import {isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady} from "../services/GlassesReadiness"
|
|
25
26
|
import {
|
|
26
27
|
logAutomaticReportSubmissionStatus,
|
|
@@ -278,10 +279,11 @@ export const pairing = {
|
|
|
278
279
|
// --- pairing-identity lifecycle (the PairingIdentity read-model + the JS-owned
|
|
279
280
|
// identity writes; promotion to `paired` only ever happens natively) ---
|
|
280
281
|
/** The identity lifecycle snapshot: none | pending (chosen, never paired) | paired. */
|
|
281
|
-
identity: (): IdentitySnapshot => projectPairingIdentity(),
|
|
282
|
+
identity: (): IdentitySnapshot => fixtureIdentity(projectPairingIdentity()),
|
|
282
283
|
/** Subscribe to identity changes; fires only when the projected snapshot changes.
|
|
283
284
|
* Returns an unsubscribe. */
|
|
284
|
-
onIdentity: (cb: (identity: IdentitySnapshot) => void): (() => void) =>
|
|
285
|
+
onIdentity: (cb: (identity: IdentitySnapshot) => void): (() => void) =>
|
|
286
|
+
subscribePairingIdentity((identity) => cb(fixtureIdentity(identity))),
|
|
285
287
|
/** Mark the chosen model as the pending selection (the scan-entry write); the
|
|
286
288
|
* host renders it as a finish-pairing affordance until pairing succeeds. */
|
|
287
289
|
markPendingSelection: (model: string) => markPendingSelection(model),
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.3.5",
|
|
15
|
-
"releaseIdentity": "0.3.5-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.3.5-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.5-dev.844",
|
|
16
|
+
"releaseSetId": "glassly-0.3.5-dev.844",
|
|
17
|
+
"sourceCommit": "12cbc9656d02c3499e9014c86d19325018d6da4c",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot fixture — dresses Phone Mode up as a paired Even Realities G2
|
|
3
|
+
* with a Meta Neural Band and an R1 ring, for the store-screenshot build only
|
|
4
|
+
* (EXPO_PUBLIC_SCREENSHOT_MODE=1, set by scripts/maestro-screenshots-*.mjs
|
|
5
|
+
* for its own Release build and never by a shipped one).
|
|
6
|
+
*
|
|
7
|
+
* The fixture is applied where hosts READ — engine.pairing.identity and
|
|
8
|
+
* engine.glasses.status/info/controllers.* — not where native writes: the
|
|
9
|
+
* Simulated driver keeps running underneath (so the mirror has frames),
|
|
10
|
+
* nothing is persisted, and native never learns of the fixture, so no
|
|
11
|
+
* reconnect, demotion or driver path can act on it. It is active only while
|
|
12
|
+
* the underlying identity is the simulated device: home enters Phone Mode as
|
|
13
|
+
* usual (identity `none` passes through untouched), the simulated pairing
|
|
14
|
+
* then flips the read-models to the fixture, and real glasses on a fixture
|
|
15
|
+
* build stay real.
|
|
16
|
+
*/
|
|
17
|
+
import type {ControllerStatus, PairedController} from "@glassly/bluetooth-sdk"
|
|
18
|
+
|
|
19
|
+
import {ControllerTypes, DeviceTypes} from "../types/enums"
|
|
20
|
+
import type {IdentitySnapshot} from "./PairingIdentity"
|
|
21
|
+
|
|
22
|
+
export function isScreenshotMode(): boolean {
|
|
23
|
+
return process.env.EXPO_PUBLIC_SCREENSHOT_MODE === "1"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const FIXTURE_GLASSES = {
|
|
27
|
+
model: DeviceTypes.G2,
|
|
28
|
+
name: "Even G2_7F3A",
|
|
29
|
+
battery: 87,
|
|
30
|
+
caseBattery: 100,
|
|
31
|
+
signal: -58,
|
|
32
|
+
} as const
|
|
33
|
+
|
|
34
|
+
interface FixtureController extends PairedController {
|
|
35
|
+
batteryLevel: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The R1's id is its ring id (from the BLE name); the band's is an opaque
|
|
39
|
+
// peripheral id. Both are stable fixtures so flows can target
|
|
40
|
+
// `controller-tile-<id>`.
|
|
41
|
+
export const FIXTURE_CONTROLLERS: readonly FixtureController[] = [
|
|
42
|
+
{id: "7C1A2B9E", model: ControllerTypes.NEURAL_BAND, name: "Meta Neural Band", batteryLevel: 73},
|
|
43
|
+
{id: "CEC5BA", model: ControllerTypes.R1, name: "CEC5BA", batteryLevel: 58},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
/** The fixture dresses Phone Mode only. */
|
|
47
|
+
export function fixtureActive(identity: IdentitySnapshot): boolean {
|
|
48
|
+
return isScreenshotMode() && identity.kind === "paired" && identity.model.includes(DeviceTypes.SIMULATED)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function fixtureIdentity(identity: IdentitySnapshot): IdentitySnapshot {
|
|
52
|
+
if (!fixtureActive(identity)) return identity
|
|
53
|
+
return {kind: "paired", model: FIXTURE_GLASSES.model, name: FIXTURE_GLASSES.name}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface StatusLike {
|
|
57
|
+
battery: number
|
|
58
|
+
charging: boolean
|
|
59
|
+
case: {battery: number; charging: boolean; open: boolean; removed: boolean}
|
|
60
|
+
signal: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function fixtureStatus<T extends StatusLike>(status: T, identity: IdentitySnapshot): T {
|
|
64
|
+
if (!fixtureActive(identity)) return status
|
|
65
|
+
return {
|
|
66
|
+
...status,
|
|
67
|
+
battery: FIXTURE_GLASSES.battery,
|
|
68
|
+
charging: false,
|
|
69
|
+
case: {battery: FIXTURE_GLASSES.caseBattery, charging: false, open: false, removed: false},
|
|
70
|
+
signal: FIXTURE_GLASSES.signal,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface InfoLike {
|
|
75
|
+
model: string
|
|
76
|
+
firmwareUpdateAvailable: boolean
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function fixtureInfo<T extends InfoLike>(info: T, identity: IdentitySnapshot): T {
|
|
80
|
+
if (!fixtureActive(identity)) return info
|
|
81
|
+
return {...info, model: FIXTURE_GLASSES.model, firmwareUpdateAvailable: false}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Fixture controllers first, then any real records that are not the fixture's. */
|
|
85
|
+
export function fixtureControllerList(list: PairedController[], identity: IdentitySnapshot): PairedController[] {
|
|
86
|
+
if (!fixtureActive(identity)) return list
|
|
87
|
+
const fixture = FIXTURE_CONTROLLERS.map(({id, model, name}) => ({id, model, name}))
|
|
88
|
+
const ids = new Set(fixture.map((record) => record.id))
|
|
89
|
+
return [...fixture, ...list.filter((record) => !ids.has(record.id))]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function fixtureControllerStatuses(
|
|
93
|
+
statuses: Record<string, ControllerStatus>,
|
|
94
|
+
identity: IdentitySnapshot,
|
|
95
|
+
): Record<string, ControllerStatus> {
|
|
96
|
+
if (!fixtureActive(identity)) return statuses
|
|
97
|
+
const result = {...statuses}
|
|
98
|
+
for (const {id, model, name, batteryLevel} of FIXTURE_CONTROLLERS) {
|
|
99
|
+
result[id] = {
|
|
100
|
+
id,
|
|
101
|
+
model,
|
|
102
|
+
name,
|
|
103
|
+
connected: true,
|
|
104
|
+
fullyBooted: true,
|
|
105
|
+
searching: false,
|
|
106
|
+
batteryLevel,
|
|
107
|
+
charging: false,
|
|
108
|
+
macAddress: "",
|
|
109
|
+
signalStrength: -62,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result
|
|
113
|
+
}
|
package/src/stores/settings.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {AsyncResult, result as Res, Result} from "typesafe-ts"
|
|
|
3
3
|
import {create} from "zustand"
|
|
4
4
|
import {subscribeWithSelector} from "zustand/middleware"
|
|
5
5
|
|
|
6
|
+
import {isScreenshotMode} from "../services/ScreenshotFixture"
|
|
6
7
|
import {storage} from "../utils/storage"
|
|
7
8
|
import {getBluetoothSettingKeysForDevice, nativeMenuApps} from "./bluetoothSettingKeys"
|
|
8
9
|
import {useGlassesStore} from "./glasses"
|
|
@@ -144,6 +145,8 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
144
145
|
home_mirror: {
|
|
145
146
|
key: "home_mirror",
|
|
146
147
|
defaultValue: () => true,
|
|
148
|
+
// The store-screenshot build always shows the mirror under its fixture G2.
|
|
149
|
+
override: () => (isScreenshotMode() ? true : undefined),
|
|
147
150
|
writable: true,
|
|
148
151
|
saveOnServer: true,
|
|
149
152
|
persist: true,
|