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/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

View File

@ -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 []

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
}
]
}
}
}