@glassly/engine 0.3.3-dev.655 → 0.3.3-dev.659

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/build/facades/display.d.ts.map +1 -1
  2. package/build/facades/display.js +1 -2
  3. package/build/facades/display.js.map +1 -1
  4. package/build/facades/glasses.d.ts.map +1 -1
  5. package/build/facades/glasses.js +1 -13
  6. package/build/facades/glasses.js.map +1 -1
  7. package/build/generated/releaseMetadata.js +3 -3
  8. package/build/generated/releaseMetadata.js.map +1 -1
  9. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  10. package/build/services/DeviceEventRouter.js +0 -7
  11. package/build/services/DeviceEventRouter.js.map +1 -1
  12. package/build/services/GlassesTouchInput.d.ts +5 -2
  13. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  14. package/build/services/GlassesTouchInput.js +23 -9
  15. package/build/services/GlassesTouchInput.js.map +1 -1
  16. package/build/services/LocalMiniappRuntime.d.ts +0 -1
  17. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  18. package/build/services/LocalMiniappRuntime.js +0 -18
  19. package/build/services/LocalMiniappRuntime.js.map +1 -1
  20. package/build/utils/audio/pcmLevel.d.ts +9 -0
  21. package/build/utils/audio/pcmLevel.d.ts.map +1 -0
  22. package/build/utils/audio/pcmLevel.js +21 -0
  23. package/build/utils/audio/pcmLevel.js.map +1 -0
  24. package/package.json +7 -7
  25. package/src/facades/display.ts +1 -3
  26. package/src/facades/glasses.ts +2 -14
  27. package/src/generated/releaseMetadata.ts +3 -3
  28. package/src/services/DeviceEventRouter.ts +0 -8
  29. package/src/services/GlassesTouchInput.ts +27 -9
  30. package/src/services/LocalMiniappRuntime.ts +0 -17
  31. package/src/utils/audio/pcmLevel.ts +20 -0
@@ -0,0 +1,9 @@
1
+ /** Floor for mic level readings; digital silence would otherwise be -Infinity. */
2
+ export declare const PCM_LEVEL_FLOOR_DBFS = -120;
3
+ /**
4
+ * RMS level of one s16le chunk in dBFS, on the volume gate's 32767 full scale.
5
+ * Native events deliver PCM as a Uint8Array (Expo converts Data/ByteArray),
6
+ * not the ArrayBuffer the event type declares, so read through a DataView.
7
+ */
8
+ export declare function pcmLevelDbfs(pcm: ArrayBuffer | ArrayBufferView): number;
9
+ //# sourceMappingURL=pcmLevel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcmLevel.d.ts","sourceRoot":"","sources":["../../../src/utils/audio/pcmLevel.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,OAAO,CAAA;AAExC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,eAAe,GAAG,MAAM,CAWvE"}
@@ -0,0 +1,21 @@
1
+ /** Floor for mic level readings; digital silence would otherwise be -Infinity. */
2
+ export const PCM_LEVEL_FLOOR_DBFS = -120;
3
+ /**
4
+ * RMS level of one s16le chunk in dBFS, on the volume gate's 32767 full scale.
5
+ * Native events deliver PCM as a Uint8Array (Expo converts Data/ByteArray),
6
+ * not the ArrayBuffer the event type declares, so read through a DataView.
7
+ */
8
+ export function pcmLevelDbfs(pcm) {
9
+ const view = ArrayBuffer.isView(pcm) ? new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength) : new DataView(pcm);
10
+ const count = view.byteLength >> 1;
11
+ if (count === 0)
12
+ return PCM_LEVEL_FLOOR_DBFS;
13
+ let sumSquares = 0;
14
+ for (let i = 0; i < count; i++) {
15
+ const sample = view.getInt16(i * 2, true);
16
+ sumSquares += sample * sample;
17
+ }
18
+ const rms = Math.sqrt(sumSquares / count);
19
+ return Math.max(PCM_LEVEL_FLOOR_DBFS, 20 * Math.log10(rms / 32767));
20
+ }
21
+ //# sourceMappingURL=pcmLevel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcmLevel.js","sourceRoot":"","sources":["../../../src/utils/audio/pcmLevel.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAG,CAAA;AAExC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAkC;IAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAA;IACnH,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,CAAA;IAClC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,oBAAoB,CAAA;IAC5C,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;QACzC,UAAU,IAAI,MAAM,GAAG,MAAM,CAAA;IAC/B,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,CAAA;IACzC,OAAO,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAA;AACrE,CAAC","sourcesContent":["/** Floor for mic level readings; digital silence would otherwise be -Infinity. */\nexport const PCM_LEVEL_FLOOR_DBFS = -120\n\n/**\n * RMS level of one s16le chunk in dBFS, on the volume gate's 32767 full scale.\n * Native events deliver PCM as a Uint8Array (Expo converts Data/ByteArray),\n * not the ArrayBuffer the event type declares, so read through a DataView.\n */\nexport function pcmLevelDbfs(pcm: ArrayBuffer | ArrayBufferView): number {\n const view = ArrayBuffer.isView(pcm) ? new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength) : new DataView(pcm)\n const count = view.byteLength >> 1\n if (count === 0) return PCM_LEVEL_FLOOR_DBFS\n let sumSquares = 0\n for (let i = 0; i < count; i++) {\n const sample = view.getInt16(i * 2, true)\n sumSquares += sample * sample\n }\n const rms = Math.sqrt(sumSquares / count)\n return Math.max(PCM_LEVEL_FLOOR_DBFS, 20 * Math.log10(rms / 32767))\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.3.3-dev.655",
3
+ "version": "0.3.3-dev.659",
4
4
  "description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,17 +97,17 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@glassly/bluetooth-sdk": "0.3.3-dev.655",
