Compare commits
	
		
			4 Commits
		
	
	
		
			main
			...
			feature/fi
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | afb3de10ca | |
|  | 3010cd3e03 | |
|  | c563a3efd7 | |
|  | 0f43da8f1e | 
|  | @ -0,0 +1,37 @@ | |||
| name: "Validate" | ||||
| 
 | ||||
| on: | ||||
| #  workflow_dispatch: | ||||
| #  schedule: | ||||
| #    - cron:  "0 0 * * *" | ||||
| #  push: | ||||
| #    branches: | ||||
| #      - "main" | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - "never" | ||||
| 
 | ||||
| jobs: | ||||
|   hassfest: # https://developers.home-assistant.io/blog/2020/04/16/hassfest | ||||
|     name: "Hassfest Validation" | ||||
|     runs-on: "ubuntu-latest" | ||||
|     steps: | ||||
|         - name: "Checkout the repository" | ||||
|           uses: "actions/checkout@v3.5.3" | ||||
| 
 | ||||
|         - name: "Run hassfest validation" | ||||
|           uses: "home-assistant/actions/hassfest@master" | ||||
| 
 | ||||
|   hacs: # https://github.com/hacs/action | ||||
|     name: "HACS Validation" | ||||
|     runs-on: "ubuntu-latest" | ||||
|     steps: | ||||
|         - name: "Checkout the repository" | ||||
|           uses: "actions/checkout@v3.5.3" | ||||
| 
 | ||||
|         - name: "Run HACS validation" | ||||
|           uses: "hacs/action@main" | ||||
|           with: | ||||
|             category: "integration" | ||||
|             # Remove this 'ignore' key when you have added brand images for your integration to https://github.com/home-assistant/brands | ||||
|             ignore: "brands" | ||||
|  | @ -0,0 +1,35 @@ | |||
| name: "Release" | ||||
| 
 | ||||
| on: | ||||
|   release: | ||||
|     types: | ||||
|       - "published" | ||||
| 
 | ||||
| permissions: {} | ||||
| 
 | ||||
| jobs: | ||||
|   release: | ||||
|     name: "Release" | ||||
|     runs-on: "ubuntu-latest" | ||||
|     permissions: | ||||
|       contents: write | ||||
|     steps: | ||||
|       - name: "Checkout the repository" | ||||
|         uses: "actions/checkout@v3.5.3" | ||||
| 
 | ||||
|       - name: "Adjust version number" | ||||
|         shell: "bash" | ||||
|         run: | | ||||
|           yq -i -o json '.version="${{ github.event.release.tag_name }}"' \ | ||||
|             "${{ github.workspace }}/custom_components/samsung_soundbar/manifest.json" | ||||
| 
 | ||||
|       - name: "ZIP the integration directory" | ||||
|         shell: "bash" | ||||
|         run: | | ||||
|           cd "${{ github.workspace }}/custom_components/samsung_soundbar" | ||||
|           zip samsung_soundbar.zip -r ./ | ||||
| 
 | ||||
|       - name: "Upload the ZIP file to the release" | ||||
|         uses: softprops/action-gh-release@v0.1.15 | ||||
|         with: | ||||
|           files: ${{ github.workspace }}/custom_components/samsung_soundbar/samsung_soundbar.zip | ||||
|  | @ -0,0 +1,161 @@ | |||
| # Byte-compiled / optimized / DLL files | ||||
| __pycache__/ | ||||
| *.py[cod] | ||||
| *$py.class | ||||
| 
 | ||||
| # C extensions | ||||
| *.so | ||||
| .idea | ||||
| .pycharm | ||||
| # Distribution / packaging | ||||
| .Python | ||||
| build/ | ||||
| develop-eggs/ | ||||
| dist/ | ||||
| downloads/ | ||||
| eggs/ | ||||
| .eggs/ | ||||
| lib/ | ||||
| lib64/ | ||||
| parts/ | ||||
| sdist/ | ||||
| var/ | ||||
| wheels/ | ||||
| share/python-wheels/ | ||||
| *.egg-info/ | ||||
| .installed.cfg | ||||
| *.egg | ||||
| MANIFEST | ||||
| 
 | ||||
| # PyInstaller | ||||
| #  Usually these files are written by a python script from a template | ||||
| #  before PyInstaller builds the exe, so as to inject date/other infos into it. | ||||
| *.manifest | ||||
| *.spec | ||||
| 
 | ||||
| # Installer logs | ||||
| pip-log.txt | ||||
| pip-delete-this-directory.txt | ||||
| 
 | ||||
| # Unit test / coverage reports | ||||
| htmlcov/ | ||||
| .tox/ | ||||
| .nox/ | ||||
| .coverage | ||||
| .coverage.* | ||||
| .cache | ||||
| nosetests.xml | ||||
| coverage.xml | ||||
| *.cover | ||||
| *.py,cover | ||||
| .hypothesis/ | ||||
| .pytest_cache/ | ||||
| cover/ | ||||
| 
 | ||||
| # Translations | ||||
| *.mo | ||||
| *.pot | ||||
| 
 | ||||
| # Django stuff: | ||||
| *.log | ||||
| local_settings.py | ||||
| db.sqlite3 | ||||
| db.sqlite3-journal | ||||
| 
 | ||||
| # Flask stuff: | ||||
| instance/ | ||||
| .webassets-cache | ||||
| 
 | ||||
| # Scrapy stuff: | ||||
| .scrapy | ||||
| 
 | ||||
| # Sphinx documentation | ||||
| docs/_build/ | ||||
| 
 | ||||
| # PyBuilder | ||||
| .pybuilder/ | ||||
| target/ | ||||
| 
 | ||||
| # Jupyter Notebook | ||||
| .ipynb_checkpoints | ||||
| 
 | ||||
| # IPython | ||||
| profile_default/ | ||||
| ipython_config.py | ||||
| 
 | ||||
| # pyenv | ||||
| #   For a library or package, you might want to ignore these files since the code is | ||||
| #   intended to run in multiple environments; otherwise, check them in: | ||||
| # .python-version | ||||
| 
 | ||||
