@elizaos/plugin-facewear 2.0.3-beta.6 → 2.0.3-beta.7
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/dist/actions/display-text.d.ts +4 -0
- package/dist/actions/display-text.d.ts.map +1 -0
- package/dist/actions/display-text.js +90 -0
- package/dist/actions/display-text.js.map +1 -0
- package/dist/actions/facewear-connect.d.ts +3 -0
- package/dist/actions/facewear-connect.d.ts.map +1 -0
- package/dist/actions/facewear-connect.js +70 -0
- package/dist/actions/facewear-connect.js.map +1 -0
- package/dist/actions/facewear-control.d.ts +4 -0
- package/dist/actions/facewear-control.d.ts.map +1 -0
- package/dist/actions/facewear-control.js +627 -0
- package/dist/actions/facewear-control.js.map +1 -0
- package/dist/actions/facewear-debug.d.ts +3 -0
- package/dist/actions/facewear-debug.d.ts.map +1 -0
- package/dist/actions/facewear-debug.js +62 -0
- package/dist/actions/facewear-debug.js.map +1 -0
- package/dist/actions/facewear-status.d.ts +4 -0
- package/dist/actions/facewear-status.d.ts.map +1 -0
- package/dist/actions/facewear-status.js +66 -0
- package/dist/actions/facewear-status.js.map +1 -0
- package/dist/actions/microphone.d.ts +4 -0
- package/dist/actions/microphone.d.ts.map +1 -0
- package/dist/actions/microphone.js +63 -0
- package/dist/actions/microphone.js.map +1 -0
- package/dist/actions/view-actions.d.ts +23 -0
- package/dist/actions/view-actions.d.ts.map +1 -0
- package/dist/actions/view-actions.js +314 -0
- package/dist/actions/view-actions.js.map +1 -0
- package/dist/actions/vision-query.d.ts +4 -0
- package/dist/actions/vision-query.d.ts.map +1 -0
- package/dist/actions/vision-query.js +41 -0
- package/dist/actions/vision-query.js.map +1 -0
- package/dist/actions/xr-view-actions.d.ts +14 -0
- package/dist/actions/xr-view-actions.d.ts.map +1 -0
- package/dist/actions/xr-view-actions.js +29 -0
- package/dist/actions/xr-view-actions.js.map +1 -0
- package/dist/components/FacewearSpatialView.d.ts +50 -0
- package/dist/components/FacewearSpatialView.d.ts.map +1 -0
- package/dist/components/FacewearSpatialView.js +129 -0
- package/dist/components/FacewearSpatialView.js.map +1 -0
- package/dist/components/FacewearView.d.ts +17 -0
- package/dist/components/FacewearView.d.ts.map +1 -0
- package/dist/components/FacewearView.js +88 -0
- package/dist/components/FacewearView.js.map +1 -0
- package/dist/components/SmartglassesPanelView.d.ts +22 -0
- package/dist/components/SmartglassesPanelView.d.ts.map +1 -0
- package/dist/components/SmartglassesPanelView.js +140 -0
- package/dist/components/SmartglassesPanelView.js.map +1 -0
- package/dist/components/SmartglassesSpatialView.d.ts +46 -0
- package/dist/components/SmartglassesSpatialView.d.ts.map +1 -0
- package/dist/components/SmartglassesSpatialView.js +240 -0
- package/dist/components/SmartglassesSpatialView.js.map +1 -0
- package/dist/components/facewear-profiles.d.ts +27 -0
- package/dist/components/facewear-profiles.d.ts.map +1 -0
- package/dist/components/facewear-profiles.js +40 -0
- package/dist/components/facewear-profiles.js.map +1 -0
- package/dist/devices/apple-vision-pro.d.ts +7 -0
- package/dist/devices/apple-vision-pro.d.ts.map +1 -0
- package/dist/devices/apple-vision-pro.js +21 -0
- package/dist/devices/apple-vision-pro.js.map +1 -0
- package/dist/devices/even-realities.d.ts +7 -0
- package/dist/devices/even-realities.d.ts.map +1 -0
- package/dist/devices/even-realities.js +13 -0
- package/dist/devices/even-realities.js.map +1 -0
- package/dist/devices/meta-quest.d.ts +5 -0
- package/dist/devices/meta-quest.d.ts.map +1 -0
- package/dist/devices/meta-quest.js +21 -0
- package/dist/devices/meta-quest.js.map +1 -0
- package/dist/devices/registry.d.ts +19 -0
- package/dist/devices/registry.d.ts.map +1 -0
- package/dist/devices/registry.js +96 -0
- package/dist/devices/registry.js.map +1 -0
- package/dist/devices/xreal.d.ts +7 -0
- package/dist/devices/xreal.d.ts.map +1 -0
- package/dist/devices/xreal.js +19 -0
- package/dist/devices/xreal.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/smartglasses.d.ts +306 -0
- package/dist/protocol/smartglasses.d.ts.map +1 -0
- package/dist/protocol/smartglasses.js +1485 -0
- package/dist/protocol/smartglasses.js.map +1 -0
- package/dist/protocol/xr.d.ts +137 -0
- package/dist/protocol/xr.d.ts.map +1 -0
- package/dist/protocol/xr.js +18 -0
- package/dist/protocol/xr.js.map +1 -0
- package/dist/providers/facewear-context.d.ts +3 -0
- package/dist/providers/facewear-context.d.ts.map +1 -0
- package/dist/providers/facewear-context.js +59 -0
- package/dist/providers/facewear-context.js.map +1 -0
- package/dist/providers/smartglasses-status.d.ts +3 -0
- package/dist/providers/smartglasses-status.d.ts.map +1 -0
- package/dist/providers/smartglasses-status.js +33 -0
- package/dist/providers/smartglasses-status.js.map +1 -0
- package/dist/register-terminal-view.d.ts +21 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +70 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +116 -0
- package/dist/register.js.map +1 -0
- package/dist/routes/connect.d.ts +3 -0
- package/dist/routes/connect.d.ts.map +1 -0
- package/dist/routes/connect.js +86 -0
- package/dist/routes/connect.js.map +1 -0
- package/dist/routes/device-config.d.ts +5 -0
- package/dist/routes/device-config.d.ts.map +1 -0
- package/dist/routes/device-config.js +56 -0
- package/dist/routes/device-config.js.map +1 -0
- package/dist/routes/simulator-route.d.ts +8 -0
- package/dist/routes/simulator-route.d.ts.map +1 -0
- package/dist/routes/simulator-route.js +32 -0
- package/dist/routes/simulator-route.js.map +1 -0
- package/dist/routes/status.d.ts +3 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +34 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/view-host.d.ts +24 -0
- package/dist/routes/view-host.d.ts.map +1 -0
- package/dist/routes/view-host.js +339 -0
- package/dist/routes/view-host.js.map +1 -0
- package/dist/routes/views.d.ts +8 -0
- package/dist/routes/views.d.ts.map +1 -0
- package/dist/routes/views.js +31 -0
- package/dist/routes/views.js.map +1 -0
- package/dist/services/audio-pipeline.d.ts +20 -0
- package/dist/services/audio-pipeline.d.ts.map +1 -0
- package/dist/services/audio-pipeline.js +87 -0
- package/dist/services/audio-pipeline.js.map +1 -0
- package/dist/services/facewear-service.d.ts +26 -0
- package/dist/services/facewear-service.d.ts.map +1 -0
- package/dist/services/facewear-service.js +45 -0
- package/dist/services/facewear-service.js.map +1 -0
- package/dist/services/smartglasses-service.d.ts +244 -0
- package/dist/services/smartglasses-service.d.ts.map +1 -0
- package/dist/services/smartglasses-service.js +821 -0
- package/dist/services/smartglasses-service.js.map +1 -0
- package/dist/services/vision-pipeline.d.ts +16 -0
- package/dist/services/vision-pipeline.d.ts.map +1 -0
- package/dist/services/vision-pipeline.js +39 -0
- package/dist/services/vision-pipeline.js.map +1 -0
- package/dist/services/xr-session-service.d.ts +54 -0
- package/dist/services/xr-session-service.d.ts.map +1 -0
- package/dist/services/xr-session-service.js +345 -0
- package/dist/services/xr-session-service.js.map +1 -0
- package/dist/status-format.d.ts +15 -0
- package/dist/status-format.d.ts.map +1 -0
- package/dist/status-format.js +89 -0
- package/dist/status-format.js.map +1 -0
- package/dist/transport/even-bridge.d.ts +69 -0
- package/dist/transport/even-bridge.d.ts.map +1 -0
- package/dist/transport/even-bridge.js +510 -0
- package/dist/transport/even-bridge.js.map +1 -0
- package/dist/transport/mock.d.ts +42 -0
- package/dist/transport/mock.d.ts.map +1 -0
- package/dist/transport/mock.js +124 -0
- package/dist/transport/mock.js.map +1 -0
- package/dist/transport/noble.d.ts +62 -0
- package/dist/transport/noble.d.ts.map +1 -0
- package/dist/transport/noble.js +256 -0
- package/dist/transport/noble.js.map +1 -0
- package/dist/transport/types.d.ts +36 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +1 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/transport/web-bluetooth.d.ts +58 -0
- package/dist/transport/web-bluetooth.d.ts.map +1 -0
- package/dist/transport/web-bluetooth.js +164 -0
- package/dist/transport/web-bluetooth.js.map +1 -0
- package/dist/ui/FacewearAppView.d.ts +4 -0
- package/dist/ui/FacewearAppView.d.ts.map +1 -0
- package/dist/ui/FacewearAppView.js +257 -0
- package/dist/ui/FacewearAppView.js.map +1 -0
- package/dist/ui/SmartglassesView.d.ts +10 -0
- package/dist/ui/SmartglassesView.d.ts.map +1 -0
- package/dist/ui/SmartglassesView.helpers.d.ts +104 -0
- package/dist/ui/SmartglassesView.helpers.d.ts.map +1 -0
- package/dist/ui/SmartglassesView.helpers.js +261 -0
- package/dist/ui/SmartglassesView.helpers.js.map +1 -0
- package/dist/ui/SmartglassesView.js +1189 -0
- package/dist/ui/SmartglassesView.js.map +1 -0
- package/dist/ui/facewear-view-bundle.d.ts +5 -0
- package/dist/ui/facewear-view-bundle.d.ts.map +1 -0
- package/dist/ui/facewear-view-bundle.js +17 -0
- package/dist/ui/facewear-view-bundle.js.map +1 -0
- package/dist/views/bundle.js +2950 -0
- package/dist/views/bundle.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
import { logger, Service } from "@elizaos/core";
|
|
2
|
+
import {
|
|
3
|
+
encodeAppWhitelist,
|
|
4
|
+
encodeBatteryStatusRequest,
|
|
5
|
+
encodeBmpTransfer,
|
|
6
|
+
encodeBrightness,
|
|
7
|
+
encodeClearScreen,
|
|
8
|
+
encodeConnectionReady,
|
|
9
|
+
encodeDashboard,
|
|
10
|
+
encodeDashboardCalendarItem,
|
|
11
|
+
encodeDashboardLayout,
|
|
12
|
+
encodeDashboardPosition,
|
|
13
|
+
encodeDashboardTimeWeather,
|
|
14
|
+
encodeExitFunction,
|
|
15
|
+
encodeG1MonochromeBmp,
|
|
16
|
+
encodeG1Setup,
|
|
17
|
+
encodeGetSerial,
|
|
18
|
+
encodeGlassesWear,
|
|
19
|
+
encodeHeadUpAngle,
|
|
20
|
+
encodeHeartbeat,
|
|
21
|
+
encodeMicCommand,
|
|
22
|
+
encodeNavigationDirections,
|
|
23
|
+
encodeNavigationEnd,
|
|
24
|
+
encodeNavigationInit,
|
|
25
|
+
encodeNavigationPoller,
|
|
26
|
+
encodeNavigationPrimaryImage,
|
|
27
|
+
encodeNavigationSecondaryImage,
|
|
28
|
+
encodeNoteAdd,
|
|
29
|
+
encodeNoteDelete,
|
|
30
|
+
encodeNotification,
|
|
31
|
+
encodeSilentMode,
|
|
32
|
+
encodeStartAi,
|
|
33
|
+
encodeTextPackets,
|
|
34
|
+
encodeTranslateLanguages,
|
|
35
|
+
encodeTranslateSetup,
|
|
36
|
+
encodeTranslateStart,
|
|
37
|
+
encodeTranslateText,
|
|
38
|
+
encodeVoiceNoteDelete,
|
|
39
|
+
encodeVoiceNoteDeleteAll,
|
|
40
|
+
encodeVoiceNoteFetch,
|
|
41
|
+
encodeVoiceNoteList,
|
|
42
|
+
G1AiStatus,
|
|
43
|
+
G1ScreenAction,
|
|
44
|
+
G1SubCommand,
|
|
45
|
+
G1TextStatus,
|
|
46
|
+
microphoneActionForInteractionEvent,
|
|
47
|
+
paginateDisplayText,
|
|
48
|
+
parseG1Notification,
|
|
49
|
+
pcm16ToFloat32
|
|
50
|
+
} from "../protocol/smartglasses.js";
|
|
51
|
+
import { getGlobalEvenBridgeTransport } from "../transport/even-bridge.js";
|
|
52
|
+
import { getNobleG1Transport } from "../transport/noble.js";
|
|
53
|
+
import { getWebBluetoothG1Transport } from "../transport/web-bluetooth.js";
|
|
54
|
+
const SMARTGLASSES_SERVICE_NAME = "smartglasses";
|
|
55
|
+
const SMARTGLASSES_EVENT = "SMARTGLASSES_EVENT";
|
|
56
|
+
const SMARTGLASSES_AUDIO_EVENT = "SMARTGLASSES_AUDIO";
|
|
57
|
+
const SMARTGLASSES_TRANSCRIPT_EVENT = "SMARTGLASSES_TRANSCRIPT";
|
|
58
|
+
const SMARTGLASSES_TRANSPORT_SETTING = "SMARTGLASSES_TRANSPORT";
|
|
59
|
+
const SMARTGLASSES_SCAN_TIMEOUT_SETTING = "SMARTGLASSES_SCAN_TIMEOUT_MS";
|
|
60
|
+
const SMARTGLASSES_AUTO_INIT_SETTING = "SMARTGLASSES_AUTO_INIT";
|
|
61
|
+
const SMARTGLASSES_INIT_MODE_SETTING = "SMARTGLASSES_INIT_MODE";
|
|
62
|
+
const FACEWEAR_SMARTGLASSES_TRANSPORT_SETTING = "FACEWEAR_SMARTGLASSES_TRANSPORT";
|
|
63
|
+
const FACEWEAR_SCAN_TIMEOUT_SETTING = "FACEWEAR_SCAN_TIMEOUT_MS";
|
|
64
|
+
const FACEWEAR_AUTO_INIT_SETTING = "FACEWEAR_AUTO_INIT";
|
|
65
|
+
const FACEWEAR_INIT_MODE_SETTING = "FACEWEAR_INIT_MODE";
|
|
66
|
+
let injectedTransport = null;
|
|
67
|
+
let injectedAudioDecoder = null;
|
|
68
|
+
function setSmartglassesTransportForRuntime(transport) {
|
|
69
|
+
injectedTransport = transport;
|
|
70
|
+
}
|
|
71
|
+
function setSmartglassesAudioDecoderForRuntime(decoder) {
|
|
72
|
+
injectedAudioDecoder = decoder;
|
|
73
|
+
}
|
|
74
|
+
class SmartglassesService extends Service {
|
|
75
|
+
static serviceType = SMARTGLASSES_SERVICE_NAME;
|
|
76
|
+
capabilityDescription = "Controls Even Realities G1/G2 smartglasses display and microphone input, including side-tap mic toggles";
|
|
77
|
+
transport = null;
|
|
78
|
+
microphoneEnabled = false;
|
|
79
|
+
lastEvent = null;
|
|
80
|
+
lastTranscript = null;
|
|
81
|
+
audioChunksReceived = 0;
|
|
82
|
+
lastAudioEncoding = null;
|
|
83
|
+
lastAudioSequence = null;
|
|
84
|
+
audioSequenceGaps = 0;
|
|
85
|
+
physicalState = null;
|
|
86
|
+
batteryState = null;
|
|
87
|
+
batteryLevels = {};
|
|
88
|
+
batteryVoltagesMv = {};
|
|
89
|
+
deviceState = null;
|
|
90
|
+
lastSerialNumber = null;
|
|
91
|
+
lastWifiStatus = null;
|
|
92
|
+
displaySeq = 0;
|
|
93
|
+
heartbeatSeq = 0;
|
|
94
|
+
dashboardSeq = 0;
|
|
95
|
+
navigationSeq = 0;
|
|
96
|
+
navigationPollerSeq = 1;
|
|
97
|
+
translateSyncId = 0;
|
|
98
|
+
heartbeatTimer = null;
|
|
99
|
+
heartbeatIntervalMs = null;
|
|
100
|
+
lastHeartbeatAt = null;
|
|
101
|
+
voiceNoteSyncId = 0;
|
|
102
|
+
transcriptCallbacks = /* @__PURE__ */ new Set();
|
|
103
|
+
audioCallbacks = /* @__PURE__ */ new Set();
|
|
104
|
+
rawAudioCallbacks = /* @__PURE__ */ new Set();
|
|
105
|
+
audioDecoder = injectedAudioDecoder;
|
|
106
|
+
disposers = [];
|
|
107
|
+
static async start(runtime) {
|
|
108
|
+
const service = new SmartglassesService(runtime);
|
|
109
|
+
service.transport = injectedTransport ?? await chooseTransport(runtime);
|
|
110
|
+
if (!service.transport) {
|
|
111
|
+
logger.info(
|
|
112
|
+
"[plugin-facewear/smartglasses] no transport available; service loaded in offline/mockable mode"
|
|
113
|
+
);
|
|
114
|
+
return service;
|
|
115
|
+
}
|
|
116
|
+
await service.connect();
|
|
117
|
+
if (readBooleanSetting(
|
|
118
|
+
runtime,
|
|
119
|
+
[FACEWEAR_AUTO_INIT_SETTING, SMARTGLASSES_AUTO_INIT_SETTING],
|
|
120
|
+
true
|
|
121
|
+
)) {
|
|
122
|
+
await service.sendConnectionReady(
|
|
123
|
+
"both",
|
|
124
|
+
readConnectionReadyModeSetting(runtime)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return service;
|
|
128
|
+
}
|
|
129
|
+
setTransport(transport) {
|
|
130
|
+
void this.disconnect();
|
|
131
|
+
this.transport = transport;
|
|
132
|
+
}
|
|
133
|
+
async connect() {
|
|
134
|
+
if (!this.transport)
|
|
135
|
+
throw new Error("No smartglasses transport is configured");
|
|
136
|
+
if (this.transport.isConnected()) {
|
|
137
|
+
this.attachTransportListeners();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
await this.transport.connect();
|
|
141
|
+
this.attachTransportListeners();
|
|
142
|
+
}
|
|
143
|
+
attachTransportListeners() {
|
|
144
|
+
if (!this.transport || this.disposers.length > 0) return;
|
|
145
|
+
this.disposers.push(
|
|
146
|
+
this.transport.onEvent((event) => void this.handleEvent(event))
|
|
147
|
+
);
|
|
148
|
+
this.disposers.push(
|
|
149
|
+
this.transport.onAudio(
|
|
150
|
+
(audioData, sampleRate, side, encoding, sequence) => {
|
|
151
|
+
const audioEncoding = encoding ?? "pcm16";
|
|
152
|
+
void this.handleAudioChunk(
|
|
153
|
+
audioData,
|
|
154
|
+
sampleRate,
|
|
155
|
+
side,
|
|
156
|
+
audioEncoding,
|
|
157
|
+
sequence
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
if (this.transport.onTranscript) {
|
|
163
|
+
this.disposers.push(
|
|
164
|
+
this.transport.onTranscript((text, isFinal, metadata) => {
|
|
165
|
+
this.receiveTranscript(text, isFinal, metadata);
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (this.transport.onWifiStatus) {
|
|
170
|
+
this.disposers.push(
|
|
171
|
+
this.transport.onWifiStatus((status) => {
|
|
172
|
+
this.lastWifiStatus = status;
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async disconnect() {
|
|
178
|
+
this.stopHeartbeatLoop();
|
|
179
|
+
for (const dispose of this.disposers.splice(0)) dispose();
|
|
180
|
+
if (this.transport?.isConnected()) await this.transport.disconnect();
|
|
181
|
+
this.microphoneEnabled = false;
|
|
182
|
+
}
|
|
183
|
+
async stop() {
|
|
184
|
+
await this.disconnect();
|
|
185
|
+
}
|
|
186
|
+
getStatus() {
|
|
187
|
+
return {
|
|
188
|
+
available: Boolean(this.transport),
|
|
189
|
+
connected: this.transport?.isConnected() ?? false,
|
|
190
|
+
transport: this.transport?.name ?? null,
|
|
191
|
+
microphoneEnabled: this.microphoneEnabled,
|
|
192
|
+
heartbeatRunning: Boolean(this.heartbeatTimer),
|
|
193
|
+
heartbeatIntervalMs: this.heartbeatIntervalMs,
|
|
194
|
+
lastHeartbeatAt: this.lastHeartbeatAt,
|
|
195
|
+
lastEvent: this.lastEvent,
|
|
196
|
+
lastTranscript: this.lastTranscript,
|
|
197
|
+
audioChunksReceived: this.audioChunksReceived,
|
|
198
|
+
lastAudioEncoding: this.lastAudioEncoding,
|
|
199
|
+
lastAudioSequence: this.lastAudioSequence,
|
|
200
|
+
audioSequenceGaps: this.audioSequenceGaps,
|
|
201
|
+
physicalState: this.physicalState,
|
|
202
|
+
batteryState: this.batteryState,
|
|
203
|
+
batteryLevels: { ...this.batteryLevels },
|
|
204
|
+
batteryVoltagesMv: { ...this.batteryVoltagesMv },
|
|
205
|
+
deviceState: this.deviceState,
|
|
206
|
+
lastSerialNumber: this.lastSerialNumber,
|
|
207
|
+
connectedLenses: this.transport?.getConnectedLenses?.() ?? {},
|
|
208
|
+
wifiAvailable: this.isWifiAvailable(),
|
|
209
|
+
lastWifiStatus: this.lastWifiStatus
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async displayText(text, options = {}) {
|
|
213
|
+
if (!this.transport)
|
|
214
|
+
throw new Error("No smartglasses transport is configured");
|
|
215
|
+
if (!this.transport.isConnected()) await this.connect();
|
|
216
|
+
const pages = paginateDisplayText(text);
|
|
217
|
+
const mode = options.mode ?? "ai";
|
|
218
|
+
for (const [index, page] of pages.entries()) {
|
|
219
|
+
const seq = this.nextDisplaySeq();
|
|
220
|
+
const streamingPage = withScreenStatus(
|
|
221
|
+
page,
|
|
222
|
+
streamingStatus(mode, index)
|
|
223
|
+
);
|
|
224
|
+
for (const packet of encodeTextPackets(streamingPage, seq)) {
|
|
225
|
+
await this.transport.writeBoth(packet);
|
|
226
|
+
}
|
|
227
|
+
if (options.pageHoldMs && index < pages.length - 1) {
|
|
228
|
+
await new Promise((resolve) => setTimeout(resolve, options.pageHoldMs));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (mode === "text") return { pages: pages.length };
|
|
232
|
+
if (options.completionDelayMs) {
|
|
233
|
+
await new Promise(
|
|
234
|
+
(resolve) => setTimeout(resolve, options.completionDelayMs)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const lastPage = pages.at(-1);
|
|
238
|
+
if (lastPage) {
|
|
239
|
+
const seq = this.nextDisplaySeq();
|
|
240
|
+
for (const packet of encodeTextPackets(
|
|
241
|
+
withScreenStatus(lastPage, G1AiStatus.DisplayComplete),
|
|
242
|
+
seq
|
|
243
|
+
)) {
|
|
244
|
+
await this.transport.writeBoth(packet);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return { pages: pages.length };
|
|
248
|
+
}
|
|
249
|
+
async displayRsvpText(text, options = {}) {
|
|
250
|
+
const words = text.split(/\s+/).map((word) => word.trim()).filter(Boolean);
|
|
251
|
+
if (words.length === 0) return { groups: 0, pages: 0 };
|
|
252
|
+
const wordsPerGroup = positiveIntegerOrDefault(options.wordsPerGroup, 1);
|
|
253
|
+
const paddingChar = options.paddingChar ?? "...";
|
|
254
|
+
const groups = [];
|
|
255
|
+
for (let offset = 0; offset < words.length; offset += wordsPerGroup) {
|
|
256
|
+
const group = words.slice(offset, offset + wordsPerGroup);
|
|
257
|
+
while (group.length < wordsPerGroup) group.push(paddingChar);
|
|
258
|
+
groups.push(group.join(" "));
|
|
259
|
+
}
|
|
260
|
+
let pages = 0;
|
|
261
|
+
const delayMs = rsvpDelayMs(options.wpm, wordsPerGroup);
|
|
262
|
+
for (const group of groups) {
|
|
263
|
+
const result = await this.displayText(group, { mode: options.mode });
|
|
264
|
+
pages += result.pages;
|
|
265
|
+
if (!options.skipDelay && delayMs > 0) {
|
|
266
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { groups: groups.length, pages };
|
|
270
|
+
}
|
|
271
|
+
async clearDisplay() {
|
|
272
|
+
await this.writeBoth(encodeClearScreen());
|
|
273
|
+
}
|
|
274
|
+
async sendHeartbeat(seq) {
|
|
275
|
+
const effectiveSeq = seq === void 0 ? this.nextHeartbeatSeq() : seq;
|
|
276
|
+
await this.writeBoth(encodeHeartbeat(effectiveSeq));
|
|
277
|
+
this.lastHeartbeatAt = Date.now();
|
|
278
|
+
}
|
|
279
|
+
async requestBatteryStatus(side = "both") {
|
|
280
|
+
await this.sendRaw(encodeBatteryStatusRequest(), side);
|
|
281
|
+
}
|
|
282
|
+
startHeartbeatLoop(options = {}) {
|
|
283
|
+
const intervalMs = positiveIntegerOrDefault(options.intervalMs, 8e3);
|
|
284
|
+
this.stopHeartbeatLoop();
|
|
285
|
+
this.heartbeatIntervalMs = intervalMs;
|
|
286
|
+
if (options.immediate !== false) void this.sendHeartbeat();
|
|
287
|
+
this.heartbeatTimer = setInterval(() => {
|
|
288
|
+
void this.sendHeartbeat().catch((error) => {
|
|
289
|
+
logger.warn(
|
|
290
|
+
{ error },
|
|
291
|
+
"[plugin-facewear/smartglasses] heartbeat failed"
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
}, intervalMs);
|
|
295
|
+
this.heartbeatTimer.unref?.();
|
|
296
|
+
}
|
|
297
|
+
stopHeartbeatLoop() {
|
|
298
|
+
if (this.heartbeatTimer) {
|
|
299
|
+
clearInterval(this.heartbeatTimer);
|
|
300
|
+
this.heartbeatTimer = null;
|
|
301
|
+
}
|
|
302
|
+
this.heartbeatIntervalMs = null;
|
|
303
|
+
}
|
|
304
|
+
async sendConnectionReady(side = "both", mode = "lens-specific") {
|
|
305
|
+
if (side === "both") {
|
|
306
|
+
await this.writeSide("left", encodeConnectionReady("left", mode));
|
|
307
|
+
await this.writeSide("right", encodeConnectionReady("right", mode));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
await this.writeSide(side, encodeConnectionReady(side, mode));
|
|
311
|
+
}
|
|
312
|
+
async sendStartAi(subcommand, param = new Uint8Array()) {
|
|
313
|
+
await this.writeBoth(encodeStartAi(subcommand, param));
|
|
314
|
+
}
|
|
315
|
+
async exitToDashboard() {
|
|
316
|
+
await this.sendStartAi(G1SubCommand.Exit);
|
|
317
|
+
}
|
|
318
|
+
async exitFunction() {
|
|
319
|
+
await this.writeBoth(encodeExitFunction());
|
|
320
|
+
}
|
|
321
|
+
async requestSerial(side = "both") {
|
|
322
|
+
await this.sendRaw(encodeGetSerial(), side);
|
|
323
|
+
}
|
|
324
|
+
async sendAppWhitelist(whitelist, side = "left") {
|
|
325
|
+
const packets = encodeAppWhitelist(whitelist);
|
|
326
|
+
for (const packet of packets) await this.sendRaw(packet, side);
|
|
327
|
+
return { packets: packets.length };
|
|
328
|
+
}
|
|
329
|
+
async sendG1Setup(payload, side = "left") {
|
|
330
|
+
const packets = encodeG1Setup(payload);
|
|
331
|
+
for (const packet of packets) await this.sendRaw(packet, side);
|
|
332
|
+
return { packets: packets.length };
|
|
333
|
+
}
|
|
334
|
+
async sendRaw(packet, side = "both") {
|
|
335
|
+
if (side === "both") {
|
|
336
|
+
await this.writeBoth(packet);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
await this.writeSide(side, packet);
|
|
340
|
+
}
|
|
341
|
+
async pageUp() {
|
|
342
|
+
await this.writeSide("left", encodeStartAi(G1SubCommand.PageControl));
|
|
343
|
+
}
|
|
344
|
+
async pageDown() {
|
|
345
|
+
await this.writeSide("right", encodeStartAi(G1SubCommand.PageControl));
|
|
346
|
+
}
|
|
347
|
+
async setMicrophoneEnabled(enabled) {
|
|
348
|
+
if (!this.transport)
|
|
349
|
+
throw new Error("No smartglasses transport is configured");
|
|
350
|
+
if (!this.transport.isConnected()) await this.connect();
|
|
351
|
+
await this.transport.openMicrophone(enabled);
|
|
352
|
+
this.microphoneEnabled = enabled;
|
|
353
|
+
}
|
|
354
|
+
async sendMicCommandPacket(enabled) {
|
|
355
|
+
if (!this.transport)
|
|
356
|
+
throw new Error("No smartglasses transport is configured");
|
|
357
|
+
if (!this.transport.isConnected()) await this.connect();
|
|
358
|
+
await this.transport.write("right", encodeMicCommand(enabled));
|
|
359
|
+
this.microphoneEnabled = enabled;
|
|
360
|
+
}
|
|
361
|
+
async setSilentMode(enabled) {
|
|
362
|
+
await this.writeBoth(encodeSilentMode(enabled));
|
|
363
|
+
}
|
|
364
|
+
async setBrightness(level, auto = false) {
|
|
365
|
+
await this.writeBoth(encodeBrightness(level, auto));
|
|
366
|
+
}
|
|
367
|
+
async setDashboard(enabled, position = 0) {
|
|
368
|
+
await this.writeBoth(encodeDashboard(enabled, position));
|
|
369
|
+
}
|
|
370
|
+
async setDashboardPosition(height, depth) {
|
|
371
|
+
await this.writeBoth(
|
|
372
|
+
encodeDashboardPosition(height, depth, this.nextDashboardSeq())
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
async setDashboardLayout(layout) {
|
|
376
|
+
await this.writeBoth(encodeDashboardLayout(layout));
|
|
377
|
+
}
|
|
378
|
+
async sendDashboardCalendarItem(payload) {
|
|
379
|
+
await this.writeBoth(encodeDashboardCalendarItem(payload));
|
|
380
|
+
}
|
|
381
|
+
async sendDashboardTimeWeather(payload) {
|
|
382
|
+
await this.writeBoth(
|
|
383
|
+
encodeDashboardTimeWeather({
|
|
384
|
+
...payload,
|
|
385
|
+
seqId: payload.seqId ?? this.nextDashboardSeq()
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
async setHeadUpAngle(angle) {
|
|
390
|
+
await this.writeBoth(encodeHeadUpAngle(angle));
|
|
391
|
+
}
|
|
392
|
+
async setGlassesWearDetection(enabled) {
|
|
393
|
+
await this.writeBoth(encodeGlassesWear(enabled));
|
|
394
|
+
}
|
|
395
|
+
async scanWifi() {
|
|
396
|
+
const wifi = this.requireWifiCapability("scanWifi");
|
|
397
|
+
const result = await wifi.scanWifi();
|
|
398
|
+
this.lastWifiStatus = result;
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
async getWifiStatus() {
|
|
402
|
+
const wifi = this.requireWifiCapability("getWifiStatus");
|
|
403
|
+
const result = await wifi.getWifiStatus();
|
|
404
|
+
this.lastWifiStatus = result;
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
async configureWifi(ssid, password) {
|
|
408
|
+
if (!ssid.trim()) throw new Error("Wi-Fi SSID is required");
|
|
409
|
+
const wifi = this.requireWifiCapability("configureWifi");
|
|
410
|
+
const result = await wifi.configureWifi(ssid.trim(), password);
|
|
411
|
+
this.lastWifiStatus = result;
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
async requestWifiSetup(reason) {
|
|
415
|
+
const wifi = this.requireWifiCapability("requestWifiSetup");
|
|
416
|
+
const result = await wifi.requestWifiSetup(reason);
|
|
417
|
+
this.lastWifiStatus = result;
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
async startNavigation() {
|
|
421
|
+
await this.writeBoth(encodeNavigationInit(this.nextNavigationSeq()));
|
|
422
|
+
}
|
|
423
|
+
async sendNavigationDirections(payload) {
|
|
424
|
+
await this.writeBoth(
|
|
425
|
+
encodeNavigationDirections({
|
|
426
|
+
...payload,
|
|
427
|
+
seqId: payload.seqId ?? this.nextNavigationSeq()
|
|
428
|
+
})
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
async sendNavigationPrimaryImage(image, overlay) {
|
|
432
|
+
const packets = encodeNavigationPrimaryImage(
|
|
433
|
+
image,
|
|
434
|
+
overlay,
|
|
435
|
+
this.navigationSeq
|
|
436
|
+
);
|
|
437
|
+
this.navigationSeq = this.navigationSeq + packets.length & 255;
|
|
438
|
+
for (const packet of packets) await this.writeBoth(packet);
|
|
439
|
+
return { packets: packets.length };
|
|
440
|
+
}
|
|
441
|
+
async sendNavigationSecondaryImage(image, overlay) {
|
|
442
|
+
const packets = encodeNavigationSecondaryImage(
|
|
443
|
+
image,
|
|
444
|
+
overlay,
|
|
445
|
+
this.navigationSeq
|
|
446
|
+
);
|
|
447
|
+
this.navigationSeq = this.navigationSeq + packets.length & 255;
|
|
448
|
+
for (const packet of packets) await this.writeBoth(packet);
|
|
449
|
+
return { packets: packets.length };
|
|
450
|
+
}
|
|
451
|
+
async sendNavigationPoller() {
|
|
452
|
+
await this.writeBoth(
|
|
453
|
+
encodeNavigationPoller(
|
|
454
|
+
this.nextNavigationSeq(),
|
|
455
|
+
this.nextNavigationPollerSeq()
|
|
456
|
+
)
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
async endNavigation() {
|
|
460
|
+
await this.writeBoth(encodeNavigationEnd(this.nextNavigationSeq()));
|
|
461
|
+
}
|
|
462
|
+
async sendTranslateSetup() {
|
|
463
|
+
await this.writeBoth(encodeTranslateSetup());
|
|
464
|
+
}
|
|
465
|
+
async startTranslate() {
|
|
466
|
+
await this.writeSide("right", encodeTranslateStart());
|
|
467
|
+
}
|
|
468
|
+
async setTranslateLanguages(fromLanguage, toLanguage) {
|
|
469
|
+
await this.writeBoth(encodeTranslateLanguages(fromLanguage, toLanguage));
|
|
470
|
+
}
|
|
471
|
+
async sendTranslateText(kind, text, syncId) {
|
|
472
|
+
const effectiveSyncId = syncId ?? this.nextTranslateSyncId();
|
|
473
|
+
await this.writeBoth(encodeTranslateText(kind, text, effectiveSyncId));
|
|
474
|
+
return { syncId: effectiveSyncId };
|
|
475
|
+
}
|
|
476
|
+
async addOrUpdateNote(noteNumber, title, text) {
|
|
477
|
+
await this.writeBoth(encodeNoteAdd(noteNumber, title, text));
|
|
478
|
+
}
|
|
479
|
+
async deleteNote(noteNumber) {
|
|
480
|
+
await this.writeBoth(encodeNoteDelete(noteNumber));
|
|
481
|
+
}
|
|
482
|
+
async requestVoiceNoteAudio(noteIndex, options = {}) {
|
|
483
|
+
const syncId = options.syncId ?? this.nextVoiceNoteSyncId();
|
|
484
|
+
await this.writeSide(
|
|
485
|
+
options.side ?? "right",
|
|
486
|
+
encodeVoiceNoteFetch(noteIndex, syncId)
|
|
487
|
+
);
|
|
488
|
+
return { syncId };
|
|
489
|
+
}
|
|
490
|
+
async requestVoiceNoteList(options = {}) {
|
|
491
|
+
const syncId = options.syncId ?? this.nextVoiceNoteSyncId();
|
|
492
|
+
await this.writeSide(options.side ?? "right", encodeVoiceNoteList(syncId));
|
|
493
|
+
return { syncId };
|
|
494
|
+
}
|
|
495
|
+
async deleteVoiceNoteAudio(noteIndex, options = {}) {
|
|
496
|
+
const syncId = options.syncId ?? this.nextVoiceNoteSyncId();
|
|
497
|
+
await this.writeSide(
|
|
498
|
+
options.side ?? "right",
|
|
499
|
+
encodeVoiceNoteDelete(noteIndex, syncId)
|
|
500
|
+
);
|
|
501
|
+
return { syncId };
|
|
502
|
+
}
|
|
503
|
+
async deleteAllVoiceNoteAudio(options = {}) {
|
|
504
|
+
const syncId = options.syncId ?? this.nextVoiceNoteSyncId();
|
|
505
|
+
await this.writeSide(
|
|
506
|
+
options.side ?? "right",
|
|
507
|
+
encodeVoiceNoteDeleteAll(syncId)
|
|
508
|
+
);
|
|
509
|
+
return { syncId };
|
|
510
|
+
}
|
|
511
|
+
async sendNotification(payload) {
|
|
512
|
+
const packets = encodeNotification(payload);
|
|
513
|
+
for (const packet of packets) await this.writeBoth(packet);
|
|
514
|
+
return { packets: packets.length };
|
|
515
|
+
}
|
|
516
|
+
async sendBmpImage(imageData) {
|
|
517
|
+
const packets = encodeBmpTransfer(imageData);
|
|
518
|
+
for (const packet of packets) await this.writeBoth(packet);
|
|
519
|
+
return { packets: packets.length };
|
|
520
|
+
}
|
|
521
|
+
async sendMonochromeBmpImage(pixels, options = {}) {
|
|
522
|
+
const imageData = encodeG1MonochromeBmp(pixels, options);
|
|
523
|
+
const result = await this.sendBmpImage(imageData);
|
|
524
|
+
return { ...result, bytes: imageData.length };
|
|
525
|
+
}
|
|
526
|
+
onTranscript(callback) {
|
|
527
|
+
this.transcriptCallbacks.add(callback);
|
|
528
|
+
return () => this.transcriptCallbacks.delete(callback);
|
|
529
|
+
}
|
|
530
|
+
onAudio(callback) {
|
|
531
|
+
this.audioCallbacks.add(callback);
|
|
532
|
+
return () => this.audioCallbacks.delete(callback);
|
|
533
|
+
}
|
|
534
|
+
onRawAudio(callback) {
|
|
535
|
+
this.rawAudioCallbacks.add(callback);
|
|
536
|
+
return () => this.rawAudioCallbacks.delete(callback);
|
|
537
|
+
}
|
|
538
|
+
setAudioDecoder(decoder) {
|
|
539
|
+
this.audioDecoder = decoder;
|
|
540
|
+
}
|
|
541
|
+
receiveTranscript(text, isFinal = true, metadata) {
|
|
542
|
+
this.lastTranscript = text;
|
|
543
|
+
for (const callback of this.transcriptCallbacks) callback(text, isFinal);
|
|
544
|
+
void this.emitPluginEvent(SMARTGLASSES_TRANSCRIPT_EVENT, {
|
|
545
|
+
text,
|
|
546
|
+
isFinal,
|
|
547
|
+
metadata
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
async receiveExternalRawEvent(side, data, options = {}) {
|
|
551
|
+
const event = parseG1Notification(side, data);
|
|
552
|
+
await this.handleEvent(event, options);
|
|
553
|
+
return event;
|
|
554
|
+
}
|
|
555
|
+
async receiveExternalAudioChunk(audioData, options = {}) {
|
|
556
|
+
await this.handleAudioChunk(
|
|
557
|
+
audioData,
|
|
558
|
+
options.sampleRate ?? 16e3,
|
|
559
|
+
options.side ?? "right",
|
|
560
|
+
options.encoding ?? "lc3",
|
|
561
|
+
options.sequence
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
async handleAudioChunk(audioData, sampleRate, side, audioEncoding, sequence) {
|
|
565
|
+
this.audioChunksReceived += 1;
|
|
566
|
+
this.lastAudioEncoding = audioEncoding;
|
|
567
|
+
if (sequence !== void 0) {
|
|
568
|
+
if (this.lastAudioSequence !== null && (this.lastAudioSequence + 1 & 255) !== sequence) {
|
|
569
|
+
this.audioSequenceGaps += 1;
|
|
570
|
+
}
|
|
571
|
+
this.lastAudioSequence = sequence;
|
|
572
|
+
}
|
|
573
|
+
for (const callback of this.rawAudioCallbacks)
|
|
574
|
+
callback(audioData, sampleRate, side, audioEncoding, sequence);
|
|
575
|
+
const payload = {
|
|
576
|
+
side,
|
|
577
|
+
sampleRate,
|
|
578
|
+
audioData,
|
|
579
|
+
audioEncoding,
|
|
580
|
+
audioSequenceGaps: this.audioSequenceGaps
|
|
581
|
+
};
|
|
582
|
+
if (sequence !== void 0) payload.sequence = sequence;
|
|
583
|
+
const audioPcm = audioEncoding === "pcm16" ? audioData : await this.decodeAudioChunk(
|
|
584
|
+
audioData,
|
|
585
|
+
sampleRate,
|
|
586
|
+
side,
|
|
587
|
+
audioEncoding,
|
|
588
|
+
sequence
|
|
589
|
+
);
|
|
590
|
+
if (audioPcm) {
|
|
591
|
+
const pcm = pcm16ToFloat32(audioPcm);
|
|
592
|
+
for (const callback of this.audioCallbacks)
|
|
593
|
+
callback(pcm, sampleRate, side);
|
|
594
|
+
payload.audioPcm = audioPcm;
|
|
595
|
+
payload.decodedAudioEncoding = "pcm16";
|
|
596
|
+
}
|
|
597
|
+
void this.emitPluginEvent(SMARTGLASSES_AUDIO_EVENT, {
|
|
598
|
+
...payload
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
async decodeAudioChunk(audioData, sampleRate, side, audioEncoding, sequence) {
|
|
602
|
+
if (!this.audioDecoder) return null;
|
|
603
|
+
try {
|
|
604
|
+
return await this.audioDecoder(audioData, {
|
|
605
|
+
sampleRate,
|
|
606
|
+
side,
|
|
607
|
+
encoding: audioEncoding,
|
|
608
|
+
sequence
|
|
609
|
+
}) ?? null;
|
|
610
|
+
} catch (error) {
|
|
611
|
+
logger.warn(
|
|
612
|
+
{ error },
|
|
613
|
+
"[plugin-facewear/smartglasses] audio decoder failed; raw audio event preserved"
|
|
614
|
+
);
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async handleEvent(event, options = {}) {
|
|
619
|
+
this.lastEvent = event;
|
|
620
|
+
void this.emitPluginEvent(SMARTGLASSES_EVENT, { event });
|
|
621
|
+
if (event.type === "mic-response" && typeof event.micEnabled === "boolean") {
|
|
622
|
+
this.microphoneEnabled = event.micEnabled;
|
|
623
|
+
}
|
|
624
|
+
if (event.type === "state") {
|
|
625
|
+
if (event.stateCategory === "physical") {
|
|
626
|
+
this.physicalState = event.stateName ?? event.label ?? null;
|
|
627
|
+
} else if (event.stateCategory === "battery") {
|
|
628
|
+
this.batteryState = event.stateName ?? event.label ?? null;
|
|
629
|
+
} else if (event.stateCategory === "device") {
|
|
630
|
+
this.deviceState = event.stateName ?? event.label ?? null;
|
|
631
|
+
}
|
|
632
|
+
const applyControls = options.applyControls !== false;
|
|
633
|
+
const microphoneAction = microphoneActionForInteractionEvent(event);
|
|
634
|
+
if (microphoneAction) {
|
|
635
|
+
const enabled = microphoneAction === "enable";
|
|
636
|
+
if (applyControls) await this.setMicrophoneEnabled(enabled);
|
|
637
|
+
else this.microphoneEnabled = enabled;
|
|
638
|
+
}
|
|
639
|
+
if (applyControls && event.label === "scroll_up") await this.pageUp();
|
|
640
|
+
if (applyControls && event.label === "scroll_down") await this.pageDown();
|
|
641
|
+
}
|
|
642
|
+
if (event.type === "serial" && event.serialNumber) {
|
|
643
|
+
this.lastSerialNumber = event.serialNumber;
|
|
644
|
+
}
|
|
645
|
+
if (event.type === "battery-status" && typeof event.batteryPercent === "number") {
|
|
646
|
+
this.batteryLevels[event.side] = event.batteryPercent;
|
|
647
|
+
if (typeof event.batteryVoltageMv === "number") {
|
|
648
|
+
this.batteryVoltagesMv[event.side] = event.batteryVoltageMv;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async writeBoth(packet) {
|
|
653
|
+
if (!this.transport)
|
|
654
|
+
throw new Error("No smartglasses transport is configured");
|
|
655
|
+
if (!this.transport.isConnected()) await this.connect();
|
|
656
|
+
await this.transport.writeBoth(packet);
|
|
657
|
+
}
|
|
658
|
+
async writeSide(side, packet) {
|
|
659
|
+
if (!this.transport)
|
|
660
|
+
throw new Error("No smartglasses transport is configured");
|
|
661
|
+
if (!this.transport.isConnected()) await this.connect();
|
|
662
|
+
await this.transport.write(side, packet);
|
|
663
|
+
}
|
|
664
|
+
requireWifiCapability(method) {
|
|
665
|
+
if (!this.transport)
|
|
666
|
+
throw new Error("No smartglasses transport is configured");
|
|
667
|
+
if (!this.isWifiAvailable() || !this.transport[method]) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
"Wi-Fi is only available through a native smartglasses bridge transport"
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
return this.transport;
|
|
673
|
+
}
|
|
674
|
+
isWifiAvailable() {
|
|
675
|
+
if (!this.transport) return false;
|
|
676
|
+
if (this.transport.supportsWifi) return this.transport.supportsWifi();
|
|
677
|
+
return Boolean(
|
|
678
|
+
this.transport.scanWifi || this.transport.getWifiStatus || this.transport.configureWifi || this.transport.requestWifiSetup
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
nextDisplaySeq() {
|
|
682
|
+
const seq = this.displaySeq & 255;
|
|
683
|
+
this.displaySeq = this.displaySeq + 1 & 255;
|
|
684
|
+
return seq;
|
|
685
|
+
}
|
|
686
|
+
nextHeartbeatSeq() {
|
|
687
|
+
const seq = this.heartbeatSeq & 255;
|
|
688
|
+
this.heartbeatSeq = this.heartbeatSeq + 1 & 255;
|
|
689
|
+
return seq;
|
|
690
|
+
}
|
|
691
|
+
nextDashboardSeq() {
|
|
692
|
+
const seq = this.dashboardSeq & 255;
|
|
693
|
+
this.dashboardSeq = this.dashboardSeq + 1 & 255;
|
|
694
|
+
return seq;
|
|
695
|
+
}
|
|
696
|
+
nextNavigationSeq() {
|
|
697
|
+
const seq = this.navigationSeq & 255;
|
|
698
|
+
this.navigationSeq = this.navigationSeq + 1 & 255;
|
|
699
|
+
return seq;
|
|
700
|
+
}
|
|
701
|
+
nextNavigationPollerSeq() {
|
|
702
|
+
const seq = this.navigationPollerSeq & 255;
|
|
703
|
+
this.navigationPollerSeq = this.navigationPollerSeq + 1 & 255;
|
|
704
|
+
return seq;
|
|
705
|
+
}
|
|
706
|
+
nextVoiceNoteSyncId() {
|
|
707
|
+
const syncId = this.voiceNoteSyncId & 255;
|
|
708
|
+
this.voiceNoteSyncId = this.voiceNoteSyncId + 1 & 255;
|
|
709
|
+
return syncId;
|
|
710
|
+
}
|
|
711
|
+
nextTranslateSyncId() {
|
|
712
|
+
this.translateSyncId = this.translateSyncId + 1 & 255;
|
|
713
|
+
return this.translateSyncId;
|
|
714
|
+
}
|
|
715
|
+
async emitPluginEvent(eventName, payload) {
|
|
716
|
+
if (!this.runtime) return;
|
|
717
|
+
await this.runtime.emitEvent(eventName, {
|
|
718
|
+
runtime: this.runtime,
|
|
719
|
+
source: "@elizaos/plugin-facewear",
|
|
720
|
+
...payload
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function getSmartglassesService(runtime) {
|
|
725
|
+
return runtime.getService(SMARTGLASSES_SERVICE_NAME) ?? null;
|
|
726
|
+
}
|
|
727
|
+
async function chooseTransport(runtime) {
|
|
728
|
+
const preferred = normalizePreferredTransport(
|
|
729
|
+
readFirstSetting(runtime, [
|
|
730
|
+
FACEWEAR_SMARTGLASSES_TRANSPORT_SETTING,
|
|
731
|
+
SMARTGLASSES_TRANSPORT_SETTING
|
|
732
|
+
])
|
|
733
|
+
);
|
|
734
|
+
const scanTimeoutMs = readPositiveIntegerSetting(runtime, [
|
|
735
|
+
FACEWEAR_SCAN_TIMEOUT_SETTING,
|
|
736
|
+
SMARTGLASSES_SCAN_TIMEOUT_SETTING
|
|
737
|
+
]);
|
|
738
|
+
if (preferred === "even-bridge") return getGlobalEvenBridgeTransport();
|
|
739
|
+
if (preferred === "web-bluetooth") return getWebBluetoothG1Transport();
|
|
740
|
+
if (preferred === "noble") return getNobleG1Transport({ scanTimeoutMs });
|
|
741
|
+
return getGlobalEvenBridgeTransport() ?? getWebBluetoothG1Transport() ?? await getNobleG1Transport({ scanTimeoutMs });
|
|
742
|
+
}
|
|
743
|
+
function readSetting(runtime, key) {
|
|
744
|
+
return runtime.getSetting?.(key) ?? (typeof process !== "undefined" ? process.env[key] : void 0);
|
|
745
|
+
}
|
|
746
|
+
function readFirstSetting(runtime, keys) {
|
|
747
|
+
for (const key of keys) {
|
|
748
|
+
const value = readSetting(runtime, key);
|
|
749
|
+
if (value !== void 0 && value !== null && value !== "") return value;
|
|
750
|
+
}
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
function normalizePreferredTransport(value) {
|
|
754
|
+
if (typeof value !== "string") return "auto";
|
|
755
|
+
const normalized = value.trim().toLowerCase();
|
|
756
|
+
if (normalized === "even-bridge" || normalized === "web-bluetooth" || normalized === "noble") {
|
|
757
|
+
return normalized;
|
|
758
|
+
}
|
|
759
|
+
return "auto";
|
|
760
|
+
}
|
|
761
|
+
function readPositiveIntegerSetting(runtime, keys) {
|
|
762
|
+
const value = Array.isArray(keys) ? readFirstSetting(runtime, keys) : readSetting(runtime, keys);
|
|
763
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
764
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
765
|
+
}
|
|
766
|
+
function readBooleanSetting(runtime, keys, fallback) {
|
|
767
|
+
const value = Array.isArray(keys) ? readFirstSetting(runtime, keys) : readSetting(runtime, keys);
|
|
768
|
+
if (typeof value === "boolean") return value;
|
|
769
|
+
if (typeof value === "number") return value !== 0;
|
|
770
|
+
if (typeof value === "string") {
|
|
771
|
+
if (/^(false|0|no|off|disabled)$/i.test(value.trim())) return false;
|
|
772
|
+
if (/^(true|1|yes|on|enabled)$/i.test(value.trim())) return true;
|
|
773
|
+
}
|
|
774
|
+
return fallback;
|
|
775
|
+
}
|
|
776
|
+
function readConnectionReadyModeSetting(runtime) {
|
|
777
|
+
const value = String(
|
|
778
|
+
readFirstSetting(runtime, [
|
|
779
|
+
FACEWEAR_INIT_MODE_SETTING,
|
|
780
|
+
SMARTGLASSES_INIT_MODE_SETTING
|
|
781
|
+
]) ?? ""
|
|
782
|
+
).trim().toLowerCase();
|
|
783
|
+
if (value === "official" || value === "official-app" || value === "even-demo-app" || value === "same-init")
|
|
784
|
+
return "official";
|
|
785
|
+
if (value === "android-f4" || value === "android" || value === "even-demo-android" || value === "f4")
|
|
786
|
+
return "android-f4";
|
|
787
|
+
return "lens-specific";
|
|
788
|
+
}
|
|
789
|
+
function withScreenStatus(page, screenStatus) {
|
|
790
|
+
return { ...page, screenStatus };
|
|
791
|
+
}
|
|
792
|
+
function streamingStatus(mode, pageIndex) {
|
|
793
|
+
if (mode === "text") return G1TextStatus.TextShow | G1ScreenAction.NewContent;
|
|
794
|
+
return pageIndex === 0 ? G1AiStatus.Displaying | G1ScreenAction.NewContent : G1AiStatus.Displaying;
|
|
795
|
+
}
|
|
796
|
+
function positiveIntegerOrDefault(value, fallback) {
|
|
797
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
798
|
+
}
|
|
799
|
+
function rsvpDelayMs(wpm, wordsPerGroup) {
|
|
800
|
+
if (!Number.isFinite(wpm) || Number(wpm) <= 0) return 0;
|
|
801
|
+
return Math.max(0, Math.round(6e4 / Number(wpm) * wordsPerGroup));
|
|
802
|
+
}
|
|
803
|
+
export {
|
|
804
|
+
FACEWEAR_AUTO_INIT_SETTING,
|
|
805
|
+
FACEWEAR_INIT_MODE_SETTING,
|
|
806
|
+
FACEWEAR_SCAN_TIMEOUT_SETTING,
|
|
807
|
+
FACEWEAR_SMARTGLASSES_TRANSPORT_SETTING,
|
|
808
|
+
SMARTGLASSES_AUDIO_EVENT,
|
|
809
|
+
SMARTGLASSES_AUTO_INIT_SETTING,
|
|
810
|
+
SMARTGLASSES_EVENT,
|
|
811
|
+
SMARTGLASSES_INIT_MODE_SETTING,
|
|
812
|
+
SMARTGLASSES_SCAN_TIMEOUT_SETTING,
|
|
813
|
+
SMARTGLASSES_SERVICE_NAME,
|
|
814
|
+
SMARTGLASSES_TRANSCRIPT_EVENT,
|
|
815
|
+
SMARTGLASSES_TRANSPORT_SETTING,
|
|
816
|
+
SmartglassesService,
|
|
817
|
+
getSmartglassesService,
|
|
818
|
+
setSmartglassesAudioDecoderForRuntime,
|
|
819
|
+
setSmartglassesTransportForRuntime
|
|
820
|
+
};
|
|
821
|
+
//# sourceMappingURL=smartglasses-service.js.map
|