iobroker.assistant 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/build/lib/voice/esphome.js +49 -4
- package/build/lib/voice/esphome.js.map +1 -1
- package/build/lib/voice/vad.js +42 -5
- package/build/lib/voice/vad.js.map +1 -1
- package/build/main.js +36 -2
- package/build/main.js.map +1 -1
- package/io-package.json +27 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,6 +235,13 @@ train it twice.
|
|
|
235
235
|
Placeholder for the next version (at the beginning of the line):
|
|
236
236
|
### **WORK IN PROGRESS**
|
|
237
237
|
-->
|
|
238
|
+
### 1.4.4 (2026-11-03)
|
|
239
|
+
* (@GermanBluefox) ESPHome satellites: a sound only counts as speech once it has lasted about 200 ms. After a false wake a single click opened the gate, and Whisper made "и все просят." of the amplified silence around it
|
|
240
|
+
* (@GermanBluefox) ESPHome satellites: when a device stops sending audio without ending the stream, the recording is closed after 2 seconds instead of the 30-second pipeline timeout, during which the satellite showed "listening"
|
|
241
|
+
|
|
242
|
+
### 1.4.3 (2026-11-03)
|
|
243
|
+
* (@GermanBluefox) An empty question sent with `sendTo('ask')` is rejected (`{error: 'empty question'}`) instead of going to the model with nothing but the time context, and the log names the sender once a minute. A script that asked on every answer looped about once a second and used pi-agent tokens. The log line of every `ask` now names the sender
|
|
244
|
+
|
|
238
245
|
### 1.4.2 (2026-11-03)
|
|
239
246
|
* (@GermanBluefox) ESPHome satellites: after 2.5 seconds of speech a pause may be half as long again before the request ends, so a pause for thought no longer cuts off a longer request. Short commands close as fast as before
|
|
240
247
|
* (@GermanBluefox) ESPHome satellites: speech recognition gets the recording without the device's chirp or beep and without the silence around the speech, and quiet speech is amplified (up to ×10). Whisper made "Продолжение следует..." of the beep and the silence
|
|
@@ -136,6 +136,22 @@ const DEFAULT_SILENCE_MS = 900;
|
|
|
136
136
|
*/
|
|
137
137
|
const LONG_REQUEST_MS = 2500;
|
|
138
138
|
const LONG_PAUSE_FACTOR = 1.5;
|
|
139
|
+
/**
|
|
140
|
+
* Sound needed before a loud moment counts as speech. On vitanova (2026-11-03) a single 100 ms sound at RMS 197
|
|
141
|
+
* after a false wake opened the gate (threshold 150), and Whisper made "и все просят." of the amplified silence
|
|
142
|
+
* around it. The shortest real answer, "да", lasts about 250 ms.
|
|
143
|
+
*/
|
|
144
|
+
const MIN_SPEECH_MS = 200;
|
|
145
|
+
/**
|
|
146
|
+
* Close the utterance when the device stops sending audio without ending the stream. Seen on a ThirdReality
|
|
147
|
+
* speaker: the audio stopped after 2.7 s, and the satellite showed "listening" for the 30 s of
|
|
148
|
+
* {@link PIPELINE_TIMEOUT_MS}. A device streams every few tens of ms while the mic is open.
|
|
149
|
+
*/
|
|
150
|
+
const STREAM_STALL_MS = 2000;
|
|
151
|
+
/** The same before the first audio arrives: a device needs a moment to open the microphone. */
|
|
152
|
+
const STREAM_START_MS = 5000;
|
|
153
|
+
/** How often a running capture checks for a stalled stream. */
|
|
154
|
+
const STALL_CHECK_MS = 250;
|
|
139
155
|
/** Silence threshold as a share of the speech threshold — keeps the default pair 250/150. */
|
|
140
156
|
const SILENCE_SHARE = 0.6;
|
|
141
157
|
/**
|
|
@@ -599,9 +615,11 @@ class EsphomeConnection {
|
|
|
599
615
|
this.send(response);
|
|
600
616
|
const timer = setTimeout(() => {
|
|
601
617
|
this.opts.log.warn(`ESPHome ${this.deviceName}: no end of speech within ${PIPELINE_TIMEOUT_MS} ms`);
|
|
602
|
-
void this.finishCapture();
|
|
618
|
+
void this.finishCapture('pipeline-timeout');
|
|
603
619
|
}, PIPELINE_TIMEOUT_MS);
|
|
604
620
|
timer.unref();
|
|
621
|
+
const stallCheck = setInterval(() => this.checkStall(), STALL_CHECK_MS);
|
|
622
|
+
stallCheck.unref();
|
|
605
623
|
const silenceMs = this.opts.silenceMs || DEFAULT_SILENCE_MS;
|
|
606
624
|
this.capture = {
|
|
607
625
|
chunks: [],
|
|
@@ -617,10 +635,14 @@ class EsphomeConnection {
|
|
|
617
635
|
startTimeoutMs: phrase ? NO_SPEECH_MS : NO_SPEECH_FOLLOW_UP_MS,
|
|
618
636
|
minStartLevel: this.speechLevel || exports.DEFAULT_SPEECH_LEVEL,
|
|
619
637
|
minEndLevel: Math.round((this.speechLevel || exports.DEFAULT_SPEECH_LEVEL) * SILENCE_SHARE),
|
|
638
|
+
minSpeechMs: MIN_SPEECH_MS,
|
|
620
639
|
}),
|
|
621
640
|
conversationId: request.getConversationId(),
|
|
622
641
|
wakeWord: phrase || '',
|
|
623
642
|
timer,
|
|
643
|
+
stallCheck,
|
|
644
|
+
startedAt: Date.now(),
|
|
645
|
+
lastAudioAt: 0,
|
|
624
646
|
closed: false,
|
|
625
647
|
};
|
|
626
648
|
this.status('listening');
|
|
@@ -638,6 +660,7 @@ class EsphomeConnection {
|
|
|
638
660
|
}
|
|
639
661
|
const chunk = Buffer.from(audio.getData_asU8());
|
|
640
662
|
if (chunk.length) {
|
|
663
|
+
capture.lastAudioAt = Date.now();
|
|
641
664
|
capture.chunks.push(chunk);
|
|
642
665
|
const end = capture.detector.push(chunk);
|
|
643
666
|
if (end) {
|
|
@@ -646,8 +669,23 @@ class EsphomeConnection {
|
|
|
646
669
|
}
|
|
647
670
|
}
|
|
648
671
|
if (audio.getEnd()) {
|
|
649
|
-
void this.finishCapture();
|
|
672
|
+
void this.finishCapture('stream');
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/** End a capture whose device stopped sending audio without ending the stream. */
|
|
676
|
+
checkStall() {
|
|
677
|
+
const capture = this.capture;
|
|
678
|
+
if (!capture || capture.closed) {
|
|
679
|
+
return;
|
|
650
680
|
}
|
|
681
|
+
const now = Date.now();
|
|
682
|
+
const quietMs = capture.lastAudioAt ? now - capture.lastAudioAt : now - capture.startedAt;
|
|
683
|
+
if (quietMs < (capture.lastAudioAt ? STREAM_STALL_MS : STREAM_START_MS)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const audioMs = Math.round(capture.chunks.reduce((sum, c) => sum + c.length, 0) / 2 / (MIC_SAMPLE_RATE / 1000));
|
|
687
|
+
this.opts.log.info(`ESPHome ${this.deviceName}: the device ${capture.lastAudioAt ? `stopped sending audio after ${(audioMs / 1000).toFixed(2)} s` : 'sent no audio'} without ending the stream — closing the utterance after ${(quietMs / 1000).toFixed(1)} s of nothing`);
|
|
688
|
+
void this.finishCapture('stall');
|
|
651
689
|
}
|
|
652
690
|
/** Hand a recording that never became a request to {@link EsphomeSatellitesOptions.onDiscarded}. */
|
|
653
691
|
discard(capture, pcm, reason, vad) {
|
|
@@ -679,11 +717,12 @@ class EsphomeConnection {
|
|
|
679
717
|
capture.closed = true;
|
|
680
718
|
this.capture = null;
|
|
681
719
|
clearTimeout(capture.timer);
|
|
720
|
+
clearInterval(capture.stallCheck);
|
|
682
721
|
// Send this first: the device streams until it sees it.
|
|
683
722
|
this.event(EVENT.STT_VAD_END);
|
|
684
723
|
const pcm = Buffer.concat(capture.chunks);
|
|
685
724
|
const seconds = pcm.length / 2 / MIC_SAMPLE_RATE;
|
|
686
|
-
const vad = { ...capture.detector.report(), end
|
|
725
|
+
const vad = { ...capture.detector.report(), end };
|
|
687
726
|
const startedAt = vad.speechStartMs !== undefined ? `, speech from ${(vad.speechStartMs / 1000).toFixed(2)} s` : '';
|
|
688
727
|
const endedBy = vad.end === 'speech-end' ? `ended after ${vad.silenceMs} ms of silence` : `ended by ${vad.end}`;
|
|
689
728
|
this.opts.log.debug(`ESPHome ${this.deviceName}: captured ${seconds.toFixed(2)} s ` +
|
|
@@ -692,7 +731,12 @@ class EsphomeConnection {
|
|
|
692
731
|
if (!pcm.length || !capture.detector.sawSpeech) {
|
|
693
732
|
const loudest = Math.round(capture.detector.analysedPeak);
|
|
694
733
|
const gate = Math.round(capture.detector.startLevel);
|
|
695
|
-
if (
|
|
734
|
+
if (capture.detector.longestSoundMs > 0) {
|
|
735
|
+
// Something crossed the gate, but too briefly for a word: a click, a knock, a cough.
|
|
736
|
+
this.opts.log.debug(`ESPHome ${this.deviceName}: only a short sound (${capture.detector.longestSoundMs} ms, ` +
|
|
737
|
+
`RMS ${loudest}) — too short for speech (${MIN_SPEECH_MS} ms), dropping the utterance`);
|
|
738
|
+
}
|
|
739
|
+
else if (pcm.length && loudest >= gate * QUIET_VOICE_SHARE) {
|
|
696
740
|
// Not an empty room: something came close to the gate. Typically a microphone that is quieter
|
|
697
741
|
// than the default threshold expects (a phone) — say where to change it.
|
|
698
742
|
this.opts.log.info(`ESPHome ${this.deviceName}: dropped the utterance — the loudest sound (RMS ${loudest}) stayed ` +
|
|
@@ -835,6 +879,7 @@ class EsphomeConnection {
|
|
|
835
879
|
this.capture = null;
|
|
836
880
|
capture.closed = true;
|
|
837
881
|
clearTimeout(capture.timer);
|
|
882
|
+
clearInterval(capture.stallCheck);
|
|
838
883
|
}
|
|
839
884
|
// ── announcements ───────────────────────────────────────────────────────────────────────────────
|
|
840
885
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"esphome.js","sourceRoot":"","sources":["../../../src/lib/voice/esphome.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESPHome voice satellites — the fourth transport, next to the UDP server (`voiceServer.ts`) and the\n * Wyoming endpoint (`wyoming.ts`).\n *\n * Unlike those two, this one is a **client**: an ESPHome voice satellite is a *server* that waits on\n * TCP 6053 for the home-automation controller to connect and drive it. We therefore dial out to each\n * configured device and play the role Home Assistant normally plays. That covers the ThirdReality\n * Voice & Music Assistant (Dev Edition), the Home Assistant Voice PE and any box running\n * OHF-Voice/linux-voice-assistant.\n *\n * Flow for one utterance (verified against the firmware's `src/satellite/Satellite.cpp`):\n *\n * device → VoiceAssistantRequest{start, wake_word_phrase} wake word fired on the device\n * we → VoiceAssistantResponse{port:0} 0 = audio over the API, no UDP\n * we → RUN_START, WAKE_WORD_END, STT_START, STT_VAD_START\n * device → VoiceAssistantAudio … 16 kHz mono s16le, *until told to stop*\n * we → STT_VAD_END ← the device has no VAD of its own\n * we → STT_END{text}, INTENT_START, INTENT_END, TTS_START{text}, TTS_END{url}, RUN_END\n * device → fetches the URL over HTTP, plays it, VoiceAssistantAnnounceFinished\n *\n * Two consequences shape this file: end-of-speech detection is ours (`vad.ts`), and the spoken reply\n * has to be reachable over HTTP (`mediaServer.ts`) rather than streamed down the socket.\n */\nimport * as net from 'node:net';\nimport {\n pb,\n encode,\n decode,\n type PbMessage,\n type AuthenticationResponse,\n type DeviceInfoResponse,\n type HelloResponse,\n type VoiceAssistantAnnounceFinished,\n type VoiceAssistantAudio,\n type VoiceAssistantConfigurationResponse,\n type VoiceAssistantEventData,\n type VoiceAssistantRequestMsg,\n} from './esphomeProto';\nimport { EntityRegistry, isEntityStateMessage, isListEntityMessage, type EsphomeEntity } from './esphomeEntities';\nimport { SpeechDetector, type SpeechEnd, type Bytes, type VadReport } from './vad';\nimport { prepareForStt } from './speechAudio';\nimport { pcmToWav } from './stt';\nimport { levelProfile, type DiscardedCapture, type DiscardReason } from './captures';\nimport type { SttEngine } from './stt';\nimport type { TtsEngine } from './tts';\nimport type { SatelliteState } from './protocol';\n\n/** `VoiceAssistantEvent` (api.proto) — the subset we emit. */\nconst EVENT = {\n ERROR: 0,\n RUN_START: 1,\n RUN_END: 2,\n STT_START: 3,\n STT_END: 4,\n INTENT_START: 5,\n INTENT_END: 6,\n TTS_START: 7,\n TTS_END: 8,\n WAKE_WORD_END: 10,\n STT_VAD_START: 11,\n STT_VAD_END: 12,\n} as const;\n\n/** `SubscribeVoiceAssistantRequest.flags`: audio over the API socket instead of a UDP side channel. */\nconst SUBSCRIBE_API_AUDIO = 1;\n/** ESPHome voice satellites always capture at 16 kHz mono 16-bit. */\nconst MIC_SAMPLE_RATE = 16000;\n/**\n * Leading audio to keep out of the end-of-speech analysis. These devices acknowledge the wake word\n * with a chirp through their own speaker and hear it back on their own microphone, far louder than\n * the speech that follows. Without this the chirp alone satisfies the detector's speech test — so\n * `sawSpeech` stops meaning \"somebody said something\" and a false wake is billed to STT — and it\n * drags the noise floor up with it.\n *\n * Measured over four captures from a ThirdReality Voice & Music Assistant (RMS per 50 ms, remarkably\n * repeatable across all four):\n *\n * ms 0 50 100 150 200 250 300 350 400 450 500 550 600\n * rms ~20 ~900 ~11k ~19k ~14k ~1.3k ~990 ~780 ~610 ~370 ~330 ~165 ~180\n *\n * so the chirp is still above the gate at 500 ms and only clears it around 550–600. 600 ms is the\n * smallest value that rejects a chirp-only wake while still detecting the speech in every one of the\n * four captures, with no change to when the utterance closes. Only the analysis skips it: STT gets the audio\n * from 400 ms before the detected speech (`speechAudio.ts`), so a speaker who starts inside this window loses\n * nothing but a late gate open.\n */\nconst WAKE_CHIRP_MS = 600;\n/** Default API port of an ESPHome device. */\nconst DEFAULT_PORT = 6053;\n/** Keepalive interval; two missed replies count as a dead link. */\nconst PING_INTERVAL_MS = 20_000;\n/** Wait between reconnect attempts. Devices reboot, and a Wi-Fi speaker drops off now and then. */\nconst RECONNECT_MS = 10_000;\n/** Give up on a pipeline that produced no audio at all (device wedged mid-stream). */\nconst PIPELINE_TIMEOUT_MS = 30_000;\n/** How long we wait for the device to report that it finished playing before forcing it back to idle. */\nconst PLAYBACK_TIMEOUT_MS = 60_000;\n/**\n * Default lowest RMS that counts as speech (the noise floor can only raise the gate above it). Fits the\n * ThirdReality speaker; quieter microphones get their own value per device ({@link EsphomeDevice.speechLevel}).\n */\nexport const DEFAULT_SPEECH_LEVEL = 250;\n/**\n * Close the microphone when nobody has started speaking this long after the wake word. Without it a false wake\n * kept the stream open for the full 12 s cap: on a ThirdReality speaker with a custom wake word that was ten\n * times in one night, each 12.6 s of an open mic (and of music ducked, if any played).\n */\nconst NO_SPEECH_MS = 5000;\n/** The same after a follow-up question re-opened the mic: the user may need a moment to think. */\nconst NO_SPEECH_FOLLOW_UP_MS = 8000;\n/** Silence that ends an utterance when the configuration leaves it empty. */\nconst DEFAULT_SILENCE_MS = 900;\n/**\n * After this much speech a pause may be {@link LONG_PAUSE_FACTOR} times as long before the utterance ends: whoever\n * talks that long is dictating a request and pauses to think, while \"Licht an\" should still close quickly. Field\n * report 2026-11-03: \"Я хочу, чтоб ты создал задачу, чтобы утром говорить мне…\" was cut off right there.\n */\nconst LONG_REQUEST_MS = 2500;\nconst LONG_PAUSE_FACTOR = 1.5;\n/** Silence threshold as a share of the speech threshold — keeps the default pair 250/150. */\nconst SILENCE_SHARE = 0.6;\n/**\n * A dropped utterance whose loudest frame reached this share of the speech threshold was probably a voice\n * that was too quiet, not an empty room — worth telling the user where to lower the threshold.\n */\nconst QUIET_VOICE_SHARE = 0.4;\n\n/** The configured speech threshold of a device, or 0 for the default. Table cells may arrive as strings. */\nexport function speechLevelOf(device: EsphomeDevice): number {\n const level = Math.round(Number(device.speechLevel));\n return Number.isFinite(level) && level > 0 ? level : 0;\n}\n\nexport interface EsphomeDevice {\n /** Host name or IP of the satellite. */\n ip: string;\n /** API port; empty/0 → 6053. */\n port?: number;\n /** API password, if one is configured on the device. */\n password?: string;\n /** Room this satellite belongs to (drives `satellites.<room>.*` and the answer context). */\n room?: string;\n /**\n * Lowest RMS that counts as speech on this device (empty/0 → {@link DEFAULT_SPEECH_LEVEL}). Microphone\n * levels differ a lot: a ThirdReality speaker hears normal speech at RMS 350–470, a phone running a\n * satellite app at under 200, so one fixed floor cannot fit both.\n */\n speechLevel?: number | string | null;\n}\n\nexport interface EsphomeSatellitesOptions {\n devices: EsphomeDevice[];\n /** ISO-639-1 language hint for STT/TTS ('' = auto/provider default). */\n language: string;\n stt: SttEngine;\n tts: TtsEngine;\n /**\n * Produce the assistant reply for a transcribed utterance. `audioMs` is the length of the recording,\n * `vad` what the voice activity detection made of it (both for the request journal).\n */\n answer: (\n question: string,\n ctx: {\n device: string;\n room: string;\n wakeWord: string;\n sttMs: number;\n audioMs: number;\n vad: VadReport;\n },\n ) => Promise<string>;\n /** The reply was synthesised: how long that took and how long it plays (for the request journal). */\n onSpoken?: (device: string, info: { ms: number; audioMs: number }) => void;\n /** Optional STT vocabulary hints (device/room names) to bias recognition; re-read per utterance. */\n getHints?: () => string[] | Promise<string[]>;\n /** Where spoken replies are published for the device to fetch. */\n media: MediaHost;\n /**\n * Host the device should call back on. Empty → the local address of our socket to that device,\n * which is automatically the right interface on a multi-homed host.\n */\n mediaHost?: string;\n /** Silence that ends an utterance, in ms ('' / 0 → the detector default). */\n silenceMs?: number;\n log: ioBroker.Logger;\n /** Notified on every satellite state transition (idle/listening/processing/speaking/offline). */\n onStatus?: (device: string, room: string, state: SatelliteState | 'offline') => void;\n /** Notified with the device's wake-word configuration, on connect and after every change. */\n onWakeWords?: (device: string, room: string, config: WakeWordConfig) => void;\n /** Notified once per connect with everything the device exposes besides the voice pipeline. */\n onEntities?: (device: string, room: string, entities: EsphomeEntity[]) => void;\n /** Notified whenever one of those entities changes (and once per entity right after connecting). */\n onEntityState?: (device: string, room: string, entity: EsphomeEntity) => void;\n /** True when the reply asks something back, so the device should re-open its mic without a wake word. */\n expectsFollowUp?: (answer: string, device: string) => boolean;\n /**\n * A recording that never became a request: dropped as silence, or returned empty by the speech recognition.\n * For diagnosis (the adapter stores it when asked to); absent → nothing is kept.\n */\n onDiscarded?: (capture: DiscardedCapture) => void;\n /**\n * Custom wake words to offer the devices, re-read for every configuration request. The device lists\n * them next to its own and downloads one (from the media server) only when it is selected.\n */\n externalWakeWords?: () => ExternalWakeWordOffer[];\n}\n\n/** A custom wake word as offered to the devices; the files are on the media server under `path`. */\nexport interface ExternalWakeWordOffer {\n id: string;\n phrase: string;\n languages: string[];\n /** Size of the `.tflite` in bytes. */\n size: number;\n /** SHA-256 of the `.tflite`, lower-case hex. */\n sha256: string;\n /** URL path of the JSON config, e.g. `/wakeword/hey_hannah_1a2b3c4d.json`; the model sits next to it. */\n path: string;\n}\n\n/** What a device reports about its wake words: what the firmware ships plus the custom ones we offered. */\nexport interface WakeWordConfig {\n /** Ids currently listening, e.g. `['okay_nabu']`. */\n active: string[];\n /** Every wake word the device can use, with the spoken phrase and the languages it was trained on. */\n available: { id: string; phrase: string; languages: string[] }[];\n /** How many may listen at once — 2 on the ThirdReality speaker, which has a sensitivity per slot. */\n max: number;\n}\n\n/** `VoiceAssistantTimerEvent` in api.proto. */\nexport const TIMER_EVENT = { started: 0, updated: 1, cancelled: 2, finished: 3 } as const;\n\n/** One assistant timer, as the device wants to hear about it. */\nexport interface TimerEvent {\n type: keyof typeof TIMER_EVENT;\n id: string;\n name: string;\n totalSeconds: number;\n secondsLeft: number;\n /** False once it has finished or been cancelled — the device clears its ring on that. */\n active: boolean;\n}\n\n/** The slice of `MediaServer` this module needs (kept narrow so tests can pass a stub). */\nexport interface MediaHost {\n port: number;\n publish(body: Buffer, contentType?: string, ttlMs?: number): string;\n}\n\n/**\n * All configured ESPHome satellites. Owns one connection per device and offers the same surface the\n * adapter already uses for the UDP server: `devices()` and `announce()`.\n */\nexport class EsphomeSatellites {\n private readonly connections: EsphomeConnection[] = [];\n\n constructor(private readonly opts: EsphomeSatellitesOptions) {}\n\n async start(): Promise<void> {\n for (const device of this.opts.devices) {\n if (!device.ip?.trim()) {\n continue;\n }\n const connection = new EsphomeConnection(device, this.opts);\n this.connections.push(connection);\n connection.start();\n }\n if (!this.connections.length) {\n this.opts.log.warn('ESPHome satellites enabled, but no device addresses are configured.');\n }\n return Promise.resolve();\n }\n\n async stop(): Promise<void> {\n await Promise.all(this.connections.map(c => c.stop()));\n this.connections.length = 0;\n }\n\n /** Device names of the satellites currently connected. */\n devices(): string[] {\n return this.connections.filter(c => c.online).map(c => c.deviceName);\n }\n\n /** Wake-word configuration of one connected satellite, or null if it is unknown/offline. */\n wakeWords(device: string): WakeWordConfig | null {\n return this.connections.find(c => c.online && c.deviceName === device)?.wakeWordConfig ?? null;\n }\n\n /**\n * Choose which wake words listen on one satellite. Ids come from {@link WakeWordConfig.available};\n * the device stores the selection itself, so it survives a restart of the adapter.\n */\n setWakeWords(device: string, ids: string[]): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n if (!target) {\n return false;\n }\n target.setWakeWords(ids);\n return true;\n }\n\n /**\n * Offer the current custom wake words to every connected satellite again — after one was uploaded\n * or deleted. Each device answers with its new configuration (→ `onWakeWords`).\n */\n refreshWakeWords(): number {\n const online = this.connections.filter(c => c.online);\n online.forEach(c => c.requestWakeWordConfig());\n return online.length;\n }\n\n /** Everything one satellite exposes besides the voice pipeline (gain, noise suppression, …). */\n entities(device: string): EsphomeEntity[] {\n return this.connections.find(c => c.online && c.deviceName === device)?.entities.list() ?? [];\n }\n\n /**\n * Write one of those entities. Returns false when the satellite is offline, the entity is unknown\n * or read-only, or the value is not one the device accepts.\n */\n setEntity(device: string, objectId: string, value: unknown): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n return target ? target.setEntity(objectId, value) : false;\n }\n\n /**\n * Mirror an assistant timer onto the satellites so they can show it on their own LED ring and ring\n * it themselves. `device` null broadcasts. Returns how many devices were told.\n */\n timerEvent(device: string | null, event: TimerEvent): number {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n targets.forEach(c => c.sendTimerEvent(event));\n return targets.length;\n }\n\n /**\n * Play a ready PCM buffer on one satellite (by device name) or all of them. Returns how many\n * devices it was handed to.\n *\n * With `startConversation` the device opens its microphone as soon as the clip has played, without\n * a wake word — that is how a question gets its answer (see `PendingQuestions`).\n */\n async announce(device: string | null, pcm: Buffer, sampleRate: number, startConversation = false): Promise<number> {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n if (!targets.length) {\n return 0;\n }\n await Promise.all(\n targets.map(c =>\n c\n .announce(pcm, sampleRate, startConversation)\n .catch(e => this.opts.log.warn(`announce to ${c.deviceName} failed: ${(e as Error).message}`)),\n ),\n );\n return targets.length;\n }\n}\n\n/** State of the utterance currently being captured. */\ninterface Capture {\n chunks: Buffer[];\n detector: SpeechDetector;\n conversationId: string;\n /** Wake word that opened this capture; '' when the mic was re-opened for a follow-up/question. */\n wakeWord: string;\n timer: NodeJS.Timeout;\n closed: boolean;\n}\n\n/** One satellite: socket lifecycle, handshake, and the voice pipeline. */\nclass EsphomeConnection {\n /** Device name as reported by the device; falls back to its address until the handshake answers. */\n deviceName: string;\n online = false;\n /** Last wake-word configuration the device reported; null until the handshake answers. */\n wakeWordConfig: WakeWordConfig | null = null;\n /**\n * One entry per configuration request in flight, in send order (the device answers each one, in\n * order): the ids a preceding `SetConfiguration` asked for, or null. The read-back is the only\n * failure signal the device gives.\n */\n private configChecks: (string[] | null)[] = [];\n /** Everything else the device exposes — rebuilt from scratch on every reconnect. */\n readonly entities = new EntityRegistry();\n\n private readonly host: string;\n private readonly port: number;\n private readonly room: string;\n\n private socket: net.Socket | null = null;\n private rx: Bytes = Buffer.alloc(0);\n private pingTimer: NodeJS.Timeout | null = null;\n private reconnectTimer: NodeJS.Timeout | null = null;\n private missedPings = 0;\n private stopping = false;\n private capture: Capture | null = null;\n private playbackTimer: NodeJS.Timeout | null = null;\n private readonly password: string;\n /** Configured speech threshold, 0 = {@link DEFAULT_SPEECH_LEVEL}. */\n private readonly speechLevel: number;\n\n constructor(\n device: EsphomeDevice,\n private readonly opts: EsphomeSatellitesOptions,\n ) {\n this.host = device.ip.trim();\n this.port = device.port || DEFAULT_PORT;\n this.room = (device.room || '').trim();\n this.password = device.password || '';\n this.speechLevel = speechLevelOf(device);\n this.deviceName = this.host;\n }\n\n // ── lifecycle ───────────────────────────────────────────────────────────────────────────────────\n\n start(): void {\n this.connect();\n }\n\n async stop(): Promise<void> {\n this.stopping = true;\n this.clearTimers();\n this.abortCapture();\n const socket = this.socket;\n this.socket = null;\n if (socket) {\n try {\n socket.write(encode(new pb.DisconnectRequest()));\n } catch {\n /* socket already gone — nothing to say goodbye on */\n }\n socket.destroy();\n }\n this.setOffline();\n return Promise.resolve();\n }\n\n private connect(): void {\n if (this.stopping) {\n return;\n }\n this.rx = Buffer.alloc(0);\n this.missedPings = 0;\n // The device re-announces everything after a reconnect, and the keys are not stable across\n // reboots, so starting from a clean slate is the only safe option.\n this.entities.clear();\n this.configChecks = [];\n\n const socket = net.createConnection({ host: this.host, port: this.port }, () => {\n this.opts.log.debug(`ESPHome ${this.host}:${this.port} connected`);\n this.handshake();\n });\n this.socket = socket;\n socket.setNoDelay(true);\n\n socket.on('data', data => this.onData(data));\n socket.on('error', e => {\n // A satellite that is switched off is normal, so this is not an error-level event.\n this.opts.log.debug(`ESPHome ${this.host}: ${e.message}`);\n });\n socket.on('close', () => {\n if (this.socket === socket) {\n this.socket = null;\n this.onDisconnected();\n }\n });\n }\n\n private onDisconnected(): void {\n this.clearTimers();\n this.abortCapture();\n if (this.online) {\n this.opts.log.info(`ESPHome satellite offline: ${this.deviceName}`);\n }\n this.setOffline();\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopping || this.reconnectTimer) {\n return;\n }\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, RECONNECT_MS);\n this.reconnectTimer.unref();\n }\n\n private clearTimers(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n }\n\n private setOffline(): void {\n if (this.online) {\n this.online = false;\n this.opts.onStatus?.(this.deviceName, this.room, 'offline');\n }\n }\n\n private status(state: SatelliteState): void {\n this.opts.onStatus?.(this.deviceName, this.room, state);\n }\n\n // ── protocol plumbing ───────────────────────────────────────────────────────────────────────────\n\n private send(message: PbMessage): void {\n const socket = this.socket;\n if (!socket || socket.destroyed) {\n return;\n }\n try {\n socket.write(encode(message));\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: send failed — ${(e as Error).message}`);\n }\n }\n\n private onData(data: Buffer): void {\n this.rx = this.rx.length ? Buffer.concat([this.rx, data]) : data;\n let frames;\n try {\n ({ frames, rest: this.rx } = decode(this.rx));\n } catch (e) {\n this.opts.log.error(`ESPHome ${this.deviceName}: ${(e as Error).message}`);\n this.socket?.destroy();\n return;\n }\n for (const frame of frames) {\n if (frame.error) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: cannot parse ${frame.name}`);\n continue;\n }\n if (!frame.name || !frame.message) {\n continue; // unknown id — already skipped by its declared length\n }\n try {\n this.onMessage(frame.name, frame.message);\n } catch (e) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: ${frame.name} handler failed — ${(e as Error).message}`,\n );\n }\n }\n }\n\n private handshake(): void {\n const hello = new pb.HelloRequest();\n hello.setClientInfo('ioBroker.assistant');\n hello.setApiVersionMajor(1);\n hello.setApiVersionMinor(10);\n this.send(hello);\n\n const auth = new pb.AuthenticationRequest();\n auth.setPassword(this.password);\n this.send(auth);\n\n this.send(new pb.DeviceInfoRequest());\n // Ask for the entity announcements *before* subscribing to their states: a state message whose\n // entity we have not seen yet has nothing to attach to and is dropped.\n this.send(new pb.ListEntitiesRequest());\n this.send(new pb.SubscribeStatesRequest());\n\n const subscribe = new pb.SubscribeVoiceAssistantRequest();\n subscribe.setSubscribe(true);\n subscribe.setFlags(SUBSCRIBE_API_AUDIO);\n this.send(subscribe);\n\n this.requestWakeWordConfig();\n\n this.pingTimer = setInterval(() => {\n if (++this.missedPings > 2) {\n this.opts.log.info(`ESPHome ${this.deviceName}: no reply to keepalive — reconnecting`);\n this.socket?.destroy();\n return;\n }\n this.send(new pb.PingRequest());\n }, PING_INTERVAL_MS);\n this.pingTimer.unref();\n }\n\n private onMessage(name: string, message: unknown): void {\n switch (name) {\n case 'HelloResponse': {\n const hello = message as HelloResponse;\n this.opts.log.debug(\n `ESPHome ${this.host}: ${hello.getServerInfo()} (API ${hello.getApiVersionMajor()}.${hello.getApiVersionMinor()})`,\n );\n return;\n }\n\n case 'AuthenticationResponse':\n if ((message as AuthenticationResponse).getInvalidPassword()) {\n this.opts.log.error(\n `ESPHome ${this.host}: API password rejected — check the device's password in the settings.`,\n );\n this.socket?.destroy();\n }\n return;\n\n case 'DeviceInfoResponse': {\n const info = message as DeviceInfoResponse;\n this.deviceName = info.getName() || this.host;\n this.online = true;\n this.opts.log.info(\n `ESPHome satellite online: ${this.deviceName} (${info.getModel()}) @ ${this.host}${this.room ? `, room ${this.room}` : ''}`,\n );\n this.status('idle');\n return;\n }\n\n case 'VoiceAssistantConfigurationResponse': {\n const config = message as VoiceAssistantConfigurationResponse;\n // A custom wake word the device already downloaded is listed twice: once from its own\n // model folder and once from our offer.\n const seen = new Set<string>();\n this.wakeWordConfig = {\n active: config.getActiveWakeWordsList(),\n available: config\n .getAvailableWakeWordsList()\n .map(w => ({\n id: w.getId(),\n phrase: w.getWakeWord(),\n languages: w.getTrainedLanguagesList(),\n }))\n .filter(w => !seen.has(w.id) && !!seen.add(w.id)),\n max: config.getMaxActiveWakeWords(),\n };\n const active = this.wakeWordConfig.active.join(', ') || '(none active)';\n const available = this.wakeWordConfig.available.map(w => w.id);\n this.opts.log.info(\n `ESPHome ${this.deviceName}: wake words ${active}${available.length ? ` (available: ${available.join(', ')})` : ''}`,\n );\n const requested = this.configChecks.shift();\n const missing = requested?.filter(id => !this.wakeWordConfig?.active.includes(id)) ?? [];\n if (missing.length) {\n // The device answers a model it cannot fetch or load with silence, not an error.\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: could not activate wake word(s) ${missing.join(', ')}. ` +\n `For an uploaded wake word the device must reach ${this.mediaUrl('/')} and the model ` +\n `must be a microWakeWord .tflite.`,\n );\n }\n this.opts.onWakeWords?.(this.deviceName, this.room, this.wakeWordConfig);\n return;\n }\n\n case 'VoiceAssistantRequest':\n this.onPipelineRequest(message as VoiceAssistantRequestMsg);\n return;\n\n case 'VoiceAssistantAudio':\n this.onAudio(message as VoiceAssistantAudio);\n return;\n\n case 'VoiceAssistantAnnounceFinished':\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: playback finished (success=${(message as VoiceAssistantAnnounceFinished).getSuccess()})`,\n );\n this.status('idle');\n return;\n\n case 'PingRequest':\n this.send(new pb.PingResponse());\n return;\n\n case 'PingResponse':\n this.missedPings = 0;\n return;\n\n case 'GetTimeRequest': {\n // The device has no RTC and syncs its clock from us — its timer ring depends on it.\n const time = new pb.GetTimeResponse();\n time.setEpochSeconds(Math.floor(Date.now() / 1000));\n this.send(time);\n return;\n }\n\n case 'DisconnectRequest':\n this.send(new pb.DisconnectResponse());\n this.socket?.destroy();\n return;\n\n case 'ListEntitiesDoneResponse': {\n const list = this.entities.list();\n if (list.length) {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: ${list.length} entities — ${list.map(e => e.objectId).join(', ')}`,\n );\n }\n this.opts.onEntities?.(this.deviceName, this.room, list);\n return;\n }\n\n default:\n if (isListEntityMessage(name)) {\n this.entities.addFromAnnouncement(name, message);\n } else if (isEntityStateMessage(name)) {\n const entity = this.entities.applyState(name, message);\n if (entity) {\n this.opts.onEntityState?.(this.deviceName, this.room, entity);\n }\n }\n return;\n }\n }\n\n /** Write one of the device's entities. See {@link EsphomeSatellites.setEntity}. */\n setEntity(objectId: string, value: unknown): boolean {\n const message = this.entities.command(objectId, value);\n if (!message) {\n const entity = this.entities.get(objectId);\n const known = this.entities\n .list()\n .map(e => e.objectId)\n .join(', ');\n const options = entity?.options ? ` (one of: ${entity.options.join(', ')})` : '';\n this.opts.log.warn(\n entity\n ? `ESPHome ${this.deviceName}: ${JSON.stringify(value)} is not a value \"${objectId}\" accepts${options}`\n : `ESPHome ${this.deviceName}: no entity \"${objectId}\" (have: ${known})`,\n );\n return false;\n }\n this.send(message);\n this.opts.log.debug(`ESPHome ${this.deviceName}: ${objectId} := ${JSON.stringify(value)}`);\n return true;\n }\n\n /** Push one assistant timer to the device so it can show and ring it itself. */\n sendTimerEvent(event: TimerEvent): void {\n const message = new pb.VoiceAssistantTimerEventResponse();\n message.setEventType(TIMER_EVENT[event.type]);\n message.setTimerId(event.id);\n message.setName(event.name);\n message.setTotalSeconds(Math.max(0, Math.round(event.totalSeconds)));\n message.setSecondsLeft(Math.max(0, Math.round(event.secondsLeft)));\n message.setIsActive(event.active);\n this.send(message);\n }\n\n // ── voice pipeline ──────────────────────────────────────────────────────────────────────────────\n\n private onPipelineRequest(request: VoiceAssistantRequestMsg): void {\n if (!request.getStart()) {\n this.abortCapture();\n return;\n }\n if (this.capture) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: pipeline already running — ignoring wake`);\n return;\n }\n\n const phrase = request.getWakeWordPhrase();\n this.opts.log.debug(`ESPHome ${this.deviceName}: wake \"${phrase || '(follow-up)'}\"`);\n\n const response = new pb.VoiceAssistantResponse();\n response.setPort(0); // 0 = stream the audio over this socket\n response.setError(false);\n this.send(response);\n\n const timer = setTimeout(() => {\n this.opts.log.warn(`ESPHome ${this.deviceName}: no end of speech within ${PIPELINE_TIMEOUT_MS} ms`);\n void this.finishCapture();\n }, PIPELINE_TIMEOUT_MS);\n timer.unref();\n\n const silenceMs = this.opts.silenceMs || DEFAULT_SILENCE_MS;\n this.capture = {\n chunks: [],\n detector: new SpeechDetector({\n sampleRate: MIC_SAMPLE_RATE,\n silenceMs,\n longSilenceMs: Math.round(silenceMs * LONG_PAUSE_FACTOR),\n longAfterMs: LONG_REQUEST_MS,\n // Microphone gain differs wildly between these devices, so fixed RMS levels do not\n // travel: derive them from the room instead, once the wake chirp is out of the way.\n skipMs: WAKE_CHIRP_MS,\n adaptive: true,\n startTimeoutMs: phrase ? NO_SPEECH_MS : NO_SPEECH_FOLLOW_UP_MS,\n minStartLevel: this.speechLevel || DEFAULT_SPEECH_LEVEL,\n minEndLevel: Math.round((this.speechLevel || DEFAULT_SPEECH_LEVEL) * SILENCE_SHARE),\n }),\n conversationId: request.getConversationId(),\n wakeWord: phrase || '',\n timer,\n closed: false,\n };\n\n this.status('listening');\n this.event(EVENT.RUN_START);\n if (phrase) {\n this.event(EVENT.WAKE_WORD_END, { wake_word_phrase: phrase });\n }\n this.event(EVENT.STT_START);\n this.event(EVENT.STT_VAD_START);\n }\n\n private onAudio(audio: VoiceAssistantAudio): void {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n const chunk = Buffer.from(audio.getData_asU8());\n if (chunk.length) {\n capture.chunks.push(chunk);\n const end = capture.detector.push(chunk);\n if (end) {\n void this.finishCapture(end);\n return;\n }\n }\n if (audio.getEnd()) {\n void this.finishCapture();\n }\n }\n\n /** Hand a recording that never became a request to {@link EsphomeSatellitesOptions.onDiscarded}. */\n private discard(capture: Capture, pcm: Bytes, reason: DiscardReason, vad: VadReport): void {\n if (!this.opts.onDiscarded || !pcm.length) {\n return;\n }\n try {\n this.opts.onDiscarded({\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n reason,\n pcm,\n sampleRate: MIC_SAMPLE_RATE,\n // The store writes its own level profile next to it.\n vad: { ...vad, levels: undefined },\n });\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: keeping the recording failed — ${(e as Error).message}`);\n }\n }\n\n /** Stop the mic stream and run STT → answer → TTS for what was captured. */\n private async finishCapture(end?: SpeechEnd): Promise<void> {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n capture.closed = true;\n this.capture = null;\n clearTimeout(capture.timer);\n\n // Send this first: the device streams until it sees it.\n this.event(EVENT.STT_VAD_END);\n\n const pcm = Buffer.concat(capture.chunks);\n const seconds = pcm.length / 2 / MIC_SAMPLE_RATE;\n const vad: VadReport = { ...capture.detector.report(), end: end ?? 'stream' };\n const startedAt =\n vad.speechStartMs !== undefined ? `, speech from ${(vad.speechStartMs / 1000).toFixed(2)} s` : '';\n const endedBy = vad.end === 'speech-end' ? `ended after ${vad.silenceMs} ms of silence` : `ended by ${vad.end}`;\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: captured ${seconds.toFixed(2)} s ` +\n `(peak RMS ${vad.peak}, noise floor ${vad.noiseFloor}, speech/silence gate ${vad.startLevel}/${vad.endLevel}` +\n `${startedAt}, ${endedBy})`,\n );\n\n if (!pcm.length || !capture.detector.sawSpeech) {\n const loudest = Math.round(capture.detector.analysedPeak);\n const gate = Math.round(capture.detector.startLevel);\n if (pcm.length && loudest >= gate * QUIET_VOICE_SHARE) {\n // Not an empty room: something came close to the gate. Typically a microphone that is quieter\n // than the default threshold expects (a phone) — say where to change it.\n this.opts.log.info(\n `ESPHome ${this.deviceName}: dropped the utterance — the loudest sound (RMS ${loudest}) stayed ` +\n `below the speech threshold ${gate}. If somebody spoke, lower \"Speech threshold\" for this ` +\n `device in the ESPHome satellites table (Voice tab), e.g. to ${Math.max(\n 30,\n Math.round((loudest * 0.6) / 10) * 10,\n )}.`,\n );\n } else {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: nothing but silence${\n end === 'no-speech' ? ' (nobody started speaking)' : ''\n } — dropping the utterance`,\n );\n }\n this.discard(capture, pcm, end === 'no-speech' ? 'no-speech' : 'silence', vad);\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.status('processing');\n try {\n const hints = this.opts.getHints ? await this.opts.getHints() : undefined;\n const speech = prepareForStt(pcm, MIC_SAMPLE_RATE, { startMs: vad.speechStartMs, endLevel: vad.endLevel });\n vad.stt = { fromMs: speech.fromMs, toMs: speech.toMs, gain: speech.gain };\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: speech recognition gets ${(speech.fromMs / 1000).toFixed(2)}–` +\n `${(speech.toMs / 1000).toFixed(2)} s${speech.gain > 1 ? `, amplified ×${speech.gain}` : ''}`,\n );\n const sttStart = Date.now();\n const text = (\n await this.opts.stt.transcribe(speech.pcm, MIC_SAMPLE_RATE, this.opts.language, hints)\n ).trim();\n const sttMs = Date.now() - sttStart;\n this.event(EVENT.STT_END, { text });\n if (!text) {\n // Speech was heard, so the user waits for an answer: say why none comes. Typically a word in another\n // language than the STT listens in (a German \"Ja\" to a Russian STT comes back empty).\n this.opts.log.info(\n `Voice Q (${this.deviceName}): (empty) — speech recognition returned no text for ${seconds.toFixed(1)} s of audio`,\n );\n this.discard(capture, pcm, 'empty-stt', vad);\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.event(EVENT.INTENT_START);\n const answer = (\n await this.opts.answer(text, {\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n sttMs,\n audioMs: Math.round(seconds * 1000),\n // Numbers only, no audio: shows where a request was cut off without keeping recordings.\n vad: { ...vad, levels: levelProfile(pcm, MIC_SAMPLE_RATE) },\n })\n ).trim();\n const followUp = !!answer && this.opts.expectsFollowUp?.(answer, this.deviceName) === true;\n this.event(EVENT.INTENT_END, followUp ? { continue_conversation: '1' } : undefined);\n\n if (!answer) {\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n const ttsStart = Date.now();\n const { pcm: ttsPcm, sampleRate } = await this.opts.tts.synthesize(answer, this.opts.language);\n this.opts.onSpoken?.(this.deviceName, {\n ms: Date.now() - ttsStart,\n audioMs: (ttsPcm.length / 2 / sampleRate) * 1000,\n });\n const url = this.publish(ttsPcm, sampleRate);\n\n this.status('speaking');\n this.event(EVENT.TTS_START, { text: answer });\n this.event(EVENT.TTS_END, { url });\n this.event(EVENT.RUN_END, followUp ? { continue_conversation: '1' } : undefined);\n this.armPlaybackTimeout();\n } catch (e) {\n const reason = (e as Error).message;\n this.opts.log.error(`ESPHome ${this.deviceName}: voice pipeline failed — ${reason}`);\n // Without this the device stays ducked and its LED keeps spinning.\n this.event(EVENT.ERROR, { code: 'pipeline-failed', message: reason });\n this.event(EVENT.RUN_END);\n this.status('idle');\n }\n }\n\n /**\n * Select which wake words listen on this device. Unknown ids are dropped and the list is trimmed\n * to what the firmware allows, because the device answers a rejected list with silence rather than\n * an error — better to send something valid and report what we changed.\n *\n * The device persists the selection and echoes it back in a `VoiceAssistantConfigurationResponse`,\n * so we ask for one instead of assuming the write took.\n */\n setWakeWords(ids: string[]): void {\n const known = this.wakeWordConfig?.available.map(w => w.id) ?? [];\n const wanted = [...new Set(ids.map(i => i.trim()).filter(Boolean))];\n const unknown = wanted.filter(i => !known.includes(i));\n if (unknown.length) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: unknown wake word(s) ${unknown.join(', ')} — ignored. ` +\n `Available: ${known.join(', ') || '(none reported)'}`,\n );\n }\n let valid = wanted.filter(i => known.includes(i));\n const max = this.wakeWordConfig?.max || 0;\n if (max > 0 && valid.length > max) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: device allows ${max} active wake word(s), keeping ${valid\n .slice(0, max)\n .join(', ')}`,\n );\n valid = valid.slice(0, max);\n }\n\n const message = new pb.VoiceAssistantSetConfiguration();\n message.setActiveWakeWordsList(valid);\n this.send(message);\n this.opts.log.info(`ESPHome ${this.deviceName}: wake words set to ${valid.join(', ') || '(none)'}`);\n // Read back, so the reported state is the device's and not our guess. The device resolves a\n // custom wake word (and downloads it) while handling the SetConfiguration, so this answer\n // already tells whether that worked.\n this.requestWakeWordConfig(valid);\n }\n\n /**\n * Ask the device for its wake-word configuration. The request always carries the current custom\n * wake words: the firmware replaces its list of offered models with every request, so one sent\n * without them would make an uploaded wake word vanish from the device until the next offer.\n *\n * @param check ids a SetConfiguration just asked for — warned about if the answer lacks them\n */\n requestWakeWordConfig(check: string[] | null = null): void {\n const request = new pb.VoiceAssistantConfigurationRequest();\n const offers = this.opts.externalWakeWords?.() ?? [];\n request.setExternalWakeWordsList(\n offers.map(offer => {\n const w = new pb.VoiceAssistantExternalWakeWord();\n w.setId(offer.id);\n w.setWakeWord(offer.phrase);\n w.setTrainedLanguagesList(offer.languages);\n w.setModelType('micro');\n w.setModelSize(offer.size);\n w.setModelHash(offer.sha256);\n w.setUrl(this.mediaUrl(offer.path));\n return w;\n }),\n );\n // A device that never answers must not let this grow without bound.\n if (this.configChecks.length < 16) {\n this.configChecks.push(check);\n }\n this.send(request);\n }\n\n /** Drop a half-finished capture (device went away, or told us to stop). */\n private abortCapture(): void {\n const capture = this.capture;\n if (!capture) {\n return;\n }\n this.capture = null;\n capture.closed = true;\n clearTimeout(capture.timer);\n }\n\n // ── announcements ───────────────────────────────────────────────────────────────────────────────\n\n /**\n * Speak a ready PCM buffer on this device (timers, alarms, `tts.text`, jingles). With\n * `startConversation` the microphone opens once the clip has played, to catch the answer to a question.\n */\n announce(pcm: Buffer, sampleRate: number, startConversation = false): Promise<void> {\n if (!this.online || !pcm.length) {\n return Promise.resolve();\n }\n const request = new pb.VoiceAssistantAnnounceRequest();\n request.setMediaId(this.publish(pcm, sampleRate));\n request.setText('');\n request.setPreannounceMediaId('');\n // Needs feature flag 32 (START_CONVERSATION); on a device without it the clip still plays and\n // only the microphone stays shut, so there is nothing to guard here.\n request.setStartConversation(startConversation);\n this.status('speaking');\n this.send(request);\n this.armPlaybackTimeout();\n return Promise.resolve();\n }\n\n /**\n * Publish PCM as a WAV on the media server and build the URL the device should fetch.\n *\n * The host matters: on a multi-homed ioBroker machine the device can usually only reach one of the\n * addresses, and the local end of *this* socket is by definition one it can talk to.\n */\n private publish(pcm: Buffer, sampleRate: number): string {\n return this.mediaUrl(this.opts.media.publish(pcmToWav(pcm, sampleRate), 'audio/wav'));\n }\n\n /** Absolute URL of a path on the media server, with a host this device can reach. */\n private mediaUrl(path: string): string {\n const local = (this.socket?.localAddress || '').replace(/^::ffff:/, '');\n const host = this.opts.mediaHost?.trim() || local || '127.0.0.1';\n // An IPv6 literal has to be bracketed in a URL.\n const authority = host.includes(':') ? `[${host}]` : host;\n return `http://${authority}:${this.opts.media.port}${path}`;\n }\n\n /** Fall back to `idle` if the device never reports that it finished playing. */\n private armPlaybackTimeout(): void {\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n }\n this.playbackTimer = setTimeout(() => {\n this.playbackTimer = null;\n this.status('idle');\n }, PLAYBACK_TIMEOUT_MS);\n this.playbackTimer.unref();\n }\n\n private event(type: number, data?: Record<string, string>): void {\n const message = new pb.VoiceAssistantEventResponse();\n message.setEventType(type);\n const items: VoiceAssistantEventData[] = [];\n for (const [name, value] of Object.entries(data || {})) {\n const item = new pb.VoiceAssistantEventData();\n item.setName(name);\n item.setValue(value);\n items.push(item);\n }\n message.setDataList(items);\n this.send(message);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"esphome.js","sourceRoot":"","sources":["../../../src/lib/voice/esphome.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESPHome voice satellites — the fourth transport, next to the UDP server (`voiceServer.ts`) and the\n * Wyoming endpoint (`wyoming.ts`).\n *\n * Unlike those two, this one is a **client**: an ESPHome voice satellite is a *server* that waits on\n * TCP 6053 for the home-automation controller to connect and drive it. We therefore dial out to each\n * configured device and play the role Home Assistant normally plays. That covers the ThirdReality\n * Voice & Music Assistant (Dev Edition), the Home Assistant Voice PE and any box running\n * OHF-Voice/linux-voice-assistant.\n *\n * Flow for one utterance (verified against the firmware's `src/satellite/Satellite.cpp`):\n *\n * device → VoiceAssistantRequest{start, wake_word_phrase} wake word fired on the device\n * we → VoiceAssistantResponse{port:0} 0 = audio over the API, no UDP\n * we → RUN_START, WAKE_WORD_END, STT_START, STT_VAD_START\n * device → VoiceAssistantAudio … 16 kHz mono s16le, *until told to stop*\n * we → STT_VAD_END ← the device has no VAD of its own\n * we → STT_END{text}, INTENT_START, INTENT_END, TTS_START{text}, TTS_END{url}, RUN_END\n * device → fetches the URL over HTTP, plays it, VoiceAssistantAnnounceFinished\n *\n * Two consequences shape this file: end-of-speech detection is ours (`vad.ts`), and the spoken reply\n * has to be reachable over HTTP (`mediaServer.ts`) rather than streamed down the socket.\n */\nimport * as net from 'node:net';\nimport {\n pb,\n encode,\n decode,\n type PbMessage,\n type AuthenticationResponse,\n type DeviceInfoResponse,\n type HelloResponse,\n type VoiceAssistantAnnounceFinished,\n type VoiceAssistantAudio,\n type VoiceAssistantConfigurationResponse,\n type VoiceAssistantEventData,\n type VoiceAssistantRequestMsg,\n} from './esphomeProto';\nimport { EntityRegistry, isEntityStateMessage, isListEntityMessage, type EsphomeEntity } from './esphomeEntities';\nimport { SpeechDetector, type CaptureEnd, type Bytes, type VadReport } from './vad';\nimport { prepareForStt } from './speechAudio';\nimport { pcmToWav } from './stt';\nimport { levelProfile, type DiscardedCapture, type DiscardReason } from './captures';\nimport type { SttEngine } from './stt';\nimport type { TtsEngine } from './tts';\nimport type { SatelliteState } from './protocol';\n\n/** `VoiceAssistantEvent` (api.proto) — the subset we emit. */\nconst EVENT = {\n ERROR: 0,\n RUN_START: 1,\n RUN_END: 2,\n STT_START: 3,\n STT_END: 4,\n INTENT_START: 5,\n INTENT_END: 6,\n TTS_START: 7,\n TTS_END: 8,\n WAKE_WORD_END: 10,\n STT_VAD_START: 11,\n STT_VAD_END: 12,\n} as const;\n\n/** `SubscribeVoiceAssistantRequest.flags`: audio over the API socket instead of a UDP side channel. */\nconst SUBSCRIBE_API_AUDIO = 1;\n/** ESPHome voice satellites always capture at 16 kHz mono 16-bit. */\nconst MIC_SAMPLE_RATE = 16000;\n/**\n * Leading audio to keep out of the end-of-speech analysis. These devices acknowledge the wake word\n * with a chirp through their own speaker and hear it back on their own microphone, far louder than\n * the speech that follows. Without this the chirp alone satisfies the detector's speech test — so\n * `sawSpeech` stops meaning \"somebody said something\" and a false wake is billed to STT — and it\n * drags the noise floor up with it.\n *\n * Measured over four captures from a ThirdReality Voice & Music Assistant (RMS per 50 ms, remarkably\n * repeatable across all four):\n *\n * ms 0 50 100 150 200 250 300 350 400 450 500 550 600\n * rms ~20 ~900 ~11k ~19k ~14k ~1.3k ~990 ~780 ~610 ~370 ~330 ~165 ~180\n *\n * so the chirp is still above the gate at 500 ms and only clears it around 550–600. 600 ms is the\n * smallest value that rejects a chirp-only wake while still detecting the speech in every one of the\n * four captures, with no change to when the utterance closes. Only the analysis skips it: STT gets the audio\n * from 400 ms before the detected speech (`speechAudio.ts`), so a speaker who starts inside this window loses\n * nothing but a late gate open.\n */\nconst WAKE_CHIRP_MS = 600;\n/** Default API port of an ESPHome device. */\nconst DEFAULT_PORT = 6053;\n/** Keepalive interval; two missed replies count as a dead link. */\nconst PING_INTERVAL_MS = 20_000;\n/** Wait between reconnect attempts. Devices reboot, and a Wi-Fi speaker drops off now and then. */\nconst RECONNECT_MS = 10_000;\n/** Give up on a pipeline that produced no audio at all (device wedged mid-stream). */\nconst PIPELINE_TIMEOUT_MS = 30_000;\n/** How long we wait for the device to report that it finished playing before forcing it back to idle. */\nconst PLAYBACK_TIMEOUT_MS = 60_000;\n/**\n * Default lowest RMS that counts as speech (the noise floor can only raise the gate above it). Fits the\n * ThirdReality speaker; quieter microphones get their own value per device ({@link EsphomeDevice.speechLevel}).\n */\nexport const DEFAULT_SPEECH_LEVEL = 250;\n/**\n * Close the microphone when nobody has started speaking this long after the wake word. Without it a false wake\n * kept the stream open for the full 12 s cap: on a ThirdReality speaker with a custom wake word that was ten\n * times in one night, each 12.6 s of an open mic (and of music ducked, if any played).\n */\nconst NO_SPEECH_MS = 5000;\n/** The same after a follow-up question re-opened the mic: the user may need a moment to think. */\nconst NO_SPEECH_FOLLOW_UP_MS = 8000;\n/** Silence that ends an utterance when the configuration leaves it empty. */\nconst DEFAULT_SILENCE_MS = 900;\n/**\n * After this much speech a pause may be {@link LONG_PAUSE_FACTOR} times as long before the utterance ends: whoever\n * talks that long is dictating a request and pauses to think, while \"Licht an\" should still close quickly. Field\n * report 2026-11-03: \"Я хочу, чтоб ты создал задачу, чтобы утром говорить мне…\" was cut off right there.\n */\nconst LONG_REQUEST_MS = 2500;\nconst LONG_PAUSE_FACTOR = 1.5;\n/**\n * Sound needed before a loud moment counts as speech. On vitanova (2026-11-03) a single 100 ms sound at RMS 197\n * after a false wake opened the gate (threshold 150), and Whisper made \"и все просят.\" of the amplified silence\n * around it. The shortest real answer, \"да\", lasts about 250 ms.\n */\nconst MIN_SPEECH_MS = 200;\n/**\n * Close the utterance when the device stops sending audio without ending the stream. Seen on a ThirdReality\n * speaker: the audio stopped after 2.7 s, and the satellite showed \"listening\" for the 30 s of\n * {@link PIPELINE_TIMEOUT_MS}. A device streams every few tens of ms while the mic is open.\n */\nconst STREAM_STALL_MS = 2000;\n/** The same before the first audio arrives: a device needs a moment to open the microphone. */\nconst STREAM_START_MS = 5000;\n/** How often a running capture checks for a stalled stream. */\nconst STALL_CHECK_MS = 250;\n/** Silence threshold as a share of the speech threshold — keeps the default pair 250/150. */\nconst SILENCE_SHARE = 0.6;\n/**\n * A dropped utterance whose loudest frame reached this share of the speech threshold was probably a voice\n * that was too quiet, not an empty room — worth telling the user where to lower the threshold.\n */\nconst QUIET_VOICE_SHARE = 0.4;\n\n/** The configured speech threshold of a device, or 0 for the default. Table cells may arrive as strings. */\nexport function speechLevelOf(device: EsphomeDevice): number {\n const level = Math.round(Number(device.speechLevel));\n return Number.isFinite(level) && level > 0 ? level : 0;\n}\n\nexport interface EsphomeDevice {\n /** Host name or IP of the satellite. */\n ip: string;\n /** API port; empty/0 → 6053. */\n port?: number;\n /** API password, if one is configured on the device. */\n password?: string;\n /** Room this satellite belongs to (drives `satellites.<room>.*` and the answer context). */\n room?: string;\n /**\n * Lowest RMS that counts as speech on this device (empty/0 → {@link DEFAULT_SPEECH_LEVEL}). Microphone\n * levels differ a lot: a ThirdReality speaker hears normal speech at RMS 350–470, a phone running a\n * satellite app at under 200, so one fixed floor cannot fit both.\n */\n speechLevel?: number | string | null;\n}\n\nexport interface EsphomeSatellitesOptions {\n devices: EsphomeDevice[];\n /** ISO-639-1 language hint for STT/TTS ('' = auto/provider default). */\n language: string;\n stt: SttEngine;\n tts: TtsEngine;\n /**\n * Produce the assistant reply for a transcribed utterance. `audioMs` is the length of the recording,\n * `vad` what the voice activity detection made of it (both for the request journal).\n */\n answer: (\n question: string,\n ctx: {\n device: string;\n room: string;\n wakeWord: string;\n sttMs: number;\n audioMs: number;\n vad: VadReport;\n },\n ) => Promise<string>;\n /** The reply was synthesised: how long that took and how long it plays (for the request journal). */\n onSpoken?: (device: string, info: { ms: number; audioMs: number }) => void;\n /** Optional STT vocabulary hints (device/room names) to bias recognition; re-read per utterance. */\n getHints?: () => string[] | Promise<string[]>;\n /** Where spoken replies are published for the device to fetch. */\n media: MediaHost;\n /**\n * Host the device should call back on. Empty → the local address of our socket to that device,\n * which is automatically the right interface on a multi-homed host.\n */\n mediaHost?: string;\n /** Silence that ends an utterance, in ms ('' / 0 → the detector default). */\n silenceMs?: number;\n log: ioBroker.Logger;\n /** Notified on every satellite state transition (idle/listening/processing/speaking/offline). */\n onStatus?: (device: string, room: string, state: SatelliteState | 'offline') => void;\n /** Notified with the device's wake-word configuration, on connect and after every change. */\n onWakeWords?: (device: string, room: string, config: WakeWordConfig) => void;\n /** Notified once per connect with everything the device exposes besides the voice pipeline. */\n onEntities?: (device: string, room: string, entities: EsphomeEntity[]) => void;\n /** Notified whenever one of those entities changes (and once per entity right after connecting). */\n onEntityState?: (device: string, room: string, entity: EsphomeEntity) => void;\n /** True when the reply asks something back, so the device should re-open its mic without a wake word. */\n expectsFollowUp?: (answer: string, device: string) => boolean;\n /**\n * A recording that never became a request: dropped as silence, or returned empty by the speech recognition.\n * For diagnosis (the adapter stores it when asked to); absent → nothing is kept.\n */\n onDiscarded?: (capture: DiscardedCapture) => void;\n /**\n * Custom wake words to offer the devices, re-read for every configuration request. The device lists\n * them next to its own and downloads one (from the media server) only when it is selected.\n */\n externalWakeWords?: () => ExternalWakeWordOffer[];\n}\n\n/** A custom wake word as offered to the devices; the files are on the media server under `path`. */\nexport interface ExternalWakeWordOffer {\n id: string;\n phrase: string;\n languages: string[];\n /** Size of the `.tflite` in bytes. */\n size: number;\n /** SHA-256 of the `.tflite`, lower-case hex. */\n sha256: string;\n /** URL path of the JSON config, e.g. `/wakeword/hey_hannah_1a2b3c4d.json`; the model sits next to it. */\n path: string;\n}\n\n/** What a device reports about its wake words: what the firmware ships plus the custom ones we offered. */\nexport interface WakeWordConfig {\n /** Ids currently listening, e.g. `['okay_nabu']`. */\n active: string[];\n /** Every wake word the device can use, with the spoken phrase and the languages it was trained on. */\n available: { id: string; phrase: string; languages: string[] }[];\n /** How many may listen at once — 2 on the ThirdReality speaker, which has a sensitivity per slot. */\n max: number;\n}\n\n/** `VoiceAssistantTimerEvent` in api.proto. */\nexport const TIMER_EVENT = { started: 0, updated: 1, cancelled: 2, finished: 3 } as const;\n\n/** One assistant timer, as the device wants to hear about it. */\nexport interface TimerEvent {\n type: keyof typeof TIMER_EVENT;\n id: string;\n name: string;\n totalSeconds: number;\n secondsLeft: number;\n /** False once it has finished or been cancelled — the device clears its ring on that. */\n active: boolean;\n}\n\n/** The slice of `MediaServer` this module needs (kept narrow so tests can pass a stub). */\nexport interface MediaHost {\n port: number;\n publish(body: Buffer, contentType?: string, ttlMs?: number): string;\n}\n\n/**\n * All configured ESPHome satellites. Owns one connection per device and offers the same surface the\n * adapter already uses for the UDP server: `devices()` and `announce()`.\n */\nexport class EsphomeSatellites {\n private readonly connections: EsphomeConnection[] = [];\n\n constructor(private readonly opts: EsphomeSatellitesOptions) {}\n\n async start(): Promise<void> {\n for (const device of this.opts.devices) {\n if (!device.ip?.trim()) {\n continue;\n }\n const connection = new EsphomeConnection(device, this.opts);\n this.connections.push(connection);\n connection.start();\n }\n if (!this.connections.length) {\n this.opts.log.warn('ESPHome satellites enabled, but no device addresses are configured.');\n }\n return Promise.resolve();\n }\n\n async stop(): Promise<void> {\n await Promise.all(this.connections.map(c => c.stop()));\n this.connections.length = 0;\n }\n\n /** Device names of the satellites currently connected. */\n devices(): string[] {\n return this.connections.filter(c => c.online).map(c => c.deviceName);\n }\n\n /** Wake-word configuration of one connected satellite, or null if it is unknown/offline. */\n wakeWords(device: string): WakeWordConfig | null {\n return this.connections.find(c => c.online && c.deviceName === device)?.wakeWordConfig ?? null;\n }\n\n /**\n * Choose which wake words listen on one satellite. Ids come from {@link WakeWordConfig.available};\n * the device stores the selection itself, so it survives a restart of the adapter.\n */\n setWakeWords(device: string, ids: string[]): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n if (!target) {\n return false;\n }\n target.setWakeWords(ids);\n return true;\n }\n\n /**\n * Offer the current custom wake words to every connected satellite again — after one was uploaded\n * or deleted. Each device answers with its new configuration (→ `onWakeWords`).\n */\n refreshWakeWords(): number {\n const online = this.connections.filter(c => c.online);\n online.forEach(c => c.requestWakeWordConfig());\n return online.length;\n }\n\n /** Everything one satellite exposes besides the voice pipeline (gain, noise suppression, …). */\n entities(device: string): EsphomeEntity[] {\n return this.connections.find(c => c.online && c.deviceName === device)?.entities.list() ?? [];\n }\n\n /**\n * Write one of those entities. Returns false when the satellite is offline, the entity is unknown\n * or read-only, or the value is not one the device accepts.\n */\n setEntity(device: string, objectId: string, value: unknown): boolean {\n const target = this.connections.find(c => c.online && c.deviceName === device);\n return target ? target.setEntity(objectId, value) : false;\n }\n\n /**\n * Mirror an assistant timer onto the satellites so they can show it on their own LED ring and ring\n * it themselves. `device` null broadcasts. Returns how many devices were told.\n */\n timerEvent(device: string | null, event: TimerEvent): number {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n targets.forEach(c => c.sendTimerEvent(event));\n return targets.length;\n }\n\n /**\n * Play a ready PCM buffer on one satellite (by device name) or all of them. Returns how many\n * devices it was handed to.\n *\n * With `startConversation` the device opens its microphone as soon as the clip has played, without\n * a wake word — that is how a question gets its answer (see `PendingQuestions`).\n */\n async announce(device: string | null, pcm: Buffer, sampleRate: number, startConversation = false): Promise<number> {\n const targets = this.connections.filter(c => c.online && (!device || c.deviceName === device));\n if (!targets.length) {\n return 0;\n }\n await Promise.all(\n targets.map(c =>\n c\n .announce(pcm, sampleRate, startConversation)\n .catch(e => this.opts.log.warn(`announce to ${c.deviceName} failed: ${(e as Error).message}`)),\n ),\n );\n return targets.length;\n }\n}\n\n/** State of the utterance currently being captured. */\ninterface Capture {\n chunks: Buffer[];\n detector: SpeechDetector;\n conversationId: string;\n /** Wake word that opened this capture; '' when the mic was re-opened for a follow-up/question. */\n wakeWord: string;\n timer: NodeJS.Timeout;\n /** Watches for a device that stops sending audio without ending the stream. */\n stallCheck: NodeJS.Timeout;\n startedAt: number;\n /** When the last audio arrived; 0 = none yet. */\n lastAudioAt: number;\n closed: boolean;\n}\n\n/** One satellite: socket lifecycle, handshake, and the voice pipeline. */\nclass EsphomeConnection {\n /** Device name as reported by the device; falls back to its address until the handshake answers. */\n deviceName: string;\n online = false;\n /** Last wake-word configuration the device reported; null until the handshake answers. */\n wakeWordConfig: WakeWordConfig | null = null;\n /**\n * One entry per configuration request in flight, in send order (the device answers each one, in\n * order): the ids a preceding `SetConfiguration` asked for, or null. The read-back is the only\n * failure signal the device gives.\n */\n private configChecks: (string[] | null)[] = [];\n /** Everything else the device exposes — rebuilt from scratch on every reconnect. */\n readonly entities = new EntityRegistry();\n\n private readonly host: string;\n private readonly port: number;\n private readonly room: string;\n\n private socket: net.Socket | null = null;\n private rx: Bytes = Buffer.alloc(0);\n private pingTimer: NodeJS.Timeout | null = null;\n private reconnectTimer: NodeJS.Timeout | null = null;\n private missedPings = 0;\n private stopping = false;\n private capture: Capture | null = null;\n private playbackTimer: NodeJS.Timeout | null = null;\n private readonly password: string;\n /** Configured speech threshold, 0 = {@link DEFAULT_SPEECH_LEVEL}. */\n private readonly speechLevel: number;\n\n constructor(\n device: EsphomeDevice,\n private readonly opts: EsphomeSatellitesOptions,\n ) {\n this.host = device.ip.trim();\n this.port = device.port || DEFAULT_PORT;\n this.room = (device.room || '').trim();\n this.password = device.password || '';\n this.speechLevel = speechLevelOf(device);\n this.deviceName = this.host;\n }\n\n // ── lifecycle ───────────────────────────────────────────────────────────────────────────────────\n\n start(): void {\n this.connect();\n }\n\n async stop(): Promise<void> {\n this.stopping = true;\n this.clearTimers();\n this.abortCapture();\n const socket = this.socket;\n this.socket = null;\n if (socket) {\n try {\n socket.write(encode(new pb.DisconnectRequest()));\n } catch {\n /* socket already gone — nothing to say goodbye on */\n }\n socket.destroy();\n }\n this.setOffline();\n return Promise.resolve();\n }\n\n private connect(): void {\n if (this.stopping) {\n return;\n }\n this.rx = Buffer.alloc(0);\n this.missedPings = 0;\n // The device re-announces everything after a reconnect, and the keys are not stable across\n // reboots, so starting from a clean slate is the only safe option.\n this.entities.clear();\n this.configChecks = [];\n\n const socket = net.createConnection({ host: this.host, port: this.port }, () => {\n this.opts.log.debug(`ESPHome ${this.host}:${this.port} connected`);\n this.handshake();\n });\n this.socket = socket;\n socket.setNoDelay(true);\n\n socket.on('data', data => this.onData(data));\n socket.on('error', e => {\n // A satellite that is switched off is normal, so this is not an error-level event.\n this.opts.log.debug(`ESPHome ${this.host}: ${e.message}`);\n });\n socket.on('close', () => {\n if (this.socket === socket) {\n this.socket = null;\n this.onDisconnected();\n }\n });\n }\n\n private onDisconnected(): void {\n this.clearTimers();\n this.abortCapture();\n if (this.online) {\n this.opts.log.info(`ESPHome satellite offline: ${this.deviceName}`);\n }\n this.setOffline();\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopping || this.reconnectTimer) {\n return;\n }\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, RECONNECT_MS);\n this.reconnectTimer.unref();\n }\n\n private clearTimers(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n }\n\n private setOffline(): void {\n if (this.online) {\n this.online = false;\n this.opts.onStatus?.(this.deviceName, this.room, 'offline');\n }\n }\n\n private status(state: SatelliteState): void {\n this.opts.onStatus?.(this.deviceName, this.room, state);\n }\n\n // ── protocol plumbing ───────────────────────────────────────────────────────────────────────────\n\n private send(message: PbMessage): void {\n const socket = this.socket;\n if (!socket || socket.destroyed) {\n return;\n }\n try {\n socket.write(encode(message));\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: send failed — ${(e as Error).message}`);\n }\n }\n\n private onData(data: Buffer): void {\n this.rx = this.rx.length ? Buffer.concat([this.rx, data]) : data;\n let frames;\n try {\n ({ frames, rest: this.rx } = decode(this.rx));\n } catch (e) {\n this.opts.log.error(`ESPHome ${this.deviceName}: ${(e as Error).message}`);\n this.socket?.destroy();\n return;\n }\n for (const frame of frames) {\n if (frame.error) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: cannot parse ${frame.name}`);\n continue;\n }\n if (!frame.name || !frame.message) {\n continue; // unknown id — already skipped by its declared length\n }\n try {\n this.onMessage(frame.name, frame.message);\n } catch (e) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: ${frame.name} handler failed — ${(e as Error).message}`,\n );\n }\n }\n }\n\n private handshake(): void {\n const hello = new pb.HelloRequest();\n hello.setClientInfo('ioBroker.assistant');\n hello.setApiVersionMajor(1);\n hello.setApiVersionMinor(10);\n this.send(hello);\n\n const auth = new pb.AuthenticationRequest();\n auth.setPassword(this.password);\n this.send(auth);\n\n this.send(new pb.DeviceInfoRequest());\n // Ask for the entity announcements *before* subscribing to their states: a state message whose\n // entity we have not seen yet has nothing to attach to and is dropped.\n this.send(new pb.ListEntitiesRequest());\n this.send(new pb.SubscribeStatesRequest());\n\n const subscribe = new pb.SubscribeVoiceAssistantRequest();\n subscribe.setSubscribe(true);\n subscribe.setFlags(SUBSCRIBE_API_AUDIO);\n this.send(subscribe);\n\n this.requestWakeWordConfig();\n\n this.pingTimer = setInterval(() => {\n if (++this.missedPings > 2) {\n this.opts.log.info(`ESPHome ${this.deviceName}: no reply to keepalive — reconnecting`);\n this.socket?.destroy();\n return;\n }\n this.send(new pb.PingRequest());\n }, PING_INTERVAL_MS);\n this.pingTimer.unref();\n }\n\n private onMessage(name: string, message: unknown): void {\n switch (name) {\n case 'HelloResponse': {\n const hello = message as HelloResponse;\n this.opts.log.debug(\n `ESPHome ${this.host}: ${hello.getServerInfo()} (API ${hello.getApiVersionMajor()}.${hello.getApiVersionMinor()})`,\n );\n return;\n }\n\n case 'AuthenticationResponse':\n if ((message as AuthenticationResponse).getInvalidPassword()) {\n this.opts.log.error(\n `ESPHome ${this.host}: API password rejected — check the device's password in the settings.`,\n );\n this.socket?.destroy();\n }\n return;\n\n case 'DeviceInfoResponse': {\n const info = message as DeviceInfoResponse;\n this.deviceName = info.getName() || this.host;\n this.online = true;\n this.opts.log.info(\n `ESPHome satellite online: ${this.deviceName} (${info.getModel()}) @ ${this.host}${this.room ? `, room ${this.room}` : ''}`,\n );\n this.status('idle');\n return;\n }\n\n case 'VoiceAssistantConfigurationResponse': {\n const config = message as VoiceAssistantConfigurationResponse;\n // A custom wake word the device already downloaded is listed twice: once from its own\n // model folder and once from our offer.\n const seen = new Set<string>();\n this.wakeWordConfig = {\n active: config.getActiveWakeWordsList(),\n available: config\n .getAvailableWakeWordsList()\n .map(w => ({\n id: w.getId(),\n phrase: w.getWakeWord(),\n languages: w.getTrainedLanguagesList(),\n }))\n .filter(w => !seen.has(w.id) && !!seen.add(w.id)),\n max: config.getMaxActiveWakeWords(),\n };\n const active = this.wakeWordConfig.active.join(', ') || '(none active)';\n const available = this.wakeWordConfig.available.map(w => w.id);\n this.opts.log.info(\n `ESPHome ${this.deviceName}: wake words ${active}${available.length ? ` (available: ${available.join(', ')})` : ''}`,\n );\n const requested = this.configChecks.shift();\n const missing = requested?.filter(id => !this.wakeWordConfig?.active.includes(id)) ?? [];\n if (missing.length) {\n // The device answers a model it cannot fetch or load with silence, not an error.\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: could not activate wake word(s) ${missing.join(', ')}. ` +\n `For an uploaded wake word the device must reach ${this.mediaUrl('/')} and the model ` +\n `must be a microWakeWord .tflite.`,\n );\n }\n this.opts.onWakeWords?.(this.deviceName, this.room, this.wakeWordConfig);\n return;\n }\n\n case 'VoiceAssistantRequest':\n this.onPipelineRequest(message as VoiceAssistantRequestMsg);\n return;\n\n case 'VoiceAssistantAudio':\n this.onAudio(message as VoiceAssistantAudio);\n return;\n\n case 'VoiceAssistantAnnounceFinished':\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n this.playbackTimer = null;\n }\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: playback finished (success=${(message as VoiceAssistantAnnounceFinished).getSuccess()})`,\n );\n this.status('idle');\n return;\n\n case 'PingRequest':\n this.send(new pb.PingResponse());\n return;\n\n case 'PingResponse':\n this.missedPings = 0;\n return;\n\n case 'GetTimeRequest': {\n // The device has no RTC and syncs its clock from us — its timer ring depends on it.\n const time = new pb.GetTimeResponse();\n time.setEpochSeconds(Math.floor(Date.now() / 1000));\n this.send(time);\n return;\n }\n\n case 'DisconnectRequest':\n this.send(new pb.DisconnectResponse());\n this.socket?.destroy();\n return;\n\n case 'ListEntitiesDoneResponse': {\n const list = this.entities.list();\n if (list.length) {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: ${list.length} entities — ${list.map(e => e.objectId).join(', ')}`,\n );\n }\n this.opts.onEntities?.(this.deviceName, this.room, list);\n return;\n }\n\n default:\n if (isListEntityMessage(name)) {\n this.entities.addFromAnnouncement(name, message);\n } else if (isEntityStateMessage(name)) {\n const entity = this.entities.applyState(name, message);\n if (entity) {\n this.opts.onEntityState?.(this.deviceName, this.room, entity);\n }\n }\n return;\n }\n }\n\n /** Write one of the device's entities. See {@link EsphomeSatellites.setEntity}. */\n setEntity(objectId: string, value: unknown): boolean {\n const message = this.entities.command(objectId, value);\n if (!message) {\n const entity = this.entities.get(objectId);\n const known = this.entities\n .list()\n .map(e => e.objectId)\n .join(', ');\n const options = entity?.options ? ` (one of: ${entity.options.join(', ')})` : '';\n this.opts.log.warn(\n entity\n ? `ESPHome ${this.deviceName}: ${JSON.stringify(value)} is not a value \"${objectId}\" accepts${options}`\n : `ESPHome ${this.deviceName}: no entity \"${objectId}\" (have: ${known})`,\n );\n return false;\n }\n this.send(message);\n this.opts.log.debug(`ESPHome ${this.deviceName}: ${objectId} := ${JSON.stringify(value)}`);\n return true;\n }\n\n /** Push one assistant timer to the device so it can show and ring it itself. */\n sendTimerEvent(event: TimerEvent): void {\n const message = new pb.VoiceAssistantTimerEventResponse();\n message.setEventType(TIMER_EVENT[event.type]);\n message.setTimerId(event.id);\n message.setName(event.name);\n message.setTotalSeconds(Math.max(0, Math.round(event.totalSeconds)));\n message.setSecondsLeft(Math.max(0, Math.round(event.secondsLeft)));\n message.setIsActive(event.active);\n this.send(message);\n }\n\n // ── voice pipeline ──────────────────────────────────────────────────────────────────────────────\n\n private onPipelineRequest(request: VoiceAssistantRequestMsg): void {\n if (!request.getStart()) {\n this.abortCapture();\n return;\n }\n if (this.capture) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: pipeline already running — ignoring wake`);\n return;\n }\n\n const phrase = request.getWakeWordPhrase();\n this.opts.log.debug(`ESPHome ${this.deviceName}: wake \"${phrase || '(follow-up)'}\"`);\n\n const response = new pb.VoiceAssistantResponse();\n response.setPort(0); // 0 = stream the audio over this socket\n response.setError(false);\n this.send(response);\n\n const timer = setTimeout(() => {\n this.opts.log.warn(`ESPHome ${this.deviceName}: no end of speech within ${PIPELINE_TIMEOUT_MS} ms`);\n void this.finishCapture('pipeline-timeout');\n }, PIPELINE_TIMEOUT_MS);\n timer.unref();\n const stallCheck = setInterval(() => this.checkStall(), STALL_CHECK_MS);\n stallCheck.unref();\n\n const silenceMs = this.opts.silenceMs || DEFAULT_SILENCE_MS;\n this.capture = {\n chunks: [],\n detector: new SpeechDetector({\n sampleRate: MIC_SAMPLE_RATE,\n silenceMs,\n longSilenceMs: Math.round(silenceMs * LONG_PAUSE_FACTOR),\n longAfterMs: LONG_REQUEST_MS,\n // Microphone gain differs wildly between these devices, so fixed RMS levels do not\n // travel: derive them from the room instead, once the wake chirp is out of the way.\n skipMs: WAKE_CHIRP_MS,\n adaptive: true,\n startTimeoutMs: phrase ? NO_SPEECH_MS : NO_SPEECH_FOLLOW_UP_MS,\n minStartLevel: this.speechLevel || DEFAULT_SPEECH_LEVEL,\n minEndLevel: Math.round((this.speechLevel || DEFAULT_SPEECH_LEVEL) * SILENCE_SHARE),\n minSpeechMs: MIN_SPEECH_MS,\n }),\n conversationId: request.getConversationId(),\n wakeWord: phrase || '',\n timer,\n stallCheck,\n startedAt: Date.now(),\n lastAudioAt: 0,\n closed: false,\n };\n\n this.status('listening');\n this.event(EVENT.RUN_START);\n if (phrase) {\n this.event(EVENT.WAKE_WORD_END, { wake_word_phrase: phrase });\n }\n this.event(EVENT.STT_START);\n this.event(EVENT.STT_VAD_START);\n }\n\n private onAudio(audio: VoiceAssistantAudio): void {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n const chunk = Buffer.from(audio.getData_asU8());\n if (chunk.length) {\n capture.lastAudioAt = Date.now();\n capture.chunks.push(chunk);\n const end = capture.detector.push(chunk);\n if (end) {\n void this.finishCapture(end);\n return;\n }\n }\n if (audio.getEnd()) {\n void this.finishCapture('stream');\n }\n }\n\n /** End a capture whose device stopped sending audio without ending the stream. */\n private checkStall(): void {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n const now = Date.now();\n const quietMs = capture.lastAudioAt ? now - capture.lastAudioAt : now - capture.startedAt;\n if (quietMs < (capture.lastAudioAt ? STREAM_STALL_MS : STREAM_START_MS)) {\n return;\n }\n const audioMs = Math.round(capture.chunks.reduce((sum, c) => sum + c.length, 0) / 2 / (MIC_SAMPLE_RATE / 1000));\n this.opts.log.info(\n `ESPHome ${this.deviceName}: the device ${\n capture.lastAudioAt ? `stopped sending audio after ${(audioMs / 1000).toFixed(2)} s` : 'sent no audio'\n } without ending the stream — closing the utterance after ${(quietMs / 1000).toFixed(1)} s of nothing`,\n );\n void this.finishCapture('stall');\n }\n\n /** Hand a recording that never became a request to {@link EsphomeSatellitesOptions.onDiscarded}. */\n private discard(capture: Capture, pcm: Bytes, reason: DiscardReason, vad: VadReport): void {\n if (!this.opts.onDiscarded || !pcm.length) {\n return;\n }\n try {\n this.opts.onDiscarded({\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n reason,\n pcm,\n sampleRate: MIC_SAMPLE_RATE,\n // The store writes its own level profile next to it.\n vad: { ...vad, levels: undefined },\n });\n } catch (e) {\n this.opts.log.debug(`ESPHome ${this.deviceName}: keeping the recording failed — ${(e as Error).message}`);\n }\n }\n\n /** Stop the mic stream and run STT → answer → TTS for what was captured. */\n private async finishCapture(end: CaptureEnd): Promise<void> {\n const capture = this.capture;\n if (!capture || capture.closed) {\n return;\n }\n capture.closed = true;\n this.capture = null;\n clearTimeout(capture.timer);\n clearInterval(capture.stallCheck);\n\n // Send this first: the device streams until it sees it.\n this.event(EVENT.STT_VAD_END);\n\n const pcm = Buffer.concat(capture.chunks);\n const seconds = pcm.length / 2 / MIC_SAMPLE_RATE;\n const vad: VadReport = { ...capture.detector.report(), end };\n const startedAt =\n vad.speechStartMs !== undefined ? `, speech from ${(vad.speechStartMs / 1000).toFixed(2)} s` : '';\n const endedBy = vad.end === 'speech-end' ? `ended after ${vad.silenceMs} ms of silence` : `ended by ${vad.end}`;\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: captured ${seconds.toFixed(2)} s ` +\n `(peak RMS ${vad.peak}, noise floor ${vad.noiseFloor}, speech/silence gate ${vad.startLevel}/${vad.endLevel}` +\n `${startedAt}, ${endedBy})`,\n );\n\n if (!pcm.length || !capture.detector.sawSpeech) {\n const loudest = Math.round(capture.detector.analysedPeak);\n const gate = Math.round(capture.detector.startLevel);\n if (capture.detector.longestSoundMs > 0) {\n // Something crossed the gate, but too briefly for a word: a click, a knock, a cough.\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: only a short sound (${capture.detector.longestSoundMs} ms, ` +\n `RMS ${loudest}) — too short for speech (${MIN_SPEECH_MS} ms), dropping the utterance`,\n );\n } else if (pcm.length && loudest >= gate * QUIET_VOICE_SHARE) {\n // Not an empty room: something came close to the gate. Typically a microphone that is quieter\n // than the default threshold expects (a phone) — say where to change it.\n this.opts.log.info(\n `ESPHome ${this.deviceName}: dropped the utterance — the loudest sound (RMS ${loudest}) stayed ` +\n `below the speech threshold ${gate}. If somebody spoke, lower \"Speech threshold\" for this ` +\n `device in the ESPHome satellites table (Voice tab), e.g. to ${Math.max(\n 30,\n Math.round((loudest * 0.6) / 10) * 10,\n )}.`,\n );\n } else {\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: nothing but silence${\n end === 'no-speech' ? ' (nobody started speaking)' : ''\n } — dropping the utterance`,\n );\n }\n this.discard(capture, pcm, end === 'no-speech' ? 'no-speech' : 'silence', vad);\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.status('processing');\n try {\n const hints = this.opts.getHints ? await this.opts.getHints() : undefined;\n const speech = prepareForStt(pcm, MIC_SAMPLE_RATE, { startMs: vad.speechStartMs, endLevel: vad.endLevel });\n vad.stt = { fromMs: speech.fromMs, toMs: speech.toMs, gain: speech.gain };\n this.opts.log.debug(\n `ESPHome ${this.deviceName}: speech recognition gets ${(speech.fromMs / 1000).toFixed(2)}–` +\n `${(speech.toMs / 1000).toFixed(2)} s${speech.gain > 1 ? `, amplified ×${speech.gain}` : ''}`,\n );\n const sttStart = Date.now();\n const text = (\n await this.opts.stt.transcribe(speech.pcm, MIC_SAMPLE_RATE, this.opts.language, hints)\n ).trim();\n const sttMs = Date.now() - sttStart;\n this.event(EVENT.STT_END, { text });\n if (!text) {\n // Speech was heard, so the user waits for an answer: say why none comes. Typically a word in another\n // language than the STT listens in (a German \"Ja\" to a Russian STT comes back empty).\n this.opts.log.info(\n `Voice Q (${this.deviceName}): (empty) — speech recognition returned no text for ${seconds.toFixed(1)} s of audio`,\n );\n this.discard(capture, pcm, 'empty-stt', vad);\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n this.event(EVENT.INTENT_START);\n const answer = (\n await this.opts.answer(text, {\n device: this.deviceName,\n room: this.room,\n wakeWord: capture.wakeWord,\n sttMs,\n audioMs: Math.round(seconds * 1000),\n // Numbers only, no audio: shows where a request was cut off without keeping recordings.\n vad: { ...vad, levels: levelProfile(pcm, MIC_SAMPLE_RATE) },\n })\n ).trim();\n const followUp = !!answer && this.opts.expectsFollowUp?.(answer, this.deviceName) === true;\n this.event(EVENT.INTENT_END, followUp ? { continue_conversation: '1' } : undefined);\n\n if (!answer) {\n this.event(EVENT.RUN_END);\n this.status('idle');\n return;\n }\n\n const ttsStart = Date.now();\n const { pcm: ttsPcm, sampleRate } = await this.opts.tts.synthesize(answer, this.opts.language);\n this.opts.onSpoken?.(this.deviceName, {\n ms: Date.now() - ttsStart,\n audioMs: (ttsPcm.length / 2 / sampleRate) * 1000,\n });\n const url = this.publish(ttsPcm, sampleRate);\n\n this.status('speaking');\n this.event(EVENT.TTS_START, { text: answer });\n this.event(EVENT.TTS_END, { url });\n this.event(EVENT.RUN_END, followUp ? { continue_conversation: '1' } : undefined);\n this.armPlaybackTimeout();\n } catch (e) {\n const reason = (e as Error).message;\n this.opts.log.error(`ESPHome ${this.deviceName}: voice pipeline failed — ${reason}`);\n // Without this the device stays ducked and its LED keeps spinning.\n this.event(EVENT.ERROR, { code: 'pipeline-failed', message: reason });\n this.event(EVENT.RUN_END);\n this.status('idle');\n }\n }\n\n /**\n * Select which wake words listen on this device. Unknown ids are dropped and the list is trimmed\n * to what the firmware allows, because the device answers a rejected list with silence rather than\n * an error — better to send something valid and report what we changed.\n *\n * The device persists the selection and echoes it back in a `VoiceAssistantConfigurationResponse`,\n * so we ask for one instead of assuming the write took.\n */\n setWakeWords(ids: string[]): void {\n const known = this.wakeWordConfig?.available.map(w => w.id) ?? [];\n const wanted = [...new Set(ids.map(i => i.trim()).filter(Boolean))];\n const unknown = wanted.filter(i => !known.includes(i));\n if (unknown.length) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: unknown wake word(s) ${unknown.join(', ')} — ignored. ` +\n `Available: ${known.join(', ') || '(none reported)'}`,\n );\n }\n let valid = wanted.filter(i => known.includes(i));\n const max = this.wakeWordConfig?.max || 0;\n if (max > 0 && valid.length > max) {\n this.opts.log.warn(\n `ESPHome ${this.deviceName}: device allows ${max} active wake word(s), keeping ${valid\n .slice(0, max)\n .join(', ')}`,\n );\n valid = valid.slice(0, max);\n }\n\n const message = new pb.VoiceAssistantSetConfiguration();\n message.setActiveWakeWordsList(valid);\n this.send(message);\n this.opts.log.info(`ESPHome ${this.deviceName}: wake words set to ${valid.join(', ') || '(none)'}`);\n // Read back, so the reported state is the device's and not our guess. The device resolves a\n // custom wake word (and downloads it) while handling the SetConfiguration, so this answer\n // already tells whether that worked.\n this.requestWakeWordConfig(valid);\n }\n\n /**\n * Ask the device for its wake-word configuration. The request always carries the current custom\n * wake words: the firmware replaces its list of offered models with every request, so one sent\n * without them would make an uploaded wake word vanish from the device until the next offer.\n *\n * @param check ids a SetConfiguration just asked for — warned about if the answer lacks them\n */\n requestWakeWordConfig(check: string[] | null = null): void {\n const request = new pb.VoiceAssistantConfigurationRequest();\n const offers = this.opts.externalWakeWords?.() ?? [];\n request.setExternalWakeWordsList(\n offers.map(offer => {\n const w = new pb.VoiceAssistantExternalWakeWord();\n w.setId(offer.id);\n w.setWakeWord(offer.phrase);\n w.setTrainedLanguagesList(offer.languages);\n w.setModelType('micro');\n w.setModelSize(offer.size);\n w.setModelHash(offer.sha256);\n w.setUrl(this.mediaUrl(offer.path));\n return w;\n }),\n );\n // A device that never answers must not let this grow without bound.\n if (this.configChecks.length < 16) {\n this.configChecks.push(check);\n }\n this.send(request);\n }\n\n /** Drop a half-finished capture (device went away, or told us to stop). */\n private abortCapture(): void {\n const capture = this.capture;\n if (!capture) {\n return;\n }\n this.capture = null;\n capture.closed = true;\n clearTimeout(capture.timer);\n clearInterval(capture.stallCheck);\n }\n\n // ── announcements ───────────────────────────────────────────────────────────────────────────────\n\n /**\n * Speak a ready PCM buffer on this device (timers, alarms, `tts.text`, jingles). With\n * `startConversation` the microphone opens once the clip has played, to catch the answer to a question.\n */\n announce(pcm: Buffer, sampleRate: number, startConversation = false): Promise<void> {\n if (!this.online || !pcm.length) {\n return Promise.resolve();\n }\n const request = new pb.VoiceAssistantAnnounceRequest();\n request.setMediaId(this.publish(pcm, sampleRate));\n request.setText('');\n request.setPreannounceMediaId('');\n // Needs feature flag 32 (START_CONVERSATION); on a device without it the clip still plays and\n // only the microphone stays shut, so there is nothing to guard here.\n request.setStartConversation(startConversation);\n this.status('speaking');\n this.send(request);\n this.armPlaybackTimeout();\n return Promise.resolve();\n }\n\n /**\n * Publish PCM as a WAV on the media server and build the URL the device should fetch.\n *\n * The host matters: on a multi-homed ioBroker machine the device can usually only reach one of the\n * addresses, and the local end of *this* socket is by definition one it can talk to.\n */\n private publish(pcm: Buffer, sampleRate: number): string {\n return this.mediaUrl(this.opts.media.publish(pcmToWav(pcm, sampleRate), 'audio/wav'));\n }\n\n /** Absolute URL of a path on the media server, with a host this device can reach. */\n private mediaUrl(path: string): string {\n const local = (this.socket?.localAddress || '').replace(/^::ffff:/, '');\n const host = this.opts.mediaHost?.trim() || local || '127.0.0.1';\n // An IPv6 literal has to be bracketed in a URL.\n const authority = host.includes(':') ? `[${host}]` : host;\n return `http://${authority}:${this.opts.media.port}${path}`;\n }\n\n /** Fall back to `idle` if the device never reports that it finished playing. */\n private armPlaybackTimeout(): void {\n if (this.playbackTimer) {\n clearTimeout(this.playbackTimer);\n }\n this.playbackTimer = setTimeout(() => {\n this.playbackTimer = null;\n this.status('idle');\n }, PLAYBACK_TIMEOUT_MS);\n this.playbackTimer.unref();\n }\n\n private event(type: number, data?: Record<string, string>): void {\n const message = new pb.VoiceAssistantEventResponse();\n message.setEventType(type);\n const items: VoiceAssistantEventData[] = [];\n for (const [name, value] of Object.entries(data || {})) {\n const item = new pb.VoiceAssistantEventData();\n item.setName(name);\n item.setValue(value);\n items.push(item);\n }\n message.setDataList(items);\n this.send(message);\n }\n}\n"]}
|