Compare commits

...

9 Commits

Author SHA1 Message Date
samuelspagl 618e89b19f Add translations 2024-05-20 21:07:55 +02:00
samuelspagl 97ae0615b3 Add service calls for enabling/disabling ActiveVoiceAmplifier and SpaceFitSound 2024-05-20 21:02:21 +02:00
samuelspagl 4839f403e8 Add service calls for setting individual speaker levels and setting the rear speaker mode 2024-05-20 20:40:39 +02:00
samuelspagl c6e96d0754 Install ruff 2024-05-20 20:38:11 +02:00
samuelspagl 64c4b7aa34 Update Changelog 2024-04-05 14:29:49 +00:00
samuelspagl 252d3855ca 🚀 Add Service calls for custom capabilities 2024-04-05 14:27:33 +00:00
Samuel Spagl 08e8f2645d Add MediaPlayer Capabilities 2024-04-05 15:05:00 +02:00
samuelspagl 00eccac431 ⚠️ [working commit]: add configuration steps 2024-04-01 20:12:04 +00:00
samuelspagl 7216766068 add devcontainer 2024-04-01 20:09:25 +00:00
22 changed files with 1312 additions and 526 deletions

View File

@ -0,0 +1,32 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "homeassistant/home-assistant:dev",
"postCreateCommand": "scripts/setup",
"forwardPorts": [
8123
],
"portsAttributes": {
"8123": {
"label": "Home Assistant",
"onAutoForward": "notify"
}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

5
.gitignore vendored
View File

@ -4,6 +4,11 @@ __pycache__/
*$py.class *$py.class
.DS_store .DS_store
config
.vscode
.ruff_cache
# C extensions # C extensions
*.so *.so
.idea .idea

View File

@ -1,5 +1,28 @@
# Changelog # Changelog
## [0.4.0] Add more fine grained configuration flow
### Added
- Configuration flow options for enable / disable
- "advanced audio" features (NightMode, Bassmode, VoiceEnhancer)
- "woofer" feature
- "soundmode" feature
- "eq" feature
- added `media_player` support for next and previous track
- Service calls for:
- "advanced audio" features (NightMode, Bassmode, VoiceEnhancer)
- "woofer" feature
- "soundmode" feature
- "speaker_level"
- "rear_speaker_mode"
- "space_fit_sound"
- "active_voice_amplifier"
### Changed
- Fixed state, also displaying "playing" and "paused" values
## [0.3.2] Fix division by zero ## [0.3.2] Fix division by zero
### Added ### Added

View File

@ -9,8 +9,7 @@ rich = "*"
homeassistant = "*" homeassistant = "*"
[dev-packages] [dev-packages]
black = "*" ruff = "*"
isort = "*"
[requires] [requires]
python_version = "3.11" python_version = "3.12"

972
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,18 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from pysmartthings import SmartThings from pysmartthings import SmartThings
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice
from .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID, from .const import (
CONF_ENTRY_DEVICE_NAME, CONF_ENTRY_MAX_VOLUME, DOMAIN, CONF_ENTRY_API_KEY,
SUPPORTED_DOMAINS) CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME,
CONF_ENTRY_MAX_VOLUME,
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
CONF_ENTRY_SETTINGS_WOOFER_NUMBER,
DOMAIN,
SUPPORTED_DOMAINS,
)
from .models import DeviceConfig, SoundbarConfig from .models import DeviceConfig, SoundbarConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -21,7 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# store shell object # store shell object
_LOGGER.info(f"[{DOMAIN}] Starting to setup a ConfigEntry") _LOGGER.info(f"[{DOMAIN}] Starting to setup a ConfigEntry")
_LOGGER.debug(f"[{DOMAIN}] Setting up ConfigEntry with the following data: {entry.data}") _LOGGER.debug(
f"[{DOMAIN}] Setting up ConfigEntry with the following data: {entry.data}"
)
if not DOMAIN in hass.data: if not DOMAIN in hass.data:
_LOGGER.debug(f"[{DOMAIN}] Domain not found in hass.data setting default") _LOGGER.debug(f"[{DOMAIN}] Domain not found in hass.data setting default")
hass.data[DOMAIN] = SoundbarConfig( hass.data[DOMAIN] = SoundbarConfig(
@ -48,6 +59,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
session, session,
entry.data.get(CONF_ENTRY_MAX_VOLUME), entry.data.get(CONF_ENTRY_MAX_VOLUME),
entry.data.get(CONF_ENTRY_DEVICE_NAME), entry.data.get(CONF_ENTRY_DEVICE_NAME),
enable_eq=entry.data.get(CONF_ENTRY_SETTINGS_EQ_SELECTOR),
enable_advanced_audio=entry.data.get(
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES
),
enable_soundmode=entry.data.get(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR),
enable_woofer=entry.data.get(CONF_ENTRY_SETTINGS_WOOFER_NUMBER),
) )
await soundbar_device.update() await soundbar_device.update()
domain_config.devices[entry.data.get(CONF_ENTRY_DEVICE_ID)] = DeviceConfig( domain_config.devices[entry.data.get(CONF_ENTRY_DEVICE_ID)] = DeviceConfig(

View File

@ -2,11 +2,11 @@ import asyncio
import datetime import datetime
import json import json
import logging import logging
import time
from urllib.parse import quote from urllib.parse import quote
from pysmartthings import DeviceEntity from pysmartthings import DeviceEntity
from .const import SpeakerIdentifier, RearSpeakerMode
from ..const import DOMAIN from ..const import DOMAIN
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -14,7 +14,15 @@ log = logging.getLogger(__name__)
class SoundbarDevice: class SoundbarDevice:
def __init__( def __init__(
self, device: DeviceEntity, session, max_volume: int, device_name: str self,
device: DeviceEntity,
session,
max_volume: int,
device_name: str,
enable_eq: bool = False,
enable_soundmode: bool = False,
enable_advanced_audio: bool = False,
enable_woofer: bool = False,
): ):
self.device = device self.device = device
self._device_id = self.device.device_id self._device_id = self.device.device_id
@ -22,17 +30,21 @@ class SoundbarDevice:
self.__session = session self.__session = session
self.__device_name = device_name self.__device_name = device_name
self.__enable_soundmode = enable_soundmode
self.__supported_soundmodes = [] self.__supported_soundmodes = []
self.__active_soundmode = "" self.__active_soundmode = ""
self.__enable_woofer = enable_woofer
self.__woofer_level = 0 self.__woofer_level = 0
self.__woofer_connection = "" self.__woofer_connection = ""
self.__enable_eq = enable_eq
self.__active_eq_preset = "" self.__active_eq_preset = ""
self.__supported_eq_presets = [] self.__supported_eq_presets = []
self.__eq_action = "" self.__eq_action = ""
self.__eq_bands = [] self.__eq_bands = []
self.__enable_advanced_audio = enable_advanced_audio
self.__voice_amplifier = 0 self.__voice_amplifier = 0
self.__night_mode = 0 self.__night_mode = 0
self.__bass_mode = 0 self.__bass_mode = 0
@ -49,10 +61,15 @@ class SoundbarDevice:
await self.device.status.refresh() await self.device.status.refresh()
await self._update_media() await self._update_media()
await self._update_soundmode()
await self._update_advanced_audio() if self.__enable_soundmode:
await self._update_woofer() await self._update_soundmode()
await self._update_equalizer() if self.__enable_advanced_audio:
await self._update_advanced_audio()
if self.__enable_soundmode:
await self._update_woofer()
if self.__enable_eq:
await self._update_equalizer()
async def _update_media(self): async def _update_media(self):
self.__media_artist = self.device.status._attributes["audioTrackData"].value[ self.__media_artist = self.device.status._attributes["audioTrackData"].value[
@ -70,14 +87,14 @@ class SoundbarDevice:
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) await asyncio.sleep(1)
payload = await self.get_execute_status() payload = await self.get_execute_status()
retry = 0 retry = 0
while ( while (
"x.com.samsung.networkaudio.supportedSoundmode" not in payload "x.com.samsung.networkaudio.supportedSoundmode" not in payload
and retry < 10 and retry < 10
): ):
await asyncio.sleep(0.2) await asyncio.sleep(1)
payload = await self.get_execute_status() payload = await self.get_execute_status()
retry += 1 retry += 1
if retry == 10: if retry == 10:
@ -179,7 +196,15 @@ class SoundbarDevice:
@property @property
def state(self) -> str: def state(self) -> str:
return "on" if self.device.status.switch else "off" if self.device.status.switch:
if self.device.status.playback_status == "playing":
return "playing"
if self.device.status.playback_status == "paused":
return "paused"
else:
return "on"
else:
return "off"
async def switch_off(self): async def switch_off(self):
await self.device.switch_off(True) await self.device.switch_off(True)
@ -362,6 +387,12 @@ class SoundbarDevice:
async def media_stop(self): async def media_stop(self):
await self.device.stop(True) await self.device.stop(True)
async def media_next_track(self):
await self.device.command("main", "mediaPlayback", "fastForward")
async def media_previous_track(self):
await self.device.command("main", "mediaPlayback", "rewind")
@property @property
def media_app_name(self): def media_app_name(self):
detail_status = self.device.status.attributes.get("detailName", None) detail_status = self.device.status.attributes.get("detailName", None)
@ -373,21 +404,54 @@ class SoundbarDevice:
def media_coverart_updated(self) -> datetime.datetime: def media_coverart_updated(self) -> datetime.datetime:
return self.__media_cover_url_update_time return self.__media_cover_url_update_time
# ------------ Speaker Level ----------------
async def set_speaker_level(self, speaker: SpeakerIdentifier, level: int):
await self.set_custom_execution_data(
href="/sec/networkaudio/channelVolume",
property="x.com.samsung.networkaudio.channelVolume",
value=[{"name": speaker.value, "value": level}],
)
async def set_rear_speaker_mode(self, mode: RearSpeakerMode):
await self.set_custom_execution_data(
href="/sec/networkaudio/surroundspeaker",
property="x.com.samsung.networkaudio.currentRearPosition",
value=mode.value,
)
# ------------ OTHER FUNCTIONS ------------
async def set_active_voice_amplifier(self, enabled: bool):
await self.set_custom_execution_data(
href="/sec/networkaudio/activeVoiceAmplifier",
property="x.com.samsung.networkaudio.activeVoiceAmplifier",
value=1 if enabled else 0
)
async def set_space_fit_sound(self, enabled: bool):
await self.set_custom_execution_data(
href="/sec/networkaudio/spacefitSound",
property="x.com.samsung.networkaudio.spacefitSound",
value=1 if enabled else 0
)
# ------------ SUPPORT FUNCTIONS ------------ # ------------ SUPPORT FUNCTIONS ------------
async def update_execution_data(self, argument: str): async def update_execution_data(self, argument: str):
return await self.device.command("main", "execute", "execute", argument) stuff = await self.device.command("main", "execute", "execute", argument)
return stuff
async def set_custom_execution_data(self, href: str, property: str, value): async def set_custom_execution_data(self, href: str, property: str, value):
argument = [href, {property: value}] argument = [href, {property: value}]
await self.device.command("main", "execute", "execute", argument) assert await self.device.command("main", "execute", "execute", argument)
async def get_execute_status(self): async def get_execute_status(self):
url = f"https://api.smartthings.com/v1/devices/{self._device_id}/components/main/capabilities/execute/status" url = f"https://api.smartthings.com/v1/devices/{self._device_id}/components/main/capabilities/execute/status"
request_headers = {"Authorization": "Bearer " + self._api_key} request_headers = {"Authorization": "Bearer " + self._api_key}
resp = await self.__session.get(url, headers=request_headers) resp = await self.__session.get(url, headers=request_headers)
dict = await resp.json() dict_stuff = await resp.json()
return dict["data"]["value"]["payload"] return dict_stuff["data"]["value"]["payload"]
async def get_song_title_artwork(self, artist: str, title: str) -> str: async def get_song_title_artwork(self, artist: str, title: str) -> str:
""" """

View File

@ -0,0 +1,15 @@
from enum import Enum
class SpeakerIdentifier(Enum):
CENTER = "Spk_Center"
SIDE = "Spk_Side"
WIDE = "Spk_Wide"
FRONT_TOP = "Spk_Front_Top"
REAR = "Spk_Rear"
REAR_TOP = "Spk_Rear_Top"
class RearSpeakerMode(Enum):
FRONT = "Front"
REAR = "Rear"

View File

@ -1,4 +1,5 @@
import logging import logging
from typing import Any
import pysmartthings import pysmartthings
import voluptuous as vol import voluptuous as vol
@ -7,8 +8,17 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from pysmartthings import APIResponseError from pysmartthings import APIResponseError
from voluptuous import All, Range from voluptuous import All, Range
from .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID, from .const import (
CONF_ENTRY_DEVICE_NAME, CONF_ENTRY_MAX_VOLUME, DOMAIN) CONF_ENTRY_API_KEY,
CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME,
CONF_ENTRY_MAX_VOLUME,
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
CONF_ENTRY_SETTINGS_WOOFER_NUMBER,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,20 +34,8 @@ async def validate_input(api, device_id: str):
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
if user_input is not None: if user_input is not None:
try: self.user_input = user_input
session = async_get_clientsession(self.hass) return await self.async_step_device()
api = pysmartthings.SmartThings(
session, user_input.get(CONF_ENTRY_API_KEY)
)
device = await validate_input(api, user_input.get(CONF_ENTRY_DEVICE_ID))
_LOGGER.debug(
f"Successfully validated Input, Creating entry with title {DOMAIN} and data {user_input}"
)
return self.async_create_entry(title=DOMAIN, data=user_input)
except Exception as excp:
_LOGGER.error(f"The ConfigFlow triggered an exception {excp}")
return self.async_abort(reason="fetch_failed")
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
@ -46,7 +44,98 @@ class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
vol.Required(CONF_ENTRY_API_KEY): str, vol.Required(CONF_ENTRY_API_KEY): str,
vol.Required(CONF_ENTRY_DEVICE_ID): str, vol.Required(CONF_ENTRY_DEVICE_ID): str,
vol.Required(CONF_ENTRY_DEVICE_NAME): str, vol.Required(CONF_ENTRY_DEVICE_NAME): str,
vol.Required(CONF_ENTRY_MAX_VOLUME, default=100): All(int, Range(min=1, max=100)) vol.Required(CONF_ENTRY_MAX_VOLUME, default=100): All(
int, Range(min=1, max=100)
),
} }
), ),
) )
async def async_step_device(self, user_input: dict[str, any] | None = None):
if user_input is not None:
self.user_input.update(user_input)
try:
session = async_get_clientsession(self.hass)
api = pysmartthings.SmartThings(
session, self.user_input.get(CONF_ENTRY_API_KEY)
)
device = await validate_input(
api, self.user_input.get(CONF_ENTRY_DEVICE_ID)
)
_LOGGER.debug(
f"Successfully validated Input, Creating entry with title {DOMAIN} and data {user_input}"
)
except Exception as excp:
_LOGGER.error(f"The ConfigFlow triggered an exception {excp}")
return self.async_abort(reason="fetch_failed")
return self.async_create_entry(title=DOMAIN, data=self.user_input)
return self.async_show_form(
step_id="device",
data_schema=vol.Schema(
{
vol.Required(CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES): bool,
vol.Required(CONF_ENTRY_SETTINGS_EQ_SELECTOR): bool,
vol.Required(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR): bool,
vol.Required(CONF_ENTRY_SETTINGS_WOOFER_NUMBER): bool,
}
),
)
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
"""Handle a reconfiguration flow initialized by the user."""
self.config_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
):
"""Handle a reconfiguration flow initialized by the user."""
errors: dict[str, str] = {}
assert self.config_entry
if user_input is not None:
return self.async_update_reload_and_abort(
self.config_entry,
data={**self.config_entry.data, **user_input},
reason="reconfigure_successful",
)
return self.async_show_form(
step_id="reconfigure_confirm",
data_schema=vol.Schema(
{
vol.Required(
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
default=self.config_entry.data.get(
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES
),
): bool,
vol.Required(
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
default=self.config_entry.data.get(
CONF_ENTRY_SETTINGS_EQ_SELECTOR
),
): bool,
vol.Required(
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
default=self.config_entry.data.get(
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR
),
): bool,
vol.Required(
CONF_ENTRY_SETTINGS_WOOFER_NUMBER,
default=self.config_entry.data.get(
CONF_ENTRY_SETTINGS_WOOFER_NUMBER
),
): bool,
vol.Required(CONF_ENTRY_MAX_VOLUME, default=100): All(
int, Range(min=1, max=100)
),
}
),
errors=errors,
)

