iobroker.assistant 1.4.7 → 1.4.8

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.
@@ -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":"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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 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
@@ -1441,7 +1441,11 @@ class Assistant extends adapter_core_1.Adapter {
1441
1441
  await this.ensureState(`${id}.inProgress`, 'Update running', 'boolean', 'indicator', false);
1442
1442
  await this.ensureState(`${id}.progress`, 'Update progress', 'number', 'value', false);
1443
1443
  await this.ensureState(`${id}.install`, 'Install the update', 'boolean', 'button', true);
1444
+ // A device looks for a new version only now and then (an http_request update: at boot and every
1445
+ // 6 h); until then latestVersion shows the old one and install does nothing.
1446
+ await this.ensureState(`${id}.check`, 'Check for an update now', 'boolean', 'button', true);
1444
1447
  this.subscribeStates(`${id}.install`);
1448
+ this.subscribeStates(`${id}.check`);
1445
1449
  }
1446
1450
  else if (entity.kind === 'event') {
1447
1451
  await this.setObjectNotExistsAsync(id, {
@@ -1477,6 +1481,20 @@ class Assistant extends adapter_core_1.Adapter {
1477
1481
  : { name, type: 'string', role: 'text', read: true, write: false, def: '' };
1478
1482
  await this.setObjectNotExistsAsync(id, { type: 'state', common, native: {} });
1479
1483
  }
1484
+ else if (entity.kind === 'text') {
1485
+ if (entity.password) {
1486
+ // A secret would sit in a state every script and admin user can read.
1487
+ this.log.debug(`Satellite ${device}: "${name}" is a password text — not mirrored`);
1488
+ continue;
1489
+ }
1490
+ // Labels and units of a satellite with a screen, set from here instead of in the firmware build.
1491
+ await this.setObjectNotExistsAsync(id, {
1492
+ type: 'state',
1493
+ common: { name, type: 'string', role: 'text', read: true, write: true, def: '' },
1494
+ native: {},
1495
+ });
1496
+ this.subscribeStates(id);
1497
+ }
1480
1498
  else {
1481
1499
  const common = entity.kind === 'switch'
1482
1500
  ? { name, type: 'boolean', role: 'switch', read: true, write: true, def: false }
@@ -1559,7 +1577,7 @@ class Assistant extends adapter_core_1.Adapter {
1559
1577
  }
1560
1578
  /** Write one entity's current value into its state(s). Always acked — this is the device talking. */
1561
1579
  async writeSatelliteControl(satId, entity) {
1562
- if (!isControlId(entity.objectId)) {
1580
+ if (!isControlId(entity.objectId) || (entity.kind === 'text' && entity.password)) {
1563
1581
  return; // never created (see buildSatelliteControls)
1564
1582
  }
1565
1583
  const base = `satellites.${satId}.controls.${entity.objectId}`;
@@ -1602,11 +1620,11 @@ class Assistant extends adapter_core_1.Adapter {
1602
1620
  // Compound entities address a leaf: `<objectId>.volume`, `<objectId>.install`, …
1603
1621
  const [objectId, leaf] = path.split('.');
1604
1622
  let payload = value;
1605
- if (leaf === 'install') {
1623
+ if (leaf === 'install' || leaf === 'check') {
1606
1624
  if (!value) {
1607
1625
  return; // button released
1608
1626
  }
1609
- payload = 'install';
1627
+ payload = leaf; // UpdateCommandRequest: install = UPDATE, check = CHECK
1610
1628
  }
1611
1629
  else if (leaf === 'volume' || leaf === 'command') {
1612
1630
  payload = value;