Skip to content

Commit

Permalink
Merge branch 'release/v7.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Dupras committed Oct 30, 2022
2 parents b4ec709 + 1b5921a commit 8156790
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 248 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ build/

dist/

pyatmo.egg-info/
**/pyatmo.egg-info/

*.pyc
.DS_Store
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

-

## [7.3.0]

### Added

- Add Legrand NLUI device class

### Changed

- Minor code clean ups

### Fixed

- Handle invalid ip addressed from the API more gracefully
- Let weather station devices register home even if home does not exist
- Catch ContentTypeError error and handle more graceful
- Fix key error when battery hits very_low
- Response handling issues

## [7.2.0]

Expand Down
3 changes: 0 additions & 3 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,3 @@ twine = "*"
requests = "*"
requests-oauthlib = "*"
aiohttp = ">3.8.1"

[pipenv]
allow_prereleases = true
300 changes: 83 additions & 217 deletions Pipfile.lock

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion fixtures/getstationsdata.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
"battery_percent": 79
},
{
"_id": "12:34:56:03:1b:e4",
"_id": "12:34:56:03:1b:e5",
"type": "NAModule2",
"module_name": "Garden",
"data_type": [
Expand Down Expand Up @@ -734,6 +734,32 @@
"date_min_temp": 1644582039,
"temp_trend": "stable"
}
},
{
"_id": "12:34:56:03:1b:e4",
"type": "NAModule2",
"module_name": "Garden",
"data_type": [
"Wind"
],
"last_setup": 1549193862,
"reachable": true,
"dashboard_data": {
"time_utc": 1559413170,
"WindStrength": 4,
"WindAngle": 217,
"GustStrength": 9,
"GustAngle": 206,
"max_wind_str": 21,
"max_wind_angle": 217,
"date_max_wind_str": 1559386669
},
"firmware": 19,
"last_message": 1559413177,
"last_seen": 1559413177,
"rf_status": 59,
"battery_vp": 5689,
"battery_percent": 85
}
]
},
Expand Down
21 changes: 20 additions & 1 deletion src/pyatmo/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,30 @@ async def update_devices(
self.find_home_of_device(device_data),
):
if home_id not in self.homes:
continue
modules_data = []
for module_data in device_data.get("modules", []):
module_data["home_id"] = home_id
module_data["id"] = module_data["_id"]
module_data["name"] = module_data.get("module_name")
modules_data.append(normalize_weather_attributes(module_data))
modules_data.append(normalize_weather_attributes(device_data))

self.homes[home_id] = Home(
self.auth,
raw_data={
"id": home_id,
"name": device_data.get("home_name", "Unknown"),
"modules": modules_data,
},
)
await self.homes[home_id].update(
{HOME: {"modules": [normalize_weather_attributes(device_data)]}},
)
else:
LOG.debug("No home %s found.", home_id)

for module_data in device_data.get("modules", []):
module_data["home_id"] = home_id
await self.update_devices({"devices": [module_data]})

