diff --git a/README.md b/README.md index 832529bd..0fe17c25 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,12 @@ Ideally, you'd be able to use the consumption sensors as part of your [energy da If you go through the [setup](https://my.home-assistant.io/redirect/config_flow_start/?domain=octopus_energy) process after you've configured your account, you can set up target rate sensors. These sensors calculate the lowest continuous or intermittent points and turn on when these rates are active. These sensors can then be used in automations to turn on/off devices the save you money (and in theory be on when there's the most renewable energy). +### Gas Meters + +When you sign into your account, if you have gas meters, we'll setup some sensors for you. However, the way these sensors report data isn't consistent between versions of the meters, and Octopus Energy doesn't expose what type of meter you have. Therefore, you have to toggle the checkbox when setting up your initial account within HA. If you've already setup your account, you can update this via the `Configure` option within the integrations configuration. This is a global setting, and therefore will apply to **all** gas meters. + ## Known Issues/Limitations - Latest consumption is at the mercy of how often Octopus Energy updates their records. This seems to be a day behind based on local testing. -- Only the first property associated with an account is exposed. \ No newline at end of file +- Only the first property associated with an account is exposed. +- Gas meter SMETS1/SMETS2 setting has to be set globally and manually as Octopus Energy doesn't provide this information. \ No newline at end of file diff --git a/custom_components/octopus_energy/__init__.py b/custom_components/octopus_energy/__init__.py index 3891fcac..d4a336f0 100644 --- a/custom_components/octopus_energy/__init__.py +++ b/custom_components/octopus_energy/__init__.py @@ -2,12 +2,14 @@ import re from datetime import timedelta from homeassistant.util.dt import utcnow +import asyncio from .const import ( DOMAIN, CONFIG_MAIN_API_KEY, CONFIG_MAIN_ACCOUNT_ID, + CONFIG_TARGET_NAME, DATA_CLIENT, @@ -27,55 +29,8 @@ _LOGGER = logging.getLogger(__name__) -def setup_dependencies(hass, config): - """Setup the coordinator and api client which will be shared by various entities""" - client = OctopusEnergyApiClient(config[CONFIG_MAIN_API_KEY]) - hass.data[DOMAIN][DATA_CLIENT] = client - - async def async_update_data(): - """Fetch data from API endpoint.""" - # Only get data every half hour or if we don't have any data - if (DATA_RATES not in hass.data[DOMAIN] or (utcnow().minute % 30) == 0 or len(hass.data[DOMAIN][DATA_RATES]) == 0): - - # FIX: Ideally we'd only get the tariffs once at the start, but it's not working - account_info = await client.async_get_account(config[CONFIG_MAIN_ACCOUNT_ID]) - - current_agreement = None - if len(account_info["electricity_meter_points"]) > 0: - # We're only interested in the tariff of the first electricity point - for point in account_info["electricity_meter_points"]: - current_agreement = get_active_agreement(point["agreements"]) - if current_agreement != None: - break - - if current_agreement == None: - raise - - tariff_code = current_agreement["tariff_code"] - matches = re.search(REGEX_PRODUCT_NAME, tariff_code) - if matches == None: - raise - - product_code = matches[1] - - _LOGGER.info('Updating rates...') - hass.data[DOMAIN][DATA_RATES] = await client.async_get_rates(product_code, tariff_code) - - return hass.data[DOMAIN][DATA_RATES] - - hass.data[DOMAIN][DATA_COORDINATOR] = DataUpdateCoordinator( - hass, - _LOGGER, - name="rates", - update_method=async_update_data, - # Because of how we're using the data, we'll update every minute, but we will only actually retrieve - # data every 30 minutes - update_interval=timedelta(minutes=1), - ) - async def async_setup_entry(hass, entry): """This is called from the config flow.""" - hass.data.setdefault(DOMAIN, {}) if CONFIG_MAIN_API_KEY in entry.data: @@ -90,5 +45,74 @@ async def async_setup_entry(hass, entry): hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "binary_sensor") ) + + entry.async_on_unload(entry.add_update_listener(options_update_listener)) + + return True + +def setup_dependencies(hass, config): + """Setup the coordinator and api client which will be shared by various entities""" + + if DATA_CLIENT not in hass.data[DOMAIN]: + client = OctopusEnergyApiClient(config[CONFIG_MAIN_API_KEY]) + hass.data[DOMAIN][DATA_CLIENT] = client + + async def async_update_data(): + """Fetch data from API endpoint.""" + # Only get data every half hour or if we don't have any data + if (DATA_RATES not in hass.data[DOMAIN] or (utcnow().minute % 30) == 0 or len(hass.data[DOMAIN][DATA_RATES]) == 0): + + # FIX: Ideally we'd only get the tariffs once at the start, but it's not working + account_info = await client.async_get_account(config[CONFIG_MAIN_ACCOUNT_ID]) + + current_agreement = None + if len(account_info["electricity_meter_points"]) > 0: + # We're only interested in the tariff of the first electricity point + for point in account_info["electricity_meter_points"]: + current_agreement = get_active_agreement(point["agreements"]) + if current_agreement != None: + break + + if current_agreement == None: + raise + + tariff_code = current_agreement["tariff_code"] + matches = re.search(REGEX_PRODUCT_NAME, tariff_code) + if matches == None: + raise + + product_code = matches[1] + + _LOGGER.info('Updating rates...') + hass.data[DOMAIN][DATA_RATES] = await client.async_get_rates(product_code, tariff_code) + + return hass.data[DOMAIN][DATA_RATES] + + hass.data[DOMAIN][DATA_COORDINATOR] = DataUpdateCoordinator( + hass, + _LOGGER, + name="rates", + update_method=async_update_data, + # Because of how we're using the data, we'll update every minute, but we will only actually retrieve + # data every 30 minutes + update_interval=timedelta(minutes=1), + ) + +async def options_update_listener(hass, entry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + if CONFIG_MAIN_API_KEY in entry.data: + target_domain = "sensor" + elif CONFIG_TARGET_NAME in entry.data: + target_domain = "binary_sensor" + + unload_ok = all( + await asyncio.gather( + *[hass.config_entries.async_forward_entry_unload(entry, target_domain)] + ) + ) - return True \ No newline at end of file + return unload_ok \ No newline at end of file diff --git a/custom_components/octopus_energy/binary_sensor.py b/custom_components/octopus_energy/binary_sensor.py index c9c4a945..17a5a1a5 100644 --- a/custom_components/octopus_energy/binary_sensor.py +++ b/custom_components/octopus_energy/binary_sensor.py @@ -19,8 +19,7 @@ CONFIG_TARGET_START_TIME, CONFIG_TARGET_END_TIME, - DATA_COORDINATOR, - DATA_CLIENT + DATA_COORDINATOR ) _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/octopus_energy/config_flow.py b/custom_components/octopus_energy/config_flow.py index 15c89416..5aa3f505 100644 --- a/custom_components/octopus_energy/config_flow.py +++ b/custom_components/octopus_energy/config_flow.py @@ -1,8 +1,11 @@ -from homeassistant.config_entries import ConfigFlow import re import voluptuous as vol import logging +from homeassistant.config_entries import (ConfigFlow, OptionsFlow) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv + from .const import ( DOMAIN, @@ -11,34 +14,21 @@ CONFIG_TARGET_NAME, CONFIG_TARGET_HOURS, - CONFIG_TARGET_TYPE, CONFIG_TARGET_START_TIME, CONFIG_TARGET_END_TIME, + CONFIG_SMETS1, + + DATA_SCHEMA_ACCOUNT, + DATA_SCHEMA_TARGET, + REGEX_TIME, REGEX_ENTITY_NAME, REGEX_HOURS ) -import homeassistant.helpers.config_validation as cv from .api_client import OctopusEnergyApiClient -ACCOUNT_DATA_SCHEMA = vol.Schema({ - vol.Required(CONFIG_MAIN_API_KEY): str, - vol.Required(CONFIG_MAIN_ACCOUNT_ID): str, -}) - -TARGET_DATA_SCHEMA = vol.Schema({ - vol.Required(CONFIG_TARGET_NAME): str, - vol.Required(CONFIG_TARGET_HOURS): str, - vol.Required(CONFIG_TARGET_TYPE, default="Continuous"): vol.In({ - "Continuous": "Continuous", - "Intermittent": "Intermittent" - }), - vol.Optional(CONFIG_TARGET_START_TIME): str, - vol.Optional(CONFIG_TARGET_END_TIME): str, -}) - _LOGGER = logging.getLogger(__name__) class OctopusEnergyConfigFlow(ConfigFlow, domain=DOMAIN): @@ -55,29 +45,20 @@ async def async_setup_initial_account(self, user_input): if (account_info == None): errors[CONFIG_MAIN_ACCOUNT_ID] = "account_not_found" return self.async_show_form( - step_id="user", data_schema=ACCOUNT_DATA_SCHEMA, errors=errors + step_id="user", data_schema=DATA_SCHEMA_ACCOUNT, errors=errors ) - config = { - CONFIG_MAIN_API_KEY: user_input[CONFIG_MAIN_API_KEY], - CONFIG_MAIN_ACCOUNT_ID: user_input[CONFIG_MAIN_ACCOUNT_ID] - } - # Setup our basic sensors return self.async_create_entry( title="Octopus Energy", - data=config + data=user_input ) async def async_step_target_rate(self, user_input): """Setup a target based on the provided user input""" errors = {} - config = { - CONFIG_TARGET_NAME: user_input[CONFIG_TARGET_NAME], - CONFIG_TARGET_TYPE: user_input[CONFIG_TARGET_TYPE] - } - matches = re.search(REGEX_ENTITY_NAME, config[CONFIG_TARGET_NAME]) + matches = re.search(REGEX_ENTITY_NAME, user_input[CONFIG_TARGET_NAME]) if matches == None: errors[CONFIG_TARGET_NAME] = "invalid_target_name" @@ -86,32 +67,32 @@ async def async_step_target_rate(self, user_input): if matches == None: errors[CONFIG_TARGET_HOURS] = "invalid_target_hours" else: - config[CONFIG_TARGET_HOURS] = float(user_input[CONFIG_TARGET_HOURS]) - if config[CONFIG_TARGET_HOURS] % 0.5 != 0: + user_input[CONFIG_TARGET_HOURS] = float(user_input[CONFIG_TARGET_HOURS]) + if user_input[CONFIG_TARGET_HOURS] % 0.5 != 0: errors[CONFIG_TARGET_HOURS] = "invalid_target_hours" if CONFIG_TARGET_START_TIME in user_input: - config[CONFIG_TARGET_START_TIME] = user_input[CONFIG_TARGET_START_TIME] - matches = re.search(REGEX_TIME, config[CONFIG_TARGET_START_TIME]) + user_input[CONFIG_TARGET_START_TIME] = user_input[CONFIG_TARGET_START_TIME] + matches = re.search(REGEX_TIME, user_input[CONFIG_TARGET_START_TIME]) if matches == None: errors[CONFIG_TARGET_START_TIME] = "invalid_target_time" if CONFIG_TARGET_END_TIME in user_input: - config[CONFIG_TARGET_END_TIME] = user_input[CONFIG_TARGET_END_TIME] - matches = re.search(REGEX_TIME, config[CONFIG_TARGET_START_TIME]) + user_input[CONFIG_TARGET_END_TIME] = user_input[CONFIG_TARGET_END_TIME] + matches = re.search(REGEX_TIME, user_input[CONFIG_TARGET_START_TIME]) if matches == None: errors[CONFIG_TARGET_END_TIME] = "invalid_target_time" if len(errors) < 1: # Setup our targets sensor return self.async_create_entry( - title=f"{config[CONFIG_TARGET_NAME]} (target)", - data=config + title=f"{user_input[CONFIG_TARGET_NAME]} (target)", + data=user_input ) # Reshow our form with raised logins return self.async_show_form( - step_id="target_rate", data_schema=TARGET_DATA_SCHEMA, errors=errors + step_id="target_rate", data_schema=DATA_SCHEMA_TARGET, errors=errors ) async def async_step_user(self, user_input): @@ -134,9 +115,47 @@ async def async_step_user(self, user_input): if is_account_setup: return self.async_show_form( - step_id="target_rate", data_schema=TARGET_DATA_SCHEMA + step_id="target_rate", data_schema=DATA_SCHEMA_TARGET ) return self.async_show_form( - step_id="user", data_schema=ACCOUNT_DATA_SCHEMA - ) \ No newline at end of file + step_id="user", data_schema=DATA_SCHEMA_ACCOUNT + ) + + @staticmethod + @callback + def async_get_options_flow(entry): + return OptionsFlowHandler(entry) + +class OptionsFlowHandler(OptionsFlow): + """Handles options flow for the component.""" + + def __init__(self, entry) -> None: + self._entry = entry + + async def async_step_init(self, user_input): + """Manage the options for the custom component.""" + + if CONFIG_MAIN_API_KEY in self._entry.data: + config = dict(self._entry.data) + if self._entry.options is not None: + config.update(self._entry.options) + + return self.async_show_form( + step_id="user", data_schema=vol.Schema({ + vol.Optional(CONFIG_SMETS1, default=config[CONFIG_SMETS1]): bool, + }) + ) + + return self.async_abort(reason="not_supported") + + async def async_step_user(self, user_input): + """Manage the options for the custom component.""" + errors = {} + + if user_input is not None: + config = dict(self._entry.data) + config.update(user_input) + return self.async_create_entry(title="", data=config) + + return self.async_abort(reason="not_supported") \ No newline at end of file diff --git a/custom_components/octopus_energy/const.py b/custom_components/octopus_energy/const.py index a8ced093..bd33882f 100644 --- a/custom_components/octopus_energy/const.py +++ b/custom_components/octopus_energy/const.py @@ -1,7 +1,10 @@ +import voluptuous as vol + DOMAIN = "octopus_energy" CONFIG_MAIN_API_KEY = "Api key" CONFIG_MAIN_ACCOUNT_ID = "Account Id" +CONFIG_SMETS1 = "SMETS1" CONFIG_TARGET_NAME = "Name" CONFIG_TARGET_HOURS = "Hours" @@ -17,4 +20,21 @@ REGEX_HOURS = "^[0-9]+(\.[0-9]+)*$" REGEX_TIME = "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$" REGEX_ENTITY_NAME = "^[a-z0-9_]+$" -REGEX_PRODUCT_NAME = "^[A-Z]-[0-9A-Z]+-([A-Z0-9-]+)-[A-Z]$" \ No newline at end of file +REGEX_PRODUCT_NAME = "^[A-Z]-[0-9A-Z]+-([A-Z0-9-]+)-[A-Z]$" + +DATA_SCHEMA_ACCOUNT = vol.Schema({ + vol.Required(CONFIG_MAIN_API_KEY): str, + vol.Required(CONFIG_MAIN_ACCOUNT_ID): str, + vol.Optional(CONFIG_SMETS1): bool, +}) + +DATA_SCHEMA_TARGET = vol.Schema({ + vol.Required(CONFIG_TARGET_NAME): str, + vol.Required(CONFIG_TARGET_HOURS): str, + vol.Required(CONFIG_TARGET_TYPE, default="Continuous"): vol.In({ + "Continuous": "Continuous", + "Intermittent": "Intermittent" + }), + vol.Optional(CONFIG_TARGET_START_TIME): str, + vol.Optional(CONFIG_TARGET_END_TIME): str, +}) \ No newline at end of file diff --git a/custom_components/octopus_energy/sensor.py b/custom_components/octopus_energy/sensor.py index 139802b6..8cffe626 100644 --- a/custom_components/octopus_energy/sensor.py +++ b/custom_components/octopus_energy/sensor.py @@ -8,15 +8,22 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_MONETARY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING, SensorEntity, ) -from .utils import get_active_agreement +from homeassistant.const import ( + ENERGY_KILO_WATT_HOUR, + VOLUME_CUBIC_METERS +) +from .utils import (get_active_agreement, convert_kwh_to_m3) from .const import ( DOMAIN, CONFIG_MAIN_API_KEY, CONFIG_MAIN_ACCOUNT_ID, + + CONFIG_SMETS1, DATA_COORDINATOR, DATA_CLIENT @@ -33,7 +40,14 @@ async def async_setup_entry(hass, entry, async_add_entities): await async_setup_default_sensors(hass, entry, async_add_entities) async def async_setup_default_sensors(hass, entry, async_add_entities): - config = entry.data + config = dict(entry.data) + + if entry.options: + config.update(entry.options) + + is_smets1 = False + if CONFIG_SMETS1 in config: + is_smets1 = config[CONFIG_SMETS1] client = hass.data[DOMAIN][DATA_CLIENT] @@ -58,8 +72,8 @@ async def async_setup_default_sensors(hass, entry, async_add_entities): # We only care about points that have active agreements if get_active_agreement(point["agreements"]) != None: for meter in point["meters"]: - entities.append(OctopusEnergyLatestGasReading(client, point["mprn"], meter["serial_number"])) - entities.append(OctopusEnergyPreviousAccumulativeGasReading(client, point["mprn"], meter["serial_number"])) + entities.append(OctopusEnergyLatestGasReading(client, point["mprn"], meter["serial_number"], is_smets1)) + entities.append(OctopusEnergyPreviousAccumulativeGasReading(client, point["mprn"], meter["serial_number"], is_smets1)) async_add_entities(entities, True) @@ -72,7 +86,7 @@ def __init__(self, coordinator): super().__init__(coordinator) self._attributes = {} - self._state = 0 + self._state = None @property def unique_id(self): @@ -109,7 +123,7 @@ def state(self): """The state of the sensor.""" # Find the current rate. We only need to do this every half an hour now = utcnow() - if (now.minute % 30) == 0 or self._state == 0: + if (now.minute % 30) == 0 or self._state == None: _LOGGER.info('Updating OctopusEnergyElectricityCurrentRate') current_rate = None @@ -136,7 +150,7 @@ def __init__(self, coordinator): super().__init__(coordinator) self._attributes = {} - self._state = 0 + self._state = None @property def unique_id(self): @@ -173,7 +187,7 @@ def state(self): """The state of the sensor.""" # Find the previous rate. We only need to do this every half an hour now = utcnow() - if (now.minute % 30) == 0 or self._state == 0: + if (now.minute % 30) == 0 or self._state == None: _LOGGER.info('Updating OctopusEnergyElectricityPreviousRate') target = now - timedelta(minutes=30) @@ -207,7 +221,7 @@ def __init__(self, client, mpan, serial_number): "Serial Number": serial_number } - self._state = 0 + self._state = None @property def unique_id(self): @@ -232,7 +246,7 @@ def state_class(self): @property def unit_of_measurement(self): """The unit of measurement of sensor""" - return "kWh" + return ENERGY_KILO_WATT_HOUR @property def icon(self): @@ -253,7 +267,7 @@ async def async_update(self): """Retrieve the latest consumption""" # We only need to do this every half an hour current_datetime = now() - if (current_datetime.minute % 30) == 0: + if (current_datetime.minute % 30) == 0 or self._state == None: _LOGGER.info('Updating OctopusEnergyLatestElectricityReading') period_from = as_utc(current_datetime - timedelta(hours=1)) @@ -278,7 +292,8 @@ def __init__(self, client, mprn, serial_number): "Serial Number": serial_number } - self._state = 0 + self._state = None + self._data = [] @property def unique_id(self): @@ -303,7 +318,7 @@ def state_class(self): @property def unit_of_measurement(self): """The unit of measurement of sensor""" - return "kWh" + return ENERGY_KILO_WATT_HOUR @property def icon(self): @@ -322,9 +337,11 @@ def state(self): async def async_update(self): """Retrieve the previous days accumulative consumption""" - # We only need to do this once a day current_datetime = now() - if (current_datetime.hour == 0 and current_datetime.minute == 0) or self._state == 0: + + # We only need to do this once a day, unless we don't have enough data for the day therefore we want to retrieve it + # every hour until we have enough data for the day + if (current_datetime.hour == 0 and current_datetime.minute == 0) or self._state == None or (current_datetime.minute % 60 == 0 and len(self._data) != 48): _LOGGER.info('Updating OctopusEnergyPreviousAccumulativeElectricityReading') period_from = as_utc((current_datetime - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)) @@ -336,24 +353,28 @@ async def async_update(self): total = total + item["consumption"] self._state = total + self._data = data else: self._state = 0 + self._data = [] class OctopusEnergyLatestGasReading(SensorEntity): """Sensor for displaying the current gas rate.""" - def __init__(self, client, mprn, serial_number): + def __init__(self, client, mprn, serial_number, is_smets1_meter): """Init sensor.""" self._mprn = mprn self._serial_number = serial_number + self._is_smets1_meter = is_smets1_meter self._client = client self._attributes = { "MPRN": mprn, - "Serial Number": serial_number + "Serial Number": serial_number, + "Is SMETS1 Meter": is_smets1_meter } - self._state = 0 + self._state = None @property def unique_id(self): @@ -368,7 +389,7 @@ def name(self): @property def device_class(self): """The type of sensor""" - return DEVICE_CLASS_ENERGY + return DEVICE_CLASS_GAS @property def state_class(self): @@ -378,12 +399,12 @@ def state_class(self): @property def unit_of_measurement(self): """The unit of measurement of sensor""" - return "kWh" + return VOLUME_CUBIC_METERS @property def icon(self): """Icon of the sensor.""" - return "mdi:lightning-bolt" + return "mdi:fire" @property def extra_state_attributes(self): @@ -399,7 +420,7 @@ async def async_update(self): """Retrieve the latest consumption""" # We only need to do this every half an hour current_datetime = now() - if (current_datetime.minute % 30) == 0: + if (current_datetime.minute % 30) == 0 or self._state == None: _LOGGER.info('Updating OctopusEnergyLatestGasReading') period_from = as_utc(current_datetime - timedelta(hours=1)) @@ -410,21 +431,27 @@ async def async_update(self): else: self._state = 0 + if self._is_smets1_meter: + self._state = convert_kwh_to_m3(self._state) + class OctopusEnergyPreviousAccumulativeGasReading(SensorEntity): """Sensor for displaying the previous days accumulative gas reading.""" - def __init__(self, client, mprn, serial_number): + def __init__(self, client, mprn, serial_number, is_smets1_meter): """Init sensor.""" self._mprn = mprn self._serial_number = serial_number + self._is_smets1_meter = is_smets1_meter self._client = client self._attributes = { "MPRN": mprn, - "Serial Number": serial_number + "Serial Number": serial_number, + "Is SMETS1 Meter": is_smets1_meter } - self._state = 0 + self._state = None + self._data = [] @property def unique_id(self): @@ -439,7 +466,7 @@ def name(self): @property def device_class(self): """The type of sensor""" - return DEVICE_CLASS_ENERGY + return DEVICE_CLASS_GAS @property def state_class(self): @@ -449,12 +476,12 @@ def state_class(self): @property def unit_of_measurement(self): """The unit of measurement of sensor""" - return "kWh" + return VOLUME_CUBIC_METERS @property def icon(self): """Icon of the sensor.""" - return "mdi:lightning-bolt" + return "mdi:fire" @property def extra_state_attributes(self): @@ -468,9 +495,11 @@ def state(self): async def async_update(self): """Retrieve the previous days accumulative consumption""" - # We only need to do this once a day current_datetime = now() - if (current_datetime.hour == 0 and current_datetime.minute == 0) or self._state == 0: + + # We only need to do this once a day, unless we don't have enough data for the day therefore we want to retrieve it + # every hour until we have enough data for the day + if (current_datetime.hour == 0 and current_datetime.minute == 0) or self._state == None or (current_datetime.minute % 60 == 0 and len(self._data) != 48): _LOGGER.info('Updating OctopusEnergyPreviousAccumulativeGasReading') period_from = as_utc((current_datetime - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)) @@ -482,5 +511,10 @@ async def async_update(self): total = total + item["consumption"] self._state = total + self._data = data else: - self._state = 0 \ No newline at end of file + self._state = 0 + self._data = [] + + if self._is_smets1_meter: + self._state = convert_kwh_to_m3(self._state) \ No newline at end of file diff --git a/custom_components/octopus_energy/translations/en.json b/custom_components/octopus_energy/translations/en.json index 46d9d5ee..d08f198d 100644 --- a/custom_components/octopus_energy/translations/en.json +++ b/custom_components/octopus_energy/translations/en.json @@ -7,7 +7,8 @@ "description": "Setup your basic account information. This can be found at https://octopus.energy/dashboard/developer/.", "data": { "Api key": "Api key", - "Account Id": "Your account Id (e.g. A-AAAA1111)" + "Account Id": "Your account Id (e.g. A-AAAA1111)", + "SMETS1": "Is SMETS1 Gas Meter" } }, "target_rate": { @@ -27,6 +28,25 @@ "invalid_target_hours": "Target hours must be in half hour increments.", "invalid_target_name": "Name must only include lower case alpha characters and underscore (e.g. my_target)", "invalid_target_time": "Must be in the format HH:MM" + }, + "abort": { + "not_supported": "Configuration for target rates is not supported at the moment." + } + }, + "options": { + "step": { + "user": { + "title": "Update Account Info", + "description": "Update your basic account information. This can be found at https://octopus.energy/dashboard/developer/.", + "data": { + "SMETS1": "Is SMETS1 Gas Meter" + } + } + }, + "error": { + }, + "abort": { + "not_supported": "Configuration for target rates is not supported at the moment." } } } \ No newline at end of file diff --git a/custom_components/octopus_energy/utils.py b/custom_components/octopus_energy/utils.py index e0d8d4c5..06c747db 100644 --- a/custom_components/octopus_energy/utils.py +++ b/custom_components/octopus_energy/utils.py @@ -15,4 +15,10 @@ def get_active_agreement(agreements): if valid_from <= now and (valid_to == None or valid_to >= now): return agreement - return None \ No newline at end of file + return None + +# Adapted from https://www.theenergyshop.com/guides/how-to-convert-gas-units-to-kwh +def convert_kwh_to_m3(value): + m3_value = value * 3.6 # kWh Conversion factor + m3_value = m3_value / 40 # Calorific value + return round(m3_value / 1.02264, 3) # Volume correction factor \ No newline at end of file