| # pipenv | ||||
| #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | ||||
| #   However, in case of collaboration, if having platform-specific dependencies or dependencies | ||||
| #   having no cross-platform support, pipenv may install dependencies that don't work, or not | ||||
| #   install all needed dependencies. | ||||
| #Pipfile.lock | ||||
| 
 | ||||
| # poetry | ||||
| #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. | ||||
| #   This is especially recommended for binary packages to ensure reproducibility, and is more | ||||
| #   commonly ignored for libraries. | ||||
| #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control | ||||
| #poetry.lock | ||||
| 
 | ||||
| # pdm | ||||
| #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. | ||||
| #pdm.lock | ||||
| #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it | ||||
| #   in version control. | ||||
| #   https://pdm.fming.dev/#use-with-ide | ||||
| .pdm.toml | ||||
| 
 | ||||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm | ||||
| __pypackages__/ | ||||
| 
 | ||||
| # Celery stuff | ||||
| celerybeat-schedule | ||||
| celerybeat.pid | ||||
| 
 | ||||
| # SageMath parsed files | ||||
| *.sage.py | ||||
| 
 | ||||
| # Environments | ||||
| .env | ||||
| .venv | ||||
| env/ | ||||
| venv/ | ||||
| ENV/ | ||||
| env.bak/ | ||||
| venv.bak/ | ||||
| 
 | ||||
| # Spyder project settings | ||||
| .spyderproject | ||||
| .spyproject | ||||
| 
 | ||||
| # Rope project settings | ||||
| .ropeproject | ||||
| 
 | ||||
| # mkdocs documentation | ||||
| /site | ||||
| 
 | ||||
| # mypy | ||||
| .mypy_cache/ | ||||
| .dmypy.json | ||||
| dmypy.json | ||||
| 
 | ||||
| # Pyre type checker | ||||
| .pyre/ | ||||
| 
 | ||||
| # pytype static type analyzer | ||||
| .pytype/ | ||||
| 
 | ||||
| # Cython debug symbols | ||||
| cython_debug/ | ||||
| 
 | ||||
| # PyCharm | ||||
| #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can | ||||
| #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | ||||
| #  and can be added to the global gitignore or merged into this file.  For a more nuclear | ||||
| #  option (not recommended) you can uncomment the following to ignore the entire idea folder. | ||||
| #.idea/ | ||||
|  | @ -0,0 +1,16 @@ | |||
| [[source]] | ||||
| url = "https://pypi.org/simple" | ||||
| verify_ssl = true | ||||
| name = "pypi" | ||||
| 
 | ||||
| [packages] | ||||
| pysmartthings = "*" | ||||
| rich = "*" | ||||
| homeassistant = "*" | ||||
| 
 | ||||
| [dev-packages] | ||||
| black = "*" | ||||
| isort = "*" | ||||
| 
 | ||||
| [requires] | ||||
| python_version = "3.11" | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,75 @@ | |||
| import logging | ||||
| 
 | ||||
| from homeassistant.config_entries import ConfigEntry | ||||
| from homeassistant.core import DOMAIN, HomeAssistant | ||||
| 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, | ||||
|     SUPPORTED_DOMAINS, | ||||
|     DOMAIN, | ||||
| ) | ||||
| from .models import DeviceConfig, SoundbarConfig | ||||
| 
 | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| PLATFORMS = ["media_player", "switch", "image", "number"] | ||||
| 
 | ||||
| 
 | ||||
| async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||||
|     """Set up component from a config entry, config_entry contains data from config entry database.""" | ||||
|     # store shell object | ||||
| 
 | ||||
|     _LOGGER.info(f"[{DOMAIN}] Starting to setup ConfigEntry {entry.data}") | ||||
|     if not DOMAIN in hass.data: | ||||
|         _LOGGER.info(f"[{DOMAIN}] Domain not found in hass.data setting default") | ||||
|         hass.data[DOMAIN] = SoundbarConfig( | ||||
|             SmartThings( | ||||
|                 async_get_clientsession(hass), entry.data.get(CONF_ENTRY_API_KEY) | ||||
|             ), | ||||
|             {}, | ||||
|         ) | ||||
| 
 | ||||
|     domain_config: SoundbarConfig = hass.data[DOMAIN] | ||||
|     _LOGGER.info(f"[{DOMAIN}] Retrieved Domain Config: {domain_config}") | ||||
| 
 | ||||
|     if not entry.data.get(CONF_ENTRY_DEVICE_ID) in domain_config.devices: | ||||
|         _LOGGER.info( | ||||
|             f"[{DOMAIN}] DeviceId: {entry.data.get(CONF_ENTRY_DEVICE_ID)} not found in domain_config, setting up new device." | ||||
|         ) | ||||
|         smart_things_device = await domain_config.api.device( | ||||
|             entry.data.get(CONF_ENTRY_DEVICE_ID) | ||||
|         ) | ||||
|         session = async_get_clientsession(hass) | ||||
|         soundbar_device = SoundbarDevice( | ||||
|                 smart_things_device, | ||||
|                 session, | ||||
|                 entry.data.get(CONF_ENTRY_MAX_VOLUME), | ||||
|                 entry.data.get(CONF_ENTRY_DEVICE_NAME), | ||||
|             ) | ||||
|         await soundbar_device.update() | ||||
|         domain_config.devices[entry.data.get(CONF_ENTRY_DEVICE_ID)] = DeviceConfig( | ||||
|             entry.data, | ||||
|             soundbar_device | ||||
|         ) | ||||
|         _LOGGER.info(f"[{DOMAIN}] after initializing Soundbar device") | ||||
| 
 | ||||
|     await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): | ||||
|     """Unload a config entry.""" | ||||
|     unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||||
|     domain_data = hass.data[DOMAIN] | ||||
|     if unload_ok: | ||||
|         del domain_data.devices[entry.data.get(CONF_ENTRY_DEVICE_ID)] | ||||
|         if len(domain_data.devices) == 0: | ||||
|             del hass.data[DOMAIN] | ||||
| 
 | ||||
