mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Updates to poolsense integration (#37613)
* Created a binary sensor and corrected some review comments. * Updated the poolsense class and its interface to handle credentials better * Moved the client session to the PoolSense class. * Apply suggestions from code review * Update binary_sensor.py * Update homeassistant/components/poolsense/__init__.py * Update sensor.py * Update binary_sensor.py Co-authored-by: Chris Talkington <chris@talkingtontech.com>
This commit is contained in:
@ -634,6 +634,7 @@ omit =
|
||||
homeassistant/components/point/*
|
||||
homeassistant/components/poolsense/__init__.py
|
||||
homeassistant/components/poolsense/sensor.py
|
||||
homeassistant/components/poolsense/binary_sensor.py
|
||||
homeassistant/components/prezzibenzina/sensor.py
|
||||
homeassistant/components/proliphix/climate.py
|
||||
homeassistant/components/prometheus/*
|
||||
|
@ -11,12 +11,14 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client, update_coordinator
|
||||
from homeassistant.helpers.update_coordinator import UpdateFailed
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS = ["sensor"]
|
||||
PLATFORMS = ["sensor", "binary_sensor"]
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -30,20 +32,21 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up PoolSense from a config entry."""
|
||||
poolsense = PoolSense()
|
||||
auth_valid = await poolsense.test_poolsense_credentials(
|
||||
|
||||
poolsense = PoolSense(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
entry.data[CONF_EMAIL],
|
||||
entry.data[CONF_PASSWORD],
|
||||
)
|
||||
auth_valid = await poolsense.test_poolsense_credentials()
|
||||
|
||||
if not auth_valid:
|
||||
_LOGGER.error("Invalid authentication")
|
||||
return False
|
||||
|
||||
coordinator = await get_coordinator(hass, entry)
|
||||
coordinator = PoolSenseDataUpdateCoordinator(hass, entry)
|
||||
|
||||
await hass.data[DOMAIN][entry.entry_id].async_refresh()
|
||||
await coordinator.async_refresh()
|
||||
|
||||
if not coordinator.last_update_success:
|
||||
raise ConfigEntryNotReady
|
||||
@ -75,29 +78,64 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def get_coordinator(hass, entry):
|
||||
"""Get the data update coordinator."""
|
||||
class PoolSenseEntity(Entity):
|
||||
"""Implements a common class elements representing the PoolSense component."""
|
||||
|
||||
async def async_get_data():
|
||||
_LOGGER.info("Run query to server")
|
||||
poolsense = PoolSense()
|
||||
return_data = {}
|
||||
def __init__(self, coordinator, email, info_type):
|
||||
"""Initialize poolsense sensor."""
|
||||
self._unique_id = f"{email}-{info_type}"
|
||||
self.coordinator = coordinator
|
||||
self.info_type = info_type
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if sensor is available."""
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return the polling requirement of the entity."""
|
||||
return False
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Connect to dispatcher listening for entity data notifications."""
|
||||
self.async_on_remove(
|
||||
self.coordinator.async_add_listener(self.async_write_ha_state)
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Request an update of the coordinator for entity."""
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
|
||||
class PoolSenseDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define an object to hold PoolSense data."""
|
||||
|
||||
def __init__(self, hass, entry):
|
||||
"""Initialize."""
|
||||
self.poolsense = PoolSense(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
entry.data[CONF_EMAIL],
|
||||
entry.data[CONF_PASSWORD],
|
||||
)
|
||||
self.hass = hass
|
||||
self.entry = entry
|
||||
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=timedelta(hours=1))
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Update data via library."""
|
||||
data = {}
|
||||
with async_timeout.timeout(10):
|
||||
try:
|
||||
return_data = await poolsense.get_poolsense_data(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
entry.data[CONF_EMAIL],
|
||||
entry.data[CONF_PASSWORD],
|
||||
)
|
||||
data = await self.poolsense.get_poolsense_data()
|
||||
except (PoolSenseError) as error:
|
||||
_LOGGER.error("PoolSense query did not complete.")
|
||||
raise UpdateFailed(error)
|
||||
|
||||
return return_data
|
||||
|
||||
return update_coordinator.DataUpdateCoordinator(
|
||||
hass,
|
||||
logging.getLogger(__name__),
|
||||
name=DOMAIN,
|
||||
update_method=async_get_data,
|
||||
update_interval=timedelta(hours=1),
|
||||
)
|
||||
return data
|
||||
|
67
homeassistant/components/poolsense/binary_sensor.py
Normal file
67
homeassistant/components/poolsense/binary_sensor.py
Normal file
@ -0,0 +1,67 @@
|
||||
"""Support for PoolSense binary sensors."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_PROBLEM,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_EMAIL
|
||||
|
||||
from . import PoolSenseEntity
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
BINARY_SENSORS = {
|
||||
"pH Status": {
|
||||
"unit": None,
|
||||
"icon": None,
|
||||
"name": "pH Status",
|
||||
"device_class": DEVICE_CLASS_PROBLEM,
|
||||
},
|
||||
"Chlorine Status": {
|
||||
"unit": None,
|
||||
"icon": None,
|
||||
"name": "Chlorine Status",
|
||||
"device_class": DEVICE_CLASS_PROBLEM,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Defer sensor setup to the shared sensor module."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
binary_sensors_list = []
|
||||
for binary_sensor in BINARY_SENSORS:
|
||||
binary_sensors_list.append(
|
||||
PoolSenseBinarySensor(
|
||||
coordinator, config_entry.data[CONF_EMAIL], binary_sensor
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(binary_sensors_list, False)
|
||||
|
||||
|
||||
class PoolSenseBinarySensor(PoolSenseEntity, BinarySensorEntity):
|
||||
"""Representation of PoolSense binary sensors."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.coordinator.data[self.info_type] == "red"
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return BINARY_SENSORS[self.info_type]["icon"]
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this device."""
|
||||
return BINARY_SENSORS[self.info_type]["device_class"]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return f"PoolSense {BINARY_SENSORS[self.info_type]['name']}"
|
@ -19,12 +19,8 @@ class PoolSenseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
_options = None
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize PoolSense config flow."""
|
||||
self._email = None
|
||||
self._password = None
|
||||
self._errors = {}
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
@ -35,37 +31,33 @@ class PoolSenseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
await self.async_set_unique_id(user_input[CONF_EMAIL])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
self._email = user_input[CONF_EMAIL]
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
_LOGGER.debug("Configuring user: %s - Password hidden", self._email)
|
||||
|
||||
poolsense = PoolSense()
|
||||
api_key_valid = await poolsense.test_poolsense_credentials(
|
||||
aiohttp_client.async_get_clientsession(self.hass),
|
||||
self._email,
|
||||
self._password,
|
||||
_LOGGER.debug(
|
||||
"Configuring user: %s - Password hidden", user_input[CONF_EMAIL]
|
||||
)
|
||||
|
||||
poolsense = PoolSense(
|
||||
aiohttp_client.async_get_clientsession(self.hass),
|
||||
user_input[CONF_EMAIL],
|
||||
user_input[CONF_PASSWORD],
|
||||
)
|
||||
api_key_valid = await poolsense.test_poolsense_credentials()
|
||||
|
||||
if not api_key_valid:
|
||||
self._errors["base"] = "invalid_auth"
|
||||
|
||||
if not self._errors:
|
||||
return self.async_create_entry(
|
||||
title=self._email,
|
||||
data={CONF_EMAIL: self._email, CONF_PASSWORD: self._password},
|
||||
title=user_input[CONF_EMAIL],
|
||||
data={
|
||||
CONF_EMAIL: user_input[CONF_EMAIL],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
},
|
||||
)
|
||||
|
||||
return await self._show_setup_form(user_input, self._errors)
|
||||
|
||||
async def _show_setup_form(self, user_input=None, errors=None):
|
||||
"""Show the setup form to the user."""
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str}
|
||||
),
|
||||
errors=errors or {},
|
||||
errors=self._errors or {},
|
||||
)
|
||||
|
@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/poolsense",
|
||||
"requirements": [
|
||||
"poolsense==0.0.5"
|
||||
"poolsense==0.0.8"
|
||||
],
|
||||
"codeowners": [
|
||||
"@haemishkyd"
|
||||
|
@ -7,13 +7,12 @@ from homeassistant.const import (
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
STATE_OK,
|
||||
STATE_PROBLEM,
|
||||
TEMP_CELSIUS,
|
||||
UNIT_PERCENTAGE,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import PoolSenseEntity
|
||||
from .const import ATTRIBUTION, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -28,7 +27,7 @@ SENSORS = {
|
||||
"pH": {"unit": None, "icon": "mdi:pool", "name": "pH", "device_class": None},
|
||||
"Battery": {
|
||||
"unit": UNIT_PERCENTAGE,
|
||||
"icon": "mdi:battery",
|
||||
"icon": None,
|
||||
"name": "Battery",
|
||||
"device_class": DEVICE_CLASS_BATTERY,
|
||||
},
|
||||
@ -68,18 +67,6 @@ SENSORS = {
|
||||
"name": "pH Low",
|
||||
"device_class": None,
|
||||
},
|
||||
"pH Status": {
|
||||
"unit": None,
|
||||
"icon": "mdi:pool",
|
||||
"name": "pH Status",
|
||||
"device_class": None,
|
||||
},
|
||||
"Chlorine Status": {
|
||||
"unit": None,
|
||||
"icon": "mdi:pool",
|
||||
"name": "Chlorine Status",
|
||||
"device_class": None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -87,53 +74,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Defer sensor setup to the shared sensor module."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
PoolSenseSensor(coordinator, config_entry.data[CONF_EMAIL], info_type)
|
||||
for info_type in SENSORS
|
||||
)
|
||||
sensors_list = []
|
||||
for sensor in SENSORS:
|
||||
sensors_list.append(
|
||||
PoolSenseSensor(coordinator, config_entry.data[CONF_EMAIL], sensor)
|
||||
)
|
||||
|
||||
async_add_entities(sensors_list, False)
|
||||
|
||||
|
||||
class PoolSenseSensor(Entity):
|
||||
class PoolSenseSensor(PoolSenseEntity, Entity):
|
||||
"""Sensor representing poolsense data."""
|
||||
|
||||
def __init__(self, coordinator, email, info_type):
|
||||
"""Initialize poolsense sensor."""
|
||||
self._email = email
|
||||
self._unique_id = f"{email}-{info_type}"
|
||||
self.coordinator = coordinator
|
||||
self.info_type = info_type
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if sensor is available."""
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID to use for this sensor."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the particular component."""
|
||||
return f"PoolSense {SENSORS[self.info_type]['name']}"
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return False, updates are controlled via coordinator."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""State of the sensor."""
|
||||
if self.info_type == "pH Status":
|
||||
if self.coordinator.data[self.info_type] == "red":
|
||||
return STATE_PROBLEM
|
||||
return STATE_OK
|
||||
if self.info_type == "Chlorine Status":
|
||||
if self.coordinator.data[self.info_type] == "red":
|
||||
return STATE_PROBLEM
|
||||
return STATE_OK
|
||||
return self.coordinator.data[self.info_type]
|
||||
|
||||
@property
|
||||
@ -144,14 +104,6 @@ class PoolSenseSensor(Entity):
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
if self.info_type == "pH Status":
|
||||
if self.coordinator.data[self.info_type] == "red":
|
||||
return "mdi:thumb-down"
|
||||
return "mdi:thumb-up"
|
||||
if self.info_type == "Chlorine Status":
|
||||
if self.coordinator.data[self.info_type] == "red":
|
||||
return "mdi:thumb-down"
|
||||
return "mdi:thumb-up"
|
||||
return SENSORS[self.info_type]["icon"]
|
||||
|
||||
@property
|
||||
@ -163,13 +115,3 @@ class PoolSenseSensor(Entity):
|
||||
def device_state_attributes(self):
|
||||
"""Return device attributes."""
|
||||
return {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
|
||||
async def async_update(self):
|
||||
"""Update status of sensor."""
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""When entity is added to hass."""
|
||||
self.async_on_remove(
|
||||
self.coordinator.async_add_listener(self.async_write_ha_state)
|
||||
)
|
||||
|
@ -11,9 +11,7 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
|
@ -1,22 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "PoolSense",
|
||||
"description": "Set up PoolSense integration. Register on the dedicated app to get your username and password. Serial is optional.",
|
||||
"data": {
|
||||
"email": "Email",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "[%key:common::config_flow::description%]",
|
||||
"title": "PoolSense"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Can't connect to PoolSense.",
|
||||
"invalid_auth": "Invalid authorisation details.",
|
||||
"unknown": "Unknown Error."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device already configured."
|
||||
}
|
||||
}
|
||||
}
|
@ -1111,7 +1111,7 @@ pmsensor==0.4
|
||||
pocketcasts==0.1
|
||||
|
||||
# homeassistant.components.poolsense
|
||||
poolsense==0.0.5
|
||||
poolsense==0.0.8
|
||||
|
||||
# homeassistant.components.reddit
|
||||
praw==6.5.1
|
||||
|
@ -503,7 +503,7 @@ plumlightpad==0.0.11
|
||||
pmsensor==0.4
|
||||
|
||||
# homeassistant.components.poolsense
|
||||
poolsense==0.0.5
|
||||
poolsense==0.0.8
|
||||
|
||||
# homeassistant.components.reddit
|
||||
praw==6.5.1
|
||||
|
Reference in New Issue
Block a user