From 7f11b6b0b4548c30208b37bceeba9b99bde5a6db Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Sat, 17 Aug 2024 08:44:21 +0200 Subject: [PATCH] Add cooler support --- CHANGELOG.md | 6 +++++- fixtures/homesdata.json | 2 ++ src/pyatmo/account.py | 3 +-- src/pyatmo/const.py | 3 ++- src/pyatmo/home.py | 20 +++++++++++++++++ src/pyatmo/modules/module.py | 16 ++++++++------ src/pyatmo/modules/smarther.py | 10 +++++++-- src/pyatmo/room.py | 39 +++++++++++++++++++++++++++++----- tests/test_home.py | 2 ++ 9 files changed, 84 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d261e77d..78e995a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- +- Expose camera person status +- Add NLE support +- Add proper energy support +- Add cooler support +- Add BNS support ### Changed diff --git a/fixtures/homesdata.json b/fixtures/homesdata.json index 7e7e753e..e5623ad4 100644 --- a/fixtures/homesdata.json +++ b/fixtures/homesdata.json @@ -805,6 +805,8 @@ } ], "therm_setpoint_default_duration": 120, + "temperature_control_mode": "cooling", + "cooling_mode": "schedule", "persons": [ { "id": "91827374-7e04-5298-83ad-a0cb8372dff1", diff --git a/src/pyatmo/account.py b/src/pyatmo/account.py index 466337cc..ff871573 100644 --- a/src/pyatmo/account.py +++ b/src/pyatmo/account.py @@ -57,7 +57,6 @@ def process_topology(self, disabled_homes_ids: list[str] | None = None) -> None: disabled_homes_ids = [] for home in self.raw_data["homes"]: - home_id = home.get("id", "Unknown") home_name = home.get("name", "Unknown") self.all_homes_id[home_id] = home_name @@ -237,7 +236,7 @@ async def update_devices( {HOME: {"modules": [normalize_weather_attributes(device_data)]}}, ) else: - LOG.debug("No home %s found.", home_id) + LOG.debug("No home %s (%s) found.", home_id, home_id) for module_data in device_data.get("modules", []): module_data["home_id"] = home_id diff --git a/src/pyatmo/const.py b/src/pyatmo/const.py index 1a471d67..0b141743 100644 --- a/src/pyatmo/const.py +++ b/src/pyatmo/const.py @@ -88,7 +88,6 @@ SCHEDULES = "schedules" EVENTS = "events" - STATION_TEMPERATURE_TYPE = "temperature" STATION_PRESSURE_TYPE = "pressure" STATION_HUMIDITY_TYPE = "humidity" @@ -105,3 +104,5 @@ # 2 days of dynamic historical data stored MAX_HISTORY_TIME_FRAME = 24 * 2 * 3600 + +UNKNOWN = "unknown" diff --git a/src/pyatmo/home.py b/src/pyatmo/home.py index 81531d1a..b11f37cf 100644 --- a/src/pyatmo/home.py +++ b/src/pyatmo/home.py @@ -49,6 +49,11 @@ class Home: persons: dict[str, Person] events: dict[str, Event] + temperature_control_mode: str | None = None + therm_mode: str | None = None + therm_setpoint_default_duration: int | None = None + cooling_mode: str | None = None + def __init__(self, auth: AbstractAsyncAuth, raw_data: RawData) -> None: """Initialize a Netatmo home instance.""" @@ -76,6 +81,13 @@ def __init__(self, auth: AbstractAsyncAuth, raw_data: RawData) -> None: } self.events = {} + self.temperature_control_mode = raw_data.get("temperature_control_mode") + self.therm_mode = raw_data.get("therm_mode") + self.therm_setpoint_default_duration = raw_data.get( + "therm_setpoint_default_duration", + ) + self.cooling_mode = raw_data.get("cooling_mode") + def get_module(self, module: dict) -> Module: """Return module.""" @@ -97,6 +109,14 @@ def update_topology(self, raw_data: RawData) -> None: self.name = raw_data.get("name", "Unknown") raw_modules = raw_data.get("modules", []) + + self.temperature_control_mode = raw_data.get("temperature_control_mode") + self.therm_mode = raw_data.get("therm_mode") + self.therm_setpoint_default_duration = raw_data.get( + "therm_setpoint_default_duration", + ) + self.cooling_mode = raw_data.get("cooling_mode") + for module in raw_modules: if (module_id := module["id"]) not in self.modules: self.modules[module_id] = self.get_module(module) diff --git a/src/pyatmo/modules/module.py b/src/pyatmo/modules/module.py index 64901659..2f5a0296 100644 --- a/src/pyatmo/modules/module.py +++ b/src/pyatmo/modules/module.py @@ -228,6 +228,16 @@ def __init__(self, home: Home, module: ModuleT): self.boiler_status: bool | None = None +class CoolerMixin(EntityBase): + """Mixin for cooler data.""" + + def __init__(self, home: Home, module: ModuleT): + """Initialize cooler mixin.""" + + super().__init__(home, module) # type: ignore # mypy issue 4335 + self.cooler_status: bool | None = None + + class BatteryMixin(EntityBase): """Mixin for battery data.""" @@ -649,13 +659,11 @@ def compute_riemann_sum( delta_energy = 0 if power_data and len(power_data) > 1: - # compute a rieman sum, as best as possible , trapezoidal, taking pessimistic asumption # as we don't want to artifically go up the previous one # (except in rare exceptions like reset, 0 , etc) for i in range(len(power_data) - 1): - dt_h = float(power_data[i + 1][0] - power_data[i][0]) / 3600.0 if conservative: @@ -713,7 +721,6 @@ def get_sum_energy_elec_power_adapted( delta_energy = 0 if not self.in_reset: - if to_ts is None: to_ts = int(time()) @@ -811,7 +818,6 @@ async def async_update_measures( prev_sum_energy_elec if prev_sum_energy_elec is not None else "NOTHING", ) else: - await self._prepare_exported_historical_data( start_time, end_time, @@ -836,7 +842,6 @@ async def _prepare_exported_historical_data( computed_end = 0 computed_end_for_calculus = 0 for cur_start_time, val, vals in hist_good_vals: - self.sum_energy_elec += val modes = [] @@ -971,7 +976,6 @@ def _get_energy_filers(self): return ENERGY_FILTERS async def _energy_API_calls(self, start_time, end_time, interval): - filters = self._get_energy_filers() params = { diff --git a/src/pyatmo/modules/smarther.py b/src/pyatmo/modules/smarther.py index 09aaffa5..871365ee 100644 --- a/src/pyatmo/modules/smarther.py +++ b/src/pyatmo/modules/smarther.py @@ -4,10 +4,16 @@ import logging -from pyatmo.modules.module import BoilerMixin, FirmwareMixin, Module, WifiMixin +from pyatmo.modules.module import ( + BoilerMixin, + CoolerMixin, + FirmwareMixin, + Module, + WifiMixin, +) LOG = logging.getLogger(__name__) -class BNS(FirmwareMixin, BoilerMixin, WifiMixin, Module): +class BNS(FirmwareMixin, BoilerMixin, CoolerMixin, WifiMixin, Module): """Smarther thermostat.""" diff --git a/src/pyatmo/room.py b/src/pyatmo/room.py index 28578bb0..ba78bc69 100644 --- a/src/pyatmo/room.py +++ b/src/pyatmo/room.py @@ -6,7 +6,14 @@ import logging from typing import TYPE_CHECKING, Any -from pyatmo.const import FROSTGUARD, HOME, MANUAL, SETROOMTHERMPOINT_ENDPOINT, RawData +from pyatmo.const import ( + FROSTGUARD, + HOME, + MANUAL, + SETROOMTHERMPOINT_ENDPOINT, + UNKNOWN, + RawData, +) from pyatmo.modules.base_class import NetatmoBase from pyatmo.modules.device_types import DeviceType @@ -29,15 +36,25 @@ class Room(NetatmoBase): climate_type: DeviceType | None = None - heating_power_request: int | None = None humidity: int | None = None + therm_measured_temperature: float | None = None + reachable: bool | None = None + + heating_power_request: int | None = None therm_setpoint_temperature: float | None = None therm_setpoint_mode: str | None = None - therm_measured_temperature: float | None = None therm_setpoint_start_time: int | None = None therm_setpoint_end_time: int | None = None + anticipating: bool | None = None + open_window: bool | None = None + + cooling_setpoint_temperature: float | None = None + cooling_setpoint_start_time: int | None = None + cooling_setpoint_end_time: int | None = None + cooling_setpoint_mode: str | None = None + def __init__( self, home: Home, @@ -60,7 +77,7 @@ def __init__( def update_topology(self, raw_data: RawData) -> None: """Update room topology.""" - self.name = raw_data["name"] + self.name = raw_data.get("name", UNKNOWN) self.modules = { m_id: m for m_id, m in self.home.modules.items() @@ -91,19 +108,31 @@ def evaluate_device_type(self) -> None: def update(self, raw_data: RawData) -> None: """Update room data.""" - self.heating_power_request = raw_data.get("heating_power_request") self.humidity = raw_data.get("humidity") if self.climate_type == DeviceType.BNTH: # BNTH is wired, so the room is always reachable self.reachable = True else: self.reachable = raw_data.get("reachable") + self.therm_measured_temperature = raw_data.get("therm_measured_temperature") + + self.reachable = raw_data.get("reachable") + + self.heating_power_request = raw_data.get("heating_power_request") self.therm_setpoint_mode = raw_data.get("therm_setpoint_mode") self.therm_setpoint_temperature = raw_data.get("therm_setpoint_temperature") self.therm_setpoint_start_time = raw_data.get("therm_setpoint_start_time") self.therm_setpoint_end_time = raw_data.get("therm_setpoint_end_time") + self.anticipating = raw_data.get("anticipating") + self.open_window = raw_data.get("open_window") + + self.cooling_setpoint_temperature = raw_data.get("cooling_setpoint_temperature") + self.cooling_setpoint_start_time = raw_data.get("cooling_setpoint_start_time") + self.cooling_setpoint_end_time = raw_data.get("cooling_setpoint_end_time") + self.cooling_setpoint_mode = raw_data.get("cooling_setpoint_mode") + async def async_therm_manual( self, temp: float | None = None, diff --git a/tests/test_home.py b/tests/test_home.py index 70e85b6c..ac4a91c3 100644 --- a/tests/test_home.py +++ b/tests/test_home.py @@ -39,6 +39,8 @@ async def test_async_home(async_home): module = async_home.modules[module_id] assert module.device_type == DeviceType.NOC + assert async_home.temperature_control_mode == "cooling" + @pytest.mark.asyncio async def test_async_home_set_schedule(async_home):