denonavr 1.1.2__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
denonavr/foundation.py CHANGED
@@ -22,12 +22,13 @@ from .const import (
22
22
  APPCOMMAND_CMD_TEXT,
23
23
  APPCOMMAND_NAME,
24
24
  AUDIO_RESTORER_MAP,
25
- AUDIO_RESTORER_MAP_LABELS,
25
+ AUDIO_RESTORER_MAP_REVERSE,
26
+ AUTO_STANDBY_MAP,
26
27
  AVR,
27
28
  AVR_X,
28
29
  AVR_X_2016,
29
- BLUETOOTH_OUTPUT_MAP_LABELS,
30
30
  BLUETOOTH_OUTPUT_MODES_MAP,
31
+ BLUETOOTH_OUTPUT_MODES_MAP_REVERSE,
31
32
  CHANNEL_VOLUME_MAP,
32
33
  DENON_ATTR_SETATTR,
33
34
  DENONAVR_TELNET_COMMANDS,
@@ -36,18 +37,22 @@ from .const import (
36
37
  DEVICEINFO_AVR_X_PATTERN,
37
38
  DEVICEINFO_COMMAPI_PATTERN,
38
39
  DIMMER_MODE_MAP,
39
- DIMMER_MODE_MAP_LABELS,
40
+ DIMMER_MODE_MAP_REVERSE,
41
+ DIMMER_MODE_MAP_TELNET,
40
42
  ECO_MODE_MAP,
41
- ECO_MODE_MAP_LABELS,
43
+ ECO_MODE_MAP_REVERSE,
44
+ ECO_MODE_MAP_TELNET,
42
45
  HDMI_OUTPUT_MAP,
43
- HDMI_OUTPUT_MAP_LABELS,
46
+ HDMI_OUTPUT_MAP_REVERSE,
47
+ ILLUMINATION_MAP,
48
+ ILLUMINATION_MAP_REVERSE,
44
49
  MAIN_ZONE,
45
50
  POWER_STATES,
46
51
  SETTINGS_MENU_STATES,
47
52
  VALID_RECEIVER_TYPES,
48
53
  VALID_ZONES,
49
54
  VIDEO_PROCESSING_MODES_MAP,
50
- VIDEO_PROCESSING_MODES_MAP_LABELS,
55
+ VIDEO_PROCESSING_MODES_MAP_REVERSE,
51
56
  ZONE2,
52
57
  ZONE2_TELNET_COMMANDS,
53
58
  ZONE2_URLS,
@@ -61,6 +66,8 @@ from .const import (
61
66
  EcoModes,
62
67
  HDMIAudioDecodes,
63
68
  HDMIOutputs,
69
+ Illuminations,
70
+ InputModes,
64
71
  PanelLocks,
65
72
  ReceiverType,
66
73
  ReceiverURLs,
@@ -71,10 +78,7 @@ from .const import (
71
78
  )
72
79
  from .exceptions import (
73
80
  AvrCommandError,
74
- AvrForbiddenError,
75
- AvrIncompleteResponseError,
76
81
  AvrNetworkError,
77
- AvrProcessingError,
78
82
  AvrRequestError,
79
83
  AvrTimoutError,
80
84
  )
@@ -130,6 +134,7 @@ class DenonAVRDeviceInfo:
130
134
  zone: str = attr.ib(
131
135
  validator=attr.validators.in_(VALID_ZONES), default=MAIN_ZONE, kw_only=True
132
136
  )
137
+ zones: int = attr.ib(converter=attr.converters.optional(int), default=0)
133
138
  friendly_name: Optional[str] = attr.ib(
134
139
  converter=attr.converters.optional(str), default=None
135
140
  )
@@ -152,11 +157,11 @@ class DenonAVRDeviceInfo:
152
157
  converter=attr.converters.optional(convert_on_off_bool), default=None
153
158
  )
154
159
  _dimmer: Optional[str] = attr.ib(
155
- converter=attr.converters.optional(str), default=None
160
+ converter=attr.converters.optional(DIMMER_MODE_MAP.get), default=None
156
161
  )
157
162
  _dimmer_modes = get_args(DimmerModes)
158
163
  _auto_standby: Optional[str] = attr.ib(
159
- converter=attr.converters.optional(str), default=None
164
+ converter=attr.converters.optional(AUTO_STANDBY_MAP.get), default=None
160
165
  )
161
166
  _auto_standbys = get_args(AutoStandbys)
162
167
  _sleep: Optional[Union[str, int]] = attr.ib(
@@ -166,11 +171,11 @@ class DenonAVRDeviceInfo:
166
171
  converter=attr.converters.optional(int), default=None
167
172
  )
168
173
  _eco_mode: Optional[str] = attr.ib(
169
- converter=attr.converters.optional(str), default=None
174
+ converter=attr.converters.optional(ECO_MODE_MAP.get), default=None
170
175
  )
171
176
  _eco_modes = get_args(EcoModes)
172
177
  _hdmi_output: Optional[str] = attr.ib(
173
- converter=attr.converters.optional(str), default=None
178
+ converter=attr.converters.optional(HDMI_OUTPUT_MAP.get), default=None
174
179
  )
175
180
  _hdmi_outputs = get_args(HDMIOutputs)
176
181
  _hdmi_audio_decode: Optional[str] = attr.ib(
@@ -178,7 +183,7 @@ class DenonAVRDeviceInfo:
178
183
  )
179
184
  _hdmi_audio_decodes = get_args(HDMIAudioDecodes)
180
185
  _video_processing_mode: Optional[str] = attr.ib(
181
- converter=attr.converters.optional(str), default=None
186
+ converter=attr.converters.optional(VIDEO_PROCESSING_MODES_MAP.get), default=None
182
187
  )
183
188
  _video_processing_modes = get_args(VideoProcessingModes)
184
189
  _tactile_transducer: Optional[str] = attr.ib(
@@ -197,20 +202,20 @@ class DenonAVRDeviceInfo:
197
202
  _room_sizes = get_args(RoomSizes)
198
203
  _triggers: Optional[Dict[int, str]] = attr.ib(default=None)
199
204
  _speaker_preset: Optional[int] = attr.ib(
200
- converter=attr.converters.optional(str), default=None
205
+ converter=attr.converters.optional(int), default=None
201
206
  )
202
207
  _bt_transmitter: Optional[bool] = attr.ib(
203
208
  converter=attr.converters.optional(convert_on_off_bool), default=None
204
209
  )
205
210
  _bt_output_mode: Optional[str] = attr.ib(
206
- converter=attr.converters.optional(str), default=None
211
+ converter=attr.converters.optional(BLUETOOTH_OUTPUT_MODES_MAP.get), default=None
207
212
  )
208
213
  _bt_output_modes = get_args(BluetoothOutputModes)
209
214
  _delay_time: Optional[int] = attr.ib(
210
215
  converter=attr.converters.optional(int), default=None
211
216
  )
212
217
  _audio_restorer: Optional[str] = attr.ib(
213
- converter=attr.converters.optional(str), default=None
218
+ converter=attr.converters.optional(AUDIO_RESTORER_MAP.get), default=None
214
219
  )
215
220
  _audio_restorers = get_args(AudioRestorers)
216
221
  _panel_locks = get_args(PanelLocks)
@@ -220,8 +225,15 @@ class DenonAVRDeviceInfo:
220
225
  _headphone_eq: Optional[bool] = attr.ib(
221
226
  converter=attr.converters.optional(convert_on_off_bool), default=None
222
227
  )
228
+ _illumination: Optional[str] = attr.ib(
229
+ converter=attr.converters.optional(ILLUMINATION_MAP.get), default=None
230
+ )
231
+ _illuminations = get_args(Illuminations)
232
+ _auto_lip_sync: Optional[bool] = attr.ib(
233
+ converter=attr.converters.optional(convert_on_off_bool), default=None
234
+ )
235
+ _input_modes = get_args(InputModes)
223
236
  _is_setup: bool = attr.ib(converter=bool, default=False, init=False)
224
- _allow_recovery: bool = attr.ib(converter=bool, default=True, init=True)
225
237
  _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock))
226
238
 
227
239
  def __attrs_post_init__(self) -> None:
@@ -239,66 +251,38 @@ class DenonAVRDeviceInfo:
239
251
  else:
240
252
  raise ValueError(f"Invalid zone {self.zone}")
241
253
 
242
- async def _async_power_callback(
243
- self, zone: str, event: str, parameter: str
244
- ) -> None:
254
+ def _power_callback(self, zone: str, event: str, parameter: str) -> None:
245
255
  """Handle a power change event."""
246
256
  if self.zone == zone and parameter in POWER_STATES:
247
257
  self._power = parameter
248
258
 
249
- async def _async_settings_menu_callback(
250
- self, zone: str, event: str, parameter: str
251
- ) -> None:
259
+ def _settings_menu_callback(self, zone: str, event: str, parameter: str) -> None:
252
260
  """Handle a settings menu event."""
253
- if (
254
- event == "MN"
255
- and parameter[0:3] == "MEN"
256
- and parameter[4:] in SETTINGS_MENU_STATES
257
- ):
261
+ if parameter[0:3] == "MEN" and parameter[4:] in SETTINGS_MENU_STATES:
258
262
  self._settings_menu = parameter[4:]
259
263
 
260
- async def _async_dimmer_callback(
261
- self, zone: str, event: str, parameter: str
262
- ) -> None:
264
+ def _dimmer_callback(self, zone: str, event: str, parameter: str) -> None:
263
265
  """Handle a dimmer change event."""
264
- if event == "DIM" and parameter[1:] in DIMMER_MODE_MAP_LABELS:
265
- self._dimmer = DIMMER_MODE_MAP_LABELS[parameter[1:]]
266
+ if parameter[1:] in DIMMER_MODE_MAP_TELNET:
267
+ self._dimmer = parameter[1:]
266
268
 
267
- async def _async_auto_standby_callback(
268
- self, zone: str, event: str, parameter: str
269
- ) -> None:
269
+ def _auto_standby_callback(self, zone: str, event: str, parameter: str) -> None:
270
270
  """Handle a auto standby change event."""
271
- if zone == "Main" and event == "STBY":
271
+ if zone == self.zone:
272
272
  self._auto_standby = parameter
273
273
 
274
- async def _async_auto_sleep_callback(
275
- self, zone: str, event: str, parameter: str
276
- ) -> None:
274
+ def _auto_sleep_callback(self, zone: str, event: str, parameter: str) -> None:
277
275
  """Handle a sleep change event."""
278
- if event != "SLP":
279
- return
280
-
281
- if parameter == "OFF":
276
+ if zone == self.zone:
282
277
  self._sleep = parameter
283
- else:
284
- self._sleep = int(parameter)
285
278
 
286
- async def _async_room_size_callback(
287
- self, zone: str, event: str, parameter: str
288
- ) -> None:
279
+ def _room_size_callback(self, zone: str, event: str, parameter: str) -> None:
289
280
  """Handle a room size change event."""
290
- if parameter[:3] != "RSZ":
291
- return
292
-
293
- self._room_size = parameter[4:]
281
+ if zone == self.zone and parameter[:3] == "RSZ":
282
+ self._room_size = parameter[4:]
294
283
 
295
- async def _async_trigger_callback(
296
- self, zone: str, event: str, parameter: str
297
- ) -> None:
284
+ def _trigger_callback(self, zone: str, event: str, parameter: str) -> None:
298
285
  """Handle a trigger change event."""
299
- if event != "TR":
300
- return
301
-
302
286
  values = parameter.split()
303
287
  if len(values) != 2:
304
288
  return
@@ -308,44 +292,36 @@ class DenonAVRDeviceInfo:
308
292
 
309
293
  self._triggers[int(values[0])] = values[1]
310
294
 
311
- async def _async_delay_callback(
312
- self, zone: str, event: str, parameter: str
313
- ) -> None:
295
+ def _delay_callback(self, zone: str, event: str, parameter: str) -> None:
314
296
  """Handle a delay change event."""
315
- if event == "PS" and parameter[0:5] == "DELAY":
316
- self._delay = int(parameter[6:])
297
+ if zone == self.zone and parameter[0:5] == "DELAY":
298
+ self._delay = parameter[6:]
317
299
 
318
- async def _async_eco_mode_callback(
319
- self, zone: str, event: str, parameter: str
320
- ) -> None:
300
+ def _eco_mode_callback(self, zone: str, event: str, parameter: str) -> None:
321
301
  """Handle an Eco-mode change event."""
322
- if event == "ECO" and parameter in ECO_MODE_MAP_LABELS:
323
- self._eco_mode = ECO_MODE_MAP_LABELS[parameter]
302
+ if zone == self.zone and parameter in ECO_MODE_MAP_TELNET:
303
+ self._eco_mode = parameter
324
304
 
325
- async def _async_hdmi_output_callback(
326
- self, zone: str, event: str, parameter: str
327
- ) -> None:
305
+ def _hdmi_output_callback(self, zone: str, event: str, parameter: str) -> None:
328
306
  """Handle a HDMI output change event."""
329
- if event == "VS" and parameter[0:4] == "MONI":
330
- self._hdmi_output = HDMI_OUTPUT_MAP_LABELS[parameter]
307
+ if zone == self.zone and parameter[0:4] == "MONI":
308
+ self._hdmi_output = parameter
331
309
 
332
- async def _async_hdmi_audio_decode_callback(
310
+ def _hdmi_audio_decode_callback(
333
311
  self, zone: str, event: str, parameter: str
334
312
  ) -> None:
335
313
  """Handle a HDMI Audio Decode mode change event."""
336
- if event == "VS" and parameter[0:5] == "AUDIO":
314
+ if zone == self.zone and parameter[0:5] == "AUDIO":
337
315
  self._hdmi_audio_decode = parameter[6:]
338
316
 
339
- async def _async_video_processing_mode_callback(
317
+ def _video_processing_mode_callback(
340
318
  self, zone: str, event: str, parameter: str
341
319
  ) -> None:
342
320
  """Handle a Video Processing Mode change event."""
343
- if event == "VS" and parameter[0:3] == "VPM":
344
- self._video_processing_mode = VIDEO_PROCESSING_MODES_MAP_LABELS[
345
- parameter[3:]
346
- ]
321
+ if zone == self.zone and parameter[0:3] == "VPM":
322
+ self._video_processing_mode = parameter[3:]
347
323
 
348
- async def _async_tactile_transducer_callback(
324
+ def _tactile_transducer_callback(
349
325
  self, zone: str, event: str, parameter: str
350
326
  ) -> None:
351
327
  """Handle a tactile transducer change event."""
@@ -363,75 +339,70 @@ class DenonAVRDeviceInfo:
363
339
  elif key == "TTRLEV":
364
340
  self._tactile_transducer_level = CHANNEL_VOLUME_MAP[value]
365
341
  elif key == "TTRLPF":
366
- self._tactile_transducer_lpf = f"{int(value)} Hz"
342
+ self._tactile_transducer_lpf = f"{value} Hz"
367
343
 
368
- async def _async_speaker_preset_callback(
369
- self, zone: str, event: str, parameter: str
370
- ) -> None:
344
+ def _speaker_preset_callback(self, zone: str, event: str, parameter: str) -> None:
371
345
  """Handle a speaker preset change event."""
372
- if event != "SP":
373
- return
374
-
375
346
  if parameter[0:2] == "PR":
376
- self._speaker_preset = int(parameter[3:])
347
+ self._speaker_preset = parameter[3:]
377
348
 
378
- async def _async_bt_callback(self, zone: str, event: str, parameter: str) -> None:
349
+ def _bt_callback(self, zone: str, event: str, parameter: str) -> None:
379
350
  """Handle a Bluetooth change event."""
380
- if event != "BT" or parameter[0:2] != "TX":
351
+ if parameter[0:2] != "TX":
381
352
  return
382
353
 
383
354
  if parameter[3:] in ("ON", "OFF"):
384
355
  self._bt_transmitter = parameter[3:]
385
356
  else:
386
- self._bt_output_mode = BLUETOOTH_OUTPUT_MAP_LABELS[parameter[3:]]
357
+ self._bt_output_mode = parameter[3:]
387
358
 
388
- async def _async_delay_time_callback(
389
- self, zone: str, event: str, parameter: str
390
- ) -> None:
359
+ def _delay_time_callback(self, zone: str, event: str, parameter: str) -> None:
391
360
  """Handle a delay time change event."""
392
361
  # do not match "DELAY" as it's another event
393
- if event != "PS" or parameter[0:3] != "DEL" or parameter[0:5] == "DELAY":
362
+ if parameter[0:3] != "DEL" or parameter[0:5] == "DELAY":
394
363
  return
395
364
 
396
365
  self._delay_time = int(parameter[4:])
397
366
 
398
- async def _async_audio_restorer_callback(
399
- self, zone: str, event: str, parameter: str
400
- ) -> None:
367
+ def _audio_restorer_callback(self, zone: str, event: str, parameter: str) -> None:
401
368
  """Handle an audio restorer change event."""
402
- if event != "PS" or parameter[0:4] != "RSTR":
369
+ if parameter[0:4] != "RSTR":
403
370
  return
404
371
 
405
- self._audio_restorer = AUDIO_RESTORER_MAP_LABELS[parameter[5:]]
372
+ self._audio_restorer = parameter[5:]
406
373
 
407
- async def _async_graphic_eq_callback(
408
- self, zone: str, event: str, parameter: str
409
- ) -> None:
374
+ def _graphic_eq_callback(self, zone: str, event: str, parameter: str) -> None:
410
375
  """Handle a Graphic EQ change event."""
411
376
  if parameter[0:3] != "GEQ":
412
377
  return
413
378
 
414
379
  self._graphic_eq = parameter[4:]
415
380
 
416
- async def _async_headphone_eq_callback(
417
- self, zone: str, event: str, parameter: str
418
- ) -> None:
381
+ def _headphone_eq_callback(self, zone: str, event: str, parameter: str) -> None:
419
382
  """Handle a Headphone EQ change event."""
420
383
  if parameter[0:3] != "HEQ":
421
384
  return
422
385
 
423
386
  self._headphone_eq = parameter[4:]
424
387
 
425
- def get_own_zone(self) -> str:
426
- """
427
- Get zone from actual instance.
388
+ def _illumination_callback(self, zone: str, event: str, parameter: str) -> None:
389
+ """Handle an illumination change event."""
390
+ if parameter[0:3] == "ILL":
391
+ self._illumination = parameter[4:]
428
392
 
429
- These zone information are used to evaluate responses of HTTP POST
430
- commands.
431
- """
432
- if self.zone == MAIN_ZONE:
433
- return "zone1"
434
- return self.zone.lower()
393
+ def _auto_lip_sync_callback(self, zone: str, event: str, parameter: str) -> None:
394
+ """Handle a auto lip sync change event."""
395
+ if parameter[0:3] != "HOS":
396
+ return
397
+
398
+ if parameter[6:] == "HOSALS":
399
+ auto_lip_sync = parameter[5:]
400
+ elif parameter[3:] == "HOS":
401
+ auto_lip_sync = parameter[4:]
402
+ else:
403
+ return
404
+
405
+ self._auto_lip_sync = auto_lip_sync
435
406
 
436
407
  async def async_setup(self) -> None:
437
408
  """Ensure that configuration is loaded from receiver asynchronously."""
@@ -453,38 +424,48 @@ class DenonAVRDeviceInfo:
453
424
 
454
425
  # Add tags for a potential AppCommand.xml update
455
426
  self.api.add_appcommand_update_tag(AppCommands.GetAllZonePowerStatus)
427
+ self.api.add_appcommand_update_tag(AppCommands.GetAutoStandby)
428
+ self.api.add_appcommand_update_tag(AppCommands.GetDimmer)
429
+ self.api.add_appcommand_update_tag(AppCommands.GetECO)
456
430
 
457
431
  power_event = "ZM"
458
432
  if self.zone == ZONE2:
459
433
  power_event = "Z2"
460
434
  elif self.zone == ZONE3:
461
435
  power_event = "Z3"
462
- self.telnet_api.register_callback(power_event, self._async_power_callback)
463
-
464
- self.telnet_api.register_callback("MN", self._async_settings_menu_callback)
465
- self.telnet_api.register_callback("DIM", self._async_dimmer_callback)
466
- self.telnet_api.register_callback("PS", self._async_delay_callback)
467
- self.telnet_api.register_callback("ECO", self._async_eco_mode_callback)
468
- self.telnet_api.register_callback("VS", self._async_hdmi_output_callback)
469
- self.telnet_api.register_callback(
470
- "VS", self._async_hdmi_audio_decode_callback
471
- )
472
- self.telnet_api.register_callback(
473
- "VS", self._async_video_processing_mode_callback
474
- )
436
+ elif self.zones == 1:
437
+ # ZM events do not always work when the receiver has only one zone
438
+ # In this case it is safe to turn the entire device on and off
439
+ power_event = "PW"
440
+ self.telnet_commands = self.telnet_commands._replace(
441
+ command_power_on="PWON", command_power_standby="PWSTANDBY"
442
+ )
443
+ self.telnet_api.register_callback(power_event, self._power_callback)
444
+
445
+ self.telnet_api.register_callback("MN", self._settings_menu_callback)
446
+ self.telnet_api.register_callback("DIM", self._dimmer_callback)
447
+ self.telnet_api.register_callback("PS", self._delay_callback)
448
+ self.telnet_api.register_callback("ECO", self._eco_mode_callback)
449
+ self.telnet_api.register_callback("VS", self._hdmi_output_callback)
450
+ self.telnet_api.register_callback("VS", self._hdmi_audio_decode_callback)
475
451
  self.telnet_api.register_callback(
476
- "SS", self._async_tactile_transducer_callback
477
- )
478
- self.telnet_api.register_callback("STBY", self._async_auto_standby_callback)
479
- self.telnet_api.register_callback("SLP", self._async_auto_sleep_callback)
480
- self.telnet_api.register_callback("PS", self._async_room_size_callback)
481
- self.telnet_api.register_callback("TR", self._async_trigger_callback)
482
- self.telnet_api.register_callback("SP", self._async_speaker_preset_callback)
483
- self.telnet_api.register_callback("BT", self._async_bt_callback)
484
- self.telnet_api.register_callback("PS", self._async_delay_time_callback)
485
- self.telnet_api.register_callback("PS", self._async_audio_restorer_callback)
486
- self.telnet_api.register_callback("PS", self._async_graphic_eq_callback)
487
- self.telnet_api.register_callback("PS", self._async_headphone_eq_callback)
452
+ "VS", self._video_processing_mode_callback
453
+ )
454
+ self.telnet_api.register_callback("SS", self._tactile_transducer_callback)
455
+ self.telnet_api.register_callback("STBY", self._auto_standby_callback)
456
+ self.telnet_api.register_callback("SLP", self._auto_sleep_callback)
457
+ self.telnet_api.register_callback("PS", self._room_size_callback)
458
+ self.telnet_api.register_callback("TR", self._trigger_callback)
459
+ self.telnet_api.register_callback("SP", self._speaker_preset_callback)
460
+ self.telnet_api.register_callback("BT", self._bt_callback)
461
+ self.telnet_api.register_callback("PS", self._delay_time_callback)
462
+ self.telnet_api.register_callback("PS", self._audio_restorer_callback)
463
+ self.telnet_api.register_callback("PS", self._graphic_eq_callback)
464
+ self.telnet_api.register_callback("PS", self._headphone_eq_callback)
465
+
466
+ if not self.is_denon:
467
+ self.telnet_api.register_callback("ILB", self._illumination_callback)
468
+ self.telnet_api.register_callback("SS", self._auto_lip_sync_callback)
488
469
 
489
470
  self._is_setup = True
490
471
  _LOGGER.debug("Finished device setup")
@@ -498,8 +479,13 @@ class DenonAVRDeviceInfo:
498
479
  if not self._is_setup:
499
480
  await self.async_setup()
500
481
 
501
- # Update power status
502
- await self.async_update_power(global_update=global_update, cache_id=cache_id)
482
+ if self.use_avr_2016_update:
483
+ await self.async_update_appcommand(
484
+ global_update=global_update, cache_id=cache_id
485
+ )
486
+ else:
487
+ await self.async_update_status_xml(cache_id=cache_id)
488
+
503
489
  _LOGGER.debug("Finished device update")
504
490
 
505
491
  async def async_identify_receiver(self) -> None:
@@ -541,6 +527,10 @@ class DenonAVRDeviceInfo:
541
527
  err,
542
528
  )
543
529
  else:
530
+ device_zones = xml.find("./DeviceZones")
531
+ if device_zones is not None:
532
+ self.zones = device_zones.text
533
+
544
534
  is_avr_x = self._is_avr_x(xml)
545
535
  if is_avr_x:
546
536
  self.receiver = r_type
@@ -567,7 +557,7 @@ class DenonAVRDeviceInfo:
567
557
  # First test by CommApiVers
568
558
  try:
569
559
  if bool(
570
- DEVICEINFO_COMMAPI_PATTERN.search(deviceinfo.find("CommApiVers").text)
560
+ DEVICEINFO_COMMAPI_PATTERN.search(deviceinfo.find("./CommApiVers").text)
571
561
  is not None
572
562
  ):
573
563
  # receiver found , return True
@@ -580,7 +570,7 @@ class DenonAVRDeviceInfo:
580
570
  # if first test did not find AVR-X device, check by model name
581
571
  try:
582
572
  if bool(
583
- DEVICEINFO_AVR_X_PATTERN.search(deviceinfo.find("ModelName").text)
573
+ DEVICEINFO_AVR_X_PATTERN.search(deviceinfo.find("./ModelName").text)
584
574
  is not None
585
575
  ):
586
576
  # receiver found , return True
@@ -640,49 +630,6 @@ class DenonAVRDeviceInfo:
640
630
  else:
641
631
  self._set_friendly_name(xml)
642
632
 
643
- async def async_verify_avr_2016_update_method(
644
- self, *, cache_id: Hashable = None
645
- ) -> None:
646
- """Verify if avr 2016 update method is working."""
647
- # Nothing to do if Appcommand.xml interface is not supported
648
- if self._is_setup and not self.use_avr_2016_update:
649
- return
650
-
651
- try:
652
- # Result is cached that it can be reused during update
653
- await self.api.async_get_global_appcommand(cache_id=cache_id)
654
- except (AvrTimoutError, AvrNetworkError) as err:
655
- _LOGGER.debug("Connection error when verifying update method: %s", err)
656
- raise
657
- except AvrForbiddenError:
658
- # Recovery in case receiver changes port from 80 to 8080 which
659
- # might happen at Denon AVR-X 2016 receivers
660
- if self._allow_recovery:
661
- self._allow_recovery = False
662
- _LOGGER.warning(
663
- "AppCommand.xml returns HTTP status 403. Running setup"
664
- " again once to check if receiver interface switched "
665
- "ports"
666
- )
667
- self._is_setup = False
668
- await self.async_setup()
669
- await self.async_verify_avr_2016_update_method(cache_id=cache_id)
670
- else:
671
- raise
672
- except AvrIncompleteResponseError as err:
673
- _LOGGER.debug("Request error when verifying update method: %s", err)
674
- # Only AVR_X devices support both interfaces
675
- if self.receiver == AVR_X:
676
- _LOGGER.warning(
677
- "Error verifying Appcommand.xml update method, it returns "
678
- "an incomplete result set. Deactivating the interface"
679
- )
680
- self.use_avr_2016_update = False
681
- else:
682
- if not self._allow_recovery:
683
- _LOGGER.info("AppCommand.xml recovered from HTTP status 403 error")
684
- self._allow_recovery = True
685
-
686
633
  def _set_friendly_name(self, xml: ET.Element) -> None:
687
634
  """Set FriendlyName from result xml."""
688
635
  # friendlyname tag of AppCommand.xml, FriendlyName tag main zone xml
@@ -729,6 +676,7 @@ class DenonAVRDeviceInfo:
729
676
 
730
677
  if device_info is None:
731
678
  self.manufacturer = "Denon"
679
+ self.telnet_api.is_denon = self.is_denon
732
680
  self.model_name = "Unknown"
733
681
  self.serial_number = None
734
682
  _LOGGER.warning(
@@ -744,89 +692,101 @@ class DenonAVRDeviceInfo:
744
692
  if self.friendly_name is None and "friendlyName" in device_info:
745
693
  self.friendly_name = device_info["friendlyName"]
746
694
  self.manufacturer = device_info["manufacturer"]
695
+ self.telnet_api.is_denon = self.is_denon
747
696
  self.model_name = device_info["modelName"]
748
697
  self.serial_number = device_info["serialNumber"]
749
698
 
750
- async def async_update_power(
751
- self, global_update: bool = False, cache_id: Optional[Hashable] = None
752
- ) -> None:
753
- """Update power status of device."""
754
- if self.use_avr_2016_update is None:
755
- raise AvrProcessingError(
756
- "Device is not setup correctly, update method not set"
757
- )
758
-
759
- if self.use_avr_2016_update:
760
- await self.async_update_power_appcommand(
761
- global_update=global_update, cache_id=cache_id
762
- )
763
- else:
764
- await self.async_update_power_status_xml(cache_id=cache_id)
765
-
766
- async def async_update_power_appcommand(
699
+ async def async_update_appcommand(
767
700
  self, global_update: bool = False, cache_id: Optional[Hashable] = None
768
701
  ) -> None:
769
- """Update power status from AppCommand.xml."""
702
+ """Update status from AppCommand.xml."""
770
703
  power_appcommand = AppCommands.GetAllZonePowerStatus
704
+ dimmer_appcommand = AppCommands.GetDimmer
705
+ autostandby_appcommand = AppCommands.GetAutoStandby
706
+ eco_appcommand = AppCommands.GetECO
707
+ appcommands = (
708
+ power_appcommand,
709
+ autostandby_appcommand,
710
+ dimmer_appcommand,
711
+ eco_appcommand,
712
+ )
713
+
771
714
  try:
772
715
  if global_update:
773
716
  xml = await self.api.async_get_global_appcommand(cache_id=cache_id)
774
717
  else:
775
718
  xml = await self.api.async_post_appcommand(
776
- self.urls.appcommand, tuple(power_appcommand), cache_id=cache_id
719
+ self.urls.appcommand, appcommands, cache_id=cache_id
777
720
  )
778
721
  except AvrRequestError as err:
779
- _LOGGER.debug("Error when getting power status: %s", err)
722
+ _LOGGER.debug("Error when getting device status: %s", err)
780
723
  raise
781
724
 
782
725
  # Extract relevant information
783
- zone = self.get_own_zone()
784
-
785
- # Search for power tag
786
- power_tag = xml.find(
787
- f"./cmd[@{APPCOMMAND_CMD_TEXT}='{power_appcommand.cmd_text}']/{zone}"
788
- )
726
+ for appcommand in appcommands:
727
+ for i, item in enumerate(
728
+ create_appcommand_search_strings(appcommand, self.zone)
729
+ ):
730
+ tag = xml.find(item)
789
731
 
790
- if power_tag is None:
791
- raise AvrProcessingError(
792
- f"Power attribute of zone {self.zone} not found on update"
793
- )
732
+ if tag is None:
733
+ _LOGGER.debug(
734
+ "%s attribute of zone %s not found on update",
735
+ appcommand.response_pattern[0].update_attribute,
736
+ self.zone,
737
+ )
738
+ continue
794
739
 
795
- self._power = power_tag.text
740
+ setattr(self, appcommand.response_pattern[i].update_attribute, tag.text)
796
741
 
797
- async def async_update_power_status_xml(
742
+ async def async_update_status_xml(
798
743
  self, cache_id: Optional[Hashable] = None
799
744
  ) -> None:
800
- """Update power status from status xml."""
745
+ """Update status from status xml."""
801
746
  # URLs to be scanned
802
747
  urls = [self.urls.status]
803
748
  if self.zone == MAIN_ZONE:
804
749
  urls.append(self.urls.mainzone)
805
750
  else:
806
751
  urls.append(f"{self.urls.mainzone}?ZoneName={self.zone}")
807
- # Tags in XML which might contain information about zones power status
808
- # ordered by their priority
809
- tags = ["./ZonePower/value", "./Power/value"]
810
752
 
811
- for tag in tags:
812
- for url in urls:
813
- try:
814
- xml = await self.api.async_get_xml(url, cache_id=cache_id)
815
- except AvrRequestError as err:
816
- _LOGGER.debug(
817
- "Error when getting power status from url %s: %s", url, err
818
- )
819
- continue
753
+ # There are different XML tags which might contain information
754
+ # about zone status attributes.
755
+ attribute_searchstrings = {
756
+ "_power": ["./ZonePower/value", "./Power/value"],
757
+ "_eco_mode": ["./ECOMode/value"],
758
+ }
820
759
 
821
- # Search for power tag
822
- power_tag = xml.find(tag)
823
- if power_tag is not None and power_tag.text is not None:
824
- self._power = power_tag.text
825
- return
760
+ for url in urls:
761
+ if len(attribute_searchstrings) == 0:
762
+ break
826
763
 
827
- raise AvrProcessingError(
828
- f"Power attribute of zone {self.zone} not found on update"
829
- )
764
+ try:
765
+ xml = await self.api.async_get_xml(url, cache_id=cache_id)
766
+ except AvrRequestError as err:
767
+ _LOGGER.debug(
768
+ "Error when getting device status from url %s: %s", url, err
769
+ )
770
+ continue
771
+
772
+ attributes_found = []
773
+ for attribute, searchstrings in attribute_searchstrings.items():
774
+ for searchstring in searchstrings:
775
+ tag = xml.find(searchstring)
776
+ if tag is not None and tag.text is not None:
777
+ setattr(self, attribute, tag.text)
778
+ attributes_found.append(attribute)
779
+ break
780
+
781
+ for attribute in attributes_found:
782
+ attribute_searchstrings.pop(attribute)
783
+
784
+ if len(attribute_searchstrings) > 0:
785
+ _LOGGER.debug(
786
+ "%s attributes of zone %s not found on update",
787
+ attribute_searchstrings.keys(),
788
+ self.zone,
789
+ )
830
790
 
831
791
  ##############
832
792
  # Properties #
@@ -854,8 +814,6 @@ class DenonAVRDeviceInfo:
854
814
  """
855
815
  Returns the dimmer state of the device.
856
816
 
857
- Only available if using Telnet.
858
-
859
817
  Possible values are: "Off", "Dark", "Dim" and "Bright"
860
818
  """
861
819
  return self._dimmer
@@ -865,9 +823,7 @@ class DenonAVRDeviceInfo:
865
823
  """
866
824
  Return the auto-standby state of the device.
867
825
 
868
- Only available if using Telnet.
869
-
870
- Possible values are: "OFF", "15M", "30M", "60M"
826
+ Possible values are: "OFF", "15M", "30M", "60M", "2H", "4H", "8H"
871
827
  """
872
828
  return self._auto_standby
873
829
 
@@ -896,8 +852,6 @@ class DenonAVRDeviceInfo:
896
852
  """
897
853
  Returns the eco-mode for the device.
898
854
 
899
- Only available if using Telnet.
900
-
901
855
  Possible values are: "Off", "On", "Auto"
902
856
  """
903
857
  return self._eco_mode
@@ -974,7 +928,7 @@ class DenonAVRDeviceInfo:
974
928
  return self._room_size
975
929
 
976
930
  @property
977
- def triggers(self) -> Dict[int, str]:
931
+ def triggers(self) -> Optional[Dict[int, str]]:
978
932
  """
979
933
  Return the triggers and their statuses for the device.
980
934
 
@@ -1051,11 +1005,38 @@ class DenonAVRDeviceInfo:
1051
1005
  """
1052
1006
  return self._headphone_eq
1053
1007
 
1008
+ @property
1009
+ def illumination(self) -> Optional[str]:
1010
+ """
1011
+ Return the illumination status for the device.
1012
+
1013
+ Only available on Marantz devices and when using Telnet.
1014
+
1015
+ Possible values are: "Auto", "Bright", "Dim", "Dark", "Off"
1016
+ """
1017
+ return self._illumination
1018
+
1019
+ @property
1020
+ def auto_lip_sync(self) -> Optional[bool]:
1021
+ """
1022
+ Return the auto lip sync status for the device.
1023
+
1024
+ Only available on Marantz devices and when using Telnet.
1025
+ """
1026
+ return self._auto_lip_sync
1027
+
1054
1028
  @property
1055
1029
  def telnet_available(self) -> bool:
1056
1030
  """Return true if telnet is connected and healthy."""
1057
1031
  return self.telnet_api.connected and self.telnet_api.healthy
1058
1032
 
1033
+ @property
1034
+ def is_denon(self) -> bool:
1035
+ """Return true if the receiver is a Denon device."""
1036
+ if not self.manufacturer:
1037
+ return True # Fallback to Denon
1038
+ return "denon" in self.manufacturer.lower()
1039
+
1059
1040
  ##########
1060
1041
  # Getter #
1061
1042
  ##########
@@ -1080,7 +1061,7 @@ class DenonAVRDeviceInfo:
1080
1061
  ##########
1081
1062
 
1082
1063
  async def async_power_on(self) -> None:
1083
- """Turn on receiver via HTTP get command."""
1064
+ """Turn on receiver."""
1084
1065
  if self.telnet_available:
1085
1066
  await self.telnet_api.async_send_commands(
1086
1067
  self.telnet_commands.command_power_on
@@ -1089,7 +1070,7 @@ class DenonAVRDeviceInfo:
1089
1070
  await self.api.async_get_command(self.urls.command_power_on)
1090
1071
 
1091
1072
  async def async_power_off(self) -> None:
1092
- """Turn off receiver via HTTP get command."""
1073
+ """Turn off receiver."""
1093
1074
  if self.telnet_available:
1094
1075
  await self.telnet_api.async_send_commands(
1095
1076
  self.telnet_commands.command_power_standby
@@ -1098,7 +1079,7 @@ class DenonAVRDeviceInfo:
1098
1079
  await self.api.async_get_command(self.urls.command_power_standby)
1099
1080
 
1100
1081
  async def async_cursor_up(self) -> None:
1101
- """Cursor Up on receiver via HTTP get command."""
1082
+ """Cursor Up on receiver."""
1102
1083
  if self.telnet_available:
1103
1084
  await self.telnet_api.async_send_commands(
1104
1085
  self.telnet_commands.command_cusor_up, skip_confirmation=True
@@ -1107,7 +1088,7 @@ class DenonAVRDeviceInfo:
1107
1088
  await self.api.async_get_command(self.urls.command_cusor_up)
1108
1089
 
1109
1090
  async def async_cursor_down(self) -> None:
1110
- """Cursor Down on receiver via HTTP get command."""
1091
+ """Cursor Down on receiver."""
1111
1092
  if self.telnet_available:
1112
1093
  await self.telnet_api.async_send_commands(
1113
1094
  self.telnet_commands.command_cusor_down, skip_confirmation=True
@@ -1116,7 +1097,7 @@ class DenonAVRDeviceInfo:
1116
1097
  await self.api.async_get_command(self.urls.command_cusor_down)
1117
1098
 
1118
1099
  async def async_cursor_left(self) -> None:
1119
- """Cursor Left on receiver via HTTP get command."""
1100
+ """Cursor Left on receiver."""
1120
1101
  if self.telnet_available:
1121
1102
  await self.telnet_api.async_send_commands(
1122
1103
  self.telnet_commands.command_cusor_left, skip_confirmation=True
@@ -1125,7 +1106,7 @@ class DenonAVRDeviceInfo:
1125
1106
  await self.api.async_get_command(self.urls.command_cusor_left)
1126
1107
 
1127
1108
  async def async_cursor_right(self) -> None:
1128
- """Cursor Right on receiver via HTTP get command."""
1109
+ """Cursor Right on receiver."""
1129
1110
  if self.telnet_available:
1130
1111
  await self.telnet_api.async_send_commands(
1131
1112
  self.telnet_commands.command_cusor_right, skip_confirmation=True
@@ -1134,7 +1115,7 @@ class DenonAVRDeviceInfo:
1134
1115
  await self.api.async_get_command(self.urls.command_cusor_right)
1135
1116
 
1136
1117
  async def async_cursor_enter(self) -> None:
1137
- """Cursor Enter on receiver via HTTP get command."""
1118
+ """Cursor Enter on receiver."""
1138
1119
  if self.telnet_available:
1139
1120
  await self.telnet_api.async_send_commands(
1140
1121
  self.telnet_commands.command_cusor_enter, skip_confirmation=True
@@ -1143,7 +1124,7 @@ class DenonAVRDeviceInfo:
1143
1124
  await self.api.async_get_command(self.urls.command_cusor_enter)
1144
1125
 
1145
1126
  async def async_back(self) -> None:
1146
- """Back command on receiver via HTTP get command."""
1127
+ """Back command on receiver."""
1147
1128
  if self.telnet_available:
1148
1129
  await self.telnet_api.async_send_commands(
1149
1130
  self.telnet_commands.command_back, skip_confirmation=True
@@ -1152,7 +1133,7 @@ class DenonAVRDeviceInfo:
1152
1133
  await self.api.async_get_command(self.urls.command_back)
1153
1134
 
1154
1135
  async def async_info(self) -> None:
1155
- """Info OSD on receiver via HTTP get command."""
1136
+ """Info OSD on receiver."""
1156
1137
  if self.telnet_available:
1157
1138
  await self.telnet_api.async_send_commands(
1158
1139
  self.telnet_commands.command_info, skip_confirmation=True
@@ -1161,7 +1142,7 @@ class DenonAVRDeviceInfo:
1161
1142
  await self.api.async_get_command(self.urls.command_info)
1162
1143
 
1163
1144
  async def async_options(self) -> None:
1164
- """Options menu on receiver via HTTP get command."""
1145
+ """Options menu on receiver."""
1165
1146
  if self.telnet_available:
1166
1147
  await self.telnet_api.async_send_commands(
1167
1148
  self.telnet_commands.command_options, skip_confirmation=True
@@ -1171,7 +1152,7 @@ class DenonAVRDeviceInfo:
1171
1152
 
1172
1153
  async def async_settings_menu(self) -> None:
1173
1154
  """
1174
- Options menu on receiver via HTTP get command.
1155
+ Options menu on receiver.
1175
1156
 
1176
1157
  Only available if using Telnet.
1177
1158
  """
@@ -1185,7 +1166,7 @@ class DenonAVRDeviceInfo:
1185
1166
  )
1186
1167
 
1187
1168
  async def async_channel_level_adjust(self) -> None:
1188
- """Toggle the channel level adjust menu on receiver via HTTP get command."""
1169
+ """Toggle the channel level adjust menu on receiver."""
1189
1170
  if self.telnet_available:
1190
1171
  await self.telnet_api.async_send_commands(
1191
1172
  self.telnet_commands.command_channel_level_adjust
@@ -1194,7 +1175,7 @@ class DenonAVRDeviceInfo:
1194
1175
  await self.api.async_get_command(self.urls.command_channel_level_adjust)
1195
1176
 
1196
1177
  async def async_dimmer_toggle(self) -> None:
1197
- """Toggle dimmer on receiver via HTTP get command."""
1178
+ """Toggle dimmer on receiver."""
1198
1179
  if self.telnet_available:
1199
1180
  await self.telnet_api.async_send_commands(
1200
1181
  self.telnet_commands.command_dimmer_toggle
@@ -1203,11 +1184,11 @@ class DenonAVRDeviceInfo:
1203
1184
  await self.api.async_get_command(self.urls.command_dimmer_toggle)
1204
1185
 
1205
1186
  async def async_dimmer(self, mode: DimmerModes) -> None:
1206
- """Set dimmer mode on receiver via HTTP get command."""
1187
+ """Set dimmer mode on receiver."""
1207
1188
  if mode not in self._dimmer_modes:
1208
1189
  raise AvrCommandError("Invalid dimmer mode")
1209
1190
 
1210
- mapped_mode = DIMMER_MODE_MAP[mode]
1191
+ mapped_mode = DIMMER_MODE_MAP_REVERSE[mode]
1211
1192
  if self.telnet_available:
1212
1193
  await self.telnet_api.async_send_commands(
1213
1194
  self.telnet_commands.command_dimmer_set.format(mode=mapped_mode)
@@ -1218,7 +1199,7 @@ class DenonAVRDeviceInfo:
1218
1199
  )
1219
1200
 
1220
1201
  async def async_tactile_transducer_on(self) -> None:
1221
- """Turn on tactile transducer on receiver via HTTP get command."""
1202
+ """Turn on tactile transducer on receiver."""
1222
1203
  if self.telnet_available:
1223
1204
  await self.telnet_api.async_send_commands(
1224
1205
  self.telnet_commands.command_tactile_transducer.format(mode="ON")
@@ -1229,7 +1210,7 @@ class DenonAVRDeviceInfo:
1229
1210
  )
1230
1211
 
1231
1212
  async def async_auto_standby(self, auto_standby: AutoStandbys) -> None:
1232
- """Set auto standby on receiver via HTTP get command."""
1213
+ """Set auto standby on receiver."""
1233
1214
  if auto_standby not in self._auto_standbys:
1234
1215
  raise AvrCommandError("Invalid Auto Standby mode")
1235
1216
  if self.telnet_available:
@@ -1243,7 +1224,7 @@ class DenonAVRDeviceInfo:
1243
1224
 
1244
1225
  async def async_sleep(self, sleep: Union[Literal["OFF"], int]) -> None:
1245
1226
  """
1246
- Set auto standby on receiver via HTTP get command.
1227
+ Set auto standby on receiver.
1247
1228
 
1248
1229
  Valid sleep values are "OFF" and 1-120 (in minutes)
1249
1230
  """
@@ -1261,7 +1242,7 @@ class DenonAVRDeviceInfo:
1261
1242
  )
1262
1243
 
1263
1244
  async def async_tactile_transducer_off(self) -> None:
1264
- """Turn on tactile transducer on receiver via HTTP get command."""
1245
+ """Turn on tactile transducer on receiver."""
1265
1246
  if self.telnet_available:
1266
1247
  await self.telnet_api.async_send_commands(
1267
1248
  self.telnet_commands.command_tactile_transducer.format(mode="OFF")
@@ -1273,7 +1254,7 @@ class DenonAVRDeviceInfo:
1273
1254
 
1274
1255
  async def async_tactile_transducer_toggle(self) -> None:
1275
1256
  """
1276
- Turn on tactile transducer on receiver via HTTP get command.
1257
+ Turn on tactile transducer on receiver.
1277
1258
 
1278
1259
  Only available if using Telnet.
1279
1260
  """
@@ -1283,7 +1264,7 @@ class DenonAVRDeviceInfo:
1283
1264
  await self.async_tactile_transducer_on()
1284
1265
 
1285
1266
  async def async_tactile_transducer_level_up(self) -> None:
1286
- """Increase the transducer level on receiver via HTTP get command."""
1267
+ """Increase the transducer level on receiver."""
1287
1268
  if self.telnet_available:
1288
1269
  await self.telnet_api.async_send_commands(
1289
1270
  self.telnet_commands.command_tactile_transducer_level.format(mode="UP")
@@ -1294,7 +1275,7 @@ class DenonAVRDeviceInfo:
1294
1275
  )
1295
1276
 
1296
1277
  async def async_tactile_transducer_level_down(self) -> None:
1297
- """Decrease the transducer level on receiver via HTTP get command."""
1278
+ """Decrease the transducer level on receiver."""
1298
1279
  if self.telnet_available:
1299
1280
  await self.telnet_api.async_send_commands(
1300
1281
  self.telnet_commands.command_tactile_transducer_level.format(
@@ -1307,7 +1288,7 @@ class DenonAVRDeviceInfo:
1307
1288
  )
1308
1289
 
1309
1290
  async def async_transducer_lpf(self, lpf: TransducerLPFs):
1310
- """Set transducer low pass filter on receiver via HTTP get command."""
1291
+ """Set transducer low pass filter on receiver."""
1311
1292
  if lpf not in self._tactile_transducer_lpfs:
1312
1293
  raise AvrCommandError("Invalid tactile transducer low pass filter")
1313
1294
 
@@ -1326,7 +1307,7 @@ class DenonAVRDeviceInfo:
1326
1307
  )
1327
1308
 
1328
1309
  async def async_room_size(self, room_size: RoomSizes) -> None:
1329
- """Set room size on receiver via HTTP get command."""
1310
+ """Set room size on receiver."""
1330
1311
  if room_size not in self._room_sizes:
1331
1312
  raise AvrCommandError("Invalid room size")
1332
1313
 
@@ -1341,7 +1322,7 @@ class DenonAVRDeviceInfo:
1341
1322
 
1342
1323
  async def async_trigger_on(self, trigger: int) -> None:
1343
1324
  """
1344
- Set trigger to ON on receiver via HTTP get command.
1325
+ Set trigger to ON on receiver.
1345
1326
 
1346
1327
  :param trigger: Trigger number to set to ON. Valid values are 1-3.
1347
1328
  """
@@ -1359,7 +1340,7 @@ class DenonAVRDeviceInfo:
1359
1340
 
1360
1341
  async def async_trigger_off(self, trigger: int) -> None:
1361
1342
  """
1362
- Set trigger to OFF on receiver via HTTP get command.
1343
+ Set trigger to OFF on receiver.
1363
1344
 
1364
1345
  :param trigger: Trigger number to set to OFF. Valid values are 1-3.
1365
1346
  """
@@ -1377,7 +1358,7 @@ class DenonAVRDeviceInfo:
1377
1358
 
1378
1359
  async def async_trigger_toggle(self, trigger: int) -> None:
1379
1360
  """
1380
- Toggle trigger on receiver via HTTP get command.
1361
+ Toggle trigger on receiver.
1381
1362
 
1382
1363
  Only available if using Telnet.
1383
1364
 
@@ -1394,7 +1375,7 @@ class DenonAVRDeviceInfo:
1394
1375
 
1395
1376
  async def async_quick_select_mode(self, quick_select_number: int) -> None:
1396
1377
  """
1397
- Set quick select mode on receiver via HTTP get command.
1378
+ Set quick select mode on receiver.
1398
1379
 
1399
1380
  :param quick_select_number: Quick select number to set. Valid values are 1-5.
1400
1381
  """
@@ -1402,15 +1383,15 @@ class DenonAVRDeviceInfo:
1402
1383
  raise AvrCommandError("Quick select number must be between 1 and 5")
1403
1384
 
1404
1385
  if self.telnet_available:
1405
- if "denon" in self.manufacturer.lower():
1406
- command = self.telnet_commands.command_quick_select_memory
1386
+ if self.is_denon:
1387
+ command = self.telnet_commands.command_quick_select_mode
1407
1388
  else:
1408
- command = self.telnet_commands.command_smart_select_memory
1389
+ command = self.telnet_commands.command_smart_select_mode
1409
1390
  await self.telnet_api.async_send_commands(
1410
1391
  command.format(number=quick_select_number)
1411
1392
  )
1412
1393
  else:
1413
- if "denon" in self.manufacturer.lower():
1394
+ if self.is_denon:
1414
1395
  command = self.urls.command_quick_select_mode
1415
1396
  else:
1416
1397
  command = self.urls.command_smart_select_mode
@@ -1418,7 +1399,7 @@ class DenonAVRDeviceInfo:
1418
1399
 
1419
1400
  async def async_quick_select_memory(self, quick_select_number: int) -> None:
1420
1401
  """
1421
- Set quick select memory on receiver via HTTP get command.
1402
+ Set quick select memory on receiver.
1422
1403
 
1423
1404
  :param quick_select_number: Quick select number to set. Valid values are 1-5.
1424
1405
  """
@@ -1426,22 +1407,22 @@ class DenonAVRDeviceInfo:
1426
1407
  raise AvrCommandError("Quick select number must be between 1 and 5")
1427
1408
 
1428
1409
  if self.telnet_available:
1429
- if "denon" in self.manufacturer.lower():
1430
- command = self.telnet_commands.command_quick_select_mode
1410
+ if self.is_denon:
1411
+ command = self.telnet_commands.command_quick_select_memory
1431
1412
  else:
1432
- command = self.telnet_commands.command_smart_select_mode
1413
+ command = self.telnet_commands.command_smart_select_memory
1433
1414
  await self.telnet_api.async_send_commands(
1434
1415
  command.format(number=quick_select_number)
1435
1416
  )
1436
1417
  else:
1437
- if "denon" in self.manufacturer.lower():
1418
+ if self.is_denon:
1438
1419
  command = self.urls.command_quick_select_memory
1439
1420
  else:
1440
1421
  command = self.urls.command_smart_select_memory
1441
1422
  await self.api.async_get_command(command.format(number=quick_select_number))
1442
1423
 
1443
1424
  async def async_delay_up(self) -> None:
1444
- """Delay up on receiver via HTTP get command."""
1425
+ """Delay up on receiver."""
1445
1426
  if self.telnet_available:
1446
1427
  await self.telnet_api.async_send_commands(
1447
1428
  self.telnet_commands.command_delay_up
@@ -1450,7 +1431,7 @@ class DenonAVRDeviceInfo:
1450
1431
  await self.api.async_get_command(self.urls.command_delay_up)
1451
1432
 
1452
1433
  async def async_delay_down(self) -> None:
1453
- """Delay down on receiver via HTTP get command."""
1434
+ """Delay down on receiver."""
1454
1435
  if self.telnet_available:
1455
1436
  await self.telnet_api.async_send_commands(
1456
1437
  self.telnet_commands.command_delay_down
@@ -1463,7 +1444,7 @@ class DenonAVRDeviceInfo:
1463
1444
  if mode not in self._eco_modes:
1464
1445
  raise AvrCommandError("Invalid Eco mode")
1465
1446
 
1466
- mapped_mode = ECO_MODE_MAP[mode]
1447
+ mapped_mode = ECO_MODE_MAP_REVERSE[mode]
1467
1448
  if self.telnet_available:
1468
1449
  await self.telnet_api.async_send_commands(
1469
1450
  self.telnet_commands.command_eco_mode.format(mode=mapped_mode)
@@ -1478,7 +1459,7 @@ class DenonAVRDeviceInfo:
1478
1459
  if output not in self._hdmi_outputs:
1479
1460
  raise AvrCommandError("Invalid HDMI output mode")
1480
1461
 
1481
- mapped_output = HDMI_OUTPUT_MAP[output]
1462
+ mapped_output = HDMI_OUTPUT_MAP_REVERSE[output]
1482
1463
  if self.telnet_available:
1483
1464
  await self.telnet_api.async_send_commands(
1484
1465
  self.telnet_commands.command_hdmi_output.format(output=mapped_output)
@@ -1489,7 +1470,7 @@ class DenonAVRDeviceInfo:
1489
1470
  )
1490
1471
 
1491
1472
  async def async_hdmi_audio_decode(self, mode: HDMIAudioDecodes) -> None:
1492
- """Set HDMI Audio Decode mode on receiver via HTTP get command."""
1473
+ """Set HDMI Audio Decode mode on receiver."""
1493
1474
  if mode not in self._hdmi_audio_decodes:
1494
1475
  raise AvrCommandError("Invalid HDMI Audio Decode mode")
1495
1476
 
@@ -1503,10 +1484,10 @@ class DenonAVRDeviceInfo:
1503
1484
  )
1504
1485
 
1505
1486
  async def async_video_processing_mode(self, mode: VideoProcessingModes) -> None:
1506
- """Set video processing mode on receiver via HTTP get command."""
1487
+ """Set video processing mode on receiver."""
1507
1488
  if mode not in self._video_processing_modes:
1508
1489
  raise AvrCommandError("Invalid video processing mode")
1509
- processing_mode = VIDEO_PROCESSING_MODES_MAP[mode]
1490
+ processing_mode = VIDEO_PROCESSING_MODES_MAP_REVERSE[mode]
1510
1491
  if self.telnet_available:
1511
1492
  await self.telnet_api.async_send_commands(
1512
1493
  self.telnet_commands.command_video_processing_mode.format(
@@ -1518,14 +1499,20 @@ class DenonAVRDeviceInfo:
1518
1499
  self.urls.command_video_processing_mode.format(mode=processing_mode)
1519
1500
  )
1520
1501
 
1521
- async def async_status(self) -> str:
1522
- """Get status of receiver via HTTP get command."""
1523
- if "denon" not in self.manufacturer.lower():
1502
+ async def async_status(self) -> None:
1503
+ """Get status of receiver."""
1504
+ if not self.is_denon:
1524
1505
  raise AvrCommandError("Status command is only supported for Denon devices")
1525
- return await self.api.async_get_command(self.urls.command_status)
1506
+
1507
+ if self.telnet_available:
1508
+ await self.telnet_api.async_send_commands(
1509
+ self.telnet_commands.command_status
1510
+ )
1511
+ else:
1512
+ await self.api.async_get_command(self.urls.command_status)
1526
1513
 
1527
1514
  async def async_system_reset(self) -> None:
1528
- """DANGER! Reset the receiver via HTTP get command."""
1515
+ """DANGER! Reset the receiver."""
1529
1516
  if self.telnet_available:
1530
1517
  await self.telnet_api.async_send_commands(
1531
1518
  self.telnet_commands.command_system_reset
@@ -1534,7 +1521,7 @@ class DenonAVRDeviceInfo:
1534
1521
  await self.api.async_get_command(self.urls.command_system_reset)
1535
1522
 
1536
1523
  async def async_network_restart(self) -> None:
1537
- """Restart the network on the receiver via HTTP get command."""
1524
+ """Restart the network on the receiver."""
1538
1525
  if self.telnet_available:
1539
1526
  await self.telnet_api.async_send_commands(
1540
1527
  self.telnet_commands.command_network_restart
@@ -1544,7 +1531,7 @@ class DenonAVRDeviceInfo:
1544
1531
 
1545
1532
  async def async_speaker_preset(self, preset: int) -> None:
1546
1533
  """
1547
- Set speaker preset on receiver via HTTP get command.
1534
+ Set speaker preset on receiver.
1548
1535
 
1549
1536
  Valid preset values are 1-2.
1550
1537
  """
@@ -1562,7 +1549,7 @@ class DenonAVRDeviceInfo:
1562
1549
 
1563
1550
  async def async_speaker_preset_toggle(self) -> None:
1564
1551
  """
1565
- Toggle speaker preset on receiver via HTTP get command.
1552
+ Toggle speaker preset on receiver.
1566
1553
 
1567
1554
  Only available if using Telnet.
1568
1555
  """
@@ -1572,7 +1559,7 @@ class DenonAVRDeviceInfo:
1572
1559
  async def async_bt_transmitter_on(
1573
1560
  self,
1574
1561
  ) -> None:
1575
- """Turn on Bluetooth transmitter on receiver via HTTP get command."""
1562
+ """Turn on Bluetooth transmitter on receiver."""
1576
1563
  if self.telnet_available:
1577
1564
  await self.telnet_api.async_send_commands(
1578
1565
  self.telnet_commands.command_bluetooth_transmitter.format(mode="ON")
@@ -1585,7 +1572,7 @@ class DenonAVRDeviceInfo:
1585
1572
  async def async_bt_transmitter_off(
1586
1573
  self,
1587
1574
  ) -> None:
1588
- """Turn off Bluetooth transmitter on receiver via HTTP get command."""
1575
+ """Turn off Bluetooth transmitter on receiver."""
1589
1576
  if self.telnet_available:
1590
1577
  await self.telnet_api.async_send_commands(
1591
1578
  self.telnet_commands.command_bluetooth_transmitter.format(mode="OFF")
@@ -1597,7 +1584,7 @@ class DenonAVRDeviceInfo:
1597
1584
 
1598
1585
  async def async_bt_transmitter_toggle(self) -> None:
1599
1586
  """
1600
- Toggle Bluetooth transmitter mode on receiver via HTTP get command.
1587
+ Toggle Bluetooth transmitter mode on receiver.
1601
1588
 
1602
1589
  Only available if using Telnet.
1603
1590
  """
@@ -1607,11 +1594,11 @@ class DenonAVRDeviceInfo:
1607
1594
  await self.async_bt_transmitter_on()
1608
1595
 
1609
1596
  async def async_bt_output_mode(self, mode: BluetoothOutputModes) -> None:
1610
- """Set Bluetooth transmitter mode on receiver via HTTP get command."""
1597
+ """Set Bluetooth transmitter mode on receiver."""
1611
1598
  if mode not in self._bt_output_modes:
1612
1599
  raise AvrCommandError("Invalid Bluetooth output mode")
1613
1600
 
1614
- mapped_mode = BLUETOOTH_OUTPUT_MODES_MAP[mode]
1601
+ mapped_mode = BLUETOOTH_OUTPUT_MODES_MAP_REVERSE[mode]
1615
1602
  if self.telnet_available:
1616
1603
  await self.telnet_api.async_send_commands(
1617
1604
  self.telnet_commands.command_bluetooth_transmitter.format(
@@ -1625,7 +1612,7 @@ class DenonAVRDeviceInfo:
1625
1612
 
1626
1613
  async def async_bt_output_mode_toggle(self) -> None:
1627
1614
  """
1628
- Toggle Bluetooth output mode on receiver via HTTP get command.
1615
+ Toggle Bluetooth output mode on receiver.
1629
1616
 
1630
1617
  Only available if using Telnet.
1631
1618
  """
@@ -1635,7 +1622,7 @@ class DenonAVRDeviceInfo:
1635
1622
  await self.async_bt_output_mode("Bluetooth + Speakers")
1636
1623
 
1637
1624
  async def async_delay_time_up(self) -> None:
1638
- """Delay time up on receiver via HTTP get command."""
1625
+ """Delay time up on receiver."""
1639
1626
  if self.telnet_available:
1640
1627
  await self.telnet_api.async_send_commands(
1641
1628
  self.telnet_commands.command_delay_time.format(value="UP")
@@ -1646,7 +1633,7 @@ class DenonAVRDeviceInfo:
1646
1633
  )
1647
1634
 
1648
1635
  async def async_delay_time_down(self) -> None:
1649
- """Delay time up on receiver via HTTP get command."""
1636
+ """Delay time up on receiver."""
1650
1637
  if self.telnet_available:
1651
1638
  await self.telnet_api.async_send_commands(
1652
1639
  self.telnet_commands.command_delay_time.format(value="DOWN")
@@ -1658,7 +1645,7 @@ class DenonAVRDeviceInfo:
1658
1645
 
1659
1646
  async def async_delay_time(self, delay_time: int) -> None:
1660
1647
  """
1661
- Set delay time on receiver via HTTP get command.
1648
+ Set delay time on receiver.
1662
1649
 
1663
1650
  :param delay_time: Delay time in ms. Valid values are 0-999.
1664
1651
  """
@@ -1675,11 +1662,11 @@ class DenonAVRDeviceInfo:
1675
1662
  )
1676
1663
 
1677
1664
  async def async_audio_restorer(self, mode: AudioRestorers):
1678
- """Set audio restorer on receiver via HTTP get command."""
1665
+ """Set audio restorer on receiver."""
1679
1666
  if mode not in self._audio_restorers:
1680
1667
  raise AvrCommandError("Invalid audio restorer mode")
1681
1668
 
1682
- mapped_mode = AUDIO_RESTORER_MAP[mode]
1669
+ mapped_mode = AUDIO_RESTORER_MAP_REVERSE[mode]
1683
1670
  if self.telnet_available:
1684
1671
  await self.telnet_api.async_send_commands(
1685
1672
  self.telnet_commands.command_audio_restorer.format(mode=mapped_mode)
@@ -1690,7 +1677,7 @@ class DenonAVRDeviceInfo:
1690
1677
  )
1691
1678
 
1692
1679
  async def async_remote_control_lock(self):
1693
- """Set remote control lock on receiver via HTTP get command."""
1680
+ """Set remote control lock on receiver."""
1694
1681
  if self.telnet_available:
1695
1682
  await self.telnet_api.async_send_commands(
1696
1683
  self.telnet_commands.command_remote_control_lock.format(mode="ON")
@@ -1701,7 +1688,7 @@ class DenonAVRDeviceInfo:
1701
1688
  )
1702
1689
 
1703
1690
  async def async_remote_control_unlock(self):
1704
- """Set remote control unlock on receiver via HTTP get command."""
1691
+ """Set remote control unlock on receiver."""
1705
1692
  if self.telnet_available:
1706
1693
  await self.telnet_api.async_send_commands(
1707
1694
  self.telnet_commands.command_remote_control_lock.format(mode="OFF")
@@ -1712,7 +1699,7 @@ class DenonAVRDeviceInfo:
1712
1699
  )
1713
1700
 
1714
1701
  async def async_panel_lock(self, panel_lock_mode: PanelLocks):
1715
- """Set panel lock on receiver via HTTP get command."""
1702
+ """Set panel lock on receiver."""
1716
1703
  if panel_lock_mode not in self._panel_locks:
1717
1704
  raise AvrCommandError("Invalid panel lock mode")
1718
1705
 
@@ -1736,7 +1723,7 @@ class DenonAVRDeviceInfo:
1736
1723
  )
1737
1724
 
1738
1725
  async def async_panel_unlock(self):
1739
- """Set panel unlock on receiver via HTTP get command."""
1726
+ """Set panel unlock on receiver."""
1740
1727
  if self.telnet_available:
1741
1728
  await self.telnet_api.async_send_commands(
1742
1729
  self.telnet_commands.command_panel_lock.format(mode="OFF")
@@ -1747,7 +1734,7 @@ class DenonAVRDeviceInfo:
1747
1734
  )
1748
1735
 
1749
1736
  async def async_graphic_eq_on(self) -> None:
1750
- """Turn on Graphic EQ on receiver via HTTP get command."""
1737
+ """Turn on Graphic EQ on receiver."""
1751
1738
  if self.telnet_available:
1752
1739
  await self.telnet_api.async_send_commands(
1753
1740
  self.telnet_commands.command_graphic_eq.format(mode="ON")
@@ -1758,7 +1745,7 @@ class DenonAVRDeviceInfo:
1758
1745
  )
1759
1746
 
1760
1747
  async def async_graphic_eq_off(self) -> None:
1761
- """Turn off Graphic EQ on receiver via HTTP get command."""
1748
+ """Turn off Graphic EQ on receiver."""
1762
1749
  if self.telnet_available:
1763
1750
  await self.telnet_api.async_send_commands(
1764
1751
  self.telnet_commands.command_graphic_eq.format(mode="OFF")
@@ -1770,7 +1757,7 @@ class DenonAVRDeviceInfo:
1770
1757
 
1771
1758
  async def async_graphic_eq_toggle(self) -> None:
1772
1759
  """
1773
- Toggle Graphic EQ on receiver via HTTP get command.
1760
+ Toggle Graphic EQ on receiver.
1774
1761
 
1775
1762
  Only available if using Telnet.
1776
1763
  """
@@ -1780,7 +1767,7 @@ class DenonAVRDeviceInfo:
1780
1767
  await self.async_graphic_eq_on()
1781
1768
 
1782
1769
  async def async_headphone_eq_on(self) -> None:
1783
- """Turn on Headphone EQ on receiver via HTTP get command."""
1770
+ """Turn on Headphone EQ on receiver."""
1784
1771
  if self.telnet_available:
1785
1772
  await self.telnet_api.async_send_commands(
1786
1773
  self.telnet_commands.command_headphone_eq.format(mode="ON")
@@ -1791,7 +1778,7 @@ class DenonAVRDeviceInfo:
1791
1778
  )
1792
1779
 
1793
1780
  async def async_headphone_eq_off(self) -> None:
1794
- """Turn off Headphone EQ on receiver via HTTP get command."""
1781
+ """Turn off Headphone EQ on receiver."""
1795
1782
  if self.telnet_available:
1796
1783
  await self.telnet_api.async_send_commands(
1797
1784
  self.telnet_commands.command_headphone_eq.format(mode="OFF")
@@ -1803,7 +1790,7 @@ class DenonAVRDeviceInfo:
1803
1790
 
1804
1791
  async def async_headphone_eq_toggle(self) -> None:
1805
1792
  """
1806
- Toggle Headphone EQ on receiver via HTTP get command.
1793
+ Toggle Headphone EQ on receiver.
1807
1794
 
1808
1795
  Only available if using Telnet.
1809
1796
  """
@@ -1812,6 +1799,223 @@ class DenonAVRDeviceInfo:
1812
1799
  else:
1813
1800
  await self.async_headphone_eq_on()
1814
1801
 
1802
+ async def async_hdmi_cec_on(self) -> None:
1803
+ """Turn on HDMI CEC on receiver."""
1804
+ if self.telnet_available:
1805
+ await self.telnet_api.async_send_commands(
1806
+ self.telnet_commands.command_denon_hdmi_cec_on
1807
+ if self.is_denon
1808
+ else self.urls.command_marantz_hdmi_cec_on
1809
+ )
1810
+ else:
1811
+ await self.api.async_get_command(
1812
+ self.urls.command_denon_hdmi_cec_on
1813
+ if self.is_denon
1814
+ else self.urls.command_marantz_hdmi_cec_on
1815
+ )
1816
+
1817
+ async def async_hdmi_cec_off(self) -> None:
1818
+ """Turn off HDMI CEC on receiver."""
1819
+ if self.telnet_available:
1820
+ await self.telnet_api.async_send_commands(
1821
+ self.telnet_commands.command_denon_hdmi_cec_off
1822
+ if self.is_denon
1823
+ else self.urls.command_marantz_hdmi_cec_off
1824
+ )
1825
+ else:
1826
+ await self.api.async_get_command(
1827
+ self.urls.command_denon_hdmi_cec_off
1828
+ if self.is_denon
1829
+ else self.urls.command_marantz_hdmi_cec_off
1830
+ )
1831
+
1832
+ async def async_illumination(self, mode: Illuminations):
1833
+ """
1834
+ Set illumination mode on receiver.
1835
+
1836
+ Only available on Marantz devices.
1837
+ """
1838
+ if self.is_denon:
1839
+ raise AvrCommandError("Illumination is only available for Marantz devices")
1840
+
1841
+ if mode not in self._illuminations:
1842
+ raise AvrCommandError("Invalid illumination mode")
1843
+
1844
+ mapped_mode = ILLUMINATION_MAP_REVERSE[mode]
1845
+ if self.telnet_available:
1846
+ await self.telnet_api.async_send_commands(
1847
+ self.telnet_commands.command_illumination.format(mode=mapped_mode)
1848
+ )
1849
+ else:
1850
+ await self.api.async_get_command(
1851
+ self.urls.command_illumination.format(mode=mapped_mode)
1852
+ )
1853
+
1854
+ async def async_auto_lip_sync_on(self) -> None:
1855
+ """
1856
+ Turn on auto lip sync on receiver.
1857
+
1858
+ Only available on Marantz devices.
1859
+ """
1860
+ if self.is_denon:
1861
+ raise AvrCommandError("Auto lip sync is only available for Marantz devices")
1862
+
1863
+ if self.telnet_available:
1864
+ await self.telnet_api.async_send_commands(
1865
+ self.telnet_commands.command_auto_lip_sync.format(mode="ON")
1866
+ )
1867
+ else:
1868
+ await self.api.async_get_command(
1869
+ self.urls.command_auto_lip_sync.format(mode="ON")
1870
+ )
1871
+
1872
+ async def async_auto_lip_sync_off(self) -> None:
1873
+ """
1874
+ Turn off auto lip sync on receiver.
1875
+
1876
+ Only available on Marantz devices.
1877
+ """
1878
+ if self.is_denon:
1879
+ raise AvrCommandError("Auto lip sync is only available for Marantz devices")
1880
+
1881
+ if self.telnet_available:
1882
+ await self.telnet_api.async_send_commands(
1883
+ self.telnet_commands.command_auto_lip_sync.format(mode="OFF")
1884
+ )
1885
+ else:
1886
+ await self.api.async_get_command(
1887
+ self.urls.command_auto_lip_sync.format(mode="OFF")
1888
+ )
1889
+
1890
+ async def async_auto_lip_sync_toggle(self) -> None:
1891
+ """
1892
+ Toggle auto lip sync on receiver.
1893
+
1894
+ Only available on Marantz devices and when using Telnet.
1895
+ """
1896
+ if self.is_denon:
1897
+ raise AvrCommandError("Auto lip sync is only available for Marantz devices")
1898
+
1899
+ if self._auto_lip_sync:
1900
+ await self.async_auto_lip_sync_off()
1901
+ else:
1902
+ await self.async_auto_lip_sync_on()
1903
+
1904
+ async def async_page_up(self) -> None:
1905
+ """Page Up on receiver."""
1906
+ if self.telnet_available:
1907
+ command = (
1908
+ self.telnet_commands.command_page_up_denon
1909
+ if self.is_denon
1910
+ else self.telnet_commands.command_page_up_marantz
1911
+ )
1912
+ await self.telnet_api.async_send_commands(command)
1913
+ else:
1914
+ command = (
1915
+ self.urls.command_page_up_denon
1916
+ if self.is_denon
1917
+ else self.urls.command_page_up_marantz
1918
+ )
1919
+ await self.api.async_get_command(command)
1920
+
1921
+ async def async_page_down(self) -> None:
1922
+ """Page Down on receiver."""
1923
+ if self.telnet_available:
1924
+ command = (
1925
+ self.telnet_commands.command_page_down_denon
1926
+ if self.is_denon
1927
+ else self.telnet_commands.command_page_down_marantz
1928
+ )
1929
+ await self.telnet_api.async_send_commands(command)
1930
+ else:
1931
+ command = (
1932
+ self.urls.command_page_down_denon
1933
+ if self.is_denon
1934
+ else self.urls.command_page_down_marantz
1935
+ )
1936
+ await self.api.async_get_command(command)
1937
+
1938
+ async def async_input_mode(self, mode: InputModes):
1939
+ """Set input mode on receiver."""
1940
+ if mode not in self._input_modes:
1941
+ raise AvrCommandError("Invalid input mode")
1942
+
1943
+ if mode == "Select":
1944
+ command = (
1945
+ self.telnet_commands.command_input_mode_select_denon
1946
+ if self.telnet_available
1947
+ else (
1948
+ self.urls.command_input_mode_select_denon
1949
+ if self.is_denon
1950
+ else (
1951
+ self.telnet_commands.command_input_mode_select_marantz
1952
+ if self.telnet_available
1953
+ else self.urls.command_input_mode_select_marantz
1954
+ )
1955
+ )
1956
+ )
1957
+ elif mode == "Auto":
1958
+ command = (
1959
+ self.telnet_commands.command_input_mode_auto_denon
1960
+ if self.telnet_available
1961
+ else (
1962
+ self.urls.command_input_mode_auto_denon
1963
+ if self.is_denon
1964
+ else (
1965
+ self.telnet_commands.command_input_mode_auto_marantz
1966
+ if self.telnet_available
1967
+ else self.urls.command_input_mode_auto_marantz
1968
+ )
1969
+ )
1970
+ )
1971
+ elif mode == "HDMI":
1972
+ command = (
1973
+ self.telnet_commands.command_input_mode_hdmi_denon
1974
+ if self.telnet_available
1975
+ else (
1976
+ self.urls.command_input_mode_hdmi_denon
1977
+ if self.is_denon
1978
+ else (
1979
+ self.telnet_commands.command_input_mode_hdmi_marantz
1980
+ if self.telnet_available
1981
+ else self.urls.command_input_mode_hdmi_marantz
1982
+ )
1983
+ )
1984
+ )
1985
+ elif mode == "Digital":
1986
+ command = (
1987
+ self.telnet_commands.command_input_mode_digital_denon
1988
+ if self.telnet_available
1989
+ else (
1990
+ self.urls.command_input_mode_digital_denon
1991
+ if self.is_denon
1992
+ else (
1993
+ self.telnet_commands.command_input_mode_digital_marantz
1994
+ if self.telnet_available
1995
+ else self.urls.command_input_mode_digital_marantz
1996
+ )
1997
+ )
1998
+ )
1999
+ else:
2000
+ command = (
2001
+ self.telnet_commands.command_input_mode_analog_denon
2002
+ if self.telnet_available
2003
+ else (
2004
+ self.urls.command_input_mode_analog_denon
2005
+ if self.is_denon
2006
+ else (
2007
+ self.telnet_commands.command_input_mode_analog_marantz
2008
+ if self.telnet_available
2009
+ else self.urls.command_input_mode_analog_marantz
2010
+ )
2011
+ )
2012
+ )
2013
+
2014
+ if self.telnet_available:
2015
+ await self.telnet_api.async_send_commands(command)
2016
+ else:
2017
+ await self.api.async_get_command(command)
2018
+
1815
2019
 
1816
2020
  @attr.s(auto_attribs=True, on_setattr=DENON_ATTR_SETATTR)
1817
2021
  class DenonAVRFoundation:
@@ -1861,11 +2065,11 @@ class DenonAVRFoundation:
1861
2065
  raise
1862
2066
 
1863
2067
  # Extract relevant information
1864
- zone = self._device.get_own_zone()
1865
-
1866
2068
  attrs = deepcopy(update_attrs)
1867
2069
  for app_command in attrs.keys():
1868
- search_strings = self.create_appcommand_search_strings(app_command, zone)
2070
+ search_strings = create_appcommand_search_strings(
2071
+ app_command, self._device.zone
2072
+ )
1869
2073
  start = 0
1870
2074
  success = 0
1871
2075
  for i, pattern in enumerate(app_command.response_pattern):
@@ -1904,9 +2108,10 @@ class DenonAVRFoundation:
1904
2108
 
1905
2109
  # Check if each attribute was updated
1906
2110
  if update_attrs:
1907
- raise AvrProcessingError(
1908
- f"Some attributes of zone {self._device.zone} not found on update:"
1909
- f" {update_attrs}"
2111
+ _LOGGER.debug(
2112
+ "Some attributes of zone %s not found on update: %s",
2113
+ self._device.zone,
2114
+ update_attrs,
1910
2115
  )
1911
2116
 
1912
2117
  async def async_update_attrs_status_xml(
@@ -1964,39 +2169,58 @@ class DenonAVRFoundation:
1964
2169
 
1965
2170
  # Check if each attribute was updated
1966
2171
  if update_attrs:
1967
- raise AvrProcessingError(
1968
- f"Some attributes of zone {self._device.zone} not found on update:"
1969
- f" {update_attrs}"
1970
- )
1971
-
1972
- @staticmethod
1973
- def create_appcommand_search_strings(
1974
- app_command_cmd: AppCommandCmd, zone: str
1975
- ) -> List[str]:
1976
- """Create search pattern for AppCommand(0300).xml response."""
1977
- result = []
1978
-
1979
- for resp in app_command_cmd.response_pattern:
1980
- string = "./cmd"
1981
- # Text of cmd tag in query was added as attribute to response
1982
- if app_command_cmd.cmd_text:
1983
- string = (
1984
- string + f"[@{APPCOMMAND_CMD_TEXT}='{app_command_cmd.cmd_text}']"
1985
- )
1986
- # Text of name tag in query was added as attribute to response
1987
- if app_command_cmd.name:
1988
- string = string + f"[@{APPCOMMAND_NAME}='{app_command_cmd.name}']"
1989
- # Some results include a zone tag
1990
- if resp.add_zone:
1991
- string = string + f"/{zone}"
1992
- # Suffix like /status, /volume
1993
- string = string + resp.suffix
1994
-
1995
- # A complete search string with all strributes set looks like
1996
- # ./cmd[@cmd_text={cmd_text}][@name={name}]/zone1/volume
1997
- result.append(string)
2172
+ _LOGGER.debug(
2173
+ "Some attributes of zone %s not found on update: %s",
2174
+ self._device.zone,
2175
+ update_attrs,
2176
+ )
2177
+
2178
+
2179
+ def create_appcommand_search_strings(
2180
+ app_command_cmd: AppCommandCmd, zone: str
2181
+ ) -> List[str]:
2182
+ """Create search pattern for AppCommand(0300).xml response."""
2183
+ result = []
2184
+
2185
+ zone_element = get_zone_element(zone)
2186
+
2187
+ for resp in app_command_cmd.response_pattern:
2188
+ string = "./cmd"
2189
+ # Text of cmd tag in query was added as attribute to response
2190
+ if app_command_cmd.cmd_text:
2191
+ string = string + f"[@{APPCOMMAND_CMD_TEXT}='{app_command_cmd.cmd_text}']"
2192
+ # Text of name tag in query was added as attribute to response
2193
+ if app_command_cmd.name:
2194
+ string = string + f"[@{APPCOMMAND_NAME}='{app_command_cmd.name}']"
2195
+ # Prefix like /list/listvalue/
2196
+ if resp.prefix:
2197
+ string = string + resp.prefix
2198
+ # Some results include a zone element
2199
+ if resp.add_zone:
2200
+ string = string + f"/{zone_element}"
2201
+ # If the result can be identified by a "zone" element in the same node
2202
+ if resp.search_zone_text:
2203
+ string = string + f"/zone[.='{zone}']/.."
2204
+ # Suffix like /status, /volume
2205
+ string = string + resp.suffix
2206
+
2207
+ # A complete search string with all attributes looks like
2208
+ # ./cmd[@cmd_text={cmd_text}][@name={name}]/zone1/volume
2209
+ result.append(string)
2210
+
2211
+ return result
2212
+
2213
+
2214
+ def get_zone_element(zone: str) -> str:
2215
+ """
2216
+ Get a zone element for evaluation of receiver XML responses.
1998
2217
 
1999
- return result
2218
+ This zone element are used to evaluate XML responses of HTTP POST
2219
+ commands.
2220
+ """
2221
+ if zone == MAIN_ZONE:
2222
+ return "zone1"
2223
+ return zone.lower()
2000
2224
 
2001
2225
 
2002
2226
  def set_api_host(
@@ -2023,7 +2247,7 @@ def set_api_timeout(
2023
2247
  return value
2024
2248
 
2025
2249
 
2026
- def convert_string_int_bool(value: Union[str, bool]) -> bool:
2250
+ def convert_string_int_bool(value: Union[str, bool]) -> Optional[bool]:
2027
2251
  """Convert an integer from string format to bool."""
2028
2252
  if value is None:
2029
2253
  return None