Compare commits
20 Commits
feature/fi
...
main
Author | SHA1 | Date |
---|---|---|
|
c113688986 | |
|
8cd2aa3d51 | |
|
bd313ea27a | |
|
0d2424b578 | |
|
9bc8be7861 | |
|
14e30ba970 | |
|
430f6a1840 | |
|
3bcabb8c77 | |
|
5e24680d5d | |
|
dd61dec79f | |
|
30ef090d9e | |
|
78e825157e | |
|
79261aa4d1 | |
|
0c9a317a7e | |
|
bfe269f608 | |
|
f93019dd68 | |
|
b7ff6d1eb0 | |
|
21a74e55db | |
|
31de3bc807 | |
|
728a11c33d |
|
@ -0,0 +1,32 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/python
|
||||
{
|
||||
"name": "Python 3",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "homeassistant/home-assistant:dev",
|
||||
"postCreateCommand": "scripts/setup",
|
||||
"forwardPorts": [
|
||||
8123
|
||||
],
|
||||
"portsAttributes": {
|
||||
"8123": {
|
||||
"label": "Home Assistant",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
}
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "pip3 install --user -r requirements.txt",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
ko_fi: samuelspagl
|
|
@ -0,0 +1,37 @@
|
|||
name: "Validate"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: "0 0 * * *"
|
||||
# push:
|
||||
# branches:
|
||||
# - "main"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
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@v4.1.0"
|
||||
|
||||
- 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,167 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.DS_store
|
||||
|
||||
config
|
||||
.vscode
|
||||
.ruff_cache
|
||||
|
||||
|
||||
# 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,93 @@
|
|||
# Changelog
|
||||
|
||||
## [0.4.1] Media Mystique: The Great Data Disappearing Act!
|
||||
|
||||
### Fixed
|
||||
|
||||
- Made media data (*track title*, *artist*, *length*) optional to acoomodate soundbars that don't provide this information (🥲)
|
||||
|
||||
### Added
|
||||
|
||||
- Add translations for the english translation file
|
||||
|
||||
## [0.4.0] Started with an "ick", but is now packed with new features 💪
|
||||
|
||||
> ⚠️ Please read the following carefully:
|
||||
> This release is a bit special. As "something" on Samsung's side changed,
|
||||
> it is currently not possible to retrieve the status of "custom capabilities", eg.
|
||||
> woofer, soundmode, eq, and others. Therefore I decided to give the option to
|
||||
> disable the entities of these features as the value of these entities is not trustworthy.
|
||||
> Instead I implemented all of these and more (thanks to @whitebearded) as service calls.
|
||||
> Have fun using them!
|
||||
|
||||
### Added
|
||||
|
||||
- Configuration flow options for enable / disable
|
||||
- "advanced audio" features (NightMode, Bassmode, VoiceEnhancer)
|
||||
- "woofer" feature
|
||||
- "soundmode" feature
|
||||
- "eq" feature
|
||||
- added `media_player` support for next and previous track
|
||||
- Service calls for:
|
||||
- "advanced audio" features (NightMode, Bassmode, VoiceEnhancer)
|
||||
- "woofer" feature
|
||||
- "soundmode" feature
|
||||
- "speaker_level"
|
||||
- "rear_speaker_mode"
|
||||
- "space_fit_sound"
|
||||
- "active_voice_amplifier"
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed state, also displaying "playing" and "paused" values
|
||||
|
||||
## [0.3.2] Fix division by zero
|
||||
|
||||
### Added
|
||||
|
||||
- The config flow now also checks whether the `int` provided for `CONF_ENTRY_MAX_VOLUME` is
|
||||
greater than `1` and lower than `100`. This will make sure that a division by zero cannot happen.
|
||||
- Add default value `100` to `CONF_ENTRY_MAX_VOLUME`
|
||||
|
||||
## [0.3.1] Documentation enhancements
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the `README` as well as the documentation website
|
||||
|
||||
## [0.3.0] Icons and Chore
|
||||
|
||||
### Added
|
||||
|
||||
- Icons for the individual entities
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the GitHub actions workflows
|
||||
- Change "magic numbers" to `MediaPlayerEntityFeature` object
|
||||
For more information see https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation
|
||||
- the `source` now returns the value `wifi` when the `media_app_name` is `AirPlay` or `Spotify`
|
||||
- removed some unnecessary logging statements, and changed others to `debug`
|
||||
|
||||
## [0.2.1] Chore: Format repository - 2024-02-08
|
||||
|
||||
### Changed
|
||||
|
||||
- formatted the repository with black and isort
|
||||
|
||||
## [0.2.0] Add volume sensor - 2024-02-08
|
||||
|
||||
### Added
|
||||
|
||||
- add new sensor entity for the volume
|
||||
|
||||
### Fix
|
||||
|
||||
- remove `extra_state_attributes` from `media_player` instance:
|
||||
The property caused some unwanted side-effects on some systems.
|
||||
|
||||
## [0.1.0] 🎉 First Version
|
||||
|
||||
### Added
|
||||
|
||||
- first version, gonna extend this Changelog sometime :D
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Samuel Spagl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,15 @@
|
|||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
pysmartthings = "*"
|
||||
rich = "*"
|
||||
homeassistant = "*"
|
||||
|
||||
[dev-packages]
|
||||
ruff = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.12"
|
File diff suppressed because it is too large
Load Diff
93
README.md
93
README.md
|
@ -1,6 +1,22 @@
|
|||
# HomeAssistant: Samsung Soundbar Integration
|
||||
# YASSI: Yet Another Samsung Soundbar Integration (for Home Assistant)
|
||||
|
||||
> Yet another Samsung Soundbar Integration (YASSI)
|
||||
Welcome to YASSI, the Home Assistant integration designed to bring comprehensive control over your Samsung Soundbar into your smart home ecosystem.
|
||||
|
||||
> [!NOTE]
|
||||
> Please use service calls for setting the attribute of a custom capability instead of the entity. (See #43 for more information)
|
||||
|
||||
**Table of Contents:**
|
||||
<!-- TOC -->
|
||||
* [Why YASSI](#why-yassi)
|
||||
* [Features](#features)
|
||||
* [Installation / Setup](#installation--setup)
|
||||
* [Prerequisites](#prerequisites)
|
||||
* [Installation:](#installation)
|
||||
* [Configuration](#configuration)
|
||||
* [Support](#support)
|
||||
* [Contributing](#contributing)
|
||||
* [General Thanks](#general-thanks)
|
||||
<!-- TOC -->
|
||||
|
||||
## Why YASSI
|
||||
|
||||
|
@ -18,47 +34,54 @@ are not documented... ;)
|
|||
|
||||
## Features
|
||||
|
||||
- Set-Up through HomeAssistant-UI
|
||||
- Theoretically it should be possible to have multiple Devices (not tested)
|
||||
|
||||
- `media_player` Entity
|
||||
- On / Off
|
||||
- Volume
|
||||
- Mute
|
||||
- Input Source
|
||||
- Sound Mode
|
||||
- Media
|
||||
- Play / Pause / Stop
|
||||
- Artist
|
||||
- Title
|
||||
- Music Cover Art url (iTunes Api)
|
||||
- `switch` entity
|
||||
- Night mode
|
||||
- Bass mode
|
||||
- Voice amplifier
|
||||
- `number` entity
|
||||
- bass level
|
||||
- *[to come] equalizer bands*
|
||||
- `select` entity
|
||||
- *[to come] sound mode* (additional control in the "Device" tab)
|
||||
- *[to come] equalizer preset*
|
||||
- **UI Setup**: You can easily set up your Soundbar through the UI.
|
||||
- **Media Player Controls**: Power, volume, mute, source selection, and media controls are all at your fingertips.
|
||||
- **Selectable Sound Modes**: Choose from various sound modes and inputs for optimal audio.
|
||||
- **Subwoofer & Equalizer Adjustment**: Fine-tune your audio experience.
|
||||
- **Switchable Enhancements**: Toggle features like night mode and voice amplification.
|
||||
- **Customizable Bass Level**: Set the bass to your preference.
|
||||
- **Multiple Devices**: should be theoretically possible but **not** tested
|
||||
|
||||
## How to install it:
|
||||
For the full feature list per entity type, please take a look at the [documentation](ha-samsung-soundbar.vercel.app) website.
|
||||
|
||||
### HACS:
|
||||
> ⚠️ not done yet but planned (hopefully)
|
||||
## Installation / Setup
|
||||
|
||||
### Adding this repository as custom repository
|
||||
### Prerequisites
|
||||
|
||||
Add this repository as custom repository in HACS and install it ;)
|
||||
Before you begin, ensure you have the following:
|
||||
|
||||
### Manual
|
||||
- A Samsung Soundbar compatible with SmartThings.
|
||||
- Home Assistant installed and running.
|
||||
- HACS (Home Assistant Community Store) for easy installation.
|
||||
|
||||
You can also copy the `samsung_soundbar` folder in the `custom_components` folder to
|
||||
your `config/custom_components` folder.
|
||||
### Installation
|
||||
|
||||
1. Add this repository as a custom repository in HACS or manually copy the `samsung_soundbar` folder to the `custom_components` directory in your Home Assistant configuration.
|
||||
|
||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=samuelspagl&repository=ha_samsung_soundbar&category=integration)
|
||||
2. Restart Home Assistant.
|
||||
|
||||
> [!NOTE]
|
||||
> It is planned to add it to the default `HACS` repository list, but not done yet.
|
||||
|
||||
### Configuration
|
||||
|
||||
To integrate your Samsung Soundbar with Home Assistant using YASSI, you will be asked for the following variables:
|
||||
|
||||
- **SmartThings API Key**: [Retrieve your API key from SmartThings Tokens.](https://account.smartthings.com/tokens)
|
||||
- **Device ID**: [Find your device ID at SmartThings Devices.](https://my.smartthings.com/advanced/devices)
|
||||
- **Device Name**: Choose a name for your soundbar to be recognized in Home Assistant.
|
||||
- **Max Volume**: Define the maximum volume level for the `media_player` slider (between `1` and `100`).
|
||||
|
||||
## Support
|
||||
|
||||
For support, feature requests, or bug reporting, please visit the Issues section of this GitHub repository.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
|
||||
|
||||
## General Thanks
|
||||
|
||||
Like already mentioned, thanks to @PiotrMachowski / @thierryBourbon for the general
|
||||
idea on how to do things.
|
||||
- Like already mentioned, thanks to @PiotrMachowski / @thierryBourbon for the general idea on how to do things.
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
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,
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
|
||||
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
|
||||
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
|
||||
CONF_ENTRY_SETTINGS_WOOFER_NUMBER,
|
||||
DOMAIN,
|
||||
SUPPORTED_DOMAINS,
|
||||
)
|
||||
from .models import DeviceConfig, SoundbarConfig
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["media_player", "switch", "image", "number", "select", "sensor"]
|
||||
|
||||
|
||||
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 a ConfigEntry")
|
||||
_LOGGER.debug(
|
||||
f"[{DOMAIN}] Setting up ConfigEntry with the following data: {entry.data}"
|
||||
)
|
||||
if not DOMAIN in hass.data:
|
||||
_LOGGER.debug(f"[{DOMAIN}] Domain not found in hass.data setting default")
|
||||
hass.data[DOMAIN] = SoundbarConfig(
|
||||
SmartThings(
|
||||
async_get_clientsession(hass), entry.data.get(CONF_ENTRY_API_KEY)
|
||||
),
|
||||
{},
|
||||
)
|
||||
|
||||
domain_config: SoundbarConfig = hass.data[DOMAIN]
|
||||
_LOGGER.debug(f"[{DOMAIN}] Retrieved Domain Config: {domain_config}")
|
||||
|
||||
if not entry.data.get(CONF_ENTRY_DEVICE_ID) in domain_config.devices:
|
||||
_LOGGER.info(f"[{DOMAIN}] Setting up new Soundbar device")
|
||||
_LOGGER.debug(
|
||||
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),
|
||||
enable_eq=entry.data.get(CONF_ENTRY_SETTINGS_EQ_SELECTOR),
|
||||
enable_advanced_audio=entry.data.get(
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES
|
||||
),
|
||||
enable_soundmode=entry.data.get(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR),
|
||||
enable_woofer=entry.data.get(CONF_ENTRY_SETTINGS_WOOFER_NUMBER),
|
||||
)
|
||||
await soundbar_device.update()
|
||||
domain_config.devices[entry.data.get(CONF_ENTRY_DEVICE_ID)] = DeviceConfig(
|
||||
entry.data, soundbar_device
|
||||
)
|
||||
_LOGGER.info(f"[{DOMAIN}] Successfully initialized new 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,520 @@
|
|||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
from urllib.parse import quote
|
||||
|
||||
from pysmartthings import DeviceEntity
|
||||
|
||||
from .const import SpeakerIdentifier, RearSpeakerMode
|
||||
from ..const import DOMAIN
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SoundbarDevice:
|
||||
def __init__(
|
||||
self,
|
||||
device: DeviceEntity,
|
||||
session,
|
||||
max_volume: int,
|
||||
device_name: str,
|
||||
enable_eq: bool = False,
|
||||
enable_soundmode: bool = False,
|
||||
enable_advanced_audio: bool = False,
|
||||
enable_woofer: bool = False,
|
||||
):
|
||||
self.device = device
|
||||
self._device_id = self.device.device_id
|
||||
self._api_key = self.device._api.token
|
||||
self.__session = session
|
||||
self.__device_name = device_name
|
||||
|
||||
self.__enable_soundmode = enable_soundmode
|
||||
self.__supported_soundmodes = []
|
||||
self.__active_soundmode = ""
|
||||
|
||||
self.__enable_woofer = enable_woofer
|
||||
self.__woofer_level = 0
|
||||
self.__woofer_connection = ""
|
||||
|
||||
self.__enable_eq = enable_eq
|
||||
self.__active_eq_preset = ""
|
||||
self.__supported_eq_presets = []
|
||||
self.__eq_action = ""
|
||||
self.__eq_bands = []
|
||||
|
||||
self.__enable_advanced_audio = enable_advanced_audio
|
||||
self.__voice_amplifier = 0
|
||||
self.__night_mode = 0
|
||||
self.__bass_mode = 0
|
||||
|
||||
self.__media_title = ""
|
||||
self.__media_artist = ""
|
||||
self.__media_cover_url = ""
|
||||
self.__media_cover_url_update_time: datetime.datetime | None = None
|
||||
self.__old_media_title = ""
|
||||
|
||||
self.__max_volume = max_volume
|
||||
|
||||
async def update(self):
|
||||
await self.device.status.refresh()
|
||||
|
||||
await self._update_media()
|
||||
|
||||
if self.__enable_soundmode:
|
||||
await self._update_soundmode()
|
||||
if self.__enable_advanced_audio:
|
||||
await self._update_advanced_audio()
|
||||
if self.__enable_soundmode:
|
||||
await self._update_woofer()
|
||||
if self.__enable_eq:
|
||||
await self._update_equalizer()
|
||||
|
||||
async def _update_media(self):
|
||||
if "audioTrackData" in self.device.status._attributes:
|
||||
self.__media_artist = self.device.status._attributes["audioTrackData"].value[
|
||||
"artist"
|
||||
]
|
||||
self.__media_title = self.device.status._attributes["audioTrackData"].value[
|
||||
"title"
|
||||
]
|
||||
if self.__media_title != self.__old_media_title:
|
||||
self.__old_media_title = self.__media_title
|
||||
self.__media_cover_url_update_time = datetime.datetime.now()
|
||||
self.__media_cover_url = await self.get_song_title_artwork(
|
||||
self.__media_artist, self.__media_title
|
||||
)
|
||||
|
||||
async def _update_soundmode(self):
|
||||
await self.update_execution_data(["/sec/networkaudio/soundmode"])
|
||||
await asyncio.sleep(1)
|
||||
payload = await self.get_execute_status()
|
||||
retry = 0
|
||||
while (
|
||||
"x.com.samsung.networkaudio.supportedSoundmode" not in payload
|
||||
and retry < 10
|
||||
):
|
||||
await asyncio.sleep(1)
|
||||
payload = await self.get_execute_status()
|
||||
retry += 1
|
||||
if retry == 10:
|
||||
log.error(
|
||||
f"[{DOMAIN}] Error: _update_soundmode exceeded a retry counter of 10"
|
||||
)
|
||||
return
|
||||
|
||||
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"])
|
||||
await asyncio.sleep(0.1)
|
||||
payload = await self.get_execute_status()
|
||||
retry = 0
|
||||
while "x.com.samsung.networkaudio.woofer" not in payload and retry < 10:
|
||||
await asyncio.sleep(0.2)
|
||||
payload = await self.get_execute_status()
|
||||
retry += 1
|
||||
if retry == 10:
|
||||
log.error(
|
||||
f"[{DOMAIN}] Error: _update_woofer exceeded a retry counter of 10"
|
||||
)
|
||||
return
|
||||
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"])
|
||||
await asyncio.sleep(0.1)
|
||||
payload = await self.get_execute_status()
|
||||
retry = 0
|
||||
while "x.com.samsung.networkaudio.EQname" not in payload and retry < 10:
|
||||
await asyncio.sleep(0.2)
|
||||
payload = await self.get_execute_status()
|
||||
retry += 1
|
||||
if retry == 10:
|
||||
log.error(
|
||||
f"[{DOMAIN}] Error: _update_equalizer exceeded a retry counter of 10"
|
||||
)
|
||||
return
|
||||
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"])
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
payload = await self.get_execute_status()
|
||||
retry = 0
|
||||
while "x.com.samsung.networkaudio.nightmode" not in payload and retry < 10:
|
||||
await asyncio.sleep(0.2)
|
||||
payload = await self.get_execute_status()
|
||||
retry += 1
|
||||
if retry == 10:
|
||||
log.error(
|
||||
f"[{DOMAIN}] Error: _update_advanced_audio exceeded a retry counter of 10"
|
||||
)
|
||||
return
|
||||
|
||||
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:
|
||||
if self.device.status.switch:
|
||||
if self.device.status.playback_status == "playing":
|
||||
return "playing"
|
||||
if self.device.status.playback_status == "paused":
|
||||
return "paused"
|
||||
else:
|
||||
return "on"
|
||||
else:
|
||||
return "off"
|
||||
|
||||
async def switch_off(self):
|
||||
await self.device.switch_off(True)
|
||||
|
||||
async def switch_on(self):
|
||||
await self.device.switch_on(True)
|
||||
|
||||
# ------------ VOLUME --------------
|
||||
|
||||
@property
|
||||
def volume_level(self) -> float:
|
||||
vol = self.device.status.volume
|
||||
if vol > self.__max_volume:
|
||||
return 1.0
|
||||
return self.device.status.volume / self.__max_volume
|
||||
|
||||
@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), True)
|
||||
|
||||
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,
|
||||
)
|
||||
self.__woofer_level = level
|
||||
|
||||
# ------------ INPUT SOURCE -------------
|
||||
|
||||
@property
|
||||
def input_source(self):
|
||||
if self.media_app_name in ("AirPlay", "Spotify"):
|
||||
return "wifi"
|
||||
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:
|
||||
attr = self.device.status.attributes.get("totalTime", None)
|
||||
if attr:
|
||||
return attr.value
|
||||
|
||||
@property
|
||||
def media_position(self) -> int | None:
|
||||
attr = self.device.status.attributes.get("elapsedTime", None)
|
||||
if attr:
|
||||
return attr.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)
|
||||
|
||||
async def media_next_track(self):
|
||||
await self.device.command("main", "mediaPlayback", "fastForward")
|
||||
|
||||
async def media_previous_track(self):
|
||||
await self.device.command("main", "mediaPlayback", "rewind")
|
||||
|
||||
@property
|
||||
def media_app_name(self):
|
||||
detail_status = self.device.status.attributes.get("detailName", None)
|
||||
if detail_status is not None:
|
||||
return detail_status.value
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_coverart_updated(self) -> datetime.datetime:
|
||||
return self.__media_cover_url_update_time
|
||||
|
||||
# ------------ Speaker Level ----------------
|
||||
|
||||
async def set_speaker_level(self, speaker: SpeakerIdentifier, level: int):
|
||||
await self.set_custom_execution_data(
|
||||
href="/sec/networkaudio/channelVolume",
|
||||
property="x.com.samsung.networkaudio.channelVolume",
|
||||
value=[{"name": speaker.value, "value": level}],
|
||||
)
|
||||
|
||||
async def set_rear_speaker_mode(self, mode: RearSpeakerMode):
|
||||
await self.set_custom_execution_data(
|
||||
href="/sec/networkaudio/surroundspeaker",
|
||||
property="x.com.samsung.networkaudio.currentRearPosition",
|
||||
value=mode.value,
|
||||
)
|
||||
|
||||
# ------------ OTHER FUNCTIONS ------------
|
||||
|
||||
async def set_active_voice_amplifier(self, enabled: bool):
|
||||
await self.set_custom_execution_data(
|
||||
href="/sec/networkaudio/activeVoiceAmplifier",
|
||||
property="x.com.samsung.networkaudio.activeVoiceAmplifier",
|
||||
value=1 if enabled else 0
|
||||
)
|
||||
|
||||
async def set_space_fit_sound(self, enabled: bool):
|
||||
await self.set_custom_execution_data(
|
||||
href="/sec/networkaudio/spacefitSound",
|
||||
property="x.com.samsung.networkaudio.spacefitSound",
|
||||
value=1 if enabled else 0
|
||||
)
|
||||
|
||||
# ------------ SUPPORT FUNCTIONS ------------
|
||||
|
||||
async def update_execution_data(self, argument: str):
|
||||
stuff = await self.device.command("main", "execute", "execute", argument)
|
||||
return stuff
|
||||
|
||||
async def set_custom_execution_data(self, href: str, property: str, value):
|
||||
argument = [href, {property: value}]
|
||||
assert await self.device.command("main", "execute", "execute", argument)
|
||||
|
||||
async def get_execute_status(self):
|
||||
url = f"https://api.smartthings.com/v1/devices/{self._device_id}/components/main/capabilities/execute/status"
|
||||
request_headers = {"Authorization": "Bearer " + self._api_key}
|
||||
resp = await self.__session.get(url, headers=request_headers)
|
||||
dict_stuff = await resp.json()
|
||||
return dict_stuff["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,15 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class SpeakerIdentifier(Enum):
|
||||
CENTER = "Spk_Center"
|
||||
SIDE = "Spk_Side"
|
||||
WIDE = "Spk_Wide"
|
||||
FRONT_TOP = "Spk_Front_Top"
|
||||
REAR = "Spk_Rear"
|
||||
REAR_TOP = "Spk_Rear_Top"
|
||||
|
||||
|
||||
class RearSpeakerMode(Enum):
|
||||
FRONT = "Front"
|
||||
REAR = "Rear"
|
|
@ -0,0 +1,141 @@
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
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 voluptuous import All, Range
|
||||
|
||||
from .const import (
|
||||
CONF_ENTRY_API_KEY,
|
||||
CONF_ENTRY_DEVICE_ID,
|
||||
CONF_ENTRY_DEVICE_NAME,
|
||||
CONF_ENTRY_MAX_VOLUME,
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
|
||||
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
|
||||
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
|
||||
CONF_ENTRY_SETTINGS_WOOFER_NUMBER,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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):
|
||||
if user_input is not None:
|
||||
self.user_input = user_input
|
||||
return await self.async_step_device()
|
||||
|
||||
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, default=100): All(
|
||||
int, Range(min=1, max=100)
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_device(self, user_input: dict[str, any] | None = None):
|
||||
if user_input is not None:
|
||||
self.user_input.update(user_input)
|
||||
|
||||
try:
|
||||
session = async_get_clientsession(self.hass)
|
||||
api = pysmartthings.SmartThings(
|
||||
session, self.user_input.get(CONF_ENTRY_API_KEY)
|
||||
)
|
||||
device = await validate_input(
|
||||
api, self.user_input.get(CONF_ENTRY_DEVICE_ID)
|
||||
)
|
||||
_LOGGER.debug(
|
||||
f"Successfully validated Input, Creating entry with title {DOMAIN} and data {user_input}"
|
||||
)
|
||||
except Exception as excp:
|
||||
_LOGGER.error(f"The ConfigFlow triggered an exception {excp}")
|
||||
return self.async_abort(reason="fetch_failed")
|
||||
return self.async_create_entry(title=DOMAIN, data=self.user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES): bool,
|
||||
vol.Required(CONF_ENTRY_SETTINGS_EQ_SELECTOR): bool,
|
||||
vol.Required(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR): bool,
|
||||
vol.Required(CONF_ENTRY_SETTINGS_WOOFER_NUMBER): bool,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
self.config_entry = self.hass.config_entries.async_get_entry(
|
||||
self.context["entry_id"]
|
||||
)
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
):
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
errors: dict[str, str] = {}
|
||||
assert self.config_entry
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_update_reload_and_abort(
|
||||
self.config_entry,
|
||||
data={**self.config_entry.data, **user_input},
|
||||
reason="reconfigure_successful",
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
|
||||
default=self.config_entry.data.get(
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES
|
||||
),
|
||||
): bool,
|
||||
vol.Required(
|
||||
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
|
||||
default=self.config_entry.data.get(
|
||||
CONF_ENTRY_SETTINGS_EQ_SELECTOR
|
||||
),
|
||||
): bool,
|
||||
vol.Required(
|
||||
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
|
||||
default=self.config_entry.data.get(
|
||||
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR
|
||||
),
|
||||
): bool,
|
||||
vol.Required(
|
||||
CONF_ENTRY_SETTINGS_WOOFER_NUMBER,
|
||||
default=self.config_entry.data.get(
|
||||
CONF_ENTRY_SETTINGS_WOOFER_NUMBER
|
||||
),
|
||||
): bool,
|
||||
vol.Required(CONF_ENTRY_MAX_VOLUME, default=100): All(
|
||||
int, Range(min=1, max=100)
|
||||
),
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
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"
|
||||
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES = "settings_advanced_audio"
|
||||
CONF_ENTRY_SETTINGS_EQ_SELECTOR = "settings_eq"
|
||||
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR = "settings_soundmode"
|
||||
CONF_ENTRY_SETTINGS_WOOFER_NUMBER = "settings_woofer"
|
||||
|
||||
DEFAULT_NAME = DOMAIN
|
||||
|
||||
BUTTON = BUTTON_DOMAIN
|
||||
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,65 @@
|
|||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.components.image import ImageEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.typing import UndefinedType
|
||||
|
||||
from .api_extension.SoundbarDevice import SoundbarDevice
|
||||
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN
|
||||
from .models import DeviceConfig
|
||||
|
||||
_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.__updated = None
|
||||
|
||||
# ---------- GENERAL ---------------
|
||||
@property
|
||||
def image_url(self) -> str | None | UndefinedType:
|
||||
"""Return URL of image."""
|
||||
return self.__device.media_coverart_url
|
||||
|
||||
@property
|
||||
def image_last_updated(self) -> datetime | None:
|
||||
"""The time when the image was last updated."""
|
||||
current = self.__device.media_coverart_updated
|
||||
if self.__updated != current:
|
||||
self._cached_image = None
|
||||
self.__updated = current
|
||||
return current
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__device.device_name
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"domain": "samsung_soundbar",
|
||||
"name": "Samsung Soundbar",
|
||||
"codeowners": [
|
||||
"@samuelspagl"
|
||||
],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.example.com",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/samuelspagl/ha_samsung_soundbar/issues",
|
||||
"requirements": [
|
||||
"pysmartthings==0.7.8"
|
||||
],
|
||||
"version": "0.4.1"
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
import logging
|
||||
from typing import Any, Mapping
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
DEVICE_CLASS_SPEAKER,
|
||||
MediaPlayerEntity,
|
||||
)
|
||||
from homeassistant.components.media_player.const import MediaPlayerEntityFeature
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import DeviceInfo, generate_entity_id
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform, selector
|
||||
import voluptuous as vol
|
||||
|
||||
from .api_extension.SoundbarDevice import SoundbarDevice
|
||||
from .api_extension.const import SpeakerIdentifier, RearSpeakerMode
|
||||
from .const import (
|
||||
CONF_ENTRY_API_KEY,
|
||||
CONF_ENTRY_DEVICE_ID,
|
||||
CONF_ENTRY_DEVICE_NAME,
|
||||
CONF_ENTRY_MAX_VOLUME,
|
||||
DOMAIN,
|
||||
)
|
||||
from .models import DeviceConfig
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = "SmartThings Soundbar"
|
||||
CONF_MAX_VOLUME = "max_volume"
|
||||
|
||||
SUPPORT_SMARTTHINGS_SOUNDBAR = (
|
||||
MediaPlayerEntityFeature.PAUSE
|
||||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
| MediaPlayerEntityFeature.TURN_ON
|
||||
| MediaPlayerEntityFeature.PLAY
|
||||
| MediaPlayerEntityFeature.NEXT_TRACK
|
||||
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||
| MediaPlayerEntityFeature.STOP
|
||||
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
)
|
||||
|
||||
|
||||
def addServices():
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"select_soundmode",
|
||||
cv.make_entity_service_schema({vol.Required("sound_mode"): str}),
|
||||
SmartThingsSoundbarMediaPlayer.async_select_sound_mode.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_woofer_level",
|
||||
cv.make_entity_service_schema(
|
||||
{vol.Required("level"): vol.All(int, vol.Range(min=-12, max=6))}
|
||||
),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_woofer_level.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_night_mode",
|
||||
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_night_mode.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_bass_enhancer",
|
||||
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_bass_mode.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_voice_enhancer",
|
||||
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_voice_mode.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_speaker_level",
|
||||
cv.make_entity_service_schema(
|
||||
{vol.Required("speaker_identifier"): str, vol.Required("level"): int}
|
||||
),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_speaker_level.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_rear_speaker_mode",
|
||||
cv.make_entity_service_schema({vol.Required("speaker_mode"): str}),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_rear_speaker_mode.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_active_voice_amplifier",
|
||||
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_active_voice_amplifier.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"set_space_fit_sound",
|
||||
cv.make_entity_service_schema({vol.Required("enabled"): bool}),
|
||||
SmartThingsSoundbarMediaPlayer.async_set_space_fit_sound.__name__,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
domain_data = hass.data[DOMAIN]
|
||||
|
||||
addServices()
|
||||
|
||||
entities = []
|
||||
for key in domain_data.devices:
|
||||
device_config: DeviceConfig = domain_data.devices[key]
|
||||
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_next_track(self):
|
||||
await self.device.media_next_track()
|
||||
|
||||
async def async_media_previous_track(self):
|
||||
await self.device.media_previous_track()
|
||||
|
||||
async def async_media_stop(self):
|
||||
await self.device.media_stop()
|
||||
|
||||
# ---------- SERVICE_UTILITY ------------
|
||||
|
||||
async def async_set_woofer_level(self, level: int):
|
||||
await self.device.set_woofer(level)
|
||||
|
||||
async def async_set_bass_mode(self, enabled: bool):
|
||||
await self.device.set_bass_mode(enabled)
|
||||
|
||||
async def async_set_voice_mode(self, enabled: bool):
|
||||
await self.device.set_voice_amplifier(enabled)
|
||||
|
||||
async def async_set_night_mode(self, enabled: bool):
|
||||
await self.device.set_night_mode(enabled)
|
||||
|
||||
# ---------- SERVICE_UTILITY ------------
|
||||
|
||||
async def async_set_speaker_level(self, speaker_identifier: str, level: int):
|
||||
await self.device.set_speaker_level(
|
||||
SpeakerIdentifier(speaker_identifier), level
|
||||
)
|
||||
|
||||
async def async_set_rear_speaker_mode(self, speaker_mode: str):
|
||||
await self.device.set_rear_speaker_mode(RearSpeakerMode(speaker_mode))
|
||||
|
||||
async def async_set_active_voice_amplifier(self, enabled: bool):
|
||||
await self.device.set_active_voice_amplifier(enabled)
|
||||
|
||||
async def async_set_space_fit_sound(self, enabled: bool):
|
||||
await self.device.set_space_fit_sound(enabled)
|
||||
|
||||
# This property can be uncommented for some extra_attributes
|
||||
# Still enabling this can cause side-effects.
|
||||
# @property
|
||||
# def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
||||
# return {"device_information": self.device.retrieve_data}
|
|
@ -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,76 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
)
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
from .api_extension.SoundbarDevice import SoundbarDevice
|
||||
from .const import CONF_ENTRY_DEVICE_ID, CONF_ENTRY_SETTINGS_WOOFER_NUMBER, DOMAIN
|
||||
from .models import DeviceConfig
|
||||
|
||||
_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
|
||||
) and config_entry.data.get(CONF_ENTRY_SETTINGS_WOOFER_NUMBER):
|
||||
entities.append(
|
||||
SoundbarWooferNumberEntity(
|
||||
device,
|
||||
"woofer_level",
|
||||
)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
return True
|
||||
|
||||
|
||||
class SoundbarWooferNumberEntity(NumberEntity):
|
||||
def __init__(
|
||||
self,
|
||||
device: SoundbarDevice,
|
||||
append_unique_id: str,
|
||||
):
|
||||
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
|
||||
self.entity_description = NumberEntityDescription(
|
||||
native_max_value=6,
|
||||
native_min_value=-10,
|
||||
mode=NumberMode.BOX,
|
||||
native_step=1,
|
||||
native_unit_of_measurement="dB",
|
||||
key=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.__append_unique_id = append_unique_id
|
||||
|
||||
# ---------- GENERAL ---------------
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__append_unique_id
|
||||
|
||||
# ------ STATE FUNCTIONS --------
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
return self.__device.woofer_level
|
||||
|
||||
async def async_set_native_value(self, value: float):
|
||||
await self.__device.set_woofer(int(value))
|
|
@ -0,0 +1,189 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
)
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
from .api_extension.SoundbarDevice import SoundbarDevice
|
||||
from .const import (
|
||||
CONF_ENTRY_DEVICE_ID,
|
||||
CONF_ENTRY_SETTINGS_EQ_SELECTOR,
|
||||
CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR,
|
||||
DOMAIN,
|
||||
)
|
||||
from .models import DeviceConfig
|
||||
|
||||
_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):
|
||||
if config_entry.data.get(CONF_ENTRY_SETTINGS_EQ_SELECTOR):
|
||||
entities.append(
|
||||
EqPresetSelectEntity(device, "eq_preset", "mdi:tune-vertical")
|
||||
)
|
||||
if config_entry.data.get(CONF_ENTRY_SETTINGS_SOUNDMODE_SELECTOR):
|
||||
entities.append(
|
||||
SoundModeSelectEntity(
|
||||
device, "sound_mode_preset", "mdi:surround-sound"
|
||||
)
|
||||
)
|
||||
|
||||
entities.append(
|
||||
InputSelectEntity(device, "input_preset", "mdi:video-input-hdmi")
|
||||
)
|
||||
async_add_entities(entities)
|
||||
return True
|
||||
|
||||
|
||||
class EqPresetSelectEntity(SelectEntity):
|
||||
def __init__(
|
||||
self,
|
||||
device: SoundbarDevice,
|
||||
append_unique_id: str,
|
||||
icon_string: str,
|
||||
):
|
||||
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
|
||||
self.entity_description = SelectEntityDescription(
|
||||
key=append_unique_id,
|
||||
)
|
||||
self.__base_icon = icon_string
|
||||
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.__append_unique_id = append_unique_id
|
||||
|
||||
self._attr_options = self.__device.supported_equalizer_presets
|
||||
|
||||
# ---------- GENERAL ---------------
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__append_unique_id
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
return self.__base_icon
|
||||
|
||||
# ------ STATE FUNCTIONS --------
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Get the current status of the select entity from device_status."""
|
||||
return self.__device.active_equalizer_preset
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set the option."""
|
||||
|
||||
await self.__device.set_equalizer_preset(option)
|
||||
|
||||
|
||||
class SoundModeSelectEntity(SelectEntity):
|
||||
def __init__(
|
||||
self,
|
||||
device: SoundbarDevice,
|
||||
append_unique_id: str,
|
||||
icon_string: str,
|
||||
):
|
||||
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
|
||||
self.entity_description = SelectEntityDescription(
|
||||
key=append_unique_id,
|
||||
)
|
||||
self.__base_icon = icon_string
|
||||
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.__append_unique_id = append_unique_id
|
||||
|
||||
self._attr_options = self.__device.supported_soundmodes
|
||||
|
||||
# ---------- GENERAL ---------------
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__append_unique_id
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
return self.__base_icon
|
||||
|
||||
# ------ STATE FUNCTIONS --------
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Get the current status of the select entity from device_status."""
|
||||
return self.__device.sound_mode
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set the option."""
|
||||
|
||||
await self.__device.select_sound_mode(option)
|
||||
|
||||
|
||||
class InputSelectEntity(SelectEntity):
|
||||
def __init__(
|
||||
self,
|
||||
device: SoundbarDevice,
|
||||
append_unique_id: str,
|
||||
icon_string: str,
|
||||
):
|
||||
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
|
||||
self.entity_description = SelectEntityDescription(
|
||||
key=append_unique_id,
|
||||
)
|
||||
self.__base_icon = icon_string
|
||||
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.__append_unique_id = append_unique_id
|
||||
|
||||
self._attr_options = self.__device.supported_input_sources
|
||||
|
||||
# ---------- GENERAL ---------------
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__append_unique_id
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
return self.__base_icon
|
||||
|
||||
# ------ STATE FUNCTIONS --------
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Get the current status of the select entity from device_status."""
|
||||
return self.__device.input_source
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set the option."""
|
||||
|
||||
await self.__device.select_source(option)
|
|
@ -0,0 +1,56 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
from .api_extension.SoundbarDevice import SoundbarDevice
|
||||
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN
|
||||
from .models import DeviceConfig
|
||||
|
||||
_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(VolumeSensor(device, "volume_level", "mdi:volume-high"))
|
||||
async_add_entities(entities)
|
||||
return True
|
||||
|
||||
|
||||
class VolumeSensor(SensorEntity):
|
||||
def __init__(self, device: SoundbarDevice, append_unique_id: str, icon_string: str):
|
||||
self.entity_id = f"sensor.{device.device_name}_{append_unique_id}"
|
||||
self.__device = device
|
||||
self._attr_unique_id = f"{device.device_id}_sw_{append_unique_id}"
|
||||
self.__base_icon = icon_string
|
||||
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.__append_unique_id = append_unique_id
|
||||
|
||||
_attr_device_class = SensorDeviceClass.VOLUME
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
return self.__base_icon
|
||||
|
||||
def update(self) -> None:
|
||||
"""Fetch new state data for the sensor.
|
||||
|
||||
This is the only method that should fetch new data for Home Assistant.
|
||||
"""
|
||||
self._attr_native_value = self.__device.device.status.volume
|
|
@ -0,0 +1,167 @@
|
|||
|
||||
select_soundmode:
|
||||
name: Select Soundmode
|
||||
description: Some Soundbars support different "sound modes". If supported you can select them here.
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
sound_mode:
|
||||
name: Sound Mode
|
||||
description: Select the Soundmode you are interested in.
|
||||
required: true
|
||||
example: "adaptive sound"
|
||||
# The default field value
|
||||
default: "standard"
|
||||
# Selector (https://www.home-assistant.io/docs/blueprint/selectors/) to control
|
||||
# the input UI for this field
|
||||
selector:
|
||||
select:
|
||||
translation_key: "soundmode"
|
||||
options:
|
||||
- "standard"
|
||||
- "surround"
|
||||
- "game"
|
||||
- "adaptive sound"
|
||||
|
||||
set_woofer_level:
|
||||
name: Set Woofer level
|
||||
description: Set the subwoofer level of your soundbar
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
level:
|
||||
name: Volume level
|
||||
required: true
|
||||
example: 3
|
||||
default: 0
|
||||
selector:
|
||||
number:
|
||||
min: -12
|
||||
max: 6
|
||||
step: 1
|
||||
|
||||
set_night_mode:
|
||||
name: Set NightMode
|
||||
description: Activates / deactivates the Nightmode
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
enabled:
|
||||
name: Enabled / Disabled
|
||||
required: true
|
||||
example: true
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
set_bass_enhancer:
|
||||
name: Set bass enhancement
|
||||
description: Activates / deactivates the bass enhancement
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
enabled:
|
||||
name: Enabled / Disabled
|
||||
required: true
|
||||
example: true
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
set_voice_enhancer:
|
||||
name: Set voice enhancement
|
||||
description: Activates / deactivates the voice enhancement
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
enabled:
|
||||
name: Enabled / Disabled
|
||||
required: true
|
||||
example: true
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
set_speaker_level:
|
||||
name: Set Speaker level
|
||||
description: Set the speaker levels of your soundbar
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
speaker_identifier:
|
||||
name: Speaker Identifier
|
||||
required: true
|
||||
example: Spk_Center
|
||||
selector:
|
||||
select:
|
||||
translation_key: "speaker_identifier"
|
||||
options:
|
||||
- "Spk_Center"
|
||||
- "Spk_Side"
|
||||
- "Spk_Wide"
|
||||
- "Spk_Front_Top"
|
||||
- "Spk_Rear"
|
||||
- "Spk_Rear_Top"
|
||||
level:
|
||||
name: Speaker Level
|
||||
required: true
|
||||
example: 0
|
||||
selector:
|
||||
number:
|
||||
min: -6
|
||||
max: 6
|
||||
step: 1
|
||||
|
||||
set_rear_speaker_mode:
|
||||
name: Set rear speaker mode
|
||||
description: Set the rear speaker mode of your soundbar
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
speaker_mode:
|
||||
name: Speaker mode
|
||||
required: true
|
||||
example: Rear
|
||||
selector:
|
||||
select:
|
||||
translation_key: "rear_speaker_mode"
|
||||
options:
|
||||
- "Rear"
|
||||
- "Front"
|
||||
|
||||
set_active_voice_amplifier:
|
||||
name: Set active voice amplifier
|
||||
description: Activates / deactivates the active voice amplifier
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
enabled:
|
||||
name: Enabled / Disabled
|
||||
required: true
|
||||
example: true
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
set_space_fit_sound:
|
||||
name: Set SpaceFitSound
|
||||
description: Activates / deactivates the SpaceFitSound
|
||||
target:
|
||||
device:
|
||||
integration: samsung_soundbar
|
||||
fields:
|
||||
enabled:
|
||||
name: Enabled / Disabled
|
||||
required: true
|
||||
example: true
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
|
@ -0,0 +1,113 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
from .api_extension.SoundbarDevice import SoundbarDevice
|
||||
from .const import (
|
||||
CONF_ENTRY_DEVICE_ID,
|
||||
CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES,
|
||||
DOMAIN,
|
||||
)
|
||||
from .models import DeviceConfig
|
||||
|
||||
_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):
|
||||
if config_entry.data.get(CONF_ENTRY_SETTINGS_ADVANCED_AUDIO_SWITCHES):
|
||||
entities.append(
|
||||
SoundbarSwitchAdvancedAudio(
|
||||
device,
|
||||
"nightmode",
|
||||
lambda: device.night_mode,
|
||||
device.set_night_mode,
|
||||
device.set_night_mode,
|
||||
"mdi:weather-night",
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
SoundbarSwitchAdvancedAudio(
|
||||
device,
|
||||
"bassmode",
|
||||
lambda: device.bass_mode,
|
||||
device.set_bass_mode,
|
||||
device.set_bass_mode,
|
||||
"mdi:speaker-wireless",
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
SoundbarSwitchAdvancedAudio(
|
||||
device,
|
||||
"voice_amplifier",
|
||||
lambda: device.voice_amplifier,
|
||||
device.set_voice_amplifier,
|
||||
device.set_voice_amplifier,
|
||||
"mdi:account-voice",
|
||||
)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
return True
|
||||
|
||||
|
||||
class SoundbarSwitchAdvancedAudio(SwitchEntity):
|
||||
def __init__(
|
||||
self,
|
||||
device: SoundbarDevice,
|
||||
append_unique_id: str,
|
||||
state_function,
|
||||
on_function,
|
||||
off_function,
|
||||
icon_string: str = "mdi:toggle-switch-variant",
|
||||
):
|
||||
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.__base_icon = icon_string
|
||||
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()
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
return self.__base_icon
|
||||
|
||||
# ------ 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,153 @@
|
|||
{
|
||||
"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"
|
||||
},
|
||||
"device":{
|
||||
"data" : {
|
||||
"settings_advanced_audio": "'Advanced Audio switches' aktivieren (NightMode, BassMode, VoiceEnhancer)",
|
||||
"settings_eq": "'EQ selector' aktivieren",
|
||||
"settings_soundmode": "'Soundmode selector' aktivieren",
|
||||
"settings_woofer": "'Subwoofer Entität' aktivieren"
|
||||
},
|
||||
"description": "Einige Soundbars haben verschiedene Featuresets. Wähle bitte aus welche Features von deiner Soundbar supported werden (einsehbar in der SmartThings App).",
|
||||
"title": "Geräte Einstellungen"
|
||||
},
|
||||
"reconfigure_confirm":{
|
||||
"data" : {
|
||||
"settings_advanced_audio": "'Advanced Audio switches' aktivieren (NightMode, BassMode, VoiceEnhancer)",
|
||||
"settings_eq": "'EQ selector' aktivieren",
|
||||
"settings_soundmode": "'Soundmode selector' aktivieren",
|
||||
"settings_woofer": "'Subwoofer Entität' aktivieren",
|
||||
"device_volume": "Max Volume (int)"
|
||||
},
|
||||
"description": "Einige Soundbars haben verschiedene Featuresets. Wähle bitte aus welche Features von deiner Soundbar supported werden (einsehbar in der SmartThings App).",
|
||||
"title": "Geräte Einstellungen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"soundmode": {
|
||||
"options": {
|
||||
"standard": "Standard",
|
||||
"surround": "Surround",
|
||||
"game": "Gaming",
|
||||
"adaptive sound": "Adaptive Sound"
|
||||
}
|
||||
},
|
||||
"speaker_identifier": {
|
||||
"options": {
|
||||
"Spk_Center": "Center",
|
||||
"Spk_Side": "Side",
|
||||
"Spk_Wide": "Wide",
|
||||
"Spk_Front_Top": "Front Top",
|
||||
"Spk_Rear": "Rear",
|
||||
"Spk_Rear_Top": "Rear Top"
|
||||
}
|
||||
},
|
||||
"rear_speaker_mode": {
|
||||
"options": {
|
||||
"Rear": "Rear",
|
||||
"Front": "Front"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services":{
|
||||
"select_soundmode":{
|
||||
"name": "SoundMode auswählen",
|
||||
"description": "Wähle hier zwischen, 'Standard', 'Surround', 'Game' und 'Adaptive Sound'."
|
||||
},
|
||||
"set_woofer_level":{
|
||||
"name": "Subwoofer Level setzen",
|
||||
"description": "Verändere die Lautstärke deines Subwoofers.",
|
||||
"fields":{
|
||||
"level":{
|
||||
"name": "Volume Level",
|
||||
"description": "Subwoofer Level, von -12 bis +6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_night_mode":{
|
||||
"name": "Nachtmodus setzen",
|
||||
"description": "Schalte den 'Nachtmodus' an / aus.",
|
||||
"fields":{
|
||||
"enabled":{
|
||||
"name": "An / ausschalten",
|
||||
"description": "Siehe Name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_bass_enhancer":{
|
||||
"name": "Bassmodus setzen",
|
||||
"description": "Schalte den 'Bassmodus' an / aus.",
|
||||
"fields":{
|
||||
"enabled":{
|
||||
"name": "An / ausschalten",
|
||||
"description": "Siehe Name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_voice_enhancer":{
|
||||
"name": "Stimmenverbesserer setzen",
|
||||
"description": "Schalte den 'Stimmenverbesserer' an / aus.",
|
||||
"fields":{
|
||||
"enabled":{
|
||||
"name": "An / ausschalten",
|
||||
"description": "Siehe Name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_speaker_level":{
|
||||
"name": "Lautsprecher level verändern",
|
||||
"description": "Verändere die Lautstärke der einzelnen Lautsprecher",
|
||||
"fields":{
|
||||
"speaker_identifier": {
|
||||
"name": "Lautsprecher",
|
||||
"description": "Auszuwählender Lautsprecher"
|
||||
},
|
||||
"level": {
|
||||
"name": "Lautstärke Level",
|
||||
"description": "Lautstärke Level zwischen -6 und 6."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_rear_speaker_mode":{
|
||||
"name": "Modus der hinteren Lautsprecher setzen",
|
||||
"description": "Nutze deine Rücklautsprecher, als 'Vorder-' oder 'Rücklautsprecher'.",
|
||||
"fields":{
|
||||
"speaker_mode": {
|
||||
"name": "Lautsprecher Modus",
|
||||
"description": "Nutze den Lautsprecher als Front oder Rear Speaker."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_active_voice_amplifier":{
|
||||
"name": "Stimmenverstärker setzen",
|
||||
"description": "Schalte den 'Stimmenverstärker' an / aus.",
|
||||
"fields":{
|
||||
"enabled":{
|
||||
"name": "An / ausschalten",
|
||||
"description": "Siehe Name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_space_fit_sound":{
|
||||
"name": "SpaceFitSound setzen",
|
||||
"description": "Schalte den 'SpaceFitSound' an / aus.",
|
||||
"fields":{
|
||||
"enabled":{
|
||||
"name": "An / ausschalten",
|
||||
"description": "Siehe Name."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
{
|
||||
"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"
|
||||
},
|
||||
"device": {
|
||||
"data": {
|
||||
"settings_advanced_audio": "Enable 'Advanced Audio switches' capabilities (NightMode, BassMode, VoiceEnhancer)",
|
||||
"settings_eq": "Enable 'EQ selector' capabilities",
|
||||
"settings_soundmode": "Enable 'Soundmode selector' capabilities",
|
||||
"settings_woofer": "Enable 'Woofer number' capability"
|
||||
},
|
||||
"description": "Some soundbars have a different featureset than others. Please the features supported by your soundbar (visible in the SmartThings App).",
|
||||
"title": "Device Settings"
|
||||
},
|
||||
"reconfigure_confirm": {
|
||||
"data": {
|
||||
"settings_advanced_audio": "Enable 'Advanced Audio switches' capabilities (NightMode, BassMode, VoiceEnhancer)",
|
||||
"settings_eq": "Enable 'EQ selector' capabilities",
|
||||
"settings_soundmode": "Enable 'Soundmode selector' capabilities",
|
||||
"settings_woofer": "Enable 'Woofer number' capability",
|
||||
"device_volume": "Max Volume (int)"
|
||||
},
|
||||
"description": "Some soundbars have a different featureset than others. Please the features supported by your soundbar (visible in the SmartThings App).",
|
||||
"title": "Device Settings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"soundmode": {
|
||||
"options": {
|
||||
"standard": "Standard",
|
||||
"surround": "Surround",
|
||||
"game": "Gaming",
|
||||
"adaptive sound": "Adaptive Sound"
|
||||
}
|
||||
},
|
||||
"speaker_identifier": {
|
||||
"options": {
|
||||
"Spk_Center": "Center",
|
||||
"Spk_Side": "Side",
|
||||
"Spk_Wide": "Wide",
|
||||
"Spk_Front_Top": "Front Top",
|
||||
"Spk_Rear": "Rear",
|
||||
"Spk_Rear_Top": "Rear Top"
|
||||
}
|
||||
},
|
||||
"rear_speaker_mode": {
|
||||
"options": {
|
||||
"Rear": "Rear",
|
||||
"Front": "Front"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"select_soundmode": {
|
||||
"name": "Select Sound Mode",
|
||||
"description": "Choose between 'Standard', 'Surround', 'Game', and 'Adaptive Sound'."
|
||||
},
|
||||
"set_woofer_level": {
|
||||
"name": "Set Subwoofer Level",
|
||||
"description": "Change the volume of your subwoofer.",
|
||||
"fields": {
|
||||
"level": {
|
||||
"name": "Volume Level",
|
||||
"description": "Subwoofer level, from -12 to +6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_night_mode": {
|
||||
"name": "Set Night Mode",
|
||||
"description": "Turn 'Night Mode' on/off.",
|
||||
"fields": {
|
||||
"enabled": {
|
||||
"name": "On/Off",
|
||||
"description": "See name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_bass_enhancer": {
|
||||
"name": "Set Bass Mode",
|
||||
"description": "Turn 'Bass Mode' on/off.",
|
||||
"fields": {
|
||||
"enabled": {
|
||||
"name": "On/Off",
|
||||
"description": "See name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_voice_enhancer": {
|
||||
"name": "Set Voice Enhancer",
|
||||
"description": "Turn 'Voice Enhancer' on/off.",
|
||||
"fields": {
|
||||
"enabled": {
|
||||
"name": "On/Off",
|
||||
"description": "See name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_speaker_level": {
|
||||
"name": "Change Speaker Level",
|
||||
"description": "Change the volume of individual speakers.",
|
||||
"fields":{
|
||||
"speaker_identifier": {
|
||||
"name": "Speaker Identifier",
|
||||
"description": "Identifier of the speaker."
|
||||
},
|
||||
"level": {
|
||||
"name": "Level",
|
||||
"description": "Level of the Speaker from -6 to 6."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_rear_speaker_mode": {
|
||||
"name": "Set Rear Speaker Mode",
|
||||
"description": "Use your rear speakers as 'Front' or 'Rear' speakers.",
|
||||
"fields":{
|
||||
"speaker_mode": {
|
||||
"name": "Speaker mode",
|
||||
"description": "Weather the speaker are used as rear / front speakers."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_active_voice_amplifier": {
|
||||
"name": "Set Voice Amplifier",
|
||||
"description": "Turn 'Voice Amplifier' on/off.",
|
||||
"fields": {
|
||||
"enabled": {
|
||||
"name": "On/Off",
|
||||
"description": "See name."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_space_fit_sound": {
|
||||
"name": "Set SpaceFitSound",
|
||||
"description": "Turn 'SpaceFitSound' on/off.",
|
||||
"fields": {
|
||||
"enabled": {
|
||||
"name": "On/Off",
|
||||
"description": "See name."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
dist
|
||||
node_modules
|
||||
.output
|
||||
.nuxt
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: '@nuxt/eslint-config',
|
||||
rules: {
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
node_modules
|
||||
*.iml
|
||||
.idea
|
||||
*.log*
|
||||
.nuxt
|
||||
.vscode
|
||||
.DS_Store
|
||||
coverage
|
||||
dist
|
||||
sw.*
|
||||
.env
|
||||
.output
|
|
@ -0,0 +1,2 @@
|
|||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
|
@ -0,0 +1,57 @@
|
|||
# Docus Starter
|
||||
|
||||
Starter template for [Docus](https://docus.dev).
|
||||
|
||||
## Clone
|
||||
|
||||
Clone the repository (using `nuxi`):
|
||||
|
||||
```bash
|
||||
npx nuxi init -t themes/docus
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Edge Side Rendering
|
||||
|
||||
Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments.
|
||||
|
||||
Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets).
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
## Static Generation
|
||||
|
||||
Use the `generate` command to build your application.
|
||||
|
||||
The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting.
|
||||
|
||||
```bash
|
||||
yarn generate
|
||||
```
|
||||
|
||||
## Preview build
|
||||
|
||||
You might want to preview the result of your build locally, to do so, run the following command:
|
||||
|
||||
```bash
|
||||
yarn preview
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For a detailed explanation of how things work, check out [Docus](https://docus.dev).
|
|
@ -0,0 +1,37 @@
|
|||
export default defineAppConfig({
|
||||
docus: {
|
||||
title: '🔊 Yassi',
|
||||
description: 'Yet another Samsung Soundbar integration for Home Assistant',
|
||||
image: 'https://user-images.githubusercontent.com/904724/185365452-87b7ca7b-6030-4813-a2db-5e65c785bf88.png',
|
||||
socials: {
|
||||
github: 'samuelspagl/ha_samsung_soundbar',
|
||||
nuxt: {
|
||||
label: 'Nuxt',
|
||||
icon: 'simple-icons:nuxtdotjs',
|
||||
href: 'https://nuxt.com'
|
||||
}
|
||||
},
|
||||
github: {
|
||||
dir: 'docs/content',
|
||||
branch: 'main',
|
||||
repo: 'ha_samsung_soundbar',
|
||||
owner: 'samuelspagl',
|
||||
edit: true
|
||||
},
|
||||
aside: {
|
||||
level: 0,
|
||||
collapsed: false,
|
||||
exclude: []
|
||||
},
|
||||
main: {
|
||||
padded: true,
|
||||
fluid: true
|
||||
},
|
||||
header: {
|
||||
logo: false,
|
||||
showLinkIcon: true,
|
||||
exclude: [],
|
||||
fluid: true
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: "YASSI"
|
||||
---
|
||||
::block-hero
|
||||
---
|
||||
cta:
|
||||
- Why another HomeAssistant integration?
|
||||
- /first-things-first/why-another-integration
|
||||
secondary:
|
||||
- Open on GitHub →
|
||||
- https://github.com/samuelspagl/ha_samsung_soundbar
|
||||
---
|
||||
|
||||
#title
|
||||
Yassi - Yet another Samsung Soundbar integration
|
||||
|
||||
#description
|
||||
**YASSI** is a **HomeAssistant** integration for **Samsung Soundbars**. It enhances control, and adds features like equalizer settings. Install it via HACS or manually. Kudos to the original idea by @PiotrMachowski and @thierryBourbon! 🎶🔊
|
||||
|
||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=samuelspagl&repository=ha_samsung_soundbar&category=integration)
|
||||
::
|
||||
|
||||
::card-grid
|
||||
#title
|
||||
Quick-Start
|
||||
#root
|
||||
:ellipsis
|
||||
|
||||
#default
|
||||
::card
|
||||
#title
|
||||
❓ Why another integration?
|
||||
#description
|
||||
Whether you thought about it or not, here is the answer ;).
|
||||
<br>
|
||||
<br>
|
||||
:button-link[click here]{href="/first-things-first/why-another-integration"}
|
||||
::
|
||||
|
||||
::card
|
||||
#title
|
||||
🚀 Getting Started
|
||||
#description
|
||||
Go, Go, Go... Here you will find information on "How to install / configure".
|
||||
<br>
|
||||
<br>
|
||||
:button-link[click here]{href="/first-things-first/getting-started"}
|
||||
::
|
||||
|
||||
::card
|
||||
#title
|
||||
✨ Features
|
||||
#description
|
||||
Many cool features are awaiting your eyes to see ✨.
|
||||
<br>
|
||||
<br>
|
||||
:button-link[click here]{href="/features"}
|
||||
::
|
||||
|
||||
::card
|
||||
#title
|
||||
⚙️ SmartThings API related information
|
||||
#description
|
||||
If you want to know some background information on how equalizer support and
|
||||
other things were implemented, this is your section.
|
||||
<br>
|
||||
<br>
|
||||
:button-link[click here]{href="/features"}
|
||||
::
|
||||
::
|
|
@ -0,0 +1,13 @@
|
|||
# ❓ Why another integration
|
||||
|
||||
The current Samsung Soundbar Integration by @PiotrMachowski / @thierryBourbon are already pretty cool.
|
||||
But I wanted it to appear as a device, and base the Foundation on the `pysmartthings` python package.
|
||||
|
||||
Additionally, I wanted full control over the *Soundmode* and more. So I tried out a few things with the API,
|
||||
and found that also the **Subwoofer** as well as the **Equalizer** are controllable.
|
||||
|
||||
I created a new wrapper around the `pysmartthings.DeviceEntity` specifically set up for a Soundbar, and this
|
||||
is the Result.
|
||||
|
||||
I hope to integrate also controls for **surround speaker** as well as **Space-Fit Sound**, but as these features
|
||||
are not documented... ;)
|
|
@ -0,0 +1,43 @@
|
|||
# 🚀 Getting Started with Yassi
|
||||
|
||||
Welcome to Yassi, the HomeAssistant integration for your Samsung Soundbar. This guide will help you get up and running in no time.
|
||||
|
||||
## 📦 Installation Options
|
||||
|
||||
### HACS (Home Assistant Community Store)
|
||||
|
||||
#### 🌟 Official Repository (Coming Soon)
|
||||
The Yassi integration will be available through the official HACS repository shortly. Stay tuned for updates.
|
||||
|
||||
#### ➕ Custom Repository
|
||||
In the meantime, you can manually add this repository to HACS:
|
||||
1. Click the following button and 'open link':
|
||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=samuelspagl&repository=ha_samsung_soundbar&category=integration)
|
||||
2. Click 'add' to add the custom repository.
|
||||
3. Download 'Yassi' and restart Home Assistant.
|
||||
|
||||
### 📂 Manual Installation
|
||||
If you prefer to install Yassi manually:
|
||||
1. Download the latest release from the repository.
|
||||
2. Extract and copy the `custom_components/samsung_soundbar` folder.
|
||||
3. Paste it into the `config/custom_components/samsung_soundbar` directory of your HomeAssistant setup.
|
||||
|
||||
## ⚙️ Configuration Steps
|
||||
|
||||
Once Yassi is installed, you can configure it via the HomeAssistant UI:
|
||||
|
||||
1. Go to 'Configuration' and then 'Integrations'.
|
||||
2. Click on 'Add Integration' and search for 'Yassi'.
|
||||
3. Enter the following details to complete the setup:
|
||||
- 🔑 SmartThings API Key: [Obtain it here](https://account.smartthings.com/tokens).
|
||||
- 🆔 Device ID: [Find your Soundbar's device ID here](https://my.smartthings.com/advanced/devices).
|
||||
- ㍻ Soundbar Name: Choose a name for easy identification.
|
||||
- 🔊 Max Volume: Set the maximum volume limit for your Soundbar.
|
||||
|
||||
Follow these steps, and you'll be enjoying seamless control over your Samsung Soundbar with Yassi in no time!
|
||||
|
||||
::alert{type="info"}
|
||||
The `🔊 Max Volume` setting will readjust the internal values of the `media_player` entity from 0-100 to 0-MaxVolume.
|
||||
Therefore will the slider not display the same value as the one provided by the `sensor` entity, which will always display
|
||||
the raw value retrieved from the SmartThings API.
|
||||
::
|
|
@ -0,0 +1,17 @@
|
|||
# ‼️ Issues and other things
|
||||
|
||||
As the creator of this personal and fun project, I am thrilled to see people using it. While I won’t always have immediate availability to address every request, I’ll do my best to fix issues and implement features. Thanks a lot in advance! 🙌
|
||||
|
||||
Here are some best practices to help me help you:
|
||||
|
||||
1. 🐞 GitHub Issues: For any issues or bugs, please submit them via GitHub Issues. ([🔗 click here](https://github.com/samuelspagl/ha_samsung_soundbar/issues/new))
|
||||
2. 📋 Provide Details: Include essential information:
|
||||
- Home Assistant OS Version
|
||||
- Samsung Soundbar Model
|
||||
- Other Relevant Details (like debug logs)
|
||||
3. 🎇 Icons for Fun:
|
||||
- 📦 = Feature Request
|
||||
- 🐛 = Bug Report
|
||||
- ❓ = General Questions
|
||||
|
||||
Let’s collaborate to enhance your soundbar experience! 🎶🔊
|
|
@ -0,0 +1,52 @@
|
|||
# ✨ Features Overview
|
||||
|
||||
Yassi allows you to retrieve and set the status of various features on your Samsung Soundbar. Below is a breakdown of capabilities organized by entity type.
|
||||
|
||||
## `media_player` Entity
|
||||
|
||||
| **Feature** | **Capability** | **Access Type** |
|
||||
|-------------------|----------------|-----------------|
|
||||
| Power | on / off | Read, Write |
|
||||
| Volume | set, step | Read, Write |
|
||||
| Input Selection | select | Read, Write |
|
||||
| Sound Mode | select | Read, Write |
|
||||
| Playback Control | play, pause | Write |
|
||||
| Media Information | artwork, title, artist | Read |
|
||||
|
||||
## `number` Entity
|
||||
|
||||
| **Feature** | **Capability** | **Access Type** |
|
||||
|-------------|----------------|-----------------|
|
||||
| Woofer Level | set | Read, Write |
|
||||
|
||||
## `select` Entity
|
||||
|
||||
| **Feature** | **Capability** | **Access Type** |
|
||||
|-------------------|-----------------------|-----------------|
|
||||
| Input | input, supported_inputs | Read, Write |
|
||||
| Sound Mode | active_soundmode, supported_soundmodes | Read, Write |
|
||||
| EQ-Preset | active_eq_preset, supported_eq_preset | Read, Write |
|
||||
|
||||
## `button` Entity
|
||||
|
||||
| **Feature** | **Capability** | **Access Type** |
|
||||
|-------------------|----------------|-----------------|
|
||||
| Night Mode | toggle | Read, Write |
|
||||
| Voice Amplifier | toggle | Read, Write |
|
||||
| Bass Mode | toggle | Read, Write |
|
||||
|
||||
## `image` Entity
|
||||
|
||||
| **Feature** | **Capability** | **Access Type** |
|
||||
|-------------------|----------------|-----------------|
|
||||
| Media Cover Art | display | Read |
|
||||
|
||||
|
||||
## `sensor` Entity
|
||||
|
||||
| **Feature** | **Capability** | **Access Type** |
|
||||
|-------------|--------------------|-----------------|
|
||||
| Volume | float sensor value | Read |
|
||||
|
||||
|
||||
Hopefully this format provides a clear and concise view of what Yassi can do with your Samsung Soundbar, making it easier to understand and configure.
|
|
@ -0,0 +1,47 @@
|
|||
# "Standard" information
|
||||
|
||||
This is the "standard" information that you can fetch with the `pysmartthings` library
|
||||
for a given soundbar:
|
||||
|
||||
```python
|
||||
{
|
||||
'supportedPlaybackCommands': status(value=['play', 'pause', 'stop'], unit=None, data=None),
|
||||
'playbackStatus': status(value='paused', unit=None, data=None),
|
||||
'mode': status(value=10, unit=None, data=None),
|
||||
'detailName': status(value='TV', unit=None, data=None),
|
||||
'volume': status(value=16, unit='%', data=None),
|
||||
'supportedInputSources': status(value=['digital', 'HDMI1', 'bluetooth', 'wifi'], unit=None, data=None),
|
||||
'inputSource': status(value='digital', unit=None, data=None),
|
||||
'data': status(value=None,unit=None,data=None),
|
||||
'switch': status(value='on', unit=None, data=None),
|
||||
'role': status(value=None, unit=None, data=None),
|
||||
'channel': status(value=None, unit=None, data=None),
|
||||
'masterName': status(value=None, unit=None, data=None),
|
||||
'status': status(value=None, unit=None, data=None),
|
||||
'st': status(value='1970-01-01T00:00:28Z', unit=None, data=None),
|
||||
'mndt': status(value='2022-01-01', unit=None, data=None),
|
||||
'mnfv': status(value='HW-Q935BWWB-1010.0', unit=None, data=None),
|
||||
'mnhw': status(value='', unit=None, data=None),
|
||||
'di': status(value='##############################', unit=None, data=None),
|
||||
'mnsl': status(value=None, unit=None, data=None),
|
||||
'dmv': status(value='res.1.1.0,sh.1.1.0', unit=None, data=None),
|
||||
'n': status(value='Samsung Soundbar Q935B', unit=None, data=None),
|
||||
'mnmo': status(value='HW-Q935B', unit=None, data=None),
|
||||
'vid': status(value='VD-NetworkAudio-002S', unit=None, data=None),
|
||||
'mnmn': status(value='Samsung Electronics', unit=None, data=None),
|
||||
'mnml': status(value=None, unit=None, data=None),
|
||||
'mnpv': status(value='6.5', unit=None, data=None),
|
||||
'mnos': status(value='Tizen', unit=None, data=None),
|
||||
'pi': status(value='##################################', unit=None, data=None),
|
||||
'icv': status(value='core.1.1.0', unit=None, data=None),
|
||||
'mute': status(value='unmuted', unit=None, data=None),
|
||||
'totalTime': status(value=174590, unit=None, data=None),
|
||||
'audioTrackData': status(value={'title': 'QUIET', 'artist': 'ELEVATION RHYTHM', 'album': ''}, unit=None, data=None),
|
||||
'elapsedTime': status(value=28601, unit=None, data=None)
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to fetch the current status (on/off) or information about the input.
|
||||
and if Spotify / AirPlay or Bluetooth are used, also the `title` and `artist` of a played track.
|
||||
|
||||
All of these states can also be set. Eg. the input, volume, mute and more.
|
|
@ -0,0 +1,234 @@
|
|||
# Additional information
|
||||
|
||||
It is possible to retrieve even more information / control more aspects of
|
||||
your Samsung soundbar, by utilizing the (undocumented) execute status.
|
||||
|
||||
As the [API states](https://developer.smartthings.com/docs/api/public/#operation/executeDeviceCommands),
|
||||
it is possible to execute custom commands. You can retrieve the status / values of your
|
||||
custom command in the `data` attribute when fetching new information of the device.
|
||||
|
||||
<details>
|
||||
<summary>Expand to see a sample of the fetched data of a soundbar device</summary>
|
||||
|
||||
This is a dictionary fetched by a `pysmartthings.device.status.attributes` after a `device.status.refresh()`.
|
||||
|
||||
```python
|
||||
{
|
||||
'supportedPlaybackCommands': status(value=['play', 'pause', 'stop'], unit=None, data=None),
|
||||
'playbackStatus': status(value='paused', unit=None, data=None),
|
||||
'mode': status(value=10, unit=None, data=None),
|
||||
'detailName': status(value='TV', unit=None, data=None),
|
||||
'volume': status(value=16, unit='%', data=None),
|
||||
'supportedInputSources': status(value=['digital', 'HDMI1', 'bluetooth', 'wifi'], unit=None, data=None),
|
||||
'inputSource': status(value='digital', unit=None, data=None),
|
||||
'data': status(
|
||||
value={
|
||||
'payload': {
|
||||
'rt': ['x.com.samsung.networkaudio.eq'],
|
||||
'if': ['oic.if.rw', 'oic.if.baseline'],
|
||||
'x.com.samsung.networkaudio.supportedList': ['NONE', 'POP', 'JAZZ', 'CLASSIC', 'CUSTOM'],
|
||||
'x.com.samsung.networkaudio.EQname': 'NONE',
|
||||
'x.com.samsung.networkaudio.action': 'setEQmode',
|
||||
'x.com.samsung.networkaudio.EQband': ['0', '0', '0', '0', '0', '0', '0']
|
||||
}
|
||||
},
|
||||
unit=None,
|
||||
data={'href': '/sec/networkaudio/eq'}
|
||||
),
|
||||
'switch': status(value='on', unit=None, data=None),
|
||||
'role': status(value=None, unit=None, data=None),
|
||||
'channel': status(value=None, unit=None, data=None),
|
||||
'masterName': status(value=None, unit=None, data=None),
|
||||
'status': status(value=None, unit=None, data=None),
|
||||
'st': status(value='1970-01-01T00:00:28Z', unit=None, data=None),
|
||||
'mndt': status(value='2022-01-01', unit=None, data=None),
|
||||
'mnfv': status(value='HW-Q935BWWB-1010.0', unit=None, data=None),
|
||||
'mnhw': status(value='', unit=None, data=None),
|
||||
'di': status(value='##############################', unit=None, data=None),
|
||||
'mnsl': status(value=None, unit=None, data=None),
|
||||
'dmv': status(value='res.1.1.0,sh.1.1.0', unit=None, data=None),
|
||||
'n': status(value='Samsung Soundbar Q935B', unit=None, data=None),
|
||||
'mnmo': status(value='HW-Q935B', unit=None, data=None),
|
||||
'vid': status(value='VD-NetworkAudio-002S', unit=None, data=None),
|
||||
'mnmn': status(value='Samsung Electronics', unit=None, data=None),
|
||||
'mnml': status(value=None, unit=None, data=None),
|
||||
'mnpv': status(value='6.5', unit=None, data=None),
|
||||
'mnos': status(value='Tizen', unit=None, data=None),
|
||||
'pi': status(value='##################################', unit=None, data=None),
|
||||
'icv': status(value='core.1.1.0', unit=None, data=None),
|
||||
'mute': status(value='unmuted', unit=None, data=None),
|
||||
'totalTime': status(value=174590, unit=None, data=None),
|
||||
'audioTrackData': status(value={'title': 'QUIET', 'artist': 'ELEVATION RHYTHM', 'album': ''}, unit=None, data=None),
|
||||
'elapsedTime': status(value=28601, unit=None, data=None)
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
The `data` attribute can also be fetched separately with an undocumented API endpoint.
|
||||
```python
|
||||
url = f"https://api.smartthings.com/v1/devices/{self._device_id}/components/main/capabilities/execute/status"
|
||||
```
|
||||
|
||||
It seems that the normal `device.status.refresh()` retrieves cached results from the execute status. Therefore
|
||||
using this endpoint separately seems to be a better solution.
|
||||
|
||||
To set the status of a given setting a command needs to be issued with the following (sample) structure:
|
||||
|
||||
```python
|
||||
data = {
|
||||
"commands": [
|
||||
{
|
||||
"component": component_id,
|
||||
"capability": capability,
|
||||
"command": command,
|
||||
"arguments": ["/sec/networkaudio/advancedaudio"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
To set a setting, you will "update" an object in the given path, with a payload
|
||||
similar to the following:
|
||||
```python
|
||||
data = {
|
||||
"commands": [
|
||||
{
|
||||
"component": component_id,
|
||||
"capability": capability,
|
||||
"command": command,
|
||||
"arguments": ["/sec/networkaudio/advancedaudio", {"x.com.samsung.networkaudio.bassboost": 1}]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Soundmode
|
||||
|
||||
This setting has the href: `"/sec/networkaudio/soundmode"`
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
A sample status looks like this:
|
||||
</summary>
|
||||
|
||||
```python
|
||||
{
|
||||
'data': {
|
||||
'value': {
|
||||
'payload': {
|
||||
'rt': ['x.com.samsung.networkaudio.soundmode'],
|
||||
'if': ['oic.if.a', 'oic.if.baseline'],
|
||||
'x.com.samsung.networkaudio.soundmode': 'adaptive sound',
|
||||
'x.com.samsung.networkaudio.supportedSoundmode': ['standard', 'surround', 'game', 'adaptive sound']
|
||||
}
|
||||
},
|
||||
'data': {'href': '/sec/networkaudio/soundmode'},
|
||||
'timestamp': '2023-09-05T14:59:50.581Z'
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
## Advanced Audio
|
||||
|
||||
This setting has the href: `"/sec/networkaudio/advancedaudio"`
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
A sample status looks like this:
|
||||
</summary>
|
||||
|
||||
```python
|
||||
{
|
||||
'data': {
|
||||
'value': {
|
||||
'payload': {
|
||||
'rt': ['x.com.samsung.networkaudio.advancedaudio'],
|
||||
'if': ['oic.if.rw', 'oic.if.baseline'],
|
||||
'x.com.samsung.networkaudio.voiceamplifier': 0,
|
||||
'x.com.samsung.networkaudio.bassboost': 0,
|
||||
'x.com.samsung.networkaudio.nightmode': 0
|
||||
}
|
||||
},
|
||||
'data': {'href': '/sec/networkaudio/advancedaudio'},
|
||||
'timestamp': '2023-09-05T15:00:14.665Z'
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
</details>
|
||||
|
||||
## Subwoofer
|
||||
|
||||
This setting has the href: `"/sec/networkaudio/woofer"`
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
A sample status looks like this:
|
||||
</summary>
|
||||
|
||||
```python
|
||||
{
|
||||
'value': {
|
||||
'payload': {
|
||||
'rt': ['x.com.samsung.networkaudio.woofer'],
|
||||
'if': ['oic.if.a', 'oic.if.baseline'],
|
||||
'x.com.samsung.networkaudio.woofer': 3,
|
||||
'x.com.samsung.networkaudio.connection': 'on'
|
||||
}
|
||||
},
|
||||
'data': {'href': '/sec/networkaudio/woofer'},
|
||||
'timestamp': '2023-09-05T14:57:36.450Z'
|
||||
}
|
||||
|
||||
```
|
||||
</details>
|
||||
|
||||
## Equalizer
|
||||
|
||||
This setting has the href: `"/sec/networkaudio/eq"`
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
A sample status looks like this:
|
||||
</summary>
|
||||
|
||||
```python
|
||||
{
|
||||
'data': {
|
||||
'value': {
|
||||
'payload': {
|
||||
'rt': ['x.com.samsung.networkaudio.eq'],
|
||||
'if': ['oic.if.rw', 'oic.if.baseline'],
|
||||
'x.com.samsung.networkaudio.supportedList': ['NONE', 'POP', 'JAZZ', 'CLASSIC', 'CUSTOM'],
|
||||
'x.com.samsung.networkaudio.EQname': 'NONE',
|
||||
'x.com.samsung.networkaudio.action': 'setEQmode',
|
||||
'x.com.samsung.networkaudio.EQband': ['0', '0', '0', '0', '0', '0', '0']
|
||||
}
|
||||
},
|
||||
'data': {'href': '/sec/networkaudio/eq'},
|
||||
'timestamp': '2023-09-05T14:59:03.490Z'
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
## Volume
|
||||
|
||||
This setting has the href: `"/sec/networkaudio/audio"`
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
A sample status looks like this:
|
||||
</summary>
|
||||
|
||||
```python
|
||||
{
|
||||
'data': {
|
||||
'value': {'payload': {'rt': ['oic.r.audio'], 'if': ['oic.if.a', 'oic.if.baseline'], 'mute': False, 'volume': 3}},
|
||||
'data': {'href': '/sec/networkaudio/audio'},
|
||||
'timestamp': '2023-09-05T15:09:04.980Z'
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
|
@ -0,0 +1,10 @@
|
|||
export default defineNuxtConfig({
|
||||
// https://github.com/nuxt-themes/docus
|
||||
extends: '@nuxt-themes/docus',
|
||||
modules: [
|
||||
// https://github.com/nuxt-modules/plausible
|
||||
'@nuxtjs/plausible',
|
||||
// https://github.com/nuxt/devtools
|
||||
'@nuxt/devtools'
|
||||
]
|
||||
})
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "YASSI",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxi dev",
|
||||
"build": "nuxi build",
|
||||
"generate": "nuxi generate",
|
||||
"preview": "nuxi preview",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt-themes/docus": "^1.13.1",
|
||||
"@nuxt/devtools": "^0.6.7",
|
||||
"@nuxt/eslint-config": "^0.1.1",
|
||||
"@nuxtjs/plausible": "^0.2.1",
|
||||
"@types/node": "^20.4.0",
|
||||
"eslint": "^8.44.0",
|
||||
"nuxt": "^3.6.2"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": [
|
||||
"@nuxtjs"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import { defineTheme } from 'pinceau'
|
||||
|
||||
export default defineTheme({
|
||||
})
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "Samsung Soundbar",
|
||||
"filename": "samsung_soundbar.zip",
|
||||
"render_readme": true,
|
||||
"zip_release": true,
|
||||
"homeassistant": "2024.3.0"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Create config dir if not present
|
||||
if [[ ! -d "${PWD}/config" ]]; then
|
||||
mkdir -p "${PWD}/config"
|
||||
hass --config "${PWD}/config" --script ensure_config
|
||||
fi
|
||||
|
||||
# Set the path to custom_components
|
||||
## This let's us have the structure we want <root>/custom_components/integration_blueprint
|
||||
## while at the same time have Home Assistant configuration inside <root>/config
|
||||
## without resulting to symlinks.
|
||||
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"
|
||||
|
||||
# Start Home Assistant
|
||||
hass --config "${PWD}/config" --debug
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
pip install rich pysmartthings
|
Loading…
Reference in New Issue