denonavr 0.11.3__py3-none-any.whl → 0.11.6__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
@@ -10,8 +10,9 @@ This module implements the foundation classes for Denon AVR receivers.
10
10
  import asyncio
11
11
  import logging
12
12
  import xml.etree.ElementTree as ET
13
+ from collections.abc import Hashable
13
14
  from copy import deepcopy
14
- from typing import Dict, Hashable, List, Optional
15
+ from typing import Dict, List, Optional, Union
15
16
 
16
17
  import attr
17
18
  import httpx
@@ -119,7 +120,7 @@ class DenonAVRDeviceInfo:
119
120
  self.telnet_commands = ZONE3_TELNET_COMMANDS
120
121
  self.urls = ZONE3_URLS
121
122
  else:
122
- raise ValueError("Invalid zone {}".format(self.zone))
123
+ raise ValueError(f"Invalid zone {self.zone}")
123
124
 
124
125
  async def _async_power_callback(
125
126
  self, zone: str, event: str, parameter: str
@@ -179,7 +180,7 @@ class DenonAVRDeviceInfo:
179
180
 
180
181
  async def async_identify_receiver(self) -> None:
181
182
  """Identify receiver asynchronously."""
182
- # Test Deviceinfo.xml if receiver is a AVR-X with port 80 for pre 2016
183
+ # Test Deviceinfo.xml if receiver is an AVR-X with port 80 for pre 2016
183
184
  # devices and port 8080 devices 2016 and later
184
185
  # 2016 models has also some of the XML but not all, try first 2016
185
186
  r_types = [AVR_X, AVR_X_2016]
@@ -200,15 +201,17 @@ class DenonAVRDeviceInfo:
200
201
  exc_info=err,
201
202
  )
202
203
 
203
- # Raise error only when occured at both types
204
+ # Raise error only when occurred at both types
204
205
  timeout_errors += 1
205
206
  if timeout_errors == len(r_types):
206
207
  raise
207
208
 
208
209
  except AvrRequestError as err:
