@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,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeMicCommand,
|
|
3
|
+
parseG1Notification
|
|
4
|
+
} from "../protocol/smartglasses.js";
|
|
5
|
+
class MockSmartglassesTransport {
|
|
6
|
+
name = "mock-smartglasses";
|
|
7
|
+
writes = [];
|
|
8
|
+
wifiRequests = [];
|
|
9
|
+
wifiResult = {
|
|
10
|
+
available: true,
|
|
11
|
+
status: "mock-wifi-ready",
|
|
12
|
+
networks: ["MockNet"]
|
|
13
|
+
};
|
|
14
|
+
connected = false;
|
|
15
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
16
|
+
audioCallbacks = /* @__PURE__ */ new Set();
|
|
17
|
+
transcriptCallbacks = /* @__PURE__ */ new Set();
|
|
18
|
+
wifiCallbacks = /* @__PURE__ */ new Set();
|
|
19
|
+
async connect() {
|
|
20
|
+
this.connected = true;
|
|
21
|
+
}
|
|
22
|
+
async disconnect() {
|
|
23
|
+
this.connected = false;
|
|
24
|
+
}
|
|
25
|
+
isConnected() {
|
|
26
|
+
return this.connected;
|
|
27
|
+
}
|
|
28
|
+
getConnectedLenses() {
|
|
29
|
+
if (!this.connected) return {};
|
|
30
|
+
return {
|
|
31
|
+
left: {
|
|
32
|
+
connected: true,
|
|
33
|
+
name: "Mock Even G1 Left",
|
|
34
|
+
address: "mock-left"
|
|
35
|
+
},
|
|
36
|
+
right: {
|
|
37
|
+
connected: true,
|
|
38
|
+
name: "Mock Even G1 Right",
|
|
39
|
+
address: "mock-right"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async write(side, data) {
|
|
44
|
+
this.writes.push({ side, data: new Uint8Array(data) });
|
|
45
|
+
}
|
|
46
|
+
async writeBoth(data) {
|
|
47
|
+
await this.write("left", data);
|
|
48
|
+
await this.write("right", data);
|
|
49
|
+
}
|
|
50
|
+
async openMicrophone(enabled) {
|
|
51
|
+
await this.write("right", encodeMicCommand(enabled));
|
|
52
|
+
this.emitRaw("right", encodeMicCommand(enabled));
|
|
53
|
+
}
|
|
54
|
+
onEvent(callback) {
|
|
55
|
+
this.eventCallbacks.add(callback);
|
|
56
|
+
return () => this.eventCallbacks.delete(callback);
|
|
57
|
+
}
|
|
58
|
+
onAudio(callback) {
|
|
59
|
+
this.audioCallbacks.add(callback);
|
|
60
|
+
return () => this.audioCallbacks.delete(callback);
|
|
61
|
+
}
|
|
62
|
+
onTranscript(callback) {
|
|
63
|
+
this.transcriptCallbacks.add(callback);
|
|
64
|
+
return () => this.transcriptCallbacks.delete(callback);
|
|
65
|
+
}
|
|
66
|
+
onWifiStatus(callback) {
|
|
67
|
+
this.wifiCallbacks.add(callback);
|
|
68
|
+
return () => this.wifiCallbacks.delete(callback);
|
|
69
|
+
}
|
|
70
|
+
emitRaw(side, data) {
|
|
71
|
+
const event = parseG1Notification(side, data);
|
|
72
|
+
this.emitEvent(event);
|
|
73
|
+
}
|
|
74
|
+
emitEvent(event) {
|
|
75
|
+
for (const callback of this.eventCallbacks) callback(event);
|
|
76
|
+
const audioData = event.audioPcm ?? event.audioData;
|
|
77
|
+
if (audioData) {
|
|
78
|
+
for (const callback of this.audioCallbacks)
|
|
79
|
+
callback(
|
|
80
|
+
audioData,
|
|
81
|
+
16e3,
|
|
82
|
+
event.side,
|
|
83
|
+
event.audioEncoding,
|
|
84
|
+
event.sequence
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
emitTranscript(text, isFinal = true, metadata) {
|
|
89
|
+
for (const callback of this.transcriptCallbacks)
|
|
90
|
+
callback(text, isFinal, metadata);
|
|
91
|
+
}
|
|
92
|
+
emitWifiStatus(status) {
|
|
93
|
+
for (const callback of this.wifiCallbacks) callback(status);
|
|
94
|
+
}
|
|
95
|
+
async scanWifi() {
|
|
96
|
+
this.wifiRequests.push({ op: "scan" });
|
|
97
|
+
return this.wifiResult;
|
|
98
|
+
}
|
|
99
|
+
async getWifiStatus() {
|
|
100
|
+
this.wifiRequests.push({ op: "status" });
|
|
101
|
+
return this.wifiResult;
|
|
102
|
+
}
|
|
103
|
+
async configureWifi(ssid, password) {
|
|
104
|
+
this.wifiRequests.push({ op: "configure", ssid, password });
|
|
105
|
+
return {
|
|
106
|
+
...this.wifiResult,
|
|
107
|
+
status: `mock credentials sent for ${ssid}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async requestWifiSetup(reason) {
|
|
111
|
+
this.wifiRequests.push({ op: "setup", reason });
|
|
112
|
+
return {
|
|
113
|
+
...this.wifiResult,
|
|
114
|
+
status: "mock Wi-Fi setup requested"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
supportsWifi() {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
MockSmartglassesTransport
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/transport/mock.ts"],"sourcesContent":["import {\n encodeMicCommand,\n type G1Event,\n type GlassSide,\n parseG1Notification,\n type SmartglassesAudioEncoding,\n} from \"../protocol/smartglasses.js\";\nimport type {\n SmartglassesConnectedLenses,\n SmartglassesTransport,\n SmartglassesWifiResult,\n} from \"./types.js\";\n\nexport class MockSmartglassesTransport implements SmartglassesTransport {\n readonly name = \"mock-smartglasses\";\n readonly writes: Array<{ side: GlassSide; data: Uint8Array }> = [];\n readonly wifiRequests: Array<{\n op: \"scan\" | \"status\" | \"configure\" | \"setup\";\n ssid?: string;\n password?: string;\n reason?: string;\n }> = [];\n wifiResult: SmartglassesWifiResult = {\n available: true,\n status: \"mock-wifi-ready\",\n networks: [\"MockNet\"],\n };\n private connected = false;\n private eventCallbacks = new Set<(event: G1Event) => void>();\n private audioCallbacks = new Set<\n (\n audioData: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n encoding?: SmartglassesAudioEncoding,\n sequence?: number,\n ) => void\n >();\n private transcriptCallbacks = new Set<\n (text: string, isFinal: boolean, metadata?: Record<string, unknown>) => void\n >();\n private wifiCallbacks = new Set<(status: SmartglassesWifiResult) => void>();\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n getConnectedLenses(): SmartglassesConnectedLenses {\n if (!this.connected) return {};\n return {\n left: {\n connected: true,\n name: \"Mock Even G1 Left\",\n address: \"mock-left\",\n },\n right: {\n connected: true,\n name: \"Mock Even G1 Right\",\n address: \"mock-right\",\n },\n };\n }\n\n async write(side: GlassSide, data: Uint8Array): Promise<void> {\n this.writes.push({ side, data: new Uint8Array(data) });\n }\n\n async writeBoth(data: Uint8Array): Promise<void> {\n await this.write(\"left\", data);\n await this.write(\"right\", data);\n }\n\n async openMicrophone(enabled: boolean): Promise<void> {\n await this.write(\"right\", encodeMicCommand(enabled));\n this.emitRaw(\"right\", encodeMicCommand(enabled));\n }\n\n onEvent(callback: (event: G1Event) => void): () => void {\n this.eventCallbacks.add(callback);\n return () => this.eventCallbacks.delete(callback);\n }\n\n onAudio(\n callback: (\n audioData: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n encoding?: SmartglassesAudioEncoding,\n sequence?: number,\n ) => void,\n ): () => void {\n this.audioCallbacks.add(callback);\n return () => this.audioCallbacks.delete(callback);\n }\n\n onTranscript(\n callback: (\n text: string,\n isFinal: boolean,\n metadata?: Record<string, unknown>,\n ) => void,\n ): () => void {\n this.transcriptCallbacks.add(callback);\n return () => this.transcriptCallbacks.delete(callback);\n }\n\n onWifiStatus(callback: (status: SmartglassesWifiResult) => void): () => void {\n this.wifiCallbacks.add(callback);\n return () => this.wifiCallbacks.delete(callback);\n }\n\n emitRaw(side: GlassSide, data: Uint8Array): void {\n const event = parseG1Notification(side, data);\n this.emitEvent(event);\n }\n\n emitEvent(event: G1Event): void {\n for (const callback of this.eventCallbacks) callback(event);\n const audioData = event.audioPcm ?? event.audioData;\n if (audioData) {\n for (const callback of this.audioCallbacks)\n callback(\n audioData,\n 16_000,\n event.side,\n event.audioEncoding,\n event.sequence,\n );\n }\n }\n\n emitTranscript(\n text: string,\n isFinal = true,\n metadata?: Record<string, unknown>,\n ): void {\n for (const callback of this.transcriptCallbacks)\n callback(text, isFinal, metadata);\n }\n\n emitWifiStatus(status: SmartglassesWifiResult): void {\n for (const callback of this.wifiCallbacks) callback(status);\n }\n\n async scanWifi(): Promise<SmartglassesWifiResult> {\n this.wifiRequests.push({ op: \"scan\" });\n return this.wifiResult;\n }\n\n async getWifiStatus(): Promise<SmartglassesWifiResult> {\n this.wifiRequests.push({ op: \"status\" });\n return this.wifiResult;\n }\n\n async configureWifi(\n ssid: string,\n password: string,\n ): Promise<SmartglassesWifiResult> {\n this.wifiRequests.push({ op: \"configure\", ssid, password });\n return {\n ...this.wifiResult,\n status: `mock credentials sent for ${ssid}`,\n };\n }\n\n async requestWifiSetup(reason?: string): Promise<SmartglassesWifiResult> {\n this.wifiRequests.push({ op: \"setup\", reason });\n return {\n ...this.wifiResult,\n status: \"mock Wi-Fi setup requested\",\n };\n }\n\n supportsWifi(): boolean {\n return true;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { EventEmitter } from "node:events";
|
|
2
|
+
import { type G1Event, type GlassSide, type SmartglassesAudioEncoding } from "../protocol/smartglasses.js";
|
|
3
|
+
import type { SmartglassesConnectedLenses, SmartglassesTransport } from "./types.js";
|
|
4
|
+
type NobleState = "unknown" | "resetting" | "unsupported" | "unauthorized" | "poweredOff" | "poweredOn";
|
|
5
|
+
export type NobleCharacteristicLike = EventEmitter & {
|
|
6
|
+
uuid: string;
|
|
7
|
+
writeAsync?: (data: Buffer, withoutResponse?: boolean) => Promise<void>;
|
|
8
|
+
write?: (data: Buffer, withoutResponse: boolean, callback: (error?: Error) => void) => void;
|
|
9
|
+
subscribeAsync?: () => Promise<void>;
|
|
10
|
+
subscribe?: (callback: (error?: Error) => void) => void;
|
|
11
|
+
unsubscribeAsync?: () => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export type NoblePeripheralLike = EventEmitter & {
|
|
14
|
+
id?: string;
|
|
15
|
+
address?: string;
|
|
16
|
+
advertisement?: {
|
|
17
|
+
localName?: string;
|
|
18
|
+
};
|
|
19
|
+
connectAsync?: () => Promise<void>;
|
|
20
|
+
connect?: (callback: (error?: Error) => void) => void;
|
|
21
|
+
disconnectAsync?: () => Promise<void>;
|
|
22
|
+
disconnect?: (callback?: (error?: Error) => void) => void;
|
|
23
|
+
discoverSomeServicesAndCharacteristicsAsync?: (serviceUuids: string[], characteristicUuids: string[]) => Promise<{
|
|
24
|
+
characteristics: NobleCharacteristicLike[];
|
|
25
|
+
}>;
|
|
26
|
+
discoverSomeServicesAndCharacteristics?: (serviceUuids: string[], characteristicUuids: string[], callback: (error: Error | null, services: unknown[], characteristics: NobleCharacteristicLike[]) => void) => void;
|
|
27
|
+
};
|
|
28
|
+
export type NobleAdapterLike = EventEmitter & {
|
|
29
|
+
state?: NobleState;
|
|
30
|
+
startScanningAsync?: (serviceUuids: string[], allowDuplicates?: boolean) => Promise<void>;
|
|
31
|
+
startScanning?: (serviceUuids: string[], allowDuplicates: boolean, callback?: (error?: Error) => void) => void;
|
|
32
|
+
stopScanningAsync?: () => Promise<void>;
|
|
33
|
+
stopScanning?: () => void;
|
|
34
|
+
};
|
|
35
|
+
export interface NobleG1TransportOptions {
|
|
36
|
+
scanTimeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
export declare class NobleG1Transport implements SmartglassesTransport {
|
|
39
|
+
private readonly noble;
|
|
40
|
+
private readonly options;
|
|
41
|
+
readonly name = "noble-g1";
|
|
42
|
+
private readonly sides;
|
|
43
|
+
private readonly eventCallbacks;
|
|
44
|
+
private readonly audioCallbacks;
|
|
45
|
+
constructor(noble: NobleAdapterLike, options?: NobleG1TransportOptions);
|
|
46
|
+
connect(): Promise<void>;
|
|
47
|
+
disconnect(): Promise<void>;
|
|
48
|
+
isConnected(): boolean;
|
|
49
|
+
getConnectedLenses(): SmartglassesConnectedLenses;
|
|
50
|
+
write(side: GlassSide, data: Uint8Array): Promise<void>;
|
|
51
|
+
writeBoth(data: Uint8Array): Promise<void>;
|
|
52
|
+
openMicrophone(enabled: boolean): Promise<void>;
|
|
53
|
+
onEvent(callback: (event: G1Event) => void): () => void;
|
|
54
|
+
onAudio(callback: (audioData: Uint8Array, sampleRate: number, side: GlassSide, encoding?: SmartglassesAudioEncoding, sequence?: number) => void): () => void;
|
|
55
|
+
private waitForPoweredOn;
|
|
56
|
+
private scanForPair;
|
|
57
|
+
private connectPeripheral;
|
|
58
|
+
private emitParsed;
|
|
59
|
+
}
|
|
60
|
+
export declare function getNobleG1Transport(options?: NobleG1TransportOptions): Promise<SmartglassesTransport | null>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=noble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noble.d.ts","sourceRoot":"","sources":["../../src/transport/noble.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVEN_G1_UART,
|
|
3
|
+
encodeMicCommand,
|
|
4
|
+
parseG1Notification
|
|
5
|
+
} from "../protocol/smartglasses.js";
|
|
6
|
+
const SERVICE_UUID = normalizeUuid(EVEN_G1_UART.service);
|
|
7
|
+
const TX_UUID = normalizeUuid(EVEN_G1_UART.tx);
|
|
8
|
+
const RX_UUID = normalizeUuid(EVEN_G1_UART.rx);
|
|
9
|
+
class NobleG1Transport {
|
|
10
|
+
constructor(noble, options = {}) {
|
|
11
|
+
this.noble = noble;
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
noble;
|
|
15
|
+
options;
|
|
16
|
+
name = "noble-g1";
|
|
17
|
+
sides = /* @__PURE__ */ new Map();
|
|
18
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
19
|
+
audioCallbacks = /* @__PURE__ */ new Set();
|
|
20
|
+
async connect() {
|
|
21
|
+
await this.waitForPoweredOn();
|
|
22
|
+
const found = await this.scanForPair();
|
|
23
|
+
try {
|
|
24
|
+
await Promise.all([
|
|
25
|
+
this.connectPeripheral("left", found.left),
|
|
26
|
+
this.connectPeripheral("right", found.right)
|
|
27
|
+
]);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
await this.disconnect();
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async disconnect() {
|
|
34
|
+
for (const [side, connection] of this.sides) {
|
|
35
|
+
connection.rx.removeListener("data", connection.dataHandler);
|
|
36
|
+
await callOptionalAsync(
|
|
37
|
+
connection.rx.unsubscribeAsync?.bind(connection.rx)
|
|
38
|
+
);
|
|
39
|
+
await disconnectPeripheral(connection.peripheral);
|
|
40
|
+
this.sides.delete(side);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
isConnected() {
|
|
44
|
+
return this.sides.size === 2;
|
|
45
|
+
}
|
|
46
|
+
getConnectedLenses() {
|
|
47
|
+
const lenses = {};
|
|
48
|
+
for (const [side, connection] of this.sides) {
|
|
49
|
+
lenses[side] = {
|
|
50
|
+
connected: true,
|
|
51
|
+
name: connection.peripheral.advertisement?.localName,
|
|
52
|
+
address: connection.peripheral.address ?? connection.peripheral.id
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return lenses;
|
|
56
|
+
}
|
|
57
|
+
async write(side, data) {
|
|
58
|
+
const connection = this.sides.get(side);
|
|
59
|
+
if (!connection) throw new Error(`G1 ${side} lens is not connected`);
|
|
60
|
+
await writeCharacteristic(connection.tx, Buffer.from(data));
|
|
61
|
+
}
|
|
62
|
+
async writeBoth(data) {
|
|
63
|
+
await this.write("left", data);
|
|
64
|
+
await this.write("right", data);
|
|
65
|
+
}
|
|
66
|
+
async openMicrophone(enabled) {
|
|
67
|
+
await this.write("right", encodeMicCommand(enabled));
|
|
68
|
+
}
|
|
69
|
+
onEvent(callback) {
|
|
70
|
+
this.eventCallbacks.add(callback);
|
|
71
|
+
return () => this.eventCallbacks.delete(callback);
|
|
72
|
+
}
|
|
73
|
+
onAudio(callback) {
|
|
74
|
+
this.audioCallbacks.add(callback);
|
|
75
|
+
return () => this.audioCallbacks.delete(callback);
|
|
76
|
+
}
|
|
77
|
+
async waitForPoweredOn() {
|
|
78
|
+
if (this.noble.state === "poweredOn" || this.noble.state === void 0)
|
|
79
|
+
return;
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
this.noble.removeListener("stateChange", onState);
|
|
83
|
+
reject(
|
|
84
|
+
new Error(
|
|
85
|
+
`Timed out waiting for Bluetooth adapter; state=${this.noble.state}`
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}, this.options.scanTimeoutMs ?? 1e4);
|
|
89
|
+
const onState = (state) => {
|
|
90
|
+
if (state !== "poweredOn") return;
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
this.noble.removeListener("stateChange", onState);
|
|
93
|
+
resolve();
|
|
94
|
+
};
|
|
95
|
+
this.noble.on("stateChange", onState);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async scanForPair() {
|
|
99
|
+
const found = {};
|
|
100
|
+
const timeoutMs = this.options.scanTimeoutMs ?? 15e3;
|
|
101
|
+
await new Promise((resolve, reject) => {
|
|
102
|
+
const timeout = setTimeout(() => {
|
|
103
|
+
cleanup();
|
|
104
|
+
reject(new Error("Timed out scanning for Even G1 left/right lenses"));
|
|
105
|
+
}, timeoutMs);
|
|
106
|
+
const cleanup = () => {
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
this.noble.removeListener("discover", onDiscover);
|
|
109
|
+
void stopScanning(this.noble);
|
|
110
|
+
};
|
|
111
|
+
const onDiscover = (peripheral) => {
|
|
112
|
+
const side = inferSide(peripheral);
|
|
113
|
+
if (!side) return;
|
|
114
|
+
found[side] = peripheral;
|
|
115
|
+
if (found.left && found.right) {
|
|
116
|
+
cleanup();
|
|
117
|
+
resolve();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
this.noble.on("discover", onDiscover);
|
|
121
|
+
void startScanning(this.noble).catch((error) => {
|
|
122
|
+
cleanup();
|
|
123
|
+
reject(error);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
if (!found.left || !found.right)
|
|
127
|
+
throw new Error("Missing G1 left or right lens");
|
|
128
|
+
return { left: found.left, right: found.right };
|
|
129
|
+
}
|
|
130
|
+
async connectPeripheral(side, peripheral) {
|
|
131
|
+
await connectPeripheral(peripheral);
|
|
132
|
+
const characteristics = await discoverCharacteristics(peripheral);
|
|
133
|
+
const tx = characteristics.find(
|
|
134
|
+
(characteristic) => normalizeUuid(characteristic.uuid) === TX_UUID
|
|
135
|
+
);
|
|
136
|
+
const rx = characteristics.find(
|
|
137
|
+
(characteristic) => normalizeUuid(characteristic.uuid) === RX_UUID
|
|
138
|
+
);
|
|
139
|
+
if (!tx || !rx)
|
|
140
|
+
throw new Error(
|
|
141
|
+
`G1 ${side} lens did not expose UART TX/RX characteristics`
|
|
142
|
+
);
|
|
143
|
+
const dataHandler = (data) => {
|
|
144
|
+
const event = parseG1Notification(side, new Uint8Array(data));
|
|
145
|
+
this.emitParsed(event);
|
|
146
|
+
};
|
|
147
|
+
rx.on("data", dataHandler);
|
|
148
|
+
await subscribeCharacteristic(rx);
|
|
149
|
+
this.sides.set(side, { peripheral, tx, rx, dataHandler });
|
|
150
|
+
}
|
|
151
|
+
emitParsed(event) {
|
|
152
|
+
for (const callback of this.eventCallbacks) callback(event);
|
|
153
|
+
const audioData = event.audioPcm ?? event.audioData;
|
|
154
|
+
if (audioData) {
|
|
155
|
+
for (const callback of this.audioCallbacks)
|
|
156
|
+
callback(
|
|
157
|
+
audioData,
|
|
158
|
+
16e3,
|
|
159
|
+
event.side,
|
|
160
|
+
event.audioEncoding,
|
|
161
|
+
event.sequence
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function getNobleG1Transport(options = {}) {
|
|
167
|
+
if (typeof process === "undefined" || typeof window !== "undefined")
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
const dynamicImport = new Function(
|
|
171
|
+
"specifier",
|
|
172
|
+
"return import(specifier)"
|
|
173
|
+
);
|
|
174
|
+
const mod = await dynamicImport("@abandonware/noble");
|
|
175
|
+
const noble = mod.default ?? mod;
|
|
176
|
+
return noble ? new NobleG1Transport(noble, options) : null;
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function inferSide(peripheral) {
|
|
182
|
+
const name = peripheral.advertisement?.localName ?? "";
|
|
183
|
+
if (/_L_|left/i.test(name)) return "left";
|
|
184
|
+
if (/_R_|right/i.test(name)) return "right";
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function normalizeUuid(uuid) {
|
|
188
|
+
return uuid.replace(/-/g, "").toLowerCase();
|
|
189
|
+
}
|
|
190
|
+
async function startScanning(noble) {
|
|
191
|
+
if (noble.startScanningAsync) return noble.startScanningAsync([], false);
|
|
192
|
+
await new Promise((resolve, reject) => {
|
|
193
|
+
noble.startScanning?.(
|
|
194
|
+
[],
|
|
195
|
+
false,
|
|
196
|
+
(error) => error ? reject(error) : resolve()
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async function stopScanning(noble) {
|
|
201
|
+
if (noble.stopScanningAsync) return noble.stopScanningAsync();
|
|
202
|
+
noble.stopScanning?.();
|
|
203
|
+
}
|
|
204
|
+
async function connectPeripheral(peripheral) {
|
|
205
|
+
if (peripheral.connectAsync) return peripheral.connectAsync();
|
|
206
|
+
await new Promise((resolve, reject) => {
|
|
207
|
+
peripheral.connect?.((error) => error ? reject(error) : resolve());
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async function disconnectPeripheral(peripheral) {
|
|
211
|
+
if (peripheral.disconnectAsync) return peripheral.disconnectAsync();
|
|
212
|
+
await new Promise((resolve, reject) => {
|
|
213
|
+
peripheral.disconnect?.((error) => error ? reject(error) : resolve());
|
|
214
|
+
resolve();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async function discoverCharacteristics(peripheral) {
|
|
218
|
+
if (peripheral.discoverSomeServicesAndCharacteristicsAsync) {
|
|
219
|
+
const result = await peripheral.discoverSomeServicesAndCharacteristicsAsync(
|
|
220
|
+
[SERVICE_UUID],
|
|
221
|
+
[TX_UUID, RX_UUID]
|
|
222
|
+
);
|
|
223
|
+
return result.characteristics;
|
|
224
|
+
}
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
peripheral.discoverSomeServicesAndCharacteristics?.(
|
|
227
|
+
[SERVICE_UUID],
|
|
228
|
+
[TX_UUID, RX_UUID],
|
|
229
|
+
(error, _services, characteristics) => error ? reject(error) : resolve(characteristics)
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async function writeCharacteristic(characteristic, data) {
|
|
234
|
+
if (characteristic.writeAsync) return characteristic.writeAsync(data, false);
|
|
235
|
+
await new Promise((resolve, reject) => {
|
|
236
|
+
characteristic.write?.(
|
|
237
|
+
data,
|
|
238
|
+
false,
|
|
239
|
+
(error) => error ? reject(error) : resolve()
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function subscribeCharacteristic(characteristic) {
|
|
244
|
+
if (characteristic.subscribeAsync) return characteristic.subscribeAsync();
|
|
245
|
+
await new Promise((resolve, reject) => {
|
|
246
|
+
characteristic.subscribe?.((error) => error ? reject(error) : resolve());
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
async function callOptionalAsync(fn) {
|
|
250
|
+
if (fn) await fn();
|
|
251
|
+
}
|
|
252
|
+
export {
|
|
253
|
+
NobleG1Transport,
|
|
254
|
+
getNobleG1Transport
|
|
255
|
+
};
|
|
256
|
+
//# sourceMappingURL=noble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/transport/noble.ts"],"sourcesContent":["import type { EventEmitter } from \"node:events\";\nimport {\n EVEN_G1_UART,\n encodeMicCommand,\n type G1Event,\n type GlassSide,\n parseG1Notification,\n type SmartglassesAudioEncoding,\n} from \"../protocol/smartglasses.js\";\nimport type {\n SmartglassesConnectedLenses,\n SmartglassesTransport,\n} from \"./types.js\";\n\ntype NobleState =\n | \"unknown\"\n | \"resetting\"\n | \"unsupported\"\n | \"unauthorized\"\n | \"poweredOff\"\n | \"poweredOn\";\n\nexport type NobleCharacteristicLike = EventEmitter & {\n uuid: string;\n writeAsync?: (data: Buffer, withoutResponse?: boolean) => Promise<void>;\n write?: (\n data: Buffer,\n withoutResponse: boolean,\n callback: (error?: Error) => void,\n ) => void;\n subscribeAsync?: () => Promise<void>;\n subscribe?: (callback: (error?: Error) => void) => void;\n unsubscribeAsync?: () => Promise<void>;\n};\n\nexport type NoblePeripheralLike = EventEmitter & {\n id?: string;\n address?: string;\n advertisement?: {\n localName?: string;\n };\n connectAsync?: () => Promise<void>;\n connect?: (callback: (error?: Error) => void) => void;\n disconnectAsync?: () => Promise<void>;\n disconnect?: (callback?: (error?: Error) => void) => void;\n discoverSomeServicesAndCharacteristicsAsync?: (\n serviceUuids: string[],\n characteristicUuids: string[],\n ) => Promise<{\n characteristics: NobleCharacteristicLike[];\n }>;\n discoverSomeServicesAndCharacteristics?: (\n serviceUuids: string[],\n characteristicUuids: string[],\n callback: (\n error: Error | null,\n services: unknown[],\n characteristics: NobleCharacteristicLike[],\n ) => void,\n ) => void;\n};\n\nexport type NobleAdapterLike = EventEmitter & {\n state?: NobleState;\n startScanningAsync?: (\n serviceUuids: string[],\n allowDuplicates?: boolean,\n ) => Promise<void>;\n startScanning?: (\n serviceUuids: string[],\n allowDuplicates: boolean,\n callback?: (error?: Error) => void,\n ) => void;\n stopScanningAsync?: () => Promise<void>;\n stopScanning?: () => void;\n};\n\ntype SideConnection = {\n peripheral: NoblePeripheralLike;\n tx: NobleCharacteristicLike;\n rx: NobleCharacteristicLike;\n dataHandler: (data: Buffer | Uint8Array) => void;\n};\n\nconst SERVICE_UUID = normalizeUuid(EVEN_G1_UART.service);\nconst TX_UUID = normalizeUuid(EVEN_G1_UART.tx);\nconst RX_UUID = normalizeUuid(EVEN_G1_UART.rx);\n\nexport interface NobleG1TransportOptions {\n scanTimeoutMs?: number;\n}\n\nexport class NobleG1Transport implements SmartglassesTransport {\n readonly name = \"noble-g1\";\n private readonly sides = new Map<GlassSide, SideConnection>();\n private readonly eventCallbacks = new Set<(event: G1Event) => void>();\n private readonly audioCallbacks = new Set<\n (\n audioData: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n encoding?: SmartglassesAudioEncoding,\n sequence?: number,\n ) => void\n >();\n\n constructor(\n private readonly noble: NobleAdapterLike,\n private readonly options: NobleG1TransportOptions = {},\n ) {}\n\n async connect(): Promise<void> {\n await this.waitForPoweredOn();\n const found = await this.scanForPair();\n try {\n await Promise.all([\n this.connectPeripheral(\"left\", found.left),\n this.connectPeripheral(\"right\", found.right),\n ]);\n } catch (error) {\n await this.disconnect();\n throw error;\n }\n }\n\n async disconnect(): Promise<void> {\n for (const [side, connection] of this.sides) {\n connection.rx.removeListener(\"data\", connection.dataHandler);\n await callOptionalAsync(\n connection.rx.unsubscribeAsync?.bind(connection.rx),\n );\n await disconnectPeripheral(connection.peripheral);\n this.sides.delete(side);\n }\n }\n\n isConnected(): boolean {\n return this.sides.size === 2;\n }\n\n getConnectedLenses(): SmartglassesConnectedLenses {\n const lenses: SmartglassesConnectedLenses = {};\n for (const [side, connection] of this.sides) {\n lenses[side] = {\n connected: true,\n name: connection.peripheral.advertisement?.localName,\n address: connection.peripheral.address ?? connection.peripheral.id,\n };\n }\n return lenses;\n }\n\n async write(side: GlassSide, data: Uint8Array): Promise<void> {\n const connection = this.sides.get(side);\n if (!connection) throw new Error(`G1 ${side} lens is not connected`);\n await writeCharacteristic(connection.tx, Buffer.from(data));\n }\n\n async writeBoth(data: Uint8Array): Promise<void> {\n await this.write(\"left\", data);\n await this.write(\"right\", data);\n }\n\n async openMicrophone(enabled: boolean): Promise<void> {\n await this.write(\"right\", encodeMicCommand(enabled));\n }\n\n onEvent(callback: (event: G1Event) => void): () => void {\n this.eventCallbacks.add(callback);\n return () => this.eventCallbacks.delete(callback);\n }\n\n onAudio(\n callback: (\n audioData: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n encoding?: SmartglassesAudioEncoding,\n sequence?: number,\n ) => void,\n ): () => void {\n this.audioCallbacks.add(callback);\n return () => this.audioCallbacks.delete(callback);\n }\n\n private async waitForPoweredOn(): Promise<void> {\n if (this.noble.state === \"poweredOn\" || this.noble.state === undefined)\n return;\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.noble.removeListener(\"stateChange\", onState);\n reject(\n new Error(\n `Timed out waiting for Bluetooth adapter; state=${this.noble.state}`,\n ),\n );\n }, this.options.scanTimeoutMs ?? 10_000);\n const onState = (state: NobleState) => {\n if (state !== \"poweredOn\") return;\n clearTimeout(timeout);\n this.noble.removeListener(\"stateChange\", onState);\n resolve();\n };\n this.noble.on(\"stateChange\", onState);\n });\n }\n\n private async scanForPair(): Promise<{\n left: NoblePeripheralLike;\n right: NoblePeripheralLike;\n }> {\n const found: Partial<Record<GlassSide, NoblePeripheralLike>> = {};\n const timeoutMs = this.options.scanTimeoutMs ?? 15_000;\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error(\"Timed out scanning for Even G1 left/right lenses\"));\n }, timeoutMs);\n const cleanup = () => {\n clearTimeout(timeout);\n this.noble.removeListener(\"discover\", onDiscover);\n void stopScanning(this.noble);\n };\n const onDiscover = (peripheral: NoblePeripheralLike) => {\n const side = inferSide(peripheral);\n if (!side) return;\n found[side] = peripheral;\n if (found.left && found.right) {\n cleanup();\n resolve();\n }\n };\n this.noble.on(\"discover\", onDiscover);\n void startScanning(this.noble).catch((error) => {\n cleanup();\n reject(error);\n });\n });\n if (!found.left || !found.right)\n throw new Error(\"Missing G1 left or right lens\");\n return { left: found.left, right: found.right };\n }\n\n private async connectPeripheral(\n side: GlassSide,\n peripheral: NoblePeripheralLike,\n ): Promise<void> {\n await connectPeripheral(peripheral);\n const characteristics = await discoverCharacteristics(peripheral);\n const tx = characteristics.find(\n (characteristic) => normalizeUuid(characteristic.uuid) === TX_UUID,\n );\n const rx = characteristics.find(\n (characteristic) => normalizeUuid(characteristic.uuid) === RX_UUID,\n );\n if (!tx || !rx)\n throw new Error(\n `G1 ${side} lens did not expose UART TX/RX characteristics`,\n );\n const dataHandler = (data: Buffer | Uint8Array) => {\n const event = parseG1Notification(side, new Uint8Array(data));\n this.emitParsed(event);\n };\n rx.on(\"data\", dataHandler);\n await subscribeCharacteristic(rx);\n this.sides.set(side, { peripheral, tx, rx, dataHandler });\n }\n\n private emitParsed(event: G1Event): void {\n for (const callback of this.eventCallbacks) callback(event);\n const audioData = event.audioPcm ?? event.audioData;\n if (audioData) {\n for (const callback of this.audioCallbacks)\n callback(\n audioData,\n 16_000,\n event.side,\n event.audioEncoding,\n event.sequence,\n );\n }\n }\n}\n\nexport async function getNobleG1Transport(\n options: NobleG1TransportOptions = {},\n): Promise<SmartglassesTransport | null> {\n if (typeof process === \"undefined\" || typeof window !== \"undefined\")\n return null;\n try {\n const dynamicImport = new Function(\n \"specifier\",\n \"return import(specifier)\",\n ) as (specifier: string) => Promise<unknown>;\n const mod = (await dynamicImport(\"@abandonware/noble\")) as {\n default?: NobleAdapterLike;\n } & NobleAdapterLike;\n const noble = mod.default ?? mod;\n return noble ? new NobleG1Transport(noble, options) : null;\n } catch {\n return null;\n }\n}\n\nfunction inferSide(peripheral: NoblePeripheralLike): GlassSide | null {\n const name = peripheral.advertisement?.localName ?? \"\";\n if (/_L_|left/i.test(name)) return \"left\";\n if (/_R_|right/i.test(name)) return \"right\";\n return null;\n}\n\nfunction normalizeUuid(uuid: string): string {\n return uuid.replace(/-/g, \"\").toLowerCase();\n}\n\nasync function startScanning(noble: NobleAdapterLike): Promise<void> {\n if (noble.startScanningAsync) return noble.startScanningAsync([], false);\n await new Promise<void>((resolve, reject) => {\n noble.startScanning?.([], false, (error) =>\n error ? reject(error) : resolve(),\n );\n });\n}\n\nasync function stopScanning(noble: NobleAdapterLike): Promise<void> {\n if (noble.stopScanningAsync) return noble.stopScanningAsync();\n noble.stopScanning?.();\n}\n\nasync function connectPeripheral(\n peripheral: NoblePeripheralLike,\n): Promise<void> {\n if (peripheral.connectAsync) return peripheral.connectAsync();\n await new Promise<void>((resolve, reject) => {\n peripheral.connect?.((error) => (error ? reject(error) : resolve()));\n });\n}\n\nasync function disconnectPeripheral(\n peripheral: NoblePeripheralLike,\n): Promise<void> {\n if (peripheral.disconnectAsync) return peripheral.disconnectAsync();\n await new Promise<void>((resolve, reject) => {\n peripheral.disconnect?.((error) => (error ? reject(error) : resolve()));\n resolve();\n });\n}\n\nasync function discoverCharacteristics(\n peripheral: NoblePeripheralLike,\n): Promise<NobleCharacteristicLike[]> {\n if (peripheral.discoverSomeServicesAndCharacteristicsAsync) {\n const result = await peripheral.discoverSomeServicesAndCharacteristicsAsync(\n [SERVICE_UUID],\n [TX_UUID, RX_UUID],\n );\n return result.characteristics;\n }\n return new Promise((resolve, reject) => {\n peripheral.discoverSomeServicesAndCharacteristics?.(\n [SERVICE_UUID],\n [TX_UUID, RX_UUID],\n (error, _services, characteristics) =>\n error ? reject(error) : resolve(characteristics),\n );\n });\n}\n\nasync function writeCharacteristic(\n characteristic: NobleCharacteristicLike,\n data: Buffer,\n): Promise<void> {\n if (characteristic.writeAsync) return characteristic.writeAsync(data, false);\n await new Promise<void>((resolve, reject) => {\n characteristic.write?.(data, false, (error) =>\n error ? reject(error) : resolve(),\n );\n });\n}\n\nasync function subscribeCharacteristic(\n characteristic: NobleCharacteristicLike,\n): Promise<void> {\n if (characteristic.subscribeAsync) return characteristic.subscribeAsync();\n await new Promise<void>((resolve, reject) => {\n characteristic.subscribe?.((error) => (error ? reject(error) : resolve()));\n });\n}\n\nasync function callOptionalAsync(\n fn: (() => Promise<void>) | undefined,\n): Promise<void> {\n if (fn) await fn();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { G1Event, GlassSide, SmartglassesAudioEncoding } from "../protocol/smartglasses.js";
|
|
2
|
+
export interface SmartglassesTransport {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
connect(): Promise<void>;
|
|
5
|
+
disconnect(): Promise<void>;
|
|
6
|
+
isConnected(): boolean;
|
|
7
|
+
write(side: GlassSide, data: Uint8Array): Promise<void>;
|
|
8
|
+
writeBoth(data: Uint8Array): Promise<void>;
|
|
9
|
+
openMicrophone(enabled: boolean): Promise<void>;
|
|
10
|
+
onEvent(callback: (event: G1Event) => void): () => void;
|
|
11
|
+
onAudio(callback: (audioData: Uint8Array, sampleRate: number, side: GlassSide, encoding?: SmartglassesAudioEncoding, sequence?: number) => void): () => void;
|
|
12
|
+
onTranscript?(callback: (text: string, isFinal: boolean, metadata?: Record<string, unknown>) => void): () => void;
|
|
13
|
+
onWifiStatus?(callback: (status: SmartglassesWifiResult) => void): () => void;
|
|
14
|
+
scanWifi?(): Promise<SmartglassesWifiResult>;
|
|
15
|
+
getWifiStatus?(): Promise<SmartglassesWifiResult>;
|
|
16
|
+
configureWifi?(ssid: string, password: string): Promise<SmartglassesWifiResult>;
|
|
17
|
+
requestWifiSetup?(reason?: string): Promise<SmartglassesWifiResult>;
|
|
18
|
+
supportsWifi?(): boolean;
|
|
19
|
+
getConnectedLenses?(): SmartglassesConnectedLenses;
|
|
20
|
+
}
|
|
21
|
+
export interface SmartglassesTransportFactory {
|
|
22
|
+
create(): SmartglassesTransport | null;
|
|
23
|
+
}
|
|
24
|
+
export interface SmartglassesWifiResult {
|
|
25
|
+
available: boolean;
|
|
26
|
+
status: string;
|
|
27
|
+
networks: string[];
|
|
28
|
+
raw?: unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface SmartglassesLensConnection {
|
|
31
|
+
connected: boolean;
|
|
32
|
+
name?: string;
|
|
33
|
+
address?: string;
|
|
34
|
+
}
|
|
35
|
+
export type SmartglassesConnectedLenses = Partial<Record<GlassSide, SmartglassesLensConnection>>;
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transport/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,yBAAyB,EAC1B,MAAM,6BAA6B,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,IAAI,OAAO,CAAC;IACvB,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACxD,OAAO,CACL,QAAQ,EAAE,CACR,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,EACf,QAAQ,CAAC,EAAE,yBAAyB,EACpC,QAAQ,CAAC,EAAE,MAAM,KACd,IAAI,GACR,MAAM,IAAI,CAAC;IACd,YAAY,CAAC,CACX,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,IAAI,GACR,MAAM,IAAI,CAAC;IACd,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC9E,QAAQ,CAAC,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC7C,aAAa,CAAC,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAClD,aAAa,CAAC,CACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACnC,gBAAgB,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACpE,YAAY,CAAC,IAAI,OAAO,CAAC;IACzB,kBAAkB,CAAC,IAAI,2BAA2B,CAAC;CACpD;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,IAAI,qBAAqB,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAC/C,MAAM,CAAC,SAAS,EAAE,0BAA0B,CAAC,CAC9C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type G1Event, type GlassSide, type SmartglassesAudioEncoding } from "../protocol/smartglasses.js";
|
|
2
|
+
import type { SmartglassesConnectedLenses, SmartglassesTransport } from "./types.js";
|
|
3
|
+
type BluetoothRemoteGATTCharacteristicLike = {
|
|
4
|
+
value?: DataView;
|
|
5
|
+
writeValueWithoutResponse?: (data: ArrayBuffer) => Promise<void>;
|
|
6
|
+
writeValueWithResponse?: (data: ArrayBuffer) => Promise<void>;
|
|
7
|
+
writeValue?: (data: ArrayBuffer) => Promise<void>;
|
|
8
|
+
startNotifications: () => Promise<BluetoothRemoteGATTCharacteristicLike>;
|
|
9
|
+
stopNotifications?: () => Promise<void>;
|
|
10
|
+
addEventListener: (type: string, listener: (event: Event) => void) => void;
|
|
11
|
+
removeEventListener?: (type: string, listener: (event: Event) => void) => void;
|
|
12
|
+
};
|
|
13
|
+
type BluetoothRemoteGATTServerLike = {
|
|
14
|
+
connected?: boolean;
|
|
15
|
+
getPrimaryService: (service: string) => Promise<{
|
|
16
|
+
getCharacteristic: (characteristic: string) => Promise<BluetoothRemoteGATTCharacteristicLike>;
|
|
17
|
+
}>;
|
|
18
|
+
disconnect?: () => void;
|
|
19
|
+
};
|
|
20
|
+
type BluetoothDeviceLike = {
|
|
21
|
+
name?: string;
|
|
22
|
+
id?: string;
|
|
23
|
+
gatt?: {
|
|
24
|
+
connect: () => Promise<BluetoothRemoteGATTServerLike>;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
type NavigatorBluetoothLike = {
|
|
28
|
+
requestDevice: (options: {
|
|
29
|
+
filters?: Array<{
|
|
30
|
+
namePrefix?: string;
|
|
31
|
+
services?: string[];
|
|
32
|
+
}>;
|
|
33
|
+
optionalServices?: string[];
|
|
34
|
+
}) => Promise<BluetoothDeviceLike>;
|
|
35
|
+
};
|
|
36
|
+
export declare class WebBluetoothG1Transport implements SmartglassesTransport {
|
|
37
|
+
private readonly bluetooth;
|
|
38
|
+
readonly name = "web-bluetooth-g1";
|
|
39
|
+
private readonly sides;
|
|
40
|
+
private eventCallbacks;
|
|
41
|
+
private audioCallbacks;
|
|
42
|
+
constructor(bluetooth?: NavigatorBluetoothLike);
|
|
43
|
+
connect(): Promise<void>;
|
|
44
|
+
disconnect(): Promise<void>;
|
|
45
|
+
isConnected(): boolean;
|
|
46
|
+
getConnectedLenses(): SmartglassesConnectedLenses;
|
|
47
|
+
write(side: GlassSide, data: Uint8Array): Promise<void>;
|
|
48
|
+
writeBoth(data: Uint8Array): Promise<void>;
|
|
49
|
+
openMicrophone(enabled: boolean): Promise<void>;
|
|
50
|
+
onEvent(callback: (event: G1Event) => void): () => void;
|
|
51
|
+
onAudio(callback: (audioData: Uint8Array, sampleRate: number, side: GlassSide, encoding?: SmartglassesAudioEncoding, sequence?: number) => void): () => void;
|
|
52
|
+
connectLens(side: GlassSide): Promise<void>;
|
|
53
|
+
private findConnectedDeviceSide;
|
|
54
|
+
private emitParsed;
|
|
55
|
+
}
|
|
56
|
+
export declare function getWebBluetoothG1Transport(): SmartglassesTransport | null;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=web-bluetooth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-bluetooth.d.ts","sourceRoot":"","sources":["../../src/transport/web-bluetooth.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,SAAS,EAEd,KAAK,yBAAyB,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB,KAAK,qCAAqC,GAAG;IAC3C,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,yBAAyB,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,sBAAsB,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,kBAAkB,EAAE,MAAM,OAAO,CAAC,qCAAqC,CAAC,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC;IAC3E,mBAAmB,CAAC,EAAE,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAC7B,IAAI,CAAC;CACX,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAC9C,iBAAiB,EAAE,CACjB,cAAc,EAAE,MAAM,KACnB,OAAO,CAAC,qCAAqC,CAAC,CAAC;KACrD,CAAC,CAAC;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,CAAC;KACvD,CAAC;CACH,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,aAAa,EAAE,CAAC,OAAO,EAAE;QACvB,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,UAAU,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAC;QAC9D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC7B,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACpC,CAAC;AAUF,qBAAa,uBAAwB,YAAW,qBAAqB;IAejE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAd5B,QAAQ,CAAC,IAAI,sBAAsB;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,OAAO,CAAC,cAAc,CAAuC;IAC7D,OAAO,CAAC,cAAc,CAQlB;gBAGe,SAAS,GAAE,sBAAgD;IAGxE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAUxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC,WAAW,IAAI,OAAO;IAStB,kBAAkB,IAAI,2BAA2B;IAY3C,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAevD,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAKvD,OAAO,CACL,QAAQ,EAAE,CACR,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,EACf,QAAQ,CAAC,EAAE,yBAAyB,EACpC,QAAQ,CAAC,EAAE,MAAM,KACd,IAAI,GACR,MAAM,IAAI;IAKP,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CjD,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,UAAU;CAcnB;AAQD,wBAAgB,0BAA0B,IAAI,qBAAqB,GAAG,IAAI,CAKzE"}
|