Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 35 additions & 76 deletions custom_components/eldes_alarm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import asyncio
from http import HTTPStatus

import aiohttp
from aiohttp import ClientResponseError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant, callback
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_PIN, CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, ConfigEntryAuthFailed
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand All @@ -20,15 +20,15 @@

from .const import (
DEFAULT_NAME,
DEFAULT_ZONE,
DATA_CLIENT,
DATA_COORDINATOR,
DATA_DEVICES,
DEFAULT_SCAN_INTERVAL,
DEFAULT_EVENTS_LIST_SIZE,
CONF_DEVICE_IMEI,
CONF_EVENTS_LIST_SIZE,
DOMAIN
DEFAULT_EVENTS_LIST_SIZE,
DOMAIN,
)

from .core.eldes_cloud import EldesCloud

_LOGGER = logging.getLogger(__name__)
Expand All @@ -42,86 +42,73 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Eldes from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
pin = entry.data[CONF_PIN]
selected_imei = entry.data[CONF_DEVICE_IMEI]
scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)

session = async_get_clientsession(hass)
eldes_client = EldesCloud(session, username, password)
eldes_client = EldesCloud(session, username, password, pin)

try:
await eldes_client.login()
except (asyncio.TimeoutError, aiohttp.ClientError) as ex:
if ex.status == HTTPStatus.UNAUTHORIZED:
except (asyncio.TimeoutError, ClientResponseError) as ex:
if isinstance(ex, ClientResponseError) and ex.status == HTTPStatus.UNAUTHORIZED:
raise ConfigEntryAuthFailed from ex

raise ConfigEntryNotReady from ex
except Exception as ex:
_LOGGER.error("Failed to setup Eldes: %s", ex)
_LOGGER.error("Failed to login to Eldes: %s", ex)
return False

async def async_update_data():
"""Fetch data for selected Eldes device."""
try:
return await async_get_devices(hass, entry, eldes_client)
except:
pass

try:
await eldes_client.login()
return await async_get_devices(hass, entry, eldes_client)
await eldes_client.renew_token()
return [await async_fetch_device_data(eldes_client, selected_imei, entry)]
except Exception as ex:
_LOGGER.exception("Unknown error occurred during Eldes update request: %s", ex)
_LOGGER.exception("Failed to update Eldes device data: %s", ex)
raise UpdateFailed(ex) from ex

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN,
name=f"Eldes {selected_imei}",
update_method=async_update_data,
update_interval=timedelta(seconds=scan_interval),
)

await coordinator.async_config_entry_first_refresh()

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_CLIENT: eldes_client,
DATA_COORDINATOR: coordinator,
DATA_DEVICES: [],
}

# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()

# Setup components
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True


async def async_get_devices(hass: HomeAssistant, entry: ConfigEntry, eldes_client: EldesCloud):
"""Fetch data from Eldes API."""
async def async_fetch_device_data(eldes_client: EldesCloud, imei: str, entry: ConfigEntry) -> dict:
"""Fetch full data for a single Eldes device."""
events_list_size = entry.options.get(CONF_EVENTS_LIST_SIZE, DEFAULT_EVENTS_LIST_SIZE)

await eldes_client.renew_token()

devices = await eldes_client.get_devices()

# Retrieve additional device info, partitions and outputs
for device in devices:
device["info"] = await eldes_client.get_device_info(device["imei"])
device["partitions"] = await eldes_client.get_device_partitions(device["imei"])
device["outputs"] = await eldes_client.get_device_outputs(device["imei"])
device["temp"] = await eldes_client.get_temperatures(device["imei"])
device["events"] = await eldes_client.get_events(events_list_size)

hass.data[DOMAIN][entry.entry_id][DATA_DEVICES] = devices
device = {
"imei": imei,
"info": await eldes_client.get_device_info(imei),
"partitions": await eldes_client.get_device_partitions(imei),
"outputs": await eldes_client.get_device_outputs(imei),
"temp": await eldes_client.get_temperatures(imei),
"events": await eldes_client.get_events(imei, events_list_size),
}

return devices
return device


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
"""Unload Eldes config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

hass.data[DOMAIN].pop(entry.entry_id, None)
return unload_ok


Expand All @@ -138,7 +125,7 @@ def __init__(self, client, coordinator, device_index, entity_index=None):

@property
def data(self):
"""Shortcut to access data for the entity."""
"""Shortcut to access this device's data."""
return self.coordinator.data[self.device_index]

@property
Expand All @@ -149,33 +136,5 @@ def device_info(self):
"name": self.data["info"]["model"],
"manufacturer": DEFAULT_NAME,
"sw_version": self.data["info"]["firmware"],
"model": self.data["info"]["model"]
}


class EldesZoneEntity(CoordinatorEntity):
"""Defines a base Eldes zone entity."""

def __init__(self, client, coordinator, device_index, entity_index):
"""Initialize the Eldes entity."""
super().__init__(coordinator)
self.client = client
self.device_index = device_index
self.entity_index = entity_index
self.imei = self.coordinator.data[self.device_index]["imei"]

@property
def data(self):
"""Shortcut to access data for the entity."""
return self.coordinator.data[self.device_index]["partitions"][self.entity_index]

