Refactor project into two standalone components
Split monorepo into separate units for future independent repositories: - media-server/: Standalone FastAPI server with own README, requirements, config example, and CLAUDE.md - haos-integration/: HACS-ready Home Assistant integration with hacs.json, own README, and CLAUDE.md Both components now have their own .gitignore files and can be easily extracted into separate repositories. Also adds custom icon support for scripts configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"""The Remote Media Player integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .api_client import MediaServerClient, MediaServerError
|
||||
from .const import (
|
||||
ATTR_SCRIPT_ARGS,
|
||||
ATTR_SCRIPT_NAME,
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
CONF_TOKEN,
|
||||
DOMAIN,
|
||||
SERVICE_EXECUTE_SCRIPT,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.BUTTON]
|
||||
|
||||
# Service schema for execute_script
|
||||
SERVICE_EXECUTE_SCRIPT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_SCRIPT_NAME): cv.string,
|
||||
vol.Optional(ATTR_SCRIPT_ARGS, default=[]): vol.All(
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Remote Media Player from a config entry.
|
||||
|
||||
Args:
|
||||
hass: Home Assistant instance
|
||||
entry: Config entry
|
||||
|
||||
Returns:
|
||||
True if setup was successful
|
||||
"""
|
||||
_LOGGER.debug("Setting up Remote Media Player: %s", entry.entry_id)
|
||||
|
||||
# Create API client
|
||||
client = MediaServerClient(
|
||||
host=entry.data[CONF_HOST],
|
||||
port=entry.data[CONF_PORT],
|
||||
token=entry.data[CONF_TOKEN],
|
||||
)
|
||||
|
||||
# Verify connection
|
||||
if not await client.check_connection():
|
||||
_LOGGER.error("Failed to connect to Media Server")
|
||||
await client.close()
|
||||
return False
|
||||
|
||||
# Store client in hass.data
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
"client": client,
|
||||
}
|
||||
|
||||
# Register services if not already registered
|
||||
if not hass.services.has_service(DOMAIN, SERVICE_EXECUTE_SCRIPT):
|
||||
async def async_execute_script(call: ServiceCall) -> dict[str, Any]:
|
||||
"""Execute a script on the media server."""
|
||||
script_name = call.data[ATTR_SCRIPT_NAME]
|
||||
script_args = call.data.get(ATTR_SCRIPT_ARGS, [])
|
||||
|
||||
_LOGGER.debug(
|
||||
"Executing script '%s' with args: %s", script_name, script_args
|
||||
)
|
||||
|
||||
# Get all clients and execute on all of them
|
||||
results = {}
|
||||
for entry_id, data in hass.data[DOMAIN].items():
|
||||
client: MediaServerClient = data["client"]
|
||||
try:
|
||||
result = await client.execute_script(script_name, script_args)
|
||||
results[entry_id] = result
|
||||
_LOGGER.info(
|
||||
"Script '%s' executed on %s: success=%s",
|
||||
script_name,
|
||||
entry_id,
|
||||
result.get("success", False),
|
||||
)
|
||||
except MediaServerError as err:
|
||||
_LOGGER.error(
|
||||
"Failed to execute script '%s' on %s: %s",
|
||||
script_name,
|
||||
entry_id,
|
||||
err,
|
||||
)
|
||||
results[entry_id] = {"success": False, "error": str(err)}
|
||||
|
||||
return results
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_EXECUTE_SCRIPT,
|
||||
async_execute_script,
|
||||
schema=SERVICE_EXECUTE_SCRIPT_SCHEMA,
|
||||
)
|
||||
|
||||
# Forward setup to platforms
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# Register update listener for options
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry.
|
||||
|
||||
Args:
|
||||
hass: Home Assistant instance
|
||||
entry: Config entry
|
||||
|
||||
Returns:
|
||||
True if unload was successful
|
||||
"""
|
||||
_LOGGER.debug("Unloading Remote Media Player: %s", entry.entry_id)
|
||||
|
||||
# Unload platforms
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok:
|
||||
# Close client and remove data
|
||||
data = hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
# Shutdown coordinator (WebSocket cleanup)
|
||||
if "coordinator" in data:
|
||||
await data["coordinator"].async_shutdown()
|
||||
|
||||
# Close HTTP client
|
||||
await data["client"].close()
|
||||
|
||||
# Remove services if this was the last entry
|
||||
if not hass.data[DOMAIN]:
|
||||
hass.services.async_remove(DOMAIN, SERVICE_EXECUTE_SCRIPT)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update.
|
||||
|
||||
Args:
|
||||
hass: Home Assistant instance
|
||||
entry: Config entry
|
||||
"""
|
||||
_LOGGER.debug("Options updated for: %s", entry.entry_id)
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
Reference in New Issue
Block a user