various improvements:

- image entity updates as expected
- add select options to device
This commit is contained in:
samuelspagl 2023-09-07 11:57:56 +02:00
parent b7ff6d1eb0
commit e010f0af50
7 changed files with 260 additions and 14 deletions

0
CHANGELOG.md Normal file
View File

View File

@ -18,7 +18,7 @@ from .models import DeviceConfig, SoundbarConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["media_player", "switch", "image", "number"] PLATFORMS = ["media_player", "switch", "image", "number", "select"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -1,9 +1,13 @@
import asyncio
import datetime
import json import json
import time import time
from urllib.parse import quote from urllib.parse import quote
import logging
from pysmartthings import DeviceEntity from pysmartthings import DeviceEntity
from ..const import DOMAIN
log = logging.getLogger(__name__)
class SoundbarDevice: class SoundbarDevice:
def __init__( def __init__(
@ -33,6 +37,7 @@ class SoundbarDevice:
self.__media_title = "" self.__media_title = ""
self.__media_artist = "" self.__media_artist = ""
self.__media_cover_url = "" self.__media_cover_url = ""
self.__media_cover_url_update_time: datetime.datetime | None = None
self.__old_media_title = "" self.__old_media_title = ""
self.__max_volume = max_volume self.__max_volume = max_volume
@ -55,13 +60,24 @@ class SoundbarDevice:
] ]
if self.__media_title != self.__old_media_title: if self.__media_title != self.__old_media_title:
self.__old_media_title = self.__media_title self.__old_media_title = self.__media_title
self.__media_cover_url_update_time = datetime.datetime.now()
self.__media_cover_url = await self.get_song_title_artwork( self.__media_cover_url = await self.get_song_title_artwork(
self.__media_artist, self.__media_title self.__media_artist, self.__media_title
) )
async def _update_soundmode(self): async def _update_soundmode(self):
await self.update_execution_data(["/sec/networkaudio/soundmode"]) await self.update_execution_data(["/sec/networkaudio/soundmode"])
await asyncio.sleep(0.1)
payload = await self.get_execute_status() payload = await self.get_execute_status()
retry = 0
while("x.com.samsung.networkaudio.supportedSoundmode" not in payload and retry < 10):
await asyncio.sleep(0.2)
payload = await self.get_execute_status()
retry += 1
if retry == 10:
log.error(f"[{DOMAIN}] Error: _update_soundmode exceeded a retry counter of 10")
return
self.__supported_soundmodes = payload[ self.__supported_soundmodes = payload[
"x.com.samsung.networkaudio.supportedSoundmode" "x.com.samsung.networkaudio.supportedSoundmode"
] ]
@ -69,13 +85,31 @@ class SoundbarDevice:
async def _update_woofer(self): async def _update_woofer(self):
await self.update_execution_data(["/sec/networkaudio/woofer"]) await self.update_execution_data(["/sec/networkaudio/woofer"])
await asyncio.sleep(0.1)
payload = await self.get_execute_status() payload = await self.get_execute_status()
retry = 0
while("x.com.samsung.networkaudio.woofer" not in payload and retry < 10):
await asyncio.sleep(0.2)
payload = await self.get_execute_status()
retry += 1
if retry == 10:
log.error(f"[{DOMAIN}] Error: _update_woofer exceeded a retry counter of 10")
return
self.__woofer_level = payload["x.com.samsung.networkaudio.woofer"] self.__woofer_level = payload["x.com.samsung.networkaudio.woofer"]
self.__woofer_connection = payload["x.com.samsung.networkaudio.connection"] self.__woofer_connection = payload["x.com.samsung.networkaudio.connection"]
async def _update_equalizer(self): async def _update_equalizer(self):
await self.update_execution_data(["/sec/networkaudio/eq"]) await self.update_execution_data(["/sec/networkaudio/eq"])
await asyncio.sleep(0.1)
payload = await self.get_execute_status() payload = await self.get_execute_status()
retry = 0
while("x.com.samsung.networkaudio.EQname" not in payload and retry < 10):
await asyncio.sleep(0.2)
payload = await self.get_execute_status()
retry += 1
if retry == 10:
log.error(f"[{DOMAIN}] Error: _update_equalizer exceeded a retry counter of 10")
return
self.__active_eq_preset = payload["x.com.samsung.networkaudio.EQname"] self.__active_eq_preset = payload["x.com.samsung.networkaudio.EQname"]
self.__supported_eq_presets = payload[ self.__supported_eq_presets = payload[
"x.com.samsung.networkaudio.supportedList" "x.com.samsung.networkaudio.supportedList"
@ -85,7 +119,18 @@ class SoundbarDevice:
async def _update_advanced_audio(self): async def _update_advanced_audio(self):
await self.update_execution_data(["/sec/networkaudio/advancedaudio"]) await self.update_execution_data(["/sec/networkaudio/advancedaudio"])
await asyncio.sleep(0.1)
payload = await self.get_execute_status() payload = await self.get_execute_status()
retry = 0
while("x.com.samsung.networkaudio.nightmode" not in payload and retry < 10):
await asyncio.sleep(0.2)
payload = await self.get_execute_status()
retry += 1
if retry == 10:
log.error(f"[{DOMAIN}] Error: _update_advanced_audio exceeded a retry counter of 10")
return
self.__night_mode = payload["x.com.samsung.networkaudio.nightmode"] self.__night_mode = payload["x.com.samsung.networkaudio.nightmode"]
self.__bass_mode = payload["x.com.samsung.networkaudio.bassboost"] self.__bass_mode = payload["x.com.samsung.networkaudio.bassboost"]
self.__voice_amplifier = payload["x.com.samsung.networkaudio.voiceamplifier"] self.__voice_amplifier = payload["x.com.samsung.networkaudio.voiceamplifier"]
@ -132,7 +177,10 @@ class SoundbarDevice:
@property @property
def volume_level(self) -> float: def volume_level(self) -> float:
return ((self.device.status.volume / 100) * self.__max_volume) / 100 vol = self.device.status.volume
if vol > self.__max_volume:
return 1.0
return self.device.status.volume / self.__max_volume
@property @property
def volume_muted(self) -> bool: def volume_muted(self) -> bool:
@ -144,7 +192,7 @@ class SoundbarDevice:
This respects the max volume and hovers between This respects the max volume and hovers between
:param volume: between 0 and 1 :param volume: between 0 and 1
""" """
await self.device.set_volume(int(volume * self.__max_volume)) await self.device.set_volume(int(volume * self.__max_volume), True)
async def mute_volume(self, mute: bool): async def mute_volume(self, mute: bool):
if mute: if mute:
@ -174,6 +222,7 @@ class SoundbarDevice:
property="x.com.samsung.networkaudio.woofer", property="x.com.samsung.networkaudio.woofer",
value=level, value=level,
) )
self.__woofer_level = level
# ------------ INPUT SOURCE ------------- # ------------ INPUT SOURCE -------------
@ -304,6 +353,10 @@ class SoundbarDevice:
return detail_status.value return detail_status.value
return None return None
@property
def media_coverart_updated(self) -> datetime.datetime:
return self.__media_cover_url_update_time
# ------------ SUPPORT FUNCTIONS ------------ # ------------ SUPPORT FUNCTIONS ------------
async def update_execution_data(self, argument: str): async def update_execution_data(self, argument: str):

View File

@ -1,8 +1,10 @@
import logging import logging
from datetime import datetime
from homeassistant.components.image import ImageEntity from homeassistant.components.image import ImageEntity
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import UndefinedType
from .models import DeviceConfig from .models import DeviceConfig
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice
@ -41,9 +43,22 @@ class SoundbarImageEntity(ImageEntity):
sw_version=self.__device.firmware_version, sw_version=self.__device.firmware_version,
) )
self._attr_image_url = self.__device.media_coverart_url self.__updated = None
# ---------- GENERAL --------------- # ---------- GENERAL ---------------
@property
def image_url(self) -> str | None | UndefinedType:
"""Return URL of image."""
return self.__device.media_coverart_url
@property
def image_last_updated(self) -> datetime | None:
"""The time when the image was last updated."""
current = self.__device.media_coverart_updated
if self.__updated != current:
self._cached_image = None
self.__updated = current
return current
@property @property
def name(self): def name(self):

