Compare commits

..

4 Commits

Author SHA1 Message Date
orkun c113688986 Update custom_components/samsung_soundbar/manifest.json 2025-04-10 19:37:48 +03:00
orkun 8cd2aa3d51 Update custom_components/samsung_soundbar/manifest.json 2025-04-10 19:36:41 +03:00
Samuel Spagl bd313ea27a
Fix 'audiotrackdata' (#42)
## [0.4.1] Media Mystique: The Great Data Disappearing Act!

### Fixed

- Made media data (*track title*, *artist*, *length*) optional to acoomodate soundbars that don't provide this information (🥲)

### Added

- Add translations for the english translation file
2024-10-14 21:46:14 +02:00
Samuel Spagl 0d2424b578
Feature: Add more fine grained configuration steps (#28)
> ⚠️ Please read the following carefully:
> This release is a bit special. As "something" on Samsung's side changed,
> it is currently not possible to retrieve the status of "custom capabilities", eg.
> woofer, soundmode, eq, and others. Therefore I decided to give the option to
> disable the entities of these features as the value of these entities is not trustworthy.
> Instead I implemented all of these and more (thanks to @whitebearded) as service calls.
> Have fun using them!

### 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

---------

Co-authored-by: Samuel Spagl <samuel.spagl@kobil.com>
2024-06-09 17:13:38 +02:00
23 changed files with 1543 additions and 563 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
.DS_store
config
.vscode
.ruff_cache
# C extensions
*.so
.idea

View File

@ -1,5 +1,46 @@
# Changelog
## [0.4.1] Media Mystique: The Great Data Disappearing Act!
### Fixed
- Made media data (*track title*, *artist*, *length*) optional to acoomodate soundbars that don't provide this information (🥲)
### Added
- Add translations for the english translation file
## [0.4.0] Started with an "ick", but is now packed with new features 💪
> ⚠️ Please read the following carefully:
> This release is a bit special. As "something" on Samsung's side changed,
> it is currently not possible to retrieve the status of "custom capabilities", eg.
> woofer, soundmode, eq, and others. Therefore I decided to give the option to
> disable the entities of these features as the value of these entities is not trustworthy.
> Instead I implemented all of these and more (thanks to @whitebearded) as service calls.
> Have fun using them!
### 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
### Added

View File

@ -9,8 +9,7 @@ rich = "*"
homeassistant = "*"
[dev-packages]
black = "*"
isort = "*"
ruff = "*"
[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

@ -2,21 +2,8 @@
Welcome to YASSI, the Home Assistant integration designed to bring comprehensive control over your Samsung Soundbar into your smart home ecosystem.
> [!CAUTION]
> **Soundbar Integration Issues**:
>
> Samsung changed (un)intentionally? something in the SmartThings API. Therefore it is currently not possible to retrieve a status update for
> custom capabilities like *Soundmode, EQ, Woofer and Advanced Audio settings (Nightmode, Bassmode, Voice-enhancer).
> Other than this, the integration is working as expected.
>
> **I released a new beta version where you can select for which custom capability entities should be created. If one is disabled so is the update
> process, and therefore the error logs will disappear.**
>
> It is still possible to adjust all settings of the custom capabilties, therefore the beta version features service calls for each of those.
> For more and updated information please refer to [#26](https://github.com/samuelspagl/ha_samsung_soundbar/issues/26).
>
> Best Samuel ✌️
> [!NOTE]
> Please use service calls for setting the attribute of a custom capability instead of the entity. (See #43 for more information)
**Table of Contents:**
<!-- TOC -->

View File

@ -6,9 +6,18 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from pysmartthings import SmartThings
from .api_extension.SoundbarDevice import SoundbarDevice
from .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME, CONF_ENTRY_MAX_VOLUME, DOMAIN,
SUPPORTED_DOMAINS)
from .const import (
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,
SUPPORTED_DOMAINS,
)
from .models import DeviceConfig, SoundbarConfig
_LOGGER = logging.getLogger(__name__)
@ -21,7 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# store shell object
_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:
_LOGGER.debug(f"[{DOMAIN}] Domain not found in hass.data setting default")
hass.data[DOMAIN] = SoundbarConfig(
@ -48,6 +59,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
session,
entry.data.get(CONF_ENTRY_MAX_VOLUME),
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()
domain_config.devices[entry.data.get(CONF_ENTRY_DEVICE_ID)] = DeviceConfig(

View File

@ -2,11 +2,11 @@ import asyncio
import datetime
import json
import logging
import time
from urllib.parse import quote
from pysmartthings import DeviceEntity
from .const import SpeakerIdentifier, RearSpeakerMode
from ..const import DOMAIN
log = logging.getLogger(__name__)
@ -14,7 +14,15 @@ log = logging.getLogger(__name__)
class SoundbarDevice:
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_id = self.device.device_id
@ -22,17 +30,21 @@ class SoundbarDevice:
self.__session = session
self.__device_name = device_name
self.__enable_soundmode = enable_soundmode
self.__supported_soundmodes = []
self.__active_soundmode = ""
self.__enable_woofer = enable_woofer
self.__woofer_level = 0
self.__woofer_connection = ""
self.__enable_eq = enable_eq
self.__active_eq_preset = ""
self.__supported_eq_presets = []
self.__eq_action = ""
self.__eq_bands = []
self.__enable_advanced_audio = enable_advanced_audio
self.__voice_amplifier = 0
self.__night_mode = 0
self.__bass_mode = 0
@ -49,35 +61,41 @@ class SoundbarDevice:
await self.device.status.refresh()
await self._update_media()
await self._update_soundmode()
await self._update_advanced_audio()
await self._update_woofer()
await self._update_equalizer()
if self.__enable_soundmode:
await self._update_soundmode()
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):
self.__media_artist = self.device.status._attributes["audioTrackData"].value[
"artist"
]
self.__media_title = self.device.status._attributes["audioTrackData"].value[
"title"
]
if self.__media_title != self.__old_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_artist, self.__media_title
)
if "audioTrackData" in self.device.status._attributes:
self.__media_artist = self.device.status._attributes["audioTrackData"].value[
"artist"
]
self.__media_title = self.device.status._attributes["audioTrackData"].value[
"title"
]
if self.__media_title != self.__old_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_artist, self.__media_title
)
async def _update_soundmode(self):
await self.update_execution_data(["/sec/networkaudio/soundmode"])
await asyncio.sleep(0.1)
await asyncio.sleep(1)
payload = await self.get_execute_status()
retry = 0
while (
"x.com.samsung.networkaudio.supportedSoundmode" not in payload
and retry < 10
"x.com.samsung.networkaudio.supportedSoundmode" not in payload
and retry < 10
):
await asyncio.sleep(0.2)
await asyncio.sleep(1)
payload = await self.get_execute_status()
retry += 1
if retry == 10:
@ -179,7 +197,15 @@ class SoundbarDevice:
@property
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):
await self.device.switch_off(True)
@ -347,11 +373,15 @@ class SoundbarDevice:
@property
def media_duration(self) -> int | None:
return self.device.status.attributes.get("totalTime").value
attr = self.device.status.attributes.get("totalTime", None)
if attr:
return attr.value
@property
def media_position(self) -> int | None:
return self.device.status.attributes.get("elapsedTime").value
attr = self.device.status.attributes.get("elapsedTime", None)
if attr:
return attr.value
async def media_play(self):
await self.device.play(True)
@ -362,6 +392,12 @@ class SoundbarDevice:
async def media_stop(self):
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
def media_app_name(self):
detail_status = self.device.status.attributes.get("detailName", None)
@ -373,21 +409,54 @@ class SoundbarDevice:
def media_coverart_updated(self) -> datetime.datetime:
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 ------------
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):
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):
url = f"https://api.smartthings.com/v1/devices/{self._device_id}/components/main/capabilities/execute/status"
request_headers = {"Authorization": "Bearer " + self._api_key}
resp = await self.__session.get(url, headers=request_headers)
dict = await resp.json()
return dict["data"]["value"]["payload"]
dict_stuff = await resp.json()
return dict_stuff["data"]["value"]["payload"]
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
from typing import Any
import pysmartthings
import voluptuous as vol
@ -7,8 +8,17 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from pysmartthings import APIResponseError
from voluptuous import All, Range
from .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID,
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,
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__)
@ -24,20 +34,8 @@ async def validate_input(api, device_id: str):
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None):
if user_input is not None:
try:
session = async_get_clientsession(self.hass)
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")
self.user_input = user_input
return await self.async_step_device()
return self.async_show_form(
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_DEVICE_ID): 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_NAME = "device_name"
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
BUTTON = BUTTON_DOMAIN

