🚀: Initial Commit
This commit is contained in:
		
							parent
							
								
									d96323fd81
								
							
						
					
					
						commit
						0f43da8f1e
					
				|  | @ -0,0 +1,29 @@ | ||||||
|  | name: Validate HACS | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |   pull_request: | ||||||
|  | jobs: | ||||||
|  |   ci: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |         name: Download repo | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  | 
 | ||||||
|  |       - uses: actions/setup-python@v2 | ||||||
|  |         name: Setup Python | ||||||
|  |         with: | ||||||
|  |           python-version: '3.8.x' | ||||||
|  | 
 | ||||||
|  |       - uses: actions/cache@v2 | ||||||
|  |         name: Cache | ||||||
|  |         with: | ||||||
|  |           path: | | ||||||
|  |             ~/.cache/pip | ||||||
|  |           key: custom-component-ci | ||||||
|  | 
 | ||||||
|  |       - name: HACS Action | ||||||
|  |         uses: hacs/action@main | ||||||
|  |         with: | ||||||
|  |           CATEGORY: integration | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | name: Validate with hassfest | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |   pull_request: | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   validate: | ||||||
|  |     runs-on: "ubuntu-latest" | ||||||
|  |     steps: | ||||||
|  |       - uses: "actions/checkout@v2" | ||||||
|  |       - uses: home-assistant/actions/hassfest@master | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | name: Release | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   release: | ||||||
|  |     types: [published] | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   release: | ||||||
|  |     name: Prepare release | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Download repo | ||||||
|  |         uses: actions/checkout@v1 | ||||||
|  | 
 | ||||||
|  |       - name: Zip samsung_soundbar dir | ||||||
|  |         run: | | ||||||
|  |           cd /home/runner/work/ha_samsung_soundbar/ha_samsung_soundbar/custom_components/samsung_soundbar | ||||||
|  |           zip samsung_soundbar.zip -r ./ | ||||||
|  | 
 | ||||||
|  |       - name: Upload zip to release | ||||||
|  |         uses: svenstaro/upload-release-action@v1-release | ||||||
|  |         with: | ||||||
|  |           repo_token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |           file: /home/runner/work/ha_samsung_soundbar/ha_samsung_soundbar/custom_components/samsung_soundbar/samsung_soundbar.zip | ||||||
|  |           asset_name: samsung_soundbar.zip | ||||||
|  |           tag: ${{ github.ref }} | ||||||
|  |           overwrite: true | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | name: Validate | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |   pull_request: | ||||||
|  |   schedule: | ||||||
|  |     - cron: "0 0 * * *" | ||||||
|  |   workflow_dispatch: | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   validate-hacs: | ||||||
|  |     runs-on: "ubuntu-latest" | ||||||
|  |     steps: | ||||||
|  |       - uses: "actions/checkout@v3" | ||||||
|  |       - name: HACS validation | ||||||
|  |         uses: "hacs/action@main" | ||||||
|  |         with: | ||||||
|  |           category: "integration" | ||||||
|  | @ -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