iobroker.yamaha 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -11
- package/build/lib/catalog/types.js.map +2 -2
- package/build/lib/xml/device-controller.js +20 -2
- package/build/lib/xml/device-controller.js.map +2 -2
- package/build/lib/yxc/object-mapper.js +43 -5
- package/build/lib/yxc/object-mapper.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,6 +103,10 @@ On the very first contact the adapter asks the receiver which functions it suppo
|
|
|
103
103
|
Placeholder for the next version (at the beginning of the line):
|
|
104
104
|
### **WORK IN PROGRESS**
|
|
105
105
|
-->
|
|
106
|
+
### 2.3.2 (2026-09-03)
|
|
107
|
+
|
|
108
|
+
- (krobipd) Fixed: eight tuner and scene datapoints still showed no explanation on MusicCast and older XML receivers
|
|
109
|
+
|
|
106
110
|
### 2.3.1 (2026-09-03)
|
|
107
111
|
|
|
108
112
|
- (krobipd) Fixed: the new datapoint explanations were missing on MusicCast and older XML devices, so those users saw an empty description where the receiver has one
|
|
@@ -130,17 +134,6 @@ On the very first contact the adapter asks the receiver which functions it suppo
|
|
|
130
134
|
|
|
131
135
|
- (krobipd) Fixed: on an updated instance the info datapoints now get the translated names too, instead of keeping the ones an older version had written
|
|
132
136
|
|
|
133
|
-
### 2.1.0 (2026-09-02)
|
|
134
|
-
|
|
135
|
-
- (krobipd) New: every datapoint name is now shown in your ioBroker language, in eleven languages; a name you changed yourself stays untouched
|
|
136
|
-
- (krobipd) Fixed: the empty "Media server" folder left behind by the 2.0.0 object tree is removed, together with any other folder that no longer holds a datapoint
|
|
137
|
-
- (krobipd) Fixed: the menu view starts empty after a restart instead of still showing the menu from the last time you browsed
|
|
138
|
-
- (krobipd) Fixed: a receiver that gets a new address from your router is found again and reconnected there, instead of staying offline for good
|
|
139
|
-
- (krobipd) Fixed: deleting an automatically found receiver now really removes it — the connection is closed, its datapoints are gone, and the next search does not bring it back
|
|
140
|
-
- (krobipd) Fixed: the DAB date, the audio signal fields and the tuner preset list no longer show the receiver's own placeholder text — they stay empty, and only stored presets are listed
|
|
141
|
-
- (krobipd) Fixed: on a receiver with both an analogue and a DAB tuner the AM band stays selectable and every band change reaches the right tuner
|
|
142
|
-
- (krobipd) Fixed: a hiccup of the ioBroker database while the adapter starts no longer stops the instance — it says so in the log and keeps the receivers running
|
|
143
|
-
|
|
144
137
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
145
138
|
|
|
146
139
|
## History
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/catalog/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ValueSpec } from \"./value-coerce\";\nimport type { I18nKey } from \"../i18n\";\n\n/**\n * One catalogued device function \u2192 one ioBroker state. The catalog is\n * device-agnostic: it lists every function a protocol can expose; the\n * per-device mapper picks the entries the device actually reports.\n *\n * **Single source rule.** Each protocol catalog extends this with its own\n * protocol key (YNCA: the subunit function name; YXC: the getStatus field +\n * write method; XML: the Basic_Status field + PUT builder). The init sweep, the\n * device\u2192state read-back and the user-write encode are all derived from that one\n * extended list \u2014 never a second state\u2194function table beside the catalog (the\n * `AMP_STATES` vs `STATE_MAPPINGS` split this rewrite removes). `catalogToObjects`\n * reads only the object fields below and ignores the protocol key.\n */\nexport interface CatalogEntry {\n /** State id relative to the device \u2014 dotted for a channel (e.g. `power`, `sound.bass`, `zone2.power`). */\n id: string;\n /**\n * The object's display name, as its translation KEY (the English text, which is also the key\n * in `admin/i18n`). The catalogs are module-level constants, built before the adapter starts\n * \u2014 so they carry the key and {@link catalogToObjects} resolves it into all eleven languages.\n */\n nameKey: I18nKey;\n /**\n * Values substituted into the name key's `%s` placeholders, in order. Lets a family of\n * datapoints built in a loop carry distinguishable names (the assignable input names)\n * instead of all sharing the family's label.\n */\n nameArgs?: Array<string | number>;\n /**\n * The datapoint's EXPLANATION, as its translation key (fleet standard\n * `feedback_beschreibung_ist_erklaerung`): one short sentence saying what the value means,\n * never the protocol identifier and never the name again. Deliberately OPTIONAL \u2014 where a\n * datapoint explains itself (`power`, `volume`, `artist`) an invented sentence would be\n * noise, and the standard asks for an empty field rather than filler. What must never\n * happen is a SILENT gap, so every catalog entry without a key is listed by name in\n * `SELF_EXPLANATORY` and the manifest test refuses an entry that is in neither.\n */\n descKey?: I18nKey;\n /** Value semantics \u2014 drives type/role/states/range via {@link ValueSpec}. */\n spec: ValueSpec;\n /** Whether the user can write this state. */\n write: boolean;\n /** Explicit ioBroker role; when omitted it is derived from the spec kind. */\n role?: string;\n}\n\n/** An object to create in the device tree: a state or a channel. */\nexport interface ObjectDef {\n /** Object id relative to the device. */\n id: string;\n /** Object kind. */\n type: \"state\" | \"channel\";\n /** ioBroker common part. */\n common: {\n /** Resolved to all admin languages \u2014 never a plain string (state-role gate). */\n name: ioBroker.StringOrTranslated;\n type?: \"boolean\" | \"number\" | \"string\";\n role?: string;\n read?: boolean;\n write?: boolean;\n unit?: string;\n min?: number;\n max?: number;\n step?: number;\n states?: Record<string, string>;\n };\n}\n\n/**\n * Display names for the channel ids the catalogs use. A channel id not listed\n * here falls back to its capitalised segment, so a new channel still renders \u2014 but\n * every channel a catalog actually creates is named here, so nothing shows a raw id\n * like \"Pc\" or \"Ipod\" in the object browser. Keys match the real channel segments\n * (verified against the built catalogs); do not add speculative entries.\n */\n/**\n * Explanations for the channel ids that need one \u2014 the folders whose purpose is not obvious\n * from their name. Same rule as {@link CatalogEntry.descKey}: a folder that explains itself\n * (`sound`, `tuner`, a source name like `spotify`) stays out, because the fleet standard wants\n * an empty description rather than filler. Keys are the identifiers in `admin/i18n`.\n */\nexport const CHANNEL_DESC_KEYS: Record<string, I18nKey> = {\n info: \"descChannelInfo\",\n zoneB: \"descChannelZoneB\",\n advanced: \"descChannelAdvanced\",\n speakers: \"descChannelSpeakers\",\n scene: \"descChannelScene\",\n remote: \"descChannelRemote\",\n inputNames: \"descChannelInputNames\",\n initialVolume: \"descChannelInitialVolume\",\n equalizer: \"descChannelEqualizer\",\n signal: \"descChannelSignal\",\n dab: \"descChannelDab\",\n player: \"descChannelPlayer\",\n multiroom: \"descChannelMultiroom\",\n group: \"descChannelGroup\",\n musicCastLink: \"descChannelMusicCastLink\",\n browse: \"descChannelBrowse\",\n};\n\nexport const CHANNEL_NAME_KEYS: Record<string, I18nKey> = {\n // Device info (metadata beside the per-device connection indicator)\n info: \"info\",\n // Zones\n zone2: \"zone2\",\n zone3: \"zone3\",\n zone4: \"zone4\",\n zoneB: \"zoneB\",\n // Amplifier groups\n sound: \"sound\",\n advanced: \"advanced\",\n hdmi: \"hdmi\",\n speakers: \"speakers\",\n scene: \"scenes\",\n remote: \"remoteControl\",\n inputNames: \"inputNames\",\n initialVolume: \"initialVolume\",\n equalizer: \"equalizer\",\n signal: \"audioSignal\",\n // Tuner\n tuner: \"tuner\",\n dab: \"dab\",\n // Media player container + multiroom\n player: \"mediaPlayer\",\n multiroom: \"multiroom\",\n // The MusicCast-Link folder under multiroom \u2014 a group of linked DEVICES, not zones.\n group: \"musiccastGroupLinkedDevices\",\n // Media player sources\n netRadio: \"netRadio\",\n server: \"mediaServer\",\n usb: \"usb\",\n spotify: \"spotify\",\n deezer: \"deezer\",\n tidal: \"tidal\",\n napster: \"napster\",\n pandora: \"pandora\",\n rhapsody: \"rhapsody\",\n sirius: \"siriusxm\",\n airplay: \"airplay\",\n bluetooth: \"bluetooth\",\n pc: \"pc\",\n musicCastLink: \"musiccastLink\",\n ipod: \"iPod\",\n ipodUsb: \"ipodUSB\",\n // YXC/XML media channels\n cd: \"cd\",\n netPlayer: \"networkPlayer\",\n clock: \"clock\",\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import type { ValueSpec } from \"./value-coerce\";\nimport type { I18nKey } from \"../i18n\";\n\n/**\n * One catalogued device function \u2192 one ioBroker state. The catalog is\n * device-agnostic: it lists every function a protocol can expose; the\n * per-device mapper picks the entries the device actually reports.\n *\n * **Single source rule.** Each protocol catalog extends this with its own\n * protocol key (YNCA: the subunit function name; YXC: the getStatus field +\n * write method; XML: the Basic_Status field + PUT builder). The init sweep, the\n * device\u2192state read-back and the user-write encode are all derived from that one\n * extended list \u2014 never a second state\u2194function table beside the catalog (the\n * `AMP_STATES` vs `STATE_MAPPINGS` split this rewrite removes). `catalogToObjects`\n * reads only the object fields below and ignores the protocol key.\n */\nexport interface CatalogEntry {\n /** State id relative to the device \u2014 dotted for a channel (e.g. `power`, `sound.bass`, `zone2.power`). */\n id: string;\n /**\n * The object's display name, as its translation KEY (the English text, which is also the key\n * in `admin/i18n`). The catalogs are module-level constants, built before the adapter starts\n * \u2014 so they carry the key and {@link catalogToObjects} resolves it into all eleven languages.\n */\n nameKey: I18nKey;\n /**\n * Values substituted into the name key's `%s` placeholders, in order. Lets a family of\n * datapoints built in a loop carry distinguishable names (the assignable input names)\n * instead of all sharing the family's label.\n */\n nameArgs?: Array<string | number>;\n /**\n * The datapoint's EXPLANATION, as its translation key (fleet standard\n * `feedback_beschreibung_ist_erklaerung`): one short sentence saying what the value means,\n * never the protocol identifier and never the name again. Deliberately OPTIONAL \u2014 where a\n * datapoint explains itself (`power`, `volume`, `artist`) an invented sentence would be\n * noise, and the standard asks for an empty field rather than filler. What must never\n * happen is a SILENT gap, so every catalog entry without a key is listed by name in\n * `SELF_EXPLANATORY` and the manifest test refuses an entry that is in neither.\n */\n descKey?: I18nKey;\n /** Value semantics \u2014 drives type/role/states/range via {@link ValueSpec}. */\n spec: ValueSpec;\n /** Whether the user can write this state. */\n write: boolean;\n /** Explicit ioBroker role; when omitted it is derived from the spec kind. */\n role?: string;\n}\n\n/** An object to create in the device tree: a state or a channel. */\nexport interface ObjectDef {\n /** Object id relative to the device. */\n id: string;\n /** Object kind. */\n type: \"state\" | \"channel\";\n /** ioBroker common part. */\n common: {\n /** Resolved to all admin languages \u2014 never a plain string (state-role gate). */\n name: ioBroker.StringOrTranslated;\n /**\n * The datapoint's explanation, resolved to all admin languages. Absent where the datapoint\n * explains itself \u2014 the fleet standard wants an empty field there, not invented prose.\n * The field was MISSING from this type until 2026-09-03, which is why the tuner and scene\n * datapoints built directly by the MusicCast and XML controllers could never carry one:\n * the catalogs had the key, the object type had nowhere to put it.\n */\n desc?: ioBroker.StringOrTranslated;\n type?: \"boolean\" | \"number\" | \"string\";\n role?: string;\n read?: boolean;\n write?: boolean;\n unit?: string;\n min?: number;\n max?: number;\n step?: number;\n states?: Record<string, string>;\n };\n}\n\n/**\n * Display names for the channel ids the catalogs use. A channel id not listed\n * here falls back to its capitalised segment, so a new channel still renders \u2014 but\n * every channel a catalog actually creates is named here, so nothing shows a raw id\n * like \"Pc\" or \"Ipod\" in the object browser. Keys match the real channel segments\n * (verified against the built catalogs); do not add speculative entries.\n */\n/**\n * Explanations for the channel ids that need one \u2014 the folders whose purpose is not obvious\n * from their name. Same rule as {@link CatalogEntry.descKey}: a folder that explains itself\n * (`sound`, `tuner`, a source name like `spotify`) stays out, because the fleet standard wants\n * an empty description rather than filler. Keys are the identifiers in `admin/i18n`.\n */\nexport const CHANNEL_DESC_KEYS: Record<string, I18nKey> = {\n info: \"descChannelInfo\",\n zoneB: \"descChannelZoneB\",\n advanced: \"descChannelAdvanced\",\n speakers: \"descChannelSpeakers\",\n scene: \"descChannelScene\",\n remote: \"descChannelRemote\",\n inputNames: \"descChannelInputNames\",\n initialVolume: \"descChannelInitialVolume\",\n equalizer: \"descChannelEqualizer\",\n signal: \"descChannelSignal\",\n dab: \"descChannelDab\",\n player: \"descChannelPlayer\",\n multiroom: \"descChannelMultiroom\",\n group: \"descChannelGroup\",\n musicCastLink: \"descChannelMusicCastLink\",\n browse: \"descChannelBrowse\",\n};\n\nexport const CHANNEL_NAME_KEYS: Record<string, I18nKey> = {\n // Device info (metadata beside the per-device connection indicator)\n info: \"info\",\n // Zones\n zone2: \"zone2\",\n zone3: \"zone3\",\n zone4: \"zone4\",\n zoneB: \"zoneB\",\n // Amplifier groups\n sound: \"sound\",\n advanced: \"advanced\",\n hdmi: \"hdmi\",\n speakers: \"speakers\",\n scene: \"scenes\",\n remote: \"remoteControl\",\n inputNames: \"inputNames\",\n initialVolume: \"initialVolume\",\n equalizer: \"equalizer\",\n signal: \"audioSignal\",\n // Tuner\n tuner: \"tuner\",\n dab: \"dab\",\n // Media player container + multiroom\n player: \"mediaPlayer\",\n multiroom: \"multiroom\",\n // The MusicCast-Link folder under multiroom \u2014 a group of linked DEVICES, not zones.\n group: \"musiccastGroupLinkedDevices\",\n // Media player sources\n netRadio: \"netRadio\",\n server: \"mediaServer\",\n usb: \"usb\",\n spotify: \"spotify\",\n deezer: \"deezer\",\n tidal: \"tidal\",\n napster: \"napster\",\n pandora: \"pandora\",\n rhapsody: \"rhapsody\",\n sirius: \"siriusxm\",\n airplay: \"airplay\",\n bluetooth: \"bluetooth\",\n pc: \"pc\",\n musicCastLink: \"musiccastLink\",\n ipod: \"iPod\",\n ipodUsb: \"ipodUSB\",\n // YXC/XML media channels\n cd: \"cd\",\n netPlayer: \"networkPlayer\",\n clock: \"clock\",\n};\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4FO,MAAM,oBAA6C;AAAA,EACxD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AAAA,EACV,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,OAAO;AAAA,EACP,eAAe;AAAA,EACf,QAAQ;AACV;AAEO,MAAM,oBAA6C;AAAA;AAAA,EAExD,MAAM;AAAA;AAAA,EAEN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA;AAAA,EAEP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EAER,OAAO;AAAA,EACP,KAAK;AAAA;AAAA,EAEL,QAAQ;AAAA,EACR,WAAW;AAAA;AAAA,EAEX,OAAO;AAAA;AAAA,EAEP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,IAAI;AAAA,EACJ,eAAe;AAAA,EACf,MAAM;AAAA,EACN,SAAS;AAAA;AAAA,EAET,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,OAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -251,6 +251,7 @@ class XmlDeviceController {
|
|
|
251
251
|
type: "state",
|
|
252
252
|
common: {
|
|
253
253
|
name: (0, import_i18n.tName)("recallScene"),
|
|
254
|
+
desc: (0, import_i18n.tName)("descRecallScene"),
|
|
254
255
|
type: "number",
|
|
255
256
|
role: "level",
|
|
256
257
|
read: true,
|
|
@@ -300,6 +301,7 @@ class XmlDeviceController {
|
|
|
300
301
|
};
|
|
301
302
|
await state("preset", {
|
|
302
303
|
name: (0, import_i18n.tName)("presetRecallByNumber"),
|
|
304
|
+
desc: (0, import_i18n.tName)("descPresetRecallByNumber"),
|
|
303
305
|
type: "number",
|
|
304
306
|
role: "level",
|
|
305
307
|
read: true,
|
|
@@ -318,10 +320,25 @@ class XmlDeviceController {
|
|
|
318
320
|
read: true,
|
|
319
321
|
write: false
|
|
320
322
|
});
|
|
321
|
-
await state("rdsService", {
|
|
322
|
-
|
|
323
|
+
await state("rdsService", {
|
|
324
|
+
name: (0, import_i18n.tName)("rdsStation"),
|
|
325
|
+
desc: (0, import_i18n.tName)("descRdsStation"),
|
|
326
|
+
type: "string",
|
|
327
|
+
role: "text",
|
|
328
|
+
read: true,
|
|
329
|
+
write: false
|
|
330
|
+
});
|
|
331
|
+
await state("rdsText", {
|
|
332
|
+
name: (0, import_i18n.tName)("rdsText"),
|
|
333
|
+
desc: (0, import_i18n.tName)("descRdsText"),
|
|
334
|
+
type: "string",
|
|
335
|
+
role: "text",
|
|
336
|
+
read: true,
|
|
337
|
+
write: false
|
|
338
|
+
});
|
|
323
339
|
await state("tuned", {
|
|
324
340
|
name: (0, import_i18n.tName)("tunedToAStation"),
|
|
341
|
+
desc: (0, import_i18n.tName)("descTunedToAStation"),
|
|
325
342
|
type: "boolean",
|
|
326
343
|
role: "indicator",
|
|
327
344
|
read: true,
|
|
@@ -329,6 +346,7 @@ class XmlDeviceController {
|
|
|
329
346
|
});
|
|
330
347
|
await state("stereo", {
|
|
331
348
|
name: (0, import_i18n.tName)("stereoReception"),
|
|
349
|
+
desc: (0, import_i18n.tName)("descStereoReception"),
|
|
332
350
|
type: "boolean",
|
|
333
351
|
role: "indicator",
|
|
334
352
|
read: true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/xml/device-controller.ts"],
|
|
4
|
-
"sourcesContent": ["import { CHANNEL_NAME_KEYS, type ObjectDef } from \"../catalog/types\";\nimport { tName } from \"../i18n\";\nimport {\n isPermanentXmlRefusal,\n parseInputList,\n parseReturnCode,\n parseSceneList,\n parseTunerInfo,\n type BasicStatus,\n type XmlScene,\n} from \"./protocol\";\nimport { parseXmlStatus, stateToXml, type XmlCommand } from \"./command-mapper\";\nimport { XML_AMP_CATALOG } from \"./catalog\";\nimport type { ConnectionHandle, ControllerLog } from \"../controller\";\nimport { errorMessage } from \"../util\";\nimport { PollDropDetector } from \"../lifecycle/poll-drop-detector\";\nimport type { ProbeMemory } from \"../lifecycle/probe-memory\";\nimport type { CommandGate } from \"../lifecycle/command-gate\";\nimport type { BrowseEngine } from \"../browse/browse-engine\";\nimport { createBrowseSurface } from \"../browse/surface\";\nimport { XML_BROWSE_SOURCES, XmlBrowseDriver } from \"../browse/xml-browse-driver\";\n\n/** XML/YNC has no push channel, so the state is polled at this interval by default. */\nconst DEFAULT_POLL_INTERVAL_MS = 60 * 1000;\n\ninterface XmlZone {\n /** Unified zone key (`main`, `zone2`, \u2026). */\n key: string;\n /** XML zone element (`Main_Zone`, `Zone_2`, \u2026). */\n element: string;\n /** State-id prefix for the zone. */\n prefix: string;\n}\n\nconst XML_ZONES: XmlZone[] = [\n { key: \"main\", element: \"Main_Zone\", prefix: \"\" },\n { key: \"zone2\", element: \"Zone_2\", prefix: \"multiroom.zone2.\" },\n { key: \"zone3\", element: \"Zone_3\", prefix: \"multiroom.zone3.\" },\n { key: \"zone4\", element: \"Zone_4\", prefix: \"multiroom.zone4.\" },\n];\n\n/** The subset of the XML client the controller uses (so tests can inject a fake). */\nexport interface XmlClientLike {\n /** Read a zone's Basic_Status. */\n getStatus(zone: string): Promise<BasicStatus>;\n /** Read the device's model name (System > Config). */\n getModelName(): Promise<string | undefined>;\n /** Send an inner command to a zone. */\n send(zone: string, inner: string): Promise<void>;\n /** Read an element's inner GET request and return the raw response body. */\n getXml(element: string, inner: string): Promise<string>;\n}\n\n/** The adapter callbacks the controller drives \u2014 narrow, so no adapter mock is needed in tests. */\nexport interface XmlControllerDeps {\n /** The XML client for this device. */\n client: XmlClientLike;\n /** Schedule the keepalive poll; returns a function that cancels it. */\n scheduleKeepalive(handler: () => void, ms: number): () => void;\n /** Create or update an object in the device tree. */\n upsertObject(id: string, def: ObjectDef): Promise<void>;\n /** Write a state value with ack (device-originated). */\n setStateAck(id: string, value: boolean | number | string): void;\n /** Adapter log. */\n log: ControllerLog;\n /**\n * The device's command gate: every request is paced through it, and its signal is the\n * connection's shutdown flag \u2014 a closed gate ends pending waits and stops state writes\n * from a poll that was already in flight. Absent in older tests \u2192 no browsing.\n */\n gate?: CommandGate;\n /** Per-device memory for answers that do not change while the device runs (see ProbeMemory). */\n probeMemory?: ProbeMemory;\n}\n\n/**\n * Drives one XML/YNC device: probe which zones answer, build the amp tree, seed\n * state, and route commands both ways. XML has no push, so state is refreshed by\n * a keepalive poll. Create-only.\n */\nexport class XmlDeviceController implements ConnectionHandle {\n private zones: XmlZone[] = [];\n private cancelKeepalive: (() => void) | undefined;\n private readonly dropDetector = new PollDropDetector();\n private browseEngine: BrowseEngine | undefined;\n /** The scenes each zone DECLARES (`Scene_Sel_Item`), for the recall write path. */\n private readonly scenesByZone = new Map<string, XmlScene[]>();\n /** Whether the device answers `<Tuner><Play_Info>` (the classic pre-2010 tuner). */\n private hasTuner = false;\n /** The amp state ids this controller actually created \u2014 the claim-with-proof gate for BOTH ways. */\n private readonly createdStates = new Set<string>();\n /** Per zone: the Basic_Status fields this device is known to deliver (persisted union). */\n private readonly zoneFields = new Map<string, Set<string>>();\n\n /**\n * @param deviceId the id-safe device id (object-tree path segment)\n * @param deps the client and adapter callbacks\n * @param pollIntervalMs how often to poll the device for state (default 60 s)\n */\n public constructor(\n private readonly deviceId: string,\n private readonly deps: XmlControllerDeps,\n private readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS,\n ) {}\n\n /**\n * Probe each zone, create the tree for the ones that answer, seed state, and\n * start the keepalive poll.\n *\n * @returns true if the main zone answered and the tree was created\n */\n public async start(): Promise<boolean> {\n // Probe all zones in parallel and keep each answering zone's status, so a zone is\n // fetched once (for the probe) and seeded from that same response \u2014 not twice.\n const probes = await Promise.all(\n XML_ZONES.map(async zone => ({ zone, status: await this.tryGetStatus(zone.element) })),\n );\n const answered = probes.filter(probe => probe.status && Object.keys(probe.status).length > 0);\n if (!answered.some(probe => probe.zone.key === \"main\")) {\n this.deps.log.debug(`${this.deviceId}: no XML main zone \u2014 creating no objects`);\n return false;\n }\n this.zones = answered.map(probe => probe.zone);\n // Freshness guard for the (persisted) probe memory: the model name is the identity\n // this transport can read. A different device behind the address drops the\n // remembered XML declarations (scenes, inputs, tuner, browse sources); a device\n // that reports no model keeps them \u2014 the YNCA/YXC guards catch a swap there.\n let model: string | undefined;\n try {\n model = await this.deps.client.getModelName();\n if (model !== undefined && this.deps.probeMemory) {\n if (this.deps.probeMemory.remembered(\"xmlModel\") !== model) {\n // Every XML-owned memory key carries the xml prefix (xmlBrowseSources,\n // xmlScenes:*, xmlInputs:*, xmlTuner, xmlModel).\n this.deps.probeMemory.drop(key => key.startsWith(\"xml\"));\n this.deps.probeMemory.set(\"xmlModel\", model);\n }\n }\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: getModelName failed (${errorMessage(e)})`);\n }\n // The zone's own input list (`Input_Sel_Item`, per zone \u2014 Main and Zone 2 differ on\n // real hardware): the device says which inputs it accepts, so the input state gets a\n // dropdown instead of a free string. Constant per model \u2014 remembered per device.\n const inputsByZone = new Map<string, string[]>();\n for (const zone of this.zones) {\n const body = await this.probeXml(\n `xmlInputs:${zone.key}`,\n zone.element,\n \"<Input><Input_Sel_Item>GetParam</Input_Sel_Item></Input>\",\n );\n inputsByZone.set(zone.key, parseInputList(body));\n }\n // Claim with proof, XML edition (2.0.1): only the states whose Basic_Status field\n // this device DELIVERS are created \u2014 a blind full-catalog rollout left valueless\n // objects (hdmi.out2, sound.direct, \u2026) standing on devices without the feature.\n // The delivered field set is a model property, remembered per zone (union, so a\n // later standby start \u2014 which may report fewer fields \u2014 cannot shrink the tree).\n for (const { zone, status } of answered) {\n const key = `xmlStatusFields:${zone.key}`;\n const remembered = this.deps.probeMemory?.remembered<string[]>(key);\n const fields = new Set<string>(Array.isArray(remembered) ? remembered : []);\n for (const field of Object.keys(status ?? {})) {\n fields.add(field);\n }\n this.deps.probeMemory?.set(key, [...fields]);\n this.zoneFields.set(zone.key, fields);\n }\n // Every parent \u2014 the zone channels included \u2014 is created by the per-state loop below\n // and named from the shared CHANNEL_NAME_KEYS table (a zone that answered has at least\n // its power state, so its channel always comes into being this way).\n const createdChannels = new Set<string>();\n for (const zone of this.zones) {\n for (const entry of XML_AMP_CATALOG) {\n // Main/system-wide features (scenes, HDMI outputs, party) exist only on the main zone.\n if (entry.mainOnly && zone.key !== \"main\") {\n continue;\n }\n // Claim with proof: skip what this device's status never delivered.\n if (entry.statusField !== undefined && !this.zoneFields.get(zone.key)?.has(entry.statusField)) {\n continue;\n }\n const stateId = `${zone.prefix}${entry.state}`;\n // A dotted state (e.g. scene.recall) needs its parent channel created first.\n const segments = stateId.split(\".\");\n for (let i = 1; i < segments.length; i++) {\n const channelId = segments.slice(0, i).join(\".\");\n if (!createdChannels.has(channelId)) {\n createdChannels.add(channelId);\n await this.deps.upsertObject(`${this.deviceId}.${channelId}`, {\n id: channelId,\n type: \"channel\",\n common: {\n // Capitalised like the catalog path does it, so the same folder cannot end up\n // called \"sound\" here and \"Sound\" there depending on which transport owns it.\n name: CHANNEL_NAME_KEYS[segments[i - 1]]\n ? tName(CHANNEL_NAME_KEYS[segments[i - 1]])\n : segments[i - 1].charAt(0).toUpperCase() + segments[i - 1].slice(1),\n },\n });\n }\n }\n const { nameKey, descKey, ...rest } = entry.common;\n const common: ObjectDef[\"common\"] = {\n ...rest,\n name: tName(nameKey),\n // An absent key means the datapoint explains itself \u2014 the fleet standard wants the\n // field empty there rather than filled with invented prose.\n ...(descKey ? { desc: tName(descKey) } : {}),\n };\n // The device's own input list becomes the dropdown (XML-owned devices only \u2014\n // where YNCA is present its dropdown wins via the owner policy).\n const inputs = entry.state === \"input\" ? inputsByZone.get(zone.key) : undefined;\n if (inputs && inputs.length > 0) {\n common.states = Object.fromEntries(inputs.map(input => [input, input]));\n }\n await this.deps.upsertObject(`${this.deviceId}.${stateId}`, {\n id: stateId,\n type: \"state\",\n common,\n });\n this.createdStates.add(stateId);\n }\n }\n await this.setupScenes(createdChannels);\n await this.setupTuner(createdChannels);\n // Seed from the statuses already fetched during the probe \u2014 no second round-trip.\n for (const { zone, status } of answered) {\n if (status) {\n this.seedZone(zone, status);\n }\n }\n // The model name (already read by the freshness guard) for the device-manager card.\n // Best-effort \u2014 a device that does not report it still connects, the line stays empty.\n if (model) {\n // The info channel and info.model already exist \u2014 the adapter creates them for\n // every device up front, so the card renders even while the device is offline.\n this.emit(\"info.model\", model);\n }\n await this.setupBrowse();\n this.cancelKeepalive = this.deps.scheduleKeepalive(() => void this.keepalive(), this.pollIntervalMs);\n // The adapter logs one combined \"ready\" line across all transports; this stays at debug.\n this.deps.log.debug(`${this.deviceId}: Yamaha (XML) device ready (XML)`);\n return true;\n }\n\n /**\n * Read one element's list once per device: the answer is a property of the MODEL\n * (input lists, scene declarations), so reconnects reuse it via the probe memory.\n *\n * Only a DEFINITE answer is remembered: a body, or the model's own \"no such node\"\n * (bodyless HTTP 400 / return code 2, both captured on the RX-V6A) as \"declares none\".\n * A transient failure \u2014 timeout, connection error, HTTP 5xx, or a state-dependent\n * refusal (return code 3/4, \"not now\") \u2014 is NOT remembered: before this, one busy\n * moment during the first contact recorded \"no scenes\" for that device for good, until\n * the model changed. Now it is simply asked again on the next connect.\n *\n * @param key the probe-memory key\n * @param element the XML element to ask\n * @param inner the inner GET request\n * @returns the raw response body, or \"\" when the device (definitely or for now) has none\n */\n private async probeXml(key: string, element: string, inner: string): Promise<string> {\n const probe = async (): Promise<string> => {\n let body: string;\n try {\n body = await this.deps.client.getXml(element, inner);\n } catch (e) {\n if (isPermanentXmlRefusal(e)) {\n return \"\"; // the model has no such node \u2014 definite\n }\n throw e; // transient \u2014 not remembered\n }\n const rc = parseReturnCode(body);\n if (rc !== undefined && rc !== 0) {\n if (rc === 2) {\n return \"\"; // RC 2 = the node does not exist on this model \u2014 definite\n }\n throw new Error(`device refused ${element} probe (RC=${rc})`); // \"not now\" \u2014 not remembered\n }\n return body;\n };\n try {\n return this.deps.probeMemory ? await this.deps.probeMemory.once(key, probe) : await probe();\n } catch (e) {\n this.deps.log.debug(\n `${this.deviceId}: ${key} probe failed, asking again on the next connect (${errorMessage(e)})`,\n );\n return \"\";\n }\n }\n\n /**\n * Build the scene surface from the device's OWN declaration (#615): each zone's\n * `<Scene_Sel_Item>` names the scenes that exist, their titles and the write value\n * (`Scene N` via `<Scene_Sel>`). The predecessor blindly sent `Scene_Load` to a\n * fixed 1..12 main-zone state; the capture shows the device declaring `Scene_Sel`\n * instead \u2014 and declaring scenes for Zone 2 too.\n *\n * @param createdChannels the channel ids already created (extended here)\n */\n private async setupScenes(createdChannels: Set<string>): Promise<void> {\n for (const zone of this.zones) {\n const body = await this.probeXml(\n `xmlScenes:${zone.key}`,\n zone.element,\n \"<Scene><Scene_Sel_Item>GetParam</Scene_Sel_Item></Scene>\",\n );\n const scenes = parseSceneList(body);\n if (scenes.length === 0) {\n continue;\n }\n this.scenesByZone.set(zone.key, scenes);\n const channelId = `${zone.prefix}scene`;\n if (!createdChannels.has(channelId)) {\n createdChannels.add(channelId);\n await this.deps.upsertObject(`${this.deviceId}.${channelId}`, {\n id: channelId,\n type: \"channel\",\n common: { name: tName(CHANNEL_NAME_KEYS.scene ?? \"Scenes\") },\n });\n }\n const max = Math.max(...scenes.map(scene => scene.num));\n await this.deps.upsertObject(`${this.deviceId}.${channelId}.recall`, {\n id: `${channelId}.recall`,\n type: \"state\",\n common: {\n name: tName(\"recallScene\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n max,\n step: 1,\n // The declared titles as the dropdown, so the picker shows \"Movie Viewing\",\n // not a bare number.\n states: Object.fromEntries(scenes.map(scene => [scene.num, scene.title])),\n },\n });\n // ONE list state instead of a name datapoint per scene (v2.0.0): visualizations\n // read titles as VALUES (button captions \u2014 the #613 reporter's setup), and a\n // dropdown's labels are not readable, so the list carries them.\n await this.deps.upsertObject(`${this.deviceId}.${channelId}.list`, {\n id: `${channelId}.list`,\n type: \"state\",\n common: { name: tName(\"scenesNumberTitle\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n this.emit(`${channelId}.list`, JSON.stringify(scenes));\n }\n }\n\n /**\n * Build the classic tuner surface (pre-2010 devices, where XML is the ONLY\n * transport \u2014 the predecessor served their tuner, the rewrite had dropped it).\n * Existence is probed once per device; the preset write is the openHAB-verified\n * `<Play_Control><Preset><Preset_Sel>`; frequency/RDS/tuned are read-only from\n * Play_Info. On newer devices YNCA/YXC own these ids via the owner policy.\n *\n * @param createdChannels the channel ids already created (extended here)\n */\n private async setupTuner(createdChannels: Set<string>): Promise<void> {\n const probe = await this.probeXml(\"xmlTuner\", \"Tuner\", \"<Play_Info>GetParam</Play_Info>\");\n if (probe.length === 0) {\n return;\n }\n this.hasTuner = true;\n if (!createdChannels.has(\"tuner\")) {\n createdChannels.add(\"tuner\");\n await this.deps.upsertObject(`${this.deviceId}.tuner`, {\n id: \"tuner\",\n type: \"channel\",\n common: { name: tName(CHANNEL_NAME_KEYS.tuner ?? \"Tuner\") },\n });\n }\n const state = async (id: string, common: ObjectDef[\"common\"]): Promise<void> => {\n await this.deps.upsertObject(`${this.deviceId}.tuner.${id}`, { id: `tuner.${id}`, type: \"state\", common });\n };\n await state(\"preset\", {\n name: tName(\"presetRecallByNumber\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n // Slot 1 upwards \u2014 `handleTunerWrite` drops a 0, so offering it as the lower bound\n // invited a write that goes nowhere.\n min: 1,\n max: 40,\n step: 1,\n });\n await state(\"frequency\", {\n name: tName(\"frequency\"),\n type: \"number\",\n role: \"value\",\n unit: \"kHz\",\n read: true,\n write: false,\n });\n await state(\"rdsService\", { name: tName(\"rdsStation\"), type: \"string\", role: \"text\", read: true, write: false });\n await state(\"rdsText\", { name: tName(\"rdsText\"), type: \"string\", role: \"text\", read: true, write: false });\n await state(\"tuned\", {\n name: tName(\"tunedToAStation\"),\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n });\n await state(\"stereo\", {\n name: tName(\"stereoReception\"),\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n });\n // NOT `emitTunerInfo(probe)`: the probe body comes out of the PERSISTED memory on every\n // reconnect and restart, so seeding from it published a snapshot of an earlier session\n // \u2014 frequency, RDS station and text, \"tuned\" \u2014 as the CURRENT reading, until the first\n // poll up to a whole interval later. The existence verdict is a model property and stays\n // remembered; the values are read fresh. (Same class as the menu's resting shape, which\n // showed rows six days older than the connection until it was fixed.)\n await this.refreshTuner();\n }\n\n /**\n * Write the tuner states from a Play_Info response.\n *\n * @param xml the Play_Info response body\n */\n private emitTunerInfo(xml: string): void {\n const info = parseTunerInfo(xml);\n if (info.preset !== undefined) {\n this.emit(\"tuner.preset\", info.preset);\n }\n if (info.frequency !== undefined) {\n // Unified kHz (v2.0.0): the device reports FM in MHz, AM in kHz \u2014 normalize.\n this.emit(\"tuner.frequency\", Math.round(info.frequencyUnit === \"MHz\" ? info.frequency * 1000 : info.frequency));\n }\n if (info.rdsService !== undefined) {\n this.emit(\"tuner.rdsService\", info.rdsService);\n }\n if (info.rdsText !== undefined) {\n this.emit(\"tuner.rdsText\", info.rdsText);\n }\n if (info.tuned !== undefined) {\n this.emit(\"tuner.tuned\", info.tuned);\n }\n if (info.stereo !== undefined) {\n this.emit(\"tuner.stereo\", info.stereo);\n }\n }\n\n /**\n * Poll the tuner's Play_Info (keepalive) and write the states.\n */\n private async refreshTuner(): Promise<void> {\n try {\n this.emitTunerInfo(await this.deps.client.getXml(\"Tuner\", \"<Play_Info>GetParam</Play_Info>\"));\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: tuner Play_Info failed: ${errorMessage(e)}`);\n }\n }\n\n /**\n * A user write to `tuner.preset` \u2192 the openHAB-verified preset recall.\n *\n * @param stateId the state id relative to the device\n * @param value the written value\n * @returns true when the id was the tuner preset (handled here)\n */\n private handleTunerWrite(stateId: string, value: unknown): boolean {\n if (stateId !== \"tuner.preset\" || !this.hasTuner) {\n return stateId === \"tuner.preset\";\n }\n const num = Math.round(Number(value));\n if (!Number.isFinite(num) || num < 1) {\n return true;\n }\n void this.applyCommand({\n zone: \"Tuner\",\n inner: `<Play_Control><Preset><Preset_Sel>${num}</Preset_Sel></Preset></Play_Control>`,\n });\n return true;\n }\n\n /**\n * A user write to a zone's `scene.recall` \u2192 the DECLARED write element\n * (`<Scene><Scene_Sel>Scene N</Scene_Sel></Scene>`). Only zones that declared\n * scenes accept the write; a refusal lands in the log via applyCommand.\n *\n * @param stateId the state id relative to the device\n * @param value the written value\n * @returns true when the id was a scene recall (handled here)\n */\n private handleSceneWrite(stateId: string, value: unknown): boolean {\n const match = /^(?:multiroom\\.(zone[234])\\.)?scene\\.recall$/.exec(stateId);\n if (!match) {\n return false;\n }\n const zoneKey = match[1] ?? \"main\";\n const zone = this.zones.find(z => z.key === zoneKey);\n const scenes = this.scenesByZone.get(zoneKey);\n // A TITLE is as valid a write as a number (\"Movie Viewing\" \u2192 Scene 1).\n const byTitle =\n typeof value === \"string\" && !/^\\d+$/.test(value.trim())\n ? scenes?.find(scene => scene.title.toLowerCase() === value.trim().toLowerCase())?.num\n : undefined;\n const num = byTitle ?? Math.round(Number(value));\n if (!zone || !scenes || !scenes.some(scene => scene.num === num)) {\n return true;\n }\n void this.applyCommand({ zone: zone.element, inner: `<Scene><Scene_Sel>Scene ${num}</Scene_Sel></Scene>` });\n return true;\n }\n\n /**\n * Create the browsing surface (#613) when at least one source answers a List_Info\n * probe (NET_RADIO/SERVER/USB \u2014 the menus the predecessor adapter's users drove\n * via `Realtime.*.LINE1TXT` + `xmlCommand`). Skipped without a delay dep (older tests).\n */\n private async setupBrowse(): Promise<void> {\n const gate = this.deps.gate;\n if (!gate) {\n return;\n }\n const delay = (ms: number): Promise<void> => gate.delay(ms);\n // Which sources have a menu is a property of the MODEL, not of this connection \u2014 ask\n // once per device instead of costing three extra requests (up to five seconds on a\n // receiver that has no menus at all) on every single reconnect.\n const probe = async (): Promise<string[]> => {\n const probes = await Promise.all(\n XML_BROWSE_SOURCES.map(async source => {\n try {\n const body = await this.deps.client.getXml(source.element, \"<List_Info>GetParam</List_Info>\");\n return body.includes(\"<Menu_Status>\") ? source.key : undefined;\n } catch (e) {\n if (isPermanentXmlRefusal(e)) {\n return undefined; // this model has no menu for that source (bodyless HTTP 400)\n }\n throw e; // transient \u2014 \"no menus\" must not be remembered for good\n }\n }),\n );\n return probes.filter((key): key is string => key !== undefined);\n };\n let available: Set<string>;\n try {\n available = new Set(\n this.deps.probeMemory ? await this.deps.probeMemory.once(\"xmlBrowseSources\", probe) : await probe(),\n );\n } catch (e) {\n this.deps.log.debug(\n `${this.deviceId}: browse probe failed, asking again on the next connect (${errorMessage(e)})`,\n );\n return;\n }\n if (available.size === 0) {\n return;\n }\n const driver = new XmlBrowseDriver(this.deps.client, available, delay);\n this.browseEngine = await createBrowseSurface(driver, this.deviceId, {\n upsertObject: this.deps.upsertObject,\n emit: (id, value) => this.emit(id, value),\n log: this.deps.log,\n delay,\n });\n }\n\n /**\n * Write a device-originated value \u2014 but never after the connection was closed. A poll\n * that was already in flight when the adapter stopped would otherwise still write into\n * a tree that is being torn down.\n *\n * @param relativeId the state id relative to the device\n * @param value the value to write\n */\n private emit(relativeId: string, value: boolean | number | string): void {\n if (this.deps.gate?.closed) {\n return;\n }\n this.deps.setStateAck(`${this.deviceId}.${relativeId}`, value);\n }\n\n /**\n * Handle a state change: a user write (ack false) becomes an XML command; an\n * acked change (the device's own echo) is ignored.\n *\n * @param fullStateId the full state id (device id + \".\" + state)\n * @param ack whether the change is acked (device-originated)\n * @param value the new value\n */\n public handleStateChange(fullStateId: string, ack: boolean, value: unknown): void {\n if (ack) {\n return;\n }\n const prefix = `${this.deviceId}.`;\n if (!fullStateId.startsWith(prefix)) {\n return;\n }\n const stateId = fullStateId.slice(prefix.length);\n if (stateId.startsWith(\"player.browse.\")) {\n this.browseEngine?.handleWrite(stateId, value);\n return;\n }\n // Scenes and the classic tuner are device-declared (not in the static catalog).\n if (this.handleSceneWrite(stateId, value)) {\n return;\n }\n if (this.handleTunerWrite(stateId, value)) {\n return;\n }\n // Claim with proof, on the WRITE way too. Object creation has been proof-gated since\n // 2.0.1 (only fields this device's Basic_Status really delivers), but the write path was\n // not \u2014 the comment on `createdStates` claimed otherwise while it guarded the read side\n // alone. XML was the last transport without it: YNCA writes only through its per-device\n // write map, MusicCast only through device-declared endpoints. It bites on a datapoint an\n // adapter version before 2.0.1 created and that once carried a value, so no sweep removes\n // it: writing it put a blind command on the wire that the device answers with a refusal.\n if (!this.createdStates.has(stateId)) {\n this.deps.log.debug(`${this.deviceId}: ${stateId} was not reported by this device \u2014 write dropped`);\n return;\n }\n const command = stateToXml(stateId, value);\n if (command) {\n void this.applyCommand(command);\n }\n }\n\n /**\n * Register the supervisor's drop handler. XML has no push/socket-drop event, so a\n * drop is inferred from a run of failed polls (see keepalive).\n *\n * @param cb invoked once when the device is judged gone\n */\n public onDrop(cb: (reason?: Error) => void): void {\n this.dropDetector.onDrop(cb);\n }\n\n /** Cancel the keepalive poll. Synchronous \u2014 safe to call from onUnload. */\n public close(): void {\n this.browseEngine?.close();\n // Closing the gate empties its queue and aborts its signal: queued requests are\n // dropped and every pending wait ends, so nothing writes after the teardown.\n this.deps.gate?.close();\n this.cancelKeepalive?.();\n this.cancelKeepalive = undefined;\n }\n\n /**\n * Poll every live zone. If every zone fails for three consecutive failed polls in a\n * row, the device is judged gone and a drop is reported so the supervisor reconnects.\n */\n private async keepalive(): Promise<void> {\n // The keepalive is an async handler on an adapter timer: a rejection here is an\n // UNHANDLED rejection, and js-controller turns those into an adapter stop. Every step\n // below catches for itself today, so the guard is what makes that a guarantee instead\n // of something the next change has to remember.\n try {\n let anyOk = false;\n for (const zone of this.zones) {\n if (await this.refreshZone(zone)) {\n anyOk = true;\n }\n }\n if (this.hasTuner) {\n await this.refreshTuner();\n }\n this.dropDetector.record(anyOk);\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: keepalive poll failed: ${errorMessage(e)}`);\n }\n }\n\n /**\n * Fetch a zone's status and write its amp states with ack.\n *\n * @param zone the zone to refresh\n * @returns true if the status was fetched, false if the request failed\n */\n private async refreshZone(zone: XmlZone): Promise<boolean> {\n const status = await this.tryGetStatus(zone.element);\n if (!status) {\n return false;\n }\n this.seedZone(zone, status);\n return true;\n }\n\n /**\n * Write a zone's amp states from an already-fetched Basic_Status (used to seed\n * from the start-up probe without a second round-trip).\n *\n * @param zone the zone the status belongs to\n * @param status the parsed Basic_Status\n */\n private seedZone(zone: XmlZone, status: BasicStatus): void {\n // A field the device delivers for the FIRST time mid-run has no object yet\n // (claim-with-proof creates only proven fields at start): remember it \u2014 the next\n // start creates it \u2014 and skip the write, so no state lands without an object.\n const known = this.zoneFields.get(zone.key);\n if (known) {\n let grew = false;\n for (const field of Object.keys(status)) {\n if (!known.has(field)) {\n known.add(field);\n grew = true;\n }\n }\n if (grew) {\n this.deps.probeMemory?.set(`xmlStatusFields:${zone.key}`, [...known]);\n }\n }\n for (const update of parseXmlStatus(status, zone.key)) {\n if (this.createdStates.has(update.id)) {\n this.emit(update.id, update.value);\n }\n }\n }\n\n /**\n * Read a zone's status, swallowing errors (an absent zone or an offline device).\n *\n * @param element the XML zone element\n * @returns the parsed status, or undefined on failure\n */\n private async tryGetStatus(element: string): Promise<BasicStatus | undefined> {\n try {\n return await this.deps.client.getStatus(element);\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: getStatus(${element}) failed: ${errorMessage(e)}`);\n return undefined;\n }\n }\n\n /**\n * Send a mapped command to the device.\n *\n * @param command the XML command to apply\n */\n private async applyCommand(command: XmlCommand): Promise<void> {\n try {\n await this.deps.client.send(command.zone, command.inner);\n } catch (e) {\n this.deps.log.warn(`${this.deviceId}: XML command failed: ${errorMessage(e)}`);\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { CHANNEL_NAME_KEYS, type ObjectDef } from \"../catalog/types\";\nimport { tName } from \"../i18n\";\nimport {\n isPermanentXmlRefusal,\n parseInputList,\n parseReturnCode,\n parseSceneList,\n parseTunerInfo,\n type BasicStatus,\n type XmlScene,\n} from \"./protocol\";\nimport { parseXmlStatus, stateToXml, type XmlCommand } from \"./command-mapper\";\nimport { XML_AMP_CATALOG } from \"./catalog\";\nimport type { ConnectionHandle, ControllerLog } from \"../controller\";\nimport { errorMessage } from \"../util\";\nimport { PollDropDetector } from \"../lifecycle/poll-drop-detector\";\nimport type { ProbeMemory } from \"../lifecycle/probe-memory\";\nimport type { CommandGate } from \"../lifecycle/command-gate\";\nimport type { BrowseEngine } from \"../browse/browse-engine\";\nimport { createBrowseSurface } from \"../browse/surface\";\nimport { XML_BROWSE_SOURCES, XmlBrowseDriver } from \"../browse/xml-browse-driver\";\n\n/** XML/YNC has no push channel, so the state is polled at this interval by default. */\nconst DEFAULT_POLL_INTERVAL_MS = 60 * 1000;\n\ninterface XmlZone {\n /** Unified zone key (`main`, `zone2`, \u2026). */\n key: string;\n /** XML zone element (`Main_Zone`, `Zone_2`, \u2026). */\n element: string;\n /** State-id prefix for the zone. */\n prefix: string;\n}\n\nconst XML_ZONES: XmlZone[] = [\n { key: \"main\", element: \"Main_Zone\", prefix: \"\" },\n { key: \"zone2\", element: \"Zone_2\", prefix: \"multiroom.zone2.\" },\n { key: \"zone3\", element: \"Zone_3\", prefix: \"multiroom.zone3.\" },\n { key: \"zone4\", element: \"Zone_4\", prefix: \"multiroom.zone4.\" },\n];\n\n/** The subset of the XML client the controller uses (so tests can inject a fake). */\nexport interface XmlClientLike {\n /** Read a zone's Basic_Status. */\n getStatus(zone: string): Promise<BasicStatus>;\n /** Read the device's model name (System > Config). */\n getModelName(): Promise<string | undefined>;\n /** Send an inner command to a zone. */\n send(zone: string, inner: string): Promise<void>;\n /** Read an element's inner GET request and return the raw response body. */\n getXml(element: string, inner: string): Promise<string>;\n}\n\n/** The adapter callbacks the controller drives \u2014 narrow, so no adapter mock is needed in tests. */\nexport interface XmlControllerDeps {\n /** The XML client for this device. */\n client: XmlClientLike;\n /** Schedule the keepalive poll; returns a function that cancels it. */\n scheduleKeepalive(handler: () => void, ms: number): () => void;\n /** Create or update an object in the device tree. */\n upsertObject(id: string, def: ObjectDef): Promise<void>;\n /** Write a state value with ack (device-originated). */\n setStateAck(id: string, value: boolean | number | string): void;\n /** Adapter log. */\n log: ControllerLog;\n /**\n * The device's command gate: every request is paced through it, and its signal is the\n * connection's shutdown flag \u2014 a closed gate ends pending waits and stops state writes\n * from a poll that was already in flight. Absent in older tests \u2192 no browsing.\n */\n gate?: CommandGate;\n /** Per-device memory for answers that do not change while the device runs (see ProbeMemory). */\n probeMemory?: ProbeMemory;\n}\n\n/**\n * Drives one XML/YNC device: probe which zones answer, build the amp tree, seed\n * state, and route commands both ways. XML has no push, so state is refreshed by\n * a keepalive poll. Create-only.\n */\nexport class XmlDeviceController implements ConnectionHandle {\n private zones: XmlZone[] = [];\n private cancelKeepalive: (() => void) | undefined;\n private readonly dropDetector = new PollDropDetector();\n private browseEngine: BrowseEngine | undefined;\n /** The scenes each zone DECLARES (`Scene_Sel_Item`), for the recall write path. */\n private readonly scenesByZone = new Map<string, XmlScene[]>();\n /** Whether the device answers `<Tuner><Play_Info>` (the classic pre-2010 tuner). */\n private hasTuner = false;\n /** The amp state ids this controller actually created \u2014 the claim-with-proof gate for BOTH ways. */\n private readonly createdStates = new Set<string>();\n /** Per zone: the Basic_Status fields this device is known to deliver (persisted union). */\n private readonly zoneFields = new Map<string, Set<string>>();\n\n /**\n * @param deviceId the id-safe device id (object-tree path segment)\n * @param deps the client and adapter callbacks\n * @param pollIntervalMs how often to poll the device for state (default 60 s)\n */\n public constructor(\n private readonly deviceId: string,\n private readonly deps: XmlControllerDeps,\n private readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS,\n ) {}\n\n /**\n * Probe each zone, create the tree for the ones that answer, seed state, and\n * start the keepalive poll.\n *\n * @returns true if the main zone answered and the tree was created\n */\n public async start(): Promise<boolean> {\n // Probe all zones in parallel and keep each answering zone's status, so a zone is\n // fetched once (for the probe) and seeded from that same response \u2014 not twice.\n const probes = await Promise.all(\n XML_ZONES.map(async zone => ({ zone, status: await this.tryGetStatus(zone.element) })),\n );\n const answered = probes.filter(probe => probe.status && Object.keys(probe.status).length > 0);\n if (!answered.some(probe => probe.zone.key === \"main\")) {\n this.deps.log.debug(`${this.deviceId}: no XML main zone \u2014 creating no objects`);\n return false;\n }\n this.zones = answered.map(probe => probe.zone);\n // Freshness guard for the (persisted) probe memory: the model name is the identity\n // this transport can read. A different device behind the address drops the\n // remembered XML declarations (scenes, inputs, tuner, browse sources); a device\n // that reports no model keeps them \u2014 the YNCA/YXC guards catch a swap there.\n let model: string | undefined;\n try {\n model = await this.deps.client.getModelName();\n if (model !== undefined && this.deps.probeMemory) {\n if (this.deps.probeMemory.remembered(\"xmlModel\") !== model) {\n // Every XML-owned memory key carries the xml prefix (xmlBrowseSources,\n // xmlScenes:*, xmlInputs:*, xmlTuner, xmlModel).\n this.deps.probeMemory.drop(key => key.startsWith(\"xml\"));\n this.deps.probeMemory.set(\"xmlModel\", model);\n }\n }\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: getModelName failed (${errorMessage(e)})`);\n }\n // The zone's own input list (`Input_Sel_Item`, per zone \u2014 Main and Zone 2 differ on\n // real hardware): the device says which inputs it accepts, so the input state gets a\n // dropdown instead of a free string. Constant per model \u2014 remembered per device.\n const inputsByZone = new Map<string, string[]>();\n for (const zone of this.zones) {\n const body = await this.probeXml(\n `xmlInputs:${zone.key}`,\n zone.element,\n \"<Input><Input_Sel_Item>GetParam</Input_Sel_Item></Input>\",\n );\n inputsByZone.set(zone.key, parseInputList(body));\n }\n // Claim with proof, XML edition (2.0.1): only the states whose Basic_Status field\n // this device DELIVERS are created \u2014 a blind full-catalog rollout left valueless\n // objects (hdmi.out2, sound.direct, \u2026) standing on devices without the feature.\n // The delivered field set is a model property, remembered per zone (union, so a\n // later standby start \u2014 which may report fewer fields \u2014 cannot shrink the tree).\n for (const { zone, status } of answered) {\n const key = `xmlStatusFields:${zone.key}`;\n const remembered = this.deps.probeMemory?.remembered<string[]>(key);\n const fields = new Set<string>(Array.isArray(remembered) ? remembered : []);\n for (const field of Object.keys(status ?? {})) {\n fields.add(field);\n }\n this.deps.probeMemory?.set(key, [...fields]);\n this.zoneFields.set(zone.key, fields);\n }\n // Every parent \u2014 the zone channels included \u2014 is created by the per-state loop below\n // and named from the shared CHANNEL_NAME_KEYS table (a zone that answered has at least\n // its power state, so its channel always comes into being this way).\n const createdChannels = new Set<string>();\n for (const zone of this.zones) {\n for (const entry of XML_AMP_CATALOG) {\n // Main/system-wide features (scenes, HDMI outputs, party) exist only on the main zone.\n if (entry.mainOnly && zone.key !== \"main\") {\n continue;\n }\n // Claim with proof: skip what this device's status never delivered.\n if (entry.statusField !== undefined && !this.zoneFields.get(zone.key)?.has(entry.statusField)) {\n continue;\n }\n const stateId = `${zone.prefix}${entry.state}`;\n // A dotted state (e.g. scene.recall) needs its parent channel created first.\n const segments = stateId.split(\".\");\n for (let i = 1; i < segments.length; i++) {\n const channelId = segments.slice(0, i).join(\".\");\n if (!createdChannels.has(channelId)) {\n createdChannels.add(channelId);\n await this.deps.upsertObject(`${this.deviceId}.${channelId}`, {\n id: channelId,\n type: \"channel\",\n common: {\n // Capitalised like the catalog path does it, so the same folder cannot end up\n // called \"sound\" here and \"Sound\" there depending on which transport owns it.\n name: CHANNEL_NAME_KEYS[segments[i - 1]]\n ? tName(CHANNEL_NAME_KEYS[segments[i - 1]])\n : segments[i - 1].charAt(0).toUpperCase() + segments[i - 1].slice(1),\n },\n });\n }\n }\n const { nameKey, descKey, ...rest } = entry.common;\n const common: ObjectDef[\"common\"] = {\n ...rest,\n name: tName(nameKey),\n // An absent key means the datapoint explains itself \u2014 the fleet standard wants the\n // field empty there rather than filled with invented prose.\n ...(descKey ? { desc: tName(descKey) } : {}),\n };\n // The device's own input list becomes the dropdown (XML-owned devices only \u2014\n // where YNCA is present its dropdown wins via the owner policy).\n const inputs = entry.state === \"input\" ? inputsByZone.get(zone.key) : undefined;\n if (inputs && inputs.length > 0) {\n common.states = Object.fromEntries(inputs.map(input => [input, input]));\n }\n await this.deps.upsertObject(`${this.deviceId}.${stateId}`, {\n id: stateId,\n type: \"state\",\n common,\n });\n this.createdStates.add(stateId);\n }\n }\n await this.setupScenes(createdChannels);\n await this.setupTuner(createdChannels);\n // Seed from the statuses already fetched during the probe \u2014 no second round-trip.\n for (const { zone, status } of answered) {\n if (status) {\n this.seedZone(zone, status);\n }\n }\n // The model name (already read by the freshness guard) for the device-manager card.\n // Best-effort \u2014 a device that does not report it still connects, the line stays empty.\n if (model) {\n // The info channel and info.model already exist \u2014 the adapter creates them for\n // every device up front, so the card renders even while the device is offline.\n this.emit(\"info.model\", model);\n }\n await this.setupBrowse();\n this.cancelKeepalive = this.deps.scheduleKeepalive(() => void this.keepalive(), this.pollIntervalMs);\n // The adapter logs one combined \"ready\" line across all transports; this stays at debug.\n this.deps.log.debug(`${this.deviceId}: Yamaha (XML) device ready (XML)`);\n return true;\n }\n\n /**\n * Read one element's list once per device: the answer is a property of the MODEL\n * (input lists, scene declarations), so reconnects reuse it via the probe memory.\n *\n * Only a DEFINITE answer is remembered: a body, or the model's own \"no such node\"\n * (bodyless HTTP 400 / return code 2, both captured on the RX-V6A) as \"declares none\".\n * A transient failure \u2014 timeout, connection error, HTTP 5xx, or a state-dependent\n * refusal (return code 3/4, \"not now\") \u2014 is NOT remembered: before this, one busy\n * moment during the first contact recorded \"no scenes\" for that device for good, until\n * the model changed. Now it is simply asked again on the next connect.\n *\n * @param key the probe-memory key\n * @param element the XML element to ask\n * @param inner the inner GET request\n * @returns the raw response body, or \"\" when the device (definitely or for now) has none\n */\n private async probeXml(key: string, element: string, inner: string): Promise<string> {\n const probe = async (): Promise<string> => {\n let body: string;\n try {\n body = await this.deps.client.getXml(element, inner);\n } catch (e) {\n if (isPermanentXmlRefusal(e)) {\n return \"\"; // the model has no such node \u2014 definite\n }\n throw e; // transient \u2014 not remembered\n }\n const rc = parseReturnCode(body);\n if (rc !== undefined && rc !== 0) {\n if (rc === 2) {\n return \"\"; // RC 2 = the node does not exist on this model \u2014 definite\n }\n throw new Error(`device refused ${element} probe (RC=${rc})`); // \"not now\" \u2014 not remembered\n }\n return body;\n };\n try {\n return this.deps.probeMemory ? await this.deps.probeMemory.once(key, probe) : await probe();\n } catch (e) {\n this.deps.log.debug(\n `${this.deviceId}: ${key} probe failed, asking again on the next connect (${errorMessage(e)})`,\n );\n return \"\";\n }\n }\n\n /**\n * Build the scene surface from the device's OWN declaration (#615): each zone's\n * `<Scene_Sel_Item>` names the scenes that exist, their titles and the write value\n * (`Scene N` via `<Scene_Sel>`). The predecessor blindly sent `Scene_Load` to a\n * fixed 1..12 main-zone state; the capture shows the device declaring `Scene_Sel`\n * instead \u2014 and declaring scenes for Zone 2 too.\n *\n * @param createdChannels the channel ids already created (extended here)\n */\n private async setupScenes(createdChannels: Set<string>): Promise<void> {\n for (const zone of this.zones) {\n const body = await this.probeXml(\n `xmlScenes:${zone.key}`,\n zone.element,\n \"<Scene><Scene_Sel_Item>GetParam</Scene_Sel_Item></Scene>\",\n );\n const scenes = parseSceneList(body);\n if (scenes.length === 0) {\n continue;\n }\n this.scenesByZone.set(zone.key, scenes);\n const channelId = `${zone.prefix}scene`;\n if (!createdChannels.has(channelId)) {\n createdChannels.add(channelId);\n await this.deps.upsertObject(`${this.deviceId}.${channelId}`, {\n id: channelId,\n type: \"channel\",\n common: { name: tName(CHANNEL_NAME_KEYS.scene ?? \"Scenes\") },\n });\n }\n const max = Math.max(...scenes.map(scene => scene.num));\n await this.deps.upsertObject(`${this.deviceId}.${channelId}.recall`, {\n id: `${channelId}.recall`,\n type: \"state\",\n common: {\n name: tName(\"recallScene\"),\n desc: tName(\"descRecallScene\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n max,\n step: 1,\n // The declared titles as the dropdown, so the picker shows \"Movie Viewing\",\n // not a bare number.\n states: Object.fromEntries(scenes.map(scene => [scene.num, scene.title])),\n },\n });\n // ONE list state instead of a name datapoint per scene (v2.0.0): visualizations\n // read titles as VALUES (button captions \u2014 the #613 reporter's setup), and a\n // dropdown's labels are not readable, so the list carries them.\n await this.deps.upsertObject(`${this.deviceId}.${channelId}.list`, {\n id: `${channelId}.list`,\n type: \"state\",\n common: { name: tName(\"scenesNumberTitle\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n this.emit(`${channelId}.list`, JSON.stringify(scenes));\n }\n }\n\n /**\n * Build the classic tuner surface (pre-2010 devices, where XML is the ONLY\n * transport \u2014 the predecessor served their tuner, the rewrite had dropped it).\n * Existence is probed once per device; the preset write is the openHAB-verified\n * `<Play_Control><Preset><Preset_Sel>`; frequency/RDS/tuned are read-only from\n * Play_Info. On newer devices YNCA/YXC own these ids via the owner policy.\n *\n * @param createdChannels the channel ids already created (extended here)\n */\n private async setupTuner(createdChannels: Set<string>): Promise<void> {\n const probe = await this.probeXml(\"xmlTuner\", \"Tuner\", \"<Play_Info>GetParam</Play_Info>\");\n if (probe.length === 0) {\n return;\n }\n this.hasTuner = true;\n if (!createdChannels.has(\"tuner\")) {\n createdChannels.add(\"tuner\");\n await this.deps.upsertObject(`${this.deviceId}.tuner`, {\n id: \"tuner\",\n type: \"channel\",\n common: { name: tName(CHANNEL_NAME_KEYS.tuner ?? \"Tuner\") },\n });\n }\n const state = async (id: string, common: ObjectDef[\"common\"]): Promise<void> => {\n await this.deps.upsertObject(`${this.deviceId}.tuner.${id}`, { id: `tuner.${id}`, type: \"state\", common });\n };\n await state(\"preset\", {\n name: tName(\"presetRecallByNumber\"),\n desc: tName(\"descPresetRecallByNumber\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n // Slot 1 upwards \u2014 `handleTunerWrite` drops a 0, so offering it as the lower bound\n // invited a write that goes nowhere.\n min: 1,\n max: 40,\n step: 1,\n });\n await state(\"frequency\", {\n name: tName(\"frequency\"),\n type: \"number\",\n role: \"value\",\n unit: \"kHz\",\n read: true,\n write: false,\n });\n await state(\"rdsService\", {\n name: tName(\"rdsStation\"),\n desc: tName(\"descRdsStation\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: false,\n });\n await state(\"rdsText\", {\n name: tName(\"rdsText\"),\n desc: tName(\"descRdsText\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: false,\n });\n await state(\"tuned\", {\n name: tName(\"tunedToAStation\"),\n desc: tName(\"descTunedToAStation\"),\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n });\n await state(\"stereo\", {\n name: tName(\"stereoReception\"),\n desc: tName(\"descStereoReception\"),\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n });\n // NOT `emitTunerInfo(probe)`: the probe body comes out of the PERSISTED memory on every\n // reconnect and restart, so seeding from it published a snapshot of an earlier session\n // \u2014 frequency, RDS station and text, \"tuned\" \u2014 as the CURRENT reading, until the first\n // poll up to a whole interval later. The existence verdict is a model property and stays\n // remembered; the values are read fresh. (Same class as the menu's resting shape, which\n // showed rows six days older than the connection until it was fixed.)\n await this.refreshTuner();\n }\n\n /**\n * Write the tuner states from a Play_Info response.\n *\n * @param xml the Play_Info response body\n */\n private emitTunerInfo(xml: string): void {\n const info = parseTunerInfo(xml);\n if (info.preset !== undefined) {\n this.emit(\"tuner.preset\", info.preset);\n }\n if (info.frequency !== undefined) {\n // Unified kHz (v2.0.0): the device reports FM in MHz, AM in kHz \u2014 normalize.\n this.emit(\"tuner.frequency\", Math.round(info.frequencyUnit === \"MHz\" ? info.frequency * 1000 : info.frequency));\n }\n if (info.rdsService !== undefined) {\n this.emit(\"tuner.rdsService\", info.rdsService);\n }\n if (info.rdsText !== undefined) {\n this.emit(\"tuner.rdsText\", info.rdsText);\n }\n if (info.tuned !== undefined) {\n this.emit(\"tuner.tuned\", info.tuned);\n }\n if (info.stereo !== undefined) {\n this.emit(\"tuner.stereo\", info.stereo);\n }\n }\n\n /**\n * Poll the tuner's Play_Info (keepalive) and write the states.\n */\n private async refreshTuner(): Promise<void> {\n try {\n this.emitTunerInfo(await this.deps.client.getXml(\"Tuner\", \"<Play_Info>GetParam</Play_Info>\"));\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: tuner Play_Info failed: ${errorMessage(e)}`);\n }\n }\n\n /**\n * A user write to `tuner.preset` \u2192 the openHAB-verified preset recall.\n *\n * @param stateId the state id relative to the device\n * @param value the written value\n * @returns true when the id was the tuner preset (handled here)\n */\n private handleTunerWrite(stateId: string, value: unknown): boolean {\n if (stateId !== \"tuner.preset\" || !this.hasTuner) {\n return stateId === \"tuner.preset\";\n }\n const num = Math.round(Number(value));\n if (!Number.isFinite(num) || num < 1) {\n return true;\n }\n void this.applyCommand({\n zone: \"Tuner\",\n inner: `<Play_Control><Preset><Preset_Sel>${num}</Preset_Sel></Preset></Play_Control>`,\n });\n return true;\n }\n\n /**\n * A user write to a zone's `scene.recall` \u2192 the DECLARED write element\n * (`<Scene><Scene_Sel>Scene N</Scene_Sel></Scene>`). Only zones that declared\n * scenes accept the write; a refusal lands in the log via applyCommand.\n *\n * @param stateId the state id relative to the device\n * @param value the written value\n * @returns true when the id was a scene recall (handled here)\n */\n private handleSceneWrite(stateId: string, value: unknown): boolean {\n const match = /^(?:multiroom\\.(zone[234])\\.)?scene\\.recall$/.exec(stateId);\n if (!match) {\n return false;\n }\n const zoneKey = match[1] ?? \"main\";\n const zone = this.zones.find(z => z.key === zoneKey);\n const scenes = this.scenesByZone.get(zoneKey);\n // A TITLE is as valid a write as a number (\"Movie Viewing\" \u2192 Scene 1).\n const byTitle =\n typeof value === \"string\" && !/^\\d+$/.test(value.trim())\n ? scenes?.find(scene => scene.title.toLowerCase() === value.trim().toLowerCase())?.num\n : undefined;\n const num = byTitle ?? Math.round(Number(value));\n if (!zone || !scenes || !scenes.some(scene => scene.num === num)) {\n return true;\n }\n void this.applyCommand({ zone: zone.element, inner: `<Scene><Scene_Sel>Scene ${num}</Scene_Sel></Scene>` });\n return true;\n }\n\n /**\n * Create the browsing surface (#613) when at least one source answers a List_Info\n * probe (NET_RADIO/SERVER/USB \u2014 the menus the predecessor adapter's users drove\n * via `Realtime.*.LINE1TXT` + `xmlCommand`). Skipped without a delay dep (older tests).\n */\n private async setupBrowse(): Promise<void> {\n const gate = this.deps.gate;\n if (!gate) {\n return;\n }\n const delay = (ms: number): Promise<void> => gate.delay(ms);\n // Which sources have a menu is a property of the MODEL, not of this connection \u2014 ask\n // once per device instead of costing three extra requests (up to five seconds on a\n // receiver that has no menus at all) on every single reconnect.\n const probe = async (): Promise<string[]> => {\n const probes = await Promise.all(\n XML_BROWSE_SOURCES.map(async source => {\n try {\n const body = await this.deps.client.getXml(source.element, \"<List_Info>GetParam</List_Info>\");\n return body.includes(\"<Menu_Status>\") ? source.key : undefined;\n } catch (e) {\n if (isPermanentXmlRefusal(e)) {\n return undefined; // this model has no menu for that source (bodyless HTTP 400)\n }\n throw e; // transient \u2014 \"no menus\" must not be remembered for good\n }\n }),\n );\n return probes.filter((key): key is string => key !== undefined);\n };\n let available: Set<string>;\n try {\n available = new Set(\n this.deps.probeMemory ? await this.deps.probeMemory.once(\"xmlBrowseSources\", probe) : await probe(),\n );\n } catch (e) {\n this.deps.log.debug(\n `${this.deviceId}: browse probe failed, asking again on the next connect (${errorMessage(e)})`,\n );\n return;\n }\n if (available.size === 0) {\n return;\n }\n const driver = new XmlBrowseDriver(this.deps.client, available, delay);\n this.browseEngine = await createBrowseSurface(driver, this.deviceId, {\n upsertObject: this.deps.upsertObject,\n emit: (id, value) => this.emit(id, value),\n log: this.deps.log,\n delay,\n });\n }\n\n /**\n * Write a device-originated value \u2014 but never after the connection was closed. A poll\n * that was already in flight when the adapter stopped would otherwise still write into\n * a tree that is being torn down.\n *\n * @param relativeId the state id relative to the device\n * @param value the value to write\n */\n private emit(relativeId: string, value: boolean | number | string): void {\n if (this.deps.gate?.closed) {\n return;\n }\n this.deps.setStateAck(`${this.deviceId}.${relativeId}`, value);\n }\n\n /**\n * Handle a state change: a user write (ack false) becomes an XML command; an\n * acked change (the device's own echo) is ignored.\n *\n * @param fullStateId the full state id (device id + \".\" + state)\n * @param ack whether the change is acked (device-originated)\n * @param value the new value\n */\n public handleStateChange(fullStateId: string, ack: boolean, value: unknown): void {\n if (ack) {\n return;\n }\n const prefix = `${this.deviceId}.`;\n if (!fullStateId.startsWith(prefix)) {\n return;\n }\n const stateId = fullStateId.slice(prefix.length);\n if (stateId.startsWith(\"player.browse.\")) {\n this.browseEngine?.handleWrite(stateId, value);\n return;\n }\n // Scenes and the classic tuner are device-declared (not in the static catalog).\n if (this.handleSceneWrite(stateId, value)) {\n return;\n }\n if (this.handleTunerWrite(stateId, value)) {\n return;\n }\n // Claim with proof, on the WRITE way too. Object creation has been proof-gated since\n // 2.0.1 (only fields this device's Basic_Status really delivers), but the write path was\n // not \u2014 the comment on `createdStates` claimed otherwise while it guarded the read side\n // alone. XML was the last transport without it: YNCA writes only through its per-device\n // write map, MusicCast only through device-declared endpoints. It bites on a datapoint an\n // adapter version before 2.0.1 created and that once carried a value, so no sweep removes\n // it: writing it put a blind command on the wire that the device answers with a refusal.\n if (!this.createdStates.has(stateId)) {\n this.deps.log.debug(`${this.deviceId}: ${stateId} was not reported by this device \u2014 write dropped`);\n return;\n }\n const command = stateToXml(stateId, value);\n if (command) {\n void this.applyCommand(command);\n }\n }\n\n /**\n * Register the supervisor's drop handler. XML has no push/socket-drop event, so a\n * drop is inferred from a run of failed polls (see keepalive).\n *\n * @param cb invoked once when the device is judged gone\n */\n public onDrop(cb: (reason?: Error) => void): void {\n this.dropDetector.onDrop(cb);\n }\n\n /** Cancel the keepalive poll. Synchronous \u2014 safe to call from onUnload. */\n public close(): void {\n this.browseEngine?.close();\n // Closing the gate empties its queue and aborts its signal: queued requests are\n // dropped and every pending wait ends, so nothing writes after the teardown.\n this.deps.gate?.close();\n this.cancelKeepalive?.();\n this.cancelKeepalive = undefined;\n }\n\n /**\n * Poll every live zone. If every zone fails for three consecutive failed polls in a\n * row, the device is judged gone and a drop is reported so the supervisor reconnects.\n */\n private async keepalive(): Promise<void> {\n // The keepalive is an async handler on an adapter timer: a rejection here is an\n // UNHANDLED rejection, and js-controller turns those into an adapter stop. Every step\n // below catches for itself today, so the guard is what makes that a guarantee instead\n // of something the next change has to remember.\n try {\n let anyOk = false;\n for (const zone of this.zones) {\n if (await this.refreshZone(zone)) {\n anyOk = true;\n }\n }\n if (this.hasTuner) {\n await this.refreshTuner();\n }\n this.dropDetector.record(anyOk);\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: keepalive poll failed: ${errorMessage(e)}`);\n }\n }\n\n /**\n * Fetch a zone's status and write its amp states with ack.\n *\n * @param zone the zone to refresh\n * @returns true if the status was fetched, false if the request failed\n */\n private async refreshZone(zone: XmlZone): Promise<boolean> {\n const status = await this.tryGetStatus(zone.element);\n if (!status) {\n return false;\n }\n this.seedZone(zone, status);\n return true;\n }\n\n /**\n * Write a zone's amp states from an already-fetched Basic_Status (used to seed\n * from the start-up probe without a second round-trip).\n *\n * @param zone the zone the status belongs to\n * @param status the parsed Basic_Status\n */\n private seedZone(zone: XmlZone, status: BasicStatus): void {\n // A field the device delivers for the FIRST time mid-run has no object yet\n // (claim-with-proof creates only proven fields at start): remember it \u2014 the next\n // start creates it \u2014 and skip the write, so no state lands without an object.\n const known = this.zoneFields.get(zone.key);\n if (known) {\n let grew = false;\n for (const field of Object.keys(status)) {\n if (!known.has(field)) {\n known.add(field);\n grew = true;\n }\n }\n if (grew) {\n this.deps.probeMemory?.set(`xmlStatusFields:${zone.key}`, [...known]);\n }\n }\n for (const update of parseXmlStatus(status, zone.key)) {\n if (this.createdStates.has(update.id)) {\n this.emit(update.id, update.value);\n }\n }\n }\n\n /**\n * Read a zone's status, swallowing errors (an absent zone or an offline device).\n *\n * @param element the XML zone element\n * @returns the parsed status, or undefined on failure\n */\n private async tryGetStatus(element: string): Promise<BasicStatus | undefined> {\n try {\n return await this.deps.client.getStatus(element);\n } catch (e) {\n this.deps.log.debug(`${this.deviceId}: getStatus(${element}) failed: ${errorMessage(e)}`);\n return undefined;\n }\n }\n\n /**\n * Send a mapped command to the device.\n *\n * @param command the XML command to apply\n */\n private async applyCommand(command: XmlCommand): Promise<void> {\n try {\n await this.deps.client.send(command.zone, command.inner);\n } catch (e) {\n this.deps.log.warn(`${this.deviceId}: XML command failed: ${errorMessage(e)}`);\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -231,6 +231,7 @@ function mapYxcToObjects(capabilities) {
|
|
|
231
231
|
type: "state",
|
|
232
232
|
common: {
|
|
233
233
|
name: (0, import_i18n.tName)("recallScene"),
|
|
234
|
+
desc: (0, import_i18n.tName)("descRecallScene"),
|
|
234
235
|
type: "number",
|
|
235
236
|
role: "level",
|
|
236
237
|
read: true,
|
|
@@ -301,7 +302,15 @@ function mapYxcToObjects(capabilities) {
|
|
|
301
302
|
objects.push({
|
|
302
303
|
id: "player.netPlayer.preset",
|
|
303
304
|
type: "state",
|
|
304
|
-
common: {
|
|
305
|
+
common: {
|
|
306
|
+
name: (0, import_i18n.tName)("recallPreset"),
|
|
307
|
+
desc: (0, import_i18n.tName)("descRecallPreset"),
|
|
308
|
+
type: "number",
|
|
309
|
+
role: "level",
|
|
310
|
+
read: true,
|
|
311
|
+
write: true,
|
|
312
|
+
min: 1
|
|
313
|
+
}
|
|
305
314
|
});
|
|
306
315
|
objects.push({
|
|
307
316
|
id: "player.netPlayer.presets",
|
|
@@ -390,17 +399,38 @@ function mapYxcToObjects(capabilities) {
|
|
|
390
399
|
objects.push({
|
|
391
400
|
id: "tuner.rdsText",
|
|
392
401
|
type: "state",
|
|
393
|
-
common: {
|
|
402
|
+
common: {
|
|
403
|
+
name: (0, import_i18n.tName)("rdsText"),
|
|
404
|
+
desc: (0, import_i18n.tName)("descRdsText"),
|
|
405
|
+
type: "string",
|
|
406
|
+
role: "text",
|
|
407
|
+
read: true,
|
|
408
|
+
write: false
|
|
409
|
+
}
|
|
394
410
|
});
|
|
395
411
|
objects.push({
|
|
396
412
|
id: "tuner.rdsTextB",
|
|
397
413
|
type: "state",
|
|
398
|
-
common: {
|
|
414
|
+
common: {
|
|
415
|
+
name: (0, import_i18n.tName)("rdsTextB"),
|
|
416
|
+
desc: (0, import_i18n.tName)("descRdsTextB"),
|
|
417
|
+
type: "string",
|
|
418
|
+
role: "text",
|
|
419
|
+
read: true,
|
|
420
|
+
write: false
|
|
421
|
+
}
|
|
399
422
|
});
|
|
400
423
|
objects.push({
|
|
401
424
|
id: "tuner.rdsService",
|
|
402
425
|
type: "state",
|
|
403
|
-
common: {
|
|
426
|
+
common: {
|
|
427
|
+
name: (0, import_i18n.tName)("rdsStation"),
|
|
428
|
+
desc: (0, import_i18n.tName)("descRdsStation"),
|
|
429
|
+
type: "string",
|
|
430
|
+
role: "text",
|
|
431
|
+
read: true,
|
|
432
|
+
write: false
|
|
433
|
+
}
|
|
404
434
|
});
|
|
405
435
|
objects.push({
|
|
406
436
|
id: "tuner.rdsProgramType",
|
|
@@ -409,6 +439,7 @@ function mapYxcToObjects(capabilities) {
|
|
|
409
439
|
});
|
|
410
440
|
const presetCommon = {
|
|
411
441
|
name: (0, import_i18n.tName)("presetRecallByNumber"),
|
|
442
|
+
desc: (0, import_i18n.tName)("descPresetRecallByNumber"),
|
|
412
443
|
type: "number",
|
|
413
444
|
role: "level",
|
|
414
445
|
read: true,
|
|
@@ -442,7 +473,14 @@ function mapYxcToObjects(capabilities) {
|
|
|
442
473
|
objects.push({
|
|
443
474
|
id: "tuner.audioMode",
|
|
444
475
|
type: "state",
|
|
445
|
-
common: {
|
|
476
|
+
common: {
|
|
477
|
+
name: (0, import_i18n.tName)("audioMode"),
|
|
478
|
+
desc: (0, import_i18n.tName)("descAudioMode"),
|
|
479
|
+
type: "string",
|
|
480
|
+
role: "state",
|
|
481
|
+
read: true,
|
|
482
|
+
write: false
|
|
483
|
+
}
|
|
446
484
|
});
|
|
447
485
|
if (bands.includes("dab")) {
|
|
448
486
|
objects.push({ id: "tuner.dab", type: "channel", common: { name: (0, import_i18n.tName)("dab") } });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/yxc/object-mapper.ts"],
|
|
4
|
-
"sourcesContent": ["import { CHANNEL_NAME_KEYS, type ObjectDef } from \"../catalog/types\";\nimport { tName, type I18nKey } from \"../i18n\";\nimport { YXC_ZONE_IDS, zonePrefix } from \"./zones\";\nimport type { YxcCapabilities } from \"./capability\";\nimport { YXC_AMP_CATALOG } from \"./catalog\";\nimport { ALARM_DAYS, DAB_FIELDS } from \"./command-mapper\";\n\n/**\n * Build a value \u2192 label dropdown map from a device-reported value list.\n *\n * @param values the allowed values\n * @returns the states map\n */\nfunction selfMap(values: readonly string[]): Record<string, string> {\n return Object.fromEntries(values.map(value => [value, value]));\n}\n\n/** The zones the adapter maps: main flat, zone2-4 each under multiroom. */\nconst ZONES: Array<{ id: string; prefix: string }> = YXC_ZONE_IDS.map(id => ({ id, prefix: zonePrefix(id) }));\n\n/** The \"now playing\" block's states (v2.0.0, one per zone): read metadata + transport buttons. */\nconst PLAYER_STATES: Array<{\n state: string;\n common: Omit<ObjectDef[\"common\"], \"name\"> & { nameKey: I18nKey; descKey?: I18nKey };\n}> = [\n {\n // What the zone is playing (the netusb source name, or `cd`) \u2014 read-only display;\n // switching happens over the zone's `input` state.\n state: \"source\",\n common: { nameKey: \"playingSource\", type: \"string\", role: \"text\", read: true, write: false },\n },\n {\n state: \"playback\",\n common: {\n // media.state is a number in the type-detector; the same 0/1/2 coding as the YNCA player.\n nameKey: \"playback\",\n type: \"number\",\n role: \"media.state\",\n read: true,\n write: false,\n states: { 0: \"Play\", 1: \"Stop\", 2: \"Pause\" },\n },\n },\n { state: \"artist\", common: { nameKey: \"artist\", type: \"string\", role: \"media.artist\", read: true, write: false } },\n { state: \"album\", common: { nameKey: \"album\", type: \"string\", role: \"media.album\", read: true, write: false } },\n { state: \"track\", common: { nameKey: \"track\", type: \"string\", role: \"media.title\", read: true, write: false } },\n // Read-only playback metadata, typed exactly like the YNCA sources so both players\n // present the same shape on one device: repeat as the media.mode.repeat number code\n // (wire off/one/all, captures-verified), shuffle as a media.mode.shuffle boolean\n // (wire knows only off/on). Writing stays with the toggle buttons \u2014 YXC has no setter.\n {\n state: \"repeat\",\n common: {\n nameKey: \"repeat\",\n type: \"number\",\n role: \"media.mode.repeat\",\n read: true,\n write: false,\n states: { 0: \"Off\", 1: \"Single\", 2: \"All\" },\n },\n },\n {\n state: \"shuffle\",\n common: { nameKey: \"shuffle\", type: \"boolean\", role: \"media.mode.shuffle\", read: true, write: false },\n },\n // Both forms of each time, from the one value the device reports: the seconds fill the\n // type detector's media-player slot (it takes nothing else), the text is what a\n // visualisation shows. The YNCA side publishes exactly the same pair, converted the other\n // way round \u2014 so the datapoints mean the same thing on every device.\n {\n state: \"elapsedTime\",\n common: {\n nameKey: \"elapsedTime\",\n descKey: \"descElapsedTime\",\n type: \"number\",\n unit: \"s\",\n role: \"media.elapsed\",\n read: true,\n write: false,\n },\n },\n {\n state: \"elapsedTimeText\",\n common: {\n nameKey: \"elapsedTimeReadable\",\n descKey: \"descElapsedTimeReadable\",\n type: \"string\",\n role: \"media.elapsed.text\",\n read: true,\n write: false,\n },\n },\n {\n state: \"totalTime\",\n common: {\n nameKey: \"totalTime\",\n descKey: \"descTotalTime\",\n type: \"number\",\n unit: \"s\",\n role: \"media.duration\",\n read: true,\n write: false,\n },\n },\n {\n state: \"totalTimeText\",\n common: {\n nameKey: \"totalTimeReadable\",\n descKey: \"descTotalTimeReadable\",\n type: \"string\",\n role: \"media.duration.text\",\n read: true,\n write: false,\n },\n },\n {\n state: \"albumArt\",\n common: { nameKey: \"albumArt\", type: \"string\", role: \"media.cover\", read: true, write: false },\n },\n // Transport buttons carry the type-detector media-player roles so a MusicCast player's\n // controls are recognised as play/pause/stop/next/prev, not generic buttons.\n { state: \"play\", common: { nameKey: \"play\", type: \"boolean\", role: \"button.play\", read: false, write: true } },\n { state: \"pause\", common: { nameKey: \"pause\", type: \"boolean\", role: \"button.pause\", read: false, write: true } },\n { state: \"stop\", common: { nameKey: \"stop\", type: \"boolean\", role: \"button.stop\", read: false, write: true } },\n { state: \"next\", common: { nameKey: \"next\", type: \"boolean\", role: \"button.next\", read: false, write: true } },\n { state: \"prev\", common: { nameKey: \"previous\", type: \"boolean\", role: \"button.prev\", read: false, write: true } },\n {\n state: \"repeatToggle\",\n common: { nameKey: \"toggleRepeat\", type: \"boolean\", role: \"button\", read: false, write: true },\n },\n {\n state: \"shuffleToggle\",\n common: { nameKey: \"toggleShuffle\", type: \"boolean\", role: \"button\", read: false, write: true },\n },\n];\n\n/**\n * Append a media-player block (channel + the shared player states) under a\n * dotted prefix. Used for every player source the device reports.\n *\n * @param objects the object list to append to\n * @param prefix the channel/state prefix (e.g. `netPlayer`, `cd`)\n * @param channelName the human-readable channel name\n */\nfunction pushPlayerBlock(objects: ObjectDef[], prefix: string, channelName: ioBroker.StringOrTranslated): void {\n objects.push({ id: prefix, type: \"channel\", common: { name: channelName } });\n for (const player of PLAYER_STATES) {\n const { nameKey: playerNameKey, descKey: playerDescKey, ...playerCommon } = player.common;\n objects.push({\n id: `${prefix}.${player.state}`,\n type: \"state\",\n common: {\n ...playerCommon,\n name: tName(playerNameKey),\n ...(playerDescKey ? { desc: tName(playerDescKey) } : {}),\n },\n });\n }\n}\n\n/**\n * Turn YXC capabilities into the unified object tree: main's functions as\n * top-level states, each additional zone as a channel with its own states. An\n * input state is added when the zone offers inputs. Player sources (netusb, cd)\n * and the tuner get their own channel. Only reported functions are created,\n * parents before children. States and their common come from {@link YXC_AMP_CATALOG}.\n *\n * @param capabilities the parsed YXC capabilities\n * @returns the object definitions to create\n */\nexport function mapYxcToObjects(capabilities: YxcCapabilities): ObjectDef[] {\n const objects: ObjectDef[] = [];\n const channels = new Set<string>();\n for (const zoneDef of ZONES) {\n const zone = capabilities.zones.find(z => z.id === zoneDef.id);\n if (!zone) {\n continue;\n }\n const hasInput = zone.inputs.length > 0;\n const entries = YXC_AMP_CATALOG.filter(entry => {\n // Device-global entries (id under multiroom.) exist once \u2014 never as a per-zone copy.\n if (zoneDef.id !== \"main\" && entry.state.startsWith(\"multiroom.\")) {\n return false;\n }\n if (entry.create.kind === \"always\") {\n return true;\n }\n if (entry.create.kind === \"input\") {\n return hasInput;\n }\n return zone.funcs.includes(entry.create.func);\n });\n // A zone needs an advertised function or an input to exist \u2014 the \"always\" status\n // fields alone (which every entry set contains) do not create a zone.\n if (!entries.some(entry => entry.create.kind !== \"always\")) {\n continue;\n }\n // Every parent \u2014 the zone channel included \u2014 is created by the per-state loop and\n // named from the shared CHANNEL_NAMES table (a zone exists only with an entry).\n for (const entry of entries) {\n const fullId = `${zoneDef.prefix}${entry.state}`;\n const segments = fullId.split(\".\");\n for (let i = 1; i < segments.length; i++) {\n const channelId = segments.slice(0, i).join(\".\");\n if (!channels.has(channelId)) {\n channels.add(channelId);\n const segment = segments[i - 1];\n objects.push({\n id: channelId,\n type: \"channel\",\n common: {\n name: CHANNEL_NAME_KEYS[segment]\n ? tName(CHANNEL_NAME_KEYS[segment])\n : segment.charAt(0).toUpperCase() + segment.slice(1),\n },\n });\n }\n }\n const { nameKey: entryNameKey, descKey: entryDescKey, ...entryRest } = entry.common;\n const common: ObjectDef[\"common\"] = {\n ...entryRest,\n name: tName(entryNameKey),\n ...(entryDescKey ? { desc: tName(entryDescKey) } : {}),\n };\n if (entry.state === \"volume\" && zone.volumeRange) {\n common.min = zone.volumeRange.min;\n common.max = zone.volumeRange.max;\n common.step = zone.volumeRange.step;\n }\n // The device's own allowed-value lists (getFeatures) become dropdowns: the zone's\n // inputs on the input state, sound_program_list & co on their states. On a device\n // that also speaks YNCA the YNCA-owned dropdown wins via the owner policy \u2014 these\n // matter on MusicCast-only devices, where the raw string was the old poverty.\n if (entry.state === \"input\" && zone.inputs.length > 0) {\n common.states = selfMap(zone.inputs);\n }\n const valueList = zone.valueLists?.[entry.state];\n if (valueList) {\n common.states = selfMap(valueList);\n }\n objects.push({ id: fullId, type: \"state\", common });\n }\n const zoneChannelHelper = (id: string, name: ioBroker.StringOrTranslated): void => {\n if (!channels.has(id)) {\n channels.add(id);\n objects.push({ id, type: \"channel\", common: { name } });\n }\n };\n // Scene recall (#615): the zone declares `scene` + scene_num \u2014 per zone, so a\n // Zone-2 scene is first-class (the RX-V6A declares 8 for main AND zone2).\n if (zone.funcs.includes(\"scene\") && zone.sceneNum && zone.sceneNum > 0) {\n zoneChannelHelper(`${zoneDef.prefix}scene`, tName(CHANNEL_NAME_KEYS.scene ?? \"Scenes\"));\n objects.push({\n id: `${zoneDef.prefix}scene.recall`,\n type: \"state\",\n common: {\n name: tName(\"recallScene\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n max: zone.sceneNum,\n step: 1,\n },\n });\n }\n // The on-screen remote (cursor pad + menu keys) \u2014 declared as zone functions\n // `cursor`/`menu`; the endpoints and their vocabulary are device-verified.\n if (zone.funcs.includes(\"cursor\") || zone.funcs.includes(\"menu\")) {\n zoneChannelHelper(`${zoneDef.prefix}remote`, tName(CHANNEL_NAME_KEYS.remote ?? \"Remote control\"));\n if (zone.funcs.includes(\"cursor\")) {\n objects.push({\n id: `${zoneDef.prefix}remote.cursor`,\n type: \"state\",\n common: {\n name: tName(\"cursorPad\"),\n type: \"string\",\n role: \"state\",\n read: false,\n write: true,\n states: selfMap([\"up\", \"down\", \"left\", \"right\", \"select\", \"return\"]),\n },\n });\n }\n if (zone.funcs.includes(\"menu\")) {\n objects.push({\n id: `${zoneDef.prefix}remote.menu`,\n type: \"state\",\n common: {\n name: tName(\"menuKey\"),\n type: \"string\",\n role: \"state\",\n read: false,\n write: true,\n states: selfMap([\"on_screen\", \"top_menu\", \"menu\", \"option\", \"display\", \"home\"]),\n },\n });\n }\n }\n // The audio-signal info (own endpoint, declared as `signal_info`): what the zone\n // currently decodes \u2014 format, sampling rate, bit depth, bitrate.\n if (zone.funcs.includes(\"signal_info\")) {\n zoneChannelHelper(`${zoneDef.prefix}sound`, tName(CHANNEL_NAME_KEYS.sound ?? \"Sound\"));\n zoneChannelHelper(`${zoneDef.prefix}sound.signal`, tName(CHANNEL_NAME_KEYS.signal ?? \"Audio signal\"));\n const signal = (\n id: string,\n name: ioBroker.StringOrTranslated,\n type: \"string\" | \"number\",\n role: \"text\" | \"value\",\n ): void => {\n objects.push({\n id: `${zoneDef.prefix}sound.signal.${id}`,\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n });\n };\n signal(\"format\", tName(\"audioSignalFormat\"), \"string\", \"text\");\n signal(\"sampling\", tName(\"audioSamplingRate\"), \"string\", \"text\");\n signal(\"bits\", tName(\"audioBitDepth\"), \"string\", \"text\");\n signal(\"bitrate\", tName(\"audioBitrate\"), \"number\", \"value\");\n }\n }\n if (capabilities.media.includes(\"netusb\") || capabilities.media.includes(\"cd\")) {\n // ONE \"now playing\" block per zone (v2.0.0): the controller feeds it from\n // whichever source the zone is listening to (netusb or cd) and clears it on a\n // source switch. The source folders below keep only their genuinely own states.\n pushPlayerBlock(objects, \"player\", tName(\"mediaPlayer\"));\n for (const zone of capabilities.zones) {\n if (zone.id !== \"main\") {\n pushPlayerBlock(objects, `${zonePrefix(zone.id)}player`, tName(\"mediaPlayer\"));\n }\n }\n }\n if (capabilities.media.includes(\"netusb\")) {\n objects.push({ id: \"player.netPlayer\", type: \"channel\", common: { name: tName(\"networkPlayer\") } });\n objects.push({\n id: \"player.netPlayer.preset\",\n type: \"state\",\n common: { name: tName(\"recallPreset\"), type: \"number\", role: \"level\", read: true, write: true, min: 1 },\n });\n // The favourites and recently-played lists (names included) plus the recall-by-number\n // for recents \u2014 the musiccast adapter's selection surface, on our tree.\n objects.push({\n id: \"player.netPlayer.presets\",\n type: \"state\",\n common: { name: tName(\"favouritesStoredPresets\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n objects.push({\n id: \"player.netPlayer.recent\",\n type: \"state\",\n common: { name: tName(\"recentlyPlayed\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n objects.push({\n id: \"player.netPlayer.recallRecent\",\n type: \"state\",\n common: {\n name: tName(\"recallRecentlyPlayedNumber\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n },\n });\n // MusicCast playlists and the play queue \u2014 declared in the netusb func_list.\n // Read-only surfaces: no write path for them is documented anywhere, and blind\n // writes are exactly what this adapter no longer does.\n if (capabilities.netusbFuncs?.includes(\"mc_playlist\")) {\n objects.push({\n id: \"player.netPlayer.playlists\",\n type: \"state\",\n common: { name: tName(\"musiccastPlaylists\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n }\n if (capabilities.netusbFuncs?.includes(\"play_queue\")) {\n objects.push({\n id: \"player.netPlayer.queue\",\n type: \"state\",\n common: { name: tName(\"playQueue\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n }\n }\n if (capabilities.media.includes(\"cd\")) {\n // Drive-own states only \u2014 what the disc is PLAYING shows in the flat block above.\n objects.push({ id: \"player.cd\", type: \"channel\", common: { name: tName(\"cd\") } });\n objects.push({\n id: \"player.cd.tray\",\n type: \"state\",\n common: { name: tName(\"toggleTray\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"player.cd.trackNumber\",\n type: \"state\",\n common: { name: tName(\"trackNumber\"), type: \"number\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"player.cd.totalTracks\",\n type: \"state\",\n common: { name: tName(\"totalTracks\"), type: \"number\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"player.cd.discTime\",\n type: \"state\",\n common: { name: tName(\"discTime\"), type: \"number\", unit: \"s\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"player.cd.deviceStatus\",\n type: \"state\",\n common: { name: tName(\"driveStatus\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n }\n if (capabilities.media.includes(\"tuner\")) {\n objects.push({ id: \"tuner\", type: \"channel\", common: { name: tName(\"tuner\") } });\n const bandCommon: ObjectDef[\"common\"] = {\n name: tName(\"band\"),\n type: \"string\",\n role: \"state\",\n read: true,\n write: true,\n };\n const bands = capabilities.tuner?.bands ?? [];\n if (bands.length > 0) {\n bandCommon.states = selfMap(bands);\n }\n objects.push({ id: \"tuner.band\", type: \"state\", common: bandCommon });\n // Frequency in kHz \u2014 FM/AM/DAB all report kHz in getPlayInfo (FM 100900 =\n // 100.9 MHz, AM 1080, DAB 180064), verified against real device captures.\n objects.push({\n id: \"tuner.frequency\",\n type: \"state\",\n common: { name: tName(\"frequency\"), type: \"number\", unit: \"kHz\", role: \"level\", read: true, write: true },\n });\n objects.push({\n id: \"tuner.rdsText\",\n type: \"state\",\n common: { name: tName(\"rdsText\"), type: \"string\", role: \"text\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.rdsTextB\",\n type: \"state\",\n common: { name: tName(\"rdsTextB\"), type: \"string\", role: \"text\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.rdsService\",\n type: \"state\",\n common: { name: tName(\"rdsStation\"), type: \"string\", role: \"text\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.rdsProgramType\",\n type: \"state\",\n common: { name: tName(\"rdsProgrammeType\"), type: \"string\", role: \"text\", read: true, write: false },\n });\n // The stored-station surface: recall by number (writable), the active slot read back\n // from play info, up/down stepping, and the stored lists (with what the device knows\n // about each slot) as JSON \u2014 the selection surface the musiccast adapter offered.\n const presetCommon: ObjectDef[\"common\"] = {\n name: tName(\"presetRecallByNumber\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 0,\n };\n if (capabilities.tuner?.presetNum) {\n presetCommon.max = capabilities.tuner.presetNum;\n }\n objects.push({ id: \"tuner.preset\", type: \"state\", common: presetCommon });\n objects.push({\n id: \"tuner.presetUp\",\n type: \"state\",\n common: { name: tName(\"nextPreset\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"tuner.presetDown\",\n type: \"state\",\n common: { name: tName(\"previousPreset\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"tuner.presets\",\n type: \"state\",\n common: { name: tName(\"storedPresets\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.tuned\",\n type: \"state\",\n common: { name: tName(\"tuned\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.audioMode\",\n type: \"state\",\n common: { name: tName(\"audioMode\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n if (bands.includes(\"dab\")) {\n objects.push({ id: \"tuner.dab\", type: \"channel\", common: { name: tName(\"dab\") } });\n for (const field of DAB_FIELDS) {\n objects.push({\n id: field.id,\n type: \"state\",\n common: {\n name: tName(field.nameKey),\n ...(field.descKey ? { desc: tName(field.descKey) } : {}),\n type: field.type,\n role: field.type === \"boolean\" ? \"indicator\" : field.type === \"number\" ? \"value\" : \"text\",\n read: true,\n write: false,\n },\n });\n }\n }\n }\n if (capabilities.clock) {\n // The clock/alarm block, as the musiccast adapter showed it \u2014 read-only display\n // (the predecessor's clock datapoints had no working write path either); the\n // devices that report it are the desk-audio/clock models.\n objects.push({ id: \"clock\", type: \"channel\", common: { name: tName(\"clockAlarm\") } });\n objects.push({\n id: \"clock.autoSync\",\n type: \"state\",\n common: { name: tName(\"automaticTimeSync\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n objects.push({\n id: \"clock.format\",\n type: \"state\",\n common: { name: tName(\"clockFormat\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n objects.push({ id: \"clock.alarm\", type: \"channel\", common: { name: tName(\"alarm\") } });\n objects.push({\n id: \"clock.alarm.on\",\n type: \"state\",\n common: { name: tName(\"alarmArmed\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n const volumeCommon: ObjectDef[\"common\"] = {\n name: tName(\"alarmVolume\"),\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n };\n if (capabilities.clock.alarmVolumeRange) {\n volumeCommon.min = capabilities.clock.alarmVolumeRange.min;\n volumeCommon.max = capabilities.clock.alarmVolumeRange.max;\n }\n objects.push({ id: \"clock.alarm.volume\", type: \"state\", common: volumeCommon });\n objects.push({\n id: \"clock.alarm.fadeInterval\",\n type: \"state\",\n common: { name: tName(\"fadeInTime\"), type: \"number\", unit: \"s\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"clock.alarm.fadeType\",\n type: \"state\",\n common: { name: tName(\"fadeType\"), type: \"number\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"clock.alarm.mode\",\n type: \"state\",\n common: { name: tName(\"alarmMode\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n objects.push({\n id: \"clock.alarm.repeat\",\n type: \"state\",\n common: { name: tName(\"repeatSnooze\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n const detailChannels = [\"oneday\", ...(capabilities.clock.alarmModes.includes(\"weekly\") ? ALARM_DAYS : [])];\n for (const channel of detailChannels) {\n // The weekday channels are named by the device; only the fixed one-day channel translates.\n const label: ioBroker.StringOrTranslated =\n channel === \"oneday\" ? tName(\"oneDayAlarm\") : channel.charAt(0).toUpperCase() + channel.slice(1);\n objects.push({ id: `clock.alarm.${channel}`, type: \"channel\", common: { name: label } });\n const detail = (\n id: string,\n name: ioBroker.StringOrTranslated,\n type: \"boolean\" | \"number\" | \"string\",\n role: string,\n ): void => {\n objects.push({\n id: `clock.alarm.${channel}.${id}`,\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n });\n };\n detail(\"enable\", tName(\"enabled\"), \"boolean\", \"indicator\");\n detail(\"time\", tName(\"alarmTime\"), \"string\", \"text\");\n detail(\"beep\", tName(\"beep\"), \"boolean\", \"indicator\");\n detail(\"playbackType\", tName(\"playbackType\"), \"string\", \"state\");\n detail(\"resumeInput\", tName(\"resumeInput\"), \"string\", \"state\");\n detail(\"presetType\", tName(\"presetType\"), \"string\", \"state\");\n detail(\"presetNumber\", tName(\"presetNumber\"), \"number\", \"value\");\n detail(\"presetInput\", tName(\"presetSource\"), \"string\", \"state\");\n }\n }\n if (capabilities.hasDistribution) {\n // The MusicCast-Link states live in their own folder so the tree itself tells the\n // scope: directly under multiroom = all zones of this device, group = linked devices.\n // Both channels already exist: the main zone's always-created\n // multiroom.group.streamingEnabled state brought them in through the parent loop.\n const distState = (id: string, name: ioBroker.StringOrTranslated, role: string): void => {\n objects.push({\n id: `multiroom.group.${id}`,\n type: \"state\",\n common: { name, type: \"string\", role, read: true, write: false },\n });\n };\n distState(\"role\", tName(\"roleServerClient\"), \"state\");\n distState(\"id\", tName(\"groupID\"), \"text\");\n distState(\"name\", tName(\"groupName\"), \"text\");\n distState(\"serverZone\", tName(\"serverZoneFeedsTheGroup\"), \"text\");\n distState(\"linkedDevices\", tName(\"linkedDevices\"), \"json\");\n objects.push({\n id: \"multiroom.group.leave\",\n type: \"state\",\n common: { name: tName(\"leaveGroup\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"multiroom.group.linkDevice\",\n type: \"state\",\n common: { name: tName(\"linkADeviceItsIP\"), type: \"string\", role: \"text\", read: false, write: true },\n });\n }\n return objects;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { CHANNEL_NAME_KEYS, type ObjectDef } from \"../catalog/types\";\nimport { tName, type I18nKey } from \"../i18n\";\nimport { YXC_ZONE_IDS, zonePrefix } from \"./zones\";\nimport type { YxcCapabilities } from \"./capability\";\nimport { YXC_AMP_CATALOG } from \"./catalog\";\nimport { ALARM_DAYS, DAB_FIELDS } from \"./command-mapper\";\n\n/**\n * Build a value \u2192 label dropdown map from a device-reported value list.\n *\n * @param values the allowed values\n * @returns the states map\n */\nfunction selfMap(values: readonly string[]): Record<string, string> {\n return Object.fromEntries(values.map(value => [value, value]));\n}\n\n/** The zones the adapter maps: main flat, zone2-4 each under multiroom. */\nconst ZONES: Array<{ id: string; prefix: string }> = YXC_ZONE_IDS.map(id => ({ id, prefix: zonePrefix(id) }));\n\n/** The \"now playing\" block's states (v2.0.0, one per zone): read metadata + transport buttons. */\nconst PLAYER_STATES: Array<{\n state: string;\n common: Omit<ObjectDef[\"common\"], \"name\"> & { nameKey: I18nKey; descKey?: I18nKey };\n}> = [\n {\n // What the zone is playing (the netusb source name, or `cd`) \u2014 read-only display;\n // switching happens over the zone's `input` state.\n state: \"source\",\n common: { nameKey: \"playingSource\", type: \"string\", role: \"text\", read: true, write: false },\n },\n {\n state: \"playback\",\n common: {\n // media.state is a number in the type-detector; the same 0/1/2 coding as the YNCA player.\n nameKey: \"playback\",\n type: \"number\",\n role: \"media.state\",\n read: true,\n write: false,\n states: { 0: \"Play\", 1: \"Stop\", 2: \"Pause\" },\n },\n },\n { state: \"artist\", common: { nameKey: \"artist\", type: \"string\", role: \"media.artist\", read: true, write: false } },\n { state: \"album\", common: { nameKey: \"album\", type: \"string\", role: \"media.album\", read: true, write: false } },\n { state: \"track\", common: { nameKey: \"track\", type: \"string\", role: \"media.title\", read: true, write: false } },\n // Read-only playback metadata, typed exactly like the YNCA sources so both players\n // present the same shape on one device: repeat as the media.mode.repeat number code\n // (wire off/one/all, captures-verified), shuffle as a media.mode.shuffle boolean\n // (wire knows only off/on). Writing stays with the toggle buttons \u2014 YXC has no setter.\n {\n state: \"repeat\",\n common: {\n nameKey: \"repeat\",\n type: \"number\",\n role: \"media.mode.repeat\",\n read: true,\n write: false,\n states: { 0: \"Off\", 1: \"Single\", 2: \"All\" },\n },\n },\n {\n state: \"shuffle\",\n common: { nameKey: \"shuffle\", type: \"boolean\", role: \"media.mode.shuffle\", read: true, write: false },\n },\n // Both forms of each time, from the one value the device reports: the seconds fill the\n // type detector's media-player slot (it takes nothing else), the text is what a\n // visualisation shows. The YNCA side publishes exactly the same pair, converted the other\n // way round \u2014 so the datapoints mean the same thing on every device.\n {\n state: \"elapsedTime\",\n common: {\n nameKey: \"elapsedTime\",\n descKey: \"descElapsedTime\",\n type: \"number\",\n unit: \"s\",\n role: \"media.elapsed\",\n read: true,\n write: false,\n },\n },\n {\n state: \"elapsedTimeText\",\n common: {\n nameKey: \"elapsedTimeReadable\",\n descKey: \"descElapsedTimeReadable\",\n type: \"string\",\n role: \"media.elapsed.text\",\n read: true,\n write: false,\n },\n },\n {\n state: \"totalTime\",\n common: {\n nameKey: \"totalTime\",\n descKey: \"descTotalTime\",\n type: \"number\",\n unit: \"s\",\n role: \"media.duration\",\n read: true,\n write: false,\n },\n },\n {\n state: \"totalTimeText\",\n common: {\n nameKey: \"totalTimeReadable\",\n descKey: \"descTotalTimeReadable\",\n type: \"string\",\n role: \"media.duration.text\",\n read: true,\n write: false,\n },\n },\n {\n state: \"albumArt\",\n common: { nameKey: \"albumArt\", type: \"string\", role: \"media.cover\", read: true, write: false },\n },\n // Transport buttons carry the type-detector media-player roles so a MusicCast player's\n // controls are recognised as play/pause/stop/next/prev, not generic buttons.\n { state: \"play\", common: { nameKey: \"play\", type: \"boolean\", role: \"button.play\", read: false, write: true } },\n { state: \"pause\", common: { nameKey: \"pause\", type: \"boolean\", role: \"button.pause\", read: false, write: true } },\n { state: \"stop\", common: { nameKey: \"stop\", type: \"boolean\", role: \"button.stop\", read: false, write: true } },\n { state: \"next\", common: { nameKey: \"next\", type: \"boolean\", role: \"button.next\", read: false, write: true } },\n { state: \"prev\", common: { nameKey: \"previous\", type: \"boolean\", role: \"button.prev\", read: false, write: true } },\n {\n state: \"repeatToggle\",\n common: { nameKey: \"toggleRepeat\", type: \"boolean\", role: \"button\", read: false, write: true },\n },\n {\n state: \"shuffleToggle\",\n common: { nameKey: \"toggleShuffle\", type: \"boolean\", role: \"button\", read: false, write: true },\n },\n];\n\n/**\n * Append a media-player block (channel + the shared player states) under a\n * dotted prefix. Used for every player source the device reports.\n *\n * @param objects the object list to append to\n * @param prefix the channel/state prefix (e.g. `netPlayer`, `cd`)\n * @param channelName the human-readable channel name\n */\nfunction pushPlayerBlock(objects: ObjectDef[], prefix: string, channelName: ioBroker.StringOrTranslated): void {\n objects.push({ id: prefix, type: \"channel\", common: { name: channelName } });\n for (const player of PLAYER_STATES) {\n const { nameKey: playerNameKey, descKey: playerDescKey, ...playerCommon } = player.common;\n objects.push({\n id: `${prefix}.${player.state}`,\n type: \"state\",\n common: {\n ...playerCommon,\n name: tName(playerNameKey),\n ...(playerDescKey ? { desc: tName(playerDescKey) } : {}),\n },\n });\n }\n}\n\n/**\n * Turn YXC capabilities into the unified object tree: main's functions as\n * top-level states, each additional zone as a channel with its own states. An\n * input state is added when the zone offers inputs. Player sources (netusb, cd)\n * and the tuner get their own channel. Only reported functions are created,\n * parents before children. States and their common come from {@link YXC_AMP_CATALOG}.\n *\n * @param capabilities the parsed YXC capabilities\n * @returns the object definitions to create\n */\nexport function mapYxcToObjects(capabilities: YxcCapabilities): ObjectDef[] {\n const objects: ObjectDef[] = [];\n const channels = new Set<string>();\n for (const zoneDef of ZONES) {\n const zone = capabilities.zones.find(z => z.id === zoneDef.id);\n if (!zone) {\n continue;\n }\n const hasInput = zone.inputs.length > 0;\n const entries = YXC_AMP_CATALOG.filter(entry => {\n // Device-global entries (id under multiroom.) exist once \u2014 never as a per-zone copy.\n if (zoneDef.id !== \"main\" && entry.state.startsWith(\"multiroom.\")) {\n return false;\n }\n if (entry.create.kind === \"always\") {\n return true;\n }\n if (entry.create.kind === \"input\") {\n return hasInput;\n }\n return zone.funcs.includes(entry.create.func);\n });\n // A zone needs an advertised function or an input to exist \u2014 the \"always\" status\n // fields alone (which every entry set contains) do not create a zone.\n if (!entries.some(entry => entry.create.kind !== \"always\")) {\n continue;\n }\n // Every parent \u2014 the zone channel included \u2014 is created by the per-state loop and\n // named from the shared CHANNEL_NAMES table (a zone exists only with an entry).\n for (const entry of entries) {\n const fullId = `${zoneDef.prefix}${entry.state}`;\n const segments = fullId.split(\".\");\n for (let i = 1; i < segments.length; i++) {\n const channelId = segments.slice(0, i).join(\".\");\n if (!channels.has(channelId)) {\n channels.add(channelId);\n const segment = segments[i - 1];\n objects.push({\n id: channelId,\n type: \"channel\",\n common: {\n name: CHANNEL_NAME_KEYS[segment]\n ? tName(CHANNEL_NAME_KEYS[segment])\n : segment.charAt(0).toUpperCase() + segment.slice(1),\n },\n });\n }\n }\n const { nameKey: entryNameKey, descKey: entryDescKey, ...entryRest } = entry.common;\n const common: ObjectDef[\"common\"] = {\n ...entryRest,\n name: tName(entryNameKey),\n ...(entryDescKey ? { desc: tName(entryDescKey) } : {}),\n };\n if (entry.state === \"volume\" && zone.volumeRange) {\n common.min = zone.volumeRange.min;\n common.max = zone.volumeRange.max;\n common.step = zone.volumeRange.step;\n }\n // The device's own allowed-value lists (getFeatures) become dropdowns: the zone's\n // inputs on the input state, sound_program_list & co on their states. On a device\n // that also speaks YNCA the YNCA-owned dropdown wins via the owner policy \u2014 these\n // matter on MusicCast-only devices, where the raw string was the old poverty.\n if (entry.state === \"input\" && zone.inputs.length > 0) {\n common.states = selfMap(zone.inputs);\n }\n const valueList = zone.valueLists?.[entry.state];\n if (valueList) {\n common.states = selfMap(valueList);\n }\n objects.push({ id: fullId, type: \"state\", common });\n }\n const zoneChannelHelper = (id: string, name: ioBroker.StringOrTranslated): void => {\n if (!channels.has(id)) {\n channels.add(id);\n objects.push({ id, type: \"channel\", common: { name } });\n }\n };\n // Scene recall (#615): the zone declares `scene` + scene_num \u2014 per zone, so a\n // Zone-2 scene is first-class (the RX-V6A declares 8 for main AND zone2).\n if (zone.funcs.includes(\"scene\") && zone.sceneNum && zone.sceneNum > 0) {\n zoneChannelHelper(`${zoneDef.prefix}scene`, tName(CHANNEL_NAME_KEYS.scene ?? \"Scenes\"));\n objects.push({\n id: `${zoneDef.prefix}scene.recall`,\n type: \"state\",\n common: {\n name: tName(\"recallScene\"),\n desc: tName(\"descRecallScene\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n max: zone.sceneNum,\n step: 1,\n },\n });\n }\n // The on-screen remote (cursor pad + menu keys) \u2014 declared as zone functions\n // `cursor`/`menu`; the endpoints and their vocabulary are device-verified.\n if (zone.funcs.includes(\"cursor\") || zone.funcs.includes(\"menu\")) {\n zoneChannelHelper(`${zoneDef.prefix}remote`, tName(CHANNEL_NAME_KEYS.remote ?? \"Remote control\"));\n if (zone.funcs.includes(\"cursor\")) {\n objects.push({\n id: `${zoneDef.prefix}remote.cursor`,\n type: \"state\",\n common: {\n name: tName(\"cursorPad\"),\n type: \"string\",\n role: \"state\",\n read: false,\n write: true,\n states: selfMap([\"up\", \"down\", \"left\", \"right\", \"select\", \"return\"]),\n },\n });\n }\n if (zone.funcs.includes(\"menu\")) {\n objects.push({\n id: `${zoneDef.prefix}remote.menu`,\n type: \"state\",\n common: {\n name: tName(\"menuKey\"),\n type: \"string\",\n role: \"state\",\n read: false,\n write: true,\n states: selfMap([\"on_screen\", \"top_menu\", \"menu\", \"option\", \"display\", \"home\"]),\n },\n });\n }\n }\n // The audio-signal info (own endpoint, declared as `signal_info`): what the zone\n // currently decodes \u2014 format, sampling rate, bit depth, bitrate.\n if (zone.funcs.includes(\"signal_info\")) {\n zoneChannelHelper(`${zoneDef.prefix}sound`, tName(CHANNEL_NAME_KEYS.sound ?? \"Sound\"));\n zoneChannelHelper(`${zoneDef.prefix}sound.signal`, tName(CHANNEL_NAME_KEYS.signal ?? \"Audio signal\"));\n const signal = (\n id: string,\n name: ioBroker.StringOrTranslated,\n type: \"string\" | \"number\",\n role: \"text\" | \"value\",\n ): void => {\n objects.push({\n id: `${zoneDef.prefix}sound.signal.${id}`,\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n });\n };\n signal(\"format\", tName(\"audioSignalFormat\"), \"string\", \"text\");\n signal(\"sampling\", tName(\"audioSamplingRate\"), \"string\", \"text\");\n signal(\"bits\", tName(\"audioBitDepth\"), \"string\", \"text\");\n signal(\"bitrate\", tName(\"audioBitrate\"), \"number\", \"value\");\n }\n }\n if (capabilities.media.includes(\"netusb\") || capabilities.media.includes(\"cd\")) {\n // ONE \"now playing\" block per zone (v2.0.0): the controller feeds it from\n // whichever source the zone is listening to (netusb or cd) and clears it on a\n // source switch. The source folders below keep only their genuinely own states.\n pushPlayerBlock(objects, \"player\", tName(\"mediaPlayer\"));\n for (const zone of capabilities.zones) {\n if (zone.id !== \"main\") {\n pushPlayerBlock(objects, `${zonePrefix(zone.id)}player`, tName(\"mediaPlayer\"));\n }\n }\n }\n if (capabilities.media.includes(\"netusb\")) {\n objects.push({ id: \"player.netPlayer\", type: \"channel\", common: { name: tName(\"networkPlayer\") } });\n objects.push({\n id: \"player.netPlayer.preset\",\n type: \"state\",\n common: {\n name: tName(\"recallPreset\"),\n desc: tName(\"descRecallPreset\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n },\n });\n // The favourites and recently-played lists (names included) plus the recall-by-number\n // for recents \u2014 the musiccast adapter's selection surface, on our tree.\n objects.push({\n id: \"player.netPlayer.presets\",\n type: \"state\",\n common: { name: tName(\"favouritesStoredPresets\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n objects.push({\n id: \"player.netPlayer.recent\",\n type: \"state\",\n common: { name: tName(\"recentlyPlayed\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n objects.push({\n id: \"player.netPlayer.recallRecent\",\n type: \"state\",\n common: {\n name: tName(\"recallRecentlyPlayedNumber\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 1,\n },\n });\n // MusicCast playlists and the play queue \u2014 declared in the netusb func_list.\n // Read-only surfaces: no write path for them is documented anywhere, and blind\n // writes are exactly what this adapter no longer does.\n if (capabilities.netusbFuncs?.includes(\"mc_playlist\")) {\n objects.push({\n id: \"player.netPlayer.playlists\",\n type: \"state\",\n common: { name: tName(\"musiccastPlaylists\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n }\n if (capabilities.netusbFuncs?.includes(\"play_queue\")) {\n objects.push({\n id: \"player.netPlayer.queue\",\n type: \"state\",\n common: { name: tName(\"playQueue\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n }\n }\n if (capabilities.media.includes(\"cd\")) {\n // Drive-own states only \u2014 what the disc is PLAYING shows in the flat block above.\n objects.push({ id: \"player.cd\", type: \"channel\", common: { name: tName(\"cd\") } });\n objects.push({\n id: \"player.cd.tray\",\n type: \"state\",\n common: { name: tName(\"toggleTray\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"player.cd.trackNumber\",\n type: \"state\",\n common: { name: tName(\"trackNumber\"), type: \"number\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"player.cd.totalTracks\",\n type: \"state\",\n common: { name: tName(\"totalTracks\"), type: \"number\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"player.cd.discTime\",\n type: \"state\",\n common: { name: tName(\"discTime\"), type: \"number\", unit: \"s\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"player.cd.deviceStatus\",\n type: \"state\",\n common: { name: tName(\"driveStatus\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n }\n if (capabilities.media.includes(\"tuner\")) {\n objects.push({ id: \"tuner\", type: \"channel\", common: { name: tName(\"tuner\") } });\n const bandCommon: ObjectDef[\"common\"] = {\n name: tName(\"band\"),\n type: \"string\",\n role: \"state\",\n read: true,\n write: true,\n };\n const bands = capabilities.tuner?.bands ?? [];\n if (bands.length > 0) {\n bandCommon.states = selfMap(bands);\n }\n objects.push({ id: \"tuner.band\", type: \"state\", common: bandCommon });\n // Frequency in kHz \u2014 FM/AM/DAB all report kHz in getPlayInfo (FM 100900 =\n // 100.9 MHz, AM 1080, DAB 180064), verified against real device captures.\n objects.push({\n id: \"tuner.frequency\",\n type: \"state\",\n common: { name: tName(\"frequency\"), type: \"number\", unit: \"kHz\", role: \"level\", read: true, write: true },\n });\n objects.push({\n id: \"tuner.rdsText\",\n type: \"state\",\n common: {\n name: tName(\"rdsText\"),\n desc: tName(\"descRdsText\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: false,\n },\n });\n objects.push({\n id: \"tuner.rdsTextB\",\n type: \"state\",\n common: {\n name: tName(\"rdsTextB\"),\n desc: tName(\"descRdsTextB\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: false,\n },\n });\n objects.push({\n id: \"tuner.rdsService\",\n type: \"state\",\n common: {\n name: tName(\"rdsStation\"),\n desc: tName(\"descRdsStation\"),\n type: \"string\",\n role: \"text\",\n read: true,\n write: false,\n },\n });\n objects.push({\n id: \"tuner.rdsProgramType\",\n type: \"state\",\n common: { name: tName(\"rdsProgrammeType\"), type: \"string\", role: \"text\", read: true, write: false },\n });\n // The stored-station surface: recall by number (writable), the active slot read back\n // from play info, up/down stepping, and the stored lists (with what the device knows\n // about each slot) as JSON \u2014 the selection surface the musiccast adapter offered.\n const presetCommon: ObjectDef[\"common\"] = {\n name: tName(\"presetRecallByNumber\"),\n desc: tName(\"descPresetRecallByNumber\"),\n type: \"number\",\n role: \"level\",\n read: true,\n write: true,\n min: 0,\n };\n if (capabilities.tuner?.presetNum) {\n presetCommon.max = capabilities.tuner.presetNum;\n }\n objects.push({ id: \"tuner.preset\", type: \"state\", common: presetCommon });\n objects.push({\n id: \"tuner.presetUp\",\n type: \"state\",\n common: { name: tName(\"nextPreset\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"tuner.presetDown\",\n type: \"state\",\n common: { name: tName(\"previousPreset\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"tuner.presets\",\n type: \"state\",\n common: { name: tName(\"storedPresets\"), type: \"string\", role: \"json\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.tuned\",\n type: \"state\",\n common: { name: tName(\"tuned\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n objects.push({\n id: \"tuner.audioMode\",\n type: \"state\",\n common: {\n name: tName(\"audioMode\"),\n desc: tName(\"descAudioMode\"),\n type: \"string\",\n role: \"state\",\n read: true,\n write: false,\n },\n });\n if (bands.includes(\"dab\")) {\n objects.push({ id: \"tuner.dab\", type: \"channel\", common: { name: tName(\"dab\") } });\n for (const field of DAB_FIELDS) {\n objects.push({\n id: field.id,\n type: \"state\",\n common: {\n name: tName(field.nameKey),\n ...(field.descKey ? { desc: tName(field.descKey) } : {}),\n type: field.type,\n role: field.type === \"boolean\" ? \"indicator\" : field.type === \"number\" ? \"value\" : \"text\",\n read: true,\n write: false,\n },\n });\n }\n }\n }\n if (capabilities.clock) {\n // The clock/alarm block, as the musiccast adapter showed it \u2014 read-only display\n // (the predecessor's clock datapoints had no working write path either); the\n // devices that report it are the desk-audio/clock models.\n objects.push({ id: \"clock\", type: \"channel\", common: { name: tName(\"clockAlarm\") } });\n objects.push({\n id: \"clock.autoSync\",\n type: \"state\",\n common: { name: tName(\"automaticTimeSync\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n objects.push({\n id: \"clock.format\",\n type: \"state\",\n common: { name: tName(\"clockFormat\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n objects.push({ id: \"clock.alarm\", type: \"channel\", common: { name: tName(\"alarm\") } });\n objects.push({\n id: \"clock.alarm.on\",\n type: \"state\",\n common: { name: tName(\"alarmArmed\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n const volumeCommon: ObjectDef[\"common\"] = {\n name: tName(\"alarmVolume\"),\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n };\n if (capabilities.clock.alarmVolumeRange) {\n volumeCommon.min = capabilities.clock.alarmVolumeRange.min;\n volumeCommon.max = capabilities.clock.alarmVolumeRange.max;\n }\n objects.push({ id: \"clock.alarm.volume\", type: \"state\", common: volumeCommon });\n objects.push({\n id: \"clock.alarm.fadeInterval\",\n type: \"state\",\n common: { name: tName(\"fadeInTime\"), type: \"number\", unit: \"s\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"clock.alarm.fadeType\",\n type: \"state\",\n common: { name: tName(\"fadeType\"), type: \"number\", role: \"value\", read: true, write: false },\n });\n objects.push({\n id: \"clock.alarm.mode\",\n type: \"state\",\n common: { name: tName(\"alarmMode\"), type: \"string\", role: \"state\", read: true, write: false },\n });\n objects.push({\n id: \"clock.alarm.repeat\",\n type: \"state\",\n common: { name: tName(\"repeatSnooze\"), type: \"boolean\", role: \"indicator\", read: true, write: false },\n });\n const detailChannels = [\"oneday\", ...(capabilities.clock.alarmModes.includes(\"weekly\") ? ALARM_DAYS : [])];\n for (const channel of detailChannels) {\n // The weekday channels are named by the device; only the fixed one-day channel translates.\n const label: ioBroker.StringOrTranslated =\n channel === \"oneday\" ? tName(\"oneDayAlarm\") : channel.charAt(0).toUpperCase() + channel.slice(1);\n objects.push({ id: `clock.alarm.${channel}`, type: \"channel\", common: { name: label } });\n const detail = (\n id: string,\n name: ioBroker.StringOrTranslated,\n type: \"boolean\" | \"number\" | \"string\",\n role: string,\n ): void => {\n objects.push({\n id: `clock.alarm.${channel}.${id}`,\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n });\n };\n detail(\"enable\", tName(\"enabled\"), \"boolean\", \"indicator\");\n detail(\"time\", tName(\"alarmTime\"), \"string\", \"text\");\n detail(\"beep\", tName(\"beep\"), \"boolean\", \"indicator\");\n detail(\"playbackType\", tName(\"playbackType\"), \"string\", \"state\");\n detail(\"resumeInput\", tName(\"resumeInput\"), \"string\", \"state\");\n detail(\"presetType\", tName(\"presetType\"), \"string\", \"state\");\n detail(\"presetNumber\", tName(\"presetNumber\"), \"number\", \"value\");\n detail(\"presetInput\", tName(\"presetSource\"), \"string\", \"state\");\n }\n }\n if (capabilities.hasDistribution) {\n // The MusicCast-Link states live in their own folder so the tree itself tells the\n // scope: directly under multiroom = all zones of this device, group = linked devices.\n // Both channels already exist: the main zone's always-created\n // multiroom.group.streamingEnabled state brought them in through the parent loop.\n const distState = (id: string, name: ioBroker.StringOrTranslated, role: string): void => {\n objects.push({\n id: `multiroom.group.${id}`,\n type: \"state\",\n common: { name, type: \"string\", role, read: true, write: false },\n });\n };\n distState(\"role\", tName(\"roleServerClient\"), \"state\");\n distState(\"id\", tName(\"groupID\"), \"text\");\n distState(\"name\", tName(\"groupName\"), \"text\");\n distState(\"serverZone\", tName(\"serverZoneFeedsTheGroup\"), \"text\");\n distState(\"linkedDevices\", tName(\"linkedDevices\"), \"json\");\n objects.push({\n id: \"multiroom.group.leave\",\n type: \"state\",\n common: { name: tName(\"leaveGroup\"), type: \"boolean\", role: \"button\", read: false, write: true },\n });\n objects.push({\n id: \"multiroom.group.linkDevice\",\n type: \"state\",\n common: { name: tName(\"linkADeviceItsIP\"), type: \"string\", role: \"text\", read: false, write: true },\n });\n }\n return objects;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "yamaha",
|
|
4
|
-
"version": "2.3.
|
|
4
|
+
"version": "2.3.2",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.3.2": {
|
|
7
|
+
"en": "Fixed: eight tuner and scene datapoints still showed no explanation on MusicCast and XML receivers",
|
|
8
|
+
"de": "Behoben: acht Tuner- und Szenen-Datenpunkte zeigten auf MusicCast- und XML-Receivern noch keine Erklärung",
|
|
9
|
+
"ru": "Исправлено: восемь датапоинтов тюнера и сцен не имели пояснения на MusicCast- и XML-ресиверах",
|
|
10
|
+
"pt": "Corrigido: oito pontos de dados de sintonizador e cenas ainda não mostravam explicação em recetores MusicCast e XML",
|
|
11
|
+
"nl": "Opgelost: acht tuner- en scènedatapunten toonden op MusicCast- en XML-receivers nog geen uitleg",
|
|
12
|
+
"fr": "Corrigé : huit points de données tuner et scène n'affichaient encore aucune explication sur les amplis MusicCast et XML",
|
|
13
|
+
"it": "Corretto: otto datapoint di sintonizzatore e scene non mostravano ancora spiegazioni su ricevitori MusicCast e XML",
|
|
14
|
+
"es": "Corregido: ocho puntos de datos de sintonizador y escenas aún no mostraban explicación en receptores MusicCast y XML",
|
|
15
|
+
"pl": "Naprawiono: osiem punktów danych tunera i scen nadal nie miało objaśnienia na amplitunerach MusicCast i XML",
|
|
16
|
+
"uk": "Виправлено: вісім датапоінтів тюнера й сцен ще не мали пояснення на ресиверах MusicCast та XML",
|
|
17
|
+
"zh-cn": "修复:MusicCast 与 XML 功放上仍有八个调谐器与场景数据点没有说明"
|
|
18
|
+
},
|
|
6
19
|
"2.3.1": {
|
|
7
20
|
"en": "Fixed: the new datapoint explanations were missing on MusicCast and older XML devices",
|
|
8
21
|
"de": "Behoben: die neuen Datenpunkt-Erklärungen fehlten auf MusicCast- und älteren XML-Geräten",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Zakłócenia bazy danych nie restartują już adaptera; wskaźniki protokołów zerowane przy starcie/stopie; czyste zatrzymanie podczas wyszukiwania; poprawiono sceny, sondy i equalizer",
|
|
81
94
|
"uk": "Збої бази даних більше не перезапускають адаптер; індикатори протоколів скидаються під час запуску/зупинки; чиста зупинка під час пошуку; виправлено сцени, проби й еквалайзер",
|
|
82
95
|
"zh-cn": "数据库短暂故障不再重启适配器;协议指示在启动和停止时重置;搜索期间可干净停止;修复场景标题、探测重试和均衡器写入"
|
|
83
|
-
},
|
|
84
|
-
"2.0.3": {
|
|
85
|
-
"en": "The update cleanup now removes every never-filled leftover datapoint — a history recording setting no longer shields it, because nothing was ever recorded there and nothing is lost",
|
|
86
|
-
"de": "Das Update-Aufräumen entfernt jetzt jeden nie befüllten Alt-Datenpunkt — eine Verlaufs-Aufzeichnungseinstellung schützt ihn nicht mehr, denn dort wurde nie etwas aufgezeichnet",
|
|
87
|
-
"ru": "Очистка при обновлении теперь удаляет каждую незаполненную устаревшую точку данных — настройка записи истории больше не защищает её, ведь туда никогда ничего не записывалось",
|
|
88
|
-
"pt": "A limpeza de atualização agora remove todos os pontos de dados antigos nunca preenchidos — uma configuração de gravação de histórico não os protege mais, pois nada foi gravado lá",
|
|
89
|
-
"nl": "De update-opschoning verwijdert nu elk nooit gevuld achtergebleven datapunt — een instelling voor historie-opname beschermt het niet meer, want er is daar nooit iets opgenomen",
|
|
90
|
-
"fr": "Le nettoyage de mise à jour supprime désormais chaque point de données jamais rempli — un réglage d'enregistrement d'historique ne le protège plus, car rien n'y a jamais été enregistré",
|
|
91
|
-
"it": "La pulizia dell'aggiornamento ora rimuove ogni datapoint residuo mai riempito — un'impostazione di registrazione della cronologia non lo protegge più, lì non è mai stato registrato nulla",
|
|
92
|
-
"es": "La limpieza de actualización ahora elimina cada punto de datos antiguo nunca llenado — un ajuste de grabación de historial ya no lo protege, pues allí nunca se grabó nada",
|
|
93
|
-
"pl": "Czyszczenie przy aktualizacji usuwa teraz każdy nigdy niewypełniony stary punkt danych — ustawienie zapisu historii już go nie chroni, bo nic tam nigdy nie zapisano",
|
|
94
|
-
"uk": "Очищення під час оновлення тепер видаляє кожну ніколи не заповнену застарілу точку даних — налаштування запису історії більше не захищає її, адже туди ніколи нічого не записувалось",
|
|
95
|
-
"zh-cn": "更新清理现在会删除每个从未填充过的遗留数据点——历史记录设置不再保护它,因为那里从未记录过任何数据,不会有任何损失"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|