Skip to content

Commit

Permalink
⚠️ [working commit]: add configuration steps
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelspagl committed Apr 1, 2024
1 parent 7216766 commit 00eccac
Show file tree
Hide file tree
Showing 17 changed files with 1,005 additions and 443 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ __pycache__/
*$py.class
.DS_store

config
.vscode
.ruff_cache


# C extensions
*.so
.idea
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## [0.4.0] Add more fine grained configuration flow

### Added

- Configuration flow options for enable / disable
- "advanced audio" features (NightMode, Bassmode, VoiceEnhancer)
- "woofer" feature
- "soundmode" feature
- "eq" feature

## [0.3.2] Fix division by zero

### Added
Expand Down
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ black = "*"
isort = "*"

[requires]
python_version = "3.11"
python_version = "3.12"
1,070 changes: 709 additions & 361 deletions Pipfile.lock

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions custom_components/samsung_soundbar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@
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, DOMAIN,
SUPPORTED_DOMAINS)
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__)
Expand All @@ -21,7 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# 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}")
_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(
Expand All @@ -48,6 +59,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
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(
Expand Down
38 changes: 27 additions & 11 deletions custom_components/samsung_soundbar/api_extension/SoundbarDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,37 @@

class SoundbarDevice:
def __init__(
self, device: DeviceEntity, session, max_volume: int, device_name: str
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
Expand All @@ -48,11 +60,14 @@ def __init__(
async def update(self):
await self.device.status.refresh()

await self._update_media()
await self._update_soundmode()
await self._update_advanced_audio()
await self._update_woofer()
await self._update_equalizer()
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):
self.__media_artist = self.device.status._attributes["audioTrackData"].value[
Expand All @@ -70,14 +85,14 @@ async def _update_media(self):

async def _update_soundmode(self):
await self.update_execution_data(["/sec/networkaudio/soundmode"])
await asyncio.sleep(0.1)
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(0.2)
await asyncio.sleep(1)
payload = await self.get_execute_status()
retry += 1
if retry == 10:
Expand Down Expand Up @@ -376,7 +391,8 @@ def media_coverart_updated(self) -> datetime.datetime:
# ------------ SUPPORT FUNCTIONS ------------

async def update_execution_data(self, argument: str):
return await self.device.command("main", "execute", "execute", argument)
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}]
Expand All @@ -386,8 +402,8 @@ async def get_execute_status(self):
url = f"https://api.smartthings.com/v1/devices/{self._device_id}/components/main/capabilities/execute/status"
request_headers = {"Authorization": "Bearer " + self._api_key}
resp = await self.__session.get(url, headers=request_headers)
dict = await resp.json()
return dict["data"]["value"]["payload"]
dict_stuff = await resp.json()
return dict_stuff["data"]["value"]["payload"]

async def get_song_title_artwork(self, artist: str, title: str) -> str:
"""
Expand Down
111 changes: 100 additions & 11 deletions custom_components/samsung_soundbar/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from typing import Any

import pysmartthings
import voluptuous as vol
Expand All @@ -7,8 +8,17 @@
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, DOMAIN)
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__)

Expand All @@ -24,29 +34,108 @@ async def validate_input(api, device_id: str):
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, user_input.get(CONF_ENTRY_API_KEY)
session, self.user_input.get(CONF_ENTRY_API_KEY)
)
device = await validate_input(
api, self.user_input.get(CONF_ENTRY_DEVICE_ID)
)
device = await validate_input(api, user_input.get(CONF_ENTRY_DEVICE_ID))

_LOGGER.debug(
f"Successfully validated Input, Creating entry with title {DOMAIN} and data {user_input}"
)
return self.async_create_entry(title=DOMAIN, data=user_input)
except Exception as excp:
_LOGGER.error(f"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="user",
step_id="device",
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))
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,
)
6 changes: 6 additions & 0 deletions custom_components/samsung_soundbar/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
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
Expand Down
2 changes: 1 addition & 1 deletion custom_components/samsung_soundbar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/samuelspagl/ha_samsung_soundbar/issues",
"requirements": ["pysmartthings"],
"version": "0.3.2"
"version": "0.4.0"
}
18 changes: 12 additions & 6 deletions custom_components/samsung_soundbar/media_player.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
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.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 .api_extension.SoundbarDevice import SoundbarDevice
from .const import (CONF_ENTRY_API_KEY, CONF_ENTRY_DEVICE_ID,
CONF_ENTRY_DEVICE_NAME, CONF_ENTRY_MAX_VOLUME, DOMAIN)
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__)
Expand Down
14 changes: 9 additions & 5 deletions custom_components/samsung_soundbar/number.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import logging

from homeassistant.components.number import (NumberEntity,
NumberEntityDescription,
NumberMode)
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, DOMAIN
from .const import CONF_ENTRY_DEVICE_ID, CONF_ENTRY_SETTINGS_WOOFER_NUMBER, DOMAIN
from .models import DeviceConfig

_LOGGER = logging.getLogger(__name__)
Expand All @@ -19,7 +21,9 @@ async def async_setup_entry(hass, config_entry, async_add_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 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,
Expand Down
Loading

0 comments on commit 00eccac

Please sign in to comment.