Skip to content

Commit 0405b47

Browse files
authored
Merge pull request #56 from zeronounours/add_support_for_non_grid
Add support for non grid source type (Gas, Water, Return to Grid)
2 parents 0ea15eb + 56ad5d0 commit 0405b47

File tree

10 files changed

+1088
-47
lines changed

10 files changed

+1088
-47
lines changed

README.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ The main difference is the addition of `price` and `price_entity` options:
4848

4949
**price** _float (optional)_
5050

51-
The static price of the tariff (in currency/kWh)
51+
The static price of the tariff (in currency per source unit, e.g. USD/kWh or
52+
USD/m³)
5253

5354
---
5455

5556
**price_entity** _string (optional)_
5657

57-
The entity ID of a sensor giving the current price of the tariff (in
58-
currency/kWh)
58+
The entity ID of a sensor giving the current price of the tariff (in currency
59+
per source unit, e.g. USD/kWh or USD/m³)
5960

6061
---
6162

@@ -67,6 +68,30 @@ Only one of `price` or `price_entity` should be given. If both are given,
6768
`price_entity` would have precedence. If none is defined, this integration will
6869
act as a basic utility meter, with no cost tracking.
6970

71+
### Energy cost type (Gas, Water, Return to grid)
72+
73+
The configuration can contain the optional `source_type` option to define the
74+
type of energy being monitored:
75+
76+
---
77+
78+
**source_type** _string (optional)_
79+
80+
The type of energy being followed as source. These are the same as the builtin
81+
energy dashboard. It can be of 4 types:
82+
83+
- `from_grid`: this is the default. To be used if source tracks grid
84+
consumption.
85+
- `to_grid`: to be used if source tracks energy returned to grid.
86+
- `gas`: to be used if source tracks gas consumption.
87+
- `water`: to be used if source tracks water consumption.
88+
89+
---
90+
91+
Keep in mind that depending on the time, the allowed units for the source will
92+
differ. `from_grid` and `to_grid` needs an electrical energy, while `gas` and
93+
`water` expect a volume.
94+
7095
### Energy cost sensor only
7196

