@iobroker/assistant-satellite 0.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;;;;;GAKG;AACH,4CAA8B;AAC9B,mCAAsF;AACtF,uCAA6D;AAC7D,qCAAkD;AAClD,mCAAyC;AAEzC,+FAA+F;AAC/F,IAAI,YAAY,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACvC,MAAM,GAAG,GAAG;IACR,IAAI,EAAE,CAAC,CAAS,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;IACtD,IAAI,EAAE,CAAC,CAAS,EAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;IACvD,KAAK,EAAE,CAAC,CAAS,EAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;IACzD,KAAK,EAAE,CAAC,CAAS,EAAQ,EAAE;QACvB,IAAI,YAAY,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;CACJ,CAAC;AAEF,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACjD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC;AAEvE,iFAAiF;AACjF,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,GAAG,CAAC,KAAK,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,CAAC;QACD,MAAM,CAAC,GAAG,IAAA,kBAAU,EAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAA6B,CAAC,CAAC;QACjG,MAAM,MAAM,GAAG,IAAA,oBAAW,EAAC,IAAA,kBAAS,EAAC,CAAC,EAAE,IAAA,sBAAc,EAAC,CAAC,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACpF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,GAAG,CAAC,KAAK,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,mFAAmF;AACnF,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;IACnD,IAAI,CAAC;QACD,IAAA,wBAAc,EAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,GAAG,CAAC,KAAK,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AACD,IAAI,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;IACvD,IAAI,CAAC;QACD,IAAA,0BAAgB,EAAC,GAAG,CAAC,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,GAAG,CAAC,KAAK,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,GAAG,OAAO,IAAI,aAAa,CAAC;AAE5C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;IAC7B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAc,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7E,GAAG,CAAC,IAAI,CAAC,uCAAuC,UAAU,GAAG,CAAC,CAAC;IAC/D,GAAG,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,GAAoB,CAAC;AACzB,IAAI,CAAC;IACD,GAAG,GAAG,IAAA,kBAAU,EAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAA6B,CAAC,CAAC;AAClG,CAAC;AAAC,OAAO,CAAC,EAAE,CAAC;IACT,GAAG,CAAC,KAAK,CAAC,eAAe,UAAU,KAAM,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;IAC3B,YAAY,GAAG,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,iBAAS,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAExF,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACxB,GAAG,CAAC,KAAK,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,IAAI,QAAQ,GAAG,KAAK,CAAC;AACrB,KAAK,UAAU,QAAQ;IACnB,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO;IACX,CAAC;IACD,QAAQ,GAAG,IAAI,CAAC;IAChB,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC5B,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;IACvB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AACD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC5C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC"}
@@ -0,0 +1,46 @@
1
+ /** Satellite configuration (loaded from config.json / CLI). */
2
+ export interface SatelliteConfig {
3
+ /** Log verbosity. 'debug' also prints the wake-word/mic diagnostics. */
4
+ logLevel: 'info' | 'debug';
5
+ /** Device identity reported to the adapter. */
6
+ device: string;
7
+ room: string;
8
+ /** Adapter (voice-server) address. Discovery via MQTT is optional; a fixed host needs no broker. */
9
+ host: string;
10
+ port: number;
11
+ /** UDP port the satellite listens on for TTS/control from the adapter. */
12
+ listenPort: number;
13
+ /** Optional MQTT discovery — leave broker empty to run without a broker (fixed host only). */
14
+ mqttBroker: string;
15
+ mqttPort: number;
16
+ mqttUser: string;
17
+ mqttPass: string;
18
+ discoveryTopic: string;
19
+ discoveryTimeoutMs: number;
20
+ /** Audio backend: 'auto' (alsa on Linux, ffmpeg elsewhere), or force 'alsa' / 'ffmpeg'. */
21
+ audioBackend: 'auto' | 'alsa' | 'ffmpeg';
22
+ /**
23
+ * Capture/playback devices. ALSA: e.g. "plughw:2,0". ffmpeg mic: dshow device name (Windows),
24
+ * avfoundation index (macOS). "default" or "" = system default.
25
+ */
26
+ micDevice: string;
27
+ speakerDevice: string;
28
+ /** Wake word. modelPath: path to a wakeword .onnx ('' = built-in "hey_jarvis"). */
29
+ wakewordModel: string;
30
+ wakewordThreshold: number;
31
+ /** Directory the melspectrogram/embedding/wakeword models are downloaded to/read from. */
32
+ modelsDir: string;
33
+ /** Recording (VAD) — silence detection to end an utterance. */
34
+ silenceThreshold: number;
35
+ silenceMs: number;
36
+ minRecordMs: number;
37
+ maxRecordMs: number;
38
+ /** Number of pre-wake 80 ms chunks prepended to the recording (so no speech is lost). */
39
+ preBufferChunks: number;
40
+ /** Heartbeat / registration. */
41
+ registrationTimeoutMs: number;
42
+ heartbeatIntervalMs: number;
43
+ }
44
+ export declare const DEFAULT_CONFIG: SatelliteConfig;
45
+ /** Merge a partial config (from config.json) onto the defaults. */
46
+ export declare function loadConfig(partial: Partial<SatelliteConfig>): SatelliteConfig;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ exports.loadConfig = loadConfig;
5
+ exports.DEFAULT_CONFIG = {
6
+ logLevel: 'info',
7
+ device: 'satellite',
8
+ room: '',
9
+ host: '',
10
+ port: 7775,
11
+ listenPort: 7776,
12
+ mqttBroker: '',
13
+ mqttPort: 1883,
14
+ mqttUser: '',
15
+ mqttPass: '',
16
+ discoveryTopic: 'hannah/server',
17
+ discoveryTimeoutMs: 5000,
18
+ audioBackend: 'auto',
19
+ micDevice: 'default',
20
+ speakerDevice: 'default',
21
+ wakewordModel: '',
22
+ wakewordThreshold: 0.5,
23
+ modelsDir: 'models',
24
+ silenceThreshold: 300,
25
+ silenceMs: 800,
26
+ minRecordMs: 800,
27
+ maxRecordMs: 8000,
28
+ preBufferChunks: 5,
29
+ registrationTimeoutMs: 5000,
30
+ heartbeatIntervalMs: 10000,
31
+ };
32
+ /** Merge a partial config (from config.json) onto the defaults. */
33
+ function loadConfig(partial) {
34
+ return { ...exports.DEFAULT_CONFIG, ...partial };
35
+ }
36
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;AAgFA,gCAEC;AA/BY,QAAA,cAAc,GAAoB;IAC3C,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,WAAW;IACnB,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,EAAE;IACd,QAAQ,EAAE,IAAI;IACd,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;IACZ,cAAc,EAAE,eAAe;IAC/B,kBAAkB,EAAE,IAAI;IACxB,YAAY,EAAE,MAAM;IACpB,SAAS,EAAE,SAAS;IACpB,aAAa,EAAE,SAAS;IACxB,aAAa,EAAE,EAAE;IACjB,iBAAiB,EAAE,GAAG;IACtB,SAAS,EAAE,QAAQ;IACnB,gBAAgB,EAAE,GAAG;IACrB,SAAS,EAAE,GAAG;IACd,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,IAAI;IACjB,eAAe,EAAE,CAAC;IAClB,qBAAqB,EAAE,IAAI;IAC3B,mBAAmB,EAAE,KAAK;CAC7B,CAAC;AAEF,mEAAmE;AACnE,SAAgB,UAAU,CAAC,OAAiC;IACxD,OAAO,EAAE,GAAG,sBAAc,EAAE,GAAG,OAAO,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @iobroker/assistant-satellite — public API.
3
+ *
4
+ * The Satellite is dependency-injected with a host (logger + status callback) so the same code runs
5
+ * standalone (CLI) and, later, inside an ioBroker adapter wrapper — with no ioBroker dependency here.
6
+ */
7
+ import type { SatelliteState } from './protocol';
8
+ export interface Logger {
9
+ info(m: string): void;
10
+ warn(m: string): void;
11
+ error(m: string): void;
12
+ debug(m: string): void;
13
+ }
14
+ export interface SatelliteHost {
15
+ log: Logger;
16
+ /** Called on every state transition (idle/listening/processing/speaking). */
17
+ onStatus?(state: SatelliteState): void;
18
+ }
19
+ export { Satellite } from './satellite';
20
+ export { loadConfig, DEFAULT_CONFIG, type SatelliteConfig } from './config';
21
+ export type { SatelliteState } from './protocol';
package/build/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = exports.loadConfig = exports.Satellite = void 0;
4
+ var satellite_1 = require("./satellite");
5
+ Object.defineProperty(exports, "Satellite", { enumerable: true, get: function () { return satellite_1.Satellite; } });
6
+ var config_1 = require("./config");
7
+ Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return config_1.loadConfig; } });
8
+ Object.defineProperty(exports, "DEFAULT_CONFIG", { enumerable: true, get: function () { return config_1.DEFAULT_CONFIG; } });
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAqBA,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAClB,mCAA4E;AAAnE,oGAAA,UAAU,OAAA;AAAE,wGAAA,cAAc,OAAA"}
@@ -0,0 +1,15 @@
1
+ import type { Logger } from './index';
2
+ export declare const MELSPEC_URL = "https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/melspectrogram.onnx";
3
+ export declare const EMBEDDING_URL = "https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/embedding_model.onnx";
4
+ /** Built-in wake words available as ONNX (name → asset). Default "hey_jarvis". */
5
+ export declare const WAKEWORDS: Record<string, string>;
6
+ export interface ModelPaths {
7
+ melspec: string;
8
+ embedding: string;
9
+ wakeword: string;
10
+ }
11
+ /**
12
+ * Ensure the three model files exist locally, downloading the missing ones.
13
+ * `wakewordModel` may be a name from WAKEWORDS, a URL, or a local .onnx path.
14
+ */
15
+ export declare function ensureModels(modelsDir: string, wakewordModel: string, log: Logger): Promise<ModelPaths>;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WAKEWORDS = exports.EMBEDDING_URL = exports.MELSPEC_URL = void 0;
37
+ exports.ensureModels = ensureModels;
38
+ /**
39
+ * OpenWakeWord ONNX model management. The pipeline needs three models:
40
+ * - melspectrogram.onnx (shared) — audio → mel spectrogram
41
+ * - embedding_model.onnx (shared) — mel → Google speech embeddings
42
+ * - <wakeword>.onnx — embeddings → wake-word score
43
+ *
44
+ * Models are downloaded on first run to `modelsDir` from the openWakeWord releases.
45
+ */
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ /** openWakeWord release assets (feature models + a few pretrained wake words). */
49
+ const RELEASE = 'https://github.com/dscripka/openWakeWord/releases/download/v0.5.1';
50
+ exports.MELSPEC_URL = `${RELEASE}/melspectrogram.onnx`;
51
+ exports.EMBEDDING_URL = `${RELEASE}/embedding_model.onnx`;
52
+ /** Built-in wake words available as ONNX (name → asset). Default "hey_jarvis". */
53
+ exports.WAKEWORDS = {
54
+ hey_jarvis: `${RELEASE}/hey_jarvis_v0.1.onnx`,
55
+ alexa: `${RELEASE}/alexa_v0.1.onnx`,
56
+ hey_mycroft: `${RELEASE}/hey_mycroft_v0.1.onnx`,
57
+ hey_rhasspy: `${RELEASE}/hey_rhasspy_v0.1.onnx`,
58
+ };
59
+ async function download(url, dest, log) {
60
+ if (fs.existsSync(dest)) {
61
+ return;
62
+ }
63
+ log.info(`Downloading model ${path.basename(dest)} …`);
64
+ const res = await fetch(url);
65
+ if (!res.ok) {
66
+ throw new Error(`download ${url} failed: HTTP ${res.status}`);
67
+ }
68
+ const buf = Buffer.from(await res.arrayBuffer());
69
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
70
+ fs.writeFileSync(dest, buf);
71
+ log.info(`Saved ${path.basename(dest)} (${(buf.length / 1024).toFixed(0)} KB).`);
72
+ }
73
+ /**
74
+ * Ensure the three model files exist locally, downloading the missing ones.
75
+ * `wakewordModel` may be a name from WAKEWORDS, a URL, or a local .onnx path.
76
+ */
77
+ async function ensureModels(modelsDir, wakewordModel, log) {
78
+ const melspec = path.join(modelsDir, 'melspectrogram.onnx');
79
+ const embedding = path.join(modelsDir, 'embedding_model.onnx');
80
+ await download(exports.MELSPEC_URL, melspec, log);
81
+ await download(exports.EMBEDDING_URL, embedding, log);
82
+ let wakeword;
83
+ const name = wakewordModel || 'hey_jarvis';
84
+ if (fs.existsSync(name)) {
85
+ wakeword = name; // a local file path
86
+ }
87
+ else if (/^https?:\/\//.test(name)) {
88
+ wakeword = path.join(modelsDir, path.basename(name));
89
+ await download(name, wakeword, log);
90
+ }
91
+ else {
92
+ const url = exports.WAKEWORDS[name];
93
+ if (!url) {
94
+ throw new Error(`unknown wake word "${name}" (known: ${Object.keys(exports.WAKEWORDS).join(', ')})`);
95
+ }
96
+ wakeword = path.join(modelsDir, `${name}.onnx`);
97
+ await download(url, wakeword, log);
98
+ }
99
+ return { melspec, embedding, wakeword };
100
+ }
101
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SatelliteConfig } from './config';
2
+ import type { Logger } from './index';
3
+ export declare function discoverServer(cfg: SatelliteConfig, log: Logger): Promise<{
4
+ host: string;
5
+ port: number;
6
+ }>;
package/build/mqtt.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.discoverServer = discoverServer;
7
+ /**
8
+ * Optional MQTT discovery — reads the adapter address from a retained topic
9
+ * (`{ "host": "...", "port": 7775 }`). Only used when no fixed host is configured.
10
+ */
11
+ const mqtt_1 = __importDefault(require("mqtt"));
12
+ function discoverServer(cfg, log) {
13
+ return new Promise((resolve, reject) => {
14
+ const client = mqtt_1.default.connect(`mqtt://${cfg.mqttBroker}:${cfg.mqttPort}`, {
15
+ username: cfg.mqttUser || undefined,
16
+ password: cfg.mqttPass || undefined,
17
+ connectTimeout: cfg.discoveryTimeoutMs,
18
+ });
19
+ const timer = setTimeout(() => {
20
+ client.end(true);
21
+ reject(new Error(`MQTT discovery timed out (topic '${cfg.discoveryTopic}' on ${cfg.mqttBroker})`));
22
+ }, cfg.discoveryTimeoutMs);
23
+ client.on('connect', () => {
24
+ log.info(`MQTT connected (${cfg.mqttBroker}:${cfg.mqttPort}), waiting for discovery …`);
25
+ client.subscribe(cfg.discoveryTopic, { qos: 1 });
26
+ });
27
+ client.on('message', (_topic, payload) => {
28
+ try {
29
+ const data = JSON.parse(payload.toString());
30
+ if (data.host) {
31
+ clearTimeout(timer);
32
+ client.end();
33
+ resolve({ host: data.host, port: Number(data.port) || cfg.port });
34
+ }
35
+ }
36
+ catch {
37
+ /* ignore malformed discovery message */
38
+ }
39
+ });
40
+ client.on('error', e => {
41
+ clearTimeout(timer);
42
+ client.end(true);
43
+ reject(e);
44
+ });
45
+ });
46
+ }
47
+ //# sourceMappingURL=mqtt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mqtt.js","sourceRoot":"","sources":["../src/mqtt.ts"],"names":[],"mappings":";;;;;AAQA,wCAkCC;AA1CD;;;GAGG;AACH,gDAAwB;AAIxB,SAAgB,cAAc,CAAC,GAAoB,EAAE,GAAW;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,cAAI,CAAC,OAAO,CAAC,UAAU,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,QAAQ,EAAE,EAAE;YACpE,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,SAAS;YACnC,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,SAAS;YACnC,cAAc,EAAE,GAAG,CAAC,kBAAkB;SACzC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjB,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,GAAG,CAAC,cAAc,QAAQ,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QACvG,CAAC,EAAE,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAE3B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACtB,GAAG,CAAC,IAAI,CAAC,mBAAmB,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,QAAQ,4BAA4B,CAAC,CAAC;YACxF,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;YACrC,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAmC,CAAC;gBAC9E,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBACZ,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,CAAC,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;gBACtE,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACL,wCAAwC;YAC5C,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjB,MAAM,CAAC,CAAC,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Voice-satellite UDP wire protocol — must match the adapter's
3
+ * `ioBroker.assistant/src/lib/voice/protocol.ts` (and the Hannah satellite).
4
+ *
5
+ * One UDP socket, first byte is the packet type:
6
+ * 0x01 CONTROL — UTF-8 JSON (both directions)
7
+ * 0x02 AUDIO — raw 16 kHz mono 16-bit signed LE PCM (satellite → server)
8
+ * 0x03 TTS — raw mono 16-bit signed LE PCM (server → satellite)
9
+ *
10
+ * ⚠️ KEEP IN SYNC with the adapter copy.
11
+ */
12
+ export declare const TYPE_CONTROL = 1;
13
+ export declare const TYPE_AUDIO = 2;
14
+ export declare const TYPE_TTS = 3;
15
+ /** Audio streamed to the server is always 16 kHz, mono, 16-bit signed little-endian PCM. */
16
+ export declare const AUDIO_SAMPLE_RATE = 16000;
17
+ /** Control messages the satellite sends to the server. */
18
+ export type SatToServer = {
19
+ type: 'register';
20
+ device: string;
21
+ room?: string;
22
+ listen_port?: number;
23
+ } | {
24
+ type: 'heartbeat';
25
+ device: string;
26
+ } | {
27
+ type: 'audio_end';
28
+ device: string;
29
+ };
30
+ export type SatelliteState = 'idle' | 'listening' | 'processing' | 'speaking';
31
+ /** Control messages the satellite receives from the server. */
32
+ export type ServerToSat = {
33
+ type: 'registered';
34
+ ok: boolean;
35
+ } | {
36
+ type: 'heartbeat_ack';
37
+ } | {
38
+ type: 'reregister';
39
+ } | {
40
+ type: 'tts_end';
41
+ sample_rate: number;
42
+ } | {
43
+ type: 'status';
44
+ state: SatelliteState;
45
+ };
46
+ /** Prefix a control JSON message with the CONTROL type byte. */
47
+ export declare function encodeControl(msg: SatToServer): Buffer;
48
+ /** Prefix a raw PCM chunk with the AUDIO type byte. */
49
+ export declare function encodeAudio(pcm: Buffer): Buffer;
50
+ /** Split an incoming datagram into its type byte and payload (a view onto the original buffer). */
51
+ export declare function decodePacket(data: Buffer): {
52
+ type: number;
53
+ payload: Buffer;
54
+ };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Voice-satellite UDP wire protocol — must match the adapter's
4
+ * `ioBroker.assistant/src/lib/voice/protocol.ts` (and the Hannah satellite).
5
+ *
6
+ * One UDP socket, first byte is the packet type:
7
+ * 0x01 CONTROL — UTF-8 JSON (both directions)
8
+ * 0x02 AUDIO — raw 16 kHz mono 16-bit signed LE PCM (satellite → server)
9
+ * 0x03 TTS — raw mono 16-bit signed LE PCM (server → satellite)
10
+ *
11
+ * ⚠️ KEEP IN SYNC with the adapter copy.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.AUDIO_SAMPLE_RATE = exports.TYPE_TTS = exports.TYPE_AUDIO = exports.TYPE_CONTROL = void 0;
15
+ exports.encodeControl = encodeControl;
16
+ exports.encodeAudio = encodeAudio;
17
+ exports.decodePacket = decodePacket;
18
+ exports.TYPE_CONTROL = 0x01;
19
+ exports.TYPE_AUDIO = 0x02;
20
+ exports.TYPE_TTS = 0x03;
21
+ /** Audio streamed to the server is always 16 kHz, mono, 16-bit signed little-endian PCM. */
22
+ exports.AUDIO_SAMPLE_RATE = 16000;
23
+ /** Prefix a control JSON message with the CONTROL type byte. */
24
+ function encodeControl(msg) {
25
+ return Buffer.concat([Buffer.from([exports.TYPE_CONTROL]), Buffer.from(JSON.stringify(msg), 'utf8')]);
26
+ }
27
+ /** Prefix a raw PCM chunk with the AUDIO type byte. */
28
+ function encodeAudio(pcm) {
29
+ return Buffer.concat([Buffer.from([exports.TYPE_AUDIO]), pcm]);
30
+ }
31
+ /** Split an incoming datagram into its type byte and payload (a view onto the original buffer). */
32
+ function decodePacket(data) {
33
+ return { type: data[0], payload: data.subarray(1) };
34
+ }
35
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AA0BH,sCAEC;AAGD,kCAEC;AAGD,oCAEC;AApCY,QAAA,YAAY,GAAG,IAAI,CAAC;AACpB,QAAA,UAAU,GAAG,IAAI,CAAC;AAClB,QAAA,QAAQ,GAAG,IAAI,CAAC;AAE7B,4FAA4F;AAC/E,QAAA,iBAAiB,GAAG,KAAK,CAAC;AAkBvC,gEAAgE;AAChE,SAAgB,aAAa,CAAC,GAAgB;IAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,oBAAY,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,uDAAuD;AACvD,SAAgB,WAAW,CAAC,GAAW;IACnC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,kBAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,mGAAmG;AACnG,SAAgB,YAAY,CAAC,IAAY;IACrC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;AACxD,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { SatelliteConfig } from './config';
2
+ import type { SatelliteHost } from './index';
3
+ export declare class Satellite {
4
+ private readonly cfg;
5
+ private readonly host;
6
+ private readonly log;
7
+ private socket;
8
+ private serverHost;
9
+ private serverPort;
10
+ private wakeword;
11
+ private mic;
12
+ private readonly backend;
13
+ private plingPcm;
14
+ private heartbeatTimer;
15
+ private micRemainder;
16
+ private pumping;
17
+ private micBytesLogged;
18
+ private wakeFrames;
19
+ private wakeMaxScore;
20
+ private wakeMaxRms;
21
+ private recording;
22
+ private silence;
23
+ private preBuffer;
24
+ private recFrames;
25
+ private recPeakRms;
26
+ private ttsChunks;
27
+ private ttsDiscard;
28
+ private registerResolve;
29
+ private running;
30
+ private heartbeatMisses;
31
+ private awaitingHeartbeatAck;
32
+ private reconnecting;
33
+ constructor(cfg: SatelliteConfig, host: SatelliteHost);
34
+ start(): Promise<void>;
35
+ stop(): Promise<void>;
36
+ private resolveAddress;
37
+ private openSocket;
38
+ /** Register, retrying with exponential backoff until the adapter acknowledges (or we stop). */
39
+ private registerWithRetry;
40
+ /** Triggered when heartbeats stop being acknowledged: re-register (adapter probably restarted). */
41
+ private reconnect;
42
+ private heartbeatTick;
43
+ private register;
44
+ private onMicData;
45
+ private pump;
46
+ private handleFrame;
47
+ private startRecording;
48
+ private endRecording;
49
+ private onMessage;
50
+ private playTts;
51
+ private sendAudio;
52
+ private sendControl;
53
+ private setStatus;
54
+ }