101
- "@glassly/cloud-client": "0.3.3-dev.655",
102
- "@glassly/cloud-protocol": "0.3.3-dev.655",
103
- "@glassly/crust": "0.3.3-dev.655",
104
- "@glassly/miniapp": "0.3.3-dev.655",
100
+ "@glassly/bluetooth-sdk": "0.3.3-dev.659",
101
+ "@glassly/cloud-client": "0.3.3-dev.659",
102
+ "@glassly/cloud-protocol": "0.3.3-dev.659",
103
+ "@glassly/crust": "0.3.3-dev.659",
104
+ "@glassly/miniapp": "0.3.3-dev.659",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.1.1",
108
108
  "semver": "^7.7.4",
109
109
  "typesafe-ts": "^1.7.1",
110
- "@glassly/llm-providers": "0.3.3-dev.655"
110
+ "@glassly/llm-providers": "0.3.3-dev.659"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@glassly/llm-providers": "workspace:*",
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import BluetoothSdk from "@glassly/bluetooth-sdk"
8
8
  import {sendGesture} from "../services/GlassesTouchInput"
9
- import localMiniappRuntime from "../services/LocalMiniappRuntime"
10
9
  import {useDisplayStore} from "../stores/display"
11
10
  import {displayMirror} from "./displayMirror"
12
11
 