View File

@ -1,12 +1,16 @@
{
"domain": "samsung_soundbar",
"name": "Samsung Soundbar",
"codeowners": ["@samuelspagl"],
"codeowners": [
"@samuelspagl"
],
"config_flow": true,
"documentation": "https://www.example.com",
"integration_type": "hub",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/samuelspagl/ha_samsung_soundbar/issues",
"requirements": ["pysmartthings"],
"version": "0.3.2"
}
"requirements": [
"pysmartthings==0.7.8"
],
"version": "0.4.1"
}

View File

@ -1,16 +1,25 @@
import logging
from typing import Any, Mapping
from homeassistant.components.media_player import (DEVICE_CLASS_SPEAKER,
MediaPlayerEntity)
from homeassistant.components.media_player.const import \
MediaPlayerEntityFeature
from homeassistant.components.media_player import (
DEVICE_CLASS_SPEAKER,
MediaPlayerEntity,
)
from homeassistant.components.media_player.const import MediaPlayerEntityFeature
from homeassistant.helpers.aiohttp_client import async_get_clientsession
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 .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME, CONF_ENTRY_MAX_VOLUME, DOMAIN)
from .api_extension.const import SpeakerIdentifier, RearSpeakerMode
from .const import (
CONF_ENTRY_API_KEY,
CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME,
CONF_ENTRY_MAX_VOLUME,
DOMAIN,
)
from .models import DeviceConfig
_LOGGER = logging.getLogger(__name__)
@ -27,14 +36,82 @@ SUPPORT_SMARTTHINGS_SOUNDBAR = (
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.STOP
| 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):
domain_data = hass.data[DOMAIN]
addServices()
entities = []
for key in domain_data.devices:
device_config: DeviceConfig = domain_data.devices[key]
@ -171,9 +248,45 @@ class SmartThingsSoundbarMediaPlayer(MediaPlayerEntity):
async def async_media_pause(self):
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):
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
# Still enabling this can cause side-effects.
# @property

