mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add Srp energy component (#41091)
This commit is contained in:
parent
2ed27fc15d
commit
c42b1f65b3
@ -424,6 +424,7 @@ homeassistant/components/splunk/* @Bre77
|
|||||||
homeassistant/components/spotify/* @frenck
|
homeassistant/components/spotify/* @frenck
|
||||||
homeassistant/components/sql/* @dgomes
|
homeassistant/components/sql/* @dgomes
|
||||||
homeassistant/components/squeezebox/* @rajlaud
|
homeassistant/components/squeezebox/* @rajlaud
|
||||||
|
homeassistant/components/srp_energy/* @briglx
|
||||||
homeassistant/components/starline/* @anonym-tsk
|
homeassistant/components/starline/* @anonym-tsk
|
||||||
homeassistant/components/statistics/* @fabaff
|
homeassistant/components/statistics/* @fabaff
|
||||||
homeassistant/components/stiebel_eltron/* @fucm
|
homeassistant/components/stiebel_eltron/* @fucm
|
||||||
|
52
homeassistant/components/srp_energy/__init__.py
Normal file
52
homeassistant/components/srp_energy/__init__.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
"""The SRP Energy integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from srpenergy.client import SrpEnergyClient
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
|
||||||
|
from .const import SRP_ENERGY_DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Old way of setting up the srp_energy component."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Set up the SRP Energy component from a config entry."""
|
||||||
|
# Store an SrpEnergyClient object for your srp_energy to access
|
||||||
|
try:
|
||||||
|
srp_energy_client = SrpEnergyClient(
|
||||||
|
entry.data.get(CONF_ID),
|
||||||
|
entry.data.get(CONF_USERNAME),
|
||||||
|
entry.data.get(CONF_PASSWORD),
|
||||||
|
)
|
||||||
|
hass.data[SRP_ENERGY_DOMAIN] = srp_energy_client
|
||||||
|
except (Exception) as ex:
|
||||||
|
_LOGGER.error("Unable to connect to Srp Energy: %s", str(ex))
|
||||||
|
raise ConfigEntryNotReady from ex
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "sensor")
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
# unload srp client
|
||||||
|
hass.data[SRP_ENERGY_DOMAIN] = None
|
||||||
|
# Remove config entry
|
||||||
|
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
||||||
|
|
||||||
|
return True
|
71
homeassistant/components/srp_energy/config_flow.py
Normal file
71
homeassistant/components/srp_energy/config_flow.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
"""Config flow for SRP Energy."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from srpenergy.client import SrpEnergyClient
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_ID, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
from .const import ( # pylint:disable=unused-import
|
||||||
|
CONF_IS_TOU,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
SRP_ENERGY_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=SRP_ENERGY_DOMAIN):
|
||||||
|
"""Handle a config flow for SRP Energy."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
config = {
|
||||||
|
vol.Required(CONF_ID): str,
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
|
||||||
|
vol.Optional(CONF_IS_TOU, default=False): bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
|
||||||
|
srp_client = SrpEnergyClient(
|
||||||
|
user_input[CONF_ID],
|
||||||
|
user_input[CONF_USERNAME],
|
||||||
|
user_input[CONF_PASSWORD],
|
||||||
|
)
|
||||||
|
|
||||||
|
is_valid = await self.hass.async_add_executor_job(srp_client.validate)
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input[CONF_NAME], data=user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
errors["base"] = "invalid_account"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=vol.Schema(self.config), errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import from config."""
|
||||||
|
# Validate config values
|
||||||
|
return await self.async_step_user(user_input=import_config)
|
15
homeassistant/components/srp_energy/const.py
Normal file
15
homeassistant/components/srp_energy/const.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""Constants for the SRP Energy integration."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
SRP_ENERGY_DOMAIN = "srp_energy"
|
||||||
|
DEFAULT_NAME = "SRP Energy"
|
||||||
|
|
||||||
|
CONF_IS_TOU = "is_tou"
|
||||||
|
|
||||||
|
ATTRIBUTION = "Powered by SRP Energy"
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1440)
|
||||||
|
|
||||||
|
SENSOR_NAME = "Usage"
|
||||||
|
SENSOR_TYPE = "usage"
|
||||||
|
|
||||||
|
ICON = "mdi:flash"
|
16
homeassistant/components/srp_energy/manifest.json
Normal file
16
homeassistant/components/srp_energy/manifest.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"domain": "srp_energy",
|
||||||
|
"name": "SRP Energy",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/srp_energy",
|
||||||
|
"requirements": [
|
||||||
|
"srpenergy==1.3.2"
|
||||||
|
],
|
||||||
|
"ssdp": [],
|
||||||
|
"zeroconf": [],
|
||||||
|
"homekit": {},
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@briglx"
|
||||||
|
]
|
||||||
|
}
|
153
homeassistant/components/srp_energy/sensor.py
Normal file
153
homeassistant/components/srp_energy/sensor.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
"""Support for SRP Energy Sensor."""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout
|
||||||
|
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, ENERGY_KILO_WATT_HOUR
|
||||||
|
from homeassistant.helpers import entity
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
ATTRIBUTION,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
ICON,
|
||||||
|
MIN_TIME_BETWEEN_UPDATES,
|
||||||
|
SENSOR_NAME,
|
||||||
|
SENSOR_TYPE,
|
||||||
|
SRP_ENERGY_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
|
"""Set up the SRP Energy Usage sensor."""
|
||||||
|
# API object stored here by __init__.py
|
||||||
|
is_time_of_use = False
|
||||||
|
api = hass.data[SRP_ENERGY_DOMAIN]
|
||||||
|
if entry and entry.data:
|
||||||
|
is_time_of_use = entry.data["is_tou"]
|
||||||
|
|
||||||
|
async def async_update_data():
|
||||||
|
"""Fetch data from API endpoint.
|
||||||
|
|
||||||
|
This is the place to pre-process the data to lookup tables
|
||||||
|
so entities can quickly look up their data.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Fetch srp_energy data
|
||||||
|
start_date = datetime.now() + timedelta(days=-1)
|
||||||
|
end_date = datetime.now()
|
||||||
|
with async_timeout.timeout(10):
|
||||||
|
hourly_usage = await hass.async_add_executor_job(
|
||||||
|
api.usage,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
is_time_of_use,
|
||||||
|
)
|
||||||
|
|
||||||
|
previous_daily_usage = 0.0
|
||||||
|
for _, _, _, kwh, _ in hourly_usage:
|
||||||
|
previous_daily_usage += float(kwh)
|
||||||
|
return previous_daily_usage
|
||||||
|
except (TimeoutError) as timeout_err:
|
||||||
|
raise UpdateFailed("Timeout communicating with API") from timeout_err
|
||||||
|
except (ConnectError, HTTPError, Timeout, ValueError, TypeError) as err:
|
||||||
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
|
|
||||||
|
coordinator = DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="sensor",
|
||||||
|
update_method=async_update_data,
|
||||||
|
update_interval=MIN_TIME_BETWEEN_UPDATES,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch initial data so we have data when entities subscribe
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
async_add_entities([SrpEntity(coordinator)])
|
||||||
|
|
||||||
|
|
||||||
|
class SrpEntity(entity.Entity):
|
||||||
|
"""Implementation of a Srp Energy Usage sensor."""
|
||||||
|
|
||||||
|
def __init__(self, coordinator):
|
||||||
|
"""Initialize the SrpEntity class."""
|
||||||
|
self._name = SENSOR_NAME
|
||||||
|
self.type = SENSOR_TYPE
|
||||||
|
self.coordinator = coordinator
|
||||||
|
self._unit_of_measurement = ENERGY_KILO_WATT_HOUR
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return f"{DEFAULT_NAME} {self._name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return sensor unique_id."""
|
||||||
|
return self.type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the device."""
|
||||||
|
if self._state:
|
||||||
|
return f"{self._state:.2f}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement of this entity, if any."""
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return icon."""
|
||||||
|
return ICON
|
||||||
|
|
||||||
|
@property
|
||||||
|
def usage(self):
|
||||||
|
"""Return entity state."""
|
||||||
|
if self.coordinator.data:
|
||||||
|
return f"{self.coordinator.data:.2f}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""No need to poll. Coordinator notifies entity of updates."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes."""
|
||||||
|
if not self.coordinator.data:
|
||||||
|
return None
|
||||||
|
attributes = {
|
||||||
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return if entity is available."""
|
||||||
|
return self.coordinator.last_update_success
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
if self.coordinator.data:
|
||||||
|
self._state = self.coordinator.data
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the entity.
|
||||||
|
|
||||||
|
Only used by the generic entity update service.
|
||||||
|
"""
|
||||||
|
await self.coordinator.async_request_refresh()
|
24
homeassistant/components/srp_energy/strings.json
Normal file
24
homeassistant/components/srp_energy/strings.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"title": "SRP Energy",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"id": "Account Id",
|
||||||
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"is_tou": "Is Time of Use Plan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"invalid_account": "Account ID should be a 9 digit number",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
homeassistant/components/srp_energy/translations/en.json
Normal file
24
homeassistant/components/srp_energy/translations/en.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"invalid_account": "Account ID should be a 9 digit number",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"id": "Account Id",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"is_tou": "Is Time of Use Plan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "SRP Energy"
|
||||||
|
}
|
@ -190,6 +190,7 @@ FLOWS = [
|
|||||||
"spider",
|
"spider",
|
||||||
"spotify",
|
"spotify",
|
||||||
"squeezebox",
|
"squeezebox",
|
||||||
|
"srp_energy",
|
||||||
"starline",
|
"starline",
|
||||||
"syncthru",
|
"syncthru",
|
||||||
"synology_dsm",
|
"synology_dsm",
|
||||||
|
@ -2098,6 +2098,9 @@ spotipy==2.16.1
|
|||||||
# homeassistant.components.sql
|
# homeassistant.components.sql
|
||||||
sqlalchemy==1.3.20
|
sqlalchemy==1.3.20
|
||||||
|
|
||||||
|
# homeassistant.components.srp_energy
|
||||||
|
srpenergy==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.starline
|
# homeassistant.components.starline
|
||||||
starline==0.1.3
|
starline==0.1.3
|
||||||
|
|
||||||
|
@ -1017,6 +1017,9 @@ spotipy==2.16.1
|
|||||||
# homeassistant.components.sql
|
# homeassistant.components.sql
|
||||||
sqlalchemy==1.3.20
|
sqlalchemy==1.3.20
|
||||||
|
|
||||||
|
# homeassistant.components.srp_energy
|
||||||
|
srpenergy==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.starline
|
# homeassistant.components.starline
|
||||||
starline==0.1.3
|
starline==0.1.3
|
||||||
|
|
||||||
|
55
tests/components/srp_energy/__init__.py
Normal file
55
tests/components/srp_energy/__init__.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"""Tests for the SRP Energy integration."""
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components import srp_energy
|
||||||
|
from homeassistant.const import CONF_ID, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
ENTRY_OPTIONS = {}
|
||||||
|
|
||||||
|
ENTRY_CONFIG = {
|
||||||
|
CONF_NAME: "Test",
|
||||||
|
CONF_ID: "123456789",
|
||||||
|
CONF_USERNAME: "abba",
|
||||||
|
CONF_PASSWORD: "ana",
|
||||||
|
srp_energy.const.CONF_IS_TOU: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def init_integration(
|
||||||
|
hass,
|
||||||
|
config=None,
|
||||||
|
options=None,
|
||||||
|
entry_id="1",
|
||||||
|
source="user",
|
||||||
|
side_effect=None,
|
||||||
|
usage=None,
|
||||||
|
):
|
||||||
|
"""Set up the srp_energy integration in Home Assistant."""
|
||||||
|
if not config:
|
||||||
|
config = ENTRY_CONFIG
|
||||||
|
|
||||||
|
if not options:
|
||||||
|
options = ENTRY_OPTIONS
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=srp_energy.SRP_ENERGY_DOMAIN,
|
||||||
|
source=source,
|
||||||
|
data=config,
|
||||||
|
connection_class=config_entries.CONN_CLASS_CLOUD_POLL,
|
||||||
|
options=options,
|
||||||
|
entry_id=entry_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("srpenergy.client.SrpEnergyClient"), patch(
|
||||||
|
"homeassistant.components.srp_energy.SrpEnergyClient", side_effect=side_effect
|
||||||
|
), patch("srpenergy.client.SrpEnergyClient.usage", return_value=usage), patch(
|
||||||
|
"homeassistant.components.srp_energy.SrpEnergyClient.usage", return_value=usage
|
||||||
|
):
|
||||||
|
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return config_entry
|
105
tests/components/srp_energy/test_config_flow.py
Normal file
105
tests/components/srp_energy/test_config_flow.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"""Test the SRP Energy config flow."""
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components.srp_energy.const import CONF_IS_TOU, SRP_ENERGY_DOMAIN
|
||||||
|
|
||||||
|
from . import ENTRY_CONFIG, init_integration
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass):
|
||||||
|
"""Test user config."""
|
||||||
|
# First get the form
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
SRP_ENERGY_DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
# Fill submit form data for config entry
|
||||||
|
with patch("homeassistant.components.srp_energy.config_flow.SrpEnergyClient"):
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input=ENTRY_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == "Test"
|
||||||
|
assert result["data"][CONF_IS_TOU] is False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_invalid_auth(hass):
|
||||||
|
"""Test user config with invalid auth."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
SRP_ENERGY_DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.srp_energy.config_flow.SrpEnergyClient.validate",
|
||||||
|
return_value=False,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input=ENTRY_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["errors"]["base"] == "invalid_auth"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_value_error(hass):
|
||||||
|
"""Test user config that throws a value error."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
SRP_ENERGY_DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.srp_energy.config_flow.SrpEnergyClient",
|
||||||
|
side_effect=ValueError(),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input=ENTRY_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["errors"]["base"] == "invalid_account"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_unknown_exception(hass):
|
||||||
|
"""Test user config that throws an unknown exception."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
SRP_ENERGY_DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.srp_energy.config_flow.SrpEnergyClient",
|
||||||
|
side_effect=Exception(),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input=ENTRY_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["errors"]["base"] == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config(hass):
|
||||||
|
"""Test handling of configuration imported."""
|
||||||
|
with patch("homeassistant.components.srp_energy.config_flow.SrpEnergyClient"):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
SRP_ENERGY_DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data=ENTRY_CONFIG,
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_integration_already_configured(hass):
|
||||||
|
"""Test integration is already configured."""
|
||||||
|
await init_integration(hass)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
SRP_ENERGY_DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "single_instance_allowed"
|
26
tests/components/srp_energy/test_init.py
Normal file
26
tests/components/srp_energy/test_init.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""Tests for Srp Energy component Init."""
|
||||||
|
from homeassistant.components import srp_energy
|
||||||
|
|
||||||
|
from tests.components.srp_energy import init_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry(hass):
|
||||||
|
"""Test setup entry fails if deCONZ is not available."""
|
||||||
|
config_entry = await init_integration(hass)
|
||||||
|
assert config_entry.state == "loaded"
|
||||||
|
assert hass.data[srp_energy.SRP_ENERGY_DOMAIN]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unload_entry(hass):
|
||||||
|
"""Test being able to unload an entry."""
|
||||||
|
config_entry = await init_integration(hass)
|
||||||
|
assert hass.data[srp_energy.SRP_ENERGY_DOMAIN]
|
||||||
|
|
||||||
|
assert await srp_energy.async_unload_entry(hass, config_entry)
|
||||||
|
assert not hass.data[srp_energy.SRP_ENERGY_DOMAIN]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_setup_entry_with_exception(hass):
|
||||||
|
"""Test exception when SrpClient can't load."""
|
||||||
|
await init_integration(hass, side_effect=Exception())
|
||||||
|
assert srp_energy.SRP_ENERGY_DOMAIN not in hass.data
|
129
tests/components/srp_energy/test_sensor.py
Normal file
129
tests/components/srp_energy/test_sensor.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
"""Tests for the srp_energy sensor platform."""
|
||||||
|
from homeassistant.components.srp_energy.const import (
|
||||||
|
ATTRIBUTION,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
ICON,
|
||||||
|
SENSOR_NAME,
|
||||||
|
SENSOR_TYPE,
|
||||||
|
SRP_ENERGY_DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.components.srp_energy.sensor import SrpEntity, async_setup_entry
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, ENERGY_KILO_WATT_HOUR
|
||||||
|
|
||||||
|
from tests.async_mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_setup_entry(hass):
|
||||||
|
"""Test the sensor."""
|
||||||
|
fake_async_add_entities = MagicMock()
|
||||||
|
fake_srp_energy_client = MagicMock()
|
||||||
|
fake_srp_energy_client.usage.return_value = [{1, 2, 3, 1.999, 4}]
|
||||||
|
fake_config = MagicMock(
|
||||||
|
data={
|
||||||
|
"name": "SRP Energy",
|
||||||
|
"is_tou": False,
|
||||||
|
"id": "0123456789",
|
||||||
|
"username": "testuser@example.com",
|
||||||
|
"password": "mypassword",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
hass.data[SRP_ENERGY_DOMAIN] = fake_srp_energy_client
|
||||||
|
|
||||||
|
await async_setup_entry(hass, fake_config, fake_async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_setup_entry_timeout_error(hass):
|
||||||
|
"""Test fetching usage data. Failed the first time because was too get response."""
|
||||||
|
fake_async_add_entities = MagicMock()
|
||||||
|
fake_srp_energy_client = MagicMock()
|
||||||
|
fake_srp_energy_client.usage.return_value = [{1, 2, 3, 1.999, 4}]
|
||||||
|
fake_config = MagicMock(
|
||||||
|
data={
|
||||||
|
"name": "SRP Energy",
|
||||||
|
"is_tou": False,
|
||||||
|
"id": "0123456789",
|
||||||
|
"username": "testuser@example.com",
|
||||||
|
"password": "mypassword",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
hass.data[SRP_ENERGY_DOMAIN] = fake_srp_energy_client
|
||||||
|
fake_srp_energy_client.usage.side_effect = TimeoutError()
|
||||||
|
|
||||||
|
await async_setup_entry(hass, fake_config, fake_async_add_entities)
|
||||||
|
assert not fake_async_add_entities.call_args[0][0][
|
||||||
|
0
|
||||||
|
].coordinator.last_update_success
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_setup_entry_connect_error(hass):
|
||||||
|
"""Test fetching usage data. Failed the first time because was too get response."""
|
||||||
|
fake_async_add_entities = MagicMock()
|
||||||
|
fake_srp_energy_client = MagicMock()
|
||||||
|
fake_srp_energy_client.usage.return_value = [{1, 2, 3, 1.999, 4}]
|
||||||
|
fake_config = MagicMock(
|
||||||
|
data={
|
||||||
|
"name": "SRP Energy",
|
||||||
|
"is_tou": False,
|
||||||
|
"id": "0123456789",
|
||||||
|
"username": "testuser@example.com",
|
||||||
|
"password": "mypassword",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
hass.data[SRP_ENERGY_DOMAIN] = fake_srp_energy_client
|
||||||
|
fake_srp_energy_client.usage.side_effect = ValueError()
|
||||||
|
|
||||||
|
await async_setup_entry(hass, fake_config, fake_async_add_entities)
|
||||||
|
assert not fake_async_add_entities.call_args[0][0][
|
||||||
|
0
|
||||||
|
].coordinator.last_update_success
|
||||||
|
|
||||||
|
|
||||||
|
async def test_srp_entity(hass):
|
||||||
|
"""Test the SrpEntity."""
|
||||||
|
fake_coordinator = MagicMock(data=1.99999999999)
|
||||||
|
srp_entity = SrpEntity(fake_coordinator)
|
||||||
|
|
||||||
|
assert srp_entity is not None
|
||||||
|
assert srp_entity.name == f"{DEFAULT_NAME} {SENSOR_NAME}"
|
||||||
|
assert srp_entity.unique_id == SENSOR_TYPE
|
||||||
|
assert srp_entity.state is None
|
||||||
|
assert srp_entity.unit_of_measurement == ENERGY_KILO_WATT_HOUR
|
||||||
|
assert srp_entity.icon == ICON
|
||||||
|
assert srp_entity.usage == "2.00"
|
||||||
|
assert srp_entity.should_poll is False
|
||||||
|
assert srp_entity.device_state_attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
|
||||||
|
assert srp_entity.available is not None
|
||||||
|
|
||||||
|
await srp_entity.async_added_to_hass()
|
||||||
|
assert srp_entity.state is not None
|
||||||
|
assert fake_coordinator.async_add_listener.called
|
||||||
|
assert not fake_coordinator.async_add_listener.data.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_srp_entity_no_data(hass):
|
||||||
|
"""Test the SrpEntity."""
|
||||||
|
fake_coordinator = MagicMock(data=False)
|
||||||
|
srp_entity = SrpEntity(fake_coordinator)
|
||||||
|
assert srp_entity.device_state_attributes is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_srp_entity_no_coord_data(hass):
|
||||||
|
"""Test the SrpEntity."""
|
||||||
|
fake_coordinator = MagicMock(data=False)
|
||||||
|
srp_entity = SrpEntity(fake_coordinator)
|
||||||
|
|
||||||
|
assert srp_entity.usage is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_srp_entity_async_update(hass):
|
||||||
|
"""Test the SrpEntity."""
|
||||||
|
|
||||||
|
async def async_magic():
|
||||||
|
pass
|
||||||
|
|
||||||
|
MagicMock.__await__ = lambda x: async_magic().__await__()
|
||||||
|
fake_coordinator = MagicMock(data=False)
|
||||||
|
srp_entity = SrpEntity(fake_coordinator)
|
||||||
|
|
||||||
|
await srp_entity.async_update()
|
||||||
|
assert fake_coordinator.async_request_refresh.called
|
Loading…
x
Reference in New Issue
Block a user