diff --git a/.coveragerc b/.coveragerc
index 72d6e1e92933..d406698cbb11 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -635,8 +635,6 @@ omit =
homeassistant/components/netatmo/__init__.py
homeassistant/components/netatmo/api.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/helper.py
homeassistant/components/netatmo/light.py
diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py
index c2a6e484771c..3b05e263f023 100644
--- a/homeassistant/components/netatmo/climate.py
+++ b/homeassistant/components/netatmo/climate.py
@@ -2,6 +2,7 @@
import logging
from typing import List, Optional
+import pyatmo
import voluptuous as vol
from homeassistant.components.climate import ClimateEntity
@@ -251,7 +252,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
"""Handle webhook events."""
data = event["data"]
- if not data.get("home"):
+ if data.get("home") is None:
return
home = data["home"]
@@ -569,7 +570,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
schedule_id = sid
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
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"]}
-def interpolate(batterylevel, module_type):
+def interpolate(batterylevel: int, module_type: str) -> int:
"""Interpolate battery level depending on device type."""
na_battery_levels = {
NA_THERM: {
@@ -628,7 +631,7 @@ def interpolate(batterylevel, module_type):
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."""
if home_data is None:
return []
diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py
new file mode 100644
index 000000000000..c8014a9b2a93
--- /dev/null
+++ b/tests/components/netatmo/common.py
@@ -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 "{}"
diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py
new file mode 100644
index 000000000000..b18b70f323ec
--- /dev/null
+++ b/tests/components/netatmo/conftest.py
@@ -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
diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py
new file mode 100644
index 000000000000..e7ad16697696
--- /dev/null
+++ b/tests/components/netatmo/test_climate.py
@@ -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
diff --git a/tests/fixtures/netatmo/gethomedata.json b/tests/fixtures/netatmo/gethomedata.json
new file mode 100644
index 000000000000..db7d6aa438d8
--- /dev/null
+++ b/tests/fixtures/netatmo/gethomedata.json
@@ -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": "John Doe 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": "John Doe 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": "John Doe 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": "Jane Doe 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": "Bewegung 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": "Bewegung 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
+}
\ No newline at end of file
diff --git a/tests/fixtures/netatmo/getstationsdata.json b/tests/fixtures/netatmo/getstationsdata.json
new file mode 100644
index 000000000000..2a18c7bd2807
--- /dev/null
+++ b/tests/fixtures/netatmo/getstationsdata.json
@@ -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
+}
\ No newline at end of file
diff --git a/tests/fixtures/netatmo/homesdata.json b/tests/fixtures/netatmo/homesdata.json
new file mode 100644
index 000000000000..aecab91550cc
--- /dev/null
+++ b/tests/fixtures/netatmo/homesdata.json
@@ -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
+}
\ No newline at end of file
diff --git a/tests/fixtures/netatmo/homestatus.json b/tests/fixtures/netatmo/homestatus.json
new file mode 100644
index 000000000000..5d508ea03b0f
--- /dev/null
+++ b/tests/fixtures/netatmo/homestatus.json
@@ -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
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file