7297
If the use of utility meter is unwanted and you only want energy costs, it is
@@ -108,6 +133,16 @@ energy_meter:
108133
source: sensor.energy
109134
price: 0.20
110135
create_utility_meter: false
136+
137+
monthly_gas:
138+
source: sensor.gas_consumption
139+
name: Monthly Gas
140+
cycle: monthly
141+
price_entity: sensor.current_gas_price
142+
source_type: gas
143+
tariffs:
144+
- peak
145+
- offpeak
111146
```
112147
113148
Usually, source energy sensors shares the same price. In order to prevent

custom_components/energy_meter/__init__.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import logging
44

55
# Third party libraries
6+
from homeassistant.components.energy.sensor import (
7+
SOURCE_ADAPTERS,
8+
SourceAdapter,
9+
)
610
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
711
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
812
from homeassistant.components.utility_meter import (
@@ -24,9 +28,11 @@
2428
import voluptuous as vol
2529

2630
from .const import (
31+
CONF_ADAPTER,
2732
CONF_CONF,
2833
CONF_PRICE,
2934
CONF_PRICE_ENTITY,
35+
CONF_SOURCE_TYPE,
3036
CONF_UTILITY_METER,
3137
DATA_ENERGY_METER,
3238
DOMAIN,
@@ -36,6 +42,14 @@
3642
_LOGGER = logging.getLogger(__name__)
3743

3844

45+
SOURCE_TYPE_TO_SOURCE_ADAPTER = {
46+
"from_grid": ("grid", "flow_from"),
47+
"to_grid": ("grid", "flow_to"),
48+
"gas": ("gas", None),
49+
"water": ("water", None),
50+
}
51+
52+
3953
# update the default schema w/ new options
4054
ENERGY_METER_CONFIG_SCHEMA = vol.Schema(
4155
vol.All(
@@ -44,6 +58,9 @@
4458
vol.Optional(CONF_PRICE): cv.positive_float,
4559
vol.Optional(CONF_PRICE_ENTITY): cv.entity_id,
4660
vol.Optional(CONF_UTILITY_METER): cv.boolean,
61+
vol.Optional(CONF_SOURCE_TYPE): vol.Any(
62+
*SOURCE_TYPE_TO_SOURCE_ADAPTER.keys(),
63+
),
4764
},
4865
),
4966
*METER_CONFIG_SCHEMA.schema.validators[1:],
@@ -92,11 +109,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
92109
# - for fixed price, cost sensor is bound to the meter id
93110
# - for entity price, cost sensor is bound to the entity
94111
# price id
95-
cache_key = conf_to_cost_sensor_id(meter, conf)
112+
sensor_id = conf_to_cost_sensor_id(meter, conf)
113+
if (adapter := get_energy_cost_sensor_adapter(conf)) is None:
114+
# stop here as we cannot create a cost sensor
115+
continue
116+
cache_key = (sensor_id, adapter.entity_id_suffix)
96117
if cache_key in hass.data[DATA_ENERGY_METER]:
97118
cost_entity = hass.data[DATA_ENERGY_METER][cache_key]
98119
else:
99-
cost_entity = await setup_energy_cost_sensor(hass, config, meter, conf)
120+
cost_entity = await setup_energy_cost_sensor(
121+
hass,
122+
config,
123+
meter,
124+
conf,
125+
adapter,
126+
)
100127
hass.data[DATA_ENERGY_METER][cache_key] = cost_entity
101128

102129
if create_utility_meter:
@@ -108,17 +135,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
108135
name = um_conf.get(CONF_NAME)
109136
if not name:
110137
name = meter.replace("_", " ")
111-
um_conf[CONF_NAME] = f"{name} Cost"
138+
um_conf[CONF_NAME] = f"{name} {adapter.name_suffix}"
112139

113140
# Prevent the reuse of the same unique_id as the
114141
# utility_meter sensors
115142
if um_conf.get(CONF_UNIQUE_ID):
116-
um_conf[CONF_UNIQUE_ID] = f"{um_conf[CONF_UNIQUE_ID]}_cost"
143+
um_conf[
144+
CONF_UNIQUE_ID
145+
] = f"{um_conf[CONF_UNIQUE_ID]}_{adapter.entity_id_suffix}"
117146

118147
await setup_utility_meter_sensors(
119148
hass,
120149
config,
121-
f"{meter}_cost",
150+
f"{meter}_{adapter.entity_id_suffix}",
122151
um_conf,
123152
select_entity,
124153
)
@@ -217,6 +246,7 @@ async def setup_energy_cost_sensor(
217246
config: ConfigType,
218247
meter: str,
219248
meter_conf: dict,
249+
adapter: SourceAdapter,
220250
):
221251
"""Create a cost sensor to follow an energy sensor."""
222252
_LOGGER.debug(
@@ -229,8 +259,43 @@ async def setup_energy_cost_sensor(
229259
hass,
230260
SENSOR_DOMAIN,
231261
DOMAIN,
232-
{CONF_METER: meter, CONF_CONF: meter_conf},
262+
{CONF_METER: meter, CONF_CONF: meter_conf, CONF_ADAPTER: adapter},
233263
config,
234264
),
235265
)
236-
return f"sensor.{'_'.join(conf_to_cost_sensor_id(meter, meter_conf))}_cost"
266+
# return the entity id
267+
return (
268+
f"sensor."
269+
f"{'_'.join(conf_to_cost_sensor_id(meter, meter_conf))}"
270+
f"_{adapter.entity_id_suffix}"
271+
)
272+
273+
274+
def get_energy_cost_sensor_adapter(conf: dict):
275+
"""Resolve the SourceAdapter from config."""
276+
# resolve source adapter from source_type
277+
source_type = conf.get(CONF_SOURCE_TYPE, "from_grid")
278+
_LOGGER.debug("Setup %s: setup energy cost sensor of type %s", DOMAIN, source_type)
279+
280+
if source_type not in SOURCE_TYPE_TO_SOURCE_ADAPTER:
281+
# should never happen if config validation is correctly set
282+
_LOGGER.error(
283+
"Setup %s: Unknown source type configured: %s",
284+
DOMAIN,
285+
source_type,
286+
)
287+
return None
288+
289+
source_adapter = SOURCE_TYPE_TO_SOURCE_ADAPTER[source_type]
290+
291+
for adapter in SOURCE_ADAPTERS:
292+
# find the right
293+
if (adapter.source_type, adapter.flow_type) == source_adapter:
294+
return adapter
295+
# should not happened unless builtin adapter changes
296+
_LOGGER.error(
297+
"Setup %s: Failed to find an appropriate source adapter for type %s",
298+
DOMAIN,
299+
source_type,
300+
)
301+
return None

custom_components/energy_meter/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
DOMAIN = "energy_meter"
44

55
CONF_CONF = "conf"
6+
CONF_ADAPTER = "adapter"
67

78
CONF_PRICE = "price"
89
CONF_PRICE_ENTITY = "price_entity"
910
CONF_UTILITY_METER = "create_utility_meter"
11+
CONF_SOURCE_TYPE = "source_type"
1012

1113
DATA_ENERGY_METER = "energy_meter_data"

custom_components/energy_meter/sensor.py

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
from homeassistant.helpers.entity_platform import AddEntitiesCallback
1919
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
2020

21-
from .const import CONF_CONF, CONF_PRICE, CONF_PRICE_ENTITY
21+
from .const import (
22+
CONF_ADAPTER,
23+
CONF_CONF,
24+
CONF_PRICE,
25+
CONF_PRICE_ENTITY,
26+
DOMAIN,
27+
)
2228
from .utils import conf_to_cost_sensor_id
2329

2430
_LOGGER = logging.getLogger(__name__)
@@ -33,25 +39,29 @@ async def async_setup_platform(
3339
"""Set up the cost sensor."""
3440
if discovery_info is None:
3541
_LOGGER.error(
36-
"This platform is not available to configure "
42+
"Setup %s: This platform is not available to configure "
3743
"from 'sensor:' in configuration.yaml",
44+
DOMAIN,
3845
)
3946
return
4047

4148
conf = discovery_info[CONF_CONF]
4249
meter = discovery_info[CONF_METER]
50+
adapter = discovery_info[CONF_ADAPTER]
4351
source = conf[CONF_SOURCE_SENSOR]
4452
price = conf.get(CONF_PRICE)
4553
price_entity = conf.get(CONF_PRICE_ENTITY)
4654

55+
config = {
56+
adapter.stat_energy_key: source,
57+
adapter.total_money_key: None,
58+
"entity_energy_price": price_entity,
59+
"number_energy_price": price,
60+
}
61+
4762
async_add_entities(
4863
[
49-
EnergyCostSensor(
50-
source,
51-
price,
52-
price_entity,
53-
conf_to_cost_sensor_id(meter, conf),
54-
),
64+
EnergyCostSensor(adapter, config, conf_to_cost_sensor_id(meter, conf)),
5565
],
5666
)
5767

@@ -67,28 +77,12 @@ class EnergyCostSensor(BaseEnergyCostSensor):
6777

6878
def __init__(
6979
self,
70-
source_entity: str,
71-
price: float | None,
72-
price_entity: str | None,
80+
adapter: SourceAdapter,
81+
config: dict,
7382
sensor_id: tuple[str],
7483
) -> None:
7584
"""Initialize the sensor."""
76-
super().__init__(
77-
SourceAdapter(
78-
source_type="grid",
79-
flow_type="flow_from",
80-
stat_energy_key="stat_energy_from",
81-
total_money_key="stat_cost",
82-
name_suffix="Cost",
83-
entity_id_suffix="cost",
84-
),
85-
{
86-
"stat_energy_from": source_entity,
87-
"stat_cost": None,
88-
"entity_energy_price": price_entity,
89-
"number_energy_price": price,
90-
},
91-
)
85+
super().__init__(adapter, config)
9286
# override the entity_id
9387
self._sensor_id = sensor_id
9488
suggested_entity_id = (

0 commit comments

Comments
 (0)