|     return unload_ok | ||||
|  | @ -0,0 +1,382 @@ | |||
| import json | ||||
| import time | ||||
| from urllib.parse import quote | ||||
| 
 | ||||
| from pysmartthings import DeviceEntity | ||||
| 
 | ||||
| 
 | ||||
| class SoundbarDevice: | ||||
|     def __init__( | ||||
|         self, device: DeviceEntity, session, max_volume: int, device_name: str | ||||
|     ): | ||||
|         self.device = device | ||||
|         self._device_id = self.device.device_id | ||||
|         self._api_key = self.device._api.token | ||||
|         self.__session = session | ||||
|         self.__device_name = device_name | ||||
| 
 | ||||
|         self.__supported_soundmodes = [] | ||||
|         self.__active_soundmode = "" | ||||
| 
 | ||||
|         self.__woofer_level = 0 | ||||
|         self.__woofer_connection = "" | ||||
| 
 | ||||
|         self.__active_eq_preset = "" | ||||
|         self.__supported_eq_presets = [] | ||||
|         self.__eq_action = "" | ||||
|         self.__eq_bands = [] | ||||
| 
 | ||||
|         self.__voice_amplifier = 0 | ||||
|         self.__night_mode = 0 | ||||
|         self.__bass_mode = 0 | ||||
| 
 | ||||
|         self.__media_title = "" | ||||
|         self.__media_artist = "" | ||||
|         self.__media_cover_url = "" | ||||
|         self.__old_media_title = "" | ||||
| 
 | ||||
|         self.__max_volume = max_volume | ||||
| 
 | ||||
|     async def update(self): | ||||
|         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() | ||||
| 
 | ||||
|     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 = 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"]) | ||||
|         payload = await self.get_execute_status() | ||||
|         self.__supported_soundmodes = payload[ | ||||
|             "x.com.samsung.networkaudio.supportedSoundmode" | ||||
|         ] | ||||
|         self.__active_soundmode = payload["x.com.samsung.networkaudio.soundmode"] | ||||
| 
 | ||||
|     async def _update_woofer(self): | ||||
|         await self.update_execution_data(["/sec/networkaudio/woofer"]) | ||||
|         payload = await self.get_execute_status() | ||||
|         self.__woofer_level = payload["x.com.samsung.networkaudio.woofer"] | ||||
|         self.__woofer_connection = payload["x.com.samsung.networkaudio.connection"] | ||||
| 
 | ||||
|     async def _update_equalizer(self): | ||||
|         await self.update_execution_data(["/sec/networkaudio/eq"]) | ||||
|         payload = await self.get_execute_status() | ||||
|         self.__active_eq_preset = payload["x.com.samsung.networkaudio.EQname"] | ||||
|         self.__supported_eq_presets = payload[ | ||||
|             "x.com.samsung.networkaudio.supportedList" | ||||
|         ] | ||||
|         self.__eq_action = payload["x.com.samsung.networkaudio.action"] | ||||
|         self.__eq_bands = payload["x.com.samsung.networkaudio.EQband"] | ||||
| 
 | ||||
|     async def _update_advanced_audio(self): | ||||
|         await self.update_execution_data(["/sec/networkaudio/advancedaudio"]) | ||||
|         payload = await self.get_execute_status() | ||||
|         self.__night_mode = payload["x.com.samsung.networkaudio.nightmode"] | ||||
|         self.__bass_mode = payload["x.com.samsung.networkaudio.bassboost"] | ||||
|         self.__voice_amplifier = payload["x.com.samsung.networkaudio.voiceamplifier"] | ||||
| 
 | ||||
|     @property | ||||
|     def status(self): | ||||
|         return self.device.status | ||||
| 
 | ||||
|     # ------------ DEVICE INFORMATION ---------- | ||||
| 
 | ||||
|     @property | ||||
|     def manufacturer(self): | ||||
|         return self.device.status.ocf_manufacturer_name | ||||
| 
 | ||||
|     @property | ||||
|     def model(self): | ||||
|         return self.device.status.ocf_model_number | ||||
| 
 | ||||
|     @property | ||||
|     def firmware_version(self): | ||||
|         return self.device.status.ocf_firmware_version | ||||
| 
 | ||||
|     @property | ||||
|     def device_id(self): | ||||
|         return self.device.device_id | ||||
| 
 | ||||
|     @property | ||||
|     def device_name(self): | ||||
|         return self.__device_name | ||||
| 
 | ||||
|     # ------------ ON / OFF ------------ | ||||
| 
 | ||||
|     @property | ||||
|     def state(self) -> str: | ||||
|         return "on" if self.device.status.switch else "off" | ||||
| 
 | ||||
|     async def switch_off(self): | ||||
|         await self.device.switch_off(True) | ||||
| 
 | ||||
|     async def switch_on(self): | ||||
|         await self.device.switch_on(True) | ||||
| 
 | ||||
|     # ------------ VOLUME -------------- | ||||
| 
 | ||||
|     @property | ||||
|     def volume_level(self) -> float: | ||||
|         return ((self.device.status.volume / 100) * self.__max_volume) / 100 | ||||
| 
 | ||||
|     @property | ||||
|     def volume_muted(self) -> bool: | ||||
|         return self.device.status.mute | ||||
| 
 | ||||
|     async def set_volume(self, volume: float): | ||||
|         """ | ||||
|         Sets the volume to a certain level. | ||||
|         This respects the max volume and hovers between | ||||
|         :param volume: between 0 and 1 | ||||
|         """ | ||||
|         await self.device.set_volume(int(volume * self.__max_volume)) | ||||
| 
 | ||||
|     async def mute_volume(self, mute: bool): | ||||
|         if mute: | ||||
|             await self.device.unmute(True) | ||||
|         else: | ||||
|             await self.device.mute(True) | ||||
| 
 | ||||
|     async def volume_up(self): | ||||
|         await self.device.volume_up(True) | ||||
| 
 | ||||
|     async def volume_down(self): | ||||
|         await self.device.volume_down(True) | ||||
| 
 | ||||
