iobroker.assistant 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/admin/assistant.svg +19 -0
  4. package/admin/custom/.vite/manifest.json +169 -0
  5. package/admin/custom/assets/Components-DcnuVFM6.js +1 -0
  6. package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomAssistant__mf_owner__1-DogEjv3U.js +1 -0
  7. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-Dw5TkU1t.js +1 -0
  8. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-BEFjTs9S.js +1 -0
  9. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-C4r3udVz.js +1243 -0
  10. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DlOBHPL0.js +1494 -0
  11. package/admin/custom/assets/hostInit-DKMI-sLp.js +1 -0
  12. package/admin/custom/assets/index-B8wW0kQx.js +1 -0
  13. package/admin/custom/assets/lib-DpE8sc4h.js +3 -0
  14. package/admin/custom/assets/mf-entry-bootstrap-0-e7fa582d.js +36 -0
  15. package/admin/custom/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  16. package/admin/custom/assets/virtualExposes-DOTO22UK.js +1 -0
  17. package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__ConfigCustomAssistant__customComponents_js-CQa59yVi.js +1 -0
  18. package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__ConfigCustomAssistant__customComponents_js-9g2Fj8tk.js +2 -0
  19. package/admin/custom/assets/virtual_mf-exposes___mfe_internal__ConfigCustomAssistant__customComponents_js-BTUQTbYo.js +2 -0
  20. package/admin/custom/assets/vite-preload-helper-B7qeedMF.js +1 -0
  21. package/admin/custom/customComponents.js +1 -0
  22. package/admin/custom/customComponents.ssr.js +1 -0
  23. package/admin/custom/i18n/de.json +58 -0
  24. package/admin/custom/i18n/en.json +58 -0
  25. package/admin/custom/i18n/es.json +34 -0
  26. package/admin/custom/i18n/fr.json +34 -0
  27. package/admin/custom/i18n/it.json +34 -0
  28. package/admin/custom/i18n/nl.json +34 -0
  29. package/admin/custom/i18n/pl.json +34 -0
  30. package/admin/custom/i18n/pt.json +34 -0
  31. package/admin/custom/i18n/ru.json +34 -0
  32. package/admin/custom/i18n/uk.json +34 -0
  33. package/admin/custom/i18n/zh-cn.json +34 -0
  34. package/admin/custom/index.html +32 -0
  35. package/admin/custom/mf-manifest.json +1 -0
  36. package/admin/custom/mf-stats.json +1 -0
  37. package/admin/i18n/de.json +130 -0
  38. package/admin/i18n/en.json +130 -0
  39. package/admin/i18n/es.json +106 -0
  40. package/admin/i18n/fr.json +106 -0
  41. package/admin/i18n/it.json +106 -0
  42. package/admin/i18n/nl.json +106 -0
  43. package/admin/i18n/pl.json +106 -0
  44. package/admin/i18n/pt.json +106 -0
  45. package/admin/i18n/ru.json +106 -0
  46. package/admin/i18n/uk.json +106 -0
  47. package/admin/i18n/zh-cn.json +106 -0
  48. package/admin/jsonConfig.json +743 -0
  49. package/admin/sounds/alarm.wav +0 -0
  50. package/admin/sounds/timer.wav +0 -0
  51. package/build/lib/alarms.js +281 -0
  52. package/build/lib/alarms.js.map +1 -0
  53. package/build/lib/context.js +52 -0
  54. package/build/lib/context.js.map +1 -0
  55. package/build/lib/credentials.js +121 -0
  56. package/build/lib/credentials.js.map +1 -0
  57. package/build/lib/llm.js +303 -0
  58. package/build/lib/llm.js.map +1 -0
  59. package/build/lib/localLlm.js +221 -0
  60. package/build/lib/localLlm.js.map +1 -0
  61. package/build/lib/memory.js +190 -0
  62. package/build/lib/memory.js.map +1 -0
  63. package/build/lib/nlu.js +929 -0
  64. package/build/lib/nlu.js.map +1 -0
  65. package/build/lib/timers.js +245 -0
  66. package/build/lib/timers.js.map +1 -0
  67. package/build/lib/tools.js +299 -0
  68. package/build/lib/tools.js.map +1 -0
  69. package/build/lib/voice/aws.js +102 -0
  70. package/build/lib/voice/aws.js.map +1 -0
  71. package/build/lib/voice/azure.js +144 -0
  72. package/build/lib/voice/azure.js.map +1 -0
  73. package/build/lib/voice/download.js +76 -0
  74. package/build/lib/voice/download.js.map +1 -0
  75. package/build/lib/voice/engines.js +89 -0
  76. package/build/lib/voice/engines.js.map +1 -0
  77. package/build/lib/voice/lang.js +33 -0
  78. package/build/lib/voice/lang.js.map +1 -0
  79. package/build/lib/voice/piper.js +191 -0
  80. package/build/lib/voice/piper.js.map +1 -0
  81. package/build/lib/voice/protocol.js +35 -0
  82. package/build/lib/voice/protocol.js.map +1 -0
  83. package/build/lib/voice/stt.js +116 -0
  84. package/build/lib/voice/stt.js.map +1 -0
  85. package/build/lib/voice/tts.js +36 -0
  86. package/build/lib/voice/tts.js.map +1 -0
  87. package/build/lib/voice/voiceServer.js +249 -0
  88. package/build/lib/voice/voiceServer.js.map +1 -0
  89. package/build/lib/voice/vosk.js +260 -0
  90. package/build/lib/voice/vosk.js.map +1 -0
  91. package/build/lib/voice/wyoming.js +320 -0
  92. package/build/lib/voice/wyoming.js.map +1 -0
  93. package/build/lib/weather.js +423 -0
  94. package/build/lib/weather.js.map +1 -0
  95. package/build/main.js +3152 -0
  96. package/build/main.js.map +1 -0
  97. package/io-package.json +528 -0
  98. package/package.json +68 -0
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WyomingServer = void 0;
37
+ /**
38
+ * Wyoming protocol endpoint (https://github.com/rhasspy/wyoming) — a TCP server that bridges Wyoming
39
+ * events to the existing STT → answer → TTS pipeline, so a `wyoming-satellite`, Home Assistant Voice PE
40
+ * or ESPHome voice device can stream to this adapter directly.
41
+ *
42
+ * Wire format: one JSON header line terminated by '\n', optionally followed by `payload_length` raw
43
+ * bytes (audio). We handle: describe → info; audio-start/chunk/stop → STT → answer → TTS (streamed back
44
+ * as audio-*); synthesize → TTS.
45
+ *
46
+ * ⚠️ Framing is unit-tested (loopback); interop with real HA Voice PE / wyoming-satellite still needs
47
+ * on-device validation.
48
+ */
49
+ const net = __importStar(require("node:net"));
50
+ const node_buffer_1 = require("node:buffer");
51
+ /** Bytes of PCM per outgoing audio-chunk. */
52
+ const AUDIO_CHUNK = 8192;
53
+ class WyomingServer {
54
+ opts;
55
+ server = null;
56
+ conns = new Set();
57
+ constructor(opts) {
58
+ this.opts = opts;
59
+ }
60
+ start() {
61
+ return new Promise((resolve, reject) => {
62
+ const server = net.createServer(sock => this.onConnection(sock));
63
+ this.server = server;
64
+ server.once('error', reject);
65
+ server.on('error', e => this.opts.log.error(`Wyoming server error: ${e.message}`));
66
+ const addr = this.opts.bindAddress && this.opts.bindAddress !== '0.0.0.0' ? this.opts.bindAddress : undefined;
67
+ server.listen(this.opts.port, addr, () => {
68
+ this.opts.log.info(`Wyoming server listening on TCP ${addr || '0.0.0.0'}:${this.opts.port}`);
69
+ resolve();
70
+ });
71
+ });
72
+ }
73
+ async stop() {
74
+ for (const s of this.conns) {
75
+ s.destroy();
76
+ }
77
+ this.conns.clear();
78
+ const server = this.server;
79
+ this.server = null;
80
+ if (server) {
81
+ await new Promise(res => server.close(() => res()));
82
+ }
83
+ }
84
+ onConnection(sock) {
85
+ this.conns.add(sock);
86
+ sock.on('close', () => this.conns.delete(sock));
87
+ sock.on('error', () => this.conns.delete(sock));
88
+ new WyomingConnection(sock, this.opts);
89
+ }
90
+ }
91
+ exports.WyomingServer = WyomingServer;
92
+ /** One client connection: frame parser + event handling. */
93
+ class WyomingConnection {
94
+ sock;
95
+ opts;
96
+ buf = node_buffer_1.Buffer.alloc(0);
97
+ draining = false;
98
+ audioChunks = [];
99
+ audioRate = 16000;
100
+ /** Per-utterance language (overridable via `transcribe`/`synthesize` `data.language`). */
101
+ lang;
102
+ constructor(sock, opts) {
103
+ this.sock = sock;
104
+ this.opts = opts;
105
+ this.lang = opts.language;
106
+ sock.on('data', d => {
107
+ this.buf = node_buffer_1.Buffer.concat([this.buf, d]);
108
+ void this.drain();
109
+ });
110
+ }
111
+ /** Pull complete events (header line + optional payload) out of the receive buffer. */
112
+ async drain() {
113
+ if (this.draining) {
114
+ return;
115
+ }
116
+ this.draining = true;
117
+ try {
118
+ for (;;) {
119
+ const nl = this.buf.indexOf(0x0a); // '\n'
120
+ if (nl < 0) {
121
+ break;
122
+ }
123
+ let header;
124
+ try {
125
+ header = JSON.parse(this.buf.subarray(0, nl).toString('utf8'));
126
+ }
127
+ catch {
128
+ this.buf = this.buf.subarray(nl + 1); // skip a bad line
129
+ continue;
130
+ }
131
+ const payloadLen = Number(header.payload_length || 0);
132
+ const total = nl + 1 + payloadLen;
133
+ if (this.buf.length < total) {
134
+ break; // wait for the rest of the payload
135
+ }
136
+ const payload = payloadLen ? node_buffer_1.Buffer.from(this.buf.subarray(nl + 1, total)) : undefined;
137
+ this.buf = this.buf.subarray(total);
138
+ await this.handle({ type: String(header.type || ''), data: header.data, payload });
139
+ }
140
+ }
141
+ catch (e) {
142
+ this.opts.log.warn(`Wyoming connection error: ${e.message}`);
143
+ }
144
+ finally {
145
+ this.draining = false;
146
+ }
147
+ }
148
+ write(type, data, payload) {
149
+ const header = { type };
150
+ if (data !== undefined) {
151
+ header.data = data;
152
+ }
153
+ if (payload) {
154
+ header.payload_length = payload.length;
155
+ }
156
+ this.sock.write(`${JSON.stringify(header)}\n`);
157
+ if (payload) {
158
+ this.sock.write(payload);
159
+ }
160
+ }
161
+ async handle(ev) {
162
+ switch (ev.type) {
163
+ case 'describe':
164
+ this.write('info', this.info());
165
+ break;
166
+ // ASR request / start-of-speech (VAD) — reset the buffer, optional language hint.
167
+ case 'transcribe':
168
+ case 'voice-started':
169
+ case 'audio-start':
170
+ if (ev.data?.language) {
171
+ this.lang =
172
+ typeof ev.data.language === 'object'
173
+ ? JSON.stringify(ev.data.language)
174
+ : ev.data.language.toString();
175
+ }
176
+ if (ev.data?.rate) {
177
+ this.audioRate = Number(ev.data.rate) || this.audioRate;
178
+ }
179
+ this.audioChunks = [];
180
+ break;
181
+ case 'audio-chunk':
182
+ if (ev.payload) {
183
+ if (!this.audioChunks.length && ev.data?.rate) {
184
+ this.audioRate = Number(ev.data.rate) || this.audioRate;
185
+ }
186
+ this.audioChunks.push(ev.payload);
187
+ }
188
+ break;
189
+ // End-of-speech — either the explicit audio-stop or a VAD voice-stopped.
190
+ case 'audio-stop':
191
+ case 'voice-stopped':
192
+ await this.onUtterance();
193
+ break;
194
+ case 'synthesize':
195
+ if (ev.data?.language) {
196
+ this.lang =
197
+ typeof ev.data.language === 'object'
198
+ ? JSON.stringify(ev.data.language)
199
+ : ev.data.language.toString();
200
+ }
201
+ await this.speak(ev.data?.text
202
+ ? typeof ev.data?.text === 'object'
203
+ ? JSON.stringify(ev.data.text)
204
+ : ev.data.text.toString()
205
+ : '');
206
+ break;
207
+ case 'ping':
208
+ this.write('pong', ev.data);
209
+ break;
210
+ }
211
+ }
212
+ async onUtterance() {
213
+ const pcm = node_buffer_1.Buffer.concat(this.audioChunks);
214
+ this.audioChunks = [];
215
+ if (!pcm.length) {
216
+ return;
217
+ }
218
+ try {
219
+ const hints = this.opts.getHints ? await this.opts.getHints() : undefined;
220
+ const text = await this.opts.stt.transcribe(pcm, this.audioRate, this.lang, hints);
221
+ this.opts.log.info(`Wyoming Q: ${text || '(empty)'}`);
222
+ this.write('transcript', { text });
223
+ if (!text) {
224
+ return;
225
+ }
226
+ const answer = (await this.opts.answer(text)).trim();
227
+ this.opts.log.info(`Wyoming A: ${answer}`);
228
+ if (answer) {
229
+ await this.speak(answer);
230
+ }
231
+ }
232
+ catch (e) {
233
+ this.opts.log.error(`Wyoming pipeline failed: ${e.message}`);
234
+ this.write('error', { text: e.message });
235
+ }
236
+ }
237
+ async speak(text) {
238
+ if (!text) {
239
+ return;
240
+ }
241
+ try {
242
+ const { pcm, sampleRate } = await this.opts.tts.synthesize(text, this.lang);
243
+ const meta = { rate: sampleRate, width: 2, channels: 1 };
244
+ this.write('audio-start', meta);
245
+ for (let off = 0; off < pcm.length; off += AUDIO_CHUNK) {
246
+ this.write('audio-chunk', meta, pcm.subarray(off, off + AUDIO_CHUNK));
247
+ }
248
+ this.write('audio-stop', {});
249
+ }
250
+ catch (e) {
251
+ this.opts.log.error(`Wyoming TTS failed: ${e.message}`);
252
+ this.write('error', { text: e.message });
253
+ }
254
+ }
255
+ /** Capabilities advertised in response to `describe`. */
256
+ info() {
257
+ const lang = (this.opts.language || 'en').split('-')[0];
258
+ const attribution = { name: 'ioBroker', url: 'https://github.com/ioBroker/ioBroker.assistant' };
259
+ return {
260
+ asr: [
261
+ {
262
+ name: 'iobroker-assistant',
263
+ attribution,
264
+ installed: true,
265
+ description: 'ioBroker.assistant speech-to-text',
266
+ version: '1.0',
267
+ models: [
268
+ {
269
+ name: 'default',
270
+ attribution,
271
+ installed: true,
272
+ description: '',
273
+ version: '1.0',
274
+ languages: [lang],
275
+ },
276
+ ],
277
+ },
278
+ ],
279
+ tts: [
280
+ {
281
+ name: 'iobroker-assistant',
282
+ attribution,
283
+ installed: true,
284
+ description: 'ioBroker.assistant text-to-speech',
285
+ version: '1.0',
286
+ voices: [
287
+ {
288
+ name: 'default',
289
+ attribution,
290
+ installed: true,
291
+ description: '',
292
+ version: '1.0',
293
+ languages: [lang],
294
+ },
295
+ ],
296
+ },
297
+ ],
298
+ handle: [
299
+ {
300
+ name: 'iobroker-assistant',
301
+ attribution,
302
+ installed: true,
303
+ description: 'ioBroker.assistant intent handling',
304
+ version: '1.0',
305
+ models: [
306
+ {
307
+ name: 'default',
308
+ attribution,
309
+ installed: true,
310
+ description: '',
311
+ version: '1.0',
312
+ languages: [lang],
313
+ },
314
+ ],
315
+ },
316
+ ],
317
+ };
318
+ }
319
+ }
320
+ //# sourceMappingURL=wyoming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wyoming.js","sourceRoot":"","sources":["../../../src/lib/voice/wyoming.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wyoming protocol endpoint (https://github.com/rhasspy/wyoming) — a TCP server that bridges Wyoming\n * events to the existing STT → answer → TTS pipeline, so a `wyoming-satellite`, Home Assistant Voice PE\n * or ESPHome voice device can stream to this adapter directly.\n *\n * Wire format: one JSON header line terminated by '\\n', optionally followed by `payload_length` raw\n * bytes (audio). We handle: describe → info; audio-start/chunk/stop → STT → answer → TTS (streamed back\n * as audio-*); synthesize → TTS.\n *\n * ⚠️ Framing is unit-tested (loopback); interop with real HA Voice PE / wyoming-satellite still needs\n * on-device validation.\n */\nimport * as net from 'node:net';\nimport type { SttEngine } from './stt';\nimport type { TtsEngine } from './tts';\nimport { Buffer } from 'node:buffer';\n\nexport interface WyomingServerOptions {\n port: number;\n bindAddress?: string;\n /** ISO-639-1 language hint for STT/TTS. */\n language: string;\n stt: SttEngine;\n tts: TtsEngine;\n answer: (question: string) => Promise<string>;\n /** Optional STT vocabulary hints (device/room names) to bias recognition; re-read per utterance. */\n getHints?: () => string[] | Promise<string[]>;\n log: ioBroker.Logger;\n}\n\ninterface WyEvent {\n type: string;\n data?: Record<string, unknown>;\n payload?: Buffer;\n}\n\n/** Bytes of PCM per outgoing audio-chunk. */\nconst AUDIO_CHUNK = 8192;\n\nexport class WyomingServer {\n private server: net.Server | null = null;\n private readonly conns = new Set<net.Socket>();\n\n constructor(private readonly opts: WyomingServerOptions) {}\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer(sock => this.onConnection(sock));\n this.server = server;\n server.once('error', reject);\n server.on('error', e => this.opts.log.error(`Wyoming server error: ${e.message}`));\n const addr =\n this.opts.bindAddress && this.opts.bindAddress !== '0.0.0.0' ? this.opts.bindAddress : undefined;\n server.listen(this.opts.port, addr, () => {\n this.opts.log.info(`Wyoming server listening on TCP ${addr || '0.0.0.0'}:${this.opts.port}`);\n resolve();\n });\n });\n }\n\n async stop(): Promise<void> {\n for (const s of this.conns) {\n s.destroy();\n }\n this.conns.clear();\n const server = this.server;\n this.server = null;\n if (server) {\n await new Promise<void>(res => server.close(() => res()));\n }\n }\n\n private onConnection(sock: net.Socket): void {\n this.conns.add(sock);\n sock.on('close', () => this.conns.delete(sock));\n sock.on('error', () => this.conns.delete(sock));\n new WyomingConnection(sock, this.opts);\n }\n}\n\n/** One client connection: frame parser + event handling. */\nclass WyomingConnection {\n private buf = Buffer.alloc(0);\n private draining = false;\n private audioChunks: Buffer[] = [];\n private audioRate = 16000;\n /** Per-utterance language (overridable via `transcribe`/`synthesize` `data.language`). */\n private lang: string;\n\n constructor(\n private readonly sock: net.Socket,\n private readonly opts: WyomingServerOptions,\n ) {\n this.lang = opts.language;\n sock.on('data', d => {\n this.buf = Buffer.concat([this.buf, d]);\n void this.drain();\n });\n }\n\n /** Pull complete events (header line + optional payload) out of the receive buffer. */\n private async drain(): Promise<void> {\n if (this.draining) {\n return;\n }\n this.draining = true;\n try {\n for (;;) {\n const nl = this.buf.indexOf(0x0a); // '\\n'\n if (nl < 0) {\n break;\n }\n let header: { type?: string; data?: Record<string, unknown>; payload_length?: number };\n try {\n header = JSON.parse(this.buf.subarray(0, nl).toString('utf8'));\n } catch {\n this.buf = this.buf.subarray(nl + 1); // skip a bad line\n continue;\n }\n const payloadLen = Number(header.payload_length || 0);\n const total = nl + 1 + payloadLen;\n if (this.buf.length < total) {\n break; // wait for the rest of the payload\n }\n const payload = payloadLen ? Buffer.from(this.buf.subarray(nl + 1, total)) : undefined;\n this.buf = this.buf.subarray(total);\n await this.handle({ type: String(header.type || ''), data: header.data, payload });\n }\n } catch (e) {\n this.opts.log.warn(`Wyoming connection error: ${(e as Error).message}`);\n } finally {\n this.draining = false;\n }\n }\n\n private write(type: string, data?: Record<string, unknown>, payload?: Buffer): void {\n const header: Record<string, unknown> = { type };\n if (data !== undefined) {\n header.data = data;\n }\n if (payload) {\n header.payload_length = payload.length;\n }\n this.sock.write(`${JSON.stringify(header)}\\n`);\n if (payload) {\n this.sock.write(payload);\n }\n }\n\n private async handle(ev: WyEvent): Promise<void> {\n switch (ev.type) {\n case 'describe':\n this.write('info', this.info());\n break;\n // ASR request / start-of-speech (VAD) — reset the buffer, optional language hint.\n case 'transcribe':\n case 'voice-started':\n case 'audio-start':\n if (ev.data?.language) {\n this.lang =\n typeof ev.data.language === 'object'\n ? JSON.stringify(ev.data.language)\n : (ev.data.language as string).toString();\n }\n if (ev.data?.rate) {\n this.audioRate = Number(ev.data.rate) || this.audioRate;\n }\n this.audioChunks = [];\n break;\n case 'audio-chunk':\n if (ev.payload) {\n if (!this.audioChunks.length && ev.data?.rate) {\n this.audioRate = Number(ev.data.rate) || this.audioRate;\n }\n this.audioChunks.push(ev.payload);\n }\n break;\n // End-of-speech — either the explicit audio-stop or a VAD voice-stopped.\n case 'audio-stop':\n case 'voice-stopped':\n await this.onUtterance();\n break;\n case 'synthesize':\n if (ev.data?.language) {\n this.lang =\n typeof ev.data.language === 'object'\n ? JSON.stringify(ev.data.language)\n : (ev.data.language as string).toString();\n }\n await this.speak(\n ev.data?.text\n ? typeof ev.data?.text === 'object'\n ? JSON.stringify(ev.data.text)\n : (ev.data.text as string).toString()\n : '',\n );\n break;\n case 'ping':\n this.write('pong', ev.data);\n break;\n }\n }\n\n private async onUtterance(): Promise<void> {\n const pcm = Buffer.concat(this.audioChunks);\n this.audioChunks = [];\n if (!pcm.length) {\n return;\n }\n try {\n const hints = this.opts.getHints ? await this.opts.getHints() : undefined;\n const text = await this.opts.stt.transcribe(pcm, this.audioRate, this.lang, hints);\n this.opts.log.info(`Wyoming Q: ${text || '(empty)'}`);\n this.write('transcript', { text });\n if (!text) {\n return;\n }\n const answer = (await this.opts.answer(text)).trim();\n this.opts.log.info(`Wyoming A: ${answer}`);\n if (answer) {\n await this.speak(answer);\n }\n } catch (e) {\n this.opts.log.error(`Wyoming pipeline failed: ${(e as Error).message}`);\n this.write('error', { text: (e as Error).message });\n }\n }\n\n private async speak(text: string): Promise<void> {\n if (!text) {\n return;\n }\n try {\n const { pcm, sampleRate } = await this.opts.tts.synthesize(text, this.lang);\n const meta = { rate: sampleRate, width: 2, channels: 1 };\n this.write('audio-start', meta);\n for (let off = 0; off < pcm.length; off += AUDIO_CHUNK) {\n this.write('audio-chunk', meta, pcm.subarray(off, off + AUDIO_CHUNK));\n }\n this.write('audio-stop', {});\n } catch (e) {\n this.opts.log.error(`Wyoming TTS failed: ${(e as Error).message}`);\n this.write('error', { text: (e as Error).message });\n }\n }\n\n /** Capabilities advertised in response to `describe`. */\n private info(): Record<string, unknown> {\n const lang = (this.opts.language || 'en').split('-')[0];\n const attribution = { name: 'ioBroker', url: 'https://github.com/ioBroker/ioBroker.assistant' };\n return {\n asr: [\n {\n name: 'iobroker-assistant',\n attribution,\n installed: true,\n description: 'ioBroker.assistant speech-to-text',\n version: '1.0',\n models: [\n {\n name: 'default',\n attribution,\n installed: true,\n description: '',\n version: '1.0',\n languages: [lang],\n },\n ],\n },\n ],\n tts: [\n {\n name: 'iobroker-assistant',\n attribution,\n installed: true,\n description: 'ioBroker.assistant text-to-speech',\n version: '1.0',\n voices: [\n {\n name: 'default',\n attribution,\n installed: true,\n description: '',\n version: '1.0',\n languages: [lang],\n },\n ],\n },\n ],\n handle: [\n {\n name: 'iobroker-assistant',\n attribution,\n installed: true,\n description: 'ioBroker.assistant intent handling',\n version: '1.0',\n models: [\n {\n name: 'default',\n attribution,\n installed: true,\n description: '',\n version: '1.0',\n languages: [lang],\n },\n ],\n },\n ],\n };\n }\n}\n"]}