View File

@ -1,12 +1,14 @@
import logging
from homeassistant.components.number import (NumberEntity,
NumberEntityDescription,
NumberMode)
from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
NumberMode,
)
from homeassistant.helpers.entity import DeviceInfo
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
_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:
device_config: DeviceConfig = domain_data.devices[key]
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(
SoundbarWooferNumberEntity(
device,

View File

@ -1,14 +1,20 @@
import logging
from homeassistant.components.number import (NumberEntity,
NumberEntityDescription,
NumberMode)
from homeassistant.components.select import (SelectEntity,
SelectEntityDescription)
from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
NumberMode,
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.helpers.entity import DeviceInfo
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
_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 = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
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_EQ_SELECTOR):
entities.append(
EqPresetSelectEntity(device, "eq_preset", "mdi:tune-vertical")
)
if config_entry.data.get(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR):
entities.append(
SoundModeSelectEntity(
device, "sound_mode_preset", "mdi:surround-sound"
)
)
entities.append(
InputSelectEntity(device, "input_preset", "mdi:video-input-hdmi")
)

View File

@ -1,7 +1,10 @@
import logging
from homeassistant.components.sensor import (SensorDeviceClass, SensorEntity,
SensorStateClass)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.helpers.entity import DeviceInfo
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 .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
_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 = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
entities.append(
SoundbarSwitchAdvancedAudio(
device,
"nightmode",
lambda: device.night_mode,
device.set_night_mode,
device.set_night_mode,
"mdi:weather-night",
if config_entry.data.get(CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES):
entities.append(
SoundbarSwitchAdvancedAudio(
device,
"nightmode",
lambda: device.night_mode,
device.set_night_mode,
device.set_night_mode,
"mdi:weather-night",
)
)
)
entities.append(
SoundbarSwitchAdvancedAudio(
device,
"bassmode",
lambda: device.bass_mode,
device.set_bass_mode,
device.set_bass_mode,
"mdi:speaker-wireless",
entities.append(
SoundbarSwitchAdvancedAudio(
device,
"bassmode",
lambda: device.bass_mode,
device.set_bass_mode,
device.set_bass_mode,
"mdi:speaker-wireless",
)
)
)
entities.append(
SoundbarSwitchAdvancedAudio(
device,
"voice_amplifier",
lambda: device.voice_amplifier,
device.set_voice_amplifier,
device.set_voice_amplifier,
"mdi:account-voice",
entities.append(
SoundbarSwitchAdvancedAudio(
device,
"voice_amplifier",
lambda: device.voice_amplifier,
device.set_voice_amplifier,
device.set_voice_amplifier,
"mdi:account-voice",
)
)
)
async_add_entities(entities)
return True

View File

@ -10,7 +10,144 @@
},
"description": "Bitte gib deine Daten ein.",
"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"
}
}
},
"services":{
"select_soundmode":{
"name": "SoundMode auswählen",
"description": "Wähle hier zwischen, 'Standard', 'Surround', 'Game' und 'Adaptive Sound'."
},
"set_woofer_level":{
"name": "Subwoofer Level setzen",
"description": "Verändere die Lautstärke deines Subwoofers.",
"fields":{
"level":{
"name": "Volume Level",
"description": "Subwoofer Level, von -12 bis +6"
}
}
},
"set_night_mode":{
"name": "Nachtmodus setzen",
"description": "Schalte den 'Nachtmodus' an / aus.",
"fields":{
"enabled":{
"name": "An / ausschalten",
"description": "Siehe Name."
}
}
},
"set_bass_enhancer":{
"name": "Bassmodus setzen",
"description": "Schalte den 'Bassmodus' an / aus.",
"fields":{
"enabled":{
"name": "An / ausschalten",
"description": "Siehe Name."
}
}
},
"set_voice_enhancer":{
"name": "Stimmenverbesserer setzen",
"description": "Schalte den 'Stimmenverbesserer' an / aus.",
"fields":{
"enabled":{
"name": "An / ausschalten",
"description": "Siehe Name."
}
}
},
"set_speaker_level":{
"name": "Lautsprecher level verändern",
"description": "Verändere die Lautstärke der einzelnen Lautsprecher",
"fields":{
"speaker_identifier": {
"name": "Lautsprecher",
"description": "Auszuwählender Lautsprecher"
},
"level": {
"name": "Lautstärke Level",
"description": "Lautstärke Level zwischen -6 und 6."
}
}
},
"set_rear_speaker_mode":{
"name": "Modus der hinteren Lautsprecher setzen",
"description": "Nutze deine Rücklautsprecher, als 'Vorder-' oder 'Rücklautsprecher'.",
"fields":{
"speaker_mode": {
"name": "Lautsprecher Modus",
"description": "Nutze den Lautsprecher als Front oder Rear Speaker."
}
}
},
"set_active_voice_amplifier":{
"name": "Stimmenverstärker setzen",
"description": "Schalte den 'Stimmenverstärker' an / aus.",
"fields":{
"enabled":{
"name": "An / ausschalten",
"description": "Siehe Name."
}
}
},
"set_space_fit_sound":{
"name": "SpaceFitSound setzen",
"description": "Schalte den 'SpaceFitSound' an / aus.",
"fields":{
"enabled":{
"name": "An / ausschalten",
"description": "Siehe Name."
}
}
}
}
}

