Skip to content

Commit

Permalink
Review Configuration options for TV lists (#206)
Browse files Browse the repository at this point in the history
* Add Sources, Apps and Channels lists validation in options forms
* Reorganize options menu
* Manage errors in parsing applications list (issue #204)
  • Loading branch information
ollo69 authored Dec 20, 2022
1 parent aa5048d commit 1394898
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 102 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,9 @@ generation not work with some TV models.<br/>

Example value:
```
1| Netflix: 11101200001
2| YouTube: 111299001912
3| Spotify: 3201606009684
1| Netflix: "11101200001"
2| YouTube: "111299001912"
3| Spotify: "3201606009684"
```

Known lists of App IDs: [List 1](https://github.com/tavicu/homebridge-samsung-tizen/issues/26#issuecomment-447424879),
Expand All @@ -270,9 +270,9 @@ You can configure the pair list `Name: Key` using the yaml editor in the option

Example value:
```
1| MTV: 14
2| Eurosport: 20
3| TLC: 21
1| MTV: "14"
2| Eurosport: "20"
3| TLC: "21"
```

You can also specify the source that must be used for every channel. The source must be one of the source name defined in the `source_list`<br/>
Expand Down
192 changes: 115 additions & 77 deletions custom_components/samsungtv_smart/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import logging
from numbers import Number
import socket
from typing import Any, Dict

Expand Down Expand Up @@ -542,15 +543,112 @@ async def async_step_menu(self, _=None):
return self.async_show_menu(
step_id="menu",
menu_options=[
"init",
"adv_opt",
"sync_ent",
"source_list",
"app_list",
"channel_list",
"sync_ent",
"adv_opt",
"init",
"save_exit",
],
)

async def async_step_save_exit(self, _):
"""Handle save and exit flow."""
return self._save_entry(data=self._std_options)

async def async_step_source_list(self, user_input=None):
"""Handle sources list flow."""
errors: dict[str, str] | None = None
if user_input is not None:
valid_list = _validate_tv_list(user_input[CONF_SOURCE_LIST])
if valid_list is not None:
self._source_list = valid_list
return await self.async_step_menu()
errors = {CONF_BASE: "invalid_tv_list"}

data_schema = vol.Schema(
{
vol.Optional(
CONF_SOURCE_LIST, default=self._source_list
): ObjectSelector()
}
)
return self.async_show_form(
step_id="source_list", data_schema=data_schema, errors=errors
)

async def async_step_app_list(self, user_input=None):
"""Handle apps list flow."""
errors: dict[str, str] | None = None
if user_input is not None:
valid_list = _validate_tv_list(user_input[CONF_APP_LIST])
if valid_list is not None:
self._app_list = valid_list
return await self.async_step_menu()
errors = {CONF_BASE: "invalid_tv_list"}

data_schema = vol.Schema(
{vol.Optional(CONF_APP_LIST, default=self._app_list): ObjectSelector()}
)
return self.async_show_form(
step_id="app_list", data_schema=data_schema, errors=errors
)

async def async_step_channel_list(self, user_input=None):
"""Handle channels list flow."""
errors: dict[str, str] | None = None
if user_input is not None:
valid_list = _validate_tv_list(user_input[CONF_CHANNEL_LIST])
if valid_list is not None:
self._channel_list = valid_list
return await self.async_step_menu()
errors = {CONF_BASE: "invalid_tv_list"}

data_schema = vol.Schema(
{
vol.Optional(
CONF_CHANNEL_LIST, default=self._channel_list
): ObjectSelector()
}
)
return self.async_show_form(
step_id="channel_list", data_schema=data_schema, errors=errors
)

async def async_step_sync_ent(self, user_input=None):
"""Handle syncronized entity flow."""
if user_input is not None:
self._sync_ent_opt = user_input
return await self.async_step_menu()
return self._async_sync_ent_form()

@callback
def _async_sync_ent_form(self):
"""Return configuration form for syncronized entity."""
select_entities = EntitySelectorConfig(
domain=_async_get_domains_service(self.hass, SERVICE_TURN_ON),
exclude_entities=_async_get_entry_entities(self.hass, self._entry_id),
multiple=True,
)
options = _validate_options(self._sync_ent_opt)

data_schema = vol.Schema(
{
vol.Optional(
CONF_SYNC_TURN_OFF,
description={
"suggested_value": options.get(CONF_SYNC_TURN_OFF, [])
},
): EntitySelector(select_entities),
vol.Optional(
CONF_SYNC_TURN_ON,
description={"suggested_value": options.get(CONF_SYNC_TURN_ON, [])},
): EntitySelector(select_entities),
}
)
return self.async_show_form(step_id="sync_ent", data_schema=data_schema)

async def async_step_adv_opt(self, user_input=None):
"""Handle advanced options flow."""
if user_input is not None:
Expand Down Expand Up @@ -605,80 +703,6 @@ def _async_adv_opt_form(self):
)
return self.async_show_form(step_id="adv_opt", data_schema=data_schema)

async def async_step_sync_ent(self, user_input=None):
"""Handle syncronized entity flow."""
if user_input is not None:
self._sync_ent_opt = user_input
return await self.async_step_menu()
return self._async_sync_ent_form()

@callback
def _async_sync_ent_form(self):
"""Return configuration form for syncronized entity."""
select_entities = EntitySelectorConfig(
domain=_async_get_domains_service(self.hass, SERVICE_TURN_ON),
exclude_entities=_async_get_entry_entities(self.hass, self._entry_id),
multiple=True,
)
options = _validate_options(self._sync_ent_opt)

data_schema = vol.Schema(
{
vol.Optional(
CONF_SYNC_TURN_OFF,
description={
"suggested_value": options.get(CONF_SYNC_TURN_OFF, [])
},
): EntitySelector(select_entities),
vol.Optional(
CONF_SYNC_TURN_ON,
description={"suggested_value": options.get(CONF_SYNC_TURN_ON, [])},
): EntitySelector(select_entities),
}
)
return self.async_show_form(step_id="sync_ent", data_schema=data_schema)

async def async_step_app_list(self, user_input=None):
"""Handle apps list flow."""
if user_input is not None:
self._app_list = user_input[CONF_APP_LIST]
return await self.async_step_menu()

data_schema = vol.Schema(
{vol.Optional(CONF_APP_LIST, default=self._app_list): ObjectSelector()}
)
return self.async_show_form(step_id="app_list", data_schema=data_schema)

async def async_step_channel_list(self, user_input=None):
"""Handle channels list flow."""
if user_input is not None:
self._channel_list = user_input[CONF_CHANNEL_LIST]
return await self.async_step_menu()

data_schema = vol.Schema(
{
vol.Optional(
CONF_CHANNEL_LIST, default=self._channel_list
): ObjectSelector()
}
)
return self.async_show_form(step_id="channel_list", data_schema=data_schema)

async def async_step_source_list(self, user_input=None):
"""Handle sources list flow."""
if user_input is not None:
self._source_list = user_input[CONF_SOURCE_LIST]
return await self.async_step_menu()

data_schema = vol.Schema(
{
vol.Optional(
CONF_SOURCE_LIST, default=self._source_list
): ObjectSelector()
}
)
return self.async_show_form(step_id="source_list", data_schema=data_schema)


def _validate_options(options: dict):
"""Validate options format"""
Expand All @@ -694,6 +718,20 @@ def _validate_options(options: dict):
return valid_options


def _validate_tv_list(input_list: dict[str, Any]) -> dict[str, str] | None:
"""Validate TV list from object selector."""
valid_list = {}
for name_val, id_val in input_list.items():
if not id_val:
continue
if isinstance(id_val, Number):
id_val = str(id_val)
if not isinstance(id_val, str):
return None
valid_list[name_val] = id_val
return valid_list


def _dict_to_select(opt_dict: dict):
"""Covert a dict to a SelectSelectorConfig."""
return SelectSelectorConfig(
Expand Down
38 changes: 25 additions & 13 deletions custom_components/samsungtv_smart/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,21 +400,33 @@ def _get_add_dev_info(dev_model, dev_name, dev_os, dev_mac):
return dict(dev_info)

@staticmethod
def _split_app_list(app_list, sep=ST_APP_SEPARATOR):
def _split_app_list(app_list: dict[str, str]) -> list[dict[str, str]]:
"""Split the application list for standard and SmartThings."""
retval = {"app": {}, "appST": {}}
apps = {}
apps_st = {}

for app_name, value in app_list.items():
value_split = value.split(sep, 1)
app_id = value_split[0]
if len(value_split) == 1:
for app_name, app_ids in app_list.items():
try:
app_id_split = app_ids.split(ST_APP_SEPARATOR, 1)
except (ValueError, AttributeError):
_LOGGER.warning(
"Invalid ID [%s] for App [%s] will be ignored."
" Use integration options to correct the App ID",
app_ids,
app_name,
)
continue

app_id = app_id_split[0]
if len(app_id_split) == 1:
_, st_app_id, _ = _get_default_app_info(app_id)
else:
st_app_id = value_split[1]
retval["app"][app_name] = app_id
retval["appST"][app_name] = st_app_id or app_id
st_app_id = app_id_split[1]

apps[app_name] = app_id
apps_st[app_name] = st_app_id or app_id

return retval
return [apps, apps_st]

def _load_tv_lists(self, first_load=False):
"""Load TV sources, apps and channels."""
Expand All @@ -431,9 +443,9 @@ def _load_tv_lists(self, first_load=False):
# load apps list
app_list = self._get_option(CONF_APP_LIST, {})
if app_list:
double_list = self._split_app_list(app_list, "/")
self._app_list = double_list["app"]
self._app_list_st = double_list["appST"]
double_list = self._split_app_list(app_list)
self._app_list = double_list[0]
self._app_list_st = double_list[1]
else:
self._app_list = None if first_load else {}
self._app_list_st = None if first_load else {}
Expand Down
10 changes: 7 additions & 3 deletions custom_components/samsungtv_smart/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@
"menu": {
"title": "SamsungTV Smart options menu",
"menu_options": {
"init": "Back to main page",
"adv_opt": "Advanced options",
"sync_ent": "Synched entities configuration",
"app_list": "Applications list configuration",
"channel_list": "Channels list configuration",
"source_list": "Sources list configuration"
"init": "Back to basic options",
"save_exit": "Save options and exit",
"source_list": "Sources list configuration",
"sync_ent": "Synched entities configuration"
}
},
"adv_opt": {
Expand Down Expand Up @@ -106,6 +107,9 @@
"source_list": "Sources list:"
}
}
},
"error": {
"invalid_tv_list": "Invalid format. Please check documentation"
}
}
}
10 changes: 7 additions & 3 deletions custom_components/samsungtv_smart/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@
"menu": {
"title": "Menù opzioni SamsungTV Smart",
"menu_options": {
"init": "Torna alla pagina principale",
"adv_opt": "Opzioni avanzate",
"sync_ent": "Configurazione entità collegate",
"app_list": "Configurazione lista applicazioni",
"channel_list": "Configurazione lista canali",
"source_list": "Configurazione lista sorgenti"
"init": "Torna alle opzioni di base",
"save_exit": "Salva le opzioni ed esci",
"source_list": "Configurazione lista sorgenti",
"sync_ent": "Configurazione entità collegate"
}
},
"adv_opt": {
Expand Down Expand Up @@ -106,6 +107,9 @@
"source_list": "Lista sorgenti:"
}
}
},
"error": {
"invalid_tv_list": "Formato not valido. Controlla la documentazione"
}
}
}

0 comments on commit 1394898

Please sign in to comment.