Amberelectric (#56448)
* Add Amber Electric integration * Linting * Fixing some type hinting * Adding docstrings * Removing files that shouldn't have been changed * Splitting out test helpers * Testing the price sensor * Testing Controlled load and feed in channels * Refactoring mocks * switching state for native_value and unit_of_measurement for native_unit_of_measurement * Fixing docstrings * Fixing requiremennts_all.txt * isort fixes * Fixing pylint errors * Omitting __init__.py from test coverage * Add missing config_flow tests * Adding more sensor tests * Applying suggested changes to __init.py__ * Refactor coordinator to return the data object with all of the relevent data already setup * Another coordinator refactor - Better use the dictionary for when we build the sensors * Removing first function * Refactoring sensor files to use entity descriptions, remove factory * Rounding renewable percentage, return icons correctly * Cleaning up translation strings * Fixing relative path, removing TODO * Coordintator tests now accept new (more accurate) fixtures * Using a description placeholder * Putting missing translations strings back in * tighten up the no site error logic - self._site_id should never be None at the point of loading async_step_site * Removing DEVICE_CLASS, replacing the units with AUD/kWh * Settings _attr_unique_id * Removing icon function (it's already the default) * Apply suggestions from code review Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * Adding strings.json * Tighter wrapping for try/except * Generating translations * Removing update_method - not needed as it's being overriden * Apply suggestions from code review Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * Fixing tests * Add missing description placeholder * Fix warning * changing name from update to update_data to match async_update_data * renaming [async_]update_data => [async_]update_price_data to avoid confusion * Creating too man renewable sensors * Override update method * Coordinator tests use _async_update_data * Using $/kWh as the units * Using isinstance instead of __class__ test. Removing a zero len check * Asserting self._sites in second step * Linting * Remove useless tests Co-authored-by: jan iversen <jancasacondor@gmail.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
f93539ef4c
commit
412ecacca3
17 changed files with 1326 additions and 0 deletions
110
homeassistant/components/amberelectric/coordinator.py
Normal file
110
homeassistant/components/amberelectric/coordinator.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
"""Amber Electric Coordinator."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from amberelectric import ApiException
|
||||
from amberelectric.api import amber_api
|
||||
from amberelectric.model.actual_interval import ActualInterval
|
||||
from amberelectric.model.channel import ChannelType
|
||||
from amberelectric.model.current_interval import CurrentInterval
|
||||
from amberelectric.model.forecast_interval import ForecastInterval
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import LOGGER
|
||||
|
||||
|
||||
def is_current(interval: ActualInterval | CurrentInterval | ForecastInterval) -> bool:
|
||||
"""Return true if the supplied interval is a CurrentInterval."""
|
||||
return isinstance(interval, CurrentInterval)
|
||||
|
||||
|
||||
def is_forecast(interval: ActualInterval | CurrentInterval | ForecastInterval) -> bool:
|
||||
"""Return true if the supplied interval is a ForecastInterval."""
|
||||
return isinstance(interval, ForecastInterval)
|
||||
|
||||
|
||||
def is_general(interval: ActualInterval | CurrentInterval | ForecastInterval) -> bool:
|
||||
"""Return true if the supplied interval is on the general channel."""
|
||||
return interval.channel_type == ChannelType.GENERAL
|
||||
|
||||
|
||||
def is_controlled_load(
|
||||
interval: ActualInterval | CurrentInterval | ForecastInterval,
|
||||
) -> bool:
|
||||
"""Return true if the supplied interval is on the controlled load channel."""
|
||||
return interval.channel_type == ChannelType.CONTROLLED_LOAD
|
||||
|
||||
|
||||
def is_feed_in(interval: ActualInterval | CurrentInterval | ForecastInterval) -> bool:
|
||||
"""Return true if the supplied interval is on the feed in channel."""
|
||||
return interval.channel_type == ChannelType.FEED_IN
|
||||
|
||||
|
||||
class AmberUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""AmberUpdateCoordinator - In charge of downloading the data for a site, which all the sensors read."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: amber_api.AmberApi, site_id: str
|
||||
) -> None:
|
||||
"""Initialise the data service."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name="amberelectric",
|
||||
update_interval=timedelta(minutes=1),
|
||||
)
|
||||
self._api = api
|
||||
self.site_id = site_id
|
||||
|
||||
def update_price_data(self) -> dict[str, dict[str, Any]]:
|
||||
"""Update callback."""
|
||||
|
||||
result: dict[str, dict[str, Any]] = {
|
||||
"current": {},
|
||||
"forecasts": {},
|
||||
"grid": {},
|
||||
}
|
||||
try:
|
||||
data = self._api.get_current_price(self.site_id, next=48)
|
||||
except ApiException as api_exception:
|
||||
raise UpdateFailed("Missing price data, skipping update") from api_exception
|
||||
|
||||
current = [interval for interval in data if is_current(interval)]
|
||||
forecasts = [interval for interval in data if is_forecast(interval)]
|
||||
general = [interval for interval in current if is_general(interval)]
|
||||
|
||||
if len(general) == 0:
|
||||
raise UpdateFailed("No general channel configured")
|
||||
|
||||
result["current"]["general"] = general[0]
|
||||
result["forecasts"]["general"] = [
|
||||
interval for interval in forecasts if is_general(interval)
|
||||
]
|
||||
result["grid"]["renewables"] = round(general[0].renewables)
|
||||
|
||||
controlled_load = [
|
||||
interval for interval in current if is_controlled_load(interval)
|
||||
]
|
||||
if controlled_load:
|
||||
result["current"]["controlled_load"] = controlled_load[0]
|
||||
result["forecasts"]["controlled_load"] = [
|
||||
interval for interval in forecasts if is_controlled_load(interval)
|
||||
]
|
||||
|
||||
feed_in = [interval for interval in current if is_feed_in(interval)]
|
||||
if feed_in:
|
||||
result["current"]["feed_in"] = feed_in[0]
|
||||
result["forecasts"]["feed_in"] = [
|
||||
interval for interval in forecasts if is_feed_in(interval)
|
||||
]
|
||||
|
||||
LOGGER.debug("Fetched new Amber data: %s", data)
|
||||
return result
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Async update wrapper."""
|
||||
return await self.hass.async_add_executor_job(self.update_price_data)
|
Loading…
Add table
Add a link
Reference in a new issue