Skip to content

Commit

Permalink
Rewrote the component to have config flow and 1 instance instead of 2
Browse files Browse the repository at this point in the history
  • Loading branch information
albaintor committed Mar 1, 2024
1 parent 9c1372b commit f10c491
Show file tree
Hide file tree
Showing 22 changed files with 640 additions and 310 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ Use [HACS](https://hacs.xyz/).

## Configuration

Edit `configuration.yaml` and add `panasonic_remote` as a new `remote`
Edit `configuration.yaml` and add `panasonic_remote` as a new `remote` and `media_player`

```yaml
media_player:
- platform: panasonic_remote
- platform: panasonic_bluray
name: Panasonic Blu-Ray Media Player
host: 192.168.1.2
...
remote:
- platform: panasonic_remote
- platform: panasonic_bluray
name: Panasonic Blu-Ray remote
host: 192.168.1.2
```
Expand Down
79 changes: 79 additions & 0 deletions custom_components/panasonic_bluray/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""The panasonic_bluray remote and media player component."""
from __future__ import absolute_import

from panacotta import PanasonicBD
from .const import DOMAIN, DEFAULT_DEVICE_NAME, PANASONIC_COORDINATOR, STARTUP, PANASONIC_API, NAME
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform, CONF_HOST
from homeassistant.core import HomeAssistant
from .coordinator import PanasonicCoordinator

_LOGGER: logging.Logger = logging.getLogger(__package__)
_LOGGER.info(STARTUP)


PLATFORMS: list[Platform] = [
Platform.MEDIA_PLAYER,
Platform.REMOTE
]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Unfolded Circle Remote from a config entry."""

panasonic_device = PanasonicBD(entry.data[CONF_HOST])
_LOGGER.debug("Panasonic device initialization")
coordinator = PanasonicCoordinator(hass, panasonic_device, DEFAULT_DEVICE_NAME)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
PANASONIC_COORDINATOR: coordinator,
PANASONIC_API: panasonic_device,
}

# Retrieve info from Remote
# Get Basic Device Information
await coordinator.async_config_entry_first_refresh()

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
# await zeroconf.async_get_async_instance(hass)
return True

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
try:
coordinator: PanasonicCoordinator = hass.data[DOMAIN][entry.entry_id][PANASONIC_COORDINATOR]
# coordinator.api.?
except Exception as ex:
_LOGGER.error("Sony device async_unload_entry error", ex)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok


async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
"""Update Listener."""
#TODO Should be ?
#await async_unload_entry(hass, entry)
#await async_setup_entry(hass, entry)
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
try:
coordinator: PanasonicCoordinator = hass.data[DOMAIN][entry.entry_id][PANASONIC_COORDINATOR]
# coordinator.api.?
except Exception as ex:
_LOGGER.error("Panasonic device async_unload_entry error", ex)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok


async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
"""Update Listener."""
# TODO Should be ?
# await async_unload_entry(hass, entry)
# await async_setup_entry(hass, entry)
await hass.config_entries.async_reload(entry.entry_id)
112 changes: 112 additions & 0 deletions custom_components/panasonic_bluray/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""Config flow for Unfolded Circle Remote integration."""

import logging
from typing import Any

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.const import CONF_HOST
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from panacotta import PanasonicBD

from .const import DOMAIN, DEFAULT_DEVICE_NAME

_LOGGER = logging.getLogger(__name__)

STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})

def validate_input(user_input: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
config = {}
_LOGGER.debug("Sony device user input %s", user_input)
panasonic_device = PanasonicBD(user_input[CONF_HOST])
state = panasonic_device.get_status()
if state[0] == "error":
config = {"error": f"Could not connect to Panasonic device with {user_input[CONF_HOST]}"}
_LOGGER.error("Could not connect to Panasonic device with %s", user_input[CONF_HOST])

config.update(user_input)
return config


class PanasonicConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Panasonic."""

VERSION = 1
MINOR_VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

reauth_entry: ConfigEntry | None = None
user_input: dict[str, Any] | None = None

def __init__(self) -> None:
"""Sony Config Flow."""
self.discovery_info: dict[str, Any] = {}

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is None or user_input == {}:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

