2021-11-07 01:41:12 +00:00
|
|
|
"""Implementation of the emeter module."""
|
2024-04-16 18:21:20 +00:00
|
|
|
|
2024-04-17 13:39:24 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-11-19 15:41:49 +00:00
|
|
|
from datetime import datetime
|
|
|
|
|
2024-04-25 12:59:17 +00:00
|
|
|
from ... import Device
|
2024-02-04 15:20:08 +00:00
|
|
|
from ...emeterstatus import EmeterStatus
|
2024-04-25 12:59:17 +00:00
|
|
|
from ...feature import Feature
|
2021-11-07 01:41:12 +00:00
|
|
|
from .usage import Usage
|
|
|
|
|
|
|
|
|
|
|
|
class Emeter(Usage):
|
|
|
|
"""Emeter module."""
|
|
|
|
|
2024-04-25 12:59:17 +00:00
|
|
|
def __init__(self, device: Device, module: str):
|
|
|
|
super().__init__(device, module)
|
|
|
|
self._add_feature(
|
|
|
|
Feature(
|
|
|
|
device,
|
|
|
|
name="Current consumption",
|
|
|
|
attribute_getter="current_consumption",
|
|
|
|
container=self,
|
|
|
|
unit="W",
|
|
|
|
id="current_power_w", # for homeassistant backwards compat
|
2024-04-29 11:31:42 +00:00
|
|
|
precision_hint=1,
|
2024-05-07 09:13:35 +00:00
|
|
|
category=Feature.Category.Primary,
|
2024-04-25 12:59:17 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self._add_feature(
|
|
|
|
Feature(
|
|
|
|
device,
|
|
|
|
name="Today's consumption",
|
|
|
|
attribute_getter="emeter_today",
|
|
|
|
container=self,
|
|
|
|
unit="kWh",
|
|
|
|
id="today_energy_kwh", # for homeassistant backwards compat
|
2024-04-29 11:31:42 +00:00
|
|
|
precision_hint=3,
|
2024-05-07 09:13:35 +00:00
|
|
|
category=Feature.Category.Info,
|
2024-04-25 12:59:17 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self._add_feature(
|
|
|
|
Feature(
|
|
|
|
device,
|
2024-05-07 09:13:35 +00:00
|
|
|
id="consumption_this_month",
|
2024-04-25 12:59:17 +00:00
|
|
|
name="This month's consumption",
|
|
|
|
attribute_getter="emeter_this_month",
|
|
|
|
container=self,
|
|
|
|
unit="kWh",
|
2024-04-29 11:31:42 +00:00
|
|
|
precision_hint=3,
|
2024-05-07 09:13:35 +00:00
|
|
|
category=Feature.Category.Info,
|
2024-04-25 12:59:17 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self._add_feature(
|
|
|
|
Feature(
|
|
|
|
device,
|
|
|
|
name="Total consumption since reboot",
|
|
|
|
attribute_getter="emeter_total",
|
|
|
|
container=self,
|
|
|
|
unit="kWh",
|
|
|
|
id="total_energy_kwh", # for homeassistant backwards compat
|
2024-04-29 11:31:42 +00:00
|
|
|
precision_hint=3,
|
2024-05-07 09:13:35 +00:00
|
|
|
category=Feature.Category.Info,
|
2024-04-25 12:59:17 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self._add_feature(
|
|
|
|
Feature(
|
|
|
|
device,
|
|
|
|
name="Voltage",
|
|
|
|
attribute_getter="voltage",
|
|
|
|
container=self,
|
|
|
|
unit="V",
|
|
|
|
id="voltage", # for homeassistant backwards compat
|
2024-04-29 11:31:42 +00:00
|
|
|
precision_hint=1,
|
2024-05-07 09:13:35 +00:00
|
|
|
category=Feature.Category.Primary,
|
2024-04-25 12:59:17 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self._add_feature(
|
|
|
|
Feature(
|
|
|
|
device,
|
|
|
|
name="Current",
|
|
|
|
attribute_getter="current",
|
|
|
|
container=self,
|
|
|
|
unit="A",
|
|
|
|
id="current_a", # for homeassistant backwards compat
|
2024-04-29 11:31:42 +00:00
|
|
|
precision_hint=2,
|
2024-05-07 09:13:35 +00:00
|
|
|
category=Feature.Category.Primary,
|
2024-04-25 12:59:17 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2021-11-07 01:41:12 +00:00
|
|
|
@property # type: ignore
|
|
|
|
def realtime(self) -> EmeterStatus:
|
|
|
|
"""Return current energy readings."""
|
|
|
|
return EmeterStatus(self.data["get_realtime"])
|
|
|
|
|
2021-11-19 15:41:49 +00:00
|
|
|
@property
|
2024-04-17 13:39:24 +00:00
|
|
|
def emeter_today(self) -> float | None:
|
2021-11-19 15:41:49 +00:00
|
|
|
"""Return today's energy consumption in kWh."""
|
|
|
|
raw_data = self.daily_data
|
|
|
|
today = datetime.now().day
|
2024-01-05 01:01:00 +00:00
|
|
|
data = self._convert_stat_data(raw_data, entry_key="day", key=today)
|
2021-11-19 15:41:49 +00:00
|
|
|
return data.get(today)
|
|
|
|
|
|
|
|
@property
|
2024-04-17 13:39:24 +00:00
|
|
|
def emeter_this_month(self) -> float | None:
|
2021-11-19 15:41:49 +00:00
|
|
|
"""Return this month's energy consumption in kWh."""
|
|
|
|
raw_data = self.monthly_data
|
|
|
|
current_month = datetime.now().month
|
2024-01-05 01:01:00 +00:00
|
|
|
data = self._convert_stat_data(raw_data, entry_key="month", key=current_month)
|
2021-11-19 15:41:49 +00:00
|
|
|
return data.get(current_month)
|
|
|
|
|
2024-04-25 12:59:17 +00:00
|
|
|
@property
|
|
|
|
def current_consumption(self) -> float | None:
|
|
|
|
"""Get the current power consumption in Watt."""
|
|
|
|
return self.realtime.power
|
|
|
|
|
|
|
|
@property
|
|
|
|
def emeter_total(self) -> float | None:
|
|
|
|
"""Return total consumption since last reboot in kWh."""
|
|
|
|
return self.realtime.total
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current(self) -> float | None:
|
|
|
|
"""Return the current in A."""
|
|
|
|
return self.realtime.current
|
|
|
|
|
|
|
|
@property
|
|
|
|
def voltage(self) -> float | None:
|
|
|
|
"""Get the current voltage in V."""
|
|
|
|
return self.realtime.voltage
|
|
|
|
|
2021-11-07 01:41:12 +00:00
|
|
|
async def erase_stats(self):
|
2021-11-19 15:41:49 +00:00
|
|
|
"""Erase all stats.
|
|
|
|
|
|
|
|
Uses different query than usage meter.
|
|
|
|
"""
|
2021-11-07 01:41:12 +00:00
|
|
|
return await self.call("erase_emeter_stat")
|
2021-11-19 15:41:49 +00:00
|
|
|
|
2022-01-29 19:36:08 +00:00
|
|
|
async def get_realtime(self):
|
|
|
|
"""Return real-time statistics."""
|
|
|
|
return await self.call("get_realtime")
|
|
|
|
|
2024-04-17 13:39:24 +00:00
|
|
|
async def get_daystat(self, *, year=None, month=None, kwh=True) -> dict:
|
2023-10-29 22:15:42 +00:00
|
|
|
"""Return daily stats for the given year & month.
|
|
|
|
|
|
|
|
The return value is a dictionary of {day: energy, ...}.
|
|
|
|
"""
|
2023-02-18 19:53:02 +00:00
|
|
|
data = await self.get_raw_daystat(year=year, month=month)
|
|
|
|
data = self._convert_stat_data(data["day_list"], entry_key="day", kwh=kwh)
|
|
|
|
return data
|
|
|
|
|
2024-04-17 13:39:24 +00:00
|
|
|
async def get_monthstat(self, *, year=None, kwh=True) -> dict:
|
2023-10-29 22:15:42 +00:00
|
|
|
"""Return monthly stats for the given year.
|
|
|
|
|
|
|
|
The return value is a dictionary of {month: energy, ...}.
|
|
|
|
"""
|
2023-02-18 19:53:02 +00:00
|
|
|
data = await self.get_raw_monthstat(year=year)
|
|
|
|
data = self._convert_stat_data(data["month_list"], entry_key="month", kwh=kwh)
|
|
|
|
return data
|
2021-11-19 15:41:49 +00:00
|
|
|
|
2024-01-05 01:01:00 +00:00
|
|
|
def _convert_stat_data(
|
|
|
|
self,
|
2024-04-17 13:39:24 +00:00
|
|
|
data: list[dict[str, int | float]],
|
2024-01-05 01:01:00 +00:00
|
|
|
entry_key: str,
|
2024-01-24 08:10:55 +00:00
|
|
|
kwh: bool = True,
|
2024-04-17 13:39:24 +00:00
|
|
|
key: int | None = None,
|
|
|
|
) -> dict[int | float, int | float]:
|
2023-02-18 19:53:02 +00:00
|
|
|
"""Return emeter information keyed with the day/month.
|
2021-11-19 15:41:49 +00:00
|
|
|
|
2023-02-18 19:53:02 +00:00
|
|
|
The incoming data is a list of dictionaries::
|
2021-11-19 15:41:49 +00:00
|
|
|
|
2023-02-18 19:53:02 +00:00
|
|
|
[{'year': int,
|
|
|
|
'month': int,
|
|
|
|
'day': int, <-- for get_daystat not get_monthstat
|
|
|
|
'energy_wh': int, <-- for emeter in some versions (wh)
|
|
|
|
'energy': float <-- for emeter in other versions (kwh)
|
|
|
|
}, ...]
|
|
|
|
|
|
|
|
:return: a dictionary keyed by day or month with energy as the value.
|
|
|
|
"""
|
|
|
|
if not data:
|
2021-11-19 15:41:49 +00:00
|
|
|
return {}
|
|
|
|
|
2023-02-18 19:53:02 +00:00
|
|
|
scale: float = 1
|
2021-11-19 15:41:49 +00:00
|
|
|
|
2023-02-18 19:53:02 +00:00
|
|
|
if "energy_wh" in data[0]:
|
|
|
|
value_key = "energy_wh"
|
|
|
|
if kwh:
|
|
|
|
scale = 1 / 1000
|
|
|
|
else:
|
|
|
|
value_key = "energy"
|
|
|
|
if not kwh:
|
|
|
|
scale = 1000
|
2021-11-19 15:41:49 +00:00
|
|
|
|
2024-01-05 01:01:00 +00:00
|
|
|
if key is None:
|
|
|
|
# Return all the data
|
|
|
|
return {entry[entry_key]: entry[value_key] * scale for entry in data}
|
|
|
|
|
|
|
|
# In this case we want a specific key in the data
|
|
|
|
# i.e. the current day or month.
|
|
|
|
#
|
|
|
|
# Since we usually want the data at the end of the list so we can
|
|
|
|
# optimize the search by starting at the end and avoid scaling
|
|
|
|
# the data we don't need.
|
|
|
|
#
|
|
|
|
for entry in reversed(data):
|
|
|
|
if entry[entry_key] == key:
|
|
|
|
return {entry[entry_key]: entry[value_key] * scale}
|
|
|
|
|
|
|
|
return {}
|