View File

@ -9,6 +9,12 @@ CONF_ENTRY_API_KEY = "api_key"
CONF_ENTRY_DEVICE_ID = "device_id" CONF_ENTRY_DEVICE_ID = "device_id"
CONF_ENTRY_DEVICE_NAME = "device_name" CONF_ENTRY_DEVICE_NAME = "device_name"
CONF_ENTRY_MAX_VOLUME = "device_volume" CONF_ENTRY_MAX_VOLUME = "device_volume"
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES = "settings_advanced_audio"
CONF_ENTRY_SETTINGS_EQ_SELECTOR = "settings_eq"
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR = "settings_soundmode"
CONF_ENTRY_SETTINGS_WOOFER_NUMBER = "settings_woofer"
DEFAULT_NAME = DOMAIN DEFAULT_NAME = DOMAIN
BUTTON = BUTTON_DOMAIN BUTTON = BUTTON_DOMAIN

View File

@ -8,5 +8,5 @@
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"issue_tracker": "https://github.com/samuelspagl/ha_samsung_soundbar/issues", "issue_tracker": "https://github.com/samuelspagl/ha_samsung_soundbar/issues",
"requirements": ["pysmartthings"], "requirements": ["pysmartthings"],
"version": "0.3.2" "version": "0.4.0"
} }

