161 lines
4.8 KiB
Python
161 lines
4.8 KiB
Python
|
"""The MELCloud Climate integration."""
|
||
|
import asyncio
|
||
|
from datetime import timedelta
|
||
|
import logging
|
||
|
from typing import Any, Dict, List
|
||
|
|
||
|
from aiohttp import ClientConnectionError
|
||
|
from async_timeout import timeout
|
||
|
from pymelcloud import Device, get_devices
|
||
|
import voluptuous as vol
|
||
|
|
||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||
|
from homeassistant.const import CONF_TOKEN, CONF_USERNAME
|
||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||
|
import homeassistant.helpers.config_validation as cv
|
||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||
|
from homeassistant.util import Throttle
|
||
|
|
||
|
from .const import DOMAIN
|
||
|
|
||
|
_LOGGER = logging.getLogger(__name__)
|
||
|
|
||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||
|
|
||
|
PLATFORMS = ["climate", "sensor"]
|
||
|
|
||
|
CONF_LANGUAGE = "language"
|
||
|
CONFIG_SCHEMA = vol.Schema(
|
||
|
{
|
||
|
DOMAIN: vol.Schema(
|
||
|
{
|
||
|
vol.Required(CONF_USERNAME): cv.string,
|
||
|
vol.Required(CONF_TOKEN): cv.string,
|
||
|
}
|
||
|
)
|
||
|
},
|
||
|
extra=vol.ALLOW_EXTRA,
|
||
|
)
|
||
|
|
||
|
|
||
|
async def async_setup(hass: HomeAssistantType, config: ConfigEntry):
|
||
|
"""Establish connection with MELCloud."""
|
||
|
if DOMAIN not in config:
|
||
|
return True
|
||
|
|
||
|
username = config[DOMAIN][CONF_USERNAME]
|
||
|
token = config[DOMAIN][CONF_TOKEN]
|
||
|
hass.async_create_task(
|
||
|
hass.config_entries.flow.async_init(
|
||
|
DOMAIN,
|
||
|
context={"source": SOURCE_IMPORT},
|
||
|
data={CONF_USERNAME: username, CONF_TOKEN: token},
|
||
|
)
|
||
|
)
|
||
|
return True
|
||
|
|
||
|
|
||
|
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||
|
"""Establish connection with MELClooud."""
|
||
|
conf = entry.data
|
||
|
mel_devices = await mel_devices_setup(hass, conf[CONF_TOKEN])
|
||
|
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: mel_devices})
|
||
|
for platform in PLATFORMS:
|
||
|
hass.async_create_task(
|
||
|
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||
|
)
|
||
|
return True
|
||
|
|
||
|
|
||
|
async def async_unload_entry(hass, config_entry):
|
||
|
"""Unload a config entry."""
|
||
|
await asyncio.gather(
|
||
|
*[
|
||
|
hass.config_entries.async_forward_entry_unload(config_entry, platform)
|
||
|
for platform in PLATFORMS
|
||
|
]
|
||
|
)
|
||
|
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||
|
if not hass.data[DOMAIN]:
|
||
|
hass.data.pop(DOMAIN)
|
||
|
return True
|
||
|
|
||
|
|
||
|
class MelCloudDevice:
|
||
|
"""MELCloud Device instance."""
|
||
|
|
||
|
def __init__(self, device: Device):
|
||
|
"""Construct a device wrapper."""
|
||
|
self.device = device
|
||
|
self.name = device.name
|
||
|
self._available = True
|
||
|
|
||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||
|
async def async_update(self, **kwargs):
|
||
|
"""Pull the latest data from MELCloud."""
|
||
|
try:
|
||
|
await self.device.update()
|
||
|
self._available = True
|
||
|
except ClientConnectionError:
|
||
|
_LOGGER.warning("Connection failed for %s", self.name)
|
||
|
self._available = False
|
||
|
|
||
|
async def async_set(self, properties: Dict[str, Any]):
|
||
|
"""Write state changes to the MELCloud API."""
|
||
|
try:
|
||
|
await self.device.set(properties)
|
||
|
self._available = True
|
||
|
except ClientConnectionError:
|
||
|
_LOGGER.warning("Connection failed for %s", self.name)
|
||
|
self._available = False
|
||
|
|
||
|
@property
|
||
|
def available(self) -> bool:
|
||
|
"""Return True if entity is available."""
|
||
|
return self._available
|
||
|
|
||
|
@property
|
||
|
def device_id(self):
|
||
|
"""Return device ID."""
|
||
|
return self.device.device_id
|
||
|
|
||
|
@property
|
||
|
def building_id(self):
|
||
|
"""Return building ID of the device."""
|
||
|
return self.device.building_id
|
||
|
|
||
|
@property
|
||
|
def device_info(self):
|
||
|
"""Return a device description for device registry."""
|
||
|
_device_info = {
|
||
|
"identifiers": {(DOMAIN, f"{self.device.mac}-{self.device.serial}")},
|
||
|
"manufacturer": "Mitsubishi Electric",
|
||
|
"name": self.name,
|
||
|
}
|
||
|
unit_infos = self.device.units
|
||
|
if unit_infos is not None:
|
||
|
_device_info["model"] = ", ".join(
|
||
|
[x["model"] for x in unit_infos if x["model"]]
|
||
|
)
|
||
|
return _device_info
|
||
|
|
||
|
|
||
|
async def mel_devices_setup(hass, token) -> List[MelCloudDevice]:
|
||
|
"""Query connected devices from MELCloud."""
|
||
|
session = hass.helpers.aiohttp_client.async_get_clientsession()
|
||
|
try:
|
||
|
with timeout(10):
|
||
|
all_devices = await get_devices(
|
||
|
token,
|
||
|
session,
|
||
|
conf_update_interval=timedelta(minutes=5),
|
||
|
device_set_debounce=timedelta(seconds=1),
|
||
|
)
|
||
|
except (asyncio.TimeoutError, ClientConnectionError) as ex:
|
||
|
raise ConfigEntryNotReady() from ex
|
||
|
|
||
|
wrapped_devices = {}
|
||
|
for device_type, devices in all_devices.items():
|
||
|
wrapped_devices[device_type] = [MelCloudDevice(device) for device in devices]
|
||
|
return wrapped_devices
|