Add config flow to Streamlabs water (#104962)
* Add config flow to Streamlabs water * Add config flow to Streamlabs water * Add config flow to Streamlabs water * Add issue when import is successful * Remove import issue when entry already exists * Remove import issue when entry already exists * Fix feedback * Fix feedback --------- Co-authored-by: G Johansson <goran.johansson@shiftit.se>pull/95594/head^2
parent
c7b4f8f780
commit
01ded7daea
|
@ -1243,7 +1243,10 @@ omit =
|
|||
homeassistant/components/stream/fmp4utils.py
|
||||
homeassistant/components/stream/hls.py
|
||||
homeassistant/components/stream/worker.py
|
||||
homeassistant/components/streamlabswater/*
|
||||
homeassistant/components/streamlabswater/__init__.py
|
||||
homeassistant/components/streamlabswater/binary_sensor.py
|
||||
homeassistant/components/streamlabswater/coordinator.py
|
||||
homeassistant/components/streamlabswater/sensor.py
|
||||
homeassistant/components/suez_water/__init__.py
|
||||
homeassistant/components/suez_water/sensor.py
|
||||
homeassistant/components/supervisord/sensor.py
|
||||
|
|
|
@ -1,28 +1,31 @@
|
|||
"""Support for Streamlabs Water Monitor devices."""
|
||||
import logging
|
||||
|
||||
from streamlabswater import streamlabswater
|
||||
from streamlabswater.streamlabswater import StreamlabsClient
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.core import (
|
||||
DOMAIN as HOMEASSISTANT_DOMAIN,
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
)
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
DOMAIN = "streamlabswater"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import DOMAIN
|
||||
from .coordinator import StreamlabsCoordinator
|
||||
|
||||
ATTR_AWAY_MODE = "away_mode"
|
||||
SERVICE_SET_AWAY_MODE = "set_away_mode"
|
||||
AWAY_MODE_AWAY = "away"
|
||||
AWAY_MODE_HOME = "home"
|
||||
|
||||
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR]
|
||||
|
||||
CONF_LOCATION_ID = "location_id"
|
||||
|
||||
ISSUE_PLACEHOLDER = {"url": "/config/integrations/dashboard/add?domain=streamlabswater"}
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
|
@ -36,52 +39,77 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
SET_AWAY_MODE_SCHEMA = vol.Schema(
|
||||
{vol.Required(ATTR_AWAY_MODE): vol.In([AWAY_MODE_AWAY, AWAY_MODE_HOME])}
|
||||
{
|
||||
vol.Required(ATTR_AWAY_MODE): vol.In([AWAY_MODE_AWAY, AWAY_MODE_HOME]),
|
||||
vol.Optional(CONF_LOCATION_ID): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
|
||||
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the streamlabs water integration."""
|
||||
|
||||
conf = config[DOMAIN]
|
||||
api_key = conf.get(CONF_API_KEY)
|
||||
location_id = conf.get(CONF_LOCATION_ID)
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
client = streamlabswater.StreamlabsClient(api_key)
|
||||
locations = client.get_locations().get("locations")
|
||||
|
||||
if locations is None:
|
||||
_LOGGER.error("Unable to retrieve locations. Verify API key")
|
||||
return False
|
||||
|
||||
if location_id is None:
|
||||
location = locations[0]
|
||||
location_id = location["locationId"]
|
||||
_LOGGER.info(
|
||||
"Streamlabs Water Monitor auto-detected location_id=%s", location_id
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_API_KEY: config[DOMAIN][CONF_API_KEY]},
|
||||
)
|
||||
if (
|
||||
result["type"] == FlowResultType.CREATE_ENTRY
|
||||
or result["reason"] == "already_configured"
|
||||
):
|
||||
async_create_issue(
|
||||
hass,
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
f"deprecated_yaml_{DOMAIN}",
|
||||
breaks_in_ha_version="2024.7.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": "StreamLabs",
|
||||
},
|
||||
)
|
||||
else:
|
||||
location = next(
|
||||
(loc for loc in locations if location_id == loc["locationId"]), None
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_yaml_import_issue_${result['reason']}",
|
||||
breaks_in_ha_version="2024.7.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key=f"deprecated_yaml_import_issue_${result['reason']}",
|
||||
translation_placeholders=ISSUE_PLACEHOLDER,
|
||||
)
|
||||
if location is None:
|
||||
_LOGGER.error("Supplied location_id is invalid")
|
||||
return False
|
||||
return True
|
||||
|
||||
location_name = location["name"]
|
||||
|
||||
hass.data[DOMAIN] = {
|
||||
"client": client,
|
||||
"location_id": location_id,
|
||||
"location_name": location_name,
|
||||
}
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up StreamLabs from a config entry."""
|
||||
|
||||
for platform in PLATFORMS:
|
||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
client = StreamlabsClient(api_key)
|
||||
coordinator = StreamlabsCoordinator(hass, client)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
def set_away_mode(service: ServiceCall) -> None:
|
||||
"""Set the StreamLabsWater Away Mode."""
|
||||
away_mode = service.data.get(ATTR_AWAY_MODE)
|
||||
location_id = (
|
||||
service.data.get(CONF_LOCATION_ID) or list(coordinator.data.values())[0]
|
||||
)
|
||||
client.update_location(location_id, away_mode)
|
||||
|
||||
hass.services.register(
|
||||
|
@ -89,3 +117,11 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
|
|
@ -1,84 +1,54 @@
|
|||
"""Support for Streamlabs Water Monitor Away Mode."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from streamlabswater.streamlabswater import StreamlabsClient
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import DOMAIN as STREAMLABSWATER_DOMAIN
|
||||
from . import StreamlabsCoordinator
|
||||
from .const import DOMAIN
|
||||
from .coordinator import StreamlabsData
|
||||
|
||||
DEPENDS = ["streamlabswater"]
|
||||
|
||||
MIN_TIME_BETWEEN_LOCATION_UPDATES = timedelta(seconds=60)
|
||||
|
||||
ATTR_LOCATION_ID = "location_id"
|
||||
NAME_AWAY_MODE = "Water Away Mode"
|
||||
|
||||
|
||||
def setup_platform(
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_devices: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the StreamLabsWater mode sensor."""
|
||||
client = hass.data[STREAMLABSWATER_DOMAIN]["client"]
|
||||
location_id = hass.data[STREAMLABSWATER_DOMAIN]["location_id"]
|
||||
location_name = hass.data[STREAMLABSWATER_DOMAIN]["location_name"]
|
||||
"""Set up Streamlabs water binary sensor from a config entry."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
streamlabs_location_data = StreamlabsLocationData(location_id, client)
|
||||
streamlabs_location_data.update()
|
||||
entities = []
|
||||
|
||||
add_devices([StreamlabsAwayMode(location_name, streamlabs_location_data)])
|
||||
for location_id in coordinator.data:
|
||||
entities.append(StreamlabsAwayMode(coordinator, location_id))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class StreamlabsLocationData:
|
||||
"""Track and query location data."""
|
||||
|
||||
def __init__(self, location_id: str, client: StreamlabsClient) -> None:
|
||||
"""Initialize the location data."""
|
||||
self._location_id = location_id
|
||||
self._client = client
|
||||
self._is_away = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_LOCATION_UPDATES)
|
||||
def update(self) -> None:
|
||||
"""Query and store location data."""
|
||||
location = self._client.get_location(self._location_id)
|
||||
self._is_away = location["homeAway"] == "away"
|
||||
|
||||
def is_away(self) -> bool | None:
|
||||
"""Return whether away more is enabled."""
|
||||
return self._is_away
|
||||
|
||||
|
||||
class StreamlabsAwayMode(BinarySensorEntity):
|
||||
class StreamlabsAwayMode(CoordinatorEntity[StreamlabsCoordinator], BinarySensorEntity):
|
||||
"""Monitor the away mode state."""
|
||||
|
||||
def __init__(
|
||||
self, location_name: str, streamlabs_location_data: StreamlabsLocationData
|
||||
) -> None:
|
||||
def __init__(self, coordinator: StreamlabsCoordinator, location_id: str) -> None:
|
||||
"""Initialize the away mode device."""
|
||||
self._location_name = location_name
|
||||
self._streamlabs_location_data = streamlabs_location_data
|
||||
self._is_away = None
|
||||
super().__init__(coordinator)
|
||||
self._location_id = location_id
|
||||
|
||||
@property
|
||||
def location_data(self) -> StreamlabsData:
|
||||
"""Returns the data object."""
|
||||
return self.coordinator.data[self._location_id]
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name for away mode."""
|
||||
return f"{self._location_name} {NAME_AWAY_MODE}"
|
||||
return f"{self.location_data.name} {NAME_AWAY_MODE}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
def is_on(self) -> bool:
|
||||
"""Return if away mode is on."""
|
||||
return self._streamlabs_location_data.is_away()
|
||||
|
||||
def update(self) -> None:
|
||||
"""Retrieve the latest location data and away mode state."""
|
||||
self._streamlabs_location_data.update()
|
||||
return self.location_data.is_away
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
"""Config flow for StreamLabs integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from streamlabswater.streamlabswater import StreamlabsClient
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, api_key: str) -> None:
|
||||
"""Validate the user input allows us to connect."""
|
||||
client = StreamlabsClient(api_key)
|
||||
response = await hass.async_add_executor_job(client.get_locations)
|
||||
locations = response.get("locations")
|
||||
|
||||
if locations is None:
|
||||
raise CannotConnect
|
||||
|
||||
|
||||
class StreamlabsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for StreamLabs."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match(user_input)
|
||||
try:
|
||||
await validate_input(self.hass, user_input[CONF_API_KEY])
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(title="Streamlabs", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_API_KEY): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
|
||||
"""Import the yaml config."""
|
||||
self._async_abort_entries_match(user_input)
|
||||
try:
|
||||
await validate_input(self.hass, user_input[CONF_API_KEY])
|
||||
except CannotConnect:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.exception("Unexpected exception")
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
return self.async_create_entry(title="Streamlabs", data=user_input)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
|
@ -0,0 +1,6 @@
|
|||
"""Constants for the StreamLabs integration."""
|
||||
import logging
|
||||
|
||||
DOMAIN = "streamlabswater"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
|
@ -0,0 +1,57 @@
|
|||
"""Coordinator for Streamlabs water integration."""
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from streamlabswater.streamlabswater import StreamlabsClient
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import LOGGER
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class StreamlabsData:
|
||||
"""Class to hold Streamlabs data."""
|
||||
|
||||
is_away: bool
|
||||
name: str
|
||||
daily_usage: float
|
||||
monthly_usage: float
|
||||
yearly_usage: float
|
||||
|
||||
|
||||
class StreamlabsCoordinator(DataUpdateCoordinator[dict[str, StreamlabsData]]):
|
||||
"""Coordinator for Streamlabs."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
client: StreamlabsClient,
|
||||
) -> None:
|
||||
"""Coordinator for Streamlabs."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name="Streamlabs",
|
||||
update_interval=timedelta(seconds=60),
|
||||
)
|
||||
self.client = client
|
||||
|
||||
async def _async_update_data(self) -> dict[str, StreamlabsData]:
|
||||
return await self.hass.async_add_executor_job(self._update_data)
|
||||
|
||||
def _update_data(self) -> dict[str, StreamlabsData]:
|
||||
locations = self.client.get_locations()
|
||||
res = {}
|
||||
for location in locations:
|
||||
location_id = location["locationId"]
|
||||
water_usage = self.client.get_water_usage_summary(location_id)
|
||||
res[location_id] = StreamlabsData(
|
||||
is_away=location["homeAway"] == "away",
|
||||
name=location["name"],
|
||||
daily_usage=round(water_usage["today"], 1),
|
||||
monthly_usage=round(water_usage["thisMonth"], 1),
|
||||
yearly_usage=round(water_usage["thisYear"], 1),
|
||||
)
|
||||
return res
|
|
@ -2,6 +2,7 @@
|
|||
"domain": "streamlabswater",
|
||||
"name": "StreamLabs",
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/streamlabswater",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["streamlabswater"],
|
||||
|
|
|
@ -1,111 +1,69 @@
|
|||
"""Support for Streamlabs Water Monitor Usage."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from streamlabswater.streamlabswater import StreamlabsClient
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfVolume
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import DOMAIN as STREAMLABSWATER_DOMAIN
|
||||
|
||||
DEPENDENCIES = ["streamlabswater"]
|
||||
|
||||
WATER_ICON = "mdi:water"
|
||||
MIN_TIME_BETWEEN_USAGE_UPDATES = timedelta(seconds=60)
|
||||
from . import StreamlabsCoordinator
|
||||
from .const import DOMAIN
|
||||
from .coordinator import StreamlabsData
|
||||
|
||||
NAME_DAILY_USAGE = "Daily Water"
|
||||
NAME_MONTHLY_USAGE = "Monthly Water"
|
||||
NAME_YEARLY_USAGE = "Yearly Water"
|
||||
|
||||
|
||||
def setup_platform(
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_devices: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up water usage sensors."""
|
||||
client = hass.data[STREAMLABSWATER_DOMAIN]["client"]
|
||||
location_id = hass.data[STREAMLABSWATER_DOMAIN]["location_id"]
|
||||
location_name = hass.data[STREAMLABSWATER_DOMAIN]["location_name"]
|
||||
"""Set up Streamlabs water sensor from a config entry."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
streamlabs_usage_data = StreamlabsUsageData(location_id, client)
|
||||
streamlabs_usage_data.update()
|
||||
entities = []
|
||||
|
||||
add_devices(
|
||||
[
|
||||
StreamLabsDailyUsage(location_name, streamlabs_usage_data),
|
||||
StreamLabsMonthlyUsage(location_name, streamlabs_usage_data),
|
||||
StreamLabsYearlyUsage(location_name, streamlabs_usage_data),
|
||||
]
|
||||
)
|
||||
for location_id in coordinator.data.values():
|
||||
entities.extend(
|
||||
[
|
||||
StreamLabsDailyUsage(coordinator, location_id),
|
||||
StreamLabsMonthlyUsage(coordinator, location_id),
|
||||
StreamLabsYearlyUsage(coordinator, location_id),
|
||||
]
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class StreamlabsUsageData:
|
||||
"""Track and query usage data."""
|
||||
|
||||
def __init__(self, location_id: str, client: StreamlabsClient) -> None:
|
||||
"""Initialize the usage data."""
|
||||
self._location_id = location_id
|
||||
self._client = client
|
||||
self._today = None
|
||||
self._this_month = None
|
||||
self._this_year = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_USAGE_UPDATES)
|
||||
def update(self) -> None:
|
||||
"""Query and store usage data."""
|
||||
water_usage = self._client.get_water_usage_summary(self._location_id)
|
||||
self._today = round(water_usage["today"], 1)
|
||||
self._this_month = round(water_usage["thisMonth"], 1)
|
||||
self._this_year = round(water_usage["thisYear"], 1)
|
||||
|
||||
def get_daily_usage(self) -> float | None:
|
||||
"""Return the day's usage."""
|
||||
return self._today
|
||||
|
||||
def get_monthly_usage(self) -> float | None:
|
||||
"""Return the month's usage."""
|
||||
return self._this_month
|
||||
|
||||
def get_yearly_usage(self) -> float | None:
|
||||
"""Return the year's usage."""
|
||||
return self._this_year
|
||||
|
||||
|
||||
class StreamLabsDailyUsage(SensorEntity):
|
||||
class StreamLabsDailyUsage(CoordinatorEntity[StreamlabsCoordinator], SensorEntity):
|
||||
"""Monitors the daily water usage."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.WATER
|
||||
_attr_native_unit_of_measurement = UnitOfVolume.GALLONS
|
||||
|
||||
def __init__(
|
||||
self, location_name: str, streamlabs_usage_data: StreamlabsUsageData
|
||||
) -> None:
|
||||
def __init__(self, coordinator: StreamlabsCoordinator, location_id: str) -> None:
|
||||
"""Initialize the daily water usage device."""
|
||||
self._location_name = location_name
|
||||
self._streamlabs_usage_data = streamlabs_usage_data
|
||||
self._state = None
|
||||
super().__init__(coordinator)
|
||||
self._location_id = location_id
|
||||
|
||||
@property
|
||||
def location_data(self) -> StreamlabsData:
|
||||
"""Returns the data object."""
|
||||
return self.coordinator.data[self._location_id]
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name for daily usage."""
|
||||
return f"{self._location_name} {NAME_DAILY_USAGE}"
|
||||
return f"{self.location_data.name} {NAME_DAILY_USAGE}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
def native_value(self) -> float:
|
||||
"""Return the current daily usage."""
|
||||
return self._streamlabs_usage_data.get_daily_usage()
|
||||
|
||||
def update(self) -> None:
|
||||
"""Retrieve the latest daily usage."""
|
||||
self._streamlabs_usage_data.update()
|
||||
return self.location_data.daily_usage
|
||||
|
||||
|
||||
class StreamLabsMonthlyUsage(StreamLabsDailyUsage):
|
||||
|
@ -114,12 +72,12 @@ class StreamLabsMonthlyUsage(StreamLabsDailyUsage):
|
|||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name for monthly usage."""
|
||||
return f"{self._location_name} {NAME_MONTHLY_USAGE}"
|
||||
return f"{self.location_data.name} {NAME_MONTHLY_USAGE}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
def native_value(self) -> float:
|
||||
"""Return the current monthly usage."""
|
||||
return self._streamlabs_usage_data.get_monthly_usage()
|
||||
return self.location_data.monthly_usage
|
||||
|
||||
|
||||
class StreamLabsYearlyUsage(StreamLabsDailyUsage):
|
||||
|
@ -128,9 +86,9 @@ class StreamLabsYearlyUsage(StreamLabsDailyUsage):
|
|||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name for yearly usage."""
|
||||
return f"{self._location_name} {NAME_YEARLY_USAGE}"
|
||||
return f"{self.location_data.name} {NAME_YEARLY_USAGE}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
def native_value(self) -> float:
|
||||
"""Return the current yearly usage."""
|
||||
return self._streamlabs_usage_data.get_yearly_usage()
|
||||
return self.location_data.yearly_usage
|
||||
|
|
|
@ -7,3 +7,6 @@ set_away_mode:
|
|||
options:
|
||||
- "away"
|
||||
- "home"
|
||||
location_id:
|
||||
selector:
|
||||
text:
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"set_away_mode": {
|
||||
"name": "Set away mode",
|
||||
|
@ -7,8 +23,22 @@
|
|||
"away_mode": {
|
||||
"name": "Away mode",
|
||||
"description": "Home or away."
|
||||
},
|
||||
"location_id": {
|
||||
"name": "Location ID",
|
||||
"description": "The location ID of the Streamlabs Water Monitor."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue_cannot_connect": {
|
||||
"title": "The Streamlabs water YAML configuration import failed",
|
||||
"description": "Configuring Streamlabs water using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Streamlabs water works and restart Home Assistant to try again or remove the Streamlabs water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
},
|
||||
"deprecated_yaml_import_issue_unknown": {
|
||||
"title": "The Streamlabs water YAML configuration import failed",
|
||||
"description": "Configuring Streamlabs water using YAML is being removed but there was an unknown error when trying to import the YAML configuration.\n\nEnsure the imported configuration is correct and remove the Streamlabs water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,6 +476,7 @@ FLOWS = {
|
|||
"steamist",
|
||||
"stookalert",
|
||||
"stookwijzer",
|
||||
"streamlabswater",
|
||||
"subaru",
|
||||
"suez_water",
|
||||
"sun",
|
||||
|
|
|
@ -5585,7 +5585,7 @@
|
|||
"streamlabswater": {
|
||||
"name": "StreamLabs",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"subaru": {
|
||||
|
|
|
@ -1930,6 +1930,9 @@ stookalert==0.1.4
|
|||
# homeassistant.components.stookwijzer
|
||||
stookwijzer==1.3.0
|
||||
|
||||
# homeassistant.components.streamlabswater
|
||||
streamlabswater==1.0.1
|
||||
|
||||
# homeassistant.components.huawei_lte
|
||||
# homeassistant.components.solaredge
|
||||
# homeassistant.components.thermoworks_smoke
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the StreamLabs integration."""
|
|
@ -0,0 +1,14 @@
|
|||
"""Common fixtures for the StreamLabs tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.streamlabswater.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
|
@ -0,0 +1,193 @@
|
|||
"""Test the StreamLabs config flow."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.streamlabswater.const import DOMAIN
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Streamlabs"
|
||||
assert result["data"] == {CONF_API_KEY: "abc"}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_cannot_connect(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
|
||||
return_value={},
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_KEY: "abc"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Streamlabs"
|
||||
assert result["data"] == {CONF_API_KEY: "abc"}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_unknown(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we handle unknown error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_KEY: "abc"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "unknown"}
|
||||
|
||||
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Streamlabs"
|
||||
assert result["data"] == {CONF_API_KEY: "abc"}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_entry_already_exists(hass: HomeAssistant) -> None:
|
||||
"""Test we handle if the entry already exists."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_API_KEY: "abc"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_KEY: "abc"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_import(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test import flow."""
|
||||
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Streamlabs"
|
||||
assert result["data"] == {CONF_API_KEY: "abc"}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_import_cannot_connect(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
with patch(
|
||||
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
|
||||
return_value={},
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_import_unknown(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we handle unknown error."""
|
||||
with patch(
|
||||
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "unknown"
|
||||
|
||||
|
||||
async def test_import_entry_already_exists(hass: HomeAssistant) -> None:
|
||||
"""Test we handle if the entry already exists."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_API_KEY: "abc"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_API_KEY: "abc"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
Loading…
Reference in New Issue