View File

@ -1,16 +1,25 @@
import logging import logging
from typing import Any, Mapping from typing import Any, Mapping
from homeassistant.components.media_player import (DEVICE_CLASS_SPEAKER, from homeassistant.components.media_player import (
MediaPlayerEntity) DEVICE_CLASS_SPEAKER,
from homeassistant.components.media_player.const import \ MediaPlayerEntity,
MediaPlayerEntityFeature )
from homeassistant.components.media_player.const import MediaPlayerEntityFeature
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo, generate_entity_id from homeassistant.helpers.entity import DeviceInfo, generate_entity_id
from homeassistant.helpers import config_validation as cv, entity_platform, selector
import voluptuous as vol
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice
from .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID, from .api_extension.const import SpeakerIdentifier, RearSpeakerMode
CONF_ENTRY_DEVICE_NAME, CONF_ENTRY_MAX_VOLUME, DOMAIN) from .const import (
CONF_ENTRY_API_KEY,
CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME,
CONF_ENTRY_MAX_VOLUME,
DOMAIN,
)
from .models import DeviceConfig from .models import DeviceConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -27,14 +36,82 @@ SUPPORT_SMARTTHINGS_SOUNDBAR = (
| MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.STOP | MediaPlayerEntityFeature.STOP
| MediaPlayerEntityFeature.SELECT_SOUND_MODE | MediaPlayerEntityFeature.SELECT_SOUND_MODE
) )
def addServices():
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
"select_soundmode",
cv.make_entity_service_schema({vol.Required("sound_mode"): str}),
SmartThingsSoundbarMediaPlayer.async_select_sound_mode.__name__,
)
platform.async_register_entity_service(
"set_woofer_level",
cv.make_entity_service_schema(
{vol.Required("level"): vol.All(int, vol.Range(min=-12, max=6))}
),
SmartThingsSoundbarMediaPlayer.async_set_woofer_level.__name__,
)
platform.async_register_entity_service(
"set_night_mode",
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
SmartThingsSoundbarMediaPlayer.async_set_night_mode.__name__,
)
platform.async_register_entity_service(
"set_bass_enhancer",
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
SmartThingsSoundbarMediaPlayer.async_set_bass_mode.__name__,
)
platform.async_register_entity_service(
"set_voice_enhancer",
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
SmartThingsSoundbarMediaPlayer.async_set_voice_mode.__name__,
)
platform.async_register_entity_service(
"set_speaker_level",
cv.make_entity_service_schema(
{vol.Required("speaker_identifier"): str, vol.Required("level"): int}
),
SmartThingsSoundbarMediaPlayer.async_set_speaker_level.__name__,
)
platform.async_register_entity_service(
"set_rear_speaker_mode",
cv.make_entity_service_schema({vol.Required("speaker_mode"): str}),
SmartThingsSoundbarMediaPlayer.async_set_rear_speaker_mode.__name__,
)
platform.async_register_entity_service(
"set_active_voice_amplifier",
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
SmartThingsSoundbarMediaPlayer.async_set_active_voice_amplifier.__name__,
)
platform.async_register_entity_service(
"set_space_fit_sound",
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
SmartThingsSoundbarMediaPlayer.async_set_space_fit_sound.__name__,
)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
domain_data = hass.data[DOMAIN] domain_data = hass.data[DOMAIN]
addServices()
entities = [] entities = []
for key in domain_data.devices: for key in domain_data.devices:
device_config: DeviceConfig = domain_data.devices[key] device_config: DeviceConfig = domain_data.devices[key]
@ -171,9 +248,45 @@ class SmartThingsSoundbarMediaPlayer(MediaPlayerEntity):
async def async_media_pause(self): async def async_media_pause(self):
await self.device.media_pause() await self.device.media_pause()
async def async_media_next_track(self):
await self.device.media_next_track()
async def async_media_previous_track(self):
await self.device.media_previous_track()
async def async_media_stop(self): async def async_media_stop(self):
await self.device.media_stop() await self.device.media_stop()
# ---------- SERVICE_UTILITY ------------
async def async_set_woofer_level(self, level: int):
await self.device.set_woofer(level)
async def async_set_bass_mode(self, enabled: bool):
await self.device.set_bass_mode(enabled)
async def async_set_voice_mode(self, enabled: bool):
await self.device.set_voice_amplifier(enabled)
async def async_set_night_mode(self, enabled: bool):
await self.device.set_night_mode(enabled)
# ---------- SERVICE_UTILITY ------------
async def async_set_speaker_level(self, speaker_identifier: str, level: int):
await self.device.set_speaker_level(
SpeakerIdentifier(speaker_identifier), level
)
async def async_set_rear_speaker_mode(self, speaker_mode: str):
await self.device.set_rear_speaker_mode(RearSpeakerMode(speaker_mode))
async def async_set_active_voice_amplifier(self, enabled: bool):
await self.device.set_active_voice_amplifier(enabled)
async def async_set_space_fit_sound(self, enabled: bool):
await self.device.set_space_fit_sound(enabled)
# This property can be uncommented for some extra_attributes # This property can be uncommented for some extra_attributes
# Still enabling this can cause side-effects. # Still enabling this can cause side-effects.
# @property # @property