@property
def device_info(self):
"""Return zone info for the Eldes entity."""
return {
"identifiers": {(DOMAIN, self.data["internalId"])},
"name": self.data["name"],
"manufacturer": DEFAULT_NAME,
"model": DEFAULT_ZONE,
"suggested_area": self.data["name"]
"model": self.data["info"]["model"],
}
136 changes: 59 additions & 77 deletions custom_components/eldes_alarm/alarm_control_panel.py
Original file line number Diff line number Diff line change
@@ -1,125 +1,107 @@
"""Interfaces with Eldes control panels."""
"""Support for Eldes control panels."""
import logging

from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
AlarmControlPanelState
AlarmControlPanelState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import (
DATA_CLIENT,
DATA_COORDINATOR,
DOMAIN,
ALARM_MODES
ALARM_MODES,
)
from . import EldesZoneEntity
from . import EldesDeviceEntity

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities):
"""Set up the Eldes sensor platform."""
"""Set up the Eldes alarm control panel platform."""
client = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT]
coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
entities = []

for deviceIndex, _ in enumerate(coordinator.data):
for partitionIndex, _ in enumerate(coordinator.data[deviceIndex]["partitions"]):
entities.append(EldesAlarmPanel(client, coordinator, deviceIndex, partitionIndex))
for device_index, device in enumerate(coordinator.data):
for partition_index in range(len(device["partitions"])):
entity = EldesAlarmPanel(client, coordinator, device_index, partition_index)
entity._attr_alarm_state = entity.partition["state"]
entities.append(entity)

async_add_entities(entities)


class EldesAlarmPanel(EldesZoneEntity, AlarmControlPanelEntity):
"""Representation of an Eldes Alarm."""
class EldesAlarmPanel(EldesDeviceEntity, AlarmControlPanelEntity):
"""Class for the Eldes alarm control panel."""

_attr_supported_features = (
AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_HOME
AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_HOME
)
_attr_code_arm_required = False

def __init__(self, client, coordinator, device_index, partition_index):
super().__init__(client, coordinator, device_index, partition_index)
self._previous_state = None

@property
def unique_id(self):
"""Return the unique id."""
return f"{self.imei}_zone_{self.data['internalId']}"
def partition(self):
return self.data["partitions"][self.entity_index]

@property
def name(self):
"""Return the name of the zone."""
return self.data["name"]
def unique_id(self):
return f"{self.imei}_zone_{self.partition['internalId']}"

@property
def state(self):
"""Return the state of the alarm."""
return self.data["state"]
def name(self):
return self.partition["name"]

@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {
"armed": self.data["armed"],
"armStay": self.data["armStay"],
"state": self.data["state"],
"hasUnacceptedPartitionAlarms": self.data["hasUnacceptedPartitionAlarms"]
"armed": self.partition["armed"],
"armStay": self.partition["armStay"],
"state": self.partition["state"],
"hasUnacceptedPartitionAlarms": self.partition["hasUnacceptedPartitionAlarms"],
}

async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
current_state = self.data["state"]

self.data["state"] = AlarmControlPanelState.DISARMING
self.async_write_ha_state()

try:
await self.client.renew_token()
await self.client.set_alarm(
ALARM_MODES["DISARM"],
self.imei,
self.data['internalId']
)
except Exception as ex:
_LOGGER.error("Failed to change state: %s", ex)
self.data["state"] = current_state
self.async_write_ha_state()

async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
current_state = self.data["state"]

self.data["state"] = AlarmControlPanelState.ARMING
self.async_write_ha_state()

try:
await self.client.renew_token()
await self.client.set_alarm(
ALARM_MODES["ARM_AWAY"],
self.imei,
self.data['internalId']
)
except Exception as ex:
_LOGGER.error("Failed to change state: %s", ex)
self.data["state"] = current_state
self.async_write_ha_state()

async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm night command."""
current_state = self.data["state"]
@property
def alarm_state(self) -> AlarmControlPanelState:
return self.partition["state"]

self.data["state"] = AlarmControlPanelState.ARMING
async def _async_set_alarm(self, mode: str, transition_state: AlarmControlPanelState) -> None:
self._previous_state = self.partition["state"]
self.partition["state"] = transition_state
self.async_write_ha_state()

try:
await self.client.renew_token()
await self.client.set_alarm(
ALARM_MODES["ARM_HOME"],
self.imei,
self.data['internalId']
)
await self.client.set_alarm(mode, self.imei, self.partition["internalId"])
except Exception as ex:
_LOGGER.error("Failed to change state: %s", ex)
self.data["state"] = current_state
_LOGGER.error("Failed to set alarm (%s): %s", mode, ex)
self.partition["state"] = self._previous_state
self.async_write_ha_state()
raise


async def async_alarm_disarm(self, code=None) -> None:
await self._async_set_alarm(
ALARM_MODES["DISARM"],
AlarmControlPanelState.DISARMING
)

async def async_alarm_arm_away(self, code=None) -> None:
await self._async_set_alarm(
ALARM_MODES["ARM_AWAY"],
AlarmControlPanelState.ARMING
)

async def async_alarm_arm_home(self, code=None) -> None:
await self._async_set_alarm(
ALARM_MODES["ARM_HOME"],
AlarmControlPanelState.ARMING
)
Loading
Loading