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