Add min/max price sensor to Nord Pool (#133534)
* Add min/max price sensor to Nord Pool * Last fixes * Make link in strings * Replace funcpull/132866/head
parent
26212798a3
commit
ad34bc8910
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Reference in New Issue