if self.user_input is None:
self.user_input = user_input
else:
self.user_input.update(user_input)

try:
info = await self.hass.async_add_executor_job(validate_input, self.user_input)
if info.get("error"):
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors,
)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
if len(errors.keys()) == 0:
await self.async_set_unique_id(self.user_input[CONF_HOST])
self._abort_if_unique_id_configured()

return self.async_create_entry(title=DEFAULT_DEVICE_NAME, data=info)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)


class PanasonicDeviceOptionsFlowHandler(OptionsFlow):
"""Handle Panasonic options."""

def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, int] | None = None
) -> FlowResult:
"""Manage Unfolded Circle options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

return self.async_show_form(
step_id="init",
data_schema=STEP_USER_DATA_SCHEMA,
)


class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
PROJECT_URL = "https://github.com/albaintor/panasonic_bluray_remote"
ISSUE_URL = "{}issues".format(PROJECT_URL)

NAME = "panasonicremote"
DOMAIN = "panasonic_bluray"
PANASONIC_COORDINATOR = "panasonic_coordinator"
NAME = "Panasonic Bluray"
DEFAULT_DEVICE_NAME = "Panasonic Bluray"
PANASONIC_API = "panasonic_api"
STARTUP = """
-------------------------------------------------------------------
{}
Expand Down
75 changes: 75 additions & 0 deletions custom_components/panasonic_bluray/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""The IntelliFire integration."""
from __future__ import annotations

import logging
from typing import Any

import requests
from homeassistant.components.media_player import MediaPlayerState
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import utcnow
from panacotta import PanasonicBD

from .const import MIN_TIME_BETWEEN_SCANS, DOMAIN

_LOGGER = logging.getLogger(__name__)


class PanasonicCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Data update coordinator for an Unfolded Circle Remote device."""
# List of events to subscribe to the websocket
subscribe_events: dict[str, bool]

def __init__(self, hass: HomeAssistant, panasonic_device: PanasonicBD, name: str) -> None:
"""Initialize the Coordinator."""
super().__init__(
hass,
name=DOMAIN,
logger=_LOGGER,
update_interval=MIN_TIME_BETWEEN_SCANS,
)
self.hass = hass
self.name = name
self.api = panasonic_device
self.data = {}

async def _async_update_data(self) -> dict[str, Any]:
"""Get the latest data from the Unfolded Circle Remote."""
_LOGGER.debug("Sony device coordinator update")
try:
self.data = await self.hass.async_add_executor_job(self.update)
return self.data
except Exception as ex:
_LOGGER.error("Sony device coordinator error during update", ex)
raise UpdateFailed(
f"Error communicating with Sony device API {ex}"
) from ex

def update(self) -> dict[str, any]:
"""Update the internal state by querying the device."""
data = {}
# This can take 5+ seconds to complete
state = self.api.get_play_status()

if state[0] == "error":
data["state"] = None
elif state[0] in ["off", "standby"]:
# We map both of these to off. If it's really off we can't
# turn it on, but from standby we can go to idle by pressing
# POWER.
data["state"] = MediaPlayerState.OFF
elif state[0] in ["paused", "stopped"]:
data["state"] = MediaPlayerState.IDLE
elif state[0] == "playing":
data["state"] = MediaPlayerState.PLAYING

# Update our current media position + length
if state[1] >= 0:
data["media_position"] = state[1]
else:
data["media_position"] = 0
data["media_position_updated_at"] = utcnow()
data["media_duration"] = state[2]
return data

14 changes: 14 additions & 0 deletions custom_components/panasonic_bluray/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"domain": "panasonic_bluray",
"name": "Panasonic Blu-Ray",
"config_flow": true,
"documentation": "https://github.com/albaintor/panasonic_bluray_remote",
"codeowners": ["@albaintor"],
"iot_class": "local_polling",
"integration_type": "device",
"loggers": ["panacotta"],
"version": "1.0.0",
"requirements": ["panacotta==0.2"],
"ssdp": [],
"zeroconf": []
}
Loading

0 comments on commit f10c491

Please sign in to comment.