Add demand window sensor for amberelectric (#121356)

This commit is contained in:
Xidorn Quan 2024-07-06 20:28:52 +10:00 committed by GitHub
parent be0cf545b2
commit 2bc7904b51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 123 additions and 15 deletions

View file

@ -71,6 +71,18 @@ class AmberPriceSpikeBinarySensor(AmberPriceGridSensor):
} }
class AmberDemandWindowBinarySensor(AmberPriceGridSensor):
"""Sensor to show whether demand window is active."""
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
grid = self.coordinator.data["grid"]
if "demand_window" in grid:
return grid["demand_window"] # type: ignore[no-any-return]
return None
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
@ -83,6 +95,14 @@ async def async_setup_entry(
key="price_spike", key="price_spike",
name=f"{entry.title} - Price Spike", name=f"{entry.title} - Price Spike",
) )
async_add_entities( demand_window_description = BinarySensorEntityDescription(
[AmberPriceSpikeBinarySensor(coordinator, price_spike_description)] key="demand_window",
name=f"{entry.title} - Demand Window",
translation_key="demand_window",
)
async_add_entities(
[
AmberPriceSpikeBinarySensor(coordinator, price_spike_description),
AmberDemandWindowBinarySensor(coordinator, demand_window_description),
]
) )

View file

@ -111,6 +111,9 @@ class AmberUpdateCoordinator(DataUpdateCoordinator):
] ]
result["grid"]["renewables"] = round(general[0].renewables) result["grid"]["renewables"] = round(general[0].renewables)
result["grid"]["price_spike"] = general[0].spike_status.value result["grid"]["price_spike"] = general[0].spike_status.value
tariff_information = general[0].tariff_information
if tariff_information:
result["grid"]["demand_window"] = tariff_information.demand_window
controlled_load = [ controlled_load = [
interval for interval in current if is_controlled_load(interval) interval for interval in current if is_controlled_load(interval)

View file

@ -13,6 +13,14 @@
"renewables": { "renewables": {
"default": "mdi:solar-power" "default": "mdi:solar-power"
} }
},
"binary_sensor": {
"demand_window": {
"default": "mdi:meter-electric",
"state": {
"off": "mdi:meter-electric-outline"
}
}
} }
} }
} }

View file