View File

@ -1,12 +1,14 @@
import logging import logging
from homeassistant.components.number import (NumberEntity, from homeassistant.components.number import (
NumberEntityDescription, NumberEntity,
NumberMode) NumberEntityDescription,
NumberMode,
)
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN from .const import CONF_ENTRY_DEVICE_ID, CONF_ENTRY_SETTINGS_WOOFER_NUMBER, DOMAIN
from .models import DeviceConfig from .models import DeviceConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -19,7 +21,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for key in domain_data.devices: for key in domain_data.devices:
device_config: DeviceConfig = domain_data.devices[key] device_config: DeviceConfig = domain_data.devices[key]
device = device_config.device device = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): if device.device_id == config_entry.data.get(
CONF_ENTRY_DEVICE_ID
) and config_entry.data.get(CONF_ENTRY_SETTINGS_WOOFER_NUMBER):
entities.append( entities.append(
SoundbarWooferNumberEntity( SoundbarWooferNumberEntity(
device, device,

View File

@ -1,14 +1,20 @@
import logging import logging
from homeassistant.components.number import (NumberEntity, from homeassistant.components.number import (
NumberEntityDescription, NumberEntity,
NumberMode) NumberEntityDescription,
from homeassistant.components.select import (SelectEntity, NumberMode,
SelectEntityDescription) )
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN from .const import (
CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
DOMAIN,
)
from .models import DeviceConfig from .models import DeviceConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -21,12 +27,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_config: DeviceConfig = domain_data.devices[key] device_config: DeviceConfig = domain_data.devices[key]
device = device_config.device device = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
entities.append( if config_entry.data.get(CONF_ENTRY_SETTINGS_EQ_SELECTOR):
EqPresetSelectEntity(device, "eq_preset", "mdi:tune-vertical") entities.append(
) EqPresetSelectEntity(device, "eq_preset", "mdi:tune-vertical")
entities.append( )
SoundModeSelectEntity(device, "sound_mode_preset", "mdi:surround-sound") if config_entry.data.get(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR):
) entities.append(
SoundModeSelectEntity(
device, "sound_mode_preset", "mdi:surround-sound"
)
)
entities.append( entities.append(
InputSelectEntity(device, "input_preset", "mdi:video-input-hdmi") InputSelectEntity(device, "input_preset", "mdi:video-input-hdmi")
) )

View File

@ -1,7 +1,10 @@
import logging import logging
from homeassistant.components.sensor import (SensorDeviceClass, SensorEntity, from homeassistant.components.sensor import (
SensorStateClass) SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice

View File

@ -0,0 +1,167 @@
select_soundmode:
name: Select Soundmode
description: Some Soundbars support different "sound modes". If supported you can select them here.
target:
device:
integration: samsung_soundbar
fields:
sound_mode:
name: Sound Mode
description: Select the Soundmode you are interested in.
required: true
example: "adaptive sound"
# The default field value
default: "standard"
# Selector (https://www.home-assistant.io/docs/blueprint/selectors/) to control
# the input UI for this field
selector:
select:
translation_key: "soundmode"
options:
- "standard"
- "surround"
- "game"
- "adaptive sound"
set_woofer_level:
name: Set Woofer level
description: Set the subwoofer level of your soundbar
target:
device:
integration: samsung_soundbar
fields:
level:
name: Volume level
required: true
example: 3
default: 0
selector:
number:
min: -12
max: 6
step: 1
set_night_mode:
name: Set NightMode
description: Activates / deactivates the Nightmode
target:
device:
integration: samsung_soundbar
fields:
enabled:
name: Enabled / Disabled
required: true
example: true
default: false
selector:
boolean:
set_bass_enhancer:
name: Set bass enhancement
description: Activates / deactivates the bass enhancement
target:
device:
integration: samsung_soundbar
fields:
enabled:
name: Enabled / Disabled
required: true
example: true
default: false
selector:
boolean:
set_voice_enhancer:
name: Set voice enhancement
description: Activates / deactivates the voice enhancement
target:
device:
integration: samsung_soundbar
fields:
enabled:
name: Enabled / Disabled
required: true
example: true
default: false
selector:
boolean:
set_speaker_level:
name: Set Speaker level
description: Set the speaker levels of your soundbar
target:
device:
integration: samsung_soundbar
fields:
speaker_identifier:
name: Speaker Identifier
required: true
example: Spk_Center
selector:
select:
translation_key: "speaker_identifier"
options:
- "Spk_Center"
- "Spk_Side"
- "Spk_Wide"
- "Spk_Front_Top"
- "Spk_Rear"
- "Spk_Rear_Top"
level:
name: Speaker Level
required: true
example: 0
selector:
number:
min: -6
max: 6
step: 1
set_rear_speaker_mode:
name: Set rear speaker mode
description: Set the rear speaker mode of your soundbar
target:
device:
integration: samsung_soundbar
fields:
speaker_mode:
name: Speaker mode
required: true
example: Rear
selector:
select:
translation_key: "rear_speaker_mode"
options:
- "Rear"
- "Front"
set_active_voice_amplifier:
name: Set active voice amplifier
description: Activates / deactivates the active voice amplifier
target:
device:
integration: samsung_soundbar
fields:
enabled:
name: Enabled / Disabled
required: true
example: true
default: false
selector:
boolean:
set_space_fit_sound:
name: Set SpaceFitSound
description: Activates / deactivates the SpaceFitSound
target:
device:
integration: samsung_soundbar
fields:
enabled:
name: Enabled / Disabled
required: true
example: true
default: false
selector:
boolean:

View File

@ -4,7 +4,11 @@ from homeassistant.components.switch import SwitchEntity
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from .api_extension.SoundbarDevice import SoundbarDevice from .api_extension.SoundbarDevice import SoundbarDevice
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN from .const import (
CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
DOMAIN,
)
from .models import DeviceConfig from .models import DeviceConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -18,36 +22,37 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_config: DeviceConfig = domain_data.devices[key] device_config: DeviceConfig = domain_data.devices[key]
device = device_config.device device = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
entities.append( if config_entry.data.get(CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES):
SoundbarSwitchAdvancedAudio( entities.append(
device, SoundbarSwitchAdvancedAudio(
"nightmode", device,
lambda: device.night_mode, "nightmode",
device.set_night_mode, lambda: device.night_mode,
device.set_night_mode, device.set_night_mode,
"mdi:weather-night", device.set_night_mode,
"mdi:weather-night",
)
) )
) entities.append(
entities.append( SoundbarSwitchAdvancedAudio(
SoundbarSwitchAdvancedAudio( device,
device, "bassmode",
"bassmode", lambda: device.bass_mode,
lambda: device.bass_mode, device.set_bass_mode,
device.set_bass_mode, device.set_bass_mode,
device.set_bass_mode, "mdi:speaker-wireless",
"mdi:speaker-wireless", )
) )
) entities.append(
entities.append( SoundbarSwitchAdvancedAudio(
SoundbarSwitchAdvancedAudio( device,
device, "voice_amplifier",
"voice_amplifier", lambda: device.voice_amplifier,
lambda: device.voice_amplifier, device.set_voice_amplifier,
device.set_voice_amplifier, device.set_voice_amplifier,
device.set_voice_amplifier, "mdi:account-voice",
"mdi:account-voice", )
) )
)
async_add_entities(entities) async_add_entities(entities)
return True return True

View File

@ -10,7 +10,54 @@
}, },
"description": "Bitte gib deine Daten ein.", "description": "Bitte gib deine Daten ein.",
"title": "Authentifizierung" "title": "Authentifizierung"
},
"device":{
"data" : {
"settings_advanced_audio": "'Advanced Audio switches' aktivieren (NightMode, BassMode, VoiceEnhancer)",
"settings_eq": "'EQ selector' aktivieren",
"settings_soundmode": "'Soundmode selector' aktivieren",
"settings_woofer": "'Subwoofer Entität' aktivieren"
},
"description": "Einige Soundbars haben verschiedene Featuresets. Wähle bitte aus welche Features von deiner Soundbar supported werden (einsehbar in der SmartThings App).",
"title": "Geräte Einstellungen"
},
"reconfigure_confirm":{
"data" : {
"settings_advanced_audio": "'Advanced Audio switches' aktivieren (NightMode, BassMode, VoiceEnhancer)",
"settings_eq": "'EQ selector' aktivieren",
"settings_soundmode": "'Soundmode selector' aktivieren",
"settings_woofer": "'Subwoofer Entität' aktivieren",
"device_volume": "Max Volume (int)"
},
"description": "Einige Soundbars haben verschiedene Featuresets. Wähle bitte aus welche Features von deiner Soundbar supported werden (einsehbar in der SmartThings App).",
"title": "Geräte Einstellungen"
} }
} }
} },
"selector": {
"soundmode": {
"options": {
"standard": "Standard",
"surround": "Surround",
"game": "Gaming",
"adaptive sound": "Adaptive Sound"
}
},
"speaker_identifier": {
"options": {
"Spk_Center": "Center",
"Spk_Side": "Side",
"Spk_Wide": "Wide",
"Spk_Front_Top": "Front Top",
"Spk_Rear": "Rear",
"Spk_Rear_Top": "Rear Top"
}
},
"rear_speaker_mode": {
"options": {
"Rear": "Rear",
"Front": "Front"
}
}
}
} }

