Add tests for Netatmo climate (#46392)

* Add tests for Netatmo climate

* Add comments and fake webhook events

* Split tests

* Split tests

* Clean up

* Fix coveragerc

* Fix requirements

* Remove freezegun dependency

* Move async_block_till_done to

* Call async_handle_webhook directly

* Use async_handle_webhook directly p2

* Exclude helper.py from

* Remove assertion of implementation details

* Use the webhook integration handler

* Extract function
This commit is contained in:
Tobias Sauerwein 2021-03-15 12:45:36 +01:00 committed by GitHub
parent cfeb8eb06a
commit 6b98583bc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2343 additions and 6 deletions

View File

@ -635,8 +635,6 @@ omit =
homeassistant/components/netatmo/__init__.py homeassistant/components/netatmo/__init__.py
homeassistant/components/netatmo/api.py homeassistant/components/netatmo/api.py
homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/camera.py
homeassistant/components/netatmo/climate.py
homeassistant/components/netatmo/const.py
homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/data_handler.py
homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/helper.py
homeassistant/components/netatmo/light.py homeassistant/components/netatmo/light.py

View File

@ -2,6 +2,7 @@
import logging import logging
from typing import List, Optional from typing import List, Optional
import pyatmo
import voluptuous as vol import voluptuous as vol
from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate import ClimateEntity
@ -251,7 +252,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
"""Handle webhook events.""" """Handle webhook events."""
data = event["data"] data = event["data"]
if not data.get("home"): if data.get("home") is None:
return return
home = data["home"] home = data["home"]
@ -569,7 +570,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
schedule_id = sid schedule_id = sid
if not schedule_id: if not schedule_id:
_LOGGER.error("You passed an invalid schedule") _LOGGER.error(
"%s is not a invalid schedule", kwargs.get(ATTR_SCHEDULE_NAME)
)
return return
self._data.switch_home_schedule(home_id=self._home_id, schedule_id=schedule_id) self._data.switch_home_schedule(home_id=self._home_id, schedule_id=schedule_id)
@ -586,7 +589,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
return {**super().device_info, "suggested_area": self._room_data["name"]} return {**super().device_info, "suggested_area": self._room_data["name"]}
def interpolate(batterylevel, module_type): def interpolate(batterylevel: int, module_type: str) -> int:
"""Interpolate battery level depending on device type.""" """Interpolate battery level depending on device type."""
na_battery_levels = { na_battery_levels = {
NA_THERM: { NA_THERM: {
@ -628,7 +631,7 @@ def interpolate(batterylevel, module_type):
return int(pct) return int(pct)
def get_all_home_ids(home_data): def get_all_home_ids(home_data: pyatmo.HomeData) -> List[str]:
"""Get all the home ids returned by NetAtmo API.""" """Get all the home ids returned by NetAtmo API."""
if home_data is None: if home_data is None:
return [] return []

View File

@ -0,0 +1,44 @@
"""Common methods used across tests for Netatmo."""
import json
from tests.common import load_fixture
CLIENT_ID = "1234"
CLIENT_SECRET = "5678"
ALL_SCOPES = [
"read_station",
"read_camera",
"access_camera",
"write_camera",
"read_presence",
"access_presence",
"write_presence",
"read_homecoach",
"read_smokedetector",
"read_thermostat",
"write_thermostat",
]
def fake_post_request(**args):
"""Return fake data."""
if "url" not in args:
return "{}"
endpoint = args["url"].split("/")[-1]
if endpoint in [
"setpersonsaway",
"setpersonshome",
"setstate",
"setroomthermpoint",
"setthermmode",
"switchhomeschedule",
]:
return f'{{"{endpoint}": true}}'
return json.loads(load_fixture(f"netatmo/{endpoint}.json"))
def fake_post_request_no_data(**args):
"""Fake error during requesting backend data."""
return "{}"

View File

@ -0,0 +1,127 @@
"""Provide common Netatmo fixtures."""
from contextlib import contextmanager
from time import time
from unittest.mock import patch
import pytest
from .common import ALL_SCOPES, fake_post_request, fake_post_request_no_data
from tests.common import MockConfigEntry
@pytest.fixture(name="config_entry")
async def mock_config_entry_fixture(hass):
"""Mock a config entry."""
mock_entry = MockConfigEntry(
domain="netatmo",
data={
"auth_implementation": "cloud",
"token": {
"refresh_token": "mock-refresh-token",
"access_token": "mock-access-token",
"type": "Bearer",
"expires_in": 60,
"expires_at": time() + 1000,
"scope": " ".join(ALL_SCOPES),
},
},
options={
"weather_areas": {
"Home avg": {
"lat_ne": 32.2345678,
"lon_ne": -117.1234567,
"lat_sw": 32.1234567,
"lon_sw": -117.2345678,
"show_on_map": False,
"area_name": "Home avg",
"mode": "avg",
},
"Home max": {
"lat_ne": 32.2345678,
"lon_ne": -117.1234567,
"lat_sw": 32.1234567,
"lon_sw": -117.2345678,
"show_on_map": True,
"area_name": "Home max",
"mode": "max",
},
}
},
)
mock_entry.add_to_hass(hass)
return mock_entry
@contextmanager
def selected_platforms(platforms=["camera", "climate", "light", "sensor"]):
"""Restrict loaded platforms to list given."""
with patch("homeassistant.components.netatmo.PLATFORMS", platforms), patch(
"homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth"
) as mock_auth, patch(
"homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation",
), patch(
"homeassistant.components.webhook.async_generate_url"
):
mock_auth.return_value.post_request.side_effect = fake_post_request
yield
@pytest.fixture(name="entry")
async def mock_entry_fixture(hass, config_entry):
"""Mock setup of all platforms."""
with selected_platforms():
await hass.config_entries.async_setup(config_entry.entry_id)
return config_entry
@pytest.fixture(name="sensor_entry")
async def mock_sensor_entry_fixture(hass, config_entry):
"""Mock setup of sensor platform."""
with selected_platforms(["sensor"]):
await hass.config_entries.async_setup(config_entry.entry_id)
return config_entry
@pytest.fixture(name="camera_entry")
async def mock_camera_entry_fixture(hass, config_entry):
"""Mock setup of camera platform."""
with selected_platforms(["camera"]):
await hass.config_entries.async_setup(config_entry.entry_id)
return config_entry
@pytest.fixture(name="light_entry")
async def mock_light_entry_fixture(hass, config_entry):
"""Mock setup of light platform."""
with selected_platforms(["light"]):
await hass.config_entries.async_setup(config_entry.entry_id)
return config_entry
@pytest.fixture(name="climate_entry")
async def mock_climate_entry_fixture(hass, config_entry):
"""Mock setup of climate platform."""
with selected_platforms(["climate"]):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
@pytest.fixture(name="entry_error")
async def mock_entry_error_fixture(hass, config_entry):
"""Mock erroneous setup of platforms."""
with patch(
"homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth"
) as mock_auth, patch(
"homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation",
), patch(
"homeassistant.components.webhook.async_generate_url"
):
mock_auth.return_value.post_request.side_effect = fake_post_request_no_data
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry

View File

@ -0,0 +1,539 @@
"""The tests for the Netatmo climate platform."""
from unittest.mock import Mock
import pytest
from homeassistant.components.climate import (
DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.components.climate.const import (
ATTR_HVAC_MODE,
ATTR_PRESET_MODE,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_BOOST,
)
from homeassistant.components.netatmo import climate
from homeassistant.components.netatmo.climate import (
NA_THERM,
NA_VALVE,
PRESET_FROST_GUARD,
PRESET_SCHEDULE,
)
from homeassistant.components.netatmo.const import (
ATTR_SCHEDULE_NAME,
SERVICE_SET_SCHEDULE,
)
from homeassistant.components.webhook import async_handle_webhook
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID
from homeassistant.util.aiohttp import MockRequest
async def simulate_webhook(hass, webhook_id, response):
"""Simulate a webhook event."""
request = MockRequest(content=response, mock_source="test")
await async_handle_webhook(hass, webhook_id, request)
await hass.async_block_till_done()
async def test_webhook_event_handling_thermostats(hass, climate_entry):
"""Test service and webhook event handling with thermostats."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_livingroom = "climate.netatmo_livingroom"
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Schedule"
)
assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 12
# Test service setting the temperature
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_TEMPERATURE: 21},
blocking=True,
)
await hass.async_block_till_done()
# Fake webhook thermostat manual set point
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",'
b'"home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",'
b'"rooms": [{ "id": "2746182631", "name": "Livingroom", "type": "livingroom",'
b'"therm_setpoint_mode": "manual", "therm_setpoint_temperature": 21,'
b'"therm_setpoint_end_time": 1612734552}], "modules": [{"id": "12:34:56:00:01:ae",'
b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "manual", "event_type": "set_point",'
b'"temperature": 21, "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "heat"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Schedule"
)
assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 21
# Test service setting the HVAC mode to "heat"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_HEAT},
blocking=True,
)
await hass.async_block_till_done()
# Fake webhook thermostat mode change to "Max"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",'
b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",'
b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",'
b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],'
b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},'
b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "heat"
assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30
# Test service setting the HVAC mode to "off"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_OFF},
blocking=True,
)
await hass.async_block_till_done()
# Fake webhook turn thermostat off
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",'
b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",'
b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",'
b'"therm_setpoint_mode": "off"}],"modules": [{"id": "12:34:56:00:01:ae",'
b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "off", "event_type": "set_point",'
b'"push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "off"
# Test service setting the HVAC mode to "auto"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_AUTO},
blocking=True,
)
await hass.async_block_till_done()
# Fake webhook thermostat mode cancel set point
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b","room_id": "2746182631",'
b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",'
b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",'
b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",'
b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "home",'
b'"event_type": "cancel_set_point", "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Schedule"
)
async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry):
"""Test service with frost guard preset for thermostats."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_livingroom = "climate.netatmo_livingroom"
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Schedule"
)
# Test service setting the preset mode to "frost guard"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{
ATTR_ENTITY_ID: climate_entity_livingroom,
ATTR_PRESET_MODE: PRESET_FROST_GUARD,
},
blocking=True,
)
await hass.async_block_till_done()
# Fake webhook thermostat mode change to "Frost Guard"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",'
b'"therm_mode": "hg"}, "mode": "hg", "previous_mode": "schedule",'
b'"push_type":"home_event_changed"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Frost Guard"
)
# Test service setting the preset mode to "frost guard"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{
ATTR_ENTITY_ID: climate_entity_livingroom,
ATTR_PRESET_MODE: PRESET_SCHEDULE,
},
blocking=True,
)
await hass.async_block_till_done()
# Test webhook thermostat mode change to "Schedule"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",'
b'"therm_mode": "schedule"}, "mode": "schedule", "previous_mode": "hg",'
b'"push_type": "home_event_changed"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Schedule"
)
async def test_service_preset_modes_thermostat(hass, climate_entry):
"""Test service with preset modes for thermostats."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_livingroom = "climate.netatmo_livingroom"
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"]
== "Schedule"
)
# Test service setting the preset mode to "away"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_AWAY},
blocking=True,
)
await hass.async_block_till_done()
# Fake webhook thermostat mode change to "Away"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", '
b'"event_type": "therm_mode","home": {"id": "91763b24c43d3e344f424e8b",'
b'"therm_mode": "away"},"mode": "away","previous_mode": "schedule",'
b'"push_type": "home_event_changed"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "auto"
assert (
hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away"
)
# Test service setting the preset mode to "boost"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_BOOST},
blocking=True,
)
await hass.async_block_till_done()
# TFakeest webhook thermostat mode change to "Max"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email":"john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",'
b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",'
b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",'
b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],'
b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},'
b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_livingroom).state == "heat"
assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30
async def test_webhook_event_handling_no_data(hass, climate_entry):
"""Test service and webhook event handling with erroneous data."""
# Test webhook without home entry
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"push_type": "home_event_changed"}'
)
await simulate_webhook(hass, webhook_id, response)
# Test webhook with different home id
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "3d3e344f491763b24c424e8b",'
b'"room_id": "2746182631", "home": {"id": "3d3e344f491763b24c424e8b",'
b'"name": "MYHOME","country": "DE", "rooms": [], "modules": []}, "mode": "home",'
b'"event_type": "cancel_set_point", "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
# Test webhook without room entries
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"room_id": "2746182631", "home": {"id": "91763b24c43d3e344f424e8b",'
b'"name": "MYHOME", "country": "DE", "rooms": [], "modules": []}, "mode": "home",'
b'"event_type": "cancel_set_point","push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
async def test_service_schedule_thermostats(hass, climate_entry, caplog):
"""Test service for selecting Netatmo schedule with thermostats."""
climate_entity_livingroom = "climate.netatmo_livingroom"
# Test setting a valid schedule
await hass.services.async_call(
"netatmo",
SERVICE_SET_SCHEDULE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"},
blocking=True,
)
await hass.async_block_till_done()
assert (
"Setting 91763b24c43d3e344f424e8b schedule to Winter (b1b54a2f45795764f59d50d8)"
in caplog.text
)
# Test setting an invalid schedule
await hass.services.async_call(
"netatmo",
SERVICE_SET_SCHEDULE,
{ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"},
blocking=True,
)
await hass.async_block_till_done()
assert "summer is not a invalid schedule" in caplog.text
async def test_service_preset_mode_already_boost_valves(hass, climate_entry):
"""Test service with boost preset for valves when already in boost mode."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_entrada = "climate.netatmo_entrada"
assert hass.states.get(climate_entity_entrada).state == "auto"
assert (
hass.states.get(climate_entity_entrada).attributes["preset_mode"]
== "Frost Guard"
)
assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7
# Test webhook valve mode change to "Max"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",'
b'"country": "DE","rooms": [{"id": "2833524037", "name": "Entrada", "type": "lobby",'
b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],'
b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},'
b'"mode": "max","event_type": "set_point","push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
# Test service setting the preset mode to "boost"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST},
blocking=True,
)
await hass.async_block_till_done()
# Test webhook valve mode change to "Max"
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b",'
b'"name": "MYHOME","country": "DE","rooms": [{"id": "2833524037", "name": "Entrada",'
b'"type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],'
b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},'
b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_entrada).state == "heat"
assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30
async def test_service_preset_mode_boost_valves(hass, climate_entry):
"""Test service with boost preset for valves."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_entrada = "climate.netatmo_entrada"
# Test service setting the preset mode to "boost"
assert hass.states.get(climate_entity_entrada).state == "auto"
assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST},
blocking=True,
)
await hass.async_block_till_done()
# Fake backend response
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",'
b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",'
b'"country": "DE", "rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",'
b'"therm_setpoint_mode": "max","therm_setpoint_end_time": 1612749189}],'
b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},'
b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_entrada).state == "heat"
assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30
async def test_service_preset_mode_invalid(hass, climate_entry, caplog):
"""Test service with invalid preset."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: "climate.netatmo_cocina", ATTR_PRESET_MODE: "invalid"},
blocking=True,
)
await hass.async_block_till_done()
assert "Preset mode 'invalid' not available" in caplog.text
async def test_valves_service_turn_off(hass, climate_entry):
"""Test service turn off for valves."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_entrada = "climate.netatmo_entrada"
# Test turning valve off
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: climate_entity_entrada},
blocking=True,
)
await hass.async_block_till_done()
# Fake backend response for valve being turned off
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",'
b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",'
b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",'
b'"therm_setpoint_mode": "off"}], "modules": [{"id": "12:34:56:00:01:ae","name": "Entrada",'
b'"type": "NRV"}]}, "mode": "off", "event_type": "set_point", "push_type":"display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_entrada).state == "off"
async def test_valves_service_turn_on(hass, climate_entry):
"""Test service turn on for valves."""
webhook_id = climate_entry.data[CONF_WEBHOOK_ID]
climate_entity_entrada = "climate.netatmo_entrada"
# Test turning valve on
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: climate_entity_entrada},
blocking=True,
)
await hass.async_block_till_done()
# Fake backend response for valve being turned on
response = (
b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",'
b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",'
b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",'
b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",'
b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",'
b'"name": "Entrada", "type": "NRV"}]}, "mode": "home", "event_type": "cancel_set_point",'
b'"push_type": "display_change"}'
)
await simulate_webhook(hass, webhook_id, response)
assert hass.states.get(climate_entity_entrada).state == "auto"
@pytest.mark.parametrize(
"batterylevel, module_type, expected",
[
(4101, NA_THERM, 100),
(3601, NA_THERM, 80),
(3450, NA_THERM, 65),
(3301, NA_THERM, 50),
(3001, NA_THERM, 20),
(2799, NA_THERM, 0),
(3201, NA_VALVE, 100),
(2701, NA_VALVE, 80),
(2550, NA_VALVE, 65),
(2401, NA_VALVE, 50),
(2201, NA_VALVE, 20),
(2001, NA_VALVE, 0),
],
)
async def test_interpolate(batterylevel, module_type, expected):
"""Test interpolation of battery levels depending on device type."""
assert climate.interpolate(batterylevel, module_type) == expected
async def test_get_all_home_ids():
"""Test extracting all home ids returned by NetAtmo API."""
# Test with backend returning no data
assert climate.get_all_home_ids(None) == []
# Test with fake data
home_data = Mock()
home_data.homes = {
"123": {"id": "123", "name": "Home 1", "modules": [], "therm_schedules": []},
"987": {"id": "987", "name": "Home 2", "modules": [], "therm_schedules": []},
}
expected = ["123", "987"]
assert climate.get_all_home_ids(home_data) == expected

318
tests/fixtures/netatmo/gethomedata.json vendored Normal file
View File

@ -0,0 +1,318 @@
{
"body": {
"homes": [
{
"id": "91763b24c43d3e344f424e8b",
"name": "MYHOME",
"persons": [
{
"id": "91827374-7e04-5298-83ad-a0cb8372dff1",
"last_seen": 1557071156,
"out_of_sight": true,
"face": {
"id": "d74fad765b9100ef480720a9",
"version": 1,
"key": "a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7"
},
"pseudo": "John Doe"
},
{
"id": "91827375-7e04-5298-83ae-a0cb8372dff2",
"last_seen": 1560600726,
"out_of_sight": true,
"face": {
"id": "d74fad765b9100ef480720a9",
"version": 3,
"key": "a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72"
},
"pseudo": "Jane Doe"
},
{
"id": "91827376-7e04-5298-83af-a0cb8372dff3",
"last_seen": 1560626666,
"out_of_sight": false,
"face": {
"id": "d74fad765b9100ef480720a9",
"version": 1,
"key": "a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8"
},
"pseudo": "Richard Doe"
},
{
"id": "91827376-7e04-5298-83af-a0cb8372dff4",
"last_seen": 1560621666,
"out_of_sight": true,
"face": {
"id": "d0ef44fad765b980720710a9",
"version": 1,
"key": "ab029da89f84a95c2d1730fb67fc40cb2d74b80869ecdf2bb8b72039d2c69928",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d0ef44fad765b980720710a9ab029da89f84a95c2d1730fb67fc40cb2d74b80869ecdf2bb8b72039d2c69928"
}
}
],
"place": {
"city": "Frankfurt",
"country": "DE",
"timezone": "Europe/Berlin"
},
"cameras": [
{
"id": "12:34:56:00:f1:62",
"type": "NACamera",
"status": "on",
"vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTg,,",
"is_local": true,
"sd_status": "on",
"alim_status": "on",
"name": "Hall",
"modules": [
{
"id": "12:34:56:00:f2:f1",
"type": "NIS",
"battery_percent": 84,
"rf": 68,
"status": "no_news",
"monitoring": "on",
"alim_source": "battery",
"tamper_detection_enabled": true,
"name": "Welcome's Siren"
}
],
"use_pin_code": false,
"last_setup": 1544828430
},
{
"id": "12:34:56:00:a5:a4",
"type": "NOC",
"status": "on",
"vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTw,,",
"is_local": false,
"sd_status": "on",
"alim_status": "on",
"name": "Garden",
"last_setup": 1563737661,
"light_mode_status": "auto"
}
],
"smokedetectors": [
{
"id": "12:34:56:00:8b:a2",
"type": "NSD",
"last_setup": 1567261859,
"name": "Hall"
},
{
"id": "12:34:56:00:8b:ac",
"type": "NSD",
"last_setup": 1567262759,
"name": "Kitchen"
}
],
"events": [
{
"id": "a1b2c3d4e5f6abcdef123456",
"type": "person",
"time": 1560604700,
"camera_id": "12:34:56:00:f1:62",
"device_id": "12:34:56:00:f1:62",
"person_id": "91827374-7e04-5298-83ad-a0cb8372dff1",
"video_status": "deleted",
"is_arrival": false,
"message": "<b>John Doe</b> gesehen"
},
{
"id": "a1b2c3d4e5f6abcdef123457",
"type": "person_away",
"time": 1560602400,
"camera_id": "12:34:56:00:f1:62",
"device_id": "12:34:56:00:f1:62",
"person_id": "91827374-7e04-5298-83ad-a0cb8372dff1",
"message": "<b>John Doe</b> hat das Haus verlassen",
"sub_message": "John Doe gilt als abwesend, da das mit diesem Profil verbundene Telefon den Bereich des Hauses verlassen hat."
},
{
"id": "a1b2c3d4e5f6abcdef123458",
"type": "person",
"time": 1560601200,
"camera_id": "12:34:56:00:f1:62",
"device_id": "12:34:56:00:f1:62",
"person_id": "91827374-7e04-5298-83ad-a0cb8372dff1",
"video_status": "deleted",
"is_arrival": false,
"message": "<b>John Doe</b> gesehen"
},
{
"id": "a1b2c3d4e5f6abcdef123459",
"type": "person",
"time": 1560600100,
"camera_id": "12:34:56:00:f1:62",
"device_id": "12:34:56:00:f1:62",
"person_id": "91827375-7e04-5298-83ae-a0cb8372dff2",
"snapshot": {
"id": "d74fad765b9100ef480720a9",
"version": 1,
"key": "a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72"
},
"video_id": "12345678-36bc-4b9a-9762-5194e707ed51",
"video_status": "available",
"is_arrival": false,
"message": "<b>Jane Doe</b> gesehen"
},
{
"id": "a1b2c3d4e5f6abcdef12345a",
"type": "person",
"time": 1560603600,
"camera_id": "12:34:56:00:f1:62",
"device_id": "12:34:56:00:f1:62",
"person_id": "91827375-7e04-5298-83ae-a0cb8372dff3",
"snapshot": {
"id": "532dde8d17554c022ab071b8",
"version": 1,
"key": "9fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28",
"url": "https://netatmocameraimage.blob.core.windows.net/production/532dde8d17554c022ab071b89fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28"
},
"video_id": "12345678-1234-46cb-ad8f-23d893874099",
"video_status": "available",
"is_arrival": false,
"message": "<b>Bewegung</b> erkannt"
},
{
"id": "a1b2c3d4e5f6abcdef12345b",
"type": "movement",
"time": 1560506200,
"camera_id": "12:34:56:00:f1:62",
"device_id": "12:34:56:00:f1:62",
"category": "human",
"snapshot": {
"id": "532dde8d17554c022ab071b9",
"version": 1,
"key": "8fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28",
"url": "https://netatmocameraimage.blob.core.windows.net/production/532dde8d17554c022ab071b98fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28"
},
"vignette": {
"id": "5dc021b5dea854bd2321707a",
"version": 1,
"key": "58c5a05bd6bd908f6bf368865ef7355231c44215f8eb7ae458c919b2c67b4944",
"url": "https://netatmocameraimage.blob.core.windows.net/production/5dc021b5dea854bd2321707a58c5a05bd6bd908f6bf368865ef7355231c44215f8eb7ae458c919b2c67b4944"
},
"video_id": "12345678-1234-46cb-ad8f-23d89387409a",
"video_status": "available",
"message": "<b>Bewegung</b> erkannt"
},
{
"id": "a1b2c3d4e5f6abcdef12345c",
"type": "sound_test",
"time": 1560506210,
"camera_id": "12:34:56:00:8b:a2",
"device_id": "12:34:56:00:8b:a2",
"sub_type": 0,
"message": "Hall: Alarmton erfolgreich getestet"
},
{
"id": "a1b2c3d4e5f6abcdef12345d",
"type": "wifi_status",
"time": 1560506220,
"camera_id": "12:34:56:00:8b:a2",
"device_id": "12:34:56:00:8b:a2",
"sub_type": 1,
"message": "Hall:WLAN-Verbindung erfolgreich hergestellt"
},
{
"id": "a1b2c3d4e5f6abcdef12345e",
"type": "outdoor",
"time": 1560643100,
"camera_id": "12:34:56:00:a5:a4",
"device_id": "12:34:56:00:a5:a4",
"video_id": "string",
"video_status": "available",
"event_list": [
{
"type": "string",
"time": 1560643100,
"offset": 0,
"id": "c81bcf7b-2cfg-4ac9-8455-487ed00c0000",
"message": "Animal détecté",
"snapshot": {
"id": "5715e16849c75xxxx00000000xxxxx",
"version": 1,
"key": "7ac578d05030d0e170643a787ee0a29663dxxx00000xxxxx00000",
"url": "https://netatmocameraimage.blob.core.windows.net/production/1aa"
},
"vignette": {
"id": "5715e16849c75xxxx00000000xxxxx",
"version": 1,
"key": "7ac578d05030d0e170643a787ee0a29663dxxx00000xxxxx00000",
"url": "https://netatmocameraimage.blob.core.windows.net/production/1aa00000"
}
},
{
"type": "string",
"time": 1560506222,
"offset": 0,
"id": "c81bcf7b-2cfg-4ac9-8455-487ed00c0000",
"message": "Animal détecté",
"snapshot": {
"filename": "vod\/af74631d-8311-42dc-825b-82e3abeaab09\/events\/c53b-aze7a.jpg"
},
"vignette": {
"filename": "vod\/af74631d-8311-42dc-825b-82e3abeaab09\/events\/c5.jpg"
}
}
]
}
]
},
{
"id": "91763b24c43d3e344f424e8c",
"persons": [],
"place": {
"city": "Frankfurt",
"country": "DE",
"timezone": "Europe/Berlin"
},
"cameras": [
{
"id": "12:34:56:00:a5:a5",
"type": "NOC",
"status": "on",
"vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTz,,",
"is_local": true,
"sd_status": "on",
"alim_status": "on",
"name": "Street",
"last_setup": 1563737561,
"light_mode_status": "auto"
}
],
"smokedetectors": []
},
{
"id": "91763b24c43d3e344f424e8d",
"persons": [],
"place": {
"city": "Frankfurt",
"country": "DE",
"timezone": "Europe/Berlin"
},
"cameras": [],
"smokedetectors": []
}
],
"user": {
"reg_locale": "de-DE",
"lang": "de-DE",
"country": "DE",
"mail": "john@doe.com"
},
"global_info": {
"show_tags": true
}
},
"status": "ok",
"time_exec": 0.03621506690979,
"time_server": 1560626960
}

View File

@ -0,0 +1,600 @@
{
"body": {
"devices": [
{
"_id": "12:34:56:37:11:ca",
"cipher_id": "enc:16:zjiZF/q8jTScXVdDa/kvhUAIUPGeYszaD1ClEf8byAJkRjxc5oth7cAocrMUIApX",
"date_setup": 1544558432,
"last_setup": 1544558432,
"type": "NAMain",
"last_status_store": 1559413181,
"module_name": "NetatmoIndoor",
"firmware": 137,
"last_upgrade": 1544558433,
"wifi_status": 45,
"reachable": true,
"co2_calibrating": false,
"station_name": "MyStation",
"data_type": [
"Temperature",
"CO2",
"Humidity",
"Noise",
"Pressure"
],
"place": {
"altitude": 664,
"city": "Frankfurt",
"country": "DE",
"timezone": "Europe/Berlin",
"location": [
52.516263,
13.377726
]
},
"dashboard_data": {
"time_utc": 1559413171,
"Temperature": 24.6,
"CO2": 749,
"Humidity": 36,
"Noise": 37,
"Pressure": 1017.3,
"AbsolutePressure": 939.7,
"min_temp": 23.4,
"max_temp": 25.6,
"date_min_temp": 1559371924,
"date_max_temp": 1559411964,
"temp_trend": "stable",
"pressure_trend": "down"
},
"modules": [
{
"_id": "12:34:56:36:fc:de",
"type": "NAModule1",
"module_name": "NetatmoOutdoor",
"data_type": [
"Temperature",
"Humidity"
],
"last_setup": 1544558433,
"reachable": true,
"dashboard_data": {
"time_utc": 1559413157,
"Temperature": 28.6,
"Humidity": 24,
"min_temp": 16.9,
"max_temp": 30.3,
"date_min_temp": 1559365579,
"date_max_temp": 1559404698,
"temp_trend": "down"
},
"firmware": 46,
"last_message": 1559413177,
"last_seen": 1559413157,
"rf_status": 65,
"battery_vp": 5738,
"battery_percent": 87
},
{
"_id": "12:34:56:07:bb:3e",
"type": "NAModule4",
"module_name": "Kitchen",
"data_type": [
"Temperature",
"CO2",
"Humidity"
],
"last_setup": 1548956696,
"reachable": true,
"dashboard_data": {
"time_utc": 1559413125,
"Temperature": 28,
"CO2": 503,
"Humidity": 26,
"min_temp": 25,
"max_temp": 28,
"date_min_temp": 1559371577,
"date_max_temp": 1559412561,
"temp_trend": "up"
},
"firmware": 44,
"last_message": 1559413177,
"last_seen": 1559413177,
"rf_status": 73,
"battery_vp": 5687,
"battery_percent": 83
},
{
"_id": "12:34:56:07:bb:0e",
"type": "NAModule4",
"module_name": "Livingroom",
"data_type": [
"Temperature",
"CO2",
"Humidity"
],
"last_setup": 1548957209,
"reachable": true,
"dashboard_data": {
"time_utc": 1559413093,
"Temperature": 26.4,
"CO2": 451,
"Humidity": 31,
"min_temp": 25.1,
"max_temp": 26.4,
"date_min_temp": 1559365290,
"date_max_temp": 1559413093,
"temp_trend": "stable"
},
"firmware": 44,
"last_message": 1559413177,
"last_seen": 1559413093,
"rf_status": 84,
"battery_vp": 5626,
"battery_percent": 79
},
{
"_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
},
{
"_id": "12:34:56:05:51:20",
"type": "NAModule3",
"module_name": "Yard",
"data_type": [
"Rain"
],
"last_setup": 1549194580,
"reachable": true,
"dashboard_data": {
"time_utc": 1559413170,
"Rain": 0,
"sum_rain_24": 0,
"sum_rain_1": 0
},
"firmware": 8,
"last_message": 1559413177,
"last_seen": 1559413170,
"rf_status": 67,
"battery_vp": 5860,
"battery_percent": 93
}
]
},
{
"_id": "12 :34: 56:36:fd:3c",
"station_name": "Valley Road",
"date_setup": 1545897146,
"last_setup": 1545897146,
"type": "NAMain",
"last_status_store": 1581835369,
"firmware": 137,
"last_upgrade": 1545897125,
"wifi_status": 53,
"reachable": true,
"co2_calibrating": false,
"data_type": [
"Temperature",
"CO2",
"Humidity",
"Noise",
"Pressure"
],
"place": {
"altitude": 69,
"city": "Valley",
"country": "AU",
"timezone": "Australia/Hobart",
"location": [
148.444226,
-41.721282
]
},
"read_only": true,
"dashboard_data": {
"time_utc": 1581835330,
"Temperature": 22.4,
"CO2": 471,
"Humidity": 46,
"Noise": 47,
"Pressure": 1011.5,
"AbsolutePressure": 1002.8,
"min_temp": 18.1,
"max_temp": 22.5,
"date_max_temp": 1581829891,
"date_min_temp": 1581794878,
"temp_trend": "stable",
"pressure_trend": "stable"
},
"modules": [
{
"_id": "12 :34: 56:36:e6:c0",
"type": "NAModule1",
"module_name": "Module",
"data_type": [
"Temperature",
"Humidity"
],
"last_setup": 1545897146,
"battery_percent": 22,
"reachable": false,
"firmware": 46,
"last_message": 1572497781,
"last_seen": 1572497742,
"rf_status": 88,
"battery_vp": 4118
},
{
"_id": "12:34:56:05:25:6e",
"type": "NAModule3",
"module_name": "Rain Gauge",
"data_type": [
"Rain"
],
"last_setup": 1553997427,
"battery_percent": 82,
"reachable": true,
"firmware": 8,
"last_message": 1581835362,
"last_seen": 1581835354,
"rf_status": 78,
"battery_vp": 5594,
"dashboard_data": {
"time_utc": 1581835329,
"Rain": 0,
"sum_rain_1": 0,
"sum_rain_24": 0
}
}
]
},
{
"_id": "12:34:56:32:a7:60",
"home_name": "Ateljen",
"date_setup": 1566714693,
"last_setup": 1566714693,
"type": "NAMain",
"last_status_store": 1588481079,
"module_name": "Indoor",
"firmware": 177,
"last_upgrade": 1566714694,
"wifi_status": 50,
"reachable": true,
"co2_calibrating": false,
"data_type": [
"Temperature",
"CO2",
"Humidity",
"Noise",
"Pressure"
],
"place": {
"altitude": 93,
"city": "Gothenburg",
"country": "SE",
"timezone": "Europe/Stockholm",
"location": [
11.6136629,
57.7006827
]
},
"dashboard_data": {
"time_utc": 1588481073,
"Temperature": 18.2,
"CO2": 542,
"Humidity": 45,
"Noise": 45,
"Pressure": 1013,
"AbsolutePressure": 1001.9,
"min_temp": 18.2,
"max_temp": 19.5,
"date_max_temp": 1588456861,
"date_min_temp": 1588479561,
"temp_trend": "stable",
"pressure_trend": "up"
},
"modules": [
{
"_id": "12:34:56:32:db:06",
"type": "NAModule1",
"last_setup": 1587635819,
"data_type": [
"Temperature",
"Humidity"
],
"battery_percent": 100,
"reachable": false,
"firmware": 255,
"last_message": 0,
"last_seen": 0,
"rf_status": 255,
"battery_vp": 65535
}
]
},
{
"_id": "12:34:56:1c:68:2e",
"station_name": "Bol\u00e5s",
"date_setup": 1470935400,
"last_setup": 1470935400,
"type": "NAMain",
"last_status_store": 1588481399,
"module_name": "Inne - Nere",
"firmware": 177,
"last_upgrade": 1470935401,
"wifi_status": 13,
"reachable": true,
"co2_calibrating": false,
"data_type": [
"Temperature",
"CO2",
"Humidity",
"Noise",
"Pressure"
],
"place": {
"altitude": 93,
"city": "Gothenburg",
"country": "SE",
"timezone": "Europe/Stockholm",
"location": [
11.6136629,
57.7006827
]
},
"dashboard_data": {
"time_utc": 1588481387,
"Temperature": 20.8,
"CO2": 674,
"Humidity": 41,
"Noise": 34,
"Pressure": 1012.1,
"AbsolutePressure": 1001,
"min_temp": 20.8,
"max_temp": 22.2,
"date_max_temp": 1588456859,
"date_min_temp": 1588480176,
"temp_trend": "stable",
"pressure_trend": "up"
},
"modules": [
{
"_id": "12:34:56:02:b3:da",
"type": "NAModule3",
"module_name": "Regnm\u00e4tare",
"last_setup": 1470937706,
"data_type": [
"Rain"
],
"battery_percent": 81,
"reachable": true,
"firmware": 12,
"last_message": 1588481393,
"last_seen": 1588481386,
"rf_status": 67,
"battery_vp": 5582,
"dashboard_data": {
"time_utc": 1588481386,
"Rain": 0,
"sum_rain_1": 0,
"sum_rain_24": 0.1
}
},
{
"_id": "12:34:56:03:76:60",
"type": "NAModule4",
"module_name": "Inne - Uppe",
"last_setup": 1470938089,
"data_type": [
"Temperature",
"CO2",
"Humidity"
],
"battery_percent": 14,
"reachable": true,
"firmware": 50,
"last_message": 1588481393,
"last_seen": 1588481374,
"rf_status": 70,
"battery_vp": 4448,
"dashboard_data": {
"time_utc": 1588481374,
"Temperature": 19.6,
"CO2": 696,
"Humidity": 41,
"min_temp": 19.6,
"max_temp": 20.5,
"date_max_temp": 1588456817,
"date_min_temp": 1588481374,
"temp_trend": "stable"
}
},
{
"_id": "12:34:56:32:db:06",
"type": "NAModule1",
"module_name": "Ute",
"last_setup": 1566326027,
"data_type": [
"Temperature",
"Humidity"
],
"battery_percent": 81,
"reachable": true,
"firmware": 50,
"last_message": 1588481393,
"last_seen": 1588481380,
"rf_status": 61,
"battery_vp": 5544,
"dashboard_data": {
"time_utc": 1588481380,
"Temperature": 6.4,
"Humidity": 91,
"min_temp": 3.6,
"max_temp": 6.4,
"date_max_temp": 1588481380,
"date_min_temp": 1588471383,
"temp_trend": "up"
}
}
]
},
{
"_id": "12:34:56:1d:68:2e",
"date_setup": 1470935500,
"last_setup": 1470935500,
"type": "NAMain",
"last_status_store": 1588481399,
"module_name": "Basisstation",
"firmware": 177,
"last_upgrade": 1470935401,
"wifi_status": 13,
"reachable": true,
"co2_calibrating": false,
"data_type": [
"Temperature",
"CO2",
"Humidity",
"Noise",
"Pressure"
],
"place": {
"altitude": 93,
"city": "Gothenburg",
"country": "SE",
"timezone": "Europe/Stockholm",
"location": [
11.6136629,
57.7006827
]
},
"dashboard_data": {
"time_utc": 1588481387,
"Temperature": 20.8,
"CO2": 674,
"Humidity": 41,
"Noise": 34,
"Pressure": 1012.1,
"AbsolutePressure": 1001,
"min_temp": 20.8,
"max_temp": 22.2,
"date_max_temp": 1588456859,
"date_min_temp": 1588480176,
"temp_trend": "stable",
"pressure_trend": "up"
},
"modules": []
},
{
"_id": "12:34:56:58:c8:54",
"date_setup": 1605594014,
"last_setup": 1605594014,
"type": "NAMain",
"last_status_store": 1605878352,
"firmware": 178,
"wifi_status": 47,
"reachable": true,
"co2_calibrating": false,
"data_type": [
"Temperature",
"CO2",
"Humidity",
"Noise",
"Pressure"
],
"place": {
"altitude": 65,
"city": "Njurunda District",
"country": "SE",
"timezone": "Europe/Stockholm",
"location": [
17.123456,
62.123456
]
},
"station_name": "Njurunda (Indoor)",
"home_id": "5fb36b9ec68fd10c6467ca65",
"home_name": "Njurunda",
"dashboard_data": {
"time_utc": 1605878349,
"Temperature": 19.7,
"CO2": 993,
"Humidity": 40,
"Noise": 40,
"Pressure": 1015.6,
"AbsolutePressure": 1007.8,
"min_temp": 19.7,
"max_temp": 20.4,
"date_max_temp": 1605826917,
"date_min_temp": 1605873207,
"temp_trend": "stable",
"pressure_trend": "up"
},
"modules": [
{
"_id": "12:34:56:58:e6:38",
"type": "NAModule1",
"last_setup": 1605594034,
"data_type": [
"Temperature",
"Humidity"
],
"battery_percent": 100,
"reachable": true,
"firmware": 50,
"last_message": 1605878347,
"last_seen": 1605878328,
"rf_status": 62,
"battery_vp": 6198,
"dashboard_data": {
"time_utc": 1605878328,
"Temperature": 0.6,
"Humidity": 77,
"min_temp": -2.1,
"max_temp": 1.5,
"date_max_temp": 1605865920,
"date_min_temp": 1605826904,
"temp_trend": "down"
}
}
]
}
],
"user": {
"mail": "john@doe.com",
"administrative": {
"lang": "de-DE",
"reg_locale": "de-DE",
"country": "DE",
"unit": 0,
"windunit": 0,
"pressureunit": 0,
"feel_like_algo": 0
}
}
},
"status": "ok",
"time_exec": 0.91107702255249,
"time_server": 1559413602
}

595
tests/fixtures/netatmo/homesdata.json vendored Normal file
View File

@ -0,0 +1,595 @@
{
"body": {
"homes": [
{
"id": "91763b24c43d3e344f424e8b",
"name": "MYHOME",
"altitude": 112,
"coordinates": [
52.516263,
13.377726
],
"country": "DE",
"timezone": "Europe/Berlin",
"rooms": [
{
"id": "2746182631",
"name": "Livingroom",
"type": "livingroom",
"module_ids": [
"12:34:56:00:01:ae"
]
},
{
"id": "3688132631",
"name": "Hall",
"type": "custom",
"module_ids": [
"12:34:56:00:f1:62"
]
},
{
"id": "2833524037",
"name": "Entrada",
"type": "lobby",
"module_ids": [
"12:34:56:03:a5:54"
]
},
{
"id": "2940411577",
"name": "Cocina",
"type": "kitchen",
"module_ids": [
"12:34:56:03:a0:ac"
]
}
],
"modules": [
{
"id": "12:34:56:00:fa:d0",
"type": "NAPlug",
"name": "Thermostat",
"setup_date": 1494963356,
"modules_bridged": [
"12:34:56:00:01:ae",
"12:34:56:03:a0:ac",
"12:34:56:03:a5:54"
]
},
{
"id": "12:34:56:00:01:ae",
"type": "NATherm1",
"name": "Livingroom",
"setup_date": 1494963356,
"room_id": "2746182631",
"bridge": "12:34:56:00:fa:d0"
},
{
"id": "12:34:56:03:a5:54",
"type": "NRV",
"name": "Valve1",
"setup_date": 1554549767,
"room_id": "2833524037",
"bridge": "12:34:56:00:fa:d0"
},
{
"id": "12:34:56:03:a0:ac",
"type": "NRV",
"name": "Valve2",
"setup_date": 1554554444,
"room_id": "2940411577",
"bridge": "12:34:56:00:fa:d0"
},
{
"id": "12:34:56:00:f1:62",
"type": "NACamera",
"name": "Hall",
"setup_date": 1544828430,
"room_id": "3688132631"
}
],
"therm_schedules": [
{
"zones": [
{
"type": 0,
"name": "Comfort",
"rooms_temp": [
{
"temp": 21,
"room_id": "2746182631"
}
],
"id": 0
},
{
"type": 1,
"name": "Night",
"rooms_temp": [
{
"temp": 17,
"room_id": "2746182631"
}
],
"id": 1
},
{
"type": 5,
"name": "Eco",
"rooms_temp": [
{
"temp": 17,
"room_id": "2746182631"
}
],
"id": 4
}
],
"timetable": [
{
"zone_id": 1,
"m_offset": 0
},
{
"zone_id": 0,
"m_offset": 360
},
{
"zone_id": 4,
"m_offset": 420
},
{
"zone_id": 0,
"m_offset": 960
},
{
"zone_id": 1,
"m_offset": 1410
},
{
"zone_id": 0,
"m_offset": 1800
},
{
"zone_id": 4,
"m_offset": 1860
},
{
"zone_id": 0,
"m_offset": 2400
},
{
"zone_id": 1,
"m_offset": 2850
},
{
"zone_id": 0,
"m_offset": 3240
},
{
"zone_id": 4,
"m_offset": 3300
},
{
"zone_id": 0,
"m_offset": 3840
},
{
"zone_id": 1,
"m_offset": 4290
},
{
"zone_id": 0,
"m_offset": 4680
},
{
"zone_id": 4,
"m_offset": 4740
},
{
"zone_id": 0,
"m_offset": 5280
},
{
"zone_id": 1,
"m_offset": 5730
},
{
"zone_id": 0,
"m_offset": 6120
},
{
"zone_id": 4,
"m_offset": 6180
},
{
"zone_id": 0,
"m_offset": 6720
},
{
"zone_id": 1,
"m_offset": 7170
},
{
"zone_id": 0,
"m_offset": 7620
},
{
"zone_id": 1,
"m_offset": 8610
},
{
"zone_id": 0,
"m_offset": 9060
},
{
"zone_id": 1,
"m_offset": 10050
}
],
"hg_temp": 7,
"away_temp": 14,
"name": "Default",
"selected": true,
"id": "591b54a2764ff4d50d8b5795",
"type": "therm"
},
{
"zones": [
{
"type": 0,
"name": "Comfort",
"rooms_temp": [
{
"temp": 21,
"room_id": "2746182631"
}
],
"id": 0
},
{
"type": 1,
"name": "Night",
"rooms_temp": [
{
"temp": 17,
"room_id": "2746182631"
}
],
"id": 1
},
{
"type": 5,
"name": "Eco",
"rooms_temp": [
{
"temp": 17,
"room_id": "2746182631"
}
],
"id": 4
}
],
"timetable": [
{
"zone_id": 1,
"m_offset": 0
},
{
"zone_id": 0,
"m_offset": 360
},
{
"zone_id": 4,
"m_offset": 420
},
{
"zone_id": 0,
"m_offset": 960
},
{
"zone_id": 1,
"m_offset": 1410
},
{
"zone_id": 0,
"m_offset": 1800
},
{
"zone_id": 4,
"m_offset": 1860
},
{
"zone_id": 0,
"m_offset": 2400
},
{
"zone_id": 1,
"m_offset": 2850
},
{
"zone_id": 0,
"m_offset": 3240
},
{
"zone_id": 4,
"m_offset": 3300
},
{
"zone_id": 0,
"m_offset": 3840
},
{
"zone_id": 1,
"m_offset": 4290
},
{
"zone_id": 0,
"m_offset": 4680
},
{
"zone_id": 4,
"m_offset": 4740
},
{
"zone_id": 0,
"m_offset": 5280
},
{
"zone_id": 1,
"m_offset": 5730
},
{
"zone_id": 0,
"m_offset": 6120
},
{
"zone_id": 4,
"m_offset": 6180
},
{
"zone_id": 0,
"m_offset": 6720
},
{
"zone_id": 1,
"m_offset": 7170
},
{
"zone_id": 0,
"m_offset": 7620
},
{
"zone_id": 1,
"m_offset": 8610
},
{
"zone_id": 0,
"m_offset": 9060
},
{
"zone_id": 1,
"m_offset": 10050
}
],
"hg_temp": 7,
"away_temp": 14,
"name": "Winter",
"id": "b1b54a2f45795764f59d50d8",
"type": "therm"
}
],
"therm_setpoint_default_duration": 120,
"persons": [
{
"id": "91827374-7e04-5298-83ad-a0cb8372dff1",
"pseudo": "John Doe",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7"
},
{
"id": "91827375-7e04-5298-83ae-a0cb8372dff2",
"pseudo": "Jane Doe",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72"
},
{
"id": "91827376-7e04-5298-83af-a0cb8372dff3",
"pseudo": "Richard Doe",
"url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8"
}
],
"schedules": [
{
"zones": [
{
"type": 0,
"name": "Komfort",
"rooms_temp": [
{
"temp": 21,
"room_id": "2746182631"
}
],
"id": 0,
"rooms": [
{
"id": "2746182631",
"therm_setpoint_temperature": 21
}
]
},
{
"type": 1,
"name": "Nacht",
"rooms_temp": [
{
"temp": 17,
"room_id": "2746182631"
}
],
"id": 1,
"rooms": [
{
"id": "2746182631",
"therm_setpoint_temperature": 17
}
]
},
{
"type": 5,
"name": "Eco",
"rooms_temp": [
{
"temp": 17,
"room_id": "2746182631"
}
],
"id": 4,
"rooms": [
{
"id": "2746182631",
"therm_setpoint_temperature": 17
}
]
}
],
"timetable": [
{
"zone_id": 1,
"m_offset": 0
},
{
"zone_id": 0,
"m_offset": 360
},
{
"zone_id": 4,
"m_offset": 420
},
{
"zone_id": 0,
"m_offset": 960
},
{
"zone_id": 1,
"m_offset": 1410
},
{
"zone_id": 0,
"m_offset": 1800
},
{
"zone_id": 4,
"m_offset": 1860
},
{
"zone_id": 0,
"m_offset": 2400
},
{
"zone_id": 1,
"m_offset": 2850
},
{
"zone_id": 0,
"m_offset": 3240
},
{
"zone_id": 4,
"m_offset": 3300
},
{
"zone_id": 0,
"m_offset": 3840
},
{
"zone_id": 1,
"m_offset": 4290
},
{
"zone_id": 0,
"m_offset": 4680
},
{
"zone_id": 4,
"m_offset": 4740
},
{
"zone_id": 0,
"m_offset": 5280
},
{
"zone_id": 1,
"m_offset": 5730
},
{
"zone_id": 0,
"m_offset": 6120
},
{
"zone_id": 4,
"m_offset": 6180
},
{
"zone_id": 0,
"m_offset": 6720
},
{
"zone_id": 1,
"m_offset": 7170
},
{
"zone_id": 0,
"m_offset": 7620
},
{
"zone_id": 1,
"m_offset": 8610
},
{
"zone_id": 0,
"m_offset": 9060
},
{
"zone_id": 1,
"m_offset": 10050
}
],
"hg_temp": 7,
"away_temp": 14,
"name": "Default",
"id": "591b54a2764ff4d50d8b5795",
"selected": true,
"type": "therm"
}
],
"therm_mode": "schedule"
},
{
"id": "91763b24c43d3e344f424e8c",
"altitude": 112,
"coordinates": [
52.516263,
13.377726
],
"country": "DE",
"timezone": "Europe/Berlin",
"therm_setpoint_default_duration": 180,
"therm_mode": "schedule"
}
],
"user": {
"email": "john@doe.com",
"language": "de-DE",
"locale": "de-DE",
"feel_like_algorithm": 0,
"unit_pressure": 0,
"unit_system": 0,
"unit_wind": 0,
"id": "91763b24c43d3e344f424e8b"
}
},
"status": "ok",
"time_exec": 0.056135892868042,
"time_server": 1559171003
}

113
tests/fixtures/netatmo/homestatus.json vendored Normal file
View File

@ -0,0 +1,113 @@
{
"status": "ok",
"time_server": 1559292039,
"body": {
"home": {
"modules": [
{
"id": "12:34:56:00:f1:62",
"type": "NACamera",
"monitoring": "on",
"sd_status": 4,
"alim_status": 2,
"locked": false,
"vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.123.45/609e27de5699fb18147ab47d06846631/MTRPn_BeWCav5RBq4U1OMDruTW4dkQ0NuMwNDAw11g,,",
"is_local": true
},
{
"id": "12:34:56:00:fa:d0",
"type": "NAPlug",
"firmware_revision": 174,
"rf_strength": 107,
"wifi_strength": 42
},
{
"id": "12:34:56:00:01:ae",
"reachable": true,
"type": "NATherm1",
"firmware_revision": 65,
"rf_strength": 58,
"battery_level": 3793,
"boiler_valve_comfort_boost": false,
"boiler_status": false,
"anticipating": false,
"bridge": "12:34:56:00:fa:d0",
"battery_state": "high"
},
{
"id": "12:34:56:03:a5:54",
"reachable": true,
"type": "NRV",
"firmware_revision": 79,
"rf_strength": 51,
"battery_level": 3025,
"bridge": "12:34:56:00:fa:d0",
"battery_state": "full"
},
{
"id": "12:34:56:03:a0:ac",
"reachable": true,
"type": "NRV",
"firmware_revision": 79,
"rf_strength": 59,
"battery_level": 2329,
"bridge": "12:34:56:00:fa:d0",
"battery_state": "full"
}
],
"rooms": [
{
"id": "2746182631",
"reachable": true,
"therm_measured_temperature": 19.8,
"therm_setpoint_temperature": 12,
"therm_setpoint_mode": "schedule",
"therm_setpoint_start_time": 1559229567,
"therm_setpoint_end_time": 0
},
{
"id": "2940411577",
"reachable": true,
"therm_measured_temperature": 5,
"heating_power_request": 1,
"therm_setpoint_temperature": 7,
"therm_setpoint_mode": "away",
"therm_setpoint_start_time": 0,
"therm_setpoint_end_time": 0,
"anticipating": false,
"open_window": false
},
{
"id": "2833524037",
"reachable": true,
"therm_measured_temperature": 24.5,
"heating_power_request": 0,
"therm_setpoint_temperature": 7,
"therm_setpoint_mode": "hg",
"therm_setpoint_start_time": 0,
"therm_setpoint_end_time": 0,
"anticipating": false,
"open_window": false
}
],
"id": "91763b24c43d3e344f424e8b",
"persons": [
{
"id": "91827374-7e04-5298-83ad-a0cb8372dff1",
"last_seen": 1557071156,
"out_of_sight": true
},
{
"id": "91827375-7e04-5298-83ae-a0cb8372dff2",
"last_seen": 1559282761,
"out_of_sight": false
},
{
"id": "91827376-7e04-5298-83af-a0cb8372dff3",
"last_seen": 1559224132,
"out_of_sight": true
}
]
}
}
}