|     # ------------ WOOFER LEVEL ------------- | ||||
| 
 | ||||
|     @property | ||||
|     def woofer_level(self) -> int: | ||||
|         return self.__woofer_level | ||||
| 
 | ||||
|     @property | ||||
|     def woofer_connection(self) -> str: | ||||
|         return self.__woofer_connection | ||||
| 
 | ||||
|     async def set_woofer(self, level: int): | ||||
|         await self.set_custom_execution_data( | ||||
|             href="/sec/networkaudio/woofer", | ||||
|             property="x.com.samsung.networkaudio.woofer", | ||||
|             value=level, | ||||
|         ) | ||||
| 
 | ||||
|     # ------------ INPUT SOURCE ------------- | ||||
| 
 | ||||
|     @property | ||||
|     def input_source(self): | ||||
|         return self.device.status.input_source | ||||
| 
 | ||||
|     @property | ||||
|     def supported_input_sources(self): | ||||
|         return self.device.status.supported_input_sources | ||||
| 
 | ||||
|     async def select_source(self, source: str): | ||||
|         await self.device.set_input_source(source, True) | ||||
| 
 | ||||
|     # ------------- SOUND MODE -------------- | ||||
|     @property | ||||
|     def sound_mode(self): | ||||
|         return self.__active_soundmode | ||||
| 
 | ||||
|     @property | ||||
|     def supported_soundmodes(self): | ||||
|         return self.__supported_soundmodes | ||||
| 
 | ||||
|     async def select_sound_mode(self, sound_mode: str): | ||||
|         await self.set_custom_execution_data( | ||||
|             href="/sec/networkaudio/soundmode", | ||||
|             property="x.com.samsung.networkaudio.soundmode", | ||||
|             value=sound_mode, | ||||
|         ) | ||||
| 
 | ||||
|     # ------------- ADVANCED AUDIO --------------- | ||||
| 
 | ||||
|     @property | ||||
|     def night_mode(self) -> bool: | ||||
|         return True if self.__night_mode == 1 else False | ||||
| 
 | ||||
|     async def set_night_mode(self, value: bool): | ||||
|         await self.set_custom_execution_data( | ||||
|             href="/sec/networkaudio/advancedaudio", | ||||
|             property="x.com.samsung.networkaudio.nightmode", | ||||
|             value=1 if value else 0, | ||||
|         ) | ||||
|         self.__night_mode = 1 if value else 0 | ||||
| 
 | ||||
|     @property | ||||
|     def bass_mode(self) -> bool: | ||||
|         return True if self.__bass_mode == 1 else False | ||||
| 
 | ||||
|     async def set_bass_mode(self, value: bool): | ||||
|         await self.set_custom_execution_data( | ||||
|             href="/sec/networkaudio/advancedaudio", | ||||
|             property="x.com.samsung.networkaudio.bassboost", | ||||
|             value=1 if value else 0, | ||||
|         ) | ||||
|         self.__bass_mode = 1 if value else 0 | ||||
| 
 | ||||
|     @property | ||||
|     def voice_amplifier(self) -> bool: | ||||
|         return True if self.__voice_amplifier == 1 else False | ||||
| 
 | ||||
|     async def set_voice_amplifier(self, value: bool): | ||||
|         await self.set_custom_execution_data( | ||||
|             href="/sec/networkaudio/advancedaudio", | ||||
|             property="x.com.samsung.networkaudio.voiceamplifier", | ||||
|             value=1 if value else 0, | ||||
|         ) | ||||
|         self.__voice_amplifier = 1 if value else 0 | ||||
| 
 | ||||
|     # ------------ EQUALIZER -------------- | ||||
| 
 | ||||
|     @property | ||||
|     def active_equalizer_preset(self): | ||||
|         return self.__active_eq_preset | ||||
| 
 | ||||
|     @property | ||||
|     def supported_equalizer_presets(self): | ||||
|         return self.__supported_eq_presets | ||||
| 
 | ||||
|     @property | ||||
|     def equalizer_action(self): | ||||
|         return self.__eq_action | ||||
| 
 | ||||
|     @property | ||||
|     def equalizer_bands(self): | ||||
|         return self.__eq_bands | ||||
| 
 | ||||
|     async def set_equalizer_preset(self, preset: str): | ||||
|         await self.set_custom_execution_data( | ||||
|             href="/sec/networkaudio/eq", | ||||
|             property="x.com.samsung.networkaudio.EQname", | ||||
|             value=preset, | ||||
|         ) | ||||
| 
 | ||||
|     # ------------- MEDIA ---------------- | ||||
|     @property | ||||
|     def media_title(self): | ||||
|         return self.__media_title | ||||
| 
 | ||||
|     @property | ||||
|     def media_artist(self): | ||||
|         return self.__media_artist | ||||
| 
 | ||||
|     @property | ||||
|     def media_coverart_url(self): | ||||
|         return self.__media_cover_url | ||||
| 
 | ||||
|     @property | ||||
|     def media_duration(self) -> int | None: | ||||
|         return self.device.status.attributes.get("totalTime").value | ||||
| 
 | ||||
|     @property | ||||
|     def media_position(self) -> int | None: | ||||
|         return self.device.status.attributes.get("elapsedTime").value | ||||
| 
 | ||||
|     async def media_play(self): | ||||
|         await self.device.play(True) | ||||
| 
 | ||||
|     async def media_pause(self): | ||||
|         await self.device.pause(True) | ||||
| 
 | ||||
|     async def media_stop(self): | ||||
|         await self.device.stop(True) | ||||
| 
 | ||||
|     @property | ||||
|     def media_app_name(self): | ||||
|         detail_status = self.device.status.attributes.get("detailName", None) | ||||
|         if detail_status is not None: | ||||
|             return detail_status.value | ||||
|         return None | ||||
| 
 | ||||
|     # ------------ SUPPORT FUNCTIONS ------------ | ||||
| 
 | ||||
|     async def update_execution_data(self, argument: str): | ||||
|         return await self.device.command("main", "execute", "execute", argument) | ||||
| 
 | ||||