View File

@ -10,7 +10,54 @@
}, },
"description": "Please enter your credentials.", "description": "Please enter your credentials.",
"title": "Authentication" "title": "Authentication"
},
"device":{
"data" : {
"settings_advanced_audio": "Enable 'Advanced Audio switches' capabilities (NightMode, BassMode, VoiceEnhancer)",
"settings_eq": "Enable 'EQ selector' capabilities",
"settings_soundmode": "Enable 'Soundmode selector' capabilities",
"settings_woofer": "Enable 'Woofer number' capability"
},
"description": "Some soundbars have a different featureset than others. Please the features supported by your soundbar (visible in the SmartThings App).",
"title": "Device Settings"
},
"reconfigure_confirm":{
"data" : {
"settings_advanced_audio": "Enable 'Advanced Audio switches' capabilities (NightMode, BassMode, VoiceEnhancer)",
"settings_eq": "Enable 'EQ selector' capabilities",
"settings_soundmode": "Enable 'Soundmode selector' capabilities",
"settings_woofer": "Enable 'Woofer number' capability",
"device_volume": "Max Volume (int)"
},
"description": "Some soundbars have a different featureset than others. Please the features supported by your soundbar (visible in the SmartThings App).",
"title": "Device Settings"
} }
} }
} },
"selector": {
"soundmode": {
"options": {
"standard": "Standard",
"surround": "Surround",
"game": "Gaming",
"adaptive sound": "Adaptive Sound"
}
},
"speaker_identifier": {
"options": {
"Spk_Center": "Center",
"Spk_Side": "Side",
"Spk_Wide": "Wide",
"Spk_Front_Top": "Front Top",
"Spk_Rear": "Rear",
"Spk_Rear_Top": "Rear Top"
}
},
"rear_speaker_mode": {
"options": {
"Rear": "Rear",
"Front": "Front"
}
}
}
} }

View File

@ -3,5 +3,5 @@
"filename": "samsung_soundbar.zip", "filename": "samsung_soundbar.zip",
"render_readme": true, "render_readme": true,
"zip_release": true, "zip_release": true,
"homeassistant": "2024.1.0" "homeassistant": "2024.3.0"
} }

20
scripts/develop Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
# Create config dir if not present
if [[ ! -d "${PWD}/config" ]]; then
mkdir -p "${PWD}/config"
hass --config "${PWD}/config" --script ensure_config
fi
# Set the path to custom_components
## This let's us have the structure we want <root>/custom_components/integration_blueprint
## while at the same time have Home Assistant configuration inside <root>/config
## without resulting to symlinks.
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"
# Start Home Assistant
hass --config "${PWD}/config" --debug

7
scripts/setup Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
pip install rich pysmartthings