View File

@ -1,15 +1,152 @@
{
"config":{
"step":{
"user":{
"config": {
"step": {
"user": {
"data": {
"api_key": "SmartThings API Token",
"device_id": "Device ID",
"device_name":"Device Name",
"device_name": "Device Name",
"device_volume": "Max Volume (int)"
},
"description": "Please enter your credentials.",
"title": "Authentication"
},
"description": "Please enter your credentials.",
"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"
}
}
},
"services": {
"select_soundmode": {
"name": "Select Sound Mode",
"description": "Choose between 'Standard', 'Surround', 'Game', and 'Adaptive Sound'."
},
"set_woofer_level": {
"name": "Set Subwoofer Level",
"description": "Change the volume of your subwoofer.",
"fields": {
"level": {
"name": "Volume Level",
"description": "Subwoofer level, from -12 to +6"
}
}
},
"set_night_mode": {
"name": "Set Night Mode",
"description": "Turn 'Night Mode' on/off.",
"fields": {
"enabled": {
"name": "On/Off",
"description": "See name."
}
}
},
"set_bass_enhancer": {
"name": "Set Bass Mode",
"description": "Turn 'Bass Mode' on/off.",
"fields": {
"enabled": {
"name": "On/Off",
"description": "See name."
}
}
},
"set_voice_enhancer": {
"name": "Set Voice Enhancer",
"description": "Turn 'Voice Enhancer' on/off.",
"fields": {
"enabled": {
"name": "On/Off",
"description": "See name."
}
}
},
"set_speaker_level": {
"name": "Change Speaker Level",
"description": "Change the volume of individual speakers.",
"fields":{
"speaker_identifier": {
"name": "Speaker Identifier",
"description": "Identifier of the speaker."
},
"level": {
"name": "Level",
"description": "Level of the Speaker from -6 to 6."
}
}
},
"set_rear_speaker_mode": {
"name": "Set Rear Speaker Mode",
"description": "Use your rear speakers as 'Front' or 'Rear' speakers.",
"fields":{
"speaker_mode": {
"name": "Speaker mode",
"description": "Weather the speaker are used as rear / front speakers."
}
}
},
"set_active_voice_amplifier": {
"name": "Set Voice Amplifier",
"description": "Turn 'Voice Amplifier' on/off.",
"fields": {
"enabled": {
"name": "On/Off",
"description": "See name."
}
}
},
"set_space_fit_sound": {
"name": "Set SpaceFitSound",
"description": "Turn 'SpaceFitSound' on/off.",
"fields": {
"enabled": {
"name": "On/Off",
"description": "See name."
}
}
}
}

View File

@ -3,5 +3,5 @@
"filename": "samsung_soundbar.zip",
"render_readme": 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