|     async def set_custom_execution_data(self, href: str, property: str, value): | ||||
|         argument = [href, {property: value}] | ||||
|         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"] | ||||
| 
 | ||||
|     async def get_song_title_artwork(self, artist: str, title: str) -> str: | ||||
|         """ | ||||
|         This function loads a Music Art Cover from iTunes based on | ||||
|         the title and the artist | ||||
|         :param artist: string | ||||
|         :param title: string | ||||
|         :return: url as string | ||||
|         """ | ||||
|         query_term = f"{artist} {title}" | ||||
|         url = "https://itunes.apple.com/search?term=%s&media=music&entity=%s" % ( | ||||
|             quote(query_term), | ||||
|             "musicTrack", | ||||
|         ) | ||||
|         resp = await self.__session.get(url) | ||||
|         resp_dict = json.loads(await resp.text()) | ||||
|         if len(resp_dict["results"]) != 0: | ||||
|             return resp_dict["results"][0]["artworkUrl100"] | ||||
| 
 | ||||
|     @property | ||||
|     def retrieve_data(self): | ||||
|         return { | ||||
|             "status": self.state, | ||||
|             "device_information": { | ||||
|                 "model": self.model, | ||||
|                 "manufacture": self.manufacturer, | ||||
|                 "firmware_version": self.firmware_version, | ||||
|                 "device_id": self.device_id, | ||||
|             }, | ||||
|             "volume": {"level": self.volume_level, "muted": self.volume_muted}, | ||||
|             "woofer": { | ||||
|                 "level": self.woofer_level, | ||||
|                 "connection": self.woofer_connection, | ||||
|             }, | ||||
|             "source": { | ||||
|                 "active_source": self.input_source, | ||||
|                 "supported_sources": self.supported_input_sources, | ||||
|             }, | ||||
|             "sound_mode": { | ||||
|                 "active_sound_mode": self.sound_mode, | ||||
|                 "supported_sound_modes": self.supported_soundmodes, | ||||
|             }, | ||||
|             "advanced_audio": { | ||||
|                 "night_mode": self.night_mode, | ||||
|                 "bass_mode": self.bass_mode, | ||||
|                 "voice_amplifier": self.voice_amplifier, | ||||
|             }, | ||||
|             "equalizer": { | ||||
|                 "active_preset": self.active_equalizer_preset, | ||||
|                 "supported_presets": self.supported_equalizer_presets, | ||||
|                 "action": self.equalizer_action, | ||||
|                 "bands": self.equalizer_bands, | ||||
|             }, | ||||
|             "media": { | ||||
|                 "media_title": self.media_title, | ||||
|                 "media_artist": self.media_artist, | ||||
|                 "media_cover_url": self.media_coverart_url, | ||||
|                 "media_duration": self.media_duration, | ||||
|                 "media_position": self.media_position, | ||||
|             }, | ||||
|         } | ||||
|  | @ -0,0 +1,59 @@ | |||
| import logging | ||||
| 
 | ||||
| import pysmartthings | ||||
| import voluptuous as vol | ||||
| from homeassistant import config_entries | ||||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||||
| from pysmartthings import APIResponseError | ||||
| 
 | ||||
| from .const import ( | ||||
|     CONF_ENTRY_API_KEY, | ||||
|     CONF_ENTRY_DEVICE_ID, | ||||
|     CONF_ENTRY_DEVICE_NAME, | ||||
|     CONF_ENTRY_MAX_VOLUME, | ||||
|     DOMAIN, | ||||
| ) | ||||
| 
 | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| async def validate_input(api, device_id: str): | ||||
|     try: | ||||
|         return await api.device(device_id) | ||||
|     except APIResponseError as excp: | ||||
|         _LOGGER.error("[Samsung Soundbar] ERROR: %s", str(excp)) | ||||
|         raise ValueError | ||||
| 
 | ||||
| 
 | ||||
| class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||||
|     async def async_step_user(self, user_input=None): | ||||
|         _LOGGER.error(f"Example Flow starts with user_input {user_input}") | ||||
|         if user_input is not None: | ||||
|             _LOGGER.error(f"User Input is not filled") | ||||
|             try: | ||||
|                 session = async_get_clientsession(self.hass) | ||||
|                 api = pysmartthings.SmartThings( | ||||
|                     session, user_input.get(CONF_ENTRY_API_KEY) | ||||
|                 ) | ||||
|                 _LOGGER.error(f"Validating Input {user_input}") | ||||
|                 device = await validate_input(api, user_input.get(CONF_ENTRY_DEVICE_ID)) | ||||
| 
 | ||||
|                 _LOGGER.error( | ||||
|                     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"Example Flow triggered an exception {excp}") | ||||
|                 return self.async_abort(reason="fetch_failed") | ||||
| 
 | ||||
|         return self.async_show_form( | ||||
|             step_id="user", | ||||
|             data_schema=vol.Schema( | ||||
|                 { | ||||
|                     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): int, | ||||
|                 } | ||||
|             ), | ||||
|         ) | ||||
|  | @ -0,0 +1,21 @@ | |||
| from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN | ||||
| from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN | ||||
| from homeassistant.components.select import DOMAIN as SELECT_DOMAIN | ||||
| from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN | ||||
| 
 | ||||
| DOMAIN = "samsung_soundbar" | ||||
| CONF_CLOUD_INTEGRATION = "cloud_integration" | ||||
| CONF_ENTRY_API_KEY = "api_key" | ||||
| CONF_ENTRY_DEVICE_ID = "device_id" | ||||
| CONF_ENTRY_DEVICE_NAME = "device_name" | ||||
| CONF_ENTRY_MAX_VOLUME = "device_volume" | ||||
| DEFAULT_NAME = DOMAIN | ||||
| 
 | ||||
| BUTTON = BUTTON_DOMAIN | ||||
| SWITCH = SWITCH_DOMAIN | ||||
| MEDIA_PLAYER = MEDIA_PLAYER_DOMAIN | ||||
| SELECT = SELECT_DOMAIN | ||||
| SUPPORTED_DOMAINS = ["media_player", "switch"] | ||||
| 
 | ||||
