Compare commits
	
		
			13 Commits
		
	
	
		
			main
			...
			feature/co
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 9700063c3b | |
|  | a85e0db582 | |
|  | 221ecf2aa6 | |
|  | 53dc582fe6 | |
|  | 618e89b19f | |
|  | 97ae0615b3 | |
|  | 4839f403e8 | |
|  | c6e96d0754 | |
|  | 64c4b7aa34 | |
|  | 252d3855ca | |
|  | 08e8f2645d | |
|  | 00eccac431 | |
|  | 7216766068 | 
|  | @ -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" | ||||||
|  | } | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										31
									
								
								CHANGELOG.md
								
								
								
								
							|  | @ -1,5 +1,36 @@ | ||||||
| # Changelog | # Changelog | ||||||
| 
 | 
 | ||||||
|  | ## [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 | ## [0.3.2] Fix division by zero | ||||||
| 
 | 
 | ||||||
| ### Added | ### Added | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								Pipfile
								
								
								
								
							
							
						
						
									
										5
									
								
								Pipfile
								
								
								
								
							|  | @ -9,8 +9,7 @@ rich = "*" | ||||||
| homeassistant = "*" | homeassistant = "*" | ||||||
| 
 | 
 | ||||||
| [dev-packages] | [dev-packages] | ||||||
| black = "*" | ruff = "*" | ||||||
| isort = "*" |  | ||||||
| 
 | 
 | ||||||
| [requires] | [requires] | ||||||
| python_version = "3.11" | python_version = "3.12" | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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,9 +61,14 @@ class SoundbarDevice: | ||||||
|         await self.device.status.refresh() |         await self.device.status.refresh() | ||||||
| 
 | 
 | ||||||
|         await self._update_media() |         await self._update_media() | ||||||
|  | 
 | ||||||
|  |         if self.__enable_soundmode: | ||||||
|             await self._update_soundmode() |             await self._update_soundmode() | ||||||
|  |         if self.__enable_advanced_audio: | ||||||
|             await self._update_advanced_audio() |             await self._update_advanced_audio() | ||||||
|  |         if self.__enable_soundmode: | ||||||
|             await self._update_woofer() |             await self._update_woofer() | ||||||
|  |         if self.__enable_eq: | ||||||
|             await self._update_equalizer() |             await self._update_equalizer() | ||||||
| 
 | 
 | ||||||
|     async def _update_media(self): |     async def _update_media(self): | ||||||
|  | @ -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: | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  | @ -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, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -1,12 +1,14 @@ | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| from homeassistant.components.number import (NumberEntity, | from homeassistant.components.number import ( | ||||||
|  |     NumberEntity, | ||||||
|     NumberEntityDescription, |     NumberEntityDescription, | ||||||
|                                              NumberMode) |     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, | ||||||
|  |  | ||||||
|  | @ -1,14 +1,20 @@ | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| from homeassistant.components.number import (NumberEntity, | from homeassistant.components.number import ( | ||||||
|  |     NumberEntity, | ||||||
|     NumberEntityDescription, |     NumberEntityDescription, | ||||||
|                                              NumberMode) |     NumberMode, | ||||||
| from homeassistant.components.select import (SelectEntity, | ) | ||||||
|                                              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): | ||||||
|  |             if config_entry.data.get(CONF_ENTRY_SETTINGS_EQ_SELECTOR): | ||||||
|                 entities.append( |                 entities.append( | ||||||
|                     EqPresetSelectEntity(device, "eq_preset", "mdi:tune-vertical") |                     EqPresetSelectEntity(device, "eq_preset", "mdi:tune-vertical") | ||||||
|                 ) |                 ) | ||||||
|  |             if config_entry.data.get(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR): | ||||||
|                 entities.append( |                 entities.append( | ||||||
|                 SoundModeSelectEntity(device, "sound_mode_preset", "mdi:surround-sound") |                     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") | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
|  | @ -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,6 +22,7 @@ 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): | ||||||
|  |             if config_entry.data.get(CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES): | ||||||
|                 entities.append( |                 entities.append( | ||||||
|                     SoundbarSwitchAdvancedAudio( |                     SoundbarSwitchAdvancedAudio( | ||||||
|                         device, |                         device, | ||||||
|  |  | ||||||
|  | @ -10,6 +10,127 @@ | ||||||
|                   }, |                   }, | ||||||
|                   "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" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |   }, | ||||||
|  |   "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" | ||||||
|  |     }, | ||||||
|  |     "set_rear_speaker_mode":{ | ||||||
|  |         "name": "Modus der hinteren Lautsprecher setzen", | ||||||
|  |         "description": "Nutze deine Rücklautsprecher, als 'Vorder-' oder 'Rücklautsprecher'." | ||||||
|  |     }, | ||||||
|  |     "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." | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,53 @@ | ||||||
|                   }, |                   }, | ||||||
|                   "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" | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
| } | } | ||||||
|  | @ -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 | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | #!/usr/bin/env bash | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | cd "$(dirname "$0")/.." | ||||||
|  | 
 | ||||||
|  | pip install rich pysmartthings | ||||||
		Loading…
	
		Reference in New Issue