Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance schedule handling and cooling #511

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exclude: ^(fixtures/)

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4
rev: v0.6.5
hooks:
- id: ruff
args:
Expand Down
2 changes: 1 addition & 1 deletion fixtures/homesdata.json
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@
"name": "Default",
"selected": true,
"id": "591b54a2764ff4d50d8b5795",
"type": "therm"
"type": "cooling"
},
{
"zones": [
Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ ignore = [
"UP007", # keep type annotation style as is
# Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
# need cleanup
"FBT001",
"FBT002",
"FBT003",
"DTZ006",
"DTZ005",
"PGH003",
"ANN401",
]

[tool.ruff.lint.flake8-pytest-style]
Expand Down
4 changes: 2 additions & 2 deletions src/pyatmo/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AsyncAccount:
def __init__(
self,
auth: AbstractAsyncAuth,
favorite_stations: bool = True, # noqa: FBT001, FBT002
favorite_stations: bool = True,
) -> None:
"""Initialize the Netatmo account."""

Expand Down Expand Up @@ -148,7 +148,7 @@ def register_public_weather_area(
lat_sw: str,
lon_sw: str,
required_data_type: str | None = None,
filtering: bool = False, # noqa: FBT001, FBT002
filtering: bool = False,
*,
area_id: str = str(uuid4()),
) -> str:
Expand Down
4 changes: 2 additions & 2 deletions src/pyatmo/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async def async_get_image(
url = (base_url or self.base_url) + endpoint
async with self.websession.get(
url,
**req_args, # type: ignore # noqa: PGH003
**req_args, # type: ignore
headers=headers,
timeout=ClientTimeout(total=timeout),
) as resp:
Expand Down Expand Up @@ -115,7 +115,7 @@ async def async_post_request(

async with self.websession.post(
url,
**req_args, # type: ignore # noqa: PGH003
**req_args, # type: ignore
headers=headers,
timeout=ClientTimeout(total=timeout),
) as resp:
Expand Down
15 changes: 10 additions & 5 deletions src/pyatmo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,20 @@
"write_thermostat", # Netatmo climate products
]

EVENTS = "events"
SCHEDULES = "schedules"

MANUAL = "manual"
MAX = "max"
HOME = "home"
FROSTGUARD = "hg"
SCHEDULES = "schedules"
EVENTS = "events"
SCHEDULE = "schedule"
OFF = "off"
AWAY = "away"

HEATING = "heating"
COOLING = "cooling"
IDLE = "idle"

STATION_TEMPERATURE_TYPE = "temperature"
STATION_PRESSURE_TYPE = "pressure"
Expand All @@ -106,6 +114,3 @@
MAX_HISTORY_TIME_FRAME = 24 * 2 * 3600

UNKNOWN = "unknown"

ON = True
OFF = False
2 changes: 1 addition & 1 deletion src/pyatmo/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def fix_id(raw_data: RawData) -> dict[str, Any]:
return raw_data


def extract_raw_data(resp: Any, tag: str) -> dict[str, Any]: # noqa: ANN401
def extract_raw_data(resp: Any, tag: str) -> dict[str, Any]:
"""Extract raw data from server response."""
raw_data = {}

Expand Down
29 changes: 26 additions & 3 deletions src/pyatmo/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from pyatmo.modules.netatmo import NACamera
from pyatmo.person import Person
from pyatmo.room import Room
from pyatmo.schedule import Schedule
from pyatmo.schedule import Schedule, ScheduleType

if TYPE_CHECKING:
from aiohttp import ClientResponse
Expand All @@ -38,6 +38,12 @@
LOG = logging.getLogger(__name__)


SCHEDULE_TYPE_MAPPING = {
"heating": ScheduleType.THERM,
"cooling": ScheduleType.COOLING,
}


class Home:
"""Class to represent a Netatmo home."""

Expand Down Expand Up @@ -151,7 +157,7 @@ def update_topology(self, raw_data: RawData) -> None:
async def update(
self,
raw_data: RawData,
do_raise_for_reachability_error: bool = False, # noqa: FBT002, FBT001
do_raise_for_reachability_error: bool = False,
) -> None:
"""Update home with the latest data."""
has_error = False
Expand Down Expand Up @@ -210,10 +216,27 @@ def get_selected_schedule(self) -> Schedule | None:
"""Return selected schedule for given home."""

return next(
(schedule for schedule in self.schedules.values() if schedule.selected),
(
schedule
for schedule in self.schedules.values()
if schedule.selected
and self.temperature_control_mode
and schedule.type
== SCHEDULE_TYPE_MAPPING[self.temperature_control_mode]
),
None,
)

def get_available_schedules(self) -> list[Schedule]:
"""Return available schedules for given home."""

return [
schedule
for schedule in self.schedules.values()
if self.temperature_control_mode
and schedule.type == SCHEDULE_TYPE_MAPPING[self.temperature_control_mode]
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add inline comments to explain complex schedule handling logic

The logic in get_selected_schedule and get_available_schedules is quite dense. Consider adding some inline comments to explain the reasoning behind the conditions in the list comprehensions. This will make the code easier to understand and maintain.


def is_valid_schedule(self, schedule_id: str) -> bool:
"""Check if valid schedule."""

Expand Down
4 changes: 2 additions & 2 deletions src/pyatmo/modules/base_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
}


def default(key: str, val: Any) -> Any: # noqa: ANN401
def default(key: str, val: Any) -> Any:
"""Return default value."""

return lambda x, _: x.get(key, val)
Expand Down Expand Up @@ -103,7 +103,7 @@ def _update_attributes(self, raw_data: RawData) -> None:
def add_history_data(
self,
feature: str,
value: Any, # noqa: ANN401
value: Any,
time: int,
) -> None:
"""Add historical data at the given time."""
Expand Down
4 changes: 2 additions & 2 deletions src/pyatmo/modules/device_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from enum import Enum
import logging
from typing import Literal
from typing import Any, Literal

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -117,7 +117,7 @@ class DeviceType(str, Enum):
# pylint: enable=C0103

@classmethod
def _missing_(cls, key) -> Literal[DeviceType.NLunknown]: # noqa: ANN001
def _missing_(cls, key: Any) -> Literal[DeviceType.NLunknown]:
"""Handle unknown device types."""

msg = f"{key} device is unknown"
Expand Down
55 changes: 30 additions & 25 deletions src/pyatmo/modules/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from aiohttp import ClientConnectorError

from pyatmo.const import GETMEASURE_ENDPOINT, OFF, ON, RawData
from pyatmo.const import GETMEASURE_ENDPOINT, RawData
from pyatmo.exceptions import ApiError
from pyatmo.modules.base_class import EntityBase, NetatmoBase, Place, update_name
from pyatmo.modules.device_types import DEVICE_CATEGORY_MAP, DeviceCategory, DeviceType
Expand All @@ -23,8 +23,8 @@

LOG = logging.getLogger(__name__)

ModuleT = dict[str, Any]

ModuleT = dict[str, Any]
# Hide from features list
ATTRIBUTE_FILTER = {
"battery_state",
Expand Down Expand Up @@ -226,6 +226,7 @@ def __init__(self, home: Home, module: ModuleT) -> None:

super().__init__(home, module) # type: ignore # mypy issue 4335
self.boiler_status: bool | None = None
self.boiler_valve_comfort_boost: bool | None = None


class CoolerMixin(EntityBase):
Expand Down Expand Up @@ -354,7 +355,7 @@ def __init__(self, home: Home, module: ModuleT) -> None:
super().__init__(home, module) # type: ignore # mypy issue 4335
self.on: bool | None = None

async def async_set_switch(self, target_position: bool) -> bool: # noqa: FBT001
async def async_set_switch(self, target_position: bool) -> bool:
"""Set switch to target position."""

json_switch = {
Expand All @@ -371,12 +372,12 @@ async def async_set_switch(self, target_position: bool) -> bool: # noqa: FBT001
async def async_on(self) -> bool:
"""Switch on."""

return await self.async_set_switch(ON)
return await self.async_set_switch(True)

async def async_off(self) -> bool:
"""Switch off."""

return await self.async_set_switch(OFF)
return await self.async_set_switch(False)


class FanSpeedMixin(EntityBase):
Expand Down Expand Up @@ -657,7 +658,7 @@ class MeasureType(Enum):

def compute_riemann_sum(
power_data: list[tuple[int, float]],
conservative: bool = False, # noqa: FBT001, FBT002
conservative: bool = False,
) -> float:
"""Compute energy from power with a rieman sum."""

Expand Down Expand Up @@ -690,7 +691,7 @@ class EnergyHistoryMixin(EntityBase):
def __init__(self, home: Home, module: ModuleT) -> None:
"""Initialize history mixin."""

super().__init__(home, module) # type: ignore # mypy issue 4335
super().__init__(home, module) # type: ignore
self.historical_data: list[dict[str, Any]] | None = None
self.start_time: float | None = None
self.end_time: float | None = None
Expand All @@ -704,7 +705,7 @@ def __init__(self, home: Home, module: ModuleT) -> None:
def reset_measures(
self,
start_power_time: datetime,
in_reset: bool = True, # noqa: FBT001, FBT002
in_reset: bool = True,
) -> None:
"""Reset energy measures."""
self.in_reset = in_reset
Expand All @@ -720,7 +721,7 @@ def reset_measures(
def get_sum_energy_elec_power_adapted(
self,
to_ts: float | None = None,
conservative: bool = False, # noqa: FBT001, FBT002
conservative: bool = False,
) -> tuple[None, float] | tuple[float, float]:
"""Compute proper energy value with adaptation from power."""
v = self.sum_energy_elec
Expand Down Expand Up @@ -766,8 +767,8 @@ def _log_energy_error(
"ENERGY collection error %s %s %s %s %s %s %s",
msg,
self.name,
datetime.fromtimestamp(start_time), # noqa: DTZ006
datetime.fromtimestamp(end_time), # noqa: DTZ006
datetime.fromtimestamp(start_time),
datetime.fromtimestamp(end_time),
start_time,
end_time,
body or "NO BODY",
Expand All @@ -783,10 +784,10 @@ async def async_update_measures(
"""Update historical data."""

if end_time is None:
end_time = int(datetime.now().timestamp()) # noqa: DTZ005
end_time = int(datetime.now().timestamp())

if start_time is None:
end = datetime.fromtimestamp(end_time) # noqa: DTZ006
end = datetime.fromtimestamp(end_time)
start_time = int((end - timedelta(days=days)).timestamp())

prev_start_time = self.start_time
Expand Down Expand Up @@ -831,8 +832,8 @@ async def async_update_measures(
LOG.debug(
"NO VALUES energy update %s from: %s to %s, prev_sum=%s",
self.name,
datetime.fromtimestamp(start_time), # noqa: DTZ006
datetime.fromtimestamp(end_time), # noqa: DTZ006
datetime.fromtimestamp(start_time),
datetime.fromtimestamp(end_time),
prev_sum_energy_elec if prev_sum_energy_elec is not None else "NOTHING",
)
else:
Expand Down Expand Up @@ -912,14 +913,14 @@ async def _prepare_exported_historical_data(
LOG.debug(
msg,
self.name,
datetime.fromtimestamp(start_time), # noqa: DTZ006
datetime.fromtimestamp(end_time), # noqa: DTZ006
datetime.fromtimestamp(computed_start), # noqa: DTZ006
datetime.fromtimestamp(computed_end), # noqa: DTZ006
datetime.fromtimestamp(start_time),
datetime.fromtimestamp(end_time),
datetime.fromtimestamp(computed_start),
datetime.fromtimestamp(computed_end),
self.sum_energy_elec,
prev_sum_energy_elec,
datetime.fromtimestamp(prev_start_time), # noqa: DTZ006
datetime.fromtimestamp(prev_end_time), # noqa: DTZ006
datetime.fromtimestamp(prev_start_time),
datetime.fromtimestamp(prev_end_time),
)
else:
msg = (
Expand All @@ -929,10 +930,10 @@ async def _prepare_exported_historical_data(
LOG.debug(
msg,
self.name,
datetime.fromtimestamp(start_time), # noqa: DTZ006
datetime.fromtimestamp(end_time), # noqa: DTZ006
datetime.fromtimestamp(computed_start), # noqa: DTZ006
datetime.fromtimestamp(computed_end), # noqa: DTZ006
datetime.fromtimestamp(start_time),
datetime.fromtimestamp(end_time),
datetime.fromtimestamp(computed_start),
datetime.fromtimestamp(computed_end),
self.sum_energy_elec,
prev_sum_energy_elec if prev_sum_energy_elec is not None else "NOTHING",
)
Expand Down Expand Up @@ -1161,4 +1162,8 @@ class Energy(EnergyHistoryMixin, Module):
"""Class to represent a Netatmo energy module."""


class Boiler(BoilerMixin, Module):
"""Class to represent a Netatmo boiler."""


# pylint: enable=too-many-ancestors
2 changes: 1 addition & 1 deletion src/pyatmo/modules/netatmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def __init__(
lat_sw: str,
lon_sw: str,
required_data_type: str | None = None,
filtering: bool = False, # noqa: FBT001, FBT002
filtering: bool = False,
) -> None:
"""Initialize self."""

Expand Down
Loading