| 
 | ||||
| PLATFORMS = [SWITCH, MEDIA_PLAYER, SELECT, BUTTON] | ||||
|  | @ -0,0 +1,50 @@ | |||
| import logging | ||||
| 
 | ||||
| from homeassistant.components.image import ImageEntity | ||||
| from homeassistant.core import HomeAssistant | ||||
| from homeassistant.helpers.entity import DeviceInfo | ||||
| 
 | ||||
| from .models import DeviceConfig | ||||
| from .api_extension.SoundbarDevice import SoundbarDevice | ||||
| from .const import DOMAIN, CONF_ENTRY_DEVICE_ID | ||||
| 
 | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| async def async_setup_entry(hass, config_entry, async_add_entities): | ||||
|     domain_data = hass.data[DOMAIN] | ||||
| 
 | ||||
|     entities = [] | ||||
|     for key in domain_data.devices: | ||||
|         device_config: DeviceConfig = domain_data.devices[key] | ||||
|         device = device_config.device | ||||
|         if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): | ||||
|             entities.append(SoundbarImageEntity(device, "Image URL", hass)) | ||||
|     async_add_entities(entities) | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| class SoundbarImageEntity(ImageEntity): | ||||
|     def __init__( | ||||
|         self, device: SoundbarDevice, append_unique_id: str, hass: HomeAssistant | ||||
|     ): | ||||
|         super().__init__(hass) | ||||
|         self.entity_id = f"image.{device.device_name}_{append_unique_id}" | ||||
| 
 | ||||
|         self.__device = device | ||||
|         self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}" | ||||
|         self._attr_device_info = DeviceInfo( | ||||
|             identifiers={(DOMAIN, self.__device.device_id)}, | ||||
|             name=self.__device.device_name, | ||||
|             manufacturer=self.__device.manufacturer, | ||||
|             model=self.__device.model, | ||||
|             sw_version=self.__device.firmware_version, | ||||
|         ) | ||||
| 
 | ||||
|         self._attr_image_url = self.__device.media_coverart_url | ||||
| 
 | ||||
|     # ---------- GENERAL --------------- | ||||
| 
 | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self.__device.device_name | ||||
|  | @ -0,0 +1,12 @@ | |||
| { | ||||
|   "domain": "samsung_soundbar", | ||||
|   "name": "Samsung Soundbar", | ||||
|   "version": "0.1.0", | ||||
|   "codeowners": ['@samuelspagl'], | ||||
|   "dependencies": ['pysmartthings'], | ||||
|   "documentation": "https://www.example.com", | ||||
|   "integration_type": "hub", | ||||
|   "iot_class": "cloud_polling", | ||||
|   "requirements": [], | ||||
|   "config_flow": true | ||||
| } | ||||
|  | @ -0,0 +1,191 @@ | |||
| import logging | ||||
| 
 | ||||
| from homeassistant.components.media_player import ( | ||||
|     DEVICE_CLASS_SPEAKER, | ||||
|     MediaPlayerEntity, | ||||
| ) | ||||
| from homeassistant.components.media_player.const import ( | ||||
|     SUPPORT_PAUSE, | ||||
|     SUPPORT_PLAY, | ||||
|     SUPPORT_SELECT_SOUND_MODE, | ||||
|     SUPPORT_SELECT_SOURCE, | ||||
|     SUPPORT_STOP, | ||||
|     SUPPORT_TURN_OFF, | ||||
|     SUPPORT_TURN_ON, | ||||
|     SUPPORT_VOLUME_MUTE, | ||||
|     SUPPORT_VOLUME_SET, | ||||
|     SUPPORT_VOLUME_STEP, | ||||
| ) | ||||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||||
| from homeassistant.helpers.entity import DeviceInfo, generate_entity_id | ||||
| 
 | ||||
| from .models import DeviceConfig | ||||
| 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, | ||||
| ) | ||||
| 
 | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| DEFAULT_NAME = "SmartThings Soundbar" | ||||
| CONF_MAX_VOLUME = "max_volume" | ||||
| 
 | ||||
| SUPPORT_SMARTTHINGS_SOUNDBAR = ( | ||||
|     SUPPORT_PAUSE | ||||
|     | SUPPORT_VOLUME_STEP | ||||
|     | SUPPORT_VOLUME_MUTE | ||||
|     | SUPPORT_VOLUME_SET | ||||
|     | SUPPORT_SELECT_SOURCE | ||||
|     | SUPPORT_TURN_OFF | ||||
|     | SUPPORT_TURN_ON | ||||
|     | SUPPORT_PLAY | ||||
|     | SUPPORT_STOP | ||||
|     | SUPPORT_SELECT_SOUND_MODE | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| async def async_setup_entry(hass, config_entry, async_add_entities): | ||||
|     domain_data = hass.data[DOMAIN] | ||||
| 
 | ||||
|     entities = [] | ||||
|     for key in domain_data.devices: | ||||
|         device_config: DeviceConfig = domain_data.devices[key] | ||||
|         session = async_get_clientsession(hass) | ||||
|         device = device_config.device | ||||
|         if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): | ||||
|             entity_id = generate_entity_id( | ||||
|                 "media_player.{}", device.device_name, hass=hass | ||||
|             ) | ||||
|             entities.append(SmartThingsSoundbarMediaPlayer(device, entity_id, session)) | ||||
|     async_add_entities(entities) | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| class SmartThingsSoundbarMediaPlayer(MediaPlayerEntity): | ||||
|     def __init__(self, device: SoundbarDevice, entity_id: str, session): | ||||
|         self.session = session | ||||
|         self.device = device | ||||
|         self.entity_id = entity_id | ||||
|         self._attr_unique_id = f"{self.device.device_id}_mp" | ||||
| 
 | ||||
|         self._attr_device_info = DeviceInfo( | ||||
|             identifiers={(DOMAIN, self.device.device_id)}, | ||||
|             name=self.device.device_name, | ||||
|             manufacturer=self.device.manufacturer, | ||||
|             model=self.device.model, | ||||
|             sw_version=self.device.firmware_version, | ||||
|         ) | ||||
| 
 | ||||