View File

@ -1,4 +1,5 @@
import logging import logging
from typing import Mapping, Any
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
DEVICE_CLASS_SPEAKER, DEVICE_CLASS_SPEAKER,
@ -189,3 +190,7 @@ class SmartThingsSoundbarMediaPlayer(MediaPlayerEntity):
async def async_media_stop(self): async def async_media_stop(self):
await self.device.media_stop() await self.device.media_stop()
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
return self.device.retrieve_data

View File

@ -1,6 +1,6 @@
import logging import logging
from homeassistant.components.number import NumberEntity from homeassistant.components.number import NumberEntity, NumberEntityDescription, NumberMode
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from .models import DeviceConfig from .models import DeviceConfig
@ -22,9 +22,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
SoundbarNumberEntity( SoundbarNumberEntity(
device, device,
"woofer_level", "woofer_level",
device.woofer_level, lambda: device.woofer_level,
device.set_woofer, device.set_woofer,
(-6, 12), (-6, 12),
unit="dB",
mode=NumberMode.BOX
) )
) )
async_add_entities(entities) async_add_entities(entities)
@ -39,9 +41,19 @@ class SoundbarNumberEntity(NumberEntity):
state_function, state_function,
on_function, on_function,
min_max: tuple, min_max: tuple,
*,
unit: str = "%",
step_size: float = 1,
mode: NumberMode = NumberMode.SLIDER
): ):
self.entity_id = f"number.{device.device_name}_{append_unique_id}" self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = NumberEntityDescription(native_max_value=min_max[1],
native_min_value=min_max[0],
mode=mode,
native_step=step_size,
native_unit_of_measurement=unit,
key=append_unique_id,
)
self.__device = device self.__device = device
self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}" self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
@ -51,6 +63,7 @@ class SoundbarNumberEntity(NumberEntity):
model=self.__device.model, model=self.__device.model,
sw_version=self.__device.firmware_version, sw_version=self.__device.firmware_version,
) )
self.__append_unique_id = append_unique_id
self.__current_value_function = state_function self.__current_value_function = state_function
self.__set_value_function = on_function self.__set_value_function = on_function
@ -61,17 +74,14 @@ class SoundbarNumberEntity(NumberEntity):
@property @property
def name(self): def name(self):
return self.__device.device_name return self.__append_unique_id
# ------ STATE FUNCTIONS -------- # ------ STATE FUNCTIONS --------
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
return self.__current_value_function _LOGGER.info(f"[{DOMAIN}] Soundbar Woofer number value {self.__current_value_function()}")
return self.__current_value_function()
async def async_set_native_value(self, value: float): async def async_set_native_value(self, value: float):
if value > self.__max_value:
value = self.__min_value
if value < self.__min_value:
value = self.__min_value
await self.__set_value_function(value) await self.__set_value_function(value)

