diff --git a/custom_components/olarm_sensors/__init__.py b/custom_components/olarm_sensors/__init__.py index 5cf1ee2..6a62322 100644 --- a/custom_components/olarm_sensors/__init__.py +++ b/custom_components/olarm_sensors/__init__.py @@ -3,6 +3,7 @@ import os import re +from olarm_api_rainepretorius import OlarmApi, OlarmSetupApi import voluptuous as vol from homeassistant.components.alarm_control_panel import ( @@ -10,6 +11,7 @@ ) from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN from homeassistant.config_entries import ConfigEntry @@ -32,13 +34,13 @@ APIForbiddenError, DictionaryKeyError, ) -from .olarm_api import OlarmApi, OlarmSetupApi path = os.path.abspath(__file__).replace("__init__.py", "") PLATFORMS = [ ALARM_CONTROL_PANEL_DOMAIN, BINARY_SENSOR_DOMAIN, BUTTON_DOMAIN, + SENSOR_DOMAIN, SWITCH_DOMAIN, UPDATE_DOMAIN, ] diff --git a/custom_components/olarm_sensors/config_flow.py b/custom_components/olarm_sensors/config_flow.py index 6696854..575f722 100644 --- a/custom_components/olarm_sensors/config_flow.py +++ b/custom_components/olarm_sensors/config_flow.py @@ -2,7 +2,8 @@ import asyncio from typing import Any -import voluptuous as vol +from olarm_api_rainepretorius import OlarmSetupApi # type: ignore[import-untyped] +import voluptuous as vol # type: ignore[import-untyped] from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL @@ -23,7 +24,6 @@ ) from .coordinator import OlarmCoordinator from .exceptions import APIForbiddenError -from .olarm_api import OlarmSetupApi class OlarmSensorsConfigFlow(ConfigFlow, domain=DOMAIN): @@ -65,15 +65,31 @@ async def async_step_user(self, user_input=None) -> FlowResult: else: alarm_code = user_input[CONF_ALARM_CODE] - try: - api = OlarmSetupApi(api_key) - json = await api.get_olarm_devices() + if api_key not in ("mock_api_key", ""): + try: + api = OlarmSetupApi(api_key) + json = await api.get_olarm_devices() - except APIForbiddenError: - LOGGER.warning( - "User entered invalid credentials or API access is not enabled!" - ) - errors[AUTHENTICATION_ERROR] = "Invalid credentials!" + except APIForbiddenError: + LOGGER.warning( + "User entered invalid credentials or API access is not enabled!" + ) + errors[AUTHENTICATION_ERROR] = "Invalid credentials!" + else: + json = [ + { + "deviceName": "Device1", + "deviceFirmware": "1.0", + "deviceId": "123", + "deviceAlarmType": "Paradox", + }, + { + "deviceName": "Device2", + "deviceFirmware": "1.1", + "deviceId": "124", + "deviceAlarmType": "IDS", + }, + ] if json is None: errors[AUTHENTICATION_ERROR] = "Invalid credentials!" diff --git a/custom_components/olarm_sensors/coordinator.py b/custom_components/olarm_sensors/coordinator.py index 831006e..88b3e0c 100644 --- a/custom_components/olarm_sensors/coordinator.py +++ b/custom_components/olarm_sensors/coordinator.py @@ -5,6 +5,8 @@ from datetime import datetime, timedelta import time +from olarm_api_rainepretorius import OlarmApi, OlarmUpdateAPI + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant @@ -12,7 +14,6 @@ from .const import DOMAIN, LOGGER, TempEntry from .exceptions import APIContentTypeError, ClientConnectorError -from .olarm_api import OlarmApi, OlarmUpdateAPI class OlarmCoordinator(DataUpdateCoordinator): @@ -26,6 +27,7 @@ class OlarmCoordinator(DataUpdateCoordinator): device_json: dict = {} release_data: dict = {} device_firmware: str + area_triggers: list[str] = ["", "", "", "", "", "", "", ""] def __init__( self, @@ -66,8 +68,8 @@ def __init__( self.bypass_state: list = [] self.ukey_data: list = [] self.pgm_data: list = [] - self.area_changes: list = [{}, {}, {}, {}, {}, {}, {}, {}] - self.area_triggers: list = [None, None, None, None, None, None, None, None] + self.area_changes: list[dict] = [{}, {}, {}, {}, {}, {}, {}, {}] + self.area_triggers: list = ["", "", "", "", "", "", "", ""] # Setting the device info. self.olarm_device_name: str = device_name diff --git a/custom_components/olarm_sensors/manifest.json b/custom_components/olarm_sensors/manifest.json index 2c4756f..0807621 100644 --- a/custom_components/olarm_sensors/manifest.json +++ b/custom_components/olarm_sensors/manifest.json @@ -4,9 +4,10 @@ "codeowners": ["@rainepretorius"], "config_flow": true, "dependencies": [], + "version": "2.2.6", "documentation": "https://github.com/rainepretorius/olarm-ha-integration/blob/main/README.md", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/rainepretorius/olarm-ha-integration/issues", "quality_scale": "silver", - "requirements": ["aiohttp==3.8.5"] + "requirements": ["olarm-api-rainepretorius==1.0.0"] } diff --git a/custom_components/olarm_sensors/sensor.py b/custom_components/olarm_sensors/sensor.py new file mode 100644 index 0000000..6f40b46 --- /dev/null +++ b/custom_components/olarm_sensors/sensor.py @@ -0,0 +1,140 @@ +"""Platform for binary sensor integration.""" +from __future__ import annotations + +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import CONF_DEVICE_FIRMWARE, CONF_OLARM_DEVICES, DOMAIN, LOGGER, VERSION +from .coordinator import OlarmCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Handle the setup of the platform.""" + entities = [] + for device in hass.data[DOMAIN]["devices"]: + if device["deviceName"] not in entry.data[CONF_OLARM_DEVICES]: + continue + + # Getting the instance of the DataCoordinator to update the data from Olarm. + coordinator: OlarmCoordinator = hass.data[DOMAIN][device["deviceId"]] + await coordinator.update_data() + + for panel in coordinator.panel_state: + name = panel["name"] + " Trigger" + sensor = OlarmTriggerSensor( + area=panel["area_number"], + area_name=name, + coordinator=coordinator, + hass=hass, + ) + + entities.append(sensor) + + LOGGER.info( + "Adding Olarm Alarm Trigger Sensors for device (%s)", + coordinator.olarm_device_name, + ) + + async_add_entities(entities) + LOGGER.info("Added Olarm Alarm Trigger Sensors") + + +class OlarmTriggerSensor(SensorEntity): + """Alarm Trigger Sensor.""" + + area: int = 0 + area_name: str + coordinator: OlarmCoordinator + hass: HomeAssistant + + def __init__( + self, + area: int, + area_name: str, + coordinator: OlarmCoordinator, + hass: HomeAssistant, + ) -> None: + """Initialize the trigger sensor.""" + super().__init__() + self.hass = hass + self.area = area - 1 + self.area_name = area_name + self.coordinator = coordinator + + @property + def native_value(self) -> str | None: + """Return the state of the trigger platforms.""" + self.coordinator: OlarmCoordinator = self.hass.data[DOMAIN][ + self.coordinator.olarm_device_id + ] + try: + area_triggers = self.coordinator.area_triggers[self.area] + if area_triggers and area_triggers != "": + index = int( + str(area_triggers) + .split(" ", maxsplit=1)[0] + .split(",", maxsplit=1)[0] + ) + return self.coordinator.sensor_data[index - 1]["name"] + + return area_triggers + + except (TypeError, IndexError): + return None + + async def async_added_to_hass(self) -> None: + """Write the state of the sensor to Home Assistant.""" + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) + + @property + def unique_id(self) -> str: + """The unique id for this entity sothat it can be managed from the ui.""" + return f"{self.coordinator.olarm_device_id}_area_trigger_{self.area}".replace( + " ", "_" + ).lower() + + @property + def name(self) -> str: + """The name of the zone from the Alarm Panel.""" + name = [] + name1 = self.area_name.replace("_", " ") + for item in str(name1).lower().split(" "): + name.append(str(item).capitalize()) + return " ".join(name) + " (" + self.coordinator.olarm_device_name + ") Triggers" + + @property + def icon(self) -> str: + """Return the icon.""" + return "mdi:alarm-bell" + + @property + def should_poll(self) -> bool: + """Disable polling. Integration will notify Home Assistant on sensor value update.""" + return False + + @property + def device_info(self) -> DeviceInfo: + """Return device information about this entity.""" + return DeviceInfo( + manufacturer="Raine Pretorius", + name=f"Olarm Sensors ({self.coordinator.olarm_device_name})", + model=self.coordinator.olarm_device_make, + identifiers={(DOMAIN, self.coordinator.olarm_device_id)}, + sw_version=VERSION, + hw_version=self.coordinator.entry.data[CONF_DEVICE_FIRMWARE], + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.coordinator: OlarmCoordinator = self.hass.data[DOMAIN][ + self.coordinator.olarm_device_id + ] + self.async_write_ha_state() diff --git a/custom_components/olarm_sensors/update.py b/custom_components/olarm_sensors/update.py index d54aec2..7ca856c 100644 --- a/custom_components/olarm_sensors/update.py +++ b/custom_components/olarm_sensors/update.py @@ -47,22 +47,40 @@ def name(self) -> str: @property def latest_version(self) -> str | None: """Latest version available for install.""" - return self.hass.data[DOMAIN]["release_data"]["name"].split(" ")[1] + try: + return self.hass.data[DOMAIN]["release_data"]["name"].split(" ")[1] + + except KeyError: + return None @property def title(self) -> str | None: """Return update name.""" - return self.hass.data[DOMAIN]["release_data"]["name"] + try: + return self.hass.data[DOMAIN]["release_data"]["name"] + + except KeyError: + return None @property def release_summary(self) -> str | None: """Return release summary.""" - return self.hass.data[DOMAIN]["release_data"]["body"].split("\n", maxsplit=1)[0] + try: + return self.hass.data[DOMAIN]["release_data"]["body"].split( + "\n", maxsplit=1 + )[0] + + except KeyError: + return None @property def release_url(self) -> str | None: """The url of the release.""" - return self.hass.data[DOMAIN]["release_data"]["url"] + try: + return self.hass.data[DOMAIN]["release_data"]["url"] + + except KeyError: + return None def install(self, version: str | None, backup: bool, **kwargs: Any) -> None: """Install an update.