|     async def async_update(self): | ||||
|         await self.device.update() | ||||
| 
 | ||||
|     # ---------- GENERAL SETTINGS ------------ | ||||
| 
 | ||||
|     @property | ||||
|     def device_class(self): | ||||
|         return DEVICE_CLASS_SPEAKER | ||||
| 
 | ||||
|     @property | ||||
|     def supported_features(self): | ||||
|         return SUPPORT_SMARTTHINGS_SOUNDBAR | ||||
| 
 | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self.device.device_name | ||||
| 
 | ||||
|     # ---------- POWER ON/OFF ------------ | ||||
| 
 | ||||
|     @property | ||||
|     def state(self): | ||||
|         return self.device.state | ||||
| 
 | ||||
|     async def async_turn_off(self): | ||||
|         await self.device.switch_off() | ||||
| 
 | ||||
|     async def async_turn_on(self): | ||||
|         await self.device.switch_on() | ||||
| 
 | ||||
|     # ---------- VOLUME ------------ | ||||
|     @property | ||||
|     def volume_level(self): | ||||
|         return self.device.volume_level | ||||
| 
 | ||||
|     @property | ||||
|     def is_volume_muted(self): | ||||
|         return self.device.volume_muted | ||||
| 
 | ||||
|     async def async_set_volume_level(self, volume): | ||||
|         await self.device.set_volume(volume) | ||||
| 
 | ||||
|     async def async_mute_volume(self, mute): | ||||
|         await self.device.mute_volume(mute) | ||||
| 
 | ||||
|     async def async_volume_up(self): | ||||
|         await self.device.volume_up() | ||||
| 
 | ||||
|     async def async_volume_down(self): | ||||
|         await self.device.volume_down() | ||||
| 
 | ||||
|     # ---------- INPUT SOURCES ------------ | ||||
| 
 | ||||
|     @property | ||||
|     def source(self): | ||||
|         return self.device.input_source | ||||
| 
 | ||||
|     @property | ||||
|     def source_list(self): | ||||
|         return self.device.supported_input_sources | ||||
| 
 | ||||
|     async def async_select_source(self, source): | ||||
|         await self.device.select_source(source) | ||||
| 
 | ||||
|     # ---------- SOUND MODE ------------ | ||||
| 
 | ||||
|     @property | ||||
|     def sound_mode(self) -> str | None: | ||||
|         return self.device.sound_mode | ||||
| 
 | ||||
|     @property | ||||
|     def sound_mode_list(self) -> list[str] | None: | ||||
|         return self.device.supported_soundmodes | ||||
| 
 | ||||
|     async def async_select_sound_mode(self, sound_mode): | ||||
|         await self.device.select_sound_mode(sound_mode) | ||||
| 
 | ||||
|     # ---------- MEDIA ------------ | ||||
|     @property | ||||
|     def media_title(self): | ||||
|         return self.device.media_title | ||||
| 
 | ||||
|     @property | ||||
|     def media_artist(self) -> str | None: | ||||
|         return self.device.media_artist | ||||
| 
 | ||||
|     @property | ||||
|     def media_duration(self) -> int | None: | ||||
|         return self.device.media_duration | ||||
| 
 | ||||
|     @property | ||||
|     def media_position(self): | ||||
|         return self.device.media_position | ||||
| 
 | ||||
|     @property | ||||
|     def media_image_url(self) -> str | None: | ||||
|         return self.device.media_coverart_url | ||||
| 
 | ||||
|     @property | ||||
|     def app_name(self) -> str | None: | ||||
|         return self.device.media_app_name | ||||
| 
 | ||||
|     async def async_media_play(self): | ||||
|         await self.device.media_play() | ||||
| 
 | ||||
|     async def async_media_pause(self): | ||||
|         await self.device.media_pause() | ||||
| 
 | ||||
|     async def async_media_stop(self): | ||||
|         await self.device.media_stop() | ||||
|  | @ -0,0 +1,17 @@ | |||
| from dataclasses import dataclass | ||||
| 
 | ||||
| from pysmartthings import SmartThings | ||||
| 
 | ||||
| from .api_extension.SoundbarDevice import SoundbarDevice | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class DeviceConfig: | ||||
|     config: dict | ||||
|     device: SoundbarDevice | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class SoundbarConfig: | ||||
|     api: SmartThings | ||||
|     devices: dict | ||||
|  | @ -0,0 +1,77 @@ | |||
| import logging | ||||
| 
 | ||||
| from homeassistant.components.number import NumberEntity | ||||
| from homeassistant.helpers.entity import DeviceInfo | ||||
| 
 | ||||
| from .models import DeviceConfig | ||||
| from .api_extension.SoundbarDevice import SoundbarDevice | ||||
| from .const import CONF_ENTRY_DEVICE_ID, DOMAIN | ||||
| 
 | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| async def async_setup_entry(hass, config_entry, async_add_entities): | ||||
|     domain_data = hass.data[DOMAIN] | ||||
| 
 | ||||
|     entities = [] | ||||
|     for key in domain_data.devices: | ||||
|         device_config: DeviceConfig = domain_data.devices[key] | ||||
|         device = device_config.device | ||||
|         if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): | ||||
|             entities.append( | ||||
|                 SoundbarNumberEntity( | ||||
|                     device, | ||||
|                     "woofer_level", | ||||
|                     device.woofer_level, | ||||
|                     device.set_woofer, | ||||
|                     (-6, 12), | ||||
|                 ) | ||||
|             ) | ||||
|     async_add_entities(entities) | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| class SoundbarNumberEntity(NumberEntity): | ||||
|     def __init__( | ||||
|         self, | ||||
|         device: SoundbarDevice, | ||||
|         append_unique_id: str, | ||||
|         state_function, | ||||
|         on_function, | ||||
|         min_max: tuple, | ||||
|     ): | ||||
|         self.entity_id = f"number.{device.device_name}_{append_unique_id}" | ||||
| 
 | ||||