209
210
  _LOGGER.debug(
210
- "Request error on port %s when identifying receiver, "
211
- "device is not a %s receivers",
211
+ (
212
+ "Request error on port %s when identifying receiver, "
213
+ "device is not a %s receivers"
214
+ ),
212
215
  r_type.port,
213
216
  r_type.type,
214
217
  exc_info=err,
@@ -220,13 +223,13 @@ class DenonAVRDeviceInfo:
220
223
  # Receiver identified, return
221
224
  return
222
225
 
223
- # If check of Deviceinfo.xml was not successfull, receiver is type AVR
226
+ # If check of Deviceinfo.xml was not successful, receiver is type AVR
224
227
  self.receiver = AVR
225
228
  self.api.port = AVR.port
226
229
 
227
230
  @staticmethod
228
231
  def _is_avr_x(deviceinfo: ET.Element) -> bool:
229
- """Evaluate Deviceinfo.xml if the device is a AVR-X device."""
232
+ """Evaluate Deviceinfo.xml if the device is an AVR-X device."""
230
233
  # First test by CommApiVers
231
234
  try:
232
235
  if bool(
@@ -369,9 +372,7 @@ class DenonAVRDeviceInfo:
369
372
  """Get device information."""
370
373
  port = DESCRIPTION_TYPES[self.receiver.type].port
371
374
  command = DESCRIPTION_TYPES[self.receiver.type].url
372
- url = "http://{host}:{port}{command}".format(
373
- host=self.api.host, port=port, command=command
374
- )
375
+ url = f"http://{self.api.host}:{port}{command}"
375
376
 
376
377
  device_info = None
377
378
  try:
@@ -384,8 +385,10 @@ class DenonAVRDeviceInfo:
384
385
  raise
385
386
  except AvrRequestError as err:
386
387
  _LOGGER.error(
387
- "During DenonAVR device identification, when trying to request"
388
- " %s the following error occurred: %s",
388
+ (
389
+ "During DenonAVR device identification, when trying to request"
390
+ " %s the following error occurred: %s"
391
+ ),
389
392
  url,
390
393
  err,
391
394
  )
@@ -397,9 +400,11 @@ class DenonAVRDeviceInfo:
397
400
  self.model_name = "Unknown"
398
401
  self.serial_number = None
399
402
  _LOGGER.warning(
400
- "Unable to get device information of host %s, Device might be "
401
- "in a corrupted state. Continuing without device information. "
402
- "Disconnect and reconnect power to the device and try again.",
403
+ (
404
+ "Unable to get device information of host %s, Device might be "
405
+ "in a corrupted state. Continuing without device information. "
406
+ "Disconnect and reconnect power to the device and try again."
407
+ ),
403
408
  self.api.host,
404
409
  )
405
410
  return
@@ -447,14 +452,12 @@ class DenonAVRDeviceInfo:
447
452
 
448
453
  # Search for power tag
449
454
  power_tag = xml.find(
450
- "./cmd[@{attribute}='{cmd}']/{zone}".format(
451
- attribute=APPCOMMAND_CMD_TEXT, cmd=power_appcommand.cmd_text, zone=zone
452
- )
455
+ f"./cmd[@{APPCOMMAND_CMD_TEXT}='{power_appcommand.cmd_text}']/{zone}"
453
456
  )
454
457
 
455
458
  if power_tag is None:
456
459
  raise AvrProcessingError(
457
- "Power attribute of zone {} not found on update".format(self.zone)
460
+ f"Power attribute of zone {self.zone} not found on update"
458
461
  )
459
462
 
460
463
  self._power = power_tag.text
@@ -468,7 +471,7 @@ class DenonAVRDeviceInfo:
468
471
  if self.zone == MAIN_ZONE:
469
472
  urls.append(self.urls.mainzone)
470
473
  else:
471
- urls.append("{}?ZoneName={}".format(self.urls.mainzone, self.zone))
474
+ urls.append(f"{self.urls.mainzone}?ZoneName={self.zone}")
472
475
  # Tags in XML which might contain information about zones power status
473
476
  # ordered by their priority
474
477
  tags = ["./ZonePower/value", "./Power/value"]
@@ -490,7 +493,7 @@ class DenonAVRDeviceInfo:
490
493
  return
491
494
 
492
495
  raise AvrProcessingError(
493
- "Power attribute of zone {} not found on update".format(self.zone)
496
+ f"Power attribute of zone {self.zone} not found on update"
494
497
  )
495
498
 
496
499
  ##############
@@ -505,22 +508,31 @@ class DenonAVRDeviceInfo:
505
508
  """
506
509
  return self._power
507
510
 
511
+ @property
512
+ def telnet_available(self) -> bool:
513
+ """Return true if telnet is connected and healthy."""
514
+ return self.telnet_api.connected and self.telnet_api.healthy
515
+
508
516
  ##########
509
517
  # Setter #
510
518
  ##########
511
519
 
512
520
  async def async_power_on(self) -> None:
513
521
  """Turn on receiver via HTTP get command."""
514
- success = self.telnet_api.send_commands(self.telnet_commands.command_power_on)
515
- if not success:
522
+ if self.telnet_available:
523
+ await self.telnet_api.async_send_commands(
524
+ self.telnet_commands.command_power_on
525
+ )
526
+ else:
516
527
  await self.api.async_get_command(self.urls.command_power_on)
517
528
 
518
529
  async def async_power_off(self) -> None:
519
530
  """Turn off receiver via HTTP get command."""
520
- success = self.telnet_api.send_commands(
521
- self.telnet_commands.command_power_standby
522
- )
523
- if not success:
531
+ if self.telnet_available:
532
+ await self.telnet_api.async_send_commands(
533
+ self.telnet_commands.command_power_standby
534
+ )
535
+ else:
524
536
  await self.api.async_get_command(self.urls.command_power_standby)
525
537
 
526
538
 
@@ -617,9 +629,8 @@ class DenonAVRFoundation:
617
629
  # Check if each attribute was updated
618
630
  if update_attrs and not ignore_missing_response:
619
631
  raise AvrProcessingError(
620
- "Some attributes of zone {} not found on update: {}".format(
621
- self._device.zone, update_attrs
622
- )
632
+ f"Some attributes of zone {self._device.zone} not found on update:"
633
+ f" {update_attrs}"
623
634
  )
624
635
  if update_attrs and ignore_missing_response:
625
636
  _LOGGER.debug(
@@ -685,9 +696,8 @@ class DenonAVRFoundation:
685
696
  # Check if each attribute was updated
686
697
  if update_attrs and not ignore_missing_response:
687
698
  raise AvrProcessingError(
688
- "Some attributes of zone {} not found on update: {}".format(
689
- self._device.zone, update_attrs
690
- )
699
+ f"Some attributes of zone {self._device.zone} not found on update:"
700
+ f" {update_attrs}"
691
701
  )
692
702
 
693
703
  @staticmethod
@@ -701,17 +711,15 @@ class DenonAVRFoundation:
701
711
  string = "./cmd"
702
712
  # Text of cmd tag in query was added as attribute to response
703
713
  if app_command_cmd.cmd_text:
704
- string = string + "[@{}='{}']".format(
705
- APPCOMMAND_CMD_TEXT, app_command_cmd.cmd_text
714
+ string = (
715
+ string + f"[@{APPCOMMAND_CMD_TEXT}='{app_command_cmd.cmd_text}']"
706
716
  )
707
717
  # Text of name tag in query was added as attribute to response
708
718
  if app_command_cmd.name:
709
- string = string + "[@{}='{}']".format(
710
- APPCOMMAND_NAME, app_command_cmd.name
711
- )
719
+ string = string + f"[@{APPCOMMAND_NAME}='{app_command_cmd.name}']"
712
720
  # Some results include a zone tag
713
721
  if resp.add_zone:
714
- string = string + "/{}".format(zone)
722
+ string = string + f"/{zone}"
715
723
  # Suffix like /status, /volume
716
724
  string = string + resp.suffix
717
725
 
@@ -745,8 +753,10 @@ def set_api_timeout(
745
753
  return value
746
754
 
747
755
 
748
- def convert_string_int_bool(value: str) -> bool:
756
+ def convert_string_int_bool(value: Union[str, bool]) -> bool:
749
757
  """Convert an integer from string format to bool."""
750
758
  if value is None:
751
759
  return None
760
+ if isinstance(value, bool):
761
+ return value
752
762
  return bool(int(value))
denonavr/input.py CHANGED
@@ -8,13 +8,14 @@ This module implements the handler for input functions of Denon AVR receivers.
8
8
  """
9
9
 
10
10
  import asyncio
11
- import html
12
11
  import logging
12
+ from collections.abc import Hashable
13
13
  from copy import deepcopy
14
- from typing import Dict, Hashable, List, Optional, Set, Tuple
14
+ from typing import Dict, List, Optional, Set, Tuple
15
15
 
16
16
  import attr
17
17
  import httpx
18
+ from ftfy import fix_text
18
19
 
19
20
  from .appcommand import AppCommands
20
21
  from .const import (
@@ -56,11 +57,11 @@ def lower_string(value: Optional[str]) -> Optional[str]:
56
57
  return str(value).lower()
57
58
 
58
59
 
59
- def unescape_string(value: Optional[str]) -> Optional[str]:
60
- """Perform HTML unescape on value."""
60
+ def fix_string(value: Optional[str]) -> Optional[str]:
61
+ """Fix errors in string like unescaped HTML and wrong utf-8 encoding."""
61
62
  if value is None:
62
63
  return value
63
- return html.unescape(str(value)).strip("\x00").strip("\x01").strip()
64
+ return fix_text(str(value)).strip()
64
65
 
65
66
 
66
67
  def set_input_func(
@@ -143,22 +144,22 @@ class DenonAVRInput(DenonAVRFoundation):
143
144
  )
144
145
 
145
146
  _artist: Optional[str] = attr.ib(
146
- converter=attr.converters.optional(unescape_string), default=None
147
+ converter=attr.converters.optional(fix_string), default=None
147
148
  )
148
149
  _album: Optional[str] = attr.ib(
149
- converter=attr.converters.optional(unescape_string), default=None
150
+ converter=attr.converters.optional(fix_string), default=None
150
151
  )
151
152
  _band: Optional[str] = attr.ib(
152
- converter=attr.converters.optional(unescape_string), default=None
153
+ converter=attr.converters.optional(fix_string), default=None
153
154
  )
154
155
  _title: Optional[str] = attr.ib(
155
- converter=attr.converters.optional(unescape_string), default=None
156
+ converter=attr.converters.optional(fix_string), default=None
156
157
  )
157
158
  _frequency: Optional[str] = attr.ib(
158
- converter=attr.converters.optional(unescape_string), default=None
159
+ converter=attr.converters.optional(fix_string), default=None
159
160
  )
160
161
  _station: Optional[str] = attr.ib(
161
- converter=attr.converters.optional(unescape_string), default=None
162
+ converter=attr.converters.optional(fix_string), default=None
162
163
  )
163
164
  _image_url: Optional[str] = attr.ib(
164
165
  converter=attr.converters.optional(str), default=None
@@ -271,7 +272,8 @@ class DenonAVRInput(DenonAVRFoundation):
271
272
 
272
273
  def _update_netaudio(self) -> None:
273
274
  """Update netaudio information."""
274
- if self._device.telnet_api.send_commands("NSE"):
275
+ if self._device.telnet_available:
276
+ self._device.telnet_api.send_commands("NSE")
275
277
  self._schedule_netaudio_update()
276
278
  else:
277
279
  self._stop_media_update()
@@ -301,7 +303,7 @@ class DenonAVRInput(DenonAVRFoundation):
301
303
  port=self._device.api.port,
302
304
  hash=hash((self._title, self._artist, self._album)),
303
305
  )
304
- await self._async_test_image_accessable()
306
+ await self._async_test_image_accessible()
305
307
 
306
308
  def _schedule_tuner_update(self) -> None:
307
309
  """Schedule a tuner update task."""
@@ -313,7 +315,8 @@ class DenonAVRInput(DenonAVRFoundation):
313
315
 
314
316
  def _update_tuner(self) -> None:
315
317
  """Update tuner information."""
316
- if self._device.telnet_api.send_commands("TFAN?", "TFANNAME?"):
318
+ if self._device.telnet_available:
319
+ self._device.telnet_api.send_commands("TFAN?", "TFANNAME?")
317
320
  self._schedule_tuner_update()
318
321
  else:
319
322
  self._stop_media_update()
@@ -330,7 +333,7 @@ class DenonAVRInput(DenonAVRFoundation):
330
333
  if parameter.startswith("ANNAME"):
331
334
  self._station = parameter[6:]
332
335
  elif len(parameter) == 8:
333
- self._frequency = "{}.{}".format(parameter[2:6], parameter[6:]).strip("0")
336
+ self._frequency = f"{parameter[2:6]}.{parameter[6:]}".strip("0")
334
337
  if parameter[2:] > "050000":
335
338
  self._band = "AM"
336
339
  else:
@@ -344,7 +347,7 @@ class DenonAVRInput(DenonAVRFoundation):
344
347
  self._image_url = STATIC_ALBUM_URL.format(
345
348
  host=self._device.api.host, port=self._device.api.port
346
349
  )
347
- await self._async_test_image_accessable()
350
+ await self._async_test_image_accessible()
348
351
 
349
352
  def _schedule_hdtuner_update(self) -> None:
350
353
  """Schedule a HD tuner update task."""
@@ -356,7 +359,8 @@ class DenonAVRInput(DenonAVRFoundation):
356
359
 
357
360
  def _update_hdtuner(self) -> None:
358
361
  """Update HD tuner information."""
359
- if self._device.telnet_api.send_commands("HD?"):
362
+ if self._device.telnet_available:
363
+ self._device.telnet_api.send_commands("HD?")
360
364
  self._schedule_hdtuner_update()
361
365
  else:
362
366
  self._stop_media_update()
@@ -386,7 +390,7 @@ class DenonAVRInput(DenonAVRFoundation):
386
390
  self._image_url = STATIC_ALBUM_URL.format(
387
391
  host=self._device.api.host, port=self._device.api.port
388
392
  )
389
- await self._async_test_image_accessable()
393
+ await self._async_test_image_accessible()
390
394
 
391
395
  async def _async_input_func_update_callback(
392
396
  self, zone: str, event: str, parameter: str
@@ -447,9 +451,9 @@ class DenonAVRInput(DenonAVRFoundation):
447
451
  # Get list of all input sources of receiver
448
452
  xml_list = xml_zonecapa.find("./InputSource/List")
449
453
  for xml_source in xml_list.findall("Source"):
450
- receiver_sources[
451
- xml_source.find("FuncName").text
452
- ] = xml_source.find("DefaultName").text
454
+ receiver_sources[xml_source.find("FuncName").text] = (
455
+ xml_source.find("DefaultName").text
456
+ )
453
457
 
454
458
  return receiver_sources
455
459
 
@@ -484,9 +488,8 @@ class DenonAVRInput(DenonAVRFoundation):
484
488
  raise
485
489
 
486
490
  for child in xml.findall(
487
- "./cmd[@{attribute}='{cmd}']/functionrename/list".format(
488
- attribute=APPCOMMAND_CMD_TEXT, cmd=AppCommands.GetRenameSource.cmd_text
489
- )
491
+ f"./cmd[@{APPCOMMAND_CMD_TEXT}='{AppCommands.GetRenameSource.cmd_text}']"
492
+ "/functionrename/list"
490
493
  ):
491
494
  try:
492
495
  renamed_sources[child.find("name").text.strip()] = child.find(
@@ -496,9 +499,8 @@ class DenonAVRInput(DenonAVRFoundation):
496
499
  continue
497
500
 
498
501
  for child in xml.findall(
499
- "./cmd[@{attribute}='{cmd}']/functiondelete/list".format(
500
- attribute=APPCOMMAND_CMD_TEXT, cmd=AppCommands.GetDeletedSource.cmd_text
501
- )
502
+ f"./cmd[@{APPCOMMAND_CMD_TEXT}='{AppCommands.GetDeletedSource.cmd_text}']"
503
+ "/functiondelete/list"
502
504
  ):
503
505
  try:
504
506
  deleted_sources[child.find("FuncName").text.strip()] = (
@@ -541,9 +543,8 @@ class DenonAVRInput(DenonAVRFoundation):
541
543
  )
542
544
  else:
543
545
  raise AvrProcessingError(
544
- "Method does not work for receiver type {}".format(
545
- self._device.receiver.type
546
- )
546
+ "Method does not work for receiver type"
547
+ f" {self._device.receiver.type}"
547
548
  )
548
549
  except AvrRequestError as err:
549
550
  _LOGGER.debug("Error when getting changed sources", exc_info=err)
@@ -668,7 +669,7 @@ class DenonAVRInput(DenonAVRFoundation):
668
669
  playing_func_list = []
669
670
 
670
671
  for item in receiver_sources.items():
671
- # Mapping of item[0] because some func names are inconsistant
672
+ # Mapping of item[0] because some func names are inconsistent
672
673
  # at AVR-X receivers
673
674
 
674
675
  m_item_0 = SOURCE_MAPPING.get(item[0], item[0])
@@ -846,10 +847,10 @@ class DenonAVRInput(DenonAVRFoundation):
846
847
  # On track change assume device is PLAYING
847
848
  self._state = STATE_PLAYING
848
849
 
849
- await self._async_test_image_accessable()
850
+ await self._async_test_image_accessible()
850
851
 
851
- async def _async_test_image_accessable(self) -> None:
852
- """Test if image URL is accessable."""
852
+ async def _async_test_image_accessible(self) -> None:
853
+ """Test if image URL is accessible."""
853
854
  if self._image_available is None and self._image_url is not None:
854
855
  client = self._device.api.async_client_getter()
855
856
  try:
@@ -995,7 +996,7 @@ class DenonAVRInput(DenonAVRFoundation):
995
996
  linp = self._input_func_map[input_func]
996
997
  except KeyError as err:
997
998
  raise AvrCommandError(
998
- "No mapping for input source {}".format(input_func)
999
+ f"No mapping for input source {input_func}"
999
1000
  ) from err
1000
1001
  # Create command URL and send command via HTTP GET
1001
1002
  if linp in self._favorite_func_list:
@@ -1004,8 +1005,9 @@ class DenonAVRInput(DenonAVRFoundation):
1004
1005
  else:
1005
1006
  command_url = self._device.urls.command_sel_src + linp
1006
1007
  telnet_command = self._device.telnet_commands.command_sel_src + linp
1007
- success = self._device.telnet_api.send_commands(telnet_command)
1008
- if not success:
1008
+ if self._device.telnet_available:
1009
+ await self._device.telnet_api.async_send_commands(telnet_command)
1010
+ else:
1009
1011
  await self._device.api.async_get_command(command_url)
1010
1012
 
1011
1013
  async def async_toggle_play_pause(self) -> None:
@@ -1027,10 +1029,11 @@ class DenonAVRInput(DenonAVRFoundation):
1027
1029
  if self._state == STATE_PLAYING:
1028
1030
  _LOGGER.info("Already playing, play command not sent")
1029
1031
  return
1030
- success = self._device.telnet_api.send_commands(
1031
- self._device.telnet_commands.command_play
1032
- )
1033
- if not success:
1032
+ if self._device.telnet_available:
1033
+ await self._device.telnet_api.async_send_commands(
1034
+ self._device.telnet_commands.command_play
1035
+ )
1036
+ else:
1034
1037
  await self._device.api.async_get_command(self._device.urls.command_play)
1035
1038
  self._state = STATE_PLAYING
1036
1039
 
@@ -1038,10 +1041,11 @@ class DenonAVRInput(DenonAVRFoundation):
1038
1041
  """Send pause command to receiver command via HTTP post."""
1039
1042
  # Use pause command only for sources which support NETAUDIO
1040
1043
  if self._input_func in self._netaudio_func_list:
1041
- success = self._device.telnet_api.send_commands(
1042
- self._device.telnet_commands.command_play
1043
- )
1044
- if not success:
1044
+ if self._device.telnet_available:
1045
+ await self._device.telnet_api.async_send_commands(
1046
+ self._device.telnet_commands.command_play
1047
+ )
1048
+ else:
1045
1049
  await self._device.api.async_get_command(
1046
1050
  self._device.urls.command_pause
1047
1051
  )
denonavr/soundmode.py CHANGED
@@ -9,8 +9,9 @@ This module implements the handler for sound mode of Denon AVR receivers.
9
9
 
10
10
  import asyncio
11
11
  import logging
12
+ from collections.abc import Hashable
12
13
  from copy import deepcopy
13
- from typing import Dict, Hashable, List, Optional
14
+ from typing import Dict, List, Optional
14
15
 
15
16
  import attr
16
17
 
@@ -22,7 +23,7 @@ from .const import (
22
23
  DENON_ATTR_SETATTR,
23
24
  SOUND_MODE_MAPPING,
24
25
  )
25
- from .exceptions import AvrProcessingError
26
+ from .exceptions import AvrCommandError, AvrProcessingError
26
27
  from .foundation import DenonAVRFoundation
27
28
 
28
29
  _LOGGER = logging.getLogger(__name__)
@@ -109,7 +110,7 @@ class DenonAVRSoundMode(DenonAVRFoundation):
109
110
  self._device.api.add_appcommand_update_tag(tag)
110
111
 
111
112
  # Soundmode is always available for AVR-X and AVR-X-2016 receivers
112
- # For AVR receiver it will be tested druing the first update
113
+ # For AVR receiver it will be tested during the first update
113
114
  if self._device.receiver in [AVR_X, AVR_X_2016]:
114
115
  self._support_sound_mode = True
115
116
  else:
@@ -237,8 +238,9 @@ class DenonAVRSoundMode(DenonAVRFoundation):
237
238
  else:
238
239
  command_url += "ZST OFF"
239
240
  telnet_command += "ZST OFF"
240
- success = self._device.telnet_api.send_commands(telnet_command)
241
- if not success:
241
+ if self._device.telnet_available:
242
+ await self._device.telnet_api.async_send_commands(telnet_command)
243
+ else:
242
244
  await self._device.api.async_get_command(command_url)
243
245
 
244
246
  ##############
@@ -256,7 +258,7 @@ class DenonAVRSoundMode(DenonAVRFoundation):
256
258
  return sound_mode_matched
257
259
 
258
260
  @property
259
- def sound_mode_list(self) -> None:
261
+ def sound_mode_list(self) -> List[str]:
260
262
  """Return a list of available sound modes as string."""
261
263
  return list(self._sound_mode_map.keys())
262
264
 
@@ -285,6 +287,9 @@ class DenonAVRSoundMode(DenonAVRFoundation):
285
287
  Valid values depend on the device and should be taken from
286
288
  "sound_mode_list".
287
289
  """
290
+ if sound_mode not in self.sound_mode_list:
291
+ raise AvrCommandError(f"{sound_mode} is not a valid sound mode")
292
+
288
293
  if sound_mode == ALL_ZONE_STEREO:
289
294
  await self._async_set_all_zone_stereo(True)
290
295
  return
@@ -300,8 +305,9 @@ class DenonAVRSoundMode(DenonAVRFoundation):
300
305
  self._device.telnet_commands.command_sel_sound_mode + sound_mode
301
306
  )
302
307
  # sent command
303
- success = self._device.telnet_api.send_commands(telnet_command)
304
- if not success:
308
+ if self._device.telnet_available:
309
+ await self._device.telnet_api.async_send_commands(telnet_command)
310
+ else:
305
311
  await self._device.api.async_get_command(command_url)
306
312
 
307
313
 
denonavr/ssdp.py CHANGED
@@ -35,14 +35,14 @@ SSDP_ST_LIST = (SSDP_ST_1, SSDP_ST_2, SSDP_ST_3)
35
35
  SSDP_LOCATION_PATTERN = re.compile(r"(?<=LOCATION:\s).+?(?=\r)")
36
36
 
37
37
  SCPD_XMLNS = "{urn:schemas-upnp-org:device-1-0}"
38
- SCPD_DEVICE = "{xmlns}device".format(xmlns=SCPD_XMLNS)
39
- SCPD_DEVICELIST = "{xmlns}deviceList".format(xmlns=SCPD_XMLNS)
40
- SCPD_DEVICETYPE = "{xmlns}deviceType".format(xmlns=SCPD_XMLNS)
41
- SCPD_MANUFACTURER = "{xmlns}manufacturer".format(xmlns=SCPD_XMLNS)
42
- SCPD_MODELNAME = "{xmlns}modelName".format(xmlns=SCPD_XMLNS)
43
- SCPD_SERIALNUMBER = "{xmlns}serialNumber".format(xmlns=SCPD_XMLNS)
44
- SCPD_FRIENDLYNAME = "{xmlns}friendlyName".format(xmlns=SCPD_XMLNS)
45
- SCPD_PRESENTATIONURL = "{xmlns}presentationURL".format(xmlns=SCPD_XMLNS)
38
+ SCPD_DEVICE = f"{SCPD_XMLNS}device"
39
+ SCPD_DEVICELIST = f"{SCPD_XMLNS}deviceList"
40
+ SCPD_DEVICETYPE = f"{SCPD_XMLNS}deviceType"
41
+ SCPD_MANUFACTURER = f"{SCPD_XMLNS}manufacturer"
42
+ SCPD_MODELNAME = f"{SCPD_XMLNS}modelName"
43
+ SCPD_SERIALNUMBER = f"{SCPD_XMLNS}serialNumber"
44
+ SCPD_FRIENDLYNAME = f"{SCPD_XMLNS}friendlyName"
45
+ SCPD_PRESENTATIONURL = f"{SCPD_XMLNS}presentationURL"
46
46
 
47
47
  SUPPORTED_DEVICETYPES = [
48
48
  "urn:schemas-upnp-org:device:MediaRenderer:1",
@@ -57,10 +57,10 @@ def ssdp_request(ssdp_st: str, ssdp_mx: float = SSDP_MX) -> bytes:
57
57
  return "\r\n".join(
58
58
  [
59
59
  "M-SEARCH * HTTP/1.1",
60
- "ST: {}".format(ssdp_st),
61
- "MX: {:d}".format(ssdp_mx),
60
+ f"ST: {ssdp_st}",
61
+ f"MX: {ssdp_mx:d}",
62
62
  'MAN: "ssdp:discover"',
63
- "HOST: {}:{}".format(*SSDP_TARGET),
63
+ f"HOST: {SSDP_ADDR}:{SSDP_PORT}",
64
64
  "",
65
65
  "",
66
66
  ]
@@ -133,8 +133,8 @@ async def async_send_ssdp_broadcast() -> Set[str]:
133
133
 
134
134
  async def async_send_ssdp_broadcast_ip(ip_addr: str) -> Set[str]:
135
135
  """Send SSDP broadcast messages to a single IP."""
136
- # Ignore 169.254.0.0/16 adresses
137
- if re.search("169.254.*.*", ip_addr):
136
+ # Ignore 169.254.0.0/16 addresses
137
+ if ip_addr.startswith("169.254."):
138
138
  return set()
139
139
 
140
140
  # Prepare socket