From aea6eb592780168ef52cd816b00e45129475f1d4 Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Mon, 7 Aug 2023 17:42:05 -0400 Subject: [PATCH 1/6] Implement unload to allow reloading integration --- custom_components/healthchecksio/__init__.py | 58 ++++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/custom_components/healthchecksio/__init__.py b/custom_components/healthchecksio/__init__.py index 2a4400f..0cba396 100644 --- a/custom_components/healthchecksio/__init__.py +++ b/custom_components/healthchecksio/__init__.py @@ -4,33 +4,32 @@ For more details about this component, please refer to https://github.com/custom-components/healthchecksio """ -import os -import async_timeout import asyncio +import os from datetime import timedelta + +import async_timeout +from homeassistant import config_entries, core +from homeassistant.const import Platform from homeassistant.helpers.aiohttp_client import async_get_clientsession -import voluptuous as vol -from homeassistant import config_entries -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers import discovery +from homeassistant.helpers.typing import ConfigType from homeassistant.util import Throttle - -from integrationhelper.const import CC_STARTUP_VERSION from integrationhelper import Logger +from integrationhelper.const import CC_STARTUP_VERSION from .const import ( - DOMAIN_DATA, DOMAIN, - ISSUE_URL, - REQUIRED_FILES, + DOMAIN_DATA, INTEGRATION_VERSION, + ISSUE_URL, OFFICIAL_SITE_ROOT, + REQUIRED_FILES, ) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -async def async_setup(hass, config): +async def async_setup(hass: core.HomeAssistant, config: ConfigType): """Set up this component using YAML is not supported.""" if config.get(DOMAIN) is not None: Logger("custom_components.healthchecksio").error( @@ -40,7 +39,9 @@ async def async_setup(hass, config): return True -async def async_setup_entry(hass, config_entry): +async def async_setup_entry( + hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry +) -> bool: """Set up this integration using UI.""" # Print startup message Logger("custom_components.healthchecksio").info( @@ -74,12 +75,31 @@ async def async_setup_entry(hass, config_entry): # Add binary_sensor hass.async_add_job( - hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") + hass.config_entries.async_forward_entry_setup( + config_entry, Platform.BINARY_SENSOR + ) ) return True +async def async_unload_entry( + hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry +) -> bool: + """Unload a config entry.""" + + unload_ok = await hass.config_entries.async_forward_entry_unload( + config_entry, Platform.BINARY_SENSOR + ) + if unload_ok: + hass.data[DOMAIN_DATA].pop("client", None) + hass.data[DOMAIN_DATA].pop("data", None) + Logger("custom_components.healthchecksio").info( + "Successfully removed the healthchecksio integration" + ) + return unload_ok + + class HealthchecksioData: """This class handle communication and stores the data.""" @@ -119,7 +139,7 @@ async def update_data(self): ) -async def check_files(hass): +async def check_files(hass: core.HomeAssistant) -> bool: """Return bool that indicates if all files are present.""" # Verify that the user downloaded all files. base = f"{hass.config.path()}/custom_components/{DOMAIN}/" @@ -138,11 +158,3 @@ async def check_files(hass): returnvalue = True return returnvalue - - -async def async_remove_entry(hass, config_entry): - """Handle removal of an entry.""" - await hass.config_entries.async_forward_entry_unload(config_entry, "binary_sensor") - Logger("custom_components.healthchecksio").info( - "Successfully removed the healthchecksio integration" - ) From 6d8cdfd78e1994a843ca2cbbf697ae5f4f70b2e4 Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Mon, 7 Aug 2023 21:36:55 -0400 Subject: [PATCH 2/6] Send Check First & Separate Out Errors --- custom_components/healthchecksio/__init__.py | 63 +++++++++++---- .../healthchecksio/config_flow.py | 77 ++++++++++++++----- 2 files changed, 103 insertions(+), 37 deletions(-) diff --git a/custom_components/healthchecksio/__init__.py b/custom_components/healthchecksio/__init__.py index 0cba396..b9da11e 100644 --- a/custom_components/healthchecksio/__init__.py +++ b/custom_components/healthchecksio/__init__.py @@ -5,10 +5,11 @@ https://github.com/custom-components/healthchecksio """ import asyncio +import json import os from datetime import timedelta -import async_timeout +import aiohttp from homeassistant import config_entries, core from homeassistant.const import Platform from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -115,28 +116,56 @@ def __init__(self, hass, api_key, check, self_hosted, site_root, ping_endpoint): @Throttle(MIN_TIME_BETWEEN_UPDATES) async def update_data(self): """Update data.""" - Logger("custom_components.healthchecksio").debug("Running update") + Logger("custom_components.healthchecksio").debug("Running Update") # This is where the main logic to update platform data goes. + verify_ssl = not self.self_hosted or self.site_root.startswith("https") + session = async_get_clientsession(self.hass, verify_ssl) + timeout10 = aiohttp.ClientTimeout(total=10) + headers = {"X-Api-Key": self.api_key} + if self.self_hosted: + check_url = f"{self.site_root}/{self.ping_endpoint}/{self.check}" + else: + check_url = f"https://hc-ping.com/{self.check}" + await asyncio.sleep(1) # needed for self-hosted instances try: - verify_ssl = not self.self_hosted or self.site_root.startswith("https") - session = async_get_clientsession(self.hass, verify_ssl) - headers = {"X-Api-Key": self.api_key} - async with async_timeout.timeout(10): - data = await session.get( - f"{self.site_root}/api/v1/checks/", headers=headers + check_response = await session.get(check_url, timeout=timeout10) + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + Logger("custom_components.healthchecksio").error( + f"Could Not Send Check: {error}" + ) + else: + if check_response.ok: + Logger("custom_components.healthchecksio").debug( + f"Send Check HTTP Status Code: {check_response.status}" + ) + else: + Logger("custom_components.healthchecksio").error( + f"Error: Send Check HTTP Status Code: {check_response.status}" ) + try: + async with session.get( + f"{self.site_root}/api/v1/checks/", + headers=headers, + timeout=timeout10, + ) as data: self.hass.data[DOMAIN_DATA]["data"] = await data.json() - - if self.self_hosted: - check_url = f"{self.site_root}/{self.ping_endpoint}/{self.check}" - else: - check_url = f"https://hc-ping.com/{self.check}" - await asyncio.sleep(1) # needed for self-hosted instances - await session.get(check_url) - except Exception as error: # pylint: disable=broad-except + except (aiohttp.ClientError, asyncio.TimeoutError) as error: Logger("custom_components.healthchecksio").error( - f"Could not update data - {error}" + f"Could Not Update Data: {error}" ) + except (ValueError, json.decoder.JSONDecodeError) as error: + Logger("custom_components.healthchecksio").error( + f"Data JSON Decode Error: {error}" + ) + else: + if data.ok: + Logger("custom_components.healthchecksio").debug( + f"Get Data HTTP Status Code: {data.status}" + ) + else: + Logger("custom_components.healthchecksio").error( + f"Error: Get Data HTTP Status Code: {data.status}" + ) async def check_files(hass: core.HomeAssistant) -> bool: diff --git a/custom_components/healthchecksio/config_flow.py b/custom_components/healthchecksio/config_flow.py index 217cf5c..b4524c4 100644 --- a/custom_components/healthchecksio/config_flow.py +++ b/custom_components/healthchecksio/config_flow.py @@ -1,11 +1,13 @@ """Adds config flow for Blueprint.""" -import async_timeout import asyncio +import json from collections import OrderedDict + +import aiohttp import voluptuous as vol +from homeassistant import config_entries from homeassistant.helpers.aiohttp_client import async_get_clientsession from integrationhelper import Logger -from homeassistant import config_entries from .const import DOMAIN, DOMAIN_DATA, OFFICIAL_SITE_ROOT @@ -123,23 +125,58 @@ async def _test_credentials( self, api_key, check, self_hosted, site_root, ping_endpoint ): """Return true if credentials is valid.""" + Logger("custom_components.healthchecksio").debug("Testing Credentials") + verify_ssl = not self_hosted or site_root.startswith("https") + session = async_get_clientsession(self.hass, verify_ssl) + timeout10 = aiohttp.ClientTimeout(total=10) + headers = {"X-Api-Key": api_key} + if self_hosted: + check_url = f"{site_root}/{ping_endpoint}/{check}" + else: + check_url = f"https://hc-ping.com/{check}" + await asyncio.sleep(1) # needed for self-hosted instances try: - verify_ssl = not self_hosted or site_root.startswith("https") - session = async_get_clientsession(self.hass, verify_ssl) - headers = {"X-Api-Key": api_key} - async with async_timeout.timeout(10): - Logger("custom_components.healthchecksio").info("Checking API Key") - data = await session.get(f"{site_root}/api/v1/checks/", headers=headers) + check_response = await session.get(check_url, timeout=timeout10) + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + Logger("custom_components.healthchecksio").error( + f"Could Not Send Check: {error}" + ) + return False + else: + if check_response.ok: + Logger("custom_components.healthchecksio").debug( + f"Send Check HTTP Status Code: {check_response.status}" + ) + else: + Logger("custom_components.healthchecksio").error( + f"Error: Send Check HTTP Status Code: {check_response.status}" + ) + return False + try: + async with session.get( + f"{site_root}/api/v1/checks/", + headers=headers, + timeout=timeout10, + ) as data: self.hass.data[DOMAIN_DATA] = {"data": await data.json()} - - Logger("custom_components.healthchecksio").info("Checking Check ID") - if self_hosted: - check_url = f"{site_root}/{ping_endpoint}/{check}" - else: - check_url = f"https://hc-ping.com/{check}" - await asyncio.sleep(1) # needed for self-hosted instances - await session.get(check_url) - return True - except Exception as exception: # pylint: disable=broad-except - Logger("custom_components.healthchecksio").error(exception) - return False + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + Logger("custom_components.healthchecksio").error( + f"Could Not Update Data: {error}" + ) + return False + except (ValueError, json.decoder.JSONDecodeError) as error: + Logger("custom_components.healthchecksio").error( + f"Data JSON Decode Error: {error}" + ) + return False + else: + if data.ok: + Logger("custom_components.healthchecksio").debug( + f"Get Data HTTP Status Code: {data.status}" + ) + return True + else: + Logger("custom_components.healthchecksio").error( + f"Error: Get Data HTTP Status Code: {data.status}" + ) + return False From 1ca5010bcc2f94c59a50abf8bcddd4f02a6d6bfd Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Tue, 15 Aug 2023 17:29:38 -0400 Subject: [PATCH 3/6] Sensor, Optional Check, Code Cleanup --- .../healthchecksio/.translations/en.json | 30 -- custom_components/healthchecksio/__init__.py | 155 ++++----- .../healthchecksio/binary_sensor.py | 100 +++--- .../healthchecksio/config_flow.py | 305 ++++++++++-------- custom_components/healthchecksio/const.py | 35 +- .../healthchecksio/manifest.json | 4 +- custom_components/healthchecksio/sensor.py | 75 +++++ .../healthchecksio/translations/en.json | 16 +- hacs.json | 4 +- 9 files changed, 422 insertions(+), 302 deletions(-) delete mode 100644 custom_components/healthchecksio/.translations/en.json create mode 100644 custom_components/healthchecksio/sensor.py diff --git a/custom_components/healthchecksio/.translations/en.json b/custom_components/healthchecksio/.translations/en.json deleted file mode 100644 index fc3741d..0000000 --- a/custom_components/healthchecksio/.translations/en.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "config": { - "title": "Healthchecks.io", - "step": { - "user": { - "title": "Healthchecks.io", - "description": "If you need help with the configuration have a look here: https://github.com/custom-components/healthchecksio", - "data": { - "api_key": "API key", - "check": "Check ID", - "self_hosted": "Use self-hosted instance" - } - }, - "self_hosted": { - "title": "Healthchecks.io self-hosted instance", - "description": "If you need help with the configuration have a look here: https://github.com/custom-components/healthchecksio", - "data": { - "site_root": "Instance root URL", - "ping_endpoint": "Ping endpoint" - } - } - }, - "error": { - "auth": "API Key, Check ID, Site root or Ping endpoint is wrong." - }, - "abort": { - "single_instance_allowed": "Only a single configuration of Blueprint is allowed." - } - } -} \ No newline at end of file diff --git a/custom_components/healthchecksio/__init__.py b/custom_components/healthchecksio/__init__.py index b9da11e..83ac71a 100644 --- a/custom_components/healthchecksio/__init__.py +++ b/custom_components/healthchecksio/__init__.py @@ -6,36 +6,47 @@ """ import asyncio import json +import logging import os from datetime import timedelta import aiohttp from homeassistant import config_entries, core from homeassistant.const import Platform +from homeassistant.helpers import entity_platform from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.typing import ConfigType from homeassistant.util import Throttle -from integrationhelper import Logger from integrationhelper.const import CC_STARTUP_VERSION from .const import ( + CONF_API_KEY, + CONF_CHECK, + CONF_CREATE_BINARY_SENSOR, + CONF_CREATE_SENSOR, + CONF_PING_ENDPOINT, + CONF_SELF_HOSTED, + CONF_SITE_ROOT, + DATA_CLIENT, + DATA_DATA, DOMAIN, DOMAIN_DATA, + INTEGRATION_NAME, INTEGRATION_VERSION, ISSUE_URL, OFFICIAL_SITE_ROOT, REQUIRED_FILES, ) +_LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -async def async_setup(hass: core.HomeAssistant, config: ConfigType): +async def async_setup( + hass: core.HomeAssistant, config: config_entries.ConfigEntry +) -> bool: """Set up this component using YAML is not supported.""" if config.get(DOMAIN) is not None: - Logger("custom_components.healthchecksio").error( - "Configuration with YAML is not supported" - ) + _LOGGER.error("Configuration with YAML is not supported") return True @@ -45,9 +56,9 @@ async def async_setup_entry( ) -> bool: """Set up this integration using UI.""" # Print startup message - Logger("custom_components.healthchecksio").info( + _LOGGER.info( CC_STARTUP_VERSION.format( - name=DOMAIN, version=INTEGRATION_VERSION, issue_link=ISSUE_URL + name=INTEGRATION_NAME, version=INTEGRATION_VERSION, issue_link=ISSUE_URL ) ) @@ -59,27 +70,27 @@ async def async_setup_entry( # Create DATA dict if DOMAIN_DATA not in hass.data: hass.data[DOMAIN_DATA] = {} - if "data" not in hass.data[DOMAIN_DATA]: - hass.data[DOMAIN_DATA] = {} + if DATA_DATA not in hass.data[DOMAIN_DATA]: + hass.data[DOMAIN_DATA][DATA_DATA] = {} # Get "global" configuration. - api_key = config_entry.data.get("api_key") - check = config_entry.data.get("check") - self_hosted = config_entry.data.get("self_hosted") - site_root = config_entry.data.get("site_root") - ping_endpoint = config_entry.data.get("ping_endpoint") + api_key = config_entry.data.get(CONF_API_KEY) + check = config_entry.data.get(CONF_CHECK) + self_hosted = config_entry.data.get(CONF_SELF_HOSTED) + site_root = config_entry.data.get(CONF_SITE_ROOT) + ping_endpoint = config_entry.data.get(CONF_PING_ENDPOINT) + platforms = [] + if config_entry.data.get(CONF_CREATE_BINARY_SENSOR): + platforms.append(Platform.BINARY_SENSOR) + if config_entry.data.get(CONF_CREATE_SENSOR): + platforms.append(Platform.SENSOR) # Configure the client. - hass.data[DOMAIN_DATA]["client"] = HealthchecksioData( + hass.data[DOMAIN_DATA][DATA_CLIENT] = HealthchecksioData( hass, api_key, check, self_hosted, site_root, ping_endpoint ) - - # Add binary_sensor - hass.async_add_job( - hass.config_entries.async_forward_entry_setup( - config_entry, Platform.BINARY_SENSOR - ) - ) + await hass.config_entries.async_forward_entry_setups(config_entry, platforms) + _LOGGER.debug(f"Config Entry: {config_entry.as_dict()}") return True @@ -88,16 +99,27 @@ async def async_unload_entry( hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry ) -> bool: """Unload a config entry.""" - - unload_ok = await hass.config_entries.async_forward_entry_unload( - config_entry, Platform.BINARY_SENSOR - ) - if unload_ok: - hass.data[DOMAIN_DATA].pop("client", None) - hass.data[DOMAIN_DATA].pop("data", None) - Logger("custom_components.healthchecksio").info( - "Successfully removed the healthchecksio integration" + _LOGGER.debug(f"Unloading Config Entry: {config_entry.as_dict()}") + curr_plat = [] + for p in entity_platform.async_get_platforms(hass, DOMAIN): + if ( + p.config_entry is not None + and config_entry.entry_id == p.config_entry.entry_id + and p.config_entry.state == config_entries.ConfigEntryState.LOADED + ): + curr_plat.append(p.domain) + _LOGGER.debug(f"Unloading Platforms: {curr_plat}") + unload_ok = True + if curr_plat: + unload_ok = await hass.config_entries.async_unload_platforms( + config_entry, + curr_plat, ) + if unload_ok: + hass.data[DOMAIN_DATA].pop(DATA_CLIENT, None) + hass.data[DOMAIN_DATA].pop(DATA_DATA, None) + _LOGGER.info("Successfully removed the HealthChecks.io integration") + return unload_ok @@ -116,56 +138,49 @@ def __init__(self, hass, api_key, check, self_hosted, site_root, ping_endpoint): @Throttle(MIN_TIME_BETWEEN_UPDATES) async def update_data(self): """Update data.""" - Logger("custom_components.healthchecksio").debug("Running Update") + _LOGGER.debug("Running Update") # This is where the main logic to update platform data goes. verify_ssl = not self.self_hosted or self.site_root.startswith("https") session = async_get_clientsession(self.hass, verify_ssl) timeout10 = aiohttp.ClientTimeout(total=10) headers = {"X-Api-Key": self.api_key} - if self.self_hosted: - check_url = f"{self.site_root}/{self.ping_endpoint}/{self.check}" - else: - check_url = f"https://hc-ping.com/{self.check}" - await asyncio.sleep(1) # needed for self-hosted instances - try: - check_response = await session.get(check_url, timeout=timeout10) - except (aiohttp.ClientError, asyncio.TimeoutError) as error: - Logger("custom_components.healthchecksio").error( - f"Could Not Send Check: {error}" - ) - else: - if check_response.ok: - Logger("custom_components.healthchecksio").debug( - f"Send Check HTTP Status Code: {check_response.status}" - ) + if self.check is not None: + if self.self_hosted: + check_url = f"{self.site_root}/{self.ping_endpoint}/{self.check}" + else: + check_url = f"https://hc-ping.com/{self.check}" + await asyncio.sleep(1) # needed for self-hosted instances + try: + check_response = await session.get(check_url, timeout=timeout10) + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + _LOGGER.error(f"Could Not Send Check: {error}") else: - Logger("custom_components.healthchecksio").error( - f"Error: Send Check HTTP Status Code: {check_response.status}" - ) + if check_response.ok: + _LOGGER.debug( + f"Send Check HTTP Status Code: {check_response.status}" + ) + else: + _LOGGER.error( + f"Error: Send Check HTTP Status Code: {check_response.status}" + ) + else: + _LOGGER.debug("Send Check is not defined") try: async with session.get( f"{self.site_root}/api/v1/checks/", headers=headers, timeout=timeout10, ) as data: - self.hass.data[DOMAIN_DATA]["data"] = await data.json() + self.hass.data[DOMAIN_DATA][DATA_DATA] = await data.json() except (aiohttp.ClientError, asyncio.TimeoutError) as error: - Logger("custom_components.healthchecksio").error( - f"Could Not Update Data: {error}" - ) + _LOGGER.error(f"Could Not Update Data: {error}") except (ValueError, json.decoder.JSONDecodeError) as error: - Logger("custom_components.healthchecksio").error( - f"Data JSON Decode Error: {error}" - ) + _LOGGER.error(f"Data JSON Decode Error: {error}") else: if data.ok: - Logger("custom_components.healthchecksio").debug( - f"Get Data HTTP Status Code: {data.status}" - ) + _LOGGER.debug(f"Get Data HTTP Status Code: {data.status}") else: - Logger("custom_components.healthchecksio").error( - f"Error: Get Data HTTP Status Code: {data.status}" - ) + _LOGGER.error(f"Error: Get Data HTTP Status Code: {data.status}") async def check_files(hass: core.HomeAssistant) -> bool: @@ -179,11 +194,7 @@ async def check_files(hass: core.HomeAssistant) -> bool: missing.append(file) if missing: - Logger("custom_components.healthchecksio").critical( - f"The following files are missing: {missing}" - ) - returnvalue = False + _LOGGER.critical(f"The following files are missing: {missing}") + return False else: - returnvalue = True - - return returnvalue + return True diff --git a/custom_components/healthchecksio/binary_sensor.py b/custom_components/healthchecksio/binary_sensor.py index 11cc5b8..d2546c7 100644 --- a/custom_components/healthchecksio/binary_sensor.py +++ b/custom_components/healthchecksio/binary_sensor.py @@ -1,25 +1,39 @@ """Binary sensor platform for Healthchecksio.""" -try: - from homeassistant.components.binary_sensor import BinarySensorEntity -except ImportError: - from homeassistant.components.binary_sensor import ( - BinarySensorDevice as BinarySensorEntity, - ) -from .const import ATTRIBUTION, BINARY_SENSOR_DEVICE_CLASS, DOMAIN_DATA, DOMAIN +import logging + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) + +from .const import ( + ATTR_ATTRIBUTION, + ATTR_CHECKS, + ATTR_LAST_PING, + ATTR_NAME, + ATTR_PING_URL, + ATTR_STATUS, + ATTRIBUTION, + DATA_CLIENT, + DATA_DATA, + DOMAIN, + DOMAIN_DATA, +) + +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_devices): - """Setup sensor platform.""" - # Send update "signal" to the component - await hass.data[DOMAIN_DATA]["client"].update_data() + """Setup Binary Sensor platform.""" + await hass.data[DOMAIN_DATA][DATA_CLIENT].update_data() checks = [] - for check in hass.data[DOMAIN_DATA].get("data", {}).get("checks", []): + for check in hass.data[DOMAIN_DATA].get(DATA_DATA, {}).get(ATTR_CHECKS, []): check_data = { - "name": check.get("name"), - "last_ping": check.get("last_ping"), - "status": check.get("status"), - "ping_url": check.get("ping_url"), + ATTR_NAME: check.get(ATTR_NAME), + ATTR_LAST_PING: check.get(ATTR_LAST_PING), + ATTR_STATUS: check.get(ATTR_STATUS), + ATTR_PING_URL: check.get(ATTR_PING_URL), } checks.append(HealthchecksioBinarySensor(hass, check_data, config_entry)) async_add_devices(checks, True) @@ -30,57 +44,35 @@ class HealthchecksioBinarySensor(BinarySensorEntity): def __init__(self, hass, check_data, config_entry): self.hass = hass - self.attr = {} self.config_entry = config_entry - self._status = None self.check_data = check_data + self._attr_name = None + self._attr_device_class = BinarySensorDeviceClass.CONNECTIVITY + self._attr_unique_id = self.check_data.get(ATTR_PING_URL, "").split("/")[-1] + self._attr_extra_state_attributes = {} + self._attr_is_on = None self.check = {} async def async_update(self): """Update the binary_sensor.""" - # Send update "signal" to the component - await self.hass.data[DOMAIN_DATA]["client"].update_data() - - # Check the data and update the value. - for check in self.hass.data[DOMAIN_DATA].get("data", {}).get("checks", []): - if self.unique_id == check.get("ping_url").split("/")[-1]: + await self.hass.data[DOMAIN_DATA][DATA_CLIENT].update_data() + for check in ( + self.hass.data[DOMAIN_DATA].get(DATA_DATA, {}).get(ATTR_CHECKS, []) + ): + if self._attr_unique_id == check.get(ATTR_PING_URL).split("/")[-1]: self.check = check break - self._status = self.check.get("status") != "down" - - # Set/update attributes - self.attr["attribution"] = ATTRIBUTION - self.attr["last_ping"] = self.check.get("last_ping") - - @property - def unique_id(self): - """Return a unique ID to use for this binary_sensor.""" - return self.check_data.get("ping_url", "").split("/")[-1] + self._attr_name = self.check.get(ATTR_NAME) + self._attr_is_on = self.check.get(ATTR_STATUS) != "down" + self._attr_extra_state_attributes[ATTR_ATTRIBUTION] = ATTRIBUTION + self._attr_extra_state_attributes[ATTR_LAST_PING] = self.check.get( + ATTR_LAST_PING + ) @property def device_info(self): return { "identifiers": {(DOMAIN, self.config_entry.entry_id)}, - "name": "Healthchecks.io", + "name": "HealthChecks.io", "manufacturer": "SIA Monkey See Monkey Do", } - - @property - def name(self): - """Return the name of the binary_sensor.""" - return self.check.get("name") - - @property - def device_class(self): - """Return the class of this binary_sensor.""" - return BINARY_SENSOR_DEVICE_CLASS - - @property - def is_on(self): - """Return true if the binary_sensor is on.""" - return self._status - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return self.attr diff --git a/custom_components/healthchecksio/config_flow.py b/custom_components/healthchecksio/config_flow.py index b4524c4..4257743 100644 --- a/custom_components/healthchecksio/config_flow.py +++ b/custom_components/healthchecksio/config_flow.py @@ -1,33 +1,101 @@ -"""Adds config flow for Blueprint.""" +"""Adds config flow for HealthChecks.io""" import asyncio import json -from collections import OrderedDict +import logging import aiohttp import voluptuous as vol from homeassistant import config_entries +from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_get_clientsession -from integrationhelper import Logger -from .const import DOMAIN, DOMAIN_DATA, OFFICIAL_SITE_ROOT +from .const import ( + CONF_API_KEY, + CONF_CHECK, + CONF_CREATE_BINARY_SENSOR, + CONF_CREATE_SENSOR, + CONF_PING_ENDPOINT, + CONF_SELF_HOSTED, + CONF_SITE_ROOT, + DEFAULT_CREATE_BINARY_SENSOR, + DEFAULT_CREATE_SENSOR, + DEFAULT_PING_ENDPOINT, + DEFAULT_SELF_HOSTED, + DEFAULT_SITE_ROOT, + DOMAIN, + DOMAIN_DATA, + OFFICIAL_SITE_ROOT, +) + +_LOGGER = logging.getLogger(__name__) + + +async def _test_credentials( + hass, api_key, check, self_hosted, site_root, ping_endpoint +): + """Return true if credentials is valid.""" + _LOGGER.debug("Testing Credentials") + verify_ssl = not self_hosted or site_root.startswith("https") + session = async_get_clientsession(hass, verify_ssl) + timeout10 = aiohttp.ClientTimeout(total=10) + headers = {"X-Api-Key": api_key} + if check is not None: + if self_hosted: + check_url = f"{site_root}/{ping_endpoint}/{check}" + else: + check_url = f"https://hc-ping.com/{check}" + await asyncio.sleep(1) # needed for self-hosted instances + try: + check_response = await session.get(check_url, timeout=timeout10) + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + _LOGGER.error(f"Could Not Send Check: {error}") + return False + else: + if check_response.ok: + _LOGGER.debug(f"Send Check HTTP Status Code: {check_response.status}") + else: + check_url = f"https://hc-ping.com/{check}" + await asyncio.sleep(1) # needed for self-hosted instances + try: + check_response = await session.get(check_url, timeout=timeout10) + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + _LOGGER.error(f"Could Not Send Check: {error}") + return False + else: + _LOGGER.debug("Send Check is not defined") + try: + async with session.get( + f"{site_root}/api/v1/checks/", + headers=headers, + timeout=timeout10, + ) as data: + hass.data[DOMAIN_DATA] = {"data": await data.json()} + except (aiohttp.ClientError, asyncio.TimeoutError) as error: + _LOGGER.error(f"Could Not Update Data: {error}") + return False + except (ValueError, json.decoder.JSONDecodeError) as error: + _LOGGER.error(f"Data JSON Decode Error: {error}") + return False + else: + if data.ok: + _LOGGER.debug(f"Get Data HTTP Status Code: {data.status}") + return True + else: + _LOGGER.error(f"Error: Get Data HTTP Status Code: {data.status}") + return False -@config_entries.HANDLERS.register(DOMAIN) -class BlueprintFlowHandler(config_entries.ConfigFlow): - """Config flow for Blueprint.""" +class HealchecksioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow for HealthChecks.io""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): """Initialize.""" self._errors = {} self.initial_data = None - async def async_step_user( - self, user_input={} - ): # pylint: disable=dangerous-default-value - """Handle a flow initialized by the user.""" + async def async_step_user(self, user_input=None, errors=None): self._errors = {} if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -35,148 +103,123 @@ async def async_step_user( return self.async_abort(reason="single_instance_allowed") if user_input is not None: - if user_input["self_hosted"]: + if not user_input.get(CONF_CREATE_BINARY_SENSOR) and not user_input.get( + CONF_CREATE_SENSOR + ): + self._errors["base"] = "need_a_sensor" + elif user_input.get(CONF_SELF_HOSTED): # don't check yet, we need more info self.initial_data = user_input - return await self._show_self_hosted_config_flow(user_input) + return await self.async_step_self_hosted() else: - valid = await self._test_credentials( - user_input["api_key"], - user_input["check"], + valid = await _test_credentials( + self.hass, + user_input.get(CONF_API_KEY), + user_input.get(CONF_CHECK), False, OFFICIAL_SITE_ROOT, None, ) if valid: - user_input["self_hosted"] = False + user_input[CONF_SELF_HOSTED] = False return self.async_create_entry( - title=user_input["check"], data=user_input + title=user_input.get(CONF_CHECK, DOMAIN), data=user_input ) else: self._errors["base"] = "auth" - return await self._show_initial_config_form(user_input) + DATA_SCHEMA = vol.Schema( + { + vol.Required( + CONF_API_KEY, + default=user_input.get(CONF_API_KEY) + if user_input is not None + else None, + ): str, + } + ) - async def _show_initial_config_form(self, user_input): - """Show the configuration form to edit check data.""" - # Defaults - api_key = "" - check = "" - self_hosted = False + if user_input is not None and user_input.get(CONF_CHECK) is not None: + DATA_SCHEMA = DATA_SCHEMA.extend( + { + vol.Optional(CONF_CHECK, default=user_input.get(CONF_CHECK)): str, + } + ) + else: + DATA_SCHEMA = DATA_SCHEMA.extend( + { + vol.Optional(CONF_CHECK): str, + } + ) + DATA_SCHEMA = DATA_SCHEMA.extend( + { + vol.Optional( + CONF_CREATE_BINARY_SENSOR, + default=user_input.get( + CONF_CREATE_BINARY_SENSOR, DEFAULT_CREATE_BINARY_SENSOR + ) + if user_input is not None + else DEFAULT_CREATE_BINARY_SENSOR, + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_CREATE_SENSOR, + default=user_input.get(CONF_CREATE_SENSOR, DEFAULT_CREATE_SENSOR) + if user_input is not None + else DEFAULT_CREATE_SENSOR, + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_SELF_HOSTED, + default=user_input.get(CONF_SELF_HOSTED, DEFAULT_SELF_HOSTED) + if user_input is not None + else DEFAULT_SELF_HOSTED, + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + } + ) - if user_input is not None: - if "api_key" in user_input: - api_key = user_input["api_key"] - if "check" in user_input: - check = user_input["check"] - if "self_hosted" in user_input: - self_hosted = user_input["self_hosted"] - - data_schema = OrderedDict() - data_schema[vol.Required("api_key", default=api_key)] = str - data_schema[vol.Required("check", default=check)] = str - data_schema[vol.Required("self_hosted", default=self_hosted)] = bool return self.async_show_form( - step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors + step_id="user", data_schema=DATA_SCHEMA, errors=self._errors ) - async def async_step_self_hosted( - self, user_input={} - ): # pylint: disable=dangerous-default-value + async def async_step_self_hosted(self, user_input=None): """Handle the step for a self-hosted instance.""" self._errors = {} - valid = await self._test_credentials( - self.initial_data["api_key"], - self.initial_data["check"], - True, - user_input["site_root"], - user_input["ping_endpoint"], + if user_input is not None: + valid = await _test_credentials( + self.hass, + self.initial_data.get(CONF_API_KEY), + self.initial_data.get(CONF_CHECK), + True, + user_input.get(CONF_SITE_ROOT), + user_input.get(CONF_PING_ENDPOINT), + ) + if valid: + # merge data from initial config flow and this flow + data = {**self.initial_data, **user_input} + return self.async_create_entry( + title=self.initial_data.get(CONF_CHECK, DOMAIN), data=data + ) + else: + self._errors["base"] = "auth_self" + + SELF_HOSTED_DATA_SCHEMA = vol.Schema( + { + vol.Required( + CONF_SITE_ROOT, + default=user_input.get(CONF_SITE_ROOT, DEFAULT_SITE_ROOT) + if user_input is not None + else DEFAULT_SITE_ROOT, + ): str, + vol.Optional( + CONF_PING_ENDPOINT, + default=user_input.get(CONF_PING_ENDPOINT, DEFAULT_PING_ENDPOINT) + if user_input is not None + else DEFAULT_PING_ENDPOINT, + ): str, + } ) - if valid: - # merge data from initial config flow and this flow - data = {**self.initial_data, **user_input} - return self.async_create_entry(title=self.initial_data["check"], data=data) - else: - self._errors["base"] = "auth" - - return await self._show_self_hosted_config_flow(user_input) - - async def _show_self_hosted_config_flow(self, user_input): - """Show the configuration form to edit self-hosted instance data.""" - # Defaults - site_root = "https://checks.mydomain.com" - ping_endpoint = "ping" - if "site_root" in user_input: - site_root = user_input["site_root"] - if "ping_endpoint" in user_input: - ping_endpoint = user_input["ping_endpoint"] - - data_schema = OrderedDict() - data_schema[vol.Required("site_root", default=site_root)] = str - data_schema[vol.Required("ping_endpoint", default=ping_endpoint)] = str return self.async_show_form( step_id="self_hosted", - data_schema=vol.Schema(data_schema), + data_schema=SELF_HOSTED_DATA_SCHEMA, errors=self._errors, ) - - async def _test_credentials( - self, api_key, check, self_hosted, site_root, ping_endpoint - ): - """Return true if credentials is valid.""" - Logger("custom_components.healthchecksio").debug("Testing Credentials") - verify_ssl = not self_hosted or site_root.startswith("https") - session = async_get_clientsession(self.hass, verify_ssl) - timeout10 = aiohttp.ClientTimeout(total=10) - headers = {"X-Api-Key": api_key} - if self_hosted: - check_url = f"{site_root}/{ping_endpoint}/{check}" - else: - check_url = f"https://hc-ping.com/{check}" - await asyncio.sleep(1) # needed for self-hosted instances - try: - check_response = await session.get(check_url, timeout=timeout10) - except (aiohttp.ClientError, asyncio.TimeoutError) as error: - Logger("custom_components.healthchecksio").error( - f"Could Not Send Check: {error}" - ) - return False - else: - if check_response.ok: - Logger("custom_components.healthchecksio").debug( - f"Send Check HTTP Status Code: {check_response.status}" - ) - else: - Logger("custom_components.healthchecksio").error( - f"Error: Send Check HTTP Status Code: {check_response.status}" - ) - return False - try: - async with session.get( - f"{site_root}/api/v1/checks/", - headers=headers, - timeout=timeout10, - ) as data: - self.hass.data[DOMAIN_DATA] = {"data": await data.json()} - except (aiohttp.ClientError, asyncio.TimeoutError) as error: - Logger("custom_components.healthchecksio").error( - f"Could Not Update Data: {error}" - ) - return False - except (ValueError, json.decoder.JSONDecodeError) as error: - Logger("custom_components.healthchecksio").error( - f"Data JSON Decode Error: {error}" - ) - return False - else: - if data.ok: - Logger("custom_components.healthchecksio").debug( - f"Get Data HTTP Status Code: {data.status}" - ) - return True - else: - Logger("custom_components.healthchecksio").error( - f"Error: Get Data HTTP Status Code: {data.status}" - ) - return False diff --git a/custom_components/healthchecksio/const.py b/custom_components/healthchecksio/const.py index 3ce95af..1c36aa0 100644 --- a/custom_components/healthchecksio/const.py +++ b/custom_components/healthchecksio/const.py @@ -1,19 +1,44 @@ """Constants for blueprint.""" +from homeassistant.const import Platform + # Base component constants DOMAIN = "healthchecksio" DOMAIN_DATA = f"{DOMAIN}_data" +INTEGRATION_NAME = "HealthChecks.io" INTEGRATION_VERSION = "main" -PLATFORMS = ["binary_sensor"] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] REQUIRED_FILES = [ - ".translations/en.json", + "translations/en.json", "binary_sensor.py", "const.py", "config_flow.py", "manifest.json", ] ISSUE_URL = "https://github.com/custom-components/healthchecksio/issues" -ATTRIBUTION = "Data from this is provided by healthchecks.io." - -BINARY_SENSOR_DEVICE_CLASS = "connectivity" +ATTRIBUTION = "Data from this is provided by HealthChecks.io." OFFICIAL_SITE_ROOT = "https://healthchecks.io" + +ATTR_ATTRIBUTION = "attribution" +ATTR_NAME = "name" +ATTR_LAST_PING = "last_ping" +ATTR_STATUS = "status" +ATTR_PING_URL = "ping_url" +ATTR_CHECKS = "checks" + +CONF_API_KEY = "api_key" +CONF_CHECK = "check" +CONF_PING_ENDPOINT = "ping_endpoint" +CONF_SELF_HOSTED = "self_hosted" +CONF_SITE_ROOT = "site_root" +CONF_CREATE_SENSOR = "create_sensor" +CONF_CREATE_BINARY_SENSOR = "create_binary_sensor" + +DATA_CLIENT = "client" +DATA_DATA = "data" + +DEFAULT_PING_ENDPOINT = "ping" +DEFAULT_SELF_HOSTED = False +DEFAULT_SITE_ROOT = "https://checks.mydomain.com" +DEFAULT_CREATE_BINARY_SENSOR = True +DEFAULT_CREATE_SENSOR = False diff --git a/custom_components/healthchecksio/manifest.json b/custom_components/healthchecksio/manifest.json index c367572..d92942f 100644 --- a/custom_components/healthchecksio/manifest.json +++ b/custom_components/healthchecksio/manifest.json @@ -1,6 +1,6 @@ { "domain": "healthchecksio", - "name": "Healthchecks.io", + "name": "HealthChecks.io", "documentation": "https://github.com/custom-components/healthchecksio", "issue_tracker": "https://github.com/custom-components/healthchecksio/issues", "dependencies": [], @@ -12,4 +12,4 @@ "integrationhelper" ], "version": "0.0.0" -} \ No newline at end of file +} diff --git a/custom_components/healthchecksio/sensor.py b/custom_components/healthchecksio/sensor.py new file mode 100644 index 0000000..a4791e9 --- /dev/null +++ b/custom_components/healthchecksio/sensor.py @@ -0,0 +1,75 @@ +"""Sensor platform for Healthchecksio.""" +import logging + +from homeassistant.components.sensor import SensorEntity + +from .const import ( + ATTR_ATTRIBUTION, + ATTR_CHECKS, + ATTR_LAST_PING, + ATTR_NAME, + ATTR_PING_URL, + ATTR_STATUS, + ATTRIBUTION, + DATA_CLIENT, + DATA_DATA, + DOMAIN, + DOMAIN_DATA, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_devices): + """Setup sensor platform.""" + await hass.data[DOMAIN_DATA][DATA_CLIENT].update_data() + checks = [] + for check in hass.data[DOMAIN_DATA].get(DATA_DATA, {}).get(ATTR_CHECKS, []): + check_data = { + ATTR_NAME: check.get(ATTR_NAME), + ATTR_LAST_PING: check.get(ATTR_LAST_PING), + ATTR_STATUS: check.get(ATTR_STATUS), + ATTR_PING_URL: check.get(ATTR_PING_URL), + } + checks.append(HealthchecksioSensor(hass, check_data, config_entry)) + async_add_devices(checks, True) + + +class HealthchecksioSensor(SensorEntity): + """Healthchecksio Sensor class.""" + + def __init__(self, hass, check_data, config_entry): + self.hass = hass + self.config_entry = config_entry + self.check_data = check_data + self._attr_name = None + self._attr_unique_id = self.check_data.get(ATTR_PING_URL, "").split("/")[-1] + self._attr_extra_state_attributes = {} + self._attr_native_value = None + self.check = {} + + async def async_update(self): + """Update the Sensor.""" + await self.hass.data[DOMAIN_DATA][DATA_CLIENT].update_data() + for check in ( + self.hass.data[DOMAIN_DATA].get(DATA_DATA, {}).get(ATTR_CHECKS, []) + ): + if self._attr_unique_id == check.get(ATTR_PING_URL).split("/")[-1]: + self.check = check + break + self._attr_name = self.check.get(ATTR_NAME) + self._attr_native_value = self.check.get(ATTR_STATUS) + if isinstance(self._attr_native_value, str): + self._attr_native_value = self._attr_native_value.title() + self._attr_extra_state_attributes[ATTR_ATTRIBUTION] = ATTRIBUTION + self._attr_extra_state_attributes[ATTR_LAST_PING] = self.check.get( + ATTR_LAST_PING + ) + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self.config_entry.entry_id)}, + "name": "HealthChecks.io", + "manufacturer": "SIA Monkey See Monkey Do", + } diff --git a/custom_components/healthchecksio/translations/en.json b/custom_components/healthchecksio/translations/en.json index fc3741d..abd5842 100644 --- a/custom_components/healthchecksio/translations/en.json +++ b/custom_components/healthchecksio/translations/en.json @@ -1,18 +1,20 @@ { "config": { - "title": "Healthchecks.io", + "title": "HealthChecks.io", "step": { "user": { - "title": "Healthchecks.io", + "title": "HealthChecks.io", "description": "If you need help with the configuration have a look here: https://github.com/custom-components/healthchecksio", "data": { "api_key": "API key", "check": "Check ID", + "create_binary_sensor": "Create Binary Sensors", + "create_sensor": "Create Sensors", "self_hosted": "Use self-hosted instance" } }, "self_hosted": { - "title": "Healthchecks.io self-hosted instance", + "title": "HealthChecks.io self-hosted instance", "description": "If you need help with the configuration have a look here: https://github.com/custom-components/healthchecksio", "data": { "site_root": "Instance root URL", @@ -21,10 +23,12 @@ } }, "error": { - "auth": "API Key, Check ID, Site root or Ping endpoint is wrong." + "auth": "API Key or Check ID is wrong.", + "auth_self": "API Key, Check ID, Site root or Ping endpoint is wrong.", + "need_a_sensor": "Must choose to Create Binary Sensor, Sensor or both." }, "abort": { - "single_instance_allowed": "Only a single configuration of Blueprint is allowed." + "single_instance_allowed": "Only a single configuration of HealthChecks.io is allowed." } } -} \ No newline at end of file +} diff --git a/hacs.json b/hacs.json index b986edd..6ecb8a7 100644 --- a/hacs.json +++ b/hacs.json @@ -1,7 +1,7 @@ { - "name": "Healthchecks.io", + "name": "HealthChecks.io", "zip_release": true, "hide_default_branch": true, "filename": "healthchecksio.zip", "domain": "healthchecksio" -} \ No newline at end of file +} From f472026856e6a50d1792f47072cebcf7b4518cda Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Tue, 15 Aug 2023 21:35:20 -0400 Subject: [PATCH 4/6] Add Sensor State Icons --- custom_components/healthchecksio/const.py | 20 +++++++++++++------- custom_components/healthchecksio/sensor.py | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/custom_components/healthchecksio/const.py b/custom_components/healthchecksio/const.py index 1c36aa0..d63a7a5 100644 --- a/custom_components/healthchecksio/const.py +++ b/custom_components/healthchecksio/const.py @@ -20,25 +20,31 @@ OFFICIAL_SITE_ROOT = "https://healthchecks.io" ATTR_ATTRIBUTION = "attribution" -ATTR_NAME = "name" +ATTR_CHECKS = "checks" ATTR_LAST_PING = "last_ping" -ATTR_STATUS = "status" +ATTR_NAME = "name" ATTR_PING_URL = "ping_url" -ATTR_CHECKS = "checks" +ATTR_STATUS = "status" CONF_API_KEY = "api_key" CONF_CHECK = "check" +CONF_CREATE_BINARY_SENSOR = "create_binary_sensor" +CONF_CREATE_SENSOR = "create_sensor" CONF_PING_ENDPOINT = "ping_endpoint" CONF_SELF_HOSTED = "self_hosted" CONF_SITE_ROOT = "site_root" -CONF_CREATE_SENSOR = "create_sensor" -CONF_CREATE_BINARY_SENSOR = "create_binary_sensor" DATA_CLIENT = "client" DATA_DATA = "data" +DEFAULT_CREATE_BINARY_SENSOR = True +DEFAULT_CREATE_SENSOR = False DEFAULT_PING_ENDPOINT = "ping" DEFAULT_SELF_HOSTED = False DEFAULT_SITE_ROOT = "https://checks.mydomain.com" -DEFAULT_CREATE_BINARY_SENSOR = True -DEFAULT_CREATE_SENSOR = False + +ICON_DEFAULT = "mdi:cloud" +ICON_DOWN = "mdi:cloud-off" +ICON_GRACE = "mdi:cloud-alert" +ICON_PAUSED = "mdi:cloud-question" +ICON_UP = "mdi:cloud-check-variant" diff --git a/custom_components/healthchecksio/sensor.py b/custom_components/healthchecksio/sensor.py index a4791e9..7aa966e 100644 --- a/custom_components/healthchecksio/sensor.py +++ b/custom_components/healthchecksio/sensor.py @@ -15,6 +15,11 @@ DATA_DATA, DOMAIN, DOMAIN_DATA, + ICON_DEFAULT, + ICON_DOWN, + ICON_GRACE, + ICON_PAUSED, + ICON_UP, ) _LOGGER = logging.getLogger(__name__) @@ -46,6 +51,7 @@ def __init__(self, hass, check_data, config_entry): self._attr_unique_id = self.check_data.get(ATTR_PING_URL, "").split("/")[-1] self._attr_extra_state_attributes = {} self._attr_native_value = None + self._attr_icon = ICON_DEFAULT self.check = {} async def async_update(self): @@ -60,7 +66,21 @@ async def async_update(self): self._attr_name = self.check.get(ATTR_NAME) self._attr_native_value = self.check.get(ATTR_STATUS) if isinstance(self._attr_native_value, str): + if self._attr_native_value.lower() == "new": + self._attr_icon = ICON_DEFAULT + elif self._attr_native_value.lower() == "up": + self._attr_icon = ICON_UP + elif self._attr_native_value.lower() == "grace": + self._attr_icon = ICON_GRACE + elif self._attr_native_value.lower() == "down": + self._attr_icon = ICON_DOWN + elif self._attr_native_value.lower() == "paused": + self._attr_icon = ICON_PAUSED + else: + self._attr_icon = ICON_DEFAULT self._attr_native_value = self._attr_native_value.title() + else: + self._attr_icon = ICON_DEFAULT self._attr_extra_state_attributes[ATTR_ATTRIBUTION] = ATTRIBUTION self._attr_extra_state_attributes[ATTR_LAST_PING] = self.check.get( ATTR_LAST_PING From 0d407df6ed69145025107c5582a5d3d986282bde Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Mon, 9 Oct 2023 11:40:35 -0400 Subject: [PATCH 5/6] Address corrections in related PRs Address corrections in PR #42, #43, #44 --- custom_components/healthchecksio/__init__.py | 13 +++++-------- custom_components/healthchecksio/config_flow.py | 17 +++++++---------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/custom_components/healthchecksio/__init__.py b/custom_components/healthchecksio/__init__.py index 83ac71a..c8196b6 100644 --- a/custom_components/healthchecksio/__init__.py +++ b/custom_components/healthchecksio/__init__.py @@ -116,8 +116,7 @@ async def async_unload_entry( curr_plat, ) if unload_ok: - hass.data[DOMAIN_DATA].pop(DATA_CLIENT, None) - hass.data[DOMAIN_DATA].pop(DATA_DATA, None) + hass.data.pop(DOMAIN_DATA, None) _LOGGER.info("Successfully removed the HealthChecks.io integration") return unload_ok @@ -166,12 +165,10 @@ async def update_data(self): else: _LOGGER.debug("Send Check is not defined") try: - async with session.get( - f"{self.site_root}/api/v1/checks/", - headers=headers, - timeout=timeout10, - ) as data: - self.hass.data[DOMAIN_DATA][DATA_DATA] = await data.json() + data = await session.get( + f"{self.site_root}/api/v1/checks/", headers=headers, timeout=timeout10 + ) + self.hass.data[DOMAIN_DATA][DATA_DATA] = await data.json() except (aiohttp.ClientError, asyncio.TimeoutError) as error: _LOGGER.error(f"Could Not Update Data: {error}") except (ValueError, json.decoder.JSONDecodeError) as error: diff --git a/custom_components/healthchecksio/config_flow.py b/custom_components/healthchecksio/config_flow.py index 4257743..2d7fcfb 100644 --- a/custom_components/healthchecksio/config_flow.py +++ b/custom_components/healthchecksio/config_flow.py @@ -64,12 +64,10 @@ async def _test_credentials( else: _LOGGER.debug("Send Check is not defined") try: - async with session.get( - f"{site_root}/api/v1/checks/", - headers=headers, - timeout=timeout10, - ) as data: - hass.data[DOMAIN_DATA] = {"data": await data.json()} + data = await session.get( + f"{site_root}/api/v1/checks/", headers=headers, timeout=timeout10 + ) + hass.data[DOMAIN_DATA] = {"data": await data.json()} except (aiohttp.ClientError, asyncio.TimeoutError) as error: _LOGGER.error(f"Could Not Update Data: {error}") return False @@ -77,12 +75,11 @@ async def _test_credentials( _LOGGER.error(f"Data JSON Decode Error: {error}") return False else: - if data.ok: - _LOGGER.debug(f"Get Data HTTP Status Code: {data.status}") - return True - else: + if not data.ok: _LOGGER.error(f"Error: Get Data HTTP Status Code: {data.status}") return False + _LOGGER.debug(f"Get Data HTTP Status Code: {data.status}") + return True class HealchecksioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): From 2b84d9d43fd6dc8367efebb4355b696ea7460d2b Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Mon, 9 Oct 2023 11:49:26 -0400 Subject: [PATCH 6/6] Code Cleanup --- custom_components/healthchecksio/__init__.py | 3 +-- custom_components/healthchecksio/config_flow.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/custom_components/healthchecksio/__init__.py b/custom_components/healthchecksio/__init__.py index c8196b6..a223cab 100644 --- a/custom_components/healthchecksio/__init__.py +++ b/custom_components/healthchecksio/__init__.py @@ -193,5 +193,4 @@ async def check_files(hass: core.HomeAssistant) -> bool: if missing: _LOGGER.critical(f"The following files are missing: {missing}") return False - else: - return True + return True diff --git a/custom_components/healthchecksio/config_flow.py b/custom_components/healthchecksio/config_flow.py index 2d7fcfb..b292114 100644 --- a/custom_components/healthchecksio/config_flow.py +++ b/custom_components/healthchecksio/config_flow.py @@ -94,9 +94,7 @@ def __init__(self): async def async_step_user(self, user_input=None, errors=None): self._errors = {} - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - if self.hass.data.get(DOMAIN): + if self._async_current_entries() or self.hass.data.get(DOMAIN): return self.async_abort(reason="single_instance_allowed") if user_input is not None: