various improvements (#5)

- Added various improvements and new features.
- First version of a documentation
---------

Co-authored-by: samuelspagl <samuel@spagl-media.de>
This commit is contained in:
Samuel Spagl 2023-09-07 14:49:20 +02:00 committed by GitHub
parent b7ff6d1eb0
commit f93019dd68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 9844 additions and 29 deletions

0
CHANGELOG.md Normal file
View File

View File

@ -40,8 +40,9 @@ are not documented... ;)
- bass level
- *[to come] equalizer bands*
- `select` entity
- *[to come] sound mode* (additional control in the "Device" tab)
- *[to come] equalizer preset*
- sound mode (additional control in the "Device" tab)
- input (additional control in the "Device" tab)
- equalizer preset
## How to install it:

View File

@ -18,7 +18,7 @@ from .models import DeviceConfig, SoundbarConfig
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["media_player", "switch", "image", "number"]
PLATFORMS = ["media_player", "switch", "image", "number", "select"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -1,9 +1,13 @@
import asyncio
import datetime
import json
import time
from urllib.parse import quote
import logging
from pysmartthings import DeviceEntity
from ..const import DOMAIN
log = logging.getLogger(__name__)
class SoundbarDevice:
def __init__(
@ -33,6 +37,7 @@ class SoundbarDevice:
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
@ -55,13 +60,24 @@ class SoundbarDevice:
]
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(0.1)
payload = await self.get_execute_status()
retry = 0
while("x.com.samsung.networkaudio.supportedSoundmode" 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_soundmode exceeded a retry counter of 10")
return
self.__supported_soundmodes = payload[
"x.com.samsung.networkaudio.supportedSoundmode"
]
@ -69,13 +85,31 @@ class SoundbarDevice:
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"
@ -85,7 +119,18 @@ class SoundbarDevice:
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"]
@ -132,7 +177,10 @@ class SoundbarDevice:
@property
def volume_level(self) -> float:
return ((self.device.status.volume / 100) * self.__max_volume) / 100
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:
@ -144,7 +192,7 @@ class SoundbarDevice:
This respects the max volume and hovers between
:param volume: between 0 and 1
"""
await self.device.set_volume(int(volume * self.__max_volume))
await self.device.set_volume(int(volume * self.__max_volume), True)
async def mute_volume(self, mute: bool):
if mute:
@ -174,6 +222,7 @@ class SoundbarDevice:
property="x.com.samsung.networkaudio.woofer",
value=level,
)
self.__woofer_level = level
# ------------ INPUT SOURCE -------------
@ -304,6 +353,10 @@ class SoundbarDevice:
return detail_status.value
return None
@property
def media_coverart_updated(self) -> datetime.datetime:
return self.__media_cover_url_update_time
# ------------ SUPPORT FUNCTIONS ------------
async def update_execution_data(self, argument: str):

View File

@ -1,8 +1,10 @@
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 .models import DeviceConfig
from .api_extension.SoundbarDevice import SoundbarDevice
@ -41,9 +43,22 @@ class SoundbarImageEntity(ImageEntity):
sw_version=self.__device.firmware_version,
)
self._attr_image_url = self.__device.media_coverart_url
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):

View File

@ -1,4 +1,5 @@
import logging
from typing import Mapping, Any
from homeassistant.components.media_player import (
DEVICE_CLASS_SPEAKER,
@ -189,3 +190,7 @@ class SmartThingsSoundbarMediaPlayer(MediaPlayerEntity):
async def async_media_stop(self):
await self.device.media_stop()
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
return self.device.retrieve_data

View File

@ -1,6 +1,6 @@
import logging
from homeassistant.components.number import NumberEntity
from homeassistant.components.number import NumberEntity, NumberEntityDescription, NumberMode
from homeassistant.helpers.entity import DeviceInfo
from .models import DeviceConfig
@ -19,29 +19,29 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
entities.append(
SoundbarNumberEntity(
SoundbarWooferNumberEntity(
device,
"woofer_level",
device.woofer_level,
device.set_woofer,
(-6, 12),
)
)
async_add_entities(entities)
return True
class SoundbarNumberEntity(NumberEntity):
class SoundbarWooferNumberEntity(NumberEntity):
def __init__(
self,
device: SoundbarDevice,
append_unique_id: str,
state_function,
on_function,
min_max: tuple,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.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(
@ -51,27 +51,19 @@ class SoundbarNumberEntity(NumberEntity):
model=self.__device.model,
sw_version=self.__device.firmware_version,
)
self.__current_value_function = state_function
self.__set_value_function = on_function
self.__min_value = min_max[0]
self.__max_value = min_max[1]
self.__append_unique_id = append_unique_id
# ---------- GENERAL ---------------
@property
def name(self):
return self.__device.device_name
return self.__append_unique_id
# ------ STATE FUNCTIONS --------
@property
def native_value(self) -> float | None:
return self.__current_value_function
return self.__device.woofer_level
async def async_set_native_value(self, value: float):
if value > self.__max_value:
value = self.__min_value
if value < self.__min_value:
value = self.__min_value
await self.__set_value_function(value)
await self.__device.set_woofer(int(value))

View File

@ -0,0 +1,163 @@
import logging
from homeassistant.components.number import NumberEntity, NumberEntityDescription, NumberMode
from homeassistant.components.select import SelectEntityDescription, SelectEntity
from homeassistant.helpers.entity import DeviceInfo
from .models import DeviceConfig
from .api_extension.SoundbarDevice import SoundbarDevice
from .const import CONF_ENTRY_DEVICE_ID, DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
domain_data = hass.data[DOMAIN]
entities = []
for key in domain_data.devices:
device_config: DeviceConfig = domain_data.devices[key]
device = device_config.device
if device.device_id == config_entry.data.get(CONF_ENTRY_DEVICE_ID):
entities.append(
EqPresetSelectEntity(
device,
"eq_preset",
)
)
entities.append(
SoundModeSelectEntity(
device,
"sound_mode_preset",
)
)
entities.append(
InputSelectEntity(
device,
"input_preset",
)
)
async_add_entities(entities)
return True
class EqPresetSelectEntity(SelectEntity):
def __init__(
self,
device: SoundbarDevice,
append_unique_id: str,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = SelectEntityDescription(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
self._attr_options = self.__device.supported_equalizer_presets
# ---------- GENERAL ---------------
@property
def name(self):
return self.__append_unique_id
# ------ 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,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = SelectEntityDescription(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
self._attr_options = self.__device.supported_soundmodes
# ---------- GENERAL ---------------
@property
def name(self):
return self.__append_unique_id
# ------ 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,
):
self.entity_id = f"number.{device.device_name}_{append_unique_id}"
self.entity_description = SelectEntityDescription(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
self._attr_options = self.__device.supported_input_sources
# ---------- GENERAL ---------------
@property
def name(self):
return self.__append_unique_id
# ------ 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)

4
docs/.eslintignore Normal file
View File

@ -0,0 +1,4 @@
dist
node_modules
.output
.nuxt

8
docs/.eslintrc.cjs Normal file
View File

@ -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'
}
}

12
docs/.gitignore vendored Executable file
View File

@ -0,0 +1,12 @@
node_modules
*.iml
.idea
*.log*
.nuxt
.vscode
.DS_Store
coverage
dist
sw.*
.env
.output

2
docs/.npmrc Normal file
View File

@ -0,0 +1,2 @@
shamefully-hoist=true
strict-peer-dependencies=false

57
docs/README.md Executable file
View File

@ -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).

37
docs/app.config.ts Normal file
View File

@ -0,0 +1,37 @@
export default defineAppConfig({
docus: {
title: 'YASSI',
description: 'HomeAssistant: Yet another Samsung soundbar integration',
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: true,
showLinkIcon: true,
exclude: [],
fluid: true
}
}
})

69
docs/content/0.index.md Normal file
View File

@ -0,0 +1,69 @@
---
title: "YASSI"
---
::block-hero
---
cta:
- Why another HomeAssistant integration?
- #why-another-integration
secondary:
- Open on GitHub →
- https://github.com/nuxtlabs/docus
snippet:
- Custom Components
- "- input selection"
- "- soundmode selection"
- "- eq-preset selection"
- "- woofer settings"
- "- other cool things"
---
#title
Yassi
#description
Yet another Samsung soundbar integration for HomeAssistant
::
::card-grid
#title
Quick-Start
#root
:ellipsis
#default
::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="/getting-started"}
::
::card
#title
Features
#description
Many cool features are awaiting your eyes to see ✨.
<br>
<br>
:button-link[click here]{href="/features"}
::
::
## 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... ;)

View File

@ -0,0 +1,30 @@
# Getting Started
## Installation
### HACS (official)
> ⚠️ Not done yet, hopefully soon.
### HACS (custom repository)
You can add this repository as a custom repository to your hacs.
After you've done that, you can search for it like with the "official"
integrations.
### Manual
Copy the contents of `custom_components/samsung_soundbar` to `config/custom_components/samsung_soundbar`
on your HomeAssistant instance.
## Configuration
After you installed the custom component, it should be possible to configure the integration
in the `device` settings of your HomeAssistant.
You will need:
- a SmartThings `api_key` [click here](https://account.smartthings.com/tokens)
- the `device_id` of your device [click here](https://my.smartthings.com/advanced/devices)
- a name for your Soundbar
- and a `max_volume`

View File

@ -0,0 +1,35 @@
# Features
**YASSI** and retrieve / set the status of the following features grouped as a device:
- `media_player`:
- `on / off` [*read, write*]
- `volume` (set, step) [*read, write*]
- `input` (select) [*read*, write*]
- `sound_mode` (select) [*read, write*]
- `play` (button) [*write*]
- `pause` (button) [*write*]
- `media_artwork` (image) [*read*]
- `media_title` (text) [*read*]
- `media_artist` (text) [*read*]
- `number`
- **Woofer**
- level (set) [*read, write*]
- `select`
- **Input**
- `input` [*read, write*]
- `supported_inputs` [*read*]
- **Soundmode**
- `active_soundmode` [*read, write*]
- `supported_soundmodes` [*read*]
- **EQ-Preset**
- `active_eq_preset` [*read, write*]
- `supported_eq_preset` [*read*]
- `button`
- `night_mode` [*read, write*]
- `voice_amplifier` [*read, write*]
- `bass_mode` [*read, write*]
- `image`
- `media_coverart` [*read*]

View File

@ -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.

View File

@ -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>

13
docs/nuxt.config.ts Executable file
View File

@ -0,0 +1,13 @@
export default defineNuxtConfig({
// https://github.com/nuxt-themes/docus
extends: '@nuxt-themes/docus',
app: {
baseURL: '/ha_samsung_soundbar/',
},
modules: [
// https://github.com/nuxt-modules/plausible
'@nuxtjs/plausible',
// https://github.com/nuxt/devtools
'@nuxt/devtools'
]
})

21
docs/package.json Executable file
View File

@ -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"
}
}

BIN
docs/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

8
docs/renovate.json Executable file
View File

@ -0,0 +1,8 @@
{
"extends": [
"@nuxtjs"
],
"lockFileMaintenance": {
"enabled": true
}
}

4
docs/tokens.config.ts Normal file
View File

@ -0,0 +1,4 @@
import { defineTheme } from 'pinceau'
export default defineTheme({
})

3
docs/tsconfig.json Executable file
View File

@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

8966
docs/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

36
test.py Normal file
View File

@ -0,0 +1,36 @@
class Stuff:
def __init__(self, a: int):
self.__a = a
def set_a(self, new_a: int):
self.__a = new_a
@property
def a(self):
return self.__a
class Stuff2:
def __init__(self, a: int):
self.stuff = Stuff(a)
def set_a(self, a):
self.stuff.set_a(a)
@property
def a(self):
return self.stuff.a
hal = Stuff(3)
print(hal.a)
hal.set_a(5)
print(hal.a)
print()
print()
has = Stuff2(3)
print(has.a)
print(has.stuff.set_a(5))
print(has.a)
print(has.set_a(10))
print(has.a)