From df9483f4b5b609cbdbb04bfc00f9bfb482ceb9df Mon Sep 17 00:00:00 2001 From: mampfes Date: Mon, 8 Jan 2024 20:06:46 +0100 Subject: [PATCH] move epex_spot_sensor to own repo Because otherwise HACS won't install it correctly. --- custom_components/epex_spot/manifest.json | 2 +- .../epex_spot_sensor/__init__.py | 27 -- .../epex_spot_sensor/binary_sensor.py | 339 ------------------ .../epex_spot_sensor/config_flow.py | 85 ----- custom_components/epex_spot_sensor/const.py | 36 -- .../epex_spot_sensor/contiguous_interval.py | 135 ------- .../epex_spot_sensor/epex_spor_sensor | 1 - .../epex_spot_sensor/intermittent_interval.py | 107 ------ .../epex_spot_sensor/manifest.json | 11 - .../epex_spot_sensor/strings.json | 26 -- .../epex_spot_sensor/translations/en.json | 59 --- custom_components/epex_spot_sensor/util.py | 51 --- 12 files changed, 1 insertion(+), 878 deletions(-) delete mode 100644 custom_components/epex_spot_sensor/__init__.py delete mode 100644 custom_components/epex_spot_sensor/binary_sensor.py delete mode 100644 custom_components/epex_spot_sensor/config_flow.py delete mode 100644 custom_components/epex_spot_sensor/const.py delete mode 100644 custom_components/epex_spot_sensor/contiguous_interval.py delete mode 120000 custom_components/epex_spot_sensor/epex_spor_sensor delete mode 100644 custom_components/epex_spot_sensor/intermittent_interval.py delete mode 100644 custom_components/epex_spot_sensor/manifest.json delete mode 100644 custom_components/epex_spot_sensor/strings.json delete mode 100644 custom_components/epex_spot_sensor/translations/en.json delete mode 100644 custom_components/epex_spot_sensor/util.py diff --git a/custom_components/epex_spot/manifest.json b/custom_components/epex_spot/manifest.json index cdae303..4e0d934 100644 --- a/custom_components/epex_spot/manifest.json +++ b/custom_components/epex_spot/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/mampfes/ha_epex_spot/issues", "requirements": ["bs4"], - "version": "2.3.0b0" + "version": "2.3.0" } diff --git a/custom_components/epex_spot_sensor/__init__.py b/custom_components/epex_spot_sensor/__init__.py deleted file mode 100644 index 8562a32..0000000 --- a/custom_components/epex_spot_sensor/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -"""The EPEX Spot Sensor component.""" -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant - - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up component from a config entry.""" - await hass.config_entries.async_forward_entry_setups( - entry, (Platform.BINARY_SENSOR,) - ) - - entry.async_on_unload(entry.add_update_listener(config_entry_update_listener)) - - return True - - -async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update listener, called when the config entry options are changed.""" - await hass.config_entries.async_reload(entry.entry_id) - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Unload a config entry.""" - return await hass.config_entries.async_unload_platforms( - entry, (Platform.BINARY_SENSOR,) - ) diff --git a/custom_components/epex_spot_sensor/binary_sensor.py b/custom_components/epex_spot_sensor/binary_sensor.py deleted file mode 100644 index 522a13c..0000000 --- a/custom_components/epex_spot_sensor/binary_sensor.py +++ /dev/null @@ -1,339 +0,0 @@ -"""Support for monitoring if a sensor value is below/above a threshold.""" -from __future__ import annotations - -import logging -from typing import Any - -from datetime import time, timedelta, datetime - -import homeassistant.util.dt as dt_util -from homeassistant.components.binary_sensor import ( - BinarySensorEntity, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_ENTITY_ID, -) -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import ( - config_validation as cv, - device_registry as dr, - entity_registry as er, -) -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import ( - EventStateChangedData, - async_track_state_change_event, - async_track_time_change, -) -from homeassistant.helpers.typing import EventType - -from .const import ( - ATTR_DATA, - ATTR_RANK, - ATTR_INTERVAL_ENABLED, - ATTR_START_TIME, - ATTR_END_TIME, - PriceModes, - IntervalModes, - CONF_EARLIEST_START_TIME, - CONF_LATEST_END_TIME, - CONF_DURATION, - CONF_PRICE_MODE, - CONF_INTERVAL_MODE, -) -from .util import ( - get_marketdata_from_sensor_attrs, -) -from .intermittent_interval import ( - calc_intervals_for_intermittent, - is_now_in_intervals, -) -from .contiguous_interval import calc_interval_for_contiguous - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Initialize threshold config entry.""" - registry = er.async_get(hass) - entity_id = er.async_validate_entity_id( - registry, config_entry.options[CONF_ENTITY_ID] - ) - - source_entity = registry.async_get(entity_id) - dev_reg = dr.async_get(hass) - # Resolve source entity device - if ( - (source_entity is not None) - and (source_entity.device_id is not None) - and ( - ( - device := dev_reg.async_get( - device_id=source_entity.device_id, - ) - ) - is not None - ) - ): - device_info = DeviceInfo( - identifiers=device.identifiers, - connections=device.connections, - ) - else: - device_info = None - - async_add_entities( - [ - BinarySensor( - hass, - unique_id=config_entry.entry_id, - name=config_entry.title, - entity_id=entity_id, - earliest_start_time=config_entry.options[CONF_EARLIEST_START_TIME], - latest_end_time=config_entry.options[CONF_LATEST_END_TIME], - duration=config_entry.options[CONF_DURATION], - interval_mode=config_entry.options[CONF_INTERVAL_MODE], - price_mode=config_entry.options[CONF_PRICE_MODE], - device_info=device_info, - ) - ] - ) - - -class BinarySensor(BinarySensorEntity): - """Representation of a EPEX Spot binary sensor.""" - - _attr_should_poll = False - - def __init__( - self, - hass: HomeAssistant, - unique_id: str, - name: str, - entity_id: str, - earliest_start_time: time, - latest_end_time: time, - duration: timedelta, - interval_mode: str, - price_mode: str, - device_info: DeviceInfo | None = None, - ) -> None: - """Initialize the EPEX Spot binary sensor.""" - self._attr_unique_id = unique_id - self._attr_device_info = device_info - self._attr_name = name - - # configuration options - self._entity_id = entity_id - self._earliest_start_time = cv.time(earliest_start_time) - self._latest_end_time = cv.time(latest_end_time) - self._duration = cv.time_period_dict(duration) - self._price_mode = price_mode - self._interval_mode = interval_mode - - # price sensor values - self._sensor_attributes = None - - # calculated values - self.sensor_value: float | None = None # TODO: remove - self._interval_enabled: bool = False - self._state: bool | None = None - self._intervals = [] - - def _on_price_sensor_state_update() -> None: - """Handle sensor state changes.""" - - # set to unavailable by default - self._sensor_attributes = None - self._state = None - - if (new_state := hass.states.get(self._entity_id)) is None: - # _LOGGER.warning(f"Can't get states of {self._entity_id}") - return - - try: - self._sensor_attributes = new_state.attributes - except (ValueError, TypeError): - _LOGGER.warning(f"Can't get attributes of {self._entity_id}") - return - - self._update_state() - - @callback - def async_price_sensor_state_listener( - event: EventType[EventStateChangedData], - ) -> None: - """Handle sensor state changes.""" - _on_price_sensor_state_update() - self.async_write_ha_state() - - self.async_on_remove( - async_track_state_change_event( - hass, [entity_id], async_price_sensor_state_listener - ) - ) - - # check every minute for new states - self.async_on_remove( - async_track_time_change(hass, async_price_sensor_state_listener, second=0) - ) - _on_price_sensor_state_update() - - @property - def is_available(self) -> bool | None: - """Return true if sensor is available.""" - return self._state is not None - - @property - def is_on(self) -> bool | None: - """Return true if sensor is on.""" - return self._state - - @property - def extra_state_attributes(self) -> dict[str, Any]: - """Return the state attributes of the sensor.""" - return { - ATTR_ENTITY_ID: self._entity_id, - CONF_EARLIEST_START_TIME: self._earliest_start_time, - CONF_LATEST_END_TIME: self._latest_end_time, - CONF_DURATION: str(self._duration), - CONF_PRICE_MODE: self._price_mode, - CONF_INTERVAL_MODE: self._interval_mode, - ATTR_INTERVAL_ENABLED: self._interval_enabled, - ATTR_DATA: self._intervals, - } - - @callback - def _update_state(self) -> None: - now = dt_util.now() - now_time = now.time() - - # earliest_start always refers to today - earliest_start = datetime.combine(now, self._earliest_start_time, now.tzinfo) - - # latest_end may refer to today or tomorrow - latest_end = datetime.combine(now, self._latest_end_time, now.tzinfo) - - if self._latest_end_time > self._earliest_start_time: - # start and end refer to the same day (which is today for now) - self._interval_enabled = ( - self._earliest_start_time <= now_time < self._latest_end_time - ) - else: - # start refers to today, end refers to tomorrow - # -> there are 2 intervals: from start to midnight and from midnight to end - self._interval_enabled = ( - self._earliest_start_time <= now_time - or now_time < self._latest_end_time - ) - latest_end += timedelta(days=1) - - if self._interval_mode == IntervalModes.INTERMITTENT.value: - self._update_state_for_intermittent(earliest_start, latest_end, now) - elif self._interval_mode == IntervalModes.CONTIGUOUS.value: - self._update_state_for_contigous(earliest_start, latest_end, now) - else: - _LOGGER.error(f"invalid interval mode: {self._interval_mode}") - - def _update_state_for_intermittent( - self, earliest_start: time, latest_end: time, now: datetime - ): - marketdata = get_marketdata_from_sensor_attrs(self._sensor_attributes) - - intervals = calc_intervals_for_intermittent( - marketdata=marketdata, - earliest_start=earliest_start, - latest_end=latest_end, - duration=self._duration, - most_expensive=self._price_mode == PriceModes.MOST_EXPENSIVE.value, - ) - - if intervals is None: - return - - self._state = is_now_in_intervals(now, intervals) - - # try to calculate intervals for next day also - earliest_start += timedelta(days=1) - if earliest_start >= latest_end: - # do calculation only if latest_end is limited to 24h from earliest_start, - # --> avoid calculation if latest_end includes all available marketdata - latest_end += timedelta(days=1) - intervals2 = calc_intervals_for_intermittent( - marketdata=marketdata, - earliest_start=earliest_start, - latest_end=latest_end, - duration=self._duration, - most_expensive=self._price_mode == PriceModes.MOST_EXPENSIVE.value, - ) - - if intervals2 is not None: - intervals = [*intervals, *intervals2] - - self._intervals = [ - { - ATTR_START_TIME: dt_util.as_local(e.start_time).isoformat(), - ATTR_END_TIME: dt_util.as_local(e.end_time).isoformat(), - ATTR_RANK: e.rank, - # ATTR_PRICE_PER_MWH: e.price_eur_per_mwh, - # ATTR_PRICE_PER_KWH: e.price_eur_per_mwh / 10, - } - for e in sorted(intervals, key=lambda e: e.start_time) - ] - - def _update_state_for_contigous( - self, earliest_start: time, latest_end: time, now: datetime - ): - marketdata = get_marketdata_from_sensor_attrs(self._sensor_attributes) - - result = calc_interval_for_contiguous( - marketdata, - earliest_start=earliest_start, - latest_end=latest_end, - duration=self._duration, - most_expensive=self._price_mode == PriceModes.MOST_EXPENSIVE.value, - ) - - if result is None: - return - - self._state = result["start"] <= now < result["end"] - - self._intervals = [ - { - ATTR_START_TIME: dt_util.as_local(result["start"]).isoformat(), - ATTR_END_TIME: dt_util.as_local(result["end"]).isoformat(), - # "interval_price": result["interval_price"], - } - ] - - # try to calculate intervals for next day also - earliest_start += timedelta(days=1) - if earliest_start >= latest_end: - # do calculation only if latest_end is limited to 24h from earliest_start, - # --> avoid calculation if latest_end includes all available marketdata - latest_end += timedelta(days=1) - result = calc_interval_for_contiguous( - marketdata, - earliest_start=earliest_start, - latest_end=latest_end, - duration=self._duration, - most_expensive=self._price_mode == PriceModes.MOST_EXPENSIVE.value, - ) - - if result is None: - return - - self._intervals.append( - { - ATTR_START_TIME: dt_util.as_local(result["start"]).isoformat(), - ATTR_END_TIME: dt_util.as_local(result["end"]).isoformat(), - } - ) diff --git a/custom_components/epex_spot_sensor/config_flow.py b/custom_components/epex_spot_sensor/config_flow.py deleted file mode 100644 index 9dbd9b5..0000000 --- a/custom_components/epex_spot_sensor/config_flow.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Config flow for EPEX Spot Sensor component.""" -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any - -import voluptuous as vol - -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import CONF_ENTITY_ID, CONF_NAME -from homeassistant.helpers import selector -from homeassistant.helpers.schema_config_entry_flow import ( - SchemaConfigFlowHandler, - SchemaFlowFormStep, -) - -from .const import ( - PriceModes, - IntervalModes, - CONF_EARLIEST_START_TIME, - CONF_LATEST_END_TIME, - CONF_INTERVAL_MODE, - CONF_PRICE_MODE, - CONF_DURATION, - DOMAIN, -) - - -OPTIONS_SCHEMA = vol.Schema( - { - vol.Required(CONF_EARLIEST_START_TIME): selector.TimeSelector(), - vol.Required(CONF_LATEST_END_TIME): selector.TimeSelector(), - vol.Required(CONF_DURATION, default={"hours": 1}): selector.DurationSelector(), - vol.Required( - CONF_PRICE_MODE, default=PriceModes.CHEAPEST - ): selector.SelectSelector( - selector.SelectSelectorConfig( - translation_key=CONF_PRICE_MODE, - mode=selector.SelectSelectorMode.LIST, - options=[e.value for e in PriceModes], - ) - ), - vol.Required( - CONF_INTERVAL_MODE, default=IntervalModes.INTERMITTENT - ): selector.SelectSelector( - selector.SelectSelectorConfig( - translation_key=CONF_INTERVAL_MODE, - mode=selector.SelectSelectorMode.LIST, - options=[e.value for e in IntervalModes], - ) - ), - # vol.Required( - # CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS - # ): selector.NumberSelector( - # selector.NumberSelectorConfig( - # mode=selector.NumberSelectorMode.BOX, step="any" - # ), - # ), - } -) - -CONFIG_SCHEMA = vol.Schema( - { - vol.Required(CONF_NAME): selector.TextSelector(), - vol.Required(CONF_ENTITY_ID): selector.EntitySelector( - selector.EntitySelectorConfig(domain=SENSOR_DOMAIN) - ), - } -).extend(OPTIONS_SCHEMA.schema) - -CONFIG_FLOW = {"user": SchemaFlowFormStep(CONFIG_SCHEMA)} - -OPTIONS_FLOW = {"init": SchemaFlowFormStep(OPTIONS_SCHEMA)} - - -class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN): - """Handle a config or options flow for Threshold.""" - - config_flow = CONFIG_FLOW - options_flow = OPTIONS_FLOW - - def async_config_entry_title(self, options: Mapping[str, Any]) -> str: - """Return config entry title.""" - name: str = options[CONF_NAME] - return name diff --git a/custom_components/epex_spot_sensor/const.py b/custom_components/epex_spot_sensor/const.py deleted file mode 100644 index d48ff02..0000000 --- a/custom_components/epex_spot_sensor/const.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Constants for the EPEX Spot Sensor integration.""" -from enum import Enum - -DOMAIN = "epex_spot_sensor" - -CONF_EARLIEST_START_TIME = "earliest_start_time" -CONF_LATEST_END_TIME = "latest_end_time" -CONF_DURATION = "duration" - -CONF_INTERVAL_MODE = "interval_mode" - - -class IntervalModes(Enum): - """Work modes for config validation.""" - - CONTIGUOUS = "contiguous" - INTERMITTENT = "intermittent" - - -CONF_PRICE_MODE = "price_mode" - - -class PriceModes(Enum): - """Price modes for config validation.""" - - CHEAPEST = "cheapest" - MOST_EXPENSIVE = "most_expensive" - - -ATTR_INTERVAL_ENABLED = "enabled" -ATTR_START_TIME = "start_time" -ATTR_END_TIME = "end_time" -ATTR_RANK = "rank" -ATTR_DATA = "data" -ATTR_PRICE_PER_MWH = "price_per_mwh" -ATTR_PRICE_PER_KWH = "price_per_kwh" diff --git a/custom_components/epex_spot_sensor/contiguous_interval.py b/custom_components/epex_spot_sensor/contiguous_interval.py deleted file mode 100644 index c4010b9..0000000 --- a/custom_components/epex_spot_sensor/contiguous_interval.py +++ /dev/null @@ -1,135 +0,0 @@ -import logging -from datetime import datetime, timedelta - - -_LOGGER = logging.getLogger(__name__) - -SECONDS_PER_HOUR = 60 * 60 - - -def _find_market_price(marketdata, dt: datetime): - for mp in marketdata: - if dt >= mp.start_time and dt < mp.end_time: - return mp - - return None - - -def _calc_interval_price(marketdata, start_time: datetime, duration: timedelta): - """Calculate price for given start time and duration.""" - total_price = 0 - stop_time = start_time + duration - - while start_time < stop_time: - mp = _find_market_price(marketdata, start_time) - - if mp.end_time > stop_time: - active_duration_in_this_segment = stop_time - start_time - else: - active_duration_in_this_segment = mp.end_time - start_time - - total_price += ( - mp.price_eur_per_mwh - * active_duration_in_this_segment.total_seconds() - / SECONDS_PER_HOUR - ) - - start_time = mp.end_time - - return total_price - - -def _calc_start_times( - marketdata, earliest_start: datetime, latest_end: datetime, duration: timedelta -): - """Calculate list of meaningful start times.""" - start_times = set() - start_time = earliest_start - - # add earliest possible start (if duration matches) - if earliest_start + duration <= latest_end: - start_times.add(earliest_start) - - for md in marketdata: - # add start times for market data segment start - if md.start_time >= earliest_start and md.start_time + duration <= latest_end: - start_times.add(earliest_start) - - # add start times for market data segment end - start_time = md.end_time - duration - if md.end_time <= latest_end and earliest_start <= start_time: - start_times.add(start_time) - - # add latest possible start (if duration matches) - start_time = latest_end - duration - if earliest_start <= start_time: - start_times.add(start_time) - - return sorted(start_times) - - -def _find_extreme_price_interval( - marketdata, start_times, duration: timedelta, most_expensive: bool = False -): - """Find the lowest/highest price for all given start times. - - The argument cmp is a lambda which is used to differentiate between - lowest and highest price. - """ - interval_price: float | None = None - interval_start_time: timedelta | None = None - - if most_expensive: - - def cmp(a, b): - return a > b - - else: - - def cmp(a, b): - return a < b - - for start_time in start_times: - ip = _calc_interval_price(marketdata, start_time, duration) - - if ip is None: - return None - - if interval_price is None or cmp(ip, interval_price): - interval_price = ip - interval_start_time = start_time - - if interval_start_time is None: - return None - - return { - "start": interval_start_time, - "end": interval_start_time + duration, - "interval_price": interval_price, - "price_per_hour": interval_price * SECONDS_PER_HOUR / duration.total_seconds(), - } - - -def calc_interval_for_contiguous( - marketdata, - earliest_start: datetime, - latest_end: datetime, - duration: timedelta, - most_expensive: bool = True, -): - if len(marketdata) == 0: - return None - - if marketdata[-1].end_time < latest_end: - return None - - start_times = _calc_start_times( - marketdata, - earliest_start=earliest_start, - latest_end=latest_end, - duration=duration, - ) - - return _find_extreme_price_interval( - marketdata, start_times, duration, most_expensive - ) diff --git a/custom_components/epex_spot_sensor/epex_spor_sensor b/custom_components/epex_spot_sensor/epex_spor_sensor deleted file mode 120000 index 567ae84..0000000 --- a/custom_components/epex_spot_sensor/epex_spor_sensor +++ /dev/null @@ -1 +0,0 @@ -epex_spor_sensor \ No newline at end of file diff --git a/custom_components/epex_spot_sensor/intermittent_interval.py b/custom_components/epex_spot_sensor/intermittent_interval.py deleted file mode 100644 index 104da79..0000000 --- a/custom_components/epex_spot_sensor/intermittent_interval.py +++ /dev/null @@ -1,107 +0,0 @@ -from datetime import datetime, timedelta - - -SECONDS_PER_HOUR = 60 * 60 - - -class Interval: - def __init__( - self, - start_time: datetime, - end_time: datetime, - price_eur_per_mwh: float, - rank: int, - ): - self._start_time = start_time - self._end_time = end_time - self._price_eur_per_mwh = price_eur_per_mwh - self._rank = rank - - @property - def start_time(self): - return self._start_time - - @property - def end_time(self): - return self._end_time - - @property - def price_eur_per_mwh(self): - return self._price_eur_per_mwh - - @property - def rank(self): - return self._rank - - def __repr__(self): - return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_eur_per_mwh}, rank: {self._rank})" # noqa: E501 - - -def calc_intervals_for_intermittent( - marketdata, - earliest_start: datetime, - latest_end: datetime, - duration: timedelta, - most_expensive: bool = False, -): - """Calculate price for given start time and duration.""" - if len(marketdata) == 0: - return None - - if marketdata[-1].end_time < latest_end: - return None - - # filter intervals which fit to start- and end-time (including overlapping) - marketdata = [ - e - for e in marketdata - if earliest_start < e.end_time and latest_end > e.start_time - ] - - # sort by price - marketdata.sort(key=lambda e: e.price_eur_per_mwh, reverse=most_expensive) - - active_time: timedelta = timedelta(seconds=0) - intervals = [] - - for count, mp in enumerate(marketdata): - interval_start_time = ( - earliest_start if mp.start_time < earliest_start else mp.start_time - ) - interval_end_time = latest_end if mp.end_time > latest_end else mp.end_time - - active_duration_in_this_segment = interval_end_time - interval_start_time - - if active_time + active_duration_in_this_segment > duration: - # we don't need the full active_duration_in_this_segment - active_duration_in_this_segment = duration - active_time - - price = ( - mp.price_eur_per_mwh - * active_duration_in_this_segment.total_seconds() - / SECONDS_PER_HOUR - ) - - intervals.append( - Interval( - start_time=interval_start_time, - end_time=interval_start_time + active_duration_in_this_segment, - price_eur_per_mwh=price, - rank=count, - ) - ) - - active_time += active_duration_in_this_segment - - if active_time == duration: - break - - return intervals - - -def is_now_in_intervals(now: datetime, intervals): - for e in intervals: - if now >= e.start_time and now < e.end_time: - return True - - return False diff --git a/custom_components/epex_spot_sensor/manifest.json b/custom_components/epex_spot_sensor/manifest.json deleted file mode 100644 index 40e5589..0000000 --- a/custom_components/epex_spot_sensor/manifest.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "domain": "epex_spot_sensor", - "name": "EPEX Spot Sensor", - "codeowners": ["@mampfes"], - "config_flow": true, - "documentation": "https://github.com/mampfes/ha_epex_spot", - "integration_type": "helper", - "iot_class": "calculated", - "issue_tracker": "https://github.com/mampfes/ha_epex_spot/issues", - "version": "2.3.0b0" -} diff --git a/custom_components/epex_spot_sensor/strings.json b/custom_components/epex_spot_sensor/strings.json deleted file mode 100644 index 782ccf6..0000000 --- a/custom_components/epex_spot_sensor/strings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "title": "EPEX Spot Sensor", - "config": { - "step": { - "user": { - "title": "Add Times of the Day Sensor", - "description": "Create a binary sensor that turns on or off depending on the time.", - "data": { - "after_time": "On time", - "before_time": "Off time", - "name": "[%key:common::config_flow::data::name%]" - } - } - } - }, - "options": { - "step": { - "init": { - "data": { - "after_time": "[%key:component::tod::config::step::user::data::after_time%]", - "before_time": "[%key:component::tod::config::step::user::data::before_time%]" - } - } - } - } -} diff --git a/custom_components/epex_spot_sensor/translations/en.json b/custom_components/epex_spot_sensor/translations/en.json deleted file mode 100644 index c5cc77f..0000000 --- a/custom_components/epex_spot_sensor/translations/en.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "name": "Name", - "entity_id": "Price Input Sensor", - "earliest_start_time": "Earliest Start Time", - "latest_end_time": "Latest End Time", - "duration": "Duration", - "interval_mode": "Interval Mode", - "price_mode": "Price Mode" - }, - "data_description": { - "earliest_start_time": "Earliest time to start the appliance.", - "latest_end_time": "Latest time to end the appliance. Set it to same value as earliest start time to cover 24h. If set to smaller value than earliest start time, it automatically refers to following day.", - "duration": "Required duration to complete the appliance.", - "interval_mode": "Does the appliance need a single contigous interval or can it be splitted into multiple intervals." - }, - "description": "Create a binary sensor that turns on or off depending on the market price.", - "title": "Add EPEX Spot Binary Sensor" - } - } - }, - "options": { - "step": { - "init": { - "data": { - "earliest_start_time": "Earliest Start Time", - "latest_end_time": "Latest End Time", - "duration": "Duration", - "interval_mode": "Interval Mode", - "price_mode": "Price Mode" - }, - "data_description": { - "earliest_start_time": "Earliest time to start the appliance.", - "latest_end_time": "Latest time to end the appliance. Set it to same value as earliest start time to cover 24h. If set to smaller value than earliest start time, it automatically refers to following day.", - "duration": "Required duration to complete the appliance.", - "interval_mode": "Does the appliance need a single contigous interval or can it be splitted into multiple intervals." - } - } - } - }, - "title": "EPEX Spot Sensor", - "selector": { - "price_mode": { - "options": { - "cheapest": "Cheapest", - "most_expensive": "Most Expensive" - } - }, - "interval_mode": { - "options": { - "contiguous": "Contiguous", - "intermittent": "Intermittent" - } - } - } -} diff --git a/custom_components/epex_spot_sensor/util.py b/custom_components/epex_spot_sensor/util.py deleted file mode 100644 index b8c2fd9..0000000 --- a/custom_components/epex_spot_sensor/util.py +++ /dev/null @@ -1,51 +0,0 @@ -import logging - -from homeassistant.helpers import ( - config_validation as cv, -) - -_LOGGER = logging.getLogger(__name__) - - -class Marketprice: - def __init__(self, entry): - self._start_time = cv.datetime(entry["start_time"]) - self._end_time = cv.datetime(entry["end_time"]) - if x := entry.get("price_eur_per_mwh"): - self._price_eur_per_mwh = x - self._uom = "EUR/MWh" - elif x := entry.get("price_gbp_per_mwh"): - self._price_eur_per_mwh = x - self._uom = "GBP/MWH" - else: - raise KeyError() - - def __repr__(self): - return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_eur_per_mwh} {self._uom})" # noqa: E501 - - @property - def start_time(self): - return self._start_time - - @property - def end_time(self): - return self._end_time - - @property - def price_eur_per_mwh(self): - return self._price_eur_per_mwh - - @property - def price_eur_per_mwh_name(self): - self._price_eur_per_mwh_name - - -def get_marketdata_from_sensor_attrs(attributes): - """Convert sensor attributes to market price list.""" - try: - data = attributes["data"] - except (KeyError, TypeError): - _LOGGER.error("price sensor attributes invalid") - return [] - - return [Marketprice(e) for e in data]