iobroker.assistant 1.4.7 → 1.4.9
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 +10 -0
- package/admin/custom/.vite/manifest.json +1 -1
- package/admin/custom/assets/{Components-BR57MZLJ.js → Components-srYDLjt6.js} +2 -2
- package/admin/custom/customComponents.js +1 -1
- package/admin/custom/index.html +1 -1
- package/admin/custom/mf-manifest.json +1 -1
- package/admin/custom/mf-stats.json +1 -1
- package/build/lib/piAgent.js +2 -0
- package/build/lib/piAgent.js.map +1 -1
- package/build/lib/timers.js +7 -0
- package/build/lib/timers.js.map +1 -1
- package/build/lib/voice/esphome.js +49 -25
- package/build/lib/voice/esphome.js.map +1 -1
- package/build/lib/voice/esphomeEntities.js +52 -0
- package/build/lib/voice/esphomeEntities.js.map +1 -1
- package/build/lib/voice/esphomeProto.js.map +1 -1
- package/build/main.js +97 -19
- package/build/main.js.map +1 -1
- package/io-package.json +27 -27
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.EntityRegistry = exports.UPDATE_COMMANDS = exports.MEDIA_COMMANDS = void 0;
|
|
4
4
|
exports.float32 = float32;
|
|
5
|
+
exports.matchesPattern = matchesPattern;
|
|
5
6
|
exports.esphomeObjectId = esphomeObjectId;
|
|
6
7
|
exports.entityObjectId = entityObjectId;
|
|
7
8
|
exports.isListEntityMessage = isListEntityMessage;
|
|
@@ -46,6 +47,18 @@ exports.UPDATE_COMMANDS = { none: 0, install: 1, check: 2 };
|
|
|
46
47
|
function float32(value) {
|
|
47
48
|
return Number.isFinite(value) ? Number(value.toPrecision(7)) : value;
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether `text` matches a text entity's pattern, the way an HTML `pattern` attribute does (the whole value). A pattern
|
|
52
|
+
* JavaScript cannot compile is not held against the value — the device does not check it either.
|
|
53
|
+
*/
|
|
54
|
+
function matchesPattern(text, pattern) {
|
|
55
|
+
try {
|
|
56
|
+
return new RegExp(`^(?:${pattern})$`, 'u').test(text);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
49
62
|
/** Clamp to the device's own range — it ignores anything outside and we would never notice. */
|
|
50
63
|
function clamp(entity, value) {
|
|
51
64
|
const lo = entity.min ?? Number.NEGATIVE_INFINITY;
|
|
@@ -163,6 +176,45 @@ const KINDS = [
|
|
|
163
176
|
return message;
|
|
164
177
|
},
|
|
165
178
|
},
|
|
179
|
+
{
|
|
180
|
+
// Free text on the device: labels and units a satellite with a screen shows, so they can be set from
|
|
181
|
+
// ioBroker instead of being fixed when the firmware is built.
|
|
182
|
+
kind: 'text',
|
|
183
|
+
listMessage: 'ListEntitiesTextResponse',
|
|
184
|
+
stateMessage: 'TextStateResponse',
|
|
185
|
+
writable: true,
|
|
186
|
+
describe: m => {
|
|
187
|
+
const t = m;
|
|
188
|
+
return {
|
|
189
|
+
minLength: t.getMinLength() || undefined,
|
|
190
|
+
maxLength: t.getMaxLength() || undefined,
|
|
191
|
+
pattern: t.getPattern() || undefined,
|
|
192
|
+
password: t.getMode() === 1, // TEXT_MODE_PASSWORD
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
readState: m => {
|
|
196
|
+
const s = m;
|
|
197
|
+
return s.getMissingState() ? undefined : s.getState();
|
|
198
|
+
},
|
|
199
|
+
command: (entity, value) => {
|
|
200
|
+
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const text = String(value);
|
|
204
|
+
// The firmware drops a value outside the limits with a log line of its own; we would never notice.
|
|
205
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
206
|
+
if ((entity.minLength && bytes < entity.minLength) || (entity.maxLength && bytes > entity.maxLength)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (entity.pattern && !matchesPattern(text, entity.pattern)) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const message = new esphomeProto_1.pb.TextCommandRequest();
|
|
213
|
+
message.setKey(entity.key);
|
|
214
|
+
message.setState(text);
|
|
215
|
+
return message;
|
|
216
|
+
},
|
|
217
|
+
},
|
|
166
218
|
{
|
|
167
219
|
kind: 'event',
|
|
168
220
|
listMessage: 'ListEntitiesEventResponse',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"esphomeEntities.js","sourceRoot":"","sources":["../../../src/lib/voice/esphomeEntities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ESPHome entity layer: everything a voice satellite exposes *besides* the voice pipeline.\n *\n * A device announces its entities once, in response to `ListEntitiesRequest`, and then pushes a state\n * message whenever one changes. Each entity kind has its own pair of messages plus a command message to\n * write it back — `SwitchCommandRequest`, `NumberCommandRequest` and so on, all addressed by the\n * `fixed32 key` from the announcement.\n *\n * This is deliberately driven by {@link KINDS} rather than by the object ids of any one product: a\n * ThirdReality speaker offers microphone gain, noise suppression and two wake-word sensitivities, a Home\n * Assistant Voice PE offers a different set, and both work without a change here. Only entity kinds a\n * voice satellite can plausibly carry are handled; lights, covers and the rest of the ESPHome zoo are\n * ignored, because this adapter drives speakers, not a general ESPHome bridge.\n */\nimport { pb, type PbMessage } from './esphomeProto';\nimport type { MediaFormat } from './replyFormat';\n\n/** Entity kinds we understand. */\nexport type EntityKind =\n 'switch' | 'number' | 'select' | 'event' | 'update' | 'mediaPlayer' | 'sensor' | 'binarySensor' | 'textSensor';\n\n/** `EntityCategory` in api.proto — config/diagnostic entities are settings, not primary controls. */\nexport type EntityCategory = 'none' | 'config' | 'diagnostic';\nconst CATEGORIES: EntityCategory[] = ['none', 'config', 'diagnostic'];\n\n/** One entity as announced by the device, plus its last known value. */\nexport interface EsphomeEntity {\n kind: EntityKind;\n /** Stable id within the device (`mic_volume`), used as the ioBroker state name. */\n objectId: string;\n /** Wire handle the command messages address. */\n key: number;\n /** Human-readable name as the device labels it. */\n name: string;\n category: EntityCategory;\n icon?: string;\n /** Whether a value can be written back (false for `event`, which is read-only). */\n writable: boolean;\n /** number: range and unit. */\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n /** sensor: how many decimals the device considers meaningful. */\n decimals?: number;\n /** select: allowed values. */\n options?: string[];\n /** event: the types the device can fire. */\n eventTypes?: string[];\n /** mediaPlayer: whether it accepts a pause command. */\n supportsPause?: boolean;\n /** mediaPlayer: the audio formats it plays (empty on devices that do not say). */\n formats?: MediaFormat[];\n /** Last value the device reported; undefined until the first state message. */\n value?: EntityValue;\n}\n\n/** A value as it travels to and from ioBroker. `update` reports an object, the rest are scalars. */\nexport type EntityValue = boolean | number | string | UpdateInfo | MediaPlayerValue;\n\nexport interface UpdateInfo {\n currentVersion: string;\n latestVersion: string;\n title: string;\n inProgress: boolean;\n progress: number;\n releaseUrl: string;\n}\n\nexport interface MediaPlayerValue {\n state: 'none' | 'idle' | 'playing' | 'paused' | 'announcing' | 'off' | 'on';\n volume: number;\n muted: boolean;\n}\n\nconst MEDIA_STATES = ['none', 'idle', 'playing', 'paused', 'announcing', 'off', 'on'] as const;\n\n/** `MediaPlayerCommand` in api.proto, by the name we accept from ioBroker. */\nexport const MEDIA_COMMANDS: Record<string, number> = {\n play: 0,\n pause: 1,\n stop: 2,\n mute: 3,\n unmute: 4,\n toggle: 5,\n volume_up: 6,\n volume_down: 7,\n turn_on: 12,\n turn_off: 13,\n};\n\n/** `UpdateCommand` in api.proto. */\nexport const UPDATE_COMMANDS: Record<string, number> = { none: 0, install: 1, check: 2 };\n\n/**\n * Minimal structural typing over the generated announcement classes. Every `ListEntities*Response`\n * shares the first four fields, which is what makes the table-driven approach below possible.\n */\ninterface ListEntityMsg {\n getObjectId(): string;\n getKey(): number;\n getName(): string;\n getEntityCategory(): number;\n getIcon?(): string;\n getDisabledByDefault?(): boolean;\n}\n\n/** How one entity kind is announced, reported and written. */\ninterface KindSpec {\n kind: EntityKind;\n /** `ListEntities…Response` message name. */\n listMessage: string;\n /** `…StateResponse` message name (absent for `event`, which pushes `EventResponse`). */\n stateMessage: string;\n writable: boolean;\n /** Pull the kind-specific announcement fields out. */\n describe?: (msg: unknown) => Partial<EsphomeEntity>;\n /** Read the value out of a state message. */\n readState: (msg: unknown) => EntityValue | undefined;\n /** Build the command message that writes `value`, or null when the value makes no sense. */\n command: (entity: EsphomeEntity, value: unknown) => PbMessage | null;\n}\n\n/**\n * A `float` field of the ESPHome API as the number the device meant. The protobuf floats are 32 bit, so 0.5 from\n * the device arrives as 0.5000000237487257 and a step of 0.001 as 0.0010000000474974513 — shown as is in ioBroker\n * and in the settings dialog. float32 carries about 7 significant digits; everything after that is noise.\n */\nexport function float32(value: number): number {\n return Number.isFinite(value) ? Number(value.toPrecision(7)) : value;\n}\n\n/** Clamp to the device's own range — it ignores anything outside and we would never notice. */\nfunction clamp(entity: EsphomeEntity, value: number): number {\n const lo = entity.min ?? Number.NEGATIVE_INFINITY;\n const hi = entity.max ?? Number.POSITIVE_INFINITY;\n return Math.min(hi, Math.max(lo, value));\n}\n\nconst KINDS: KindSpec[] = [\n // ── read-only measurements ────────────────────────────────────────────────────────────────────\n // A voice satellite is also a box in a room: it usually reports its own temperature, whether\n // something was detected, or a status text. Read-only, so they only ever produce states.\n {\n kind: 'sensor',\n listMessage: 'ListEntitiesSensorResponse',\n stateMessage: 'SensorStateResponse',\n writable: false,\n describe: m => {\n const sensor = m as { getUnitOfMeasurement(): string; getAccuracyDecimals(): number };\n return {\n unit: sensor.getUnitOfMeasurement() || undefined,\n // The device tells us how many decimals are meaningful; more would be noise.\n decimals: sensor.getAccuracyDecimals(),\n };\n },\n readState: m => {\n const state = m as { getState(): number; getMissingState(): boolean };\n // `missing_state` means the device has no reading yet — not that the value is 0.\n return state.getMissingState() ? undefined : float32(state.getState());\n },\n command: () => null,\n },\n {\n kind: 'binarySensor',\n listMessage: 'ListEntitiesBinarySensorResponse',\n stateMessage: 'BinarySensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): boolean; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'textSensor',\n listMessage: 'ListEntitiesTextSensorResponse',\n stateMessage: 'TextSensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): string; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'switch',\n listMessage: 'ListEntitiesSwitchResponse',\n stateMessage: 'SwitchStateResponse',\n writable: true,\n readState: m => (m as { getState(): boolean }).getState(),\n command: (entity, value) => {\n const message = new pb.SwitchCommandRequest();\n message.setKey(entity.key);\n message.setState(value === true || value === 'true' || value === 1);\n return message;\n },\n },\n {\n kind: 'number',\n listMessage: 'ListEntitiesNumberResponse',\n stateMessage: 'NumberStateResponse',\n writable: true,\n describe: m => {\n const n = m as {\n getMinValue(): number;\n getMaxValue(): number;\n getStep(): number;\n getUnitOfMeasurement(): string;\n };\n return {\n min: float32(n.getMinValue()),\n max: float32(n.getMaxValue()),\n step: float32(n.getStep()),\n unit: n.getUnitOfMeasurement(),\n };\n },\n readState: m => {\n const n = m as { getState(): number; getMissingState(): boolean };\n return n.getMissingState() ? undefined : float32(n.getState());\n },\n command: (entity, value) => {\n const num = Number(value);\n if (!Number.isFinite(num)) {\n return null;\n }\n const message = new pb.NumberCommandRequest();\n message.setKey(entity.key);\n message.setState(clamp(entity, num));\n return message;\n },\n },\n {\n kind: 'select',\n listMessage: 'ListEntitiesSelectResponse',\n stateMessage: 'SelectStateResponse',\n writable: true,\n describe: m => ({ options: (m as { getOptionsList(): string[] }).getOptionsList() }),\n readState: m => {\n const s = m as { getState(): string; getMissingState(): boolean };\n return s.getMissingState() ? undefined : s.getState();\n },\n command: (entity, value) => {\n // Match case-insensitively: the device only accepts one of its own options verbatim.\n const wanted = String(value).trim().toLowerCase();\n const option = entity.options?.find(o => o.toLowerCase() === wanted);\n if (!option) {\n return null;\n }\n const message = new pb.SelectCommandRequest();\n message.setKey(entity.key);\n message.setState(option);\n return message;\n },\n },\n {\n kind: 'event',\n listMessage: 'ListEntitiesEventResponse',\n stateMessage: 'EventResponse',\n writable: false,\n describe: m => ({ eventTypes: (m as { getEventTypesList(): string[] }).getEventTypesList() }),\n readState: m => (m as { getEventType(): string }).getEventType(),\n command: () => null,\n },\n {\n kind: 'update',\n listMessage: 'ListEntitiesUpdateResponse',\n stateMessage: 'UpdateStateResponse',\n writable: true,\n readState: m => {\n const u = m as {\n getMissingState(): boolean;\n getCurrentVersion(): string;\n getLatestVersion(): string;\n getTitle(): string;\n getInProgress(): boolean;\n getProgress(): number;\n getReleaseUrl(): string;\n };\n if (u.getMissingState()) {\n return undefined;\n }\n return {\n currentVersion: u.getCurrentVersion(),\n latestVersion: u.getLatestVersion(),\n title: u.getTitle(),\n inProgress: u.getInProgress(),\n progress: float32(u.getProgress()),\n releaseUrl: u.getReleaseUrl(),\n };\n },\n command: (entity, value) => {\n const command = UPDATE_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined || command === 0) {\n return null;\n }\n const message = new pb.UpdateCommandRequest();\n message.setKey(entity.key);\n message.setCommand(command);\n return message;\n },\n },\n {\n kind: 'mediaPlayer',\n listMessage: 'ListEntitiesMediaPlayerResponse',\n stateMessage: 'MediaPlayerStateResponse',\n writable: true,\n describe: m => {\n const player = m as {\n getSupportsPause(): boolean;\n getSupportedFormatsList?(): {\n getFormat(): string;\n getSampleRate(): number;\n getNumChannels(): number;\n getPurpose(): number;\n getSampleBytes(): number;\n }[];\n };\n return {\n supportsPause: player.getSupportsPause(),\n formats: (player.getSupportedFormatsList?.() ?? []).map(f => ({\n format: f.getFormat(),\n sampleRate: f.getSampleRate(),\n channels: f.getNumChannels(),\n purpose: f.getPurpose() === 1 ? 'announcement' : 'default',\n sampleBytes: f.getSampleBytes(),\n })),\n };\n },\n readState: m => {\n const p = m as { getState(): number; getVolume(): number; getMuted(): boolean };\n return { state: MEDIA_STATES[p.getState()] ?? 'none', volume: float32(p.getVolume()), muted: p.getMuted() };\n },\n command: (entity, value) => {\n const message = new pb.MediaPlayerCommandRequest();\n message.setKey(entity.key);\n // A number is a volume (0…1), a string is one of the named commands.\n if (typeof value === 'number' || (typeof value === 'string' && /^[\\d.]+$/.test(value.trim()))) {\n message.setHasVolume(true);\n message.setVolume(Math.min(1, Math.max(0, Number(value))));\n return message;\n }\n const command = MEDIA_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined) {\n return null;\n }\n message.setHasCommand(true);\n message.setCommand(command);\n return message;\n },\n },\n];\n\nconst BY_LIST_MESSAGE = new Map(KINDS.map(k => [k.listMessage, k]));\nconst BY_STATE_MESSAGE = new Map(KINDS.map(k => [k.stateMessage, k]));\nconst BY_KIND = new Map(KINDS.map(k => [k.kind, k]));\n\n/**\n * ESPHome's object id for an entity name, as the firmware computes it (`EntityBase::write_object_id_to`): byte by\n * byte, a space becomes `_`, A–Z lower case, anything else outside `[-_0-9a-z]` `_`.\n */\nexport function esphomeObjectId(name: string): string {\n let out = '';\n for (const byte of Buffer.from(name, 'utf8')) {\n const c = byte === 0x20 ? 0x5f : byte >= 0x41 && byte <= 0x5a ? byte + 0x20 : byte;\n const keep = c === 0x2d || c === 0x5f || (c >= 0x30 && c <= 0x39) || (c >= 0x61 && c <= 0x7a);\n out += keep ? String.fromCharCode(c) : '_';\n }\n return out;\n}\n\n/**\n * The state name of an entity. ESPHome 2026 (API 1.19) no longer sends `object_id` at all — only `key` and `name` —\n * so it has to be derived again (field report 2026-11-03: every control of a device ended up as\n * `satellites.<id>.controls.`, an invalid id). A Latin name gets exactly the id older firmware sent, so the\n * existing objects stay in use. A name in another script would come out as nothing but underscores under ESPHome's\n * rule, so it keeps its letters (ioBroker ids allow them); without any letter or digit the kind and key are left.\n *\n * @param announced `object_id` from the device ('' on current firmware)\n * @param name the entity's name, or the device's friendly name for an entity without one of its own\n */\nexport function entityObjectId(announced: string, name: string, kind: EntityKind, key: number): string {\n if (announced) {\n return announced;\n }\n // eslint-disable-next-line no-control-regex\n if (/^[\\x00-\\x7f]*$/.test(name) && /[a-z0-9]/i.test(name)) {\n return esphomeObjectId(name);\n }\n const unicode = name\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '_')\n .replace(/[^\\p{Ll}\\p{Lu}\\p{Nd}_-]/gu, '_');\n if (/[\\p{Ll}\\p{Lu}\\p{Nd}]/u.test(unicode)) {\n return unicode;\n }\n return `${kind.replace(/[A-Z]/g, c => `_${c.toLowerCase()}`)}_${key.toString(16)}`;\n}\n\n/** True when this message announces an entity kind we handle. */\nexport function isListEntityMessage(name: string): boolean {\n return BY_LIST_MESSAGE.has(name);\n}\n\n/** True when this message carries a state for an entity kind we handle. */\nexport function isEntityStateMessage(name: string): boolean {\n return BY_STATE_MESSAGE.has(name);\n}\n\n/**\n * The entities of one device. Announcements arrive once after `ListEntitiesRequest`; state messages\n * arrive continuously and are matched back by `key`.\n */\nexport class EntityRegistry {\n private readonly byKey = new Map<number, EsphomeEntity>();\n private readonly byObjectId = new Map<string, EsphomeEntity>();\n\n /** Every entity the device announced, in announcement order. */\n list(): EsphomeEntity[] {\n return [...this.byKey.values()];\n }\n\n get(objectId: string): EsphomeEntity | undefined {\n return this.byObjectId.get(objectId);\n }\n\n /** Forget everything — the device re-announces after every reconnect. */\n clear(): void {\n this.byKey.clear();\n this.byObjectId.clear();\n }\n\n /**\n * Take a `ListEntities…Response`. Returns the entity, or null for a kind we ignore.\n *\n * @param name message name\n * @param message the announcement\n * @param deviceName the device's friendly name: an entity without a name of its own (the media player of a\n * Voice PE) is named after the device — current firmware sends such a name empty.\n */\n addFromAnnouncement(name: string, message: unknown, deviceName = ''): EsphomeEntity | null {\n const spec = BY_LIST_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const base = message as ListEntityMsg;\n const key = base.getKey();\n const label = base.getName() || deviceName;\n let objectId = entityObjectId(base.getObjectId(), label, spec.kind, key);\n const taken = this.byObjectId.get(objectId);\n if (taken && taken.key !== key) {\n // Two entities with the same name (on different sub-devices): keep both apart.\n objectId = `${objectId}_${key.toString(16)}`;\n }\n const entity: EsphomeEntity = {\n kind: spec.kind,\n objectId,\n key,\n name: label,\n category: CATEGORIES[base.getEntityCategory()] ?? 'none',\n icon: base.getIcon?.() || undefined,\n writable: spec.writable,\n ...spec.describe?.(message),\n };\n this.byKey.set(entity.key, entity);\n this.byObjectId.set(entity.objectId, entity);\n return entity;\n }\n\n /**\n * Take a `…StateResponse`. Returns the updated entity, or null when it belongs to an entity the\n * device never announced — which happens legitimately, because the device pushes the current state\n * of everything as soon as we subscribe, possibly before its announcements have arrived.\n */\n applyState(name: string, message: unknown): EsphomeEntity | null {\n const spec = BY_STATE_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const entity = this.byKey.get((message as { getKey(): number }).getKey());\n if (!entity) {\n return null;\n }\n const value = spec.readState(message);\n if (value === undefined) {\n return null;\n }\n entity.value = value;\n return entity;\n }\n\n /**\n * Build the command message that writes `value` to `objectId`, or null when the entity is unknown,\n * read-only, or the value is not one it accepts.\n */\n command(objectId: string, value: unknown): PbMessage | null {\n const entity = this.byObjectId.get(objectId);\n if (!entity?.writable) {\n return null;\n }\n return BY_KIND.get(entity.kind)?.command(entity, value) ?? null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"esphomeEntities.js","sourceRoot":"","sources":["../../../src/lib/voice/esphomeEntities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ESPHome entity layer: everything a voice satellite exposes *besides* the voice pipeline.\n *\n * A device announces its entities once, in response to `ListEntitiesRequest`, and then pushes a state\n * message whenever one changes. Each entity kind has its own pair of messages plus a command message to\n * write it back — `SwitchCommandRequest`, `NumberCommandRequest` and so on, all addressed by the\n * `fixed32 key` from the announcement.\n *\n * This is deliberately driven by {@link KINDS} rather than by the object ids of any one product: a\n * ThirdReality speaker offers microphone gain, noise suppression and two wake-word sensitivities, a Home\n * Assistant Voice PE offers a different set, and both work without a change here. Only entity kinds a\n * voice satellite can plausibly carry are handled; lights, covers and the rest of the ESPHome zoo are\n * ignored, because this adapter drives speakers, not a general ESPHome bridge.\n */\nimport { pb, type PbMessage } from './esphomeProto';\nimport type { MediaFormat } from './replyFormat';\n\n/** Entity kinds we understand. */\nexport type EntityKind =\n | 'switch'\n | 'number'\n | 'select'\n | 'text'\n | 'event'\n | 'update'\n | 'mediaPlayer'\n | 'sensor'\n | 'binarySensor'\n | 'textSensor';\n\n/** `EntityCategory` in api.proto — config/diagnostic entities are settings, not primary controls. */\nexport type EntityCategory = 'none' | 'config' | 'diagnostic';\nconst CATEGORIES: EntityCategory[] = ['none', 'config', 'diagnostic'];\n\n/** One entity as announced by the device, plus its last known value. */\nexport interface EsphomeEntity {\n kind: EntityKind;\n /** Stable id within the device (`mic_volume`), used as the ioBroker state name. */\n objectId: string;\n /** Wire handle the command messages address. */\n key: number;\n /** Human-readable name as the device labels it. */\n name: string;\n category: EntityCategory;\n icon?: string;\n /** Whether a value can be written back (false for `event`, which is read-only). */\n writable: boolean;\n /** number: range and unit. */\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n /** sensor: how many decimals the device considers meaningful. */\n decimals?: number;\n /** select: allowed values. */\n options?: string[];\n /** text: length limits in bytes of UTF-8 (the firmware counts bytes), and a pattern for its frontend. */\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n /** text: `mode: password` — the value is a secret. */\n password?: boolean;\n /** event: the types the device can fire. */\n eventTypes?: string[];\n /** mediaPlayer: whether it accepts a pause command. */\n supportsPause?: boolean;\n /** mediaPlayer: the audio formats it plays (empty on devices that do not say). */\n formats?: MediaFormat[];\n /** Last value the device reported; undefined until the first state message. */\n value?: EntityValue;\n}\n\n/** A value as it travels to and from ioBroker. `update` reports an object, the rest are scalars. */\nexport type EntityValue = boolean | number | string | UpdateInfo | MediaPlayerValue;\n\nexport interface UpdateInfo {\n currentVersion: string;\n latestVersion: string;\n title: string;\n inProgress: boolean;\n progress: number;\n releaseUrl: string;\n}\n\nexport interface MediaPlayerValue {\n state: 'none' | 'idle' | 'playing' | 'paused' | 'announcing' | 'off' | 'on';\n volume: number;\n muted: boolean;\n}\n\nconst MEDIA_STATES = ['none', 'idle', 'playing', 'paused', 'announcing', 'off', 'on'] as const;\n\n/** `MediaPlayerCommand` in api.proto, by the name we accept from ioBroker. */\nexport const MEDIA_COMMANDS: Record<string, number> = {\n play: 0,\n pause: 1,\n stop: 2,\n mute: 3,\n unmute: 4,\n toggle: 5,\n volume_up: 6,\n volume_down: 7,\n turn_on: 12,\n turn_off: 13,\n};\n\n/** `UpdateCommand` in api.proto. */\nexport const UPDATE_COMMANDS: Record<string, number> = { none: 0, install: 1, check: 2 };\n\n/**\n * Minimal structural typing over the generated announcement classes. Every `ListEntities*Response`\n * shares the first four fields, which is what makes the table-driven approach below possible.\n */\ninterface ListEntityMsg {\n getObjectId(): string;\n getKey(): number;\n getName(): string;\n getEntityCategory(): number;\n getIcon?(): string;\n getDisabledByDefault?(): boolean;\n}\n\n/** How one entity kind is announced, reported and written. */\ninterface KindSpec {\n kind: EntityKind;\n /** `ListEntities…Response` message name. */\n listMessage: string;\n /** `…StateResponse` message name (absent for `event`, which pushes `EventResponse`). */\n stateMessage: string;\n writable: boolean;\n /** Pull the kind-specific announcement fields out. */\n describe?: (msg: unknown) => Partial<EsphomeEntity>;\n /** Read the value out of a state message. */\n readState: (msg: unknown) => EntityValue | undefined;\n /** Build the command message that writes `value`, or null when the value makes no sense. */\n command: (entity: EsphomeEntity, value: unknown) => PbMessage | null;\n}\n\n/**\n * A `float` field of the ESPHome API as the number the device meant. The protobuf floats are 32 bit, so 0.5 from\n * the device arrives as 0.5000000237487257 and a step of 0.001 as 0.0010000000474974513 — shown as is in ioBroker\n * and in the settings dialog. float32 carries about 7 significant digits; everything after that is noise.\n */\nexport function float32(value: number): number {\n return Number.isFinite(value) ? Number(value.toPrecision(7)) : value;\n}\n\n/**\n * Whether `text` matches a text entity's pattern, the way an HTML `pattern` attribute does (the whole value). A pattern\n * JavaScript cannot compile is not held against the value — the device does not check it either.\n */\nexport function matchesPattern(text: string, pattern: string): boolean {\n try {\n return new RegExp(`^(?:${pattern})$`, 'u').test(text);\n } catch {\n return true;\n }\n}\n\n/** Clamp to the device's own range — it ignores anything outside and we would never notice. */\nfunction clamp(entity: EsphomeEntity, value: number): number {\n const lo = entity.min ?? Number.NEGATIVE_INFINITY;\n const hi = entity.max ?? Number.POSITIVE_INFINITY;\n return Math.min(hi, Math.max(lo, value));\n}\n\nconst KINDS: KindSpec[] = [\n // ── read-only measurements ────────────────────────────────────────────────────────────────────\n // A voice satellite is also a box in a room: it usually reports its own temperature, whether\n // something was detected, or a status text. Read-only, so they only ever produce states.\n {\n kind: 'sensor',\n listMessage: 'ListEntitiesSensorResponse',\n stateMessage: 'SensorStateResponse',\n writable: false,\n describe: m => {\n const sensor = m as { getUnitOfMeasurement(): string; getAccuracyDecimals(): number };\n return {\n unit: sensor.getUnitOfMeasurement() || undefined,\n // The device tells us how many decimals are meaningful; more would be noise.\n decimals: sensor.getAccuracyDecimals(),\n };\n },\n readState: m => {\n const state = m as { getState(): number; getMissingState(): boolean };\n // `missing_state` means the device has no reading yet — not that the value is 0.\n return state.getMissingState() ? undefined : float32(state.getState());\n },\n command: () => null,\n },\n {\n kind: 'binarySensor',\n listMessage: 'ListEntitiesBinarySensorResponse',\n stateMessage: 'BinarySensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): boolean; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'textSensor',\n listMessage: 'ListEntitiesTextSensorResponse',\n stateMessage: 'TextSensorStateResponse',\n writable: false,\n readState: m => {\n const state = m as { getState(): string; getMissingState(): boolean };\n return state.getMissingState() ? undefined : state.getState();\n },\n command: () => null,\n },\n {\n kind: 'switch',\n listMessage: 'ListEntitiesSwitchResponse',\n stateMessage: 'SwitchStateResponse',\n writable: true,\n readState: m => (m as { getState(): boolean }).getState(),\n command: (entity, value) => {\n const message = new pb.SwitchCommandRequest();\n message.setKey(entity.key);\n message.setState(value === true || value === 'true' || value === 1);\n return message;\n },\n },\n {\n kind: 'number',\n listMessage: 'ListEntitiesNumberResponse',\n stateMessage: 'NumberStateResponse',\n writable: true,\n describe: m => {\n const n = m as {\n getMinValue(): number;\n getMaxValue(): number;\n getStep(): number;\n getUnitOfMeasurement(): string;\n };\n return {\n min: float32(n.getMinValue()),\n max: float32(n.getMaxValue()),\n step: float32(n.getStep()),\n unit: n.getUnitOfMeasurement(),\n };\n },\n readState: m => {\n const n = m as { getState(): number; getMissingState(): boolean };\n return n.getMissingState() ? undefined : float32(n.getState());\n },\n command: (entity, value) => {\n const num = Number(value);\n if (!Number.isFinite(num)) {\n return null;\n }\n const message = new pb.NumberCommandRequest();\n message.setKey(entity.key);\n message.setState(clamp(entity, num));\n return message;\n },\n },\n {\n kind: 'select',\n listMessage: 'ListEntitiesSelectResponse',\n stateMessage: 'SelectStateResponse',\n writable: true,\n describe: m => ({ options: (m as { getOptionsList(): string[] }).getOptionsList() }),\n readState: m => {\n const s = m as { getState(): string; getMissingState(): boolean };\n return s.getMissingState() ? undefined : s.getState();\n },\n command: (entity, value) => {\n // Match case-insensitively: the device only accepts one of its own options verbatim.\n const wanted = String(value).trim().toLowerCase();\n const option = entity.options?.find(o => o.toLowerCase() === wanted);\n if (!option) {\n return null;\n }\n const message = new pb.SelectCommandRequest();\n message.setKey(entity.key);\n message.setState(option);\n return message;\n },\n },\n {\n // Free text on the device: labels and units a satellite with a screen shows, so they can be set from\n // ioBroker instead of being fixed when the firmware is built.\n kind: 'text',\n listMessage: 'ListEntitiesTextResponse',\n stateMessage: 'TextStateResponse',\n writable: true,\n describe: m => {\n const t = m as { getMinLength(): number; getMaxLength(): number; getPattern(): string; getMode(): number };\n return {\n minLength: t.getMinLength() || undefined,\n maxLength: t.getMaxLength() || undefined,\n pattern: t.getPattern() || undefined,\n password: t.getMode() === 1, // TEXT_MODE_PASSWORD\n };\n },\n readState: m => {\n const s = m as { getState(): string; getMissingState(): boolean };\n return s.getMissingState() ? undefined : s.getState();\n },\n command: (entity, value) => {\n if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {\n return null;\n }\n const text = String(value);\n // The firmware drops a value outside the limits with a log line of its own; we would never notice.\n const bytes = Buffer.byteLength(text, 'utf8');\n if ((entity.minLength && bytes < entity.minLength) || (entity.maxLength && bytes > entity.maxLength)) {\n return null;\n }\n if (entity.pattern && !matchesPattern(text, entity.pattern)) {\n return null;\n }\n const message = new pb.TextCommandRequest();\n message.setKey(entity.key);\n message.setState(text);\n return message;\n },\n },\n {\n kind: 'event',\n listMessage: 'ListEntitiesEventResponse',\n stateMessage: 'EventResponse',\n writable: false,\n describe: m => ({ eventTypes: (m as { getEventTypesList(): string[] }).getEventTypesList() }),\n readState: m => (m as { getEventType(): string }).getEventType(),\n command: () => null,\n },\n {\n kind: 'update',\n listMessage: 'ListEntitiesUpdateResponse',\n stateMessage: 'UpdateStateResponse',\n writable: true,\n readState: m => {\n const u = m as {\n getMissingState(): boolean;\n getCurrentVersion(): string;\n getLatestVersion(): string;\n getTitle(): string;\n getInProgress(): boolean;\n getProgress(): number;\n getReleaseUrl(): string;\n };\n if (u.getMissingState()) {\n return undefined;\n }\n return {\n currentVersion: u.getCurrentVersion(),\n latestVersion: u.getLatestVersion(),\n title: u.getTitle(),\n inProgress: u.getInProgress(),\n progress: float32(u.getProgress()),\n releaseUrl: u.getReleaseUrl(),\n };\n },\n command: (entity, value) => {\n const command = UPDATE_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined || command === 0) {\n return null;\n }\n const message = new pb.UpdateCommandRequest();\n message.setKey(entity.key);\n message.setCommand(command);\n return message;\n },\n },\n {\n kind: 'mediaPlayer',\n listMessage: 'ListEntitiesMediaPlayerResponse',\n stateMessage: 'MediaPlayerStateResponse',\n writable: true,\n describe: m => {\n const player = m as {\n getSupportsPause(): boolean;\n getSupportedFormatsList?(): {\n getFormat(): string;\n getSampleRate(): number;\n getNumChannels(): number;\n getPurpose(): number;\n getSampleBytes(): number;\n }[];\n };\n return {\n supportsPause: player.getSupportsPause(),\n formats: (player.getSupportedFormatsList?.() ?? []).map(f => ({\n format: f.getFormat(),\n sampleRate: f.getSampleRate(),\n channels: f.getNumChannels(),\n purpose: f.getPurpose() === 1 ? 'announcement' : 'default',\n sampleBytes: f.getSampleBytes(),\n })),\n };\n },\n readState: m => {\n const p = m as { getState(): number; getVolume(): number; getMuted(): boolean };\n return { state: MEDIA_STATES[p.getState()] ?? 'none', volume: float32(p.getVolume()), muted: p.getMuted() };\n },\n command: (entity, value) => {\n const message = new pb.MediaPlayerCommandRequest();\n message.setKey(entity.key);\n // A number is a volume (0…1), a string is one of the named commands.\n if (typeof value === 'number' || (typeof value === 'string' && /^[\\d.]+$/.test(value.trim()))) {\n message.setHasVolume(true);\n message.setVolume(Math.min(1, Math.max(0, Number(value))));\n return message;\n }\n const command = MEDIA_COMMANDS[String(value).trim().toLowerCase()];\n if (command === undefined) {\n return null;\n }\n message.setHasCommand(true);\n message.setCommand(command);\n return message;\n },\n },\n];\n\nconst BY_LIST_MESSAGE = new Map(KINDS.map(k => [k.listMessage, k]));\nconst BY_STATE_MESSAGE = new Map(KINDS.map(k => [k.stateMessage, k]));\nconst BY_KIND = new Map(KINDS.map(k => [k.kind, k]));\n\n/**\n * ESPHome's object id for an entity name, as the firmware computes it (`EntityBase::write_object_id_to`): byte by\n * byte, a space becomes `_`, A–Z lower case, anything else outside `[-_0-9a-z]` `_`.\n */\nexport function esphomeObjectId(name: string): string {\n let out = '';\n for (const byte of Buffer.from(name, 'utf8')) {\n const c = byte === 0x20 ? 0x5f : byte >= 0x41 && byte <= 0x5a ? byte + 0x20 : byte;\n const keep = c === 0x2d || c === 0x5f || (c >= 0x30 && c <= 0x39) || (c >= 0x61 && c <= 0x7a);\n out += keep ? String.fromCharCode(c) : '_';\n }\n return out;\n}\n\n/**\n * The state name of an entity. ESPHome 2026 (API 1.19) no longer sends `object_id` at all — only `key` and `name` —\n * so it has to be derived again (field report 2026-11-03: every control of a device ended up as\n * `satellites.<id>.controls.`, an invalid id). A Latin name gets exactly the id older firmware sent, so the\n * existing objects stay in use. A name in another script would come out as nothing but underscores under ESPHome's\n * rule, so it keeps its letters (ioBroker ids allow them); without any letter or digit the kind and key are left.\n *\n * @param announced `object_id` from the device ('' on current firmware)\n * @param name the entity's name, or the device's friendly name for an entity without one of its own\n */\nexport function entityObjectId(announced: string, name: string, kind: EntityKind, key: number): string {\n if (announced) {\n return announced;\n }\n // eslint-disable-next-line no-control-regex\n if (/^[\\x00-\\x7f]*$/.test(name) && /[a-z0-9]/i.test(name)) {\n return esphomeObjectId(name);\n }\n const unicode = name\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '_')\n .replace(/[^\\p{Ll}\\p{Lu}\\p{Nd}_-]/gu, '_');\n if (/[\\p{Ll}\\p{Lu}\\p{Nd}]/u.test(unicode)) {\n return unicode;\n }\n return `${kind.replace(/[A-Z]/g, c => `_${c.toLowerCase()}`)}_${key.toString(16)}`;\n}\n\n/** True when this message announces an entity kind we handle. */\nexport function isListEntityMessage(name: string): boolean {\n return BY_LIST_MESSAGE.has(name);\n}\n\n/** True when this message carries a state for an entity kind we handle. */\nexport function isEntityStateMessage(name: string): boolean {\n return BY_STATE_MESSAGE.has(name);\n}\n\n/**\n * The entities of one device. Announcements arrive once after `ListEntitiesRequest`; state messages\n * arrive continuously and are matched back by `key`.\n */\nexport class EntityRegistry {\n private readonly byKey = new Map<number, EsphomeEntity>();\n private readonly byObjectId = new Map<string, EsphomeEntity>();\n\n /** Every entity the device announced, in announcement order. */\n list(): EsphomeEntity[] {\n return [...this.byKey.values()];\n }\n\n get(objectId: string): EsphomeEntity | undefined {\n return this.byObjectId.get(objectId);\n }\n\n /** Forget everything — the device re-announces after every reconnect. */\n clear(): void {\n this.byKey.clear();\n this.byObjectId.clear();\n }\n\n /**\n * Take a `ListEntities…Response`. Returns the entity, or null for a kind we ignore.\n *\n * @param name message name\n * @param message the announcement\n * @param deviceName the device's friendly name: an entity without a name of its own (the media player of a\n * Voice PE) is named after the device — current firmware sends such a name empty.\n */\n addFromAnnouncement(name: string, message: unknown, deviceName = ''): EsphomeEntity | null {\n const spec = BY_LIST_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const base = message as ListEntityMsg;\n const key = base.getKey();\n const label = base.getName() || deviceName;\n let objectId = entityObjectId(base.getObjectId(), label, spec.kind, key);\n const taken = this.byObjectId.get(objectId);\n if (taken && taken.key !== key) {\n // Two entities with the same name (on different sub-devices): keep both apart.\n objectId = `${objectId}_${key.toString(16)}`;\n }\n const entity: EsphomeEntity = {\n kind: spec.kind,\n objectId,\n key,\n name: label,\n category: CATEGORIES[base.getEntityCategory()] ?? 'none',\n icon: base.getIcon?.() || undefined,\n writable: spec.writable,\n ...spec.describe?.(message),\n };\n this.byKey.set(entity.key, entity);\n this.byObjectId.set(entity.objectId, entity);\n return entity;\n }\n\n /**\n * Take a `…StateResponse`. Returns the updated entity, or null when it belongs to an entity the\n * device never announced — which happens legitimately, because the device pushes the current state\n * of everything as soon as we subscribe, possibly before its announcements have arrived.\n */\n applyState(name: string, message: unknown): EsphomeEntity | null {\n const spec = BY_STATE_MESSAGE.get(name);\n if (!spec) {\n return null;\n }\n const entity = this.byKey.get((message as { getKey(): number }).getKey());\n if (!entity) {\n return null;\n }\n const value = spec.readState(message);\n if (value === undefined) {\n return null;\n }\n entity.value = value;\n return entity;\n }\n\n /**\n * Build the command message that writes `value` to `objectId`, or null when the entity is unknown,\n * read-only, or the value is not one it accepts.\n */\n command(objectId: string, value: unknown): PbMessage | null {\n const entity = this.byObjectId.get(objectId);\n if (!entity?.writable) {\n return null;\n }\n return BY_KIND.get(entity.kind)?.command(entity, value) ?? null;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"esphomeProto.js","sourceRoot":"","sources":["../../../src/lib/voice/esphomeProto.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmRA,wBAOC;AAgBD,wBA0CC;AApVD;;;;;;;;;;;;;;;;GAgBG;AACH,4CAA8B;AAC9B,gDAAkC;AAwKlC,MAAM,aAAa,GAAG,+CAA+C,CAAC;AAEzD,QAAA,EAAE,GAAG,OAAO,CAAC,aAAa,CAAU,CAAC;AASlD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;AAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAwB,CAAC;AAClD,iFAAiF;AACjF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;AAE5C,SAAS,aAAa;IAClB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAClD,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,OAAO,EAAE,CAAC;YACV,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACrB,SAAS;QACb,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACf,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACb,CAAC;QACD,MAAM,MAAM,GAAG,wCAAwC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACtB,SAAS;QACb,CAAC;QACD,MAAM,IAAI,GAAG,UAAE,CAAC,OAAO,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,EAAE,CAAC;YACR,SAAS,CAAC,wEAAwE;QACtF,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC1B,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,IAA+B,CAAC,CAAC;QACnD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC3B,CAAC;AACL,CAAC;AACD,aAAa,EAAE,CAAC;AAEhB,sGAAsG;AACzF,QAAA,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC;AAE1C,uGAAuG;AAEvG,SAAS,YAAY,CAAC,KAAa;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC;IACvB,GAAG,CAAC;QACA,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;QACvB,IAAI,MAAM,CAAC,CAAC;QACZ,IAAI,IAAI,EAAE,CAAC;YACP,IAAI,IAAI,IAAI,CAAC;QACjB,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,QAAQ,IAAI,EAAE;IACf,OAAO,GAAG,CAAC;AACf,CAAC;AAED,kGAAkG;AAClG,SAAS,UAAU,CAAC,GAAW,EAAE,MAAc;IAC3C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,MAAM,CAAC;IACjB,OAAO,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QACxB,MAAM,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,CAAC;QACjC,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QAChD,CAAC;QACD,KAAK,IAAI,CAAC,CAAC;QACX,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACxC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,uGAAuG;AAEvG,4DAA4D;AAC5D,SAAgB,MAAM,CAAC,OAAkB;IACrC,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAE,OAAkB,CAAC,WAAW,CAAC,CAAC;IACzD,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5G,CAAC;AAYD;;;GAGG;AACH,SAAgB,MAAM,CAAC,MAAc;IACjC,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC5B,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACX,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;gBAChB,CAAC,CAAC,8EAA8E;gBAChF,CAAC,CAAC,wBAAwB,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAC9D,CAAC;QACN,CAAC;QACD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM;QACV,CAAC;QACD,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,EAAE,EAAE,CAAC;YACN,MAAM;QACV,CAAC;QACD,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;QACrC,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,MAAM,CAAC,+BAA+B;QAC1C,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;QAC5C,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,OAAO,GAAY,IAAI,CAAC;QAC5B,IAAI,KAAK,GAAiB,IAAI,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACP,IAAI,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YAC9C,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,KAAK,GAAG,CAAU,CAAC;YACvB,CAAC;QACL,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;AACrD,CAAC","sourcesContent":["/**\n * ESPHome native-API wire codec (plain-text framing) — the transport half of `esphome.ts`.\n *\n * Frame layout: `0x00 | varint(payload length) | varint(message id) | payload (protobuf)`.\n *\n * Only the generated protobuf classes of `@2colors/esphome-native-api` are reused, not its\n * `Connection`/`FrameHelper`: that package's id↔type table stops at the Bluetooth range and omits the\n * whole VoiceAssistant block (89–92, 106, 115, 119–123). Worse, an unknown id makes its frame helper\n * throw *without advancing the read buffer*, so the connection stalls instead of skipping the message.\n * The registry here is derived from the `api.proto` the package ships, which covers every message the\n * device can send, and unknown ids are skipped by their declared length.\n *\n * The device speaks plain text (no noise encryption) — confirmed in the firmware's\n * `linux-voice-assistant-cpp` README (\"ESPHome native API server (TCP 6053, plain-text framing,\n * protobuf)\") — so the encrypted variant is deliberately not implemented; `decode` reports it clearly\n * if a device ever shows up with encryption switched on.\n */\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n// ── minimal structural typing over the generated protobuf classes ───────────────────────────────────\n\nexport interface PbMessage {\n serializeBinary(): Uint8Array;\n}\n\n/** Messages we build and send (client → device). */\nexport interface HelloRequest extends PbMessage {\n setClientInfo(value: string): void;\n setApiVersionMajor(value: number): void;\n setApiVersionMinor(value: number): void;\n}\nexport interface AuthenticationRequest extends PbMessage {\n setPassword(value: string): void;\n}\nexport interface SubscribeVoiceAssistantRequest extends PbMessage {\n setSubscribe(value: boolean): void;\n setFlags(value: number): void;\n}\nexport interface VoiceAssistantResponseMsg extends PbMessage {\n setPort(value: number): void;\n setError(value: boolean): void;\n}\nexport interface VoiceAssistantEventData extends PbMessage {\n setName(value: string): void;\n setValue(value: string): void;\n}\nexport interface VoiceAssistantEventResponse extends PbMessage {\n setEventType(value: number): void;\n setDataList(value: VoiceAssistantEventData[]): void;\n}\nexport interface VoiceAssistantAnnounceRequest extends PbMessage {\n setMediaId(value: string): void;\n setText(value: string): void;\n setPreannounceMediaId(value: string): void;\n setStartConversation(value: boolean): void;\n}\nexport interface GetTimeResponse extends PbMessage {\n setEpochSeconds(value: number): void;\n}\n/** Selects which of the device's wake words are listening; the device persists this itself. */\nexport interface VoiceAssistantSetConfiguration extends PbMessage {\n setActiveWakeWordsList(value: string[]): void;\n}\n/** A model the device may download when it is selected (custom wake words). */\nexport interface VoiceAssistantExternalWakeWord extends PbMessage {\n setId(value: string): void;\n setWakeWord(value: string): void;\n setTrainedLanguagesList(value: string[]): void;\n setModelType(value: string): void;\n setModelSize(value: number): void;\n setModelHash(value: string): void;\n setUrl(value: string): void;\n}\n/** Asks for the wake-word configuration — and offers the external ones, replacing the last offer. */\nexport interface VoiceAssistantConfigurationRequest extends PbMessage {\n setExternalWakeWordsList(value: VoiceAssistantExternalWakeWord[]): void;\n}\n\n// Entity command messages. All address an entity by the `fixed32 key` from its announcement.\nexport interface SwitchCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: boolean): void;\n}\nexport interface NumberCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: number): void;\n}\nexport interface SelectCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: string): void;\n}\nexport interface UpdateCommandRequest extends PbMessage {\n setKey(value: number): void;\n setCommand(value: number): void;\n}\nexport interface MediaPlayerCommandRequest extends PbMessage {\n setKey(value: number): void;\n setHasCommand(value: boolean): void;\n setCommand(value: number): void;\n setHasVolume(value: boolean): void;\n setVolume(value: number): void;\n}\n/** Mirrors an assistant timer onto the device, which drives its own LED ring and gong. */\nexport interface VoiceAssistantTimerEventResponse extends PbMessage {\n setEventType(value: number): void;\n setTimerId(value: string): void;\n setName(value: string): void;\n setTotalSeconds(value: number): void;\n setSecondsLeft(value: number): void;\n setIsActive(value: boolean): void;\n}\n\n/** Messages we receive (device → client). */\nexport interface HelloResponse {\n getApiVersionMajor(): number;\n getApiVersionMinor(): number;\n getServerInfo(): string;\n getName(): string;\n}\nexport interface AuthenticationResponse {\n getInvalidPassword(): boolean;\n}\nexport interface DeviceInfoResponse {\n getName(): string;\n /** What the device is called in the UI; entities without a name of their own are named after it. */\n getFriendlyName(): string;\n getModel(): string;\n getEsphomeVersion(): string;\n getMacAddress(): string;\n}\nexport interface VoiceAssistantWakeWord {\n getId(): string;\n getWakeWord(): string;\n /** ISO-639-1 codes the model was trained on — only `okay_nabu` covers more than English. */\n getTrainedLanguagesList(): string[];\n}\nexport interface VoiceAssistantConfigurationResponse {\n getAvailableWakeWordsList(): VoiceAssistantWakeWord[];\n getActiveWakeWordsList(): string[];\n /** How many wake words may listen at once (2 on the ThirdReality speaker). */\n getMaxActiveWakeWords(): number;\n}\nexport interface VoiceAssistantRequestMsg {\n getStart(): boolean;\n getConversationId(): string;\n getWakeWordPhrase(): string;\n}\nexport interface VoiceAssistantAudio {\n getData_asU8(): Uint8Array;\n getEnd(): boolean;\n}\nexport interface VoiceAssistantAnnounceFinished {\n getSuccess(): boolean;\n}\n\ntype Ctor<T> = new () => T;\n\ninterface ApiPb {\n HelloRequest: Ctor<HelloRequest>;\n AuthenticationRequest: Ctor<AuthenticationRequest>;\n DeviceInfoRequest: Ctor<PbMessage>;\n ListEntitiesRequest: Ctor<PbMessage>;\n SubscribeStatesRequest: Ctor<PbMessage>;\n SubscribeVoiceAssistantRequest: Ctor<SubscribeVoiceAssistantRequest>;\n VoiceAssistantConfigurationRequest: Ctor<VoiceAssistantConfigurationRequest>;\n VoiceAssistantExternalWakeWord: Ctor<VoiceAssistantExternalWakeWord>;\n VoiceAssistantSetConfiguration: Ctor<VoiceAssistantSetConfiguration>;\n VoiceAssistantTimerEventResponse: Ctor<VoiceAssistantTimerEventResponse>;\n SwitchCommandRequest: Ctor<SwitchCommandRequest>;\n NumberCommandRequest: Ctor<NumberCommandRequest>;\n SelectCommandRequest: Ctor<SelectCommandRequest>;\n UpdateCommandRequest: Ctor<UpdateCommandRequest>;\n MediaPlayerCommandRequest: Ctor<MediaPlayerCommandRequest>;\n VoiceAssistantResponse: Ctor<VoiceAssistantResponseMsg>;\n VoiceAssistantEventResponse: Ctor<VoiceAssistantEventResponse>;\n VoiceAssistantEventData: Ctor<VoiceAssistantEventData>;\n VoiceAssistantAnnounceRequest: Ctor<VoiceAssistantAnnounceRequest>;\n PingRequest: Ctor<PbMessage>;\n PingResponse: Ctor<PbMessage>;\n GetTimeResponse: Ctor<GetTimeResponse>;\n DisconnectRequest: Ctor<PbMessage>;\n DisconnectResponse: Ctor<PbMessage>;\n [name: string]: Ctor<unknown> | undefined;\n}\n\nconst API_PB_MODULE = '@2colors/esphome-native-api/lib/protoc/api_pb';\n\nexport const pb = require(API_PB_MODULE) as ApiPb;\n\n// ── message registry, built from the shipped api.proto ──────────────────────────────────────────────\n\n/** Deserialiser for one message id. */\ninterface Deserialiser {\n deserializeBinary(bytes: Uint8Array): unknown;\n}\n\nconst idToName = new Map<number, string>();\nconst classById = new Map<number, Deserialiser>();\n/** Message class → its wire id, so `encode` can find the id from an instance. */\nconst idByCtor = new Map<unknown, number>();\n\nfunction buildRegistry(): void {\n const protoFile = path.join(path.dirname(require.resolve(API_PB_MODULE)), 'api.proto');\n const source = fs.readFileSync(protoFile, 'utf8');\n let current: string | null = null;\n\n for (const raw of source.split(/\\r?\\n/)) {\n const line = raw.trim();\n const message = /^message\\s+(\\w+)\\s*\\{/.exec(line);\n if (message) {\n current = message[1];\n continue;\n }\n if (line === '}') {\n current = null;\n continue;\n }\n const option = /^option\\s*\\(\\s*id\\s*\\)\\s*=\\s*(\\d+)\\s*;/.exec(line);\n if (!option || !current) {\n continue;\n }\n const ctor = pb[current];\n if (!ctor) {\n continue; // in the .proto but not in the generated JS — nothing we can do with it\n }\n const id = Number(option[1]);\n idToName.set(id, current);\n classById.set(id, ctor as unknown as Deserialiser);\n idByCtor.set(ctor, id);\n }\n}\nbuildRegistry();\n\n/** Number of messages the registry knows (130 with the shipped api.proto) — used by the unit test. */\nexport const registrySize = idToName.size;\n\n// ── varint helpers ──────────────────────────────────────────────────────────────────────────────────\n\nfunction encodeVarint(value: number): number[] {\n const out: number[] = [];\n let rest = value >>> 0;\n do {\n let byte = rest & 0x7f;\n rest >>>= 7;\n if (rest) {\n byte |= 0x80;\n }\n out.push(byte);\n } while (rest);\n return out;\n}\n\n/** Read a varint at `offset`; null when the buffer holds only part of it (wait for more data). */\nfunction readVarint(buf: Buffer, offset: number): { value: number; offset: number } | null {\n let result = 0;\n let shift = 0;\n let pos = offset;\n while (pos < buf.length) {\n const byte = buf[pos++];\n result |= (byte & 0x7f) << shift;\n if (!(byte & 0x80)) {\n return { value: result >>> 0, offset: pos };\n }\n shift += 7;\n if (shift > 28) {\n throw new Error('malformed varint');\n }\n }\n return null;\n}\n\n// ── framing ─────────────────────────────────────────────────────────────────────────────────────────\n\n/** Serialise one protobuf message into a complete frame. */\nexport function encode(message: PbMessage): Buffer {\n const id = idByCtor.get((message as object).constructor);\n if (id === undefined) {\n throw new Error('message class is not registered in the ESPHome id table');\n }\n const payload = Buffer.from(message.serializeBinary());\n return Buffer.concat([Buffer.from([0, ...encodeVarint(payload.length), ...encodeVarint(id)]), payload]);\n}\n\nexport interface DecodedFrame {\n id: number;\n /** null for a message id the registry doesn't know — the frame was skipped by its declared length. */\n name: string | null;\n /** The decoded message, or null when the id is unknown or the payload didn't parse. */\n message: unknown;\n /** Set when a known message failed to deserialise. */\n error: Error | null;\n}\n\n/**\n * Pull every complete frame out of `buffer`. Returns the frames plus the unconsumed tail; the input is\n * never mutated, so the caller just keeps assigning `rest` back to its receive buffer.\n */\nexport function decode(buffer: Buffer): { frames: DecodedFrame[]; rest: Buffer } {\n const frames: DecodedFrame[] = [];\n let offset = 0;\n\n while (offset < buffer.length) {\n if (buffer[offset] !== 0) {\n throw new Error(\n buffer[offset] === 1\n ? 'device uses the encrypted (noise) ESPHome API — only plain text is supported'\n : `bad frame preamble 0x${buffer[offset].toString(16)}`,\n );\n }\n const length = readVarint(buffer, offset + 1);\n if (!length) {\n break;\n }\n const id = readVarint(buffer, length.offset);\n if (!id) {\n break;\n }\n const end = id.offset + length.value;\n if (end > buffer.length) {\n break; // frame not fully received yet\n }\n\n const payload = buffer.subarray(id.offset, end);\n const name = idToName.get(id.value) ?? null;\n const ctor = classById.get(id.value);\n let message: unknown = null;\n let error: Error | null = null;\n if (ctor) {\n try {\n message = ctor.deserializeBinary(payload);\n } catch (e) {\n error = e as Error;\n }\n }\n frames.push({ id: id.value, name, message, error });\n offset = end;\n }\n\n return { frames, rest: buffer.subarray(offset) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"esphomeProto.js","sourceRoot":"","sources":["../../../src/lib/voice/esphomeProto.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESPHome native-API wire codec (plain-text framing) — the transport half of `esphome.ts`.\n *\n * Frame layout: `0x00 | varint(payload length) | varint(message id) | payload (protobuf)`.\n *\n * Only the generated protobuf classes of `@2colors/esphome-native-api` are reused, not its\n * `Connection`/`FrameHelper`: that package's id↔type table stops at the Bluetooth range and omits the\n * whole VoiceAssistant block (89–92, 106, 115, 119–123). Worse, an unknown id makes its frame helper\n * throw *without advancing the read buffer*, so the connection stalls instead of skipping the message.\n * The registry here is derived from the `api.proto` the package ships, which covers every message the\n * device can send, and unknown ids are skipped by their declared length.\n *\n * The device speaks plain text (no noise encryption) — confirmed in the firmware's\n * `linux-voice-assistant-cpp` README (\"ESPHome native API server (TCP 6053, plain-text framing,\n * protobuf)\") — so the encrypted variant is deliberately not implemented; `decode` reports it clearly\n * if a device ever shows up with encryption switched on.\n */\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n// ── minimal structural typing over the generated protobuf classes ───────────────────────────────────\n\nexport interface PbMessage {\n serializeBinary(): Uint8Array;\n}\n\n/** Messages we build and send (client → device). */\nexport interface HelloRequest extends PbMessage {\n setClientInfo(value: string): void;\n setApiVersionMajor(value: number): void;\n setApiVersionMinor(value: number): void;\n}\nexport interface AuthenticationRequest extends PbMessage {\n setPassword(value: string): void;\n}\nexport interface SubscribeVoiceAssistantRequest extends PbMessage {\n setSubscribe(value: boolean): void;\n setFlags(value: number): void;\n}\nexport interface VoiceAssistantResponseMsg extends PbMessage {\n setPort(value: number): void;\n setError(value: boolean): void;\n}\nexport interface VoiceAssistantEventData extends PbMessage {\n setName(value: string): void;\n setValue(value: string): void;\n}\nexport interface VoiceAssistantEventResponse extends PbMessage {\n setEventType(value: number): void;\n setDataList(value: VoiceAssistantEventData[]): void;\n}\nexport interface VoiceAssistantAnnounceRequest extends PbMessage {\n setMediaId(value: string): void;\n setText(value: string): void;\n setPreannounceMediaId(value: string): void;\n setStartConversation(value: boolean): void;\n}\nexport interface GetTimeResponse extends PbMessage {\n setEpochSeconds(value: number): void;\n}\n/** Selects which of the device's wake words are listening; the device persists this itself. */\nexport interface VoiceAssistantSetConfiguration extends PbMessage {\n setActiveWakeWordsList(value: string[]): void;\n}\n/** A model the device may download when it is selected (custom wake words). */\nexport interface VoiceAssistantExternalWakeWord extends PbMessage {\n setId(value: string): void;\n setWakeWord(value: string): void;\n setTrainedLanguagesList(value: string[]): void;\n setModelType(value: string): void;\n setModelSize(value: number): void;\n setModelHash(value: string): void;\n setUrl(value: string): void;\n}\n/** Asks for the wake-word configuration — and offers the external ones, replacing the last offer. */\nexport interface VoiceAssistantConfigurationRequest extends PbMessage {\n setExternalWakeWordsList(value: VoiceAssistantExternalWakeWord[]): void;\n}\n\n// Entity command messages. All address an entity by the `fixed32 key` from its announcement.\nexport interface SwitchCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: boolean): void;\n}\nexport interface NumberCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: number): void;\n}\nexport interface SelectCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: string): void;\n}\nexport interface TextCommandRequest extends PbMessage {\n setKey(value: number): void;\n setState(value: string): void;\n}\nexport interface UpdateCommandRequest extends PbMessage {\n setKey(value: number): void;\n setCommand(value: number): void;\n}\nexport interface MediaPlayerCommandRequest extends PbMessage {\n setKey(value: number): void;\n setHasCommand(value: boolean): void;\n setCommand(value: number): void;\n setHasVolume(value: boolean): void;\n setVolume(value: number): void;\n}\n/** Mirrors an assistant timer onto the device, which drives its own LED ring and gong. */\nexport interface VoiceAssistantTimerEventResponse extends PbMessage {\n setEventType(value: number): void;\n setTimerId(value: string): void;\n setName(value: string): void;\n setTotalSeconds(value: number): void;\n setSecondsLeft(value: number): void;\n setIsActive(value: boolean): void;\n}\n\n/** Messages we receive (device → client). */\nexport interface HelloResponse {\n getApiVersionMajor(): number;\n getApiVersionMinor(): number;\n getServerInfo(): string;\n getName(): string;\n}\nexport interface AuthenticationResponse {\n getInvalidPassword(): boolean;\n}\nexport interface DeviceInfoResponse {\n getName(): string;\n /** What the device is called in the UI; entities without a name of their own are named after it. */\n getFriendlyName(): string;\n getModel(): string;\n getEsphomeVersion(): string;\n getMacAddress(): string;\n}\nexport interface VoiceAssistantWakeWord {\n getId(): string;\n getWakeWord(): string;\n /** ISO-639-1 codes the model was trained on — only `okay_nabu` covers more than English. */\n getTrainedLanguagesList(): string[];\n}\nexport interface VoiceAssistantConfigurationResponse {\n getAvailableWakeWordsList(): VoiceAssistantWakeWord[];\n getActiveWakeWordsList(): string[];\n /** How many wake words may listen at once (2 on the ThirdReality speaker). */\n getMaxActiveWakeWords(): number;\n}\nexport interface VoiceAssistantRequestMsg {\n getStart(): boolean;\n getConversationId(): string;\n getWakeWordPhrase(): string;\n}\nexport interface VoiceAssistantAudio {\n getData_asU8(): Uint8Array;\n getEnd(): boolean;\n}\nexport interface VoiceAssistantAnnounceFinished {\n getSuccess(): boolean;\n}\n\ntype Ctor<T> = new () => T;\n\ninterface ApiPb {\n HelloRequest: Ctor<HelloRequest>;\n AuthenticationRequest: Ctor<AuthenticationRequest>;\n DeviceInfoRequest: Ctor<PbMessage>;\n ListEntitiesRequest: Ctor<PbMessage>;\n SubscribeStatesRequest: Ctor<PbMessage>;\n SubscribeVoiceAssistantRequest: Ctor<SubscribeVoiceAssistantRequest>;\n VoiceAssistantConfigurationRequest: Ctor<VoiceAssistantConfigurationRequest>;\n VoiceAssistantExternalWakeWord: Ctor<VoiceAssistantExternalWakeWord>;\n VoiceAssistantSetConfiguration: Ctor<VoiceAssistantSetConfiguration>;\n VoiceAssistantTimerEventResponse: Ctor<VoiceAssistantTimerEventResponse>;\n SwitchCommandRequest: Ctor<SwitchCommandRequest>;\n NumberCommandRequest: Ctor<NumberCommandRequest>;\n SelectCommandRequest: Ctor<SelectCommandRequest>;\n TextCommandRequest: Ctor<TextCommandRequest>;\n UpdateCommandRequest: Ctor<UpdateCommandRequest>;\n MediaPlayerCommandRequest: Ctor<MediaPlayerCommandRequest>;\n VoiceAssistantResponse: Ctor<VoiceAssistantResponseMsg>;\n VoiceAssistantEventResponse: Ctor<VoiceAssistantEventResponse>;\n VoiceAssistantEventData: Ctor<VoiceAssistantEventData>;\n VoiceAssistantAnnounceRequest: Ctor<VoiceAssistantAnnounceRequest>;\n PingRequest: Ctor<PbMessage>;\n PingResponse: Ctor<PbMessage>;\n GetTimeResponse: Ctor<GetTimeResponse>;\n DisconnectRequest: Ctor<PbMessage>;\n DisconnectResponse: Ctor<PbMessage>;\n [name: string]: Ctor<unknown> | undefined;\n}\n\nconst API_PB_MODULE = '@2colors/esphome-native-api/lib/protoc/api_pb';\n\nexport const pb = require(API_PB_MODULE) as ApiPb;\n\n// ── message registry, built from the shipped api.proto ──────────────────────────────────────────────\n\n/** Deserialiser for one message id. */\ninterface Deserialiser {\n deserializeBinary(bytes: Uint8Array): unknown;\n}\n\nconst idToName = new Map<number, string>();\nconst classById = new Map<number, Deserialiser>();\n/** Message class → its wire id, so `encode` can find the id from an instance. */\nconst idByCtor = new Map<unknown, number>();\n\nfunction buildRegistry(): void {\n const protoFile = path.join(path.dirname(require.resolve(API_PB_MODULE)), 'api.proto');\n const source = fs.readFileSync(protoFile, 'utf8');\n let current: string | null = null;\n\n for (const raw of source.split(/\\r?\\n/)) {\n const line = raw.trim();\n const message = /^message\\s+(\\w+)\\s*\\{/.exec(line);\n if (message) {\n current = message[1];\n continue;\n }\n if (line === '}') {\n current = null;\n continue;\n }\n const option = /^option\\s*\\(\\s*id\\s*\\)\\s*=\\s*(\\d+)\\s*;/.exec(line);\n if (!option || !current) {\n continue;\n }\n const ctor = pb[current];\n if (!ctor) {\n continue; // in the .proto but not in the generated JS — nothing we can do with it\n }\n const id = Number(option[1]);\n idToName.set(id, current);\n classById.set(id, ctor as unknown as Deserialiser);\n idByCtor.set(ctor, id);\n }\n}\nbuildRegistry();\n\n/** Number of messages the registry knows (130 with the shipped api.proto) — used by the unit test. */\nexport const registrySize = idToName.size;\n\n// ── varint helpers ──────────────────────────────────────────────────────────────────────────────────\n\nfunction encodeVarint(value: number): number[] {\n const out: number[] = [];\n let rest = value >>> 0;\n do {\n let byte = rest & 0x7f;\n rest >>>= 7;\n if (rest) {\n byte |= 0x80;\n }\n out.push(byte);\n } while (rest);\n return out;\n}\n\n/** Read a varint at `offset`; null when the buffer holds only part of it (wait for more data). */\nfunction readVarint(buf: Buffer, offset: number): { value: number; offset: number } | null {\n let result = 0;\n let shift = 0;\n let pos = offset;\n while (pos < buf.length) {\n const byte = buf[pos++];\n result |= (byte & 0x7f) << shift;\n if (!(byte & 0x80)) {\n return { value: result >>> 0, offset: pos };\n }\n shift += 7;\n if (shift > 28) {\n throw new Error('malformed varint');\n }\n }\n return null;\n}\n\n// ── framing ─────────────────────────────────────────────────────────────────────────────────────────\n\n/** Serialise one protobuf message into a complete frame. */\nexport function encode(message: PbMessage): Buffer {\n const id = idByCtor.get((message as object).constructor);\n if (id === undefined) {\n throw new Error('message class is not registered in the ESPHome id table');\n }\n const payload = Buffer.from(message.serializeBinary());\n return Buffer.concat([Buffer.from([0, ...encodeVarint(payload.length), ...encodeVarint(id)]), payload]);\n}\n\nexport interface DecodedFrame {\n id: number;\n /** null for a message id the registry doesn't know — the frame was skipped by its declared length. */\n name: string | null;\n /** The decoded message, or null when the id is unknown or the payload didn't parse. */\n message: unknown;\n /** Set when a known message failed to deserialise. */\n error: Error | null;\n}\n\n/**\n * Pull every complete frame out of `buffer`. Returns the frames plus the unconsumed tail; the input is\n * never mutated, so the caller just keeps assigning `rest` back to its receive buffer.\n */\nexport function decode(buffer: Buffer): { frames: DecodedFrame[]; rest: Buffer } {\n const frames: DecodedFrame[] = [];\n let offset = 0;\n\n while (offset < buffer.length) {\n if (buffer[offset] !== 0) {\n throw new Error(\n buffer[offset] === 1\n ? 'device uses the encrypted (noise) ESPHome API — only plain text is supported'\n : `bad frame preamble 0x${buffer[offset].toString(16)}`,\n );\n }\n const length = readVarint(buffer, offset + 1);\n if (!length) {\n break;\n }\n const id = readVarint(buffer, length.offset);\n if (!id) {\n break;\n }\n const end = id.offset + length.value;\n if (end > buffer.length) {\n break; // frame not fully received yet\n }\n\n const payload = buffer.subarray(id.offset, end);\n const name = idToName.get(id.value) ?? null;\n const ctor = classById.get(id.value);\n let message: unknown = null;\n let error: Error | null = null;\n if (ctor) {\n try {\n message = ctor.deserializeBinary(payload);\n } catch (e) {\n error = e as Error;\n }\n }\n frames.push({ id: id.value, name, message, error });\n offset = end;\n }\n\n return { frames, rest: buffer.subarray(offset) };\n}\n"]}
|
package/build/main.js
CHANGED
|
@@ -996,6 +996,11 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
996
996
|
const roomName = (room || '').replace(/^(enum\.rooms\.|system\.rooms\.)/, '');
|
|
997
997
|
return (roomName || device).replace(/[^\w-]/g, '_') || 'unknown';
|
|
998
998
|
}
|
|
999
|
+
/** The id under `satellites.*` of the satellite a request came from, or '' when it has no states there (Wyoming). */
|
|
1000
|
+
knownSatelliteId(device, room) {
|
|
1001
|
+
const id = this.satelliteStateId(device, room);
|
|
1002
|
+
return this.satStatesEnsured.has(id) ? id : '';
|
|
1003
|
+
}
|
|
999
1004
|
/** Reflect a satellite's status into `satellites.<room>.*` states (created on first sight). */
|
|
1000
1005
|
async updateSatelliteState(device, room, state) {
|
|
1001
1006
|
const id = await this.ensureSatelliteObjects(device, room);
|
|
@@ -1441,7 +1446,11 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
1441
1446
|
await this.ensureState(`${id}.inProgress`, 'Update running', 'boolean', 'indicator', false);
|
|
1442
1447
|
await this.ensureState(`${id}.progress`, 'Update progress', 'number', 'value', false);
|
|
1443
1448
|
await this.ensureState(`${id}.install`, 'Install the update', 'boolean', 'button', true);
|
|
1449
|
+
// A device looks for a new version only now and then (an http_request update: at boot and every
|
|
1450
|
+
// 6 h); until then latestVersion shows the old one and install does nothing.
|
|
1451
|
+
await this.ensureState(`${id}.check`, 'Check for an update now', 'boolean', 'button', true);
|
|
1444
1452
|
this.subscribeStates(`${id}.install`);
|
|
1453
|
+
this.subscribeStates(`${id}.check`);
|
|
1445
1454
|
}
|
|
1446
1455
|
else if (entity.kind === 'event') {
|
|
1447
1456
|
await this.setObjectNotExistsAsync(id, {
|
|
@@ -1477,6 +1486,20 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
1477
1486
|
: { name, type: 'string', role: 'text', read: true, write: false, def: '' };
|
|
1478
1487
|
await this.setObjectNotExistsAsync(id, { type: 'state', common, native: {} });
|
|
1479
1488
|
}
|
|
1489
|
+
else if (entity.kind === 'text') {
|
|
1490
|
+
if (entity.password) {
|
|
1491
|
+
// A secret would sit in a state every script and admin user can read.
|
|
1492
|
+
this.log.debug(`Satellite ${device}: "${name}" is a password text — not mirrored`);
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
// Labels and units of a satellite with a screen, set from here instead of in the firmware build.
|
|
1496
|
+
await this.setObjectNotExistsAsync(id, {
|
|
1497
|
+
type: 'state',
|
|
1498
|
+
common: { name, type: 'string', role: 'text', read: true, write: true, def: '' },
|
|
1499
|
+
native: {},
|
|
1500
|
+
});
|
|
1501
|
+
this.subscribeStates(id);
|
|
1502
|
+
}
|
|
1480
1503
|
else {
|
|
1481
1504
|
const common = entity.kind === 'switch'
|
|
1482
1505
|
? { name, type: 'boolean', role: 'switch', read: true, write: true, def: false }
|
|
@@ -1559,7 +1582,7 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
1559
1582
|
}
|
|
1560
1583
|
/** Write one entity's current value into its state(s). Always acked — this is the device talking. */
|
|
1561
1584
|
async writeSatelliteControl(satId, entity) {
|
|
1562
|
-
if (!isControlId(entity.objectId)) {
|
|
1585
|
+
if (!isControlId(entity.objectId) || (entity.kind === 'text' && entity.password)) {
|
|
1563
1586
|
return; // never created (see buildSatelliteControls)
|
|
1564
1587
|
}
|
|
1565
1588
|
const base = `satellites.${satId}.controls.${entity.objectId}`;
|
|
@@ -1602,11 +1625,11 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
1602
1625
|
// Compound entities address a leaf: `<objectId>.volume`, `<objectId>.install`, …
|
|
1603
1626
|
const [objectId, leaf] = path.split('.');
|
|
1604
1627
|
let payload = value;
|
|
1605
|
-
if (leaf === 'install') {
|
|
1628
|
+
if (leaf === 'install' || leaf === 'check') {
|
|
1606
1629
|
if (!value) {
|
|
1607
1630
|
return; // button released
|
|
1608
1631
|
}
|
|
1609
|
-
payload =
|
|
1632
|
+
payload = leaf; // UpdateCommandRequest: install = UPDATE, check = CHECK
|
|
1610
1633
|
}
|
|
1611
1634
|
else if (leaf === 'volume' || leaf === 'command') {
|
|
1612
1635
|
payload = value;
|
|
@@ -2462,18 +2485,38 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
2462
2485
|
}
|
|
2463
2486
|
return;
|
|
2464
2487
|
}
|
|
2465
|
-
// Scripts: start a countdown timer. message = { duration: seconds | "5 min", label?, room
|
|
2488
|
+
// Scripts: start a countdown timer. message = { duration | seconds: seconds | "5 min", label | name?, room?,
|
|
2489
|
+
// satellite?, announce? }. With `satellite` (its id under satellites.*, or the device name) the timer is that
|
|
2490
|
+
// satellite's like one set there by voice: an ESPHome satellite shows and rings it, and the announcement plays
|
|
2491
|
+
// there. `announce` = what to say when it fires, word for word (default: "Timer finished: <name>.").
|
|
2466
2492
|
if (obj.command === 'setTimer') {
|
|
2467
2493
|
const m = (obj.message || {});
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2494
|
+
// `seconds` are seconds also as a string; a bare number in a `duration` string stays minutes as before
|
|
2495
|
+
const duration = m.seconds !== undefined
|
|
2496
|
+
? Math.round(Number(m.seconds) || 0) ||
|
|
2497
|
+
(typeof m.seconds === 'string' ? (0, nlu_1.parseDurationSeconds)(m.seconds, false) || 0 : 0)
|
|
2498
|
+
: typeof m.duration === 'string'
|
|
2499
|
+
? (0, nlu_1.parseDurationSeconds)(m.duration, true) || 0
|
|
2500
|
+
: Math.round(Number(m.duration) || 0);
|
|
2501
|
+
const satellite = this.timerSatellite(m.satellite);
|
|
2471
2502
|
let result;
|
|
2472
|
-
if (!this.timers
|
|
2473
|
-
result = { ok: false, error:
|
|
2503
|
+
if (!this.timers) {
|
|
2504
|
+
result = { ok: false, error: 'timers not ready' };
|
|
2505
|
+
}
|
|
2506
|
+
else if (duration <= 0) {
|
|
2507
|
+
result = { ok: false, error: 'duration must be > 0 seconds' };
|
|
2508
|
+
}
|
|
2509
|
+
else if (satellite === null) {
|
|
2510
|
+
result = { ok: false, error: `unknown satellite "${String(m.satellite)}"` };
|
|
2474
2511
|
}
|
|
2475
2512
|
else {
|
|
2476
|
-
const info = this.timers.add({
|
|
2513
|
+
const info = this.timers.add({
|
|
2514
|
+
label: String(m.name ?? m.label ?? ''),
|
|
2515
|
+
room: m.room || '',
|
|
2516
|
+
source: satellite,
|
|
2517
|
+
duration,
|
|
2518
|
+
announce: typeof m.announce === 'string' ? m.announce : '',
|
|
2519
|
+
});
|
|
2477
2520
|
result = { ok: true, id: info.id, fireAt: info.fireAt };
|
|
2478
2521
|
}
|
|
2479
2522
|
if (obj.callback) {
|
|
@@ -2481,19 +2524,37 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
2481
2524
|
}
|
|
2482
2525
|
return;
|
|
2483
2526
|
}
|
|
2484
|
-
// Scripts: cancel a timer by id, or all when no id is given. message = { id? }
|
|
2527
|
+
// Scripts: cancel a timer by id, or all when no id is given. message = { id?, satellite? } — with `satellite`
|
|
2528
|
+
// only that satellite's timers.
|
|
2485
2529
|
if (obj.command === 'cancelTimer') {
|
|
2486
2530
|
const m = (obj.message || {});
|
|
2487
|
-
const
|
|
2531
|
+
const satellite = this.timerSatellite(m.satellite);
|
|
2532
|
+
let result;
|
|
2533
|
+
if (satellite === null) {
|
|
2534
|
+
result = { ok: false, cancelled: 0, error: `unknown satellite "${String(m.satellite)}"` };
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
const mine = (this.timers?.list() || []).filter(t => !satellite || t.source === satellite);
|
|
2538
|
+
const ids = m.id ? mine.filter(t => t.id === m.id).map(t => t.id) : mine.map(t => t.id);
|
|
2539
|
+
const cancelled = satellite || m.id
|
|
2540
|
+
? ids.filter(id => this.timers?.cancel(id)).length
|
|
2541
|
+
: (this.timers?.cancelAll() ?? 0);
|
|
2542
|
+
result = { ok: true, cancelled };
|
|
2543
|
+
}
|
|
2488
2544
|
if (obj.callback) {
|
|
2489
|
-
this.sendTo(obj.from, obj.command,
|
|
2545
|
+
this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
2490
2546
|
}
|
|
2491
2547
|
return;
|
|
2492
2548
|
}
|
|
2493
|
-
// Scripts: list the running timers.
|
|
2549
|
+
// Scripts: list the running timers. message = { satellite? } — with it only that satellite's.
|
|
2494
2550
|
if (obj.command === 'listTimers') {
|
|
2551
|
+
const m = (obj.message || {});
|
|
2552
|
+
const satellite = this.timerSatellite(m.satellite);
|
|
2553
|
+
const result = satellite === null
|
|
2554
|
+
? { timers: [], error: `unknown satellite "${String(m.satellite)}"` }
|
|
2555
|
+
: { timers: (this.timers?.list() || []).filter(t => !satellite || t.source === satellite) };
|
|
2495
2556
|
if (obj.callback) {
|
|
2496
|
-
this.sendTo(obj.from, obj.command,
|
|
2557
|
+
this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
2497
2558
|
}
|
|
2498
2559
|
return;
|
|
2499
2560
|
}
|
|
@@ -3132,9 +3193,10 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
3132
3193
|
return Promise.resolve('');
|
|
3133
3194
|
}
|
|
3134
3195
|
const voice = VOICE_TRANSPORTS.has(origin.transport || '');
|
|
3196
|
+
const satellite = voice ? this.knownSatelliteId(origin.satellite || source, origin.room || '') : '';
|
|
3135
3197
|
// The journal shows "[picture] What is in this picture?", like the conversation history
|
|
3136
3198
|
const shown = (0, media_1.historyText)(question, attachments.images.length, !!attachments.video);
|
|
3137
|
-
return this.requestOrigin.run({ source, room: origin.room || '', voice, attachments }, () => this.journal
|
|
3199
|
+
return this.requestOrigin.run({ source, room: origin.room || '', voice, satellite, attachments }, () => this.journal
|
|
3138
3200
|
? this.journal.run(shown, source, origin, () => {
|
|
3139
3201
|
this.traceSpeechInput(question, origin);
|
|
3140
3202
|
this.traceAttachments(attachments);
|
|
@@ -3659,7 +3721,11 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
3659
3721
|
// pi-agent asks its questions to confirm a write ("… Say yes or no.") in this language, not in the system
|
|
3660
3722
|
// language: a satellite listening in Russian does not hear a German "Ja"
|
|
3661
3723
|
const language = String(this.config.voiceLanguage || this.language || '');
|
|
3662
|
-
|
|
3724
|
+
// From a satellite: where pi-agent can speak later on its own (a reminder, the end of a background
|
|
3725
|
+
// task) — its `tts` state says it there.
|
|
3726
|
+
const satellite = this.requestOrigin.getStore()?.satellite || '';
|
|
3727
|
+
const tts = satellite ? `${this.namespace}.satellites.${satellite}.tts` : '';
|
|
3728
|
+
return await harness.ask({ text, conversation: source, instructions, language, images, satellite, tts });
|
|
3663
3729
|
}
|
|
3664
3730
|
catch (e) {
|
|
3665
3731
|
if (localFallback) {
|
|
@@ -5015,13 +5081,25 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
5015
5081
|
await this.setStateAsync(`${base}.duration`, { val: t.duration, ack: true });
|
|
5016
5082
|
await this.setStateAsync(`${base}.fireAt`, { val: t.fireAt, ack: true });
|
|
5017
5083
|
}
|
|
5084
|
+
/**
|
|
5085
|
+
* The `source` a timer of a satellite gets (the device name, like a timer set there by voice), from the satellite
|
|
5086
|
+
* a script named: '' when it named none, null when the name is unknown.
|
|
5087
|
+
*/
|
|
5088
|
+
timerSatellite(name) {
|
|
5089
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
5090
|
+
return '';
|
|
5091
|
+
}
|
|
5092
|
+
const id = this.resolveSatelliteId(name);
|
|
5093
|
+
return id ? this.satDeviceById.get(id) || id : null;
|
|
5094
|
+
}
|
|
5018
5095
|
/** A timer expired: record it, play the jingle (if any) and speak the announcement on the origin satellite. */
|
|
5019
5096
|
onTimerFired(t) {
|
|
5020
5097
|
this.log.info(`Timer fired: "${t.label || '(no label)'}"${t.room ? ` (${t.room})` : ''}`);
|
|
5021
5098
|
this.setStateAsync('timers.lastFired', { val: t.label || t.id, ack: true }).catch(() => { });
|
|
5022
|
-
|
|
5099
|
+
// A text a script gave for this timer (a reminder) is said even when timer announcements are off
|
|
5100
|
+
const announce = !!t.announce || this.config.timerAnnounce !== false;
|
|
5023
5101
|
const target = this.announceTargetForSource(t.source);
|
|
5024
|
-
this.fireEffects(this.config.timerSound || '', announce, this.timerAnnounceMessage(t), target);
|
|
5102
|
+
this.fireEffects(this.config.timerSound || '', announce, t.announce || this.timerAnnounceMessage(t), target);
|
|
5025
5103
|
}
|
|
5026
5104
|
/** Build the spoken expiry message ("Timer abgelaufen: die Nudeln.") in the interaction language. */
|
|
5027
5105
|
timerAnnounceMessage(t) {
|