if (
Expand Down
8 changes: 4 additions & 4 deletions src/pyatmo/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import Any, Callable

import requests
from aiohttp import ClientError, ClientResponse, ClientSession
from aiohttp import ClientError, ClientResponse, ClientSession, ContentTypeError
from oauthlib.oauth2 import LegacyApplicationClient, TokenExpiredError
from requests_oauthlib import OAuth2Session

Expand Down Expand Up @@ -121,7 +121,7 @@ def post_request(
timeout: int = 5,
) -> requests.Response:
"""Wrapper for post requests."""
resp = None
resp = requests.Response()
req_args = {"data": params if params is not None else {}}

if "json" in req_args["data"]:
Expand Down Expand Up @@ -166,7 +166,7 @@ def query(

resp = query(url, req_args, timeout, 3)

if resp is None:
if resp.status_code is None:
LOG.debug("Resp is None - %s", resp)
return requests.Response()

Expand Down Expand Up @@ -394,7 +394,7 @@ async def async_post_request(
f"when accessing '{url}'",
)

except JSONDecodeError as exc:
except (JSONDecodeError, ContentTypeError) as exc:
raise ApiError(
f"{resp_status} - "
f"{ERRORS.get(resp_status, '')} - "
Expand Down
23 changes: 18 additions & 5 deletions src/pyatmo/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,21 +496,34 @@ def update_camera_urls(self, camera_id: str) -> None:

if (vpn_url := camera_data.get("vpn_url")) and camera_data.get("is_local"):
if temp_local_url := self._check_url(vpn_url):
self.cameras[home_id][camera_id]["local_url"] = self._check_url(
temp_local_url,
)
if local_url := self._check_url(temp_local_url):
self.cameras[home_id][camera_id]["local_url"] = local_url
else:
LOG.warning(
"Invalid IP for camera %s (%s)",
self.cameras[home_id][camera_id]["name"],
temp_local_url,
)
self.cameras[home_id][camera_id]["is_local"] = False

def _check_url(self, url: str) -> str | None:
if url.startswith("http://169.254"):
return None
resp_json = {}
try:
resp = self.auth.post_request(url=f"{url}/command/ping").json()
resp = self.auth.post_request(url=f"{url}/command/ping")
if resp.status_code:
resp_json = resp.json()
else:
raise ReadTimeout
except ReadTimeout:
LOG.debug("Timeout validation of camera url %s", url)
return None
except ApiError:
LOG.debug("Api error for camera url %s", url)
return None

return resp.get("local_url") if resp else None
return resp_json.get("local_url") if resp_json else None

def set_state(
self,
Expand Down
1 change: 1 addition & 0 deletions src/pyatmo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
]

MANUAL = "manual"
MAX = "max"
HOME = "home"
FROSTGUARD = "hg"
SCHEDULES = "schedules"
Expand Down
6 changes: 6 additions & 0 deletions src/pyatmo/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ def extract_raw_data_new(resp: Any, tag: str) -> dict[str, Any]:
LOG.debug("Server response: %s", resp)
raise NoDevice("No device found, errors in response")

if tag == "homes":
return {
tag: fix_id(resp["body"].get(tag)),
"errors": resp["body"].get("errors", []),
}

if not (raw_data := fix_id(resp["body"].get(tag))):
LOG.debug("Server response: %s", resp)
raise NoDevice("No device data available")
Expand Down
2 changes: 2 additions & 0 deletions src/pyatmo/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ async def update(self, raw_data: RawData) -> None:
data = raw_data["home"]

for module in data.get("modules", []):
if module["id"] not in self.modules:
self.update_topology({"modules": [module]})
await self.modules[module["id"]].update(module)

for room in data.get("rooms", []):
Expand Down
6 changes: 5 additions & 1 deletion src/pyatmo/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Expose submodules."""
from .base_class import Place
from .bticino import BNCX, BNDL, BNSL
from .idiamant import NBG, NBR
from .idiamant import NBG, NBO, NBR, NBS
from .legrand import (
NLC,
NLD,
Expand All @@ -23,6 +23,7 @@
NLPS,
NLPT,
NLT,
NLUI,
NLV,
)
from .module import Camera, Dimmer, Module, Shutter, Switch
Expand Down Expand Up @@ -70,6 +71,8 @@
"NATherm1",
"NBG",
"NBR",
"NBO",
"NBS",
"NCO",
"NDB",
"NHC",
Expand All @@ -95,6 +98,7 @@
"NLPT",
"NLT",
"NLV",
"NLUI",
"NOC",
"NRV",
"NSD",
Expand Down
1 change: 1 addition & 0 deletions src/pyatmo/modules/device_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class DeviceType(str, Enum):
NLPT = "NLPT" # Connected latching relay / Telerupt
NLT = "NLT" # Global remote control
NLV = "NLV" # Legrand / BTicino shutters
NLUI = "NLUI" # Legrand device stub

# BTicino Classe 300 EOS
BNCX = "BNCX" # internal panel = gateway
Expand Down
8 changes: 8 additions & 0 deletions src/pyatmo/modules/idiamant.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ class NBG(FirmwareMixin, WifiMixin, Module):

class NBR(FirmwareMixin, RfMixin, ShutterMixin, Module):
...


class NBO(FirmwareMixin, RfMixin, ShutterMixin, Module):
...


class NBS(FirmwareMixin, RfMixin, ShutterMixin, Module):
...
4 changes: 4 additions & 0 deletions src/pyatmo/modules/legrand.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,7 @@ class NLPS(FirmwareMixin, PowerMixin, EnergyMixin, Module):

class NLC(FirmwareMixin, SwitchMixin, Module):
"""Legrand / BTicino cable outlet."""


class NLUI(FirmwareMixin, Module):
"""Legrand NLUI device stubs."""
20 changes: 13 additions & 7 deletions src/pyatmo/modules/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict

from aiohttp import ClientConnectorError

from pyatmo.const import GETMEASURE_ENDPOINT, RawData
from pyatmo.exceptions import ApiError
from pyatmo.modules.base_class import EntityBase, NetatmoBase, Place
Expand Down Expand Up @@ -49,7 +51,7 @@ def process_battery_state(data: str) -> int:
"high": 75,
"medium": 50,
"low": 25,
"very low": 10,
"very_low": 10,
}
return mapping[data]

Expand Down Expand Up @@ -324,9 +326,14 @@ async def async_update_camera_urls(self) -> None:
if self.vpn_url and self.is_local:
temp_local_url = await self._async_check_url(self.vpn_url)
if temp_local_url:
self.local_url = await self._async_check_url(
temp_local_url,
)
try:
self.local_url = await self._async_check_url(
temp_local_url,
)
except ClientConnectorError as exc:
LOG.debug("Cannot connect to %s - reason: %s", temp_local_url, exc)
self.is_local = False
self.local_url = None

async def _async_check_url(self, url: str) -> str | None:
"""Validate camera url."""
Expand Down Expand Up @@ -465,19 +472,18 @@ async def async_update_measures(
raw_data = await resp.json()

data = raw_data["body"][0]
self.start_time = int(data["beg_time"])
interval_sec = int(data["step_time"])
interval_min = interval_sec // 60

self.historical_data = []
self.start_time = int(data["beg_time"])
start_time = self.start_time
for value in data["value"]:
end_time = start_time + interval_sec
self.historical_data.append(
{
"duration": interval_min,
"startTime": datetime.utcfromtimestamp(start_time + 1).isoformat()
+ "Z",
"startTime": f"{datetime.utcfromtimestamp(start_time + 1).isoformat()}Z",
"endTime": f"{datetime.utcfromtimestamp(end_time).isoformat()}Z",
"Wh": value[0],
},
Expand Down
16 changes: 8 additions & 8 deletions src/pyatmo/thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ def update(self) -> None:
def set_thermmode(
self,
mode: str,
end_time: int = None,
schedule_id: str = None,
end_time: int | None = None,
schedule_id: str | None = None,
) -> str | None:
"""Set thermotat mode."""
post_params = {"home_id": self.home_id, "mode": mode}
Expand All @@ -287,8 +287,8 @@ def set_room_thermpoint(
self,
room_id: str,
mode: str,
temp: float = None,
end_time: int = None,
temp: float | None = None,
end_time: int | None = None,
) -> str | None:
"""Set room themperature set point."""
post_params = {"home_id": self.home_id, "room_id": room_id, "mode": mode}
Expand Down Expand Up @@ -333,8 +333,8 @@ async def async_update(self) -> None:
async def async_set_thermmode(
self,
mode: str,
end_time: int = None,
schedule_id: str = None,
end_time: int | None = None,
schedule_id: str | None = None,
) -> str | None:
"""Set thermotat mode."""
post_params = {"home_id": self.home_id, "mode": mode}
Expand All @@ -355,8 +355,8 @@ async def async_set_room_thermpoint(
self,
room_id: str,
mode: str,
temp: float = None,
end_time: int = None,
temp: float | None = None,
end_time: int | None = None,
) -> str | None:
"""Set room themperature set point."""
post_params = {"home_id": self.home_id, "room_id": room_id, "mode": mode}
Expand Down

0 comments on commit 8156790

Please sign in to comment.