Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions homeassistant/components/nordpool/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Binary sensor platform for Nord Pool integration."""

from collections.abc import Callable
from dataclasses import dataclass

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.components.sensor import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import NordPoolConfigEntry
from .const import CONF_AREAS
from .coordinator import NordPoolDataUpdateCoordinator
from .entity import NordpoolBaseEntity

PARALLEL_UPDATES = 0


def get_tomorrow_price_available(
entity: NordpoolPriceBinarySensor,
) -> bool:
"""Return tomorrow price availability.

Output: True or False
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Output: True or False

It's a bool return type that is obvious

"""
data = entity.coordinator.get_data_tomorrow()
if data and data.entries and entity.area in data.entries[0].entry:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if data and data.entries and entity.area in data.entries[0].entry:
return (data and data.entries and entity.area in data.entries[0].entry)

A bit more concise imo

return True
return False


@dataclass(frozen=True, kw_only=True)
class NordpoolBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes Nord Pool binary sensor entity."""

value_fn: Callable[[NordpoolPriceBinarySensor], bool | None]


BINARY_SENSOR_TYPES: tuple[NordpoolBinarySensorEntityDescription, ...] = (
NordpoolBinarySensorEntityDescription(
key="tomorrow_price_available",
translation_key="tomorrow_price_available",
value_fn=get_tomorrow_price_available,
entity_category=EntityCategory.DIAGNOSTIC,
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: NordPoolConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Nord Pool binary sensor platform."""

coordinator = entry.runtime_data
areas = coordinator.config_entry.data[CONF_AREAS]

async_add_entities(
NordpoolPriceBinarySensor(coordinator, description, area)
for description in BINARY_SENSOR_TYPES
for area in areas
)


class NordpoolPriceBinarySensor(NordpoolBaseEntity, BinarySensorEntity):
"""Representation of a Nord Pool binary sensor."""

entity_description: NordpoolBinarySensorEntityDescription

def __init__(
self,
coordinator: NordPoolDataUpdateCoordinator,
entity_description: NordpoolBinarySensorEntityDescription,
area: str,
) -> None:
"""Initiate Nord Pool binary sensor."""
super().__init__(coordinator, entity_description, area)

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.entity_description.value_fn(self)
2 changes: 1 addition & 1 deletion homeassistant/components/nordpool/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

DEFAULT_SCAN_INTERVAL = 60
DOMAIN = "nordpool"
PLATFORMS = [Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
DEFAULT_NAME = "Nord Pool"

CONF_AREAS = "areas"
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/nordpool/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,8 @@ def get_data_current_day(self) -> DeliveryPeriodData:
"""Return the current day data."""
current_day = dt_util.now().date()
return self.data.entries[current_day]

def get_data_tomorrow(self) -> DeliveryPeriodData | None:
"""Return tomorrow's day data if available."""
tomorrow = dt_util.now().date() + timedelta(days=1)
return self.data.entries.get(tomorrow)
5 changes: 5 additions & 0 deletions homeassistant/components/nordpool/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
}
},
"entity": {
"binary_sensor": {
"tomorrow_price_available": {
"name": "Tomorrow price available"
}
},
"sensor": {
"block_average": {
"name": "{block} average"
Expand Down
22 changes: 17 additions & 5 deletions tests/components/nordpool/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@
from pynordpool import API, NordPoolClient
import pytest

from homeassistant.components.nordpool.const import DOMAIN
from homeassistant.components.nordpool.const import DOMAIN, PLATFORMS
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from . import ENTRY_CONFIG

from tests.common import MockConfigEntry, load_fixture
from tests.common import MockConfigEntry, load_fixture, patch
from tests.test_util.aiohttp import AiohttpClientMocker


@pytest.fixture(name="load_platforms")
async def patch_platform_constant() -> list[Platform]:
"""Return list of platforms to load."""
return PLATFORMS


@pytest.fixture
async def load_int(hass: HomeAssistant, get_client: NordPoolClient) -> MockConfigEntry:
async def load_int(
hass: HomeAssistant,
get_client: NordPoolClient,
load_platforms: list[Platform],
) -> MockConfigEntry:
"""Set up the Nord Pool integration in Home Assistant."""
config_entry = MockConfigEntry(
domain=DOMAIN,
Expand All @@ -28,8 +39,9 @@ async def load_int(hass: HomeAssistant, get_client: NordPoolClient) -> MockConfi

config_entry.add_to_hass(hass)

await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
with patch("homeassistant.components.nordpool.PLATFORMS", load_platforms):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

return config_entry

Expand Down
201 changes: 201 additions & 0 deletions tests/components/nordpool/snapshots/test_binary_sensor.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# serializer version: 1
# name: test_binary_sensor_off[load_platforms0][binary_sensor.nord_pool_se3_tomorrow_price_available-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.nord_pool_se3_tomorrow_price_available',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Tomorrow price available',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Tomorrow price available',
'platform': 'nordpool',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'tomorrow_price_available',
'unique_id': 'SE3-tomorrow_price_available',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensor_off[load_platforms0][binary_sensor.nord_pool_se3_tomorrow_price_available-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Nord Pool SE3 Tomorrow price available',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nord_pool_se3_tomorrow_price_available',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensor_off[load_platforms0][binary_sensor.nord_pool_se4_tomorrow_price_available-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.nord_pool_se4_tomorrow_price_available',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Tomorrow price available',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Tomorrow price available',
'platform': 'nordpool',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'tomorrow_price_available',
'unique_id': 'SE4-tomorrow_price_available',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensor_off[load_platforms0][binary_sensor.nord_pool_se4_tomorrow_price_available-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Nord Pool SE4 Tomorrow price available',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nord_pool_se4_tomorrow_price_available',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensor_on[load_platforms0][binary_sensor.nord_pool_se3_tomorrow_price_available-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.nord_pool_se3_tomorrow_price_available',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Tomorrow price available',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Tomorrow price available',
'platform': 'nordpool',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'tomorrow_price_available',
'unique_id': 'SE3-tomorrow_price_available',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensor_on[load_platforms0][binary_sensor.nord_pool_se3_tomorrow_price_available-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Nord Pool SE3 Tomorrow price available',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nord_pool_se3_tomorrow_price_available',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_binary_sensor_on[load_platforms0][binary_sensor.nord_pool_se4_tomorrow_price_available-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.nord_pool_se4_tomorrow_price_available',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Tomorrow price available',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Tomorrow price available',
'platform': 'nordpool',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'tomorrow_price_available',
'unique_id': 'SE4-tomorrow_price_available',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensor_on[load_platforms0][binary_sensor.nord_pool_se4_tomorrow_price_available-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Nord Pool SE4 Tomorrow price available',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nord_pool_se4_tomorrow_price_available',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
Loading