@@ -22,8 +21,7 @@ export const display = {
22
21
  /** An overlay app holds the slot. */
23
22
  isOpen: (): boolean => useDisplayStore.getState().overlayOwner !== null,
24
23
  /** As if the head was raised/lowered (the dashboard app applies contextual_dashboard). */
25
- setHeadUp: (up: boolean): void =>
26
- localMiniappRuntime.forwardEvent("head_up", {position: up ? "up" : "down", timestamp: Date.now()}),
24
+ setHeadUp: (up: boolean): void => sendGesture(up ? "head_up" : "head_down"),
27
25
  /** A double tap on the empty screen: the dashboard app opens (simulator button). */
28
26
  open: (): void => {
29
27
  if (!display.dashboard.isOpen()) sendGesture("double_tap")
@@ -32,22 +32,10 @@ import neuralBandInput, {
32
32
  } from "../services/neuralBand/NeuralBandInput"
33
33
  import {recordSupportProfileConnectionFailure} from "../services/SupportProfileSync"
34
34
  import {getModelCapabilities, type DeviceTypes} from "../types"
35
+ import {pcmLevelDbfs} from "../utils/audio/pcmLevel"
35
36
  import {glassesWifi} from "./glassesWifi"
36
37
  import {glassesSettings} from "./glassesSettings"
37
38
 
38
- /** Floor for the mic level readout; digital silence would otherwise be -Infinity. */
39
- const MIC_LEVEL_FLOOR_DBFS = -120
40
-
41
- /** RMS level of one s16le chunk in dBFS, on the volume gate's 32767 full scale. */
42
- function pcmLevelDbfs(pcm: ArrayBuffer): number {
43
- const samples = new Int16Array(pcm, 0, pcm.byteLength >> 1)
44
- if (samples.length === 0) return MIC_LEVEL_FLOOR_DBFS
45
- let sumSquares = 0
46
- for (let i = 0; i < samples.length; i++) sumSquares += samples[i] * samples[i]
47
- const rms = Math.sqrt(sumSquares / samples.length)
48
- return Math.max(MIC_LEVEL_FLOOR_DBFS, 20 * Math.log10(rms / 32767))
49
- }
50
-
51
39
  let micLevelSubscriptions = 0
52
40
 
53
41
  function projectStatus() {
@@ -328,7 +316,7 @@ export const glasses = {
328
316
  onMicLevel: (cb: (dbfs: number) => void): (() => void) => {
329
317
  const owner = `mic_level_${++micLevelSubscriptions}`
330
318
  micStateCoordinator.acquireHostPcm(owner)
331
- const sub = BluetoothSdk.addListener("mic_pcm", (event: {pcm: ArrayBuffer}) => {
319
+ const sub = BluetoothSdk.addListener("mic_pcm", (event: {pcm: ArrayBuffer | Uint8Array}) => {
332
320
  if (event?.pcm) cb(pcmLevelDbfs(event.pcm))
333
321
  })
334
322
  return () => {
@@ -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.3",
15
- "releaseIdentity": "0.3.3-dev.655",
16
- "releaseSetId": "glassly-0.3.3-dev.655",
17
- "sourceCommit": "2cd9af55c9ab7e6a1d33b5c9e9dff6015e4ff6dc",
15
+ "releaseIdentity": "0.3.3-dev.659",
16
+ "releaseSetId": "glassly-0.3.3-dev.659",
17
+ "sourceCommit": "4504c1b1114103c746b0ad6921e04bf264d42644",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -257,14 +257,6 @@ export function startDeviceEventRouter(): void {
257
257
  })
258
258
  }),
259
259
  )
260
- // Head position — translate native {up:boolean} → SDK {position:"up"|"down"}.
261
- // The dashboard miniapp opens on head-up from this stream (ADR-0006).
262
- subs.push(
263
- BluetoothSdk.addListener("head_up", (event) => {
264
- if (event.up) dashboardOpenTrace.trigger("head-up")
265
- localMiniappRuntime.forwardEvent("head_up", {position: event.up ? "up" : "down", timestamp: Date.now()})
266
- }),
267
- )
268
260
  subs.push({remove: dashboardOpenTrace.start()})
269
261
  // On-device STT transcripts → local miniapps only while the local engine is
270
262
  // active (cloud fallback or an explicit forceLocal subscription). The runtime
@@ -5,6 +5,7 @@ import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
5
5
  import {useAppStatusStore} from "../stores/apps"
6
6
  import {useGlassesStore} from "../stores/glasses"
7
7
  import {BgTimer} from "../utils/timers"
8
+ import {DeviceTypes} from "../types"
8
9
  import dashboardOpenTrace from "./debug/DashboardOpenTrace"
9
10
  import gestureDotsService from "./GestureDotsService"
10
11
  import {isGlassesConnected} from "./GlassesReadiness"
@@ -12,6 +13,7 @@ import localDisplayManager from "./LocalDisplayManager"
12
13
  import localMiniappRuntime from "./LocalMiniappRuntime"
13
14
  import {MiniappExitGesture} from "./MiniappExitGesture"
14
15
  import nativeNavigationService from "./NativeNavigationService"
16
+ import {getEffectiveCapabilities} from "./effectiveCapabilities"
15
17
  import {isOverlayApp} from "./pages/overlayApps"
16
18
 
17
19
  const miniappExitGesture = new MiniappExitGesture({
@@ -40,13 +42,22 @@ function shouldForwardScroll(event: TouchEvent): boolean {
40
42
  return true
41
43
  }
42
44
 
45
+ /** Glasses whose firmware leaves the dashboard to the phone (CFW) rather than drawing its own. */
46
+ function firmwareDefersDashboard(): boolean {
47
+ const model = useGlassesStore.getState().deviceModel as DeviceTypes | null | undefined
48
+ return getEffectiveCapabilities(model ?? DeviceTypes.NONE)?.display?.views?.includes("dashboard") === true
49
+ }
50
+
43
51
  /**
44
52
  * Route both real glasses input and phone mirror controls through the same
45
53
  * policy. A claimed gesture goes to the app holding the claim, alone; an
46
54
  * unclaimed one goes to every subscriber and then the engine's default. The
47
55
  * one rule ahead of the claims: an unclaimed double tap on a miniapp whose
48
56
  * frame is visibly on the glasses leaves that app, whoever else claimed the
49
- * gesture — the dashboard's claim is for the empty screen.
57
+ * gesture — the dashboard's claim is for the empty screen. Head position
58
+ * (head_up / head_down) is a gesture like any other: the dashboard claims it
59
+ * while it runs, and an unclaimed head_up on glasses that defer the dashboard
60
+ * to the phone opens the stock one, as it would with no app at all.
50
61
  */
51
62
  export function dispatchTouchEvent(event: TouchEvent): void {
52
63
  const gesture = event.gestureName
@@ -54,6 +65,7 @@ export function dispatchTouchEvent(event: TouchEvent): void {
54
65
  // The Neural Band's middle finger (index 1) shares the tap names but is left
55
66
  // to miniapps: the engine's own double-tap behaviors are the index finger's.
56
67
  const osDoubleTap = gesture === "double_tap" && event.index !== 1
68
+ const osHeadUp = gesture === "head_up"
57
69
 
58
70
  if (osDoubleTap) {
59
71
  // A visible main miniapp opts out of OS exit by registering double tap (the
@@ -73,8 +85,10 @@ export function dispatchTouchEvent(event: TouchEvent): void {
73
85
  }
74
86
 
75
87
  if (claimant !== null) {
76
- // An overlay app claims the double tap on an empty screen to open the dashboard.
77
- if (gesture === "double_tap" && isOverlayApp(claimant)) dashboardOpenTrace.trigger("double-tap", event.timestamp)
88
+ // An overlay app claims the double tap and head-up on an empty screen to open the dashboard.
89
+ if ((gesture === "double_tap" || osHeadUp) && isOverlayApp(claimant)) {
90
+ dashboardOpenTrace.trigger(osHeadUp ? "head-up" : "double-tap", event.timestamp)
91
+ }
78
92
  // The press may have started before this app claimed its release edge.
79
93
  gestureDotsService.noteGesture(gesture, true, event.index)
80
94
  if (!shouldForwardScroll(event)) return
@@ -90,12 +104,14 @@ export function dispatchTouchEvent(event: TouchEvent): void {
90
104
  if (!shouldForwardScroll(event)) return
91
105
  localMiniappRuntime.forwardEvent("touch_event", event)
92
106
 
93
- // Nobody claimed a double tap: the glasses' own dashboard. On eligible
94
- // Glassly CFW glasses the dashboard miniapp claims it while it runs
95
- // (ADR-0006); stock firmware keeps its native dashboard (ADR-0002). The
96
- // modal nav UI owns the screen; showDashboard would fight it. Without
107
+ // Nobody claimed a double tap or a head-up: the glasses' own dashboard. On
108
+ // eligible Glassly CFW glasses the dashboard miniapp claims both while it
109
+ // runs (ADR-0006); stock firmware keeps its native dashboard (ADR-0002) and
110
+ // opens it on a head-up by itself, so only the double tap is relayed there.
111
+ // The modal nav UI owns the screen; showDashboard would fight it. Without
97
112
  // glasses there is no dashboard to open, and the mirror keeps its frame.
98
- if (osDoubleTap && !nativeNavigationService.isActive && isGlassesConnected(useGlassesStore.getState().connection)) {
113
+ const opensStockDashboard = osDoubleTap || (osHeadUp && firmwareDefersDashboard())
114
+ if (opensStockDashboard && !nativeNavigationService.isActive && isGlassesConnected(useGlassesStore.getState().connection)) {
99
115
  useDisplayStore.getState().setDisplayEvent({layout: {layoutType: "unavailable", reason: "firmware-dashboard"}})
100
116
  void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {
101
117
  console.error("DeviceEventRouter: showDashboard failed:", err)
@@ -123,7 +139,9 @@ export function sendGesture(
123
139
  | "swipe_right"
124
140
  | "long_press"
125
141
  | "dial_up"
126
- | "dial_down",
142
+ | "dial_down"
143
+ | "head_up"
144
+ | "head_down",
127
145
  ): void {
128
146
  const model = useGlassesStore.getState().deviceModel
129
147
  dispatchTouchEvent({
@@ -2345,20 +2345,6 @@ class LocalMiniappRuntime {
2345
2345
  data: {key, value: this.settingsBridgeValue(key)},
2346
2346
  })
2347
2347
  }
2348
- } else if (stream === "head_position") {
2349
- // The glasses store has no headUp field (same on the pre-engine host
2350
- // hook), so this stays a no-emit until head state lands in the store.
2351
- const headUp = (gs as {headUp?: boolean}).headUp
2352
- if (typeof headUp === "boolean") {
2353
- this.sendToMiniapp(packageName, {
2354
- type: MiniappResponseType.EVENT,
2355
- streamType: "head_position",
2356
- data: {
2357
- position: headUp ? "up" : "down",
2358
- timestamp: Date.now(),
2359
- },
2360
- })
2361
- }
2362
2348
  }
2363
2349
  }
2364
2350
  }
@@ -5457,7 +5443,6 @@ class LocalMiniappRuntime {
5457
5443
  * Forward a streamed event to all miniapps subscribed to the given stream.
5458
5444
  *
5459
5445
  * Event name translation:
5460
- * - Cloud sends "head_up" → miniapp protocol uses "head_position" (HEAD_POSITION)
5461
5446
  * - Cloud sends "VAD" (uppercase) → miniapp protocol uses "vad" (lowercase)
5462
5447
  */
5463
5448
  public forwardEvent(
@@ -5722,8 +5707,6 @@ class LocalMiniappRuntime {
5722
5707
  // Cloud / Bluetooth SDK → miniapp protocol translations.
5723
5708
  // Bluetooth SDK event names don't always match the miniapp wire values.
5724
5709
  switch (cloudEventName) {
5725
- case "head_up":
5726
- return MiniappStreamType.HEAD_POSITION // head_up → head_position
5727
5710
  case "accel_event":
5728
5711
  return MiniappStreamType.ACCEL_DATA // accel_event (native) → accel_data
5729
5712
  case "VAD":
@@ -0,0 +1,20 @@
1
+ /** Floor for mic level readings; digital silence would otherwise be -Infinity. */
2
+ export const PCM_LEVEL_FLOOR_DBFS = -120
3
+
4
+ /**
5
+ * RMS level of one s16le chunk in dBFS, on the volume gate's 32767 full scale.
6
+ * Native events deliver PCM as a Uint8Array (Expo converts Data/ByteArray),
7
+ * not the ArrayBuffer the event type declares, so read through a DataView.
8
+ */
9
+ export function pcmLevelDbfs(pcm: ArrayBuffer | ArrayBufferView): number {
10
+ const view = ArrayBuffer.isView(pcm) ? new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength) : new DataView(pcm)
11
+ const count = view.byteLength >> 1
12
+ if (count === 0) return PCM_LEVEL_FLOOR_DBFS
13
+ let sumSquares = 0
14
+ for (let i = 0; i < count; i++) {
15
+ const sample = view.getInt16(i * 2, true)
16
+ sumSquares += sample * sample
17
+ }
18
+ const rms = Math.sqrt(sumSquares / count)
19
+ return Math.max(PCM_LEVEL_FLOOR_DBFS, 20 * Math.log10(rms / 32767))
20
+ }