@glassly/engine 0.1.0-dev.59 → 0.1.0-dev.62
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 +5 -1
- package/build/facades/ota.d.ts +3 -1
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +4 -2
- package/build/facades/ota.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +6 -1
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +136 -14
- package/build/react/MentraLiveOtaFlow.js.map +1 -1
- package/build/react/useMentraLiveOta.d.ts +5 -1
- package/build/react/useMentraLiveOta.d.ts.map +1 -1
- package/build/react/useMentraLiveOta.js +135 -58
- package/build/react/useMentraLiveOta.js.map +1 -1
- package/build/services/DisplayProcessor.d.ts +3 -3
- package/build/services/DisplayProcessor.js +3 -3
- package/build/services/DisplayProcessor.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +2 -2
- package/build/services/LocalDisplayManager.js +2 -2
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/OtaInstallCoordinator.d.ts +2 -0
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +21 -1
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +1 -1
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/StreamLifecycleController.d.ts +4 -0
- package/build/services/StreamLifecycleController.d.ts.map +1 -1
- package/build/services/StreamLifecycleController.js +4 -0
- package/build/services/StreamLifecycleController.js.map +1 -1
- package/build/services/otaInstallPolicy.d.ts +4 -0
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +4 -0
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/types/applet.d.ts +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g2.js +2 -2
- package/build/types/capabilities/even-realities-g2.js.map +1 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +1 -0
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +1 -1
- package/build/types/index.js.map +1 -1
- package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
- package/build/utils/display/composer/ColumnComposer.js +1 -1
- package/build/utils/display/composer/ColumnComposer.js.map +1 -1
- package/package.json +8 -7
- package/src/facades/ota.ts +4 -2
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +2 -1
- package/src/react/MentraLiveOtaFlow.tsx +226 -41
- package/src/react/useMentraLiveOta.ts +146 -60
- package/src/services/DisplayProcessor.ts +3 -3
- package/src/services/LocalDisplayManager.ts +2 -2
- package/src/services/OtaInstallCoordinator.ts +21 -0
- package/src/services/PhonePhotoCoordinator.ts +1 -1
- package/src/services/StreamLifecycleController.ts +4 -0
- package/src/services/otaInstallPolicy.ts +4 -0
- package/src/types/applet.ts +1 -1
- package/src/types/capabilities/even-realities-g2.ts +3 -2
- package/src/types/hardware.ts +1 -0
- package/src/types/index.ts +1 -1
- package/src/utils/display/composer/ColumnComposer.ts +1 -1
|
@@ -28,7 +28,9 @@ export type MentraLiveOtaFlowPage = "check" | "progress"
|
|
|
28
28
|
export type MentraLiveOtaScreen =
|
|
29
29
|
| "initializing"
|
|
30
30
|
| "checking"
|
|
31
|
+
| "finishing"
|
|
31
32
|
| "update_available"
|
|
33
|
+
| "battery_required"
|
|
32
34
|
| "wifi_required"
|
|
33
35
|
| "up_to_date"
|
|
34
36
|
| "dev_build"
|
|
@@ -69,6 +71,7 @@ export type MentraLiveOtaReleaseTransition = {
|
|
|
69
71
|
export type MentraLiveOtaState = {
|
|
70
72
|
screen: MentraLiveOtaScreen
|
|
71
73
|
connected: boolean
|
|
74
|
+
batteryLevel: number | null
|
|
72
75
|
transport: MentraLiveOtaTransport | null
|
|
73
76
|
updateRequired: boolean
|
|
74
77
|
versionChange: boolean
|
|
@@ -94,6 +97,8 @@ export type MentraLiveOtaState = {
|
|
|
94
97
|
canDiscard: boolean
|
|
95
98
|
canOpenWifiSetup: boolean
|
|
96
99
|
continueDisabled: boolean
|
|
100
|
+
/** True only after an approved update session reaches a final no-update check. */
|
|
101
|
+
completedUpdate: boolean
|
|
97
102
|
/** Release labels for the offered or just-completed coordinated update. */
|
|
98
103
|
releaseTransition: MentraLiveOtaReleaseTransition | null
|
|
99
104
|
/** Release notes crossed by this update, newest first. Populated on completion. */
|
|
@@ -128,6 +133,7 @@ type CheckState = "checking" | "update_available" | "no_update" | "dev_build" |
|
|
|
128
133
|
|
|
129
134
|
const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000
|
|
130
135
|
const AUTO_CHAIN_COMPLETE_DELAY_MS = 750
|
|
136
|
+
export const MINIMUM_OTA_BATTERY_LEVEL = 25
|
|
131
137
|
|
|
132
138
|
function releaseRangeTargetVersion(result: OtaCheckCurrentGlassesResult): string | null {
|
|
133
139
|
return result.releaseVersion ?? result.updateInfo?.versionName ?? result.latestVersionInfo?.versionName ?? null
|
|
@@ -163,10 +169,10 @@ function installProgress(snapshot: OtaInstallSnapshot): number | null {
|
|
|
163
169
|
const isDownload = otaStatus?.phase === "download"
|
|
164
170
|
const totalSteps = otaStatus?.totalSteps ?? 1
|
|
165
171
|
const rawPercent = isDownload
|
|
166
|
-
? otaStatus?.stepPercent ?? 0
|
|
172
|
+
? (otaStatus?.stepPercent ?? 0)
|
|
167
173
|
: totalSteps >= 2
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
? (otaStatus?.overallPercent ?? 0)
|
|
175
|
+
: (otaStatus?.stepPercent ?? 0)
|
|
170
176
|
return Math.min(Math.max(rawPercent, snapshot.mtkInstallStallSimulatedPercent ?? 0, 0), 100)
|
|
171
177
|
}
|
|
172
178
|
|
|
@@ -215,7 +221,9 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
215
221
|
const [completedReleaseTransition, setCompletedReleaseTransition] = useState<MentraLiveOtaReleaseTransition | null>(
|
|
216
222
|
null,
|
|
217
223
|
)
|
|
224
|
+
const [completedUpdate, setCompletedUpdate] = useState(false)
|
|
218
225
|
const [completedChangelogs, setCompletedChangelogs] = useState<ReleaseChangelog[]>([])
|
|
226
|
+
const [batteryBlocked, setBatteryBlocked] = useState(false)
|
|
219
227
|
const [checkGeneration, setCheckGeneration] = useState(0)
|
|
220
228
|
const performCheckGenerationRef = useRef(0)
|
|
221
229
|
const activeCheckKeyRef = useRef<string | null>(null)
|
|
@@ -249,6 +257,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
249
257
|
const returnToCheck = useCallback(() => {
|
|
250
258
|
installActionPendingRef.current = false
|
|
251
259
|
autoChainAdvancedRef.current = false
|
|
260
|
+
setCheckState("checking")
|
|
252
261
|
setPage("check")
|
|
253
262
|
setCheckGeneration((generation) => generation + 1)
|
|
254
263
|
}, [])
|
|
@@ -259,6 +268,28 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
259
268
|
setPage("progress")
|
|
260
269
|
}, [])
|
|
261
270
|
|
|
271
|
+
const continueApprovedChain = useCallback(
|
|
272
|
+
(result: OtaCheckCurrentGlassesResult): boolean => {
|
|
273
|
+
if (installActionPendingRef.current || !isOtaAutoChainActive() || !result.updateInfo) return false
|
|
274
|
+
const snapshot = ota.snapshot()
|
|
275
|
+
if (!snapshot.wifiStatusKnown || (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1)) return false
|
|
276
|
+
|
|
277
|
+
const admission = tryAdvanceOtaAutoChain(
|
|
278
|
+
otaAutoChainFingerprint(result),
|
|
279
|
+
result.updateInfo.isDowngrade === true,
|
|
280
|
+
releaseRangeTargetVersion(result),
|
|
281
|
+
result.releaseVersion,
|
|
282
|
+
)
|
|
283
|
+
if (!admission.advance) return false
|
|
284
|
+
|
|
285
|
+
checkCompletedRef.current = true
|
|
286
|
+
ota.installSession.prepare(result)
|
|
287
|
+
navigateToProgress()
|
|
288
|
+
return true
|
|
289
|
+
},
|
|
290
|
+
[navigateToProgress],
|
|
291
|
+
)
|
|
292
|
+
|
|
262
293
|
useEffect(() => {
|
|
263
294
|
if (!runtimeReady || page !== "check") return
|
|
264
295
|
const MIN_DISPLAY_TIME_MS = 1100
|
|
@@ -279,18 +310,18 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
279
310
|
|
|
280
311
|
const performCheck = async () => {
|
|
281
312
|
if (checkCompletedRef.current) return
|
|
313
|
+
if (isOtaAutoChainActive() && !otaSnapshot.ready) {
|
|
314
|
+
const remainingMs = otaAutoChainReconnectWaitRemaining()
|
|
315
|
+
if (remainingMs === null) return
|
|
316
|
+
reconnectTimeout = setTimeout(() => {
|
|
317
|
+
if (cancelled || myGeneration !== performCheckGenerationRef.current || ota.snapshot().ready) return
|
|
318
|
+
stopOtaAutoChain()
|
|
319
|
+
checkCompletedRef.current = true
|
|
320
|
+
setCheckState("error")
|
|
321
|
+
}, remainingMs)
|
|
322
|
+
return
|
|
323
|
+
}
|
|
282
324
|
if (!otaSnapshot.connected) {
|
|
283
|
-
if (isOtaAutoChainActive()) {
|
|
284
|
-
const remainingMs = otaAutoChainReconnectWaitRemaining()
|
|
285
|
-
if (remainingMs === null) return
|
|
286
|
-
reconnectTimeout = setTimeout(() => {
|
|
287
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current || ota.snapshot().connected) return
|
|
288
|
-
stopOtaAutoChain()
|
|
289
|
-
checkCompletedRef.current = true
|
|
290
|
-
setCheckState("error")
|
|
291
|
-
}, remainingMs)
|
|
292
|
-
return
|
|
293
|
-
}
|
|
294
325
|
if (checkStartedRef.current) {
|
|
295
326
|
stopOtaAutoChain()
|
|
296
327
|
checkCompletedRef.current = true
|
|
@@ -364,34 +395,21 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
364
395
|
const fromVersion = currentReleaseVersion(ota.snapshot().appVersion)
|
|
365
396
|
setOfferedReleaseTransition(result.releaseVersion ? {fromVersion, toVersion: result.releaseVersion} : null)
|
|
366
397
|
}
|
|
367
|
-
if (isOtaAutoChainActive()) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
|
|
371
|
-
stopOtaAutoChain()
|
|
372
|
-
} else {
|
|
373
|
-
const admission = tryAdvanceOtaAutoChain(
|
|
374
|
-
fingerprint,
|
|
375
|
-
result.updateInfo.isDowngrade === true,
|
|
376
|
-
releaseRangeTargetVersion(result),
|
|
377
|
-
result.releaseVersion,
|
|
378
|
-
)
|
|
379
|
-
if (admission.advance) {
|
|
380
|
-
checkCompletedRef.current = true
|
|
381
|
-
ota.installSession.prepare(result)
|
|
382
|
-
navigateToProgress()
|
|
383
|
-
return
|
|
384
|
-
}
|
|
385
|
-
}
|
|
398
|
+
if (isOtaAutoChainActive() && !ota.snapshot().wifiStatusKnown) {
|
|
399
|
+
checkCompletedRef.current = true
|
|
400
|
+
return
|
|
386
401
|
}
|
|
402
|
+
if (continueApprovedChain(result)) return
|
|
387
403
|
checkCompletedRef.current = true
|
|
388
404
|
setCheckState("update_available")
|
|
389
405
|
return
|
|
390
406
|
}
|
|
391
|
-
|
|
407
|
+
const completedApprovedSession = isOtaAutoChainActive()
|
|
408
|
+
if (completedApprovedSession) {
|
|
392
409
|
setCompletedChangelogs(releaseChangelogsForActiveChain())
|
|
393
410
|
setCompletedReleaseTransition(releaseTransitionFromRange(otaAutoChainReleaseRange()))
|
|
394
411
|
}
|
|
412
|
+
setCompletedUpdate(completedApprovedSession)
|
|
395
413
|
stopOtaAutoChain()
|
|
396
414
|
checkCompletedRef.current = true
|
|
397
415
|
ota.clearUpdateAvailable()
|
|
@@ -412,7 +430,42 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
412
430
|
cancelled = true
|
|
413
431
|
if (reconnectTimeout) clearTimeout(reconnectTimeout)
|
|
414
432
|
}
|
|
415
|
-
}, [
|
|
433
|
+
}, [
|
|
434
|
+
checkGeneration,
|
|
435
|
+
continueApprovedChain,
|
|
436
|
+
otaSnapshot.connected,
|
|
437
|
+
otaSnapshot.ready,
|
|
438
|
+
otaSnapshot.wifiStatusKnown,
|
|
439
|
+
page,
|
|
440
|
+
runtimeReady,
|
|
441
|
+
])
|
|
442
|
+
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
if (
|
|
445
|
+
!runtimeReady ||
|
|
446
|
+
page !== "check" ||
|
|
447
|
+
(checkState !== "checking" && checkState !== "update_available") ||
|
|
448
|
+
!otaSnapshot.wifiStatusKnown ||
|
|
449
|
+
!isOtaAutoChainActive()
|
|
450
|
+
) {
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
const result = selectedCheckResultRef.current
|
|
454
|
+
if (!result?.updateAvailable || !result.updateInfo) return
|
|
455
|
+
if (!otaSnapshot.wifiConnected && otaSnapshot.hotspotOtaVersion !== 1) {
|
|
456
|
+
setCheckState("update_available")
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
continueApprovedChain(result)
|
|
460
|
+
}, [
|
|
461
|
+
checkState,
|
|
462
|
+
continueApprovedChain,
|
|
463
|
+
otaSnapshot.hotspotOtaVersion,
|
|
464
|
+
otaSnapshot.wifiConnected,
|
|
465
|
+
otaSnapshot.wifiStatusKnown,
|
|
466
|
+
page,
|
|
467
|
+
runtimeReady,
|
|
468
|
+
])
|
|
416
469
|
|
|
417
470
|
useEffect(() => {
|
|
418
471
|
if (!runtimeReady || page !== "progress") return
|
|
@@ -457,8 +510,10 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
457
510
|
}, [installSnapshot.connected, installSnapshot.displayState, page, returnToCheck])
|
|
458
511
|
|
|
459
512
|
const check = useCallback(() => {
|
|
513
|
+
setBatteryBlocked(false)
|
|
460
514
|
setOfferedReleaseTransition(null)
|
|
461
515
|
setCompletedReleaseTransition(null)
|
|
516
|
+
setCompletedUpdate(false)
|
|
462
517
|
setCompletedChangelogs([])
|
|
463
518
|
setPage("check")
|
|
464
519
|
setCheckState("checking")
|
|
@@ -474,6 +529,10 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
474
529
|
return
|
|
475
530
|
}
|
|
476
531
|
const snapshot = ota.snapshot()
|
|
532
|
+
if (snapshot.batteryLevel !== null && snapshot.batteryLevel < MINIMUM_OTA_BATTERY_LEVEL) {
|
|
533
|
+
setBatteryBlocked(true)
|
|
534
|
+
return
|
|
535
|
+
}
|
|
477
536
|
if (!snapshot.wifiStatusKnown) {
|
|
478
537
|
check()
|
|
479
538
|
return
|
|
@@ -483,6 +542,8 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
483
542
|
return
|
|
484
543
|
}
|
|
485
544
|
ota.installSession.prepare(result)
|
|
545
|
+
setBatteryBlocked(false)
|
|
546
|
+
setCompletedUpdate(false)
|
|
486
547
|
setCompletedChangelogs([])
|
|
487
548
|
if (updateFingerprint) {
|
|
488
549
|
const fromVersion = offeredReleaseTransition
|
|
@@ -497,6 +558,15 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
497
558
|
navigateToProgress()
|
|
498
559
|
}, [check, isVersionChange, navigateToProgress, offeredReleaseTransition, page, updateFingerprint])
|
|
499
560
|
|
|
561
|
+
useEffect(() => {
|
|
562
|
+
if (
|
|
563
|
+
batteryBlocked &&
|
|
564
|
+
(otaSnapshot.batteryLevel === null || otaSnapshot.batteryLevel >= MINIMUM_OTA_BATTERY_LEVEL)
|
|
565
|
+
) {
|
|
566
|
+
setBatteryBlocked(false)
|
|
567
|
+
}
|
|
568
|
+
}, [batteryBlocked, otaSnapshot.batteryLevel])
|
|
569
|
+
|
|
500
570
|
const retryInstall = useCallback(() => {
|
|
501
571
|
if (page !== "progress") return
|
|
502
572
|
onFirmwareRestartingChangeRef.current?.(false, true)
|
|
@@ -508,7 +578,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
508
578
|
onFinishedRef.current?.()
|
|
509
579
|
return
|
|
510
580
|
}
|
|
511
|
-
|
|
581
|
+
const restartStillInProgress =
|
|
582
|
+
installSnapshot.displayState === "restarting" ||
|
|
583
|
+
installSnapshot.versionChangePhase === "restarting" ||
|
|
584
|
+
installSnapshot.versionChangePhase === "verifying"
|
|
585
|
+
if (restartStillInProgress) return
|
|
512
586
|
const requiresGlassesReboot = shouldRequireGlassesRebootForBesFailure(
|
|
513
587
|
installSnapshot.otaStatus,
|
|
514
588
|
installSnapshot.otaProgress,
|
|
@@ -534,10 +608,12 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
534
608
|
const state = useMemo<MentraLiveOtaState>(() => {
|
|
535
609
|
const hotspotSupported = otaSnapshot.hotspotOtaVersion === 1
|
|
536
610
|
const canInstall = otaSnapshot.wifiStatusKnown && (otaSnapshot.wifiConnected || hotspotSupported)
|
|
611
|
+
const autoChainActive = isOtaAutoChainActive()
|
|
537
612
|
if (!runtimeReady) {
|
|
538
613
|
return {
|
|
539
614
|
screen: "initializing",
|
|
540
615
|
connected: otaSnapshot.connected,
|
|
616
|
+
batteryLevel: otaSnapshot.batteryLevel,
|
|
541
617
|
transport: null,
|
|
542
618
|
updateRequired: isUpdateRequired,
|
|
543
619
|
versionChange: isVersionChange,
|
|
@@ -563,6 +639,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
563
639
|
canDiscard: false,
|
|
564
640
|
canOpenWifiSetup: false,
|
|
565
641
|
continueDisabled: false,
|
|
642
|
+
completedUpdate: false,
|
|
566
643
|
releaseTransition: null,
|
|
567
644
|
changelogs: [],
|
|
568
645
|
}
|
|
@@ -570,29 +647,27 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
570
647
|
|
|
571
648
|
if (page === "check") {
|
|
572
649
|
const wifiRequired = otaSnapshot.wifiStatusKnown && !otaSnapshot.wifiConnected && !hotspotSupported
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
:
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
: screen === "update_info_unavailable"
|
|
591
|
-
? {code: "update_info_unavailable", message: "Update information for this app version is unavailable."}
|
|
592
|
-
: null
|
|
650
|
+
let screen: MentraLiveOtaScreen
|
|
651
|
+
if (checkState === "checking") screen = autoChainActive ? "finishing" : "checking"
|
|
652
|
+
else if (checkState === "update_available") {
|
|
653
|
+
screen = wifiRequired ? "wifi_required" : batteryBlocked ? "battery_required" : "update_available"
|
|
654
|
+
} else if (checkState === "no_update") screen = "up_to_date"
|
|
655
|
+
else if (checkState === "dev_build") screen = "dev_build"
|
|
656
|
+
else screen = errorKind === "pin_unavailable" ? "update_info_unavailable" : "check_failed"
|
|
657
|
+
|
|
658
|
+
let error: MentraLiveOtaError | null = null
|
|
659
|
+
if (screen === "check_failed") {
|
|
660
|
+
error = {
|
|
661
|
+
code: "check_failed",
|
|
662
|
+
message: "Couldn't check for updates. Please check your connection and try again.",
|
|
663
|
+
}
|
|
664
|
+
} else if (screen === "update_info_unavailable") {
|
|
665
|
+
error = {code: "update_info_unavailable", message: "Update information for this app version is unavailable."}
|
|
666
|
+
}
|
|
593
667
|
return {
|
|
594
668
|
screen,
|
|
595
669
|
connected: otaSnapshot.connected,
|
|
670
|
+
batteryLevel: otaSnapshot.batteryLevel,
|
|
596
671
|
transport: otaSnapshot.wifiConnected ? "wifi" : hotspotSupported ? "hotspot" : null,
|
|
597
672
|
updateRequired: isUpdateRequired,
|
|
598
673
|
versionChange: isVersionChange,
|
|
@@ -614,13 +689,19 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
614
689
|
canInstall: screen === "update_available" && canInstall,
|
|
615
690
|
canRetry: screen === "check_failed",
|
|
616
691
|
canFinish: screen === "up_to_date" || screen === "dev_build" || screen === "update_info_unavailable",
|
|
617
|
-
canDismiss:
|
|
692
|
+
canDismiss:
|
|
693
|
+
(screen === "update_available" || screen === "wifi_required" || screen === "battery_required") &&
|
|
694
|
+
!isUpdateRequired &&
|
|
695
|
+
!autoChainActive,
|
|
618
696
|
canDiscard: false,
|
|
619
697
|
canOpenWifiSetup: screen === "wifi_required",
|
|
620
698
|
continueDisabled: false,
|
|
699
|
+
completedUpdate: screen === "up_to_date" && completedUpdate,
|
|
621
700
|
releaseTransition:
|
|
622
701
|
screen === "update_available" || screen === "wifi_required"
|
|
623
|
-
?
|
|
702
|
+
? autoChainActive
|
|
703
|
+
? releaseTransitionFromRange(otaAutoChainReleaseRange())
|
|
704
|
+
: offeredReleaseTransition
|
|
624
705
|
: screen === "up_to_date"
|
|
625
706
|
? completedReleaseTransition
|
|
626
707
|
: null,
|
|
@@ -641,7 +722,8 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
641
722
|
const displayedError = requiresGlassesReboot
|
|
642
723
|
? BES_INSTALL_RESTART_MESSAGE
|
|
643
724
|
: installSnapshot.errorMsg || getOtaErrorMessage(installSnapshot.otaStatus?.error)
|
|
644
|
-
const
|
|
725
|
+
const progressState = progressScreen(installSnapshot)
|
|
726
|
+
const screen = progressState === "complete" && autoChainActive ? "finishing" : progressState
|
|
645
727
|
const error =
|
|
646
728
|
screen === "failed"
|
|
647
729
|
? {
|
|
@@ -654,6 +736,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
654
736
|
return {
|
|
655
737
|
screen,
|
|
656
738
|
connected: installSnapshot.connected,
|
|
739
|
+
batteryLevel: otaSnapshot.batteryLevel,
|
|
657
740
|
transport: installSnapshot.transport,
|
|
658
741
|
updateRequired: isUpdateRequired,
|
|
659
742
|
versionChange: installSnapshot.isVersionChange,
|
|
@@ -677,18 +760,21 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
677
760
|
error,
|
|
678
761
|
canInstall: false,
|
|
679
762
|
canRetry: screen === "failed" && !requiresGlassesReboot,
|
|
680
|
-
canFinish: screen === "complete" ||
|
|
763
|
+
canFinish: screen === "complete" || (screen === "failed" && requiresGlassesReboot),
|
|
681
764
|
canDismiss: false,
|
|
682
765
|
canDiscard: screen === "disconnected",
|
|
683
766
|
canOpenWifiSetup: screen === "failed" && showChangeWifi,
|
|
684
767
|
continueDisabled: installSnapshot.continueButtonDisabled,
|
|
768
|
+
completedUpdate: false,
|
|
685
769
|
releaseTransition: releaseTransitionFromRange(otaAutoChainReleaseRange()),
|
|
686
770
|
changelogs,
|
|
687
771
|
}
|
|
688
772
|
}, [
|
|
689
773
|
checkState,
|
|
774
|
+
batteryBlocked,
|
|
690
775
|
completedChangelogs,
|
|
691
776
|
completedReleaseTransition,
|
|
777
|
+
completedUpdate,
|
|
692
778
|
errorKind,
|
|
693
779
|
firmwareRestarting,
|
|
694
780
|
installSnapshot,
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* DisplayProcessor
|
|
3
3
|
*
|
|
4
4
|
* Processes display events with pixel-accurate text wrapping using the same
|
|
5
|
-
* display
|
|
6
|
-
*
|
|
5
|
+
* Engine display utilities. This ensures the mobile preview matches exactly
|
|
6
|
+
* what is shown on the glasses.
|
|
7
7
|
*
|
|
8
8
|
* Key responsibilities:
|
|
9
9
|
* 1. Intercept display events before sending to native SGC
|
|
10
10
|
* 2. Wrap text using the correct device profile (G1, etc.)
|
|
11
11
|
* 3. Ensure GlassesDisplayMirror shows exactly what glasses will show
|
|
12
12
|
*
|
|
13
|
-
* @see
|
|
13
|
+
* @see notes/miniapp-display-api-redesign.md for the current display design
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* LocalDisplayManager
|
|
3
3
|
*
|
|
4
4
|
* Phone-side arbiter for display requests originating from LOCAL miniapps
|
|
5
|
-
* (the @glassly/miniapp SDK).
|
|
6
|
-
*
|
|
5
|
+
* (the @glassly/miniapp SDK). Preserves the retired cloud display manager's
|
|
6
|
+
* user-facing behavior for the features that matter on the phone:
|
|
7
7
|
*
|
|
8
8
|
* - boot message ("Starting <AppName>…") with a bounded window
|
|
9
9
|
* - durationMs auto-clear
|
|
@@ -26,10 +26,12 @@ import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
|
|
|
26
26
|
import {deriveDisplayState, type DisplayState} from "./otaDisplayState"
|
|
27
27
|
import {
|
|
28
28
|
BES_CONTINUE_LOCKOUT_MS,
|
|
29
|
+
BES_RESTART_TIMEOUT_MS,
|
|
29
30
|
DOWNLOAD_STUCK_TIMEOUT_MS,
|
|
30
31
|
GLOBAL_OTA_TIMEOUT_MS,
|
|
31
32
|
LEGACY_APK_COMPLETION_SETTLE_MS,
|
|
32
33
|
LEGACY_BES_CONTINUE_LOCKOUT_MS,
|
|
34
|
+
LEGACY_BES_RESTART_TIMEOUT_MS,
|
|
33
35
|
LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS,
|
|
34
36
|
LEGACY_GLOBAL_OTA_TIMEOUT_MS,
|
|
35
37
|
LEGACY_MTK_INSTALL_TIMEOUT_MS,
|
|
@@ -245,6 +247,7 @@ class OtaInstallCoordinator {
|
|
|
245
247
|
private pingInterval: ReturnType<typeof setInterval> | null = null
|
|
246
248
|
private queryReplyTimeout: ReturnType<typeof setTimeout> | null = null
|
|
247
249
|
private continueLockoutTimer: ReturnType<typeof setTimeout> | null = null
|
|
250
|
+
private restartRecoveryTimeout: ReturnType<typeof setTimeout> | null = null
|
|
248
251
|
private legacyApkSettleTimer: ReturnType<typeof setTimeout> | null = null
|
|
249
252
|
private mtkStallDetectTimer: ReturnType<typeof setTimeout> | null = null
|
|
250
253
|
private mtkSimTickTimer: ReturnType<typeof setInterval> | null = null
|
|
@@ -1000,12 +1003,22 @@ class OtaInstallCoordinator {
|
|
|
1000
1003
|
// (15s unified, 35s for legacy-shaped sessions — WP 8C-f).
|
|
1001
1004
|
if (displayStateChanged && displayState === "restarting") {
|
|
1002
1005
|
this.clearContinueLockoutTimer()
|
|
1006
|
+
this.clearRestartRecoveryTimeout()
|
|
1003
1007
|
const lockoutMs = legacySession ? LEGACY_BES_CONTINUE_LOCKOUT_MS : BES_CONTINUE_LOCKOUT_MS
|
|
1008
|
+
const restartTimeoutMs = legacySession ? LEGACY_BES_RESTART_TIMEOUT_MS : BES_RESTART_TIMEOUT_MS
|
|
1004
1009
|
this.setContinueButtonDisabled(true)
|
|
1005
1010
|
this.continueLockoutTimer = setTimeout(() => {
|
|
1006
1011
|
this.continueLockoutTimer = null
|
|
1007
1012
|
this.setContinueButtonDisabled(false)
|
|
1008
1013
|
}, lockoutMs)
|
|
1014
|
+
this.restartRecoveryTimeout = setTimeout(() => {
|
|
1015
|
+
this.restartRecoveryTimeout = null
|
|
1016
|
+
if (this.computeDisplayStateNow() !== "restarting") return
|
|
1017
|
+
console.log(`[OTA_PROGRESS] watchdog: glasses did not finish restarting in ${restartTimeoutMs}ms`)
|
|
1018
|
+
this.setErrorMsg(OtaProgressMessages.restartTimeout)
|
|
1019
|
+
}, restartTimeoutMs)
|
|
1020
|
+
} else if (displayStateChanged) {
|
|
1021
|
+
this.clearRestartRecoveryTimeout()
|
|
1009
1022
|
}
|
|
1010
1023
|
|
|
1011
1024
|
// Legacy MTK install stall simulation (WP 8C-e): display-only. The simulation
|
|
@@ -1668,6 +1681,13 @@ class OtaInstallCoordinator {
|
|
|
1668
1681
|
}
|
|
1669
1682
|
}
|
|
1670
1683
|
|
|
1684
|
+
private clearRestartRecoveryTimeout(): void {
|
|
1685
|
+
if (this.restartRecoveryTimeout) {
|
|
1686
|
+
clearTimeout(this.restartRecoveryTimeout)
|
|
1687
|
+
this.restartRecoveryTimeout = null
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1671
1691
|
private clearLegacyApkSettleTimer(): void {
|
|
1672
1692
|
if (this.legacyApkSettleTimer) {
|
|
1673
1693
|
clearTimeout(this.legacyApkSettleTimer)
|
|
@@ -1706,6 +1726,7 @@ class OtaInstallCoordinator {
|
|
|
1706
1726
|
this.clearPingInterval()
|
|
1707
1727
|
this.clearLegacyApkSettleTimer()
|
|
1708
1728
|
this.clearMtkSimulationTimers()
|
|
1729
|
+
this.clearRestartRecoveryTimeout()
|
|
1709
1730
|
}
|
|
1710
1731
|
}
|
|
1711
1732
|
|
|
@@ -166,7 +166,7 @@ export class PhonePhotoCoordinator {
|
|
|
166
166
|
// to time out. Fail fast with a typed error.
|
|
167
167
|
//
|
|
168
168
|
// We DON'T pre-check `hasCamera` here — the canonical capability data
|
|
169
|
-
// lives in `getModelCapabilities(deviceModel)`
|
|
169
|
+
// lives in `getModelCapabilities(deviceModel)` from @glassly/engine and
|
|
170
170
|
// pulling that into this file would add a cross-package import. If a
|
|
171
171
|
// cameraless device receives the BLE photo command, the glasses-side
|
|
172
172
|
// handler will return a photo_response error within ~1s and the gated
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Drives RTMP stream liveliness checks via a keep-alive heartbeat:
|
|
3
3
|
* `setActive(true)` starts the timer; each tick sends a keep-alive with a
|
|
4
4
|
* fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.
|
|
5
|
+
*
|
|
6
|
+
* Phone-owned successor to the retired cloud stream lifecycle controller.
|
|
7
|
+
* It uses the local `LifecycleLogger` interface because pino is not a phone
|
|
8
|
+
* dependency.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
export interface LifecycleLogger {
|
|
@@ -32,6 +32,8 @@ export const PING_INTERVAL_MS = 10_000
|
|
|
32
32
|
export const QUERY_REPLY_TIMEOUT_MS = 6000
|
|
33
33
|
/** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */
|
|
34
34
|
export const BES_CONTINUE_LOCKOUT_MS = 15_000
|
|
35
|
+
/** Fail closed if a BES reboot never reconnects; normal restarts complete in a fraction of this window. */
|
|
36
|
+
export const BES_RESTART_TIMEOUT_MS = 120_000
|
|
35
37
|
|
|
36
38
|
// --- Legacy old-build (< MINIMUM_OTA_STATUS_BUILD) OTA policy (WP 8C) ---
|
|
37
39
|
//
|
|
@@ -51,6 +53,7 @@ export const LEGACY_MTK_INSTALL_TIMEOUT_MS = MTK_INSTALL_TIMEOUT_MS + LEGACY_EXT
|
|
|
51
53
|
export const LEGACY_GLOBAL_OTA_TIMEOUT_MS = GLOBAL_OTA_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
|
|
52
54
|
export const LEGACY_PING_INTERVAL_MS = PING_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS
|
|
53
55
|
export const LEGACY_BES_CONTINUE_LOCKOUT_MS = BES_CONTINUE_LOCKOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
|
|
56
|
+
export const LEGACY_BES_RESTART_TIMEOUT_MS = BES_RESTART_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
|
|
54
57
|
/**
|
|
55
58
|
* After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the
|
|
56
59
|
* completed state for 12s (+20s pad) so the ASG process restart settles (and fresh
|
|
@@ -122,6 +125,7 @@ export function selectOtaProtocolProfile(
|
|
|
122
125
|
export const OtaProgressMessages = {
|
|
123
126
|
stalledOrStuck: "Update may have failed. Ensure glasses have internet access and try again.",
|
|
124
127
|
globalTimeout: "Update took too long. Please try again.",
|
|
128
|
+
restartTimeout: "Glasses did not finish restarting. Restart them before trying the update again.",
|
|
125
129
|
sendOtaStartFailed: "Failed to communicate with glasses.",
|
|
126
130
|
hotspotArtifactDownloadFailed: "Could not download the update to your phone. Check phone internet and try again.",
|
|
127
131
|
hotspotArtifactVerifyFailed: "The update package could not be verified. Please try again later.",
|
package/src/types/applet.ts
CHANGED
|
@@ -85,6 +85,9 @@ export const evenRealitiesG2: Capabilities = {
|
|
|
85
85
|
// WiFi capabilities - G2 does not support WiFi
|
|
86
86
|
hasWifi: false,
|
|
87
87
|
|
|
88
|
+
// OTA capabilities - G2 firmware updates are not driven by the ASG OTA flow
|
|
89
|
+
hasOta: false,
|
|
90
|
+
|
|
88
91
|
// Dashboard - G2 renders Even Realities' native dashboard in firmware, so
|
|
89
92
|
// Glassly does not manage the dashboard or expose dashboard settings for it
|
|
90
93
|
hasNativeDashboard: true,
|
|
@@ -93,6 +96,4 @@ export const evenRealitiesG2: Capabilities = {
|
|
|
93
96
|
// 576×188 overview map); magnetometer streams only while it is active
|
|
94
97
|
hasNativeNavigation: true,
|
|
95
98
|
|
|
96
|
-
// OTA capabilities - G2 firmware updates are not driven by the ASG OTA flow
|
|
97
|
-
hasOta: false,
|
|
98
99
|
}
|
package/src/types/hardware.ts
CHANGED
|
@@ -189,6 +189,7 @@ export const HARDWARE_CAPABILITIES: Record<string, Capabilities> = {
|
|
|
189
189
|
[mentraLive.modelName]: mentraLive,
|
|
190
190
|
[simulatedGlasses.modelName]: simulatedGlasses,
|
|
191
191
|
[vuzixZ100.modelName]: vuzixZ100,
|
|
192
|
+
[nimo.modelName]: nimo,
|
|
192
193
|
[DeviceTypes.MACH1]: vuzixZ100, // Mach1 uses same Vuzix Ultralite hardware as Z100
|
|
193
194
|
[nimo.modelName]: nimo,
|
|
194
195
|
[DeviceTypes.AR99]: {
|
package/src/types/index.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 2. Merges columns line-by-line with pixel-precise space padding
|
|
12
12
|
* 3. Returns a single pre-composed string ready for native to chunk & send
|
|
13
13
|
*
|
|
14
|
-
* @see
|
|
14
|
+
* @see notes/miniapp-display-api-redesign.md for the current display design
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type {DisplayProfile} from "../profiles/types"
|