|         self.__device = device | ||||
|         self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}" | ||||
|         self._attr_device_info = DeviceInfo( | ||||
|             identifiers={(DOMAIN, self.__device.device_id)}, | ||||
|             name=self.__device.device_name, | ||||
|             manufacturer=self.__device.manufacturer, | ||||
|             model=self.__device.model, | ||||
|             sw_version=self.__device.firmware_version, | ||||
|         ) | ||||
| 
 | ||||
|         self.__current_value_function = state_function | ||||
|         self.__set_value_function = on_function | ||||
|         self.__min_value = min_max[0] | ||||
|         self.__max_value = min_max[1] | ||||
| 
 | ||||
|     # ---------- GENERAL --------------- | ||||
| 
 | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self.__device.device_name | ||||
| 
 | ||||
|     # ------ STATE FUNCTIONS -------- | ||||
| 
 | ||||
|     @property | ||||
|     def native_value(self) -> float | None: | ||||
|         return self.__current_value_function | ||||
| 
 | ||||
|     async def async_set_native_value(self, value: float): | ||||
|         if value > self.__max_value: | ||||
|             value = self.__min_value | ||||
|         if value < self.__min_value: | ||||
|             value = self.__min_value | ||||
|         await self.__set_value_function(value) | ||||
|  | @ -0,0 +1,99 @@ | |||
| import logging | ||||
| 
 | ||||
| from homeassistant.components.switch import SwitchEntity | ||||
| from homeassistant.helpers.entity import DeviceInfo | ||||
| 
 | ||||
| from .models import DeviceConfig | ||||
| from .api_extension.SoundbarDevice import SoundbarDevice | ||||
| from .const import CONF_ENTRY_DEVICE_ID, DOMAIN | ||||
| 
 | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| async def async_setup_entry(hass, config_entry, async_add_entities): | ||||
|     domain_data = hass.data[DOMAIN] | ||||
| 
 | ||||
|     entities = [] | ||||
|     for key in domain_data.devices: | ||||
|         device_config: DeviceConfig = domain_data.devices[key] | ||||
|         device = device_config.device | ||||
|         if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID): | ||||
|             entities.append( | ||||
|                 SoundbarSwitchAdvancedAudio( | ||||
|                     device, | ||||
|                     "nightmode", | ||||
|                     lambda: device.night_mode, | ||||
|                     device.set_night_mode, | ||||
|                     device.set_night_mode, | ||||
|                 ) | ||||
|             ) | ||||
|             entities.append( | ||||
|                 SoundbarSwitchAdvancedAudio( | ||||
|                     device, | ||||
|                     "bassmode", | ||||
|                     lambda: device.bass_mode, | ||||
|                     device.set_bass_mode, | ||||
|                     device.set_bass_mode, | ||||
|                 ) | ||||
|             ) | ||||
|             entities.append( | ||||
|                 SoundbarSwitchAdvancedAudio( | ||||
|                     device, | ||||
|                     "voice_amplifier", | ||||
|                     lambda: device.voice_amplifier, | ||||
|                     device.set_voice_amplifier, | ||||
|                     device.set_voice_amplifier, | ||||
|                 ) | ||||
|             ) | ||||
|     async_add_entities(entities) | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| class SoundbarSwitchAdvancedAudio(SwitchEntity): | ||||
|     def __init__( | ||||
|         self, | ||||
|         device: SoundbarDevice, | ||||
|         append_unique_id: str, | ||||
|         state_function, | ||||
|         on_function, | ||||
|         off_function, | ||||
|     ): | ||||
|         self.entity_id = f"switch.{device.device_name}_{append_unique_id}" | ||||
| 
 | ||||
|         self.__device = device | ||||
|         self._name = f"{self.__device.device_name} {append_unique_id}" | ||||
|         self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}" | ||||
|         self._attr_device_info = DeviceInfo( | ||||
|             identifiers={(DOMAIN, self.__device.device_id)}, | ||||
|             name=self.__device.device_name, | ||||
|             manufacturer=self.__device.manufacturer, | ||||
|             model=self.__device.model, | ||||
|             sw_version=self.__device.firmware_version, | ||||
|         ) | ||||
| 
 | ||||
|         self.__state_function = state_function | ||||
|         self.__state = False | ||||
|         self.__on_function = on_function | ||||
|         self.__off_function = off_function | ||||
| 
 | ||||
|     # ---------- GENERAL --------------- | ||||
| 
 | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self._name | ||||
| 
 | ||||
|     def update(self): | ||||
|         self.__state = self.__state_function() | ||||
| 
 | ||||
|     # ------ STATE FUNCTIONS -------- | ||||
|     @property | ||||
|     def state(self): | ||||
|         return "on" if self.__state else "off" | ||||
| 
 | ||||
|     async def async_turn_off(self): | ||||
|         await self.__off_function(False) | ||||
|         self.__state = "off" | ||||
| 
 | ||||
|     async def async_turn_on(self): | ||||
|         await self.__on_function(True) | ||||
|         self.__state = "on" | ||||
|  | @ -0,0 +1,16 @@ | |||
| { | ||||
|     "config":{ | ||||
|         "step":{ | ||||
|             "user":{ | ||||
|                 "data": { | ||||
|                     "api_key": "SmartThings API Token", | ||||
|                     "device_id": "Device ID", | ||||
|                     "device_name":"Device Name", | ||||
|                     "device_volume": "Max Volume (int)" | ||||
|                   }, | ||||
|                   "description": "Bitte gib deine Daten ein.", | ||||
|                   "title": "Authentifizierung" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,16 @@ | |||
| { | ||||
|     "config":{ | ||||
|         "step":{ | ||||
|             "user":{ | ||||
|                 "data": { | ||||
|                     "api_key": "SmartThings API Token", | ||||
|                     "device_id": "Device ID", | ||||
|                     "device_name":"Device Name", | ||||
|                     "device_volume": "Max Volume (int)" | ||||
|                   }, | ||||
|                   "description": "Please enter your credentials.", | ||||
|                   "title": "Authentication" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue