diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index caa4aebe376..9993b4efac2 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -42,6 +42,7 @@ from .const import ( DATA_SCHEDULES, DOMAIN, EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_SCHEDULE, EVENT_TYPE_SET_POINT, EVENT_TYPE_THERM_MODE, MANUFACTURER, @@ -236,6 +237,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): EVENT_TYPE_SET_POINT, EVENT_TYPE_THERM_MODE, EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_SCHEDULE, ): self._listeners.append( async_dispatcher_connect( @@ -253,7 +255,15 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): """Handle webhook events.""" data = event["data"] - if data.get("home") is None: + if self._home_id != data["home_id"]: + return + + if data["event_type"] == EVENT_TYPE_SCHEDULE and "schedule_id" in data: + self._selected_schedule = self.hass.data[DOMAIN][DATA_SCHEDULES][ + self._home_id + ].get(data["schedule_id"]) + self.async_write_ha_state() + self.data_handler.async_force_update(self._home_status_class) return home = data["home"] @@ -270,35 +280,37 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self._target_temperature = self._away_temperature elif self._preset == PRESET_SCHEDULE: self.async_update_callback() + self.data_handler.async_force_update(self._home_status_class) self.async_write_ha_state() return - if not home.get("rooms"): - return - - for room in home["rooms"]: - if data["event_type"] == EVENT_TYPE_SET_POINT: - if self._id == room["id"]: - if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: - self._hvac_mode = HVAC_MODE_OFF - elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: + for room in home.get("rooms", []): + if data["event_type"] == EVENT_TYPE_SET_POINT and self._id == room["id"]: + if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: + self._hvac_mode = HVAC_MODE_OFF + self._preset = STATE_NETATMO_OFF + self._target_temperature = 0 + elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: + self._hvac_mode = HVAC_MODE_HEAT + self._preset = PRESET_MAP_NETATMO[PRESET_BOOST] + self._target_temperature = DEFAULT_MAX_TEMP + elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: + self._hvac_mode = HVAC_MODE_HEAT + self._target_temperature = room["therm_setpoint_temperature"] + else: + self._target_temperature = room["therm_setpoint_temperature"] + if self._target_temperature == DEFAULT_MAX_TEMP: self._hvac_mode = HVAC_MODE_HEAT - self._target_temperature = DEFAULT_MAX_TEMP - elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: - self._hvac_mode = HVAC_MODE_HEAT - self._target_temperature = room["therm_setpoint_temperature"] - else: - self._target_temperature = room["therm_setpoint_temperature"] - if self._target_temperature == DEFAULT_MAX_TEMP: - self._hvac_mode = HVAC_MODE_HEAT - self.async_write_ha_state() - break + self.async_write_ha_state() + return - elif data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT: - if self._id == room["id"]: - self.async_update_callback() - self.async_write_ha_state() - break + if ( + data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT + and self._id == room["id"] + ): + self.async_update_callback() + self.async_write_ha_state() + return @property def supported_features(self): diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index ab268b8703b..b0a312fa1f3 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -94,6 +94,7 @@ SERVICE_SET_PERSON_AWAY = "set_person_away" EVENT_TYPE_SET_POINT = "set_point" EVENT_TYPE_CANCEL_SET_POINT = "cancel_set_point" EVENT_TYPE_THERM_MODE = "therm_mode" +EVENT_TYPE_SCHEDULE = "schedule" # Camera events EVENT_TYPE_LIGHT_MODE = "light_mode" EVENT_TYPE_CAMERA_OUTDOOR = "outdoor" diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index 910fb32a7cf..ecec2871df8 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -1,5 +1,5 @@ """The tests for the Netatmo climate platform.""" -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest @@ -394,30 +394,50 @@ async def test_webhook_event_handling_no_data(hass, climate_entry): async def test_service_schedule_thermostats(hass, climate_entry, caplog): """Test service for selecting Netatmo schedule with thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] 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() + with patch( + "pyatmo.thermostat.HomeData.switch_home_schedule" + ) as mock_switch_home_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() + mock_switch_home_schedule.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", schedule_id="b1b54a2f45795764f59d50d8" + ) + + # Fake backend response for valve being turned on + response = { + "event_type": "schedule", + "schedule_id": "b1b54a2f45795764f59d50d8", + "previous_schedule_id": "59d32176d183948b05ab4dce", + "push_type": "home_event_changed", + } + await simulate_webhook(hass, webhook_id, response) assert ( - "Setting 91763b24c43d3e344f424e8b schedule to Winter (b1b54a2f45795764f59d50d8)" - in caplog.text + hass.states.get(climate_entity_livingroom).attributes["selected_schedule"] + == "Winter" ) # 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() + with patch( + "pyatmo.thermostat.HomeData.switch_home_schedule" + ) as mock_switch_home_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() + mock_switch_home_schedule.assert_not_called() assert "summer is not a invalid schedule" in caplog.text @@ -668,3 +688,69 @@ async def test_get_all_home_ids(): } expected = ["123", "987"] assert climate.get_all_home_ids(home_data) == expected + + +async def test_webhook_home_id_mismatch(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + assert hass.states.get(climate_entity_entrada).state == "auto" + + # Fake backend response for valve being turned on + response = { + "room_id": "2833524037", + "home": { + "id": "123", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "auto" + + +async def test_webhook_set_point(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Fake backend response for valve being turned on + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + "therm_setpoint_temperature": 30, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "set_point", + "temperature": 21, + "push_type": "display_change", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat"