Add min/max price sensor to Nord Pool (#133534)

* Add min/max price sensor to Nord Pool

* Last fixes

* Make link in strings

* Replace func
pull/132866/head
G Johansson 2024-12-20 08:26:36 +01:00 committed by GitHub
parent 26212798a3
commit ad34bc8910
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 298 additions and 7 deletions

View File

@ -27,6 +27,20 @@ from .entity import NordpoolBaseEntity
PARALLEL_UPDATES = 0
def validate_prices(
func: Callable[
[DeliveryPeriodData], dict[str, tuple[float | None, float, float | None]]
],
data: DeliveryPeriodData,
area: str,
index: int,
) -> float | None:
"""Validate and return."""
if result := func(data)[area][index]:
return result / 1000
return None
def get_prices(
data: DeliveryPeriodData,
) -> dict[str, tuple[float | None, float, float | None]]:
@ -67,6 +81,26 @@ def get_prices(
return result
def get_min_max_price(
data: DeliveryPeriodData,
area: str,
func: Callable[[float, float], float],
) -> tuple[float, datetime, datetime]:
"""Get the lowest price from the data."""
price_data = data.entries
price: float = price_data[0].entry[area]
start: datetime = price_data[0].start
end: datetime = price_data[0].end
for entry in price_data:
for _area, _price in entry.entry.items():
if _area == area and _price == func(price, _price):
price = _price
start = entry.start
end = entry.end
return (price, start, end)
def get_blockprices(
data: DeliveryPeriodData,
) -> dict[str, dict[str, tuple[datetime, datetime, float, float, float]]]:
@ -103,7 +137,8 @@ class NordpoolDefaultSensorEntityDescription(SensorEntityDescription):
class NordpoolPricesSensorEntityDescription(SensorEntityDescription):
"""Describes Nord Pool prices sensor entity."""
value_fn: Callable[[tuple[float | None, float, float | None]], float | None]
value_fn: Callable[[DeliveryPeriodData, str], float | None]
extra_fn: Callable[[DeliveryPeriodData, str], dict[str, str] | None]
@dataclass(frozen=True, kw_only=True)
@ -142,20 +177,43 @@ PRICES_SENSOR_TYPES: tuple[NordpoolPricesSensorEntityDescription, ...] = (
NordpoolPricesSensorEntityDescription(
key="current_price",
translation_key="current_price",
value_fn=lambda data: data[1] / 1000,
value_fn=lambda data, area: validate_prices(get_prices, data, area, 1),
extra_fn=lambda data, area: None,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2,
),
NordpoolPricesSensorEntityDescription(
key="last_price",
translation_key="last_price",
value_fn=lambda data: data[0] / 1000 if data[0] else None,
value_fn=lambda data, area: validate_prices(get_prices, data, area, 0),
extra_fn=lambda data, area: None,
suggested_display_precision=2,
),
NordpoolPricesSensorEntityDescription(
key="next_price",
translation_key="next_price",
value_fn=lambda data: data[2] / 1000 if data[2] else None,
value_fn=lambda data, area: validate_prices(get_prices, data, area, 2),
extra_fn=lambda data, area: None,
suggested_display_precision=2,
),
NordpoolPricesSensorEntityDescription(
key="lowest_price",
translation_key="lowest_price",
value_fn=lambda data, area: get_min_max_price(data, area, min)[0] / 1000,
extra_fn=lambda data, area: {
"start": get_min_max_price(data, area, min)[1].isoformat(),
"end": get_min_max_price(data, area, min)[2].isoformat(),
},
suggested_display_precision=2,
),
NordpoolPricesSensorEntityDescription(
key="highest_price",
translation_key="highest_price",
value_fn=lambda data, area: get_min_max_price(data, area, max)[0] / 1000,
extra_fn=lambda data, area: {
"start": get_min_max_price(data, area, max)[1].isoformat(),
"end": get_min_max_price(data, area, max)[2].isoformat(),
},
suggested_display_precision=2,
),
)
@ -285,9 +343,12 @@ class NordpoolPriceSensor(NordpoolBaseEntity, SensorEntity):
@property
def native_value(self) -> float | None:
"""Return value of sensor."""
return self.entity_description.value_fn(
get_prices(self.coordinator.data)[self.area]
)
return self.entity_description.value_fn(self.coordinator.data, self.area)
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the extra state attributes."""
return self.entity_description.extra_fn(self.coordinator.data, self.area)
class NordpoolBlockPriceSensor(NordpoolBaseEntity, SensorEntity):

View File

@ -50,6 +50,28 @@
"next_price": {
"name": "Next price"
},
"lowest_price": {
"name": "Lowest price",
"state_attributes": {
"start": {
"name": "Start time"
},
"end": {
"name": "End time"
}
}
},
"highest_price": {
"name": "Highest price",
"state_attributes": {
"start": {
"name": "[%key:component::nordpool::entity::sensor::lowest_price::state_attributes::start::name%]"
},
"end": {
"name": "[%key:component::nordpool::entity::sensor::lowest_price::state_attributes::end::name%]"
}
}
},
"block_average": {
"name": "{block} average"
},

View File

@ -200,6 +200,58 @@
'state': '11.6402',
})
# ---
# name: test_sensor[sensor.nord_pool_se3_highest_price-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.nord_pool_se3_highest_price',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Highest price',
'platform': 'nordpool',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'highest_price',
'unique_id': 'SE3-highest_price',
'unit_of_measurement': 'SEK/kWh',
})
# ---
# name: test_sensor[sensor.nord_pool_se3_highest_price-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'end': '2024-11-05T17:00:00+00:00',
'friendly_name': 'Nord Pool SE3 Highest price',
'start': '2024-11-05T16:00:00+00:00',
'unit_of_measurement': 'SEK/kWh',
}),
'context': <ANY>,
'entity_id': 'sensor.nord_pool_se3_highest_price',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2.51265',
})
# ---
# name: test_sensor[sensor.nord_pool_se3_last_updated-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -247,6 +299,58 @@
'state': '2024-11-04T12:15:03+00:00',
})
# ---
# name: test_sensor[sensor.nord_pool_se3_lowest_price-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.nord_pool_se3_lowest_price',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Lowest price',
'platform': 'nordpool',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'lowest_price',
'unique_id': 'SE3-lowest_price',
'unit_of_measurement': 'SEK/kWh',
})
# ---
# name: test_sensor[sensor.nord_pool_se3_lowest_price-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'end': '2024-11-05T03:00:00+00:00',
'friendly_name': 'Nord Pool SE3 Lowest price',
'start': '2024-11-05T02:00:00+00:00',
'unit_of_measurement': 'SEK/kWh',
}),
'context': <ANY>,
'entity_id': 'sensor.nord_pool_se3_lowest_price',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.06169',
})
# ---
# name: test_sensor[sensor.nord_pool_se3_next_price-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -1307,6 +1411,58 @@
'state': '11.6402',
})
# ---
# name: test_sensor[sensor.nord_pool_se4_highest_price-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.nord_pool_se4_highest_price',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Highest price',
'platform': 'nordpool',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'highest_price',
'unique_id': 'SE4-highest_price',
'unit_of_measurement': 'SEK/kWh',
})
# ---
# name: test_sensor[sensor.nord_pool_se4_highest_price-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'end': '2024-11-05T17:00:00+00:00',
'friendly_name': 'Nord Pool SE4 Highest price',
'start': '2024-11-05T16:00:00+00:00',
'unit_of_measurement': 'SEK/kWh',
}),
'context': <ANY>,
'entity_id': 'sensor.nord_pool_se4_highest_price',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '3.53303',
})
# ---
# name: test_sensor[sensor.nord_pool_se4_last_updated-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -1354,6 +1510,58 @@
'state': '2024-11-04T12:15:03+00:00',
})
# ---
# name: test_sensor[sensor.nord_pool_se4_lowest_price-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.nord_pool_se4_lowest_price',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Lowest price',
'platform': 'nordpool',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'lowest_price',
'unique_id': 'SE4-lowest_price',
'unit_of_measurement': 'SEK/kWh',
})
# ---
# name: test_sensor[sensor.nord_pool_se4_lowest_price-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'end': '2024-11-05T03:00:00+00:00',
'friendly_name': 'Nord Pool SE4 Lowest price',
'start': '2024-11-05T02:00:00+00:00',
'unit_of_measurement': 'SEK/kWh',
}),
'context': <ANY>,
'entity_id': 'sensor.nord_pool_se4_lowest_price',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.06519',
})
# ---
# name: test_sensor[sensor.nord_pool_se4_next_price-entry]
EntityRegistryEntrySnapshot({
'aliases': set({