@ -8,6 +8,7 @@ from unittest.mock import Mock, patch
from amberelectric.model.channel import ChannelType from amberelectric.model.channel import ChannelType
from amberelectric.model.current_interval import CurrentInterval from amberelectric.model.current_interval import CurrentInterval
from amberelectric.model.interval import SpikeStatus from amberelectric.model.interval import SpikeStatus
from amberelectric.model.tariff_information import TariffInformation
from dateutil import parser from dateutil import parser
import pytest import pytest
@ -111,7 +112,7 @@ async def setup_spike(hass: HomeAssistant) -> AsyncGenerator[Mock]:
@pytest.mark.usefixtures("setup_no_spike") @pytest.mark.usefixtures("setup_no_spike")
def test_no_spike_sensor(hass: HomeAssistant) -> None: def test_no_spike_sensor(hass: HomeAssistant) -> None:
"""Testing the creation of the Amber renewables sensor.""" """Testing the creation of the Amber renewables sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
sensor = hass.states.get("binary_sensor.mock_title_price_spike") sensor = hass.states.get("binary_sensor.mock_title_price_spike")
assert sensor assert sensor
assert sensor.state == "off" assert sensor.state == "off"
@ -122,7 +123,7 @@ def test_no_spike_sensor(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("setup_potential_spike") @pytest.mark.usefixtures("setup_potential_spike")
def test_potential_spike_sensor(hass: HomeAssistant) -> None: def test_potential_spike_sensor(hass: HomeAssistant) -> None:
"""Testing the creation of the Amber renewables sensor.""" """Testing the creation of the Amber renewables sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
sensor = hass.states.get("binary_sensor.mock_title_price_spike") sensor = hass.states.get("binary_sensor.mock_title_price_spike")
assert sensor assert sensor
assert sensor.state == "off" assert sensor.state == "off"
@ -133,9 +134,85 @@ def test_potential_spike_sensor(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("setup_spike") @pytest.mark.usefixtures("setup_spike")
def test_spike_sensor(hass: HomeAssistant) -> None: def test_spike_sensor(hass: HomeAssistant) -> None:
"""Testing the creation of the Amber renewables sensor.""" """Testing the creation of the Amber renewables sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
sensor = hass.states.get("binary_sensor.mock_title_price_spike") sensor = hass.states.get("binary_sensor.mock_title_price_spike")
assert sensor assert sensor
assert sensor.state == "on" assert sensor.state == "on"
assert sensor.attributes["icon"] == "mdi:power-plug-off" assert sensor.attributes["icon"] == "mdi:power-plug-off"
assert sensor.attributes["spike_status"] == "spike" assert sensor.attributes["spike_status"] == "spike"
@pytest.fixture
async def setup_inactive_demand_window(hass: HomeAssistant) -> AsyncGenerator[Mock]:
"""Set up general channel."""
MockConfigEntry(
domain="amberelectric",
data={
CONF_SITE_NAME: "mock_title",
CONF_API_TOKEN: MOCK_API_TOKEN,
CONF_SITE_ID: GENERAL_ONLY_SITE_ID,
},
).add_to_hass(hass)
instance = Mock()
with patch(
"amberelectric.api.AmberApi.create",
return_value=instance,
) as mock_update:
general_channel: list[CurrentInterval] = [
generate_current_interval(
ChannelType.GENERAL, parser.parse("2021-09-21T08:30:00+10:00")
),
]
general_channel[0].tariff_information = TariffInformation(demandWindow=False)
instance.get_current_price = Mock(return_value=general_channel)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
yield mock_update.return_value
@pytest.fixture
async def setup_active_demand_window(hass: HomeAssistant) -> AsyncGenerator[Mock]:
"""Set up general channel."""
MockConfigEntry(
domain="amberelectric",
data={
CONF_SITE_NAME: "mock_title",
CONF_API_TOKEN: MOCK_API_TOKEN,
CONF_SITE_ID: GENERAL_ONLY_SITE_ID,
},
).add_to_hass(hass)
instance = Mock()
with patch(
"amberelectric.api.AmberApi.create",
return_value=instance,
) as mock_update:
general_channel: list[CurrentInterval] = [
generate_current_interval(
ChannelType.GENERAL, parser.parse("2021-09-21T08:30:00+10:00")
),
]
general_channel[0].tariff_information = TariffInformation(demandWindow=True)
instance.get_current_price = Mock(return_value=general_channel)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
yield mock_update.return_value
@pytest.mark.usefixtures("setup_inactive_demand_window")
def test_inactive_demand_window_sensor(hass: HomeAssistant) -> None:
"""Testing the creation of the Amber demand_window sensor."""
assert len(hass.states.async_all()) == 6
sensor = hass.states.get("binary_sensor.mock_title_demand_window")
assert sensor
assert sensor.state == "off"
@pytest.mark.usefixtures("setup_active_demand_window")
def test_active_demand_window_sensor(hass: HomeAssistant) -> None:
"""Testing the creation of the Amber demand_window sensor."""
assert len(hass.states.async_all()) == 6
sensor = hass.states.get("binary_sensor.mock_title_demand_window")
assert sensor
assert sensor.state == "on"

View file

@ -105,7 +105,7 @@ async def setup_general_and_feed_in(hass: HomeAssistant) -> AsyncGenerator[Mock]
async def test_general_price_sensor(hass: HomeAssistant, setup_general: Mock) -> None: async def test_general_price_sensor(hass: HomeAssistant, setup_general: Mock) -> None:
"""Test the General Price sensor.""" """Test the General Price sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
price = hass.states.get("sensor.mock_title_general_price") price = hass.states.get("sensor.mock_title_general_price")
assert price assert price
assert price.state == "0.08" assert price.state == "0.08"
@ -143,7 +143,7 @@ async def test_general_price_sensor(hass: HomeAssistant, setup_general: Mock) ->
@pytest.mark.usefixtures("setup_general_and_controlled_load") @pytest.mark.usefixtures("setup_general_and_controlled_load")
async def test_general_and_controlled_load_price_sensor(hass: HomeAssistant) -> None: async def test_general_and_controlled_load_price_sensor(hass: HomeAssistant) -> None:
"""Test the Controlled Price sensor.""" """Test the Controlled Price sensor."""
assert len(hass.states.async_all()) == 8 assert len(hass.states.async_all()) == 9
price = hass.states.get("sensor.mock_title_controlled_load_price") price = hass.states.get("sensor.mock_title_controlled_load_price")
assert price assert price
assert price.state == "0.08" assert price.state == "0.08"
@ -165,7 +165,7 @@ async def test_general_and_controlled_load_price_sensor(hass: HomeAssistant) ->
@pytest.mark.usefixtures("setup_general_and_feed_in") @pytest.mark.usefixtures("setup_general_and_feed_in")
async def test_general_and_feed_in_price_sensor(hass: HomeAssistant) -> None: async def test_general_and_feed_in_price_sensor(hass: HomeAssistant) -> None:
"""Test the Feed In sensor.""" """Test the Feed In sensor."""
assert len(hass.states.async_all()) == 8 assert len(hass.states.async_all()) == 9
price = hass.states.get("sensor.mock_title_feed_in_price") price = hass.states.get("sensor.mock_title_feed_in_price")
assert price assert price
assert price.state == "-0.08" assert price.state == "-0.08"
@ -188,7 +188,7 @@ async def test_general_forecast_sensor(
hass: HomeAssistant, setup_general: Mock hass: HomeAssistant, setup_general: Mock
) -> None: ) -> None:
"""Test the General Forecast sensor.""" """Test the General Forecast sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
price = hass.states.get("sensor.mock_title_general_forecast") price = hass.states.get("sensor.mock_title_general_forecast")
assert price assert price
assert price.state == "0.09" assert price.state == "0.09"
@ -230,7 +230,7 @@ async def test_general_forecast_sensor(
@pytest.mark.usefixtures("setup_general_and_controlled_load") @pytest.mark.usefixtures("setup_general_and_controlled_load")
async def test_controlled_load_forecast_sensor(hass: HomeAssistant) -> None: async def test_controlled_load_forecast_sensor(hass: HomeAssistant) -> None:
"""Test the Controlled Load Forecast sensor.""" """Test the Controlled Load Forecast sensor."""
assert len(hass.states.async_all()) == 8 assert len(hass.states.async_all()) == 9
price = hass.states.get("sensor.mock_title_controlled_load_forecast") price = hass.states.get("sensor.mock_title_controlled_load_forecast")
assert price assert price
assert price.state == "0.09" assert price.state == "0.09"
@ -254,7 +254,7 @@ async def test_controlled_load_forecast_sensor(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("setup_general_and_feed_in") @pytest.mark.usefixtures("setup_general_and_feed_in")
async def test_feed_in_forecast_sensor(hass: HomeAssistant) -> None: async def test_feed_in_forecast_sensor(hass: HomeAssistant) -> None:
"""Test the Feed In Forecast sensor.""" """Test the Feed In Forecast sensor."""
assert len(hass.states.async_all()) == 8 assert len(hass.states.async_all()) == 9
price = hass.states.get("sensor.mock_title_feed_in_forecast") price = hass.states.get("sensor.mock_title_feed_in_forecast")
assert price assert price
assert price.state == "-0.09" assert price.state == "-0.09"
@ -278,7 +278,7 @@ async def test_feed_in_forecast_sensor(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("setup_general") @pytest.mark.usefixtures("setup_general")
def test_renewable_sensor(hass: HomeAssistant) -> None: def test_renewable_sensor(hass: HomeAssistant) -> None:
"""Testing the creation of the Amber renewables sensor.""" """Testing the creation of the Amber renewables sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
sensor = hass.states.get("sensor.mock_title_renewables") sensor = hass.states.get("sensor.mock_title_renewables")
assert sensor assert sensor
assert sensor.state == "51" assert sensor.state == "51"
@ -287,7 +287,7 @@ def test_renewable_sensor(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("setup_general") @pytest.mark.usefixtures("setup_general")
def test_general_price_descriptor_descriptor_sensor(hass: HomeAssistant) -> None: def test_general_price_descriptor_descriptor_sensor(hass: HomeAssistant) -> None:
"""Test the General Price Descriptor sensor.""" """Test the General Price Descriptor sensor."""
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 6
price = hass.states.get("sensor.mock_title_general_price_descriptor") price = hass.states.get("sensor.mock_title_general_price_descriptor")
assert price assert price
assert price.state == "extremely_low" assert price.state == "extremely_low"
@ -298,7 +298,7 @@ def test_general_and_controlled_load_price_descriptor_sensor(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:
"""Test the Controlled Price Descriptor sensor.""" """Test the Controlled Price Descriptor sensor."""
assert len(hass.states.async_all()) == 8 assert len(hass.states.async_all()) == 9
price = hass.states.get("sensor.mock_title_controlled_load_price_descriptor") price = hass.states.get("sensor.mock_title_controlled_load_price_descriptor")
assert price assert price
assert price.state == "extremely_low" assert price.state == "extremely_low"
@ -307,7 +307,7 @@ def test_general_and_controlled_load_price_descriptor_sensor(
@pytest.mark.usefixtures("setup_general_and_feed_in") @pytest.mark.usefixtures("setup_general_and_feed_in")
def test_general_and_feed_in_price_descriptor_sensor(hass: HomeAssistant) -> None: def test_general_and_feed_in_price_descriptor_sensor(hass: HomeAssistant) -> None:
"""Test the Feed In Price Descriptor sensor.""" """Test the Feed In Price Descriptor sensor."""
assert len(hass.states.async_all()) == 8 assert len(hass.states.async_all()) == 9
price = hass.states.get("sensor.mock_title_feed_in_price_descriptor") price = hass.states.get("sensor.mock_title_feed_in_price_descriptor")
assert price assert price
assert price.state == "extremely_low" assert price.state == "extremely_low"