View File

@ -0,0 +1,163 @@
import logging
from homeassistant.components.number import NumberEntity, NumberEntityDescription, NumberMode
from homeassistant.components.select import SelectEntityDescription, SelectEntity
from homeassistant.helpers.entity import DeviceInfo
from .models import DeviceConfig
from .api_extension.SoundbarDevice import SoundbarDevice
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
domain_data = hass.data[DOMAIN]
entities = []
for key in domain_data.devices:
device_config: DeviceConfig = domain_data.devices[key]
device = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
entities.append(
EqPresetSelectEntity(
device,
"eq_preset",
)
)
entities.append(
SoundModeSelectEntity(
device,
"sound_mode_preset",
)
)
entities.append(
InputSelectEntity(
device,
"input_preset",
)
)
async_add_entities(entities)
return True
class EqPresetSelectEntity(SelectEntity):
def __init__(
self,
device: SoundbarDevice,
append_unique_id: str,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = SelectEntityDescription(key=append_unique_id,
)
self.__device = device
self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.__device.device_id)},
name=self.__device.device_name,
manufacturer=self.__device.manufacturer,
model=self.__device.model,
sw_version=self.__device.firmware_version,
)
self.__append_unique_id = append_unique_id
self._attr_options = self.__device.supported_equalizer_presets
# ---------- GENERAL ---------------
@property
def name(self):
return self.__append_unique_id
# ------ STATE FUNCTIONS --------
@property
def current_option(self) -> str | None:
"""Get the current status of the select entity from device_status."""
return self.__device.active_equalizer_preset
async def async_select_option(self, option: str) -> None:
"""Set the option."""
await self.__device.set_equalizer_preset(option)
class SoundModeSelectEntity(SelectEntity):
def __init__(
self,
device: SoundbarDevice,
append_unique_id: str,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = SelectEntityDescription(key=append_unique_id,
)
self.__device = device
self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.__device.device_id)},
name=self.__device.device_name,
manufacturer=self.__device.manufacturer,
model=self.__device.model,
sw_version=self.__device.firmware_version,
)
self.__append_unique_id = append_unique_id
self._attr_options = self.__device.supported_soundmodes
# ---------- GENERAL ---------------
@property
def name(self):
return self.__append_unique_id
# ------ STATE FUNCTIONS --------
@property
def current_option(self) -> str | None:
"""Get the current status of the select entity from device_status."""
return self.__device.sound_mode
async def async_select_option(self, option: str) -> None:
"""Set the option."""
await self.__device.select_sound_mode(option)
class InputSelectEntity(SelectEntity):
def __init__(
self,
device: SoundbarDevice,
append_unique_id: str,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = SelectEntityDescription(key=append_unique_id,
)
self.__device = device
self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.__device.device_id)},
name=self.__device.device_name,
manufacturer=self.__device.manufacturer,
model=self.__device.model,
sw_version=self.__device.firmware_version,
)
self.__append_unique_id = append_unique_id
self._attr_options = self.__device.supported_input_sources
# ---------- GENERAL ---------------
@property
def name(self):
return self.__append_unique_id
# ------ STATE FUNCTIONS --------
@property
def current_option(self) -> str | None:
"""Get the current status of the select entity from device_status."""
return self.__device.input_source
async def async_select_option(self, option: str) -> None:
"""Set the option."""
await self.__device.select_source(option)