Add config flow to kodi (#38551)

* Add config flow to kodi

* Fix lint errors

* Remove entry update listener

* Create test_init.py

* Apply suggestions from code review

Co-authored-by: Chris Talkington <chris@talkingtontech.com>

* Update __init__.py

* fix indentation

* Apply suggestions from code review

* Apply suggestions from code review

* Update tests/components/kodi/__init__.py

* Fix init test

* Fix merge

* More review changes

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Chris Talkington <chris@talkingtontech.com>

* Fix black formatting

* Fix Flake8

* Don't store CONF_ID

* Fall back to entry id

* Apply suggestions from code review

Co-authored-by: J. Nick Koston <nick@koston.org>

* Update __init__.py

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Chris Talkington <chris@talkingtontech.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
On Freund 2020-08-21 07:16:58 +03:00 committed by GitHub
parent e0e31693f5
commit c1ed584f2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1362 additions and 527 deletions

View file

@ -1,89 +1,100 @@
"""The kodi component."""
import asyncio
import logging
import voluptuous as vol
from pykodi import CannotConnectError, InvalidAuthError, Kodi, get_kodi_connection
from homeassistant.components.kodi.const import DOMAIN
from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
SERVICE_ADD_MEDIA = "add_to_playlist"
SERVICE_CALL_METHOD = "call_method"
ATTR_MEDIA_TYPE = "media_type"
ATTR_MEDIA_NAME = "media_name"
ATTR_MEDIA_ARTIST_NAME = "artist_name"
ATTR_MEDIA_ID = "media_id"
ATTR_METHOD = "method"
MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids})
KODI_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend(
{
vol.Required(ATTR_MEDIA_TYPE): cv.string,
vol.Optional(ATTR_MEDIA_ID): cv.string,
vol.Optional(ATTR_MEDIA_NAME): cv.string,
vol.Optional(ATTR_MEDIA_ARTIST_NAME): cv.string,
}
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
)
KODI_CALL_METHOD_SCHEMA = MEDIA_PLAYER_SCHEMA.extend(
{vol.Required(ATTR_METHOD): cv.string}, extra=vol.ALLOW_EXTRA
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
CONF_WS_PORT,
DATA_CONNECTION,
DATA_KODI,
DATA_REMOVE_LISTENER,
DATA_VERSION,
DOMAIN,
)
SERVICE_TO_METHOD = {
SERVICE_ADD_MEDIA: {
"method": "async_add_media_to_playlist",
"schema": KODI_ADD_MEDIA_SCHEMA,
},
SERVICE_CALL_METHOD: {
"method": "async_call_method",
"schema": KODI_CALL_METHOD_SCHEMA,
},
}
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["media_player"]
async def async_setup(hass, config):
"""Set up the Kodi integration."""
if any((CONF_PLATFORM, DOMAIN) in cfg.items() for cfg in config.get(MP_DOMAIN, [])):
# Register the Kodi media_player services
async def async_service_handler(service):
"""Map services to methods on MediaPlayerEntity."""
method = SERVICE_TO_METHOD.get(service.service)
if not method:
return
params = {
key: value for key, value in service.data.items() if key != "entity_id"
}
entity_ids = service.data.get("entity_id")
if entity_ids:
target_players = [
player
for player in hass.data[DOMAIN].values()
if player.entity_id in entity_ids
]
else:
target_players = hass.data[DOMAIN].values()
update_tasks = []
for player in target_players:
await getattr(player, method["method"])(**params)
for player in target_players:
if player.should_poll:
update_coro = player.async_update_ha_state(True)
update_tasks.append(update_coro)
if update_tasks:
await asyncio.wait(update_tasks)
for service in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service]["schema"]
hass.services.async_register(
DOMAIN, service, async_service_handler, schema=schema
)
# Return boolean to indicate that initialization was successful.
hass.data.setdefault(DOMAIN, {})
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Kodi from a config entry."""
conn = get_kodi_connection(
entry.data[CONF_HOST],
entry.data[CONF_PORT],
entry.data[CONF_WS_PORT],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
entry.data[CONF_SSL],
session=async_get_clientsession(hass),
)
try:
await conn.connect()
kodi = Kodi(conn)
await kodi.ping()
raw_version = (await kodi.get_application_properties(["version"]))["version"]
except CannotConnectError as error:
raise ConfigEntryNotReady from error
except InvalidAuthError as error:
_LOGGER.error(
"Login to %s failed: [%s]", entry.data[CONF_HOST], error,
)
return False
async def _close(event):
await conn.close()
remove_stop_listener = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close)
version = f"{raw_version['major']}.{raw_version['minor']}"
hass.data[DOMAIN][entry.entry_id] = {
DATA_CONNECTION: conn,
DATA_KODI: kodi,
DATA_REMOVE_LISTENER: remove_stop_listener,
DATA_VERSION: version,
}
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)
if unload_ok:
data = hass.data[DOMAIN].pop(entry.entry_id)
await data[DATA_CONNECTION].close()
data[DATA_REMOVE_LISTENER]()
return unload_ok