various improvements:
- image entity updates as expected - add select options to device
This commit is contained in:
parent
b7ff6d1eb0
commit
e010f0af50
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue