From 207cf18a46a30d073193ec1500cb96d6b8a28f7e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 31 Jul 2019 16:19:46 -0700 Subject: [PATCH 01/52] Bump version to 0.97.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 17e0e6752d2..54f4645edaa 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From 59b42b4236b0ccb7a787564ab924f30ab8e7f0b2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Aug 2019 12:32:48 -0700 Subject: [PATCH 02/52] Expose comfort presets as HA presets (#25491) * Expose comfort presets as HA presets * Fix bugs * Handle unavailable * log level debug on update * Lint --- homeassistant/components/ecobee/__init__.py | 2 +- homeassistant/components/ecobee/climate.py | 65 +++++++++------------ tests/components/ecobee/test_climate.py | 14 ----- 3 files changed, 29 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index e69884af59f..cb8b7436b51 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -97,7 +97,7 @@ class EcobeeData: def update(self): """Get the latest data from pyecobee.""" self.ecobee.update() - _LOGGER.info("Ecobee data updated successfully") + _LOGGER.debug("Ecobee data updated successfully") def setup(hass, config): diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 6520a3aadba..d9af0f93e11 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -88,16 +88,6 @@ PRESET_TO_ECOBEE_HOLD = { PRESET_HOLD_INDEFINITE: "indefinite", } -PRESET_MODES = [ - PRESET_NONE, - PRESET_AWAY, - PRESET_TEMPERATURE, - PRESET_HOME, - PRESET_SLEEP, - PRESET_HOLD_NEXT_TRANSITION, - PRESET_HOLD_INDEFINITE, -] - SERVICE_SET_FAN_MIN_ON_TIME = "ecobee_set_fan_min_on_time" SERVICE_RESUME_PROGRAM = "ecobee_resume_program" @@ -199,7 +189,6 @@ class Thermostat(ClimateDevice): self._name = self.thermostat["name"] self.hold_temp = hold_temp self.vacation = None - self._climate_list = self.climate_list self._operation_list = [] if self.thermostat["settings"]["heatStages"]: @@ -210,6 +199,10 @@ class Thermostat(ClimateDevice): self._operation_list.insert(0, HVAC_MODE_AUTO) self._operation_list.append(HVAC_MODE_OFF) + self._preset_modes = { + comfort["climateRef"]: comfort["name"] + for comfort in self.thermostat["program"]["climates"] + } self._fan_modes = [FAN_AUTO, FAN_ON] self.update_without_throttle = False @@ -223,6 +216,11 @@ class Thermostat(ClimateDevice): self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) + @property + def available(self): + """Return if device is available.""" + return self.thermostat["runtime"]["connected"] + @property def supported_features(self): """Return the list of supported features.""" @@ -294,15 +292,9 @@ class Thermostat(ClimateDevice): continue if event["type"] == "hold": - if event["holdClimateRef"] == "away": - if int(event["endDate"][0:4]) - int(event["startDate"][0:4]) <= 1: - # A temporary hold from away climate is a hold - return PRESET_AWAY - # A permanent hold from away climate - return PRESET_AWAY - if event["holdClimateRef"] != "": - # Any other hold based on climate - return event["holdClimateRef"] + if event["holdClimateRef"] in self._preset_modes: + return self._preset_modes[event["holdClimateRef"]] + # Any hold not based on a climate is a temp hold return PRESET_TEMPERATURE if event["type"].startswith("auto"): @@ -324,14 +316,6 @@ class Thermostat(ClimateDevice): """Return the operation modes list.""" return self._operation_list - @property - def climate_mode(self): - """Return current mode, as the user-visible name.""" - cur = self.thermostat["program"]["currentClimateRef"] - climates = self.thermostat["program"]["climates"] - current = list(filter(lambda x: x["climateRef"] == cur, climates)) - return current[0]["name"] - @property def current_humidity(self) -> Optional[int]: """Return the current humidity.""" @@ -373,9 +357,7 @@ class Thermostat(ClimateDevice): status = self.thermostat["equipmentStatus"] return { "fan": self.fan, - "climate_mode": self.climate_mode, "equipment_running": status, - "climate_list": self.climate_list, "fan_min_on_time": self.thermostat["settings"]["fanMinOnTime"], } @@ -413,6 +395,21 @@ class Thermostat(ClimateDevice): elif preset_mode == PRESET_NONE: self.data.ecobee.resume_program(self.thermostat_index) + elif preset_mode in self.preset_modes: + climate_ref = None + + for comfort in self.thermostat["program"]["climates"]: + if comfort["name"] == preset_mode: + climate_ref = comfort["climateRef"] + break + + if climate_ref is not None: + self.data.ecobee.set_climate_hold( + self.thermostat_index, climate_ref, self.hold_preference() + ) + else: + _LOGGER.warning("Received unknown preset mode: %s", preset_mode) + else: self.data.ecobee.set_climate_hold( self.thermostat_index, preset_mode, self.hold_preference() @@ -421,7 +418,7 @@ class Thermostat(ClimateDevice): @property def preset_modes(self): """Return available preset modes.""" - return PRESET_MODES + return list(self._preset_modes.values()) def set_auto_temp_hold(self, heat_temp, cool_temp): """Set temperature hold in auto mode.""" @@ -543,9 +540,3 @@ class Thermostat(ClimateDevice): # supported; note that this should not include 'indefinite' # as an indefinite away hold is interpreted as away_mode return "nextTransition" - - @property - def climate_list(self): - """Return the list of climates currently available.""" - climates = self.thermostat["program"]["climates"] - return list(map((lambda x: x["name"]), climates)) diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index fa3f84b4b12..24938e52621 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -130,44 +130,34 @@ class TestEcobee(unittest.TestCase): """Test device state attributes property.""" self.ecobee["equipmentStatus"] = "heatPump2" assert { - "climate_list": ["Climate1", "Climate2"], "fan": "off", "fan_min_on_time": 10, - "climate_mode": "Climate1", "equipment_running": "heatPump2", } == self.thermostat.device_state_attributes self.ecobee["equipmentStatus"] = "auxHeat2" assert { - "climate_list": ["Climate1", "Climate2"], "fan": "off", "fan_min_on_time": 10, - "climate_mode": "Climate1", "equipment_running": "auxHeat2", } == self.thermostat.device_state_attributes self.ecobee["equipmentStatus"] = "compCool1" assert { - "climate_list": ["Climate1", "Climate2"], "fan": "off", "fan_min_on_time": 10, - "climate_mode": "Climate1", "equipment_running": "compCool1", } == self.thermostat.device_state_attributes self.ecobee["equipmentStatus"] = "" assert { - "climate_list": ["Climate1", "Climate2"], "fan": "off", "fan_min_on_time": 10, - "climate_mode": "Climate1", "equipment_running": "", } == self.thermostat.device_state_attributes self.ecobee["equipmentStatus"] = "Unknown" assert { - "climate_list": ["Climate1", "Climate2"], "fan": "off", "fan_min_on_time": 10, - "climate_mode": "Climate1", "equipment_running": "Unknown", } == self.thermostat.device_state_attributes @@ -267,10 +257,6 @@ class TestEcobee(unittest.TestCase): self.ecobee["settings"]["holdAction"] = action assert "nextTransition" == self.thermostat.hold_preference() - def test_climate_list(self): - """Test climate list property.""" - assert ["Climate1", "Climate2"] == self.thermostat.climate_list - def test_set_fan_mode_on(self): """Test set fan mode to on.""" self.data.reset_mock() From d3f6c43bbdcf920661d4538470647a2ddb070999 Mon Sep 17 00:00:00 2001 From: Martin Eberhardt Date: Thu, 1 Aug 2019 22:02:11 +0200 Subject: [PATCH 03/52] Fix handling of empty results from Rejseplanen (#25610) * Improve handling of empty results from Rejseplanen (Fixes #25566) * Exclude attributes with null value * Add period back into docstring * Fix formatting --- .../components/rejseplanen/sensor.py | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 3ba2b06eb02..99cfe1067e8 100755 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -111,14 +111,14 @@ class RejseplanenTransportSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" if not self._times: - return None + return {ATTR_STOP_ID: self._stop_id, ATTR_ATTRIBUTION: ATTRIBUTION} next_up = [] if len(self._times) > 1: next_up = self._times[1:] - params = { - ATTR_DUE_IN: str(self._times[0][ATTR_DUE_IN]), + return { + ATTR_DUE_IN: self._times[0][ATTR_DUE_IN], ATTR_DUE_AT: self._times[0][ATTR_DUE_AT], ATTR_TYPE: self._times[0][ATTR_TYPE], ATTR_ROUTE: self._times[0][ATTR_ROUTE], @@ -128,7 +128,6 @@ class RejseplanenTransportSensor(Entity): ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_NEXT_UP: next_up, } - return {k: v for k, v in params.items() if v} @property def unit_of_measurement(self): @@ -144,10 +143,14 @@ class RejseplanenTransportSensor(Entity): """Get the latest data from rejseplanen.dk and update the states.""" self.data.update() self._times = self.data.info - try: - self._state = self._times[0][ATTR_DUE_IN] - except TypeError: - pass + + if not self._times: + self._state = None + else: + try: + self._state = self._times[0][ATTR_DUE_IN] + except TypeError: + pass class PublicTransportData: @@ -159,20 +162,7 @@ class PublicTransportData: self.route = route self.direction = direction self.departure_type = departure_type - self.info = self.empty_result() - - def empty_result(self): - """Object returned when no departures are found.""" - return [ - { - ATTR_DUE_IN: "n/a", - ATTR_DUE_AT: "n/a", - ATTR_TYPE: "n/a", - ATTR_ROUTE: self.route, - ATTR_DIRECTION: "n/a", - ATTR_STOP_NAME: "n/a", - } - ] + self.info = [] def update(self): """Get the latest data from rejseplanen.""" @@ -200,11 +190,9 @@ class PublicTransportData: ) except rjpl.rjplAPIError as error: _LOGGER.debug("API returned error: %s", error) - self.info = self.empty_result() return except (rjpl.rjplConnectionError, rjpl.rjplHTTPError): _LOGGER.debug("Error occured while connecting to the API") - self.info = self.empty_result() return # Filter result @@ -246,7 +234,6 @@ class PublicTransportData: if not self.info: _LOGGER.debug("No departures with given parameters") - self.info = self.empty_result() # Sort the data by time self.info = sorted(self.info, key=itemgetter(ATTR_DUE_IN)) From 7168dd6ceca3e7bf1fce2e69052d661ee479f16b Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 31 Jul 2019 23:33:12 -0400 Subject: [PATCH 04/52] bump quirks (#25618) --- homeassistant/components/zha/manifest.json | 7 ++----- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 199428302ad..88c5f171116 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -5,14 +5,11 @@ "documentation": "https://www.home-assistant.io/components/zha", "requirements": [ "bellows-homeassistant==0.9.0", - "zha-quirks==0.0.19", + "zha-quirks==0.0.20", "zigpy-deconz==0.2.1", "zigpy-homeassistant==0.7.0", "zigpy-xbee-homeassistant==0.4.0" ], "dependencies": [], - "codeowners": [ - "@dmulcahey", - "@adminiuga" - ] + "codeowners": ["@dmulcahey", "@adminiuga"] } diff --git a/requirements_all.txt b/requirements_all.txt index cf06f2fbe65..c6e0ca6abf1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1963,7 +1963,7 @@ zengge==0.2 zeroconf==0.23.0 # homeassistant.components.zha -zha-quirks==0.0.19 +zha-quirks==0.0.20 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 From 56ca0edaa7fbc46a7c2d2f29277c53af481a7176 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 1 Aug 2019 17:22:08 +0200 Subject: [PATCH 05/52] Handle disabled devices (#25625) --- homeassistant/components/unifi/device_tracker.py | 2 +- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/unifi/test_device_tracker.py | 10 ++++++++++ 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index e7344692ecd..4046f5f63d2 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -304,7 +304,7 @@ class UniFiDeviceTracker(ScannerEntity): @property def available(self) -> bool: """Return if controller is available.""" - return self.controller.available + return not self.device.disabled and self.controller.available @property def device_info(self): diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index dc5e89c147e..e849fd34d25 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/components/unifi", "requirements": [ - "aiounifi==8" + "aiounifi==9" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index c6e0ca6abf1..5d365c9c732 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -169,7 +169,7 @@ aiopvapi==1.6.14 aioswitcher==2019.4.26 # homeassistant.components.unifi -aiounifi==8 +aiounifi==9 # homeassistant.components.wwlln aiowwlln==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c2e4cfef22d..1feeab8f32d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -68,7 +68,7 @@ aionotion==1.1.0 aioswitcher==2019.4.26 # homeassistant.components.unifi -aiounifi==8 +aiounifi==9 # homeassistant.components.wwlln aiowwlln==1.0.0 diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 937de3ad631..5accbb584b4 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -203,6 +203,16 @@ async def test_tracked_devices(hass, mock_controller): device_1 = hass.states.get("device_tracker.device_1") assert device_1.state == "home" + device_1_copy = copy(DEVICE_1) + device_1_copy["disabled"] = True + mock_controller.mock_client_responses.append({}) + mock_controller.mock_device_responses.append([device_1_copy]) + await mock_controller.async_update() + await hass.async_block_till_done() + + device_1 = hass.states.get("device_tracker.device_1") + assert device_1.state == "unavailable" + async def test_restoring_client(hass, mock_controller): """Test the update_items function with some clients.""" From 414b85c253f86fba469cd412f7eadd509fe975b2 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 1 Aug 2019 16:35:19 +0100 Subject: [PATCH 06/52] Fix polling HomeKit devices with multiple services per accessory (#25629) --- homeassistant/components/homekit_controller/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index fd9c960980c..79636cea9f3 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -120,13 +120,21 @@ class HomeKitEntity(Entity): """Collect new data from bridge and update the entity state in hass.""" accessory_state = self._accessory.current_state.get(self._aid, {}) for iid, result in accessory_state.items(): + # No value so dont process this result if "value" not in result: continue + + # Unknown iid - this is probably for a sibling service that is part + # of the same physical accessory. Ignore it. + if iid not in self._char_names: + continue + # Callback to update the entity with this characteristic value char_name = escape_characteristic_name(self._char_names[iid]) update_fn = getattr(self, "_update_{}".format(char_name), None) if not update_fn: continue + # pylint: disable=not-callable update_fn(result["value"]) From 725d5c636e12feff5fc73bf014058193b9dcc771 Mon Sep 17 00:00:00 2001 From: Oncleben31 Date: Thu, 1 Aug 2019 21:45:16 +0200 Subject: [PATCH 07/52] Meteofrance improve log error messages (#25630) * Improve log error messages * remove unique_id not ready yet --- homeassistant/components/meteo_france/__init__.py | 14 +++++++++++--- homeassistant/components/meteo_france/sensor.py | 12 ++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index d227a7fe47c..ab3ec45867b 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -113,13 +113,17 @@ def setup(hass, config): # If weather alert monitoring is expected initiate a client to be used by # all weather_alert entities. if need_weather_alert_watcher: + _LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo") from vigilancemeteo import VigilanceMeteoFranceProxy, VigilanceMeteoError weather_alert_client = VigilanceMeteoFranceProxy() try: weather_alert_client.update_data() except VigilanceMeteoError as exp: - _LOGGER.error(exp) + _LOGGER.error( + "Unexpected error when creating the" "vigilance_meteoFrance proxy: %s ", + exp, + ) else: weather_alert_client = None hass.data[DATA_METEO_FRANCE]["weather_alert_client"] = weather_alert_client @@ -133,7 +137,9 @@ def setup(hass, config): try: client = meteofranceClient(city) except meteofranceError as exp: - _LOGGER.error(exp) + _LOGGER.error( + "Unexpected error when creating the meteofrance proxy: %s", exp + ) return client.need_rain_forecast = bool( @@ -179,4 +185,6 @@ class MeteoFranceUpdater: try: self._client.update() except meteofranceError as exp: - _LOGGER.error(exp) + _LOGGER.error( + "Unexpected error when updating the meteofrance proxy: %s", exp + ) diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 9ee9ce9cef6..95113a60cd3 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -35,18 +35,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None): datas["dept"], weather_alert_client ) except ValueError as exp: - _LOGGER.error(exp) + _LOGGER.error( + "Unexpected error when creating the weather alert sensor for %s in department %s: %s", + city, + datas["dept"], + exp, + ) alert_watcher = None else: _LOGGER.info( - "weather alert watcher added for %s" "in department %s", + "Weather alert watcher added for %s" "in department %s", city, datas["dept"], ) else: _LOGGER.warning( - "No dept key found for '%s'. So weather alert " - "information won't be available", + "No 'dept' key found for '%s'. So weather alert information won't be available", city, ) # Exit and don't create the sensor if no department code available. From 1d5709f49f6c040173a763c94353f4f0f277b5d1 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 1 Aug 2019 20:44:30 +0100 Subject: [PATCH 08/52] Bump homekit_python to 0.15 (#25631) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 62dbf3740a3..70f6f6a3ce4 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/components/homekit_controller", "requirements": [ - "homekit[IP]==0.14.0" + "homekit[IP]==0.15.0" ], "dependencies": [], "zeroconf": ["_hap._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index 5d365c9c732..b390f7ee531 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -628,7 +628,7 @@ home-assistant-frontend==20190731.0 homeassistant-pyozw==0.1.4 # homeassistant.components.homekit_controller -homekit[IP]==0.14.0 +homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud homematicip==0.10.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1feeab8f32d..2bedea3d2d6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -172,7 +172,7 @@ holidays==0.9.11 home-assistant-frontend==20190731.0 # homeassistant.components.homekit_controller -homekit[IP]==0.14.0 +homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud homematicip==0.10.9 From 476a727df3bcc6e553449ecb238d95f3f37244a6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Aug 2019 11:52:57 -0700 Subject: [PATCH 09/52] Filter out empty results in history API (#25633) --- homeassistant/components/history/__init__.py | 6 ++++-- tests/components/history/test_init.py | 22 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 3b751b86c73..d402aceaa40 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -234,6 +234,7 @@ def states_to_json( axis correctly. """ result = defaultdict(list) + # Set all entity IDs to empty lists in result set to maintain the order if entity_ids is not None: for ent_id in entity_ids: result[ent_id] = [] @@ -253,7 +254,9 @@ def states_to_json( # Append all changes to it for ent_id, group in groupby(states, lambda state: state.entity_id): result[ent_id].extend(group) - return result + + # Filter out the empty lists if some states had 0 results. + return {key: val for key, val in result.items() if val} def get_state(hass, utc_point_in_time, entity_id, run=None): @@ -348,7 +351,6 @@ class HistoryPeriodView(HomeAssistantView): # Optionally reorder the result to respect the ordering given # by any entities explicitly included in the configuration. - if self.use_include_order: sorted_result = [] for order_entity in self.filters.included_entities: diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index ebd5991235d..68bc9c5371f 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -628,3 +628,25 @@ async def test_fetch_period_api(hass, hass_client): "/api/history/period/{}".format(dt_util.utcnow().isoformat()) ) assert response.status == 200 + + +async def test_fetch_period_api_with_include_order(hass, hass_client): + """Test the fetch period view for history.""" + await hass.async_add_job(init_recorder_component, hass) + await async_setup_component( + hass, + "history", + { + "history": { + "use_include_order": True, + "include": {"entities": ["light.kitchen"]}, + } + }, + ) + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + client = await hass_client() + response = await client.get( + "/api/history/period/{}".format(dt_util.utcnow().isoformat()), + params={"filter_entity_id": "non.existing,something.else"}, + ) + assert response.status == 200 From a8c4fc33f60291ca17e6e61cf512591203081e6c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Aug 2019 13:03:45 -0700 Subject: [PATCH 10/52] Upgrade hass-nabucasa to 0.16 (#25636) --- homeassistant/components/cloud/manifest.json | 13 +++---------- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index e848f54425b..58739bededc 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,14 +2,7 @@ "domain": "cloud", "name": "Cloud", "documentation": "https://www.home-assistant.io/components/cloud", - "requirements": [ - "hass-nabucasa==0.15" - ], - "dependencies": [ - "http", - "webhook" - ], - "codeowners": [ - "@home-assistant/cloud" - ] + "requirements": ["hass-nabucasa==0.16"], + "dependencies": ["http", "webhook"], + "codeowners": ["@home-assistant/cloud"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 187ee921e25..c5c8dd9c9c0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -9,7 +9,7 @@ bcrypt==3.1.7 certifi>=2019.6.16 cryptography==2.7 distro==1.4.0 -hass-nabucasa==0.15 +hass-nabucasa==0.16 home-assistant-frontend==20190731.0 importlib-metadata==0.18 jinja2>=2.10.1 diff --git a/requirements_all.txt b/requirements_all.txt index b390f7ee531..7b88be5c023 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -592,7 +592,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.15 +hass-nabucasa==0.16 # homeassistant.components.mqtt hbmqtt==0.9.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2bedea3d2d6..33815c4c0dd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -157,7 +157,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.15 +hass-nabucasa==0.16 # homeassistant.components.mqtt hbmqtt==0.9.4 From d64730a3cfe4e6e34821680af5c421854174783c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Aug 2019 13:34:28 -0700 Subject: [PATCH 11/52] Updated frontend to 20190801.0 --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index c3b7fa3e392..b6a996afc98 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/components/frontend", "requirements": [ - "home-assistant-frontend==20190731.0" + "home-assistant-frontend==20190801.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c5c8dd9c9c0..942cdb577f9 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ certifi>=2019.6.16 cryptography==2.7 distro==1.4.0 hass-nabucasa==0.16 -home-assistant-frontend==20190731.0 +home-assistant-frontend==20190801.0 importlib-metadata==0.18 jinja2>=2.10.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 7b88be5c023..62de816853c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -622,7 +622,7 @@ hole==0.3.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190731.0 +home-assistant-frontend==20190801.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33815c4c0dd..2d3e89dc270 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -169,7 +169,7 @@ hdate==0.8.8 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190731.0 +home-assistant-frontend==20190801.0 # homeassistant.components.homekit_controller homekit[IP]==0.15.0 From ad341e21529fbe4615c1423ac602562156684f6e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Aug 2019 13:36:24 -0700 Subject: [PATCH 12/52] Bumped version to 0.97.0b1 --- homeassistant/const.py | 2 +- script/version_bump.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 54f4645edaa..4de4048d4f9 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) diff --git a/script/version_bump.py b/script/version_bump.py index 7c584daae7e..db3f3ac273d 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -102,7 +102,7 @@ def write_version(version): "MINOR_VERSION = .*\n", "MINOR_VERSION = {}\n".format(minor), content ) content = re.sub( - "PATCH_VERSION = .*\n", "PATCH_VERSION = '{}'\n".format(patch), content + "PATCH_VERSION = .*\n", 'PATCH_VERSION = "{}"\n'.format(patch), content ) with open("homeassistant/const.py", "wt") as fil: From 949875ae501984e75b4ea66a440f00abb365c6a6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 4 Aug 2019 22:30:31 -0700 Subject: [PATCH 13/52] Updated frontend to 20190804.0 --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index b6a996afc98..4cea624b4cd 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/components/frontend", "requirements": [ - "home-assistant-frontend==20190801.0" + "home-assistant-frontend==20190804.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 942cdb577f9..13c5b1a0144 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ certifi>=2019.6.16 cryptography==2.7 distro==1.4.0 hass-nabucasa==0.16 -home-assistant-frontend==20190801.0 +home-assistant-frontend==20190804.0 importlib-metadata==0.18 jinja2>=2.10.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 62de816853c..d70c0602c4c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -622,7 +622,7 @@ hole==0.3.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190801.0 +home-assistant-frontend==20190804.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2d3e89dc270..0d71d23b74f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -169,7 +169,7 @@ hdate==0.8.8 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190801.0 +home-assistant-frontend==20190804.0 # homeassistant.components.homekit_controller homekit[IP]==0.15.0 From 8d1deef9cc891e8dc2a896faaf26b21409b26493 Mon Sep 17 00:00:00 2001 From: Santobert Date: Fri, 2 Aug 2019 10:00:33 +0200 Subject: [PATCH 14/52] Feature zwave preset modes (#25537) * Initial commit * Add some more code * Local tests passing * Remove unnecessary line * Add preset attributes to __init__ * Remove some more debugger lines * Add some tests * Fix comparision to None * Improve test coverage * Use unknown modes as presets * Bugfixes and test improvements * Add tests for unknown preset modes * linting * Improve mappings * Move PRESET_MANUFACTURER_SPECIFIC to zwave * Replace isinstance with cast * Add test for hvac_action * hvac_mode is never None * Improved mapping of current mode to hvac/preset modes * Fix bugs where hvac_mode is None * Add default hvac mode * Fixed default hvac mode * Fix linting * Make flake happy * Another linting * Make black happy * Complete list of default hvac modes * Add mapping to heat/cool eco * Fixed another bug where mapping goes wrong --- homeassistant/components/zwave/climate.py | 203 ++++++++++++--- tests/components/zwave/test_climate.py | 294 ++++++++++++++++++++-- 2 files changed, 440 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 15a0c5ab78b..6f66c6f36c4 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -10,15 +10,19 @@ from homeassistant.components.climate.const import ( CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, DOMAIN, + HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_OFF, + PRESET_BOOST, + PRESET_NONE, SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_PRESET_MODE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback @@ -37,38 +41,57 @@ REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120) ATTR_OPERATING_STATE = "operating_state" ATTR_FAN_STATE = "fan_state" + +# Device is in manufacturer specific mode (e.g. setting the valve manually) +PRESET_MANUFACTURER_SPECIFIC = "Manufacturer Specific" + WORKAROUND_ZXT_120 = "zxt_120" DEVICE_MAPPINGS = {REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120} HVAC_STATE_MAPPINGS = { - "Off": HVAC_MODE_OFF, - "Heat": HVAC_MODE_HEAT, - "Heat Mode": HVAC_MODE_HEAT, - "Heat (Default)": HVAC_MODE_HEAT, - "Aux Heat": HVAC_MODE_HEAT, - "Furnace": HVAC_MODE_HEAT, - "Fan Only": HVAC_MODE_FAN_ONLY, - "Dry Air": HVAC_MODE_DRY, - "Moist Air": HVAC_MODE_DRY, - "Cool": HVAC_MODE_COOL, - "Auto": HVAC_MODE_HEAT_COOL, + "off": HVAC_MODE_OFF, + "heat": HVAC_MODE_HEAT, + "heat mode": HVAC_MODE_HEAT, + "heat (default)": HVAC_MODE_HEAT, + "aux heat": HVAC_MODE_HEAT, + "furnace": HVAC_MODE_HEAT, + "fan only": HVAC_MODE_FAN_ONLY, + "dry air": HVAC_MODE_DRY, + "moist air": HVAC_MODE_DRY, + "cool": HVAC_MODE_COOL, + "heat_cool": HVAC_MODE_HEAT_COOL, + "auto": HVAC_MODE_HEAT_COOL, } - HVAC_CURRENT_MAPPINGS = { - "Idle": CURRENT_HVAC_IDLE, - "Heat": CURRENT_HVAC_HEAT, - "Pending Heat": CURRENT_HVAC_IDLE, - "Heating": CURRENT_HVAC_HEAT, - "Cool": CURRENT_HVAC_COOL, - "Pending Cool": CURRENT_HVAC_IDLE, - "Cooling": CURRENT_HVAC_COOL, - "Fan Only": CURRENT_HVAC_FAN, - "Vent / Economiser": CURRENT_HVAC_FAN, - "Off": CURRENT_HVAC_OFF, + "idle": CURRENT_HVAC_IDLE, + "heat": CURRENT_HVAC_HEAT, + "pending heat": CURRENT_HVAC_IDLE, + "heating": CURRENT_HVAC_HEAT, + "cool": CURRENT_HVAC_COOL, + "pending cool": CURRENT_HVAC_IDLE, + "cooling": CURRENT_HVAC_COOL, + "fan only": CURRENT_HVAC_FAN, + "vent / economiser": CURRENT_HVAC_FAN, + "off": CURRENT_HVAC_OFF, } +PRESET_MAPPINGS = { + "full power": PRESET_BOOST, + "manufacturer specific": PRESET_MANUFACTURER_SPECIFIC, +} + +DEFAULT_HVAC_MODES = [ + HVAC_MODE_HEAT_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_DRY, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, +] + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave climate devices.""" @@ -101,9 +124,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): self._target_temperature = None self._current_temperature = None self._hvac_action = None - self._hvac_list = None - self._hvac_mapping = None - self._hvac_mode = None + self._hvac_list = None # [zwave_mode] + self._hvac_mapping = None # {ha_mode:zwave_mode} + self._hvac_mode = None # ha_mode + self._default_hvac_mode = None # ha_mode + self._preset_mapping = None # {ha_mode:zwave_mode} + self._preset_list = None # [zwave_mode] + self._preset_mode = None # ha_mode if exists, else zwave_mode self._current_fan_mode = None self._fan_modes = None self._fan_state = None @@ -132,6 +159,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): support |= SUPPORT_FAN_MODE if self._zxt_120 == 1 and self.values.zxt_120_swing_mode: support |= SUPPORT_SWING_MODE + if self._preset_list: + support |= SUPPORT_PRESET_MODE return support def update_properties(self): @@ -140,26 +169,86 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): if self.values.mode: self._hvac_list = [] self._hvac_mapping = {} - hvac_list = self.values.mode.data_items - if hvac_list: - for mode in hvac_list: - ha_mode = HVAC_STATE_MAPPINGS.get(mode) + self._preset_list = [] + self._preset_mapping = {} + + mode_list = self.values.mode.data_items + if mode_list: + for mode in mode_list: + ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower()) + ha_preset = PRESET_MAPPINGS.get(str(mode).lower()) if ha_mode and ha_mode not in self._hvac_mapping: self._hvac_mapping[ha_mode] = mode self._hvac_list.append(ha_mode) - continue - self._hvac_list.append(mode) + elif ha_preset and ha_preset not in self._preset_mapping: + self._preset_mapping[ha_preset] = mode + self._preset_list.append(ha_preset) + else: + # If nothing matches + self._preset_list.append(mode) + + # Default operation mode + for mode in DEFAULT_HVAC_MODES: + if mode in self._hvac_mapping.keys(): + self._default_hvac_mode = mode + break + + if self._preset_list: + # Presets are supported + self._preset_list.append(PRESET_NONE) + current_mode = self.values.mode.data - self._hvac_mode = next( + _LOGGER.debug("current_mode=%s", current_mode) + _hvac_temp = next( ( key for key, value in self._hvac_mapping.items() if value == current_mode ), - current_mode, + None, ) + + if _hvac_temp is None: + # The current mode is not a hvac mode + if ( + "heat" in current_mode.lower() + and HVAC_MODE_HEAT in self._hvac_mapping.keys() + ): + # The current preset modes maps to HVAC_MODE_HEAT + _LOGGER.debug("Mapped to HEAT") + self._hvac_mode = HVAC_MODE_HEAT + elif ( + "cool" in current_mode.lower() + and HVAC_MODE_COOL in self._hvac_mapping.keys() + ): + # The current preset modes maps to HVAC_MODE_COOL + _LOGGER.debug("Mapped to COOL") + self._hvac_mode = HVAC_MODE_COOL + else: + # The current preset modes maps to self._default_hvac_mode + _LOGGER.debug("Mapped to DEFAULT") + self._hvac_mode = self._default_hvac_mode + self._preset_mode = next( + ( + key + for key, value in self._preset_mapping.items() + if value == current_mode + ), + current_mode, + ) + else: + # The current mode is a hvac mode + self._hvac_mode = _hvac_temp + self._preset_mode = PRESET_NONE + + _LOGGER.debug("self._hvac_mapping=%s", self._hvac_mapping) _LOGGER.debug("self._hvac_list=%s", self._hvac_list) + _LOGGER.debug("self._hvac_mode=%s", self._hvac_mode) + _LOGGER.debug("self._default_hvac_mode=%s", self._default_hvac_mode) _LOGGER.debug("self._hvac_action=%s", self._hvac_action) + _LOGGER.debug("self._preset_mapping=%s", self._preset_mapping) + _LOGGER.debug("self._preset_list=%s", self._preset_list) + _LOGGER.debug("self._preset_mode=%s", self._preset_mode) # Current Temp if self.values.temperature: @@ -199,7 +288,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): # Operating state if self.values.operating_state: mode = self.values.operating_state.data - self._hvac_action = HVAC_CURRENT_MAPPINGS.get(mode) + self._hvac_action = HVAC_CURRENT_MAPPINGS.get(str(mode).lower(), mode) # Fan operating state if self.values.fan_state: @@ -247,7 +336,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """ if self.values.mode: return self._hvac_mode - return HVAC_MODE_HEAT + return self._default_hvac_mode @property def hvac_modes(self): @@ -267,6 +356,26 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """ return self._hvac_action + @property + def preset_mode(self): + """Return preset operation ie. eco, away. + + Need to be one of PRESET_*. + """ + if self.values.mode: + return self._preset_mode + return PRESET_NONE + + @property + def preset_modes(self): + """Return the list of available preset operation modes. + + Need to be a subset of PRESET_MODES. + """ + if self.values.mode: + return self._preset_list + return [] + @property def target_temperature(self): """Return the temperature we try to reach.""" @@ -274,24 +383,46 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def set_temperature(self, **kwargs): """Set new target temperature.""" + _LOGGER.debug("Set temperature to %s", kwargs.get(ATTR_TEMPERATURE)) if kwargs.get(ATTR_TEMPERATURE) is None: return self.values.primary.data = kwargs.get(ATTR_TEMPERATURE) def set_fan_mode(self, fan_mode): """Set new target fan mode.""" + _LOGGER.debug("Set fan mode to %s", fan_mode) if not self.values.fan_mode: return self.values.fan_mode.data = fan_mode def set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" + _LOGGER.debug("Set hvac_mode to %s", hvac_mode) if not self.values.mode: return - self.values.mode.data = self._hvac_mapping.get(hvac_mode, hvac_mode) + operation_mode = self._hvac_mapping.get(hvac_mode) + _LOGGER.debug("Set operation_mode to %s", operation_mode) + self.values.mode.data = operation_mode + + def set_preset_mode(self, preset_mode): + """Set new target preset mode.""" + _LOGGER.debug("Set preset_mode to %s", preset_mode) + if not self.values.mode: + return + if preset_mode == PRESET_NONE: + # Activate the current hvac mode + self.update_properties() + operation_mode = self._hvac_mapping.get(self.hvac_mode) + _LOGGER.debug("Set operation_mode to %s", operation_mode) + self.values.mode.data = operation_mode + else: + operation_mode = self._preset_mapping.get(preset_mode, preset_mode) + _LOGGER.debug("Set operation_mode to %s", operation_mode) + self.values.mode.data = operation_mode def set_swing_mode(self, swing_mode): """Set new target swing mode.""" + _LOGGER.debug("Set swing_mode to %s", swing_mode) if self._zxt_120 == 1: if self.values.zxt_120_swing_mode: self.values.zxt_120_swing_mode.data = swing_mode diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index 430b901efbc..60a9dcd0dab 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -2,11 +2,23 @@ import pytest from homeassistant.components.climate.const import ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_COOL, + HVAC_MODES, HVAC_MODE_COOL, HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + PRESET_BOOST, + PRESET_ECO, + PRESET_NONE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.components.zwave import climate +from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed @@ -19,9 +31,18 @@ def device(hass, mock_openzwave): values = MockEntityValues( primary=MockValue(data=1, node=node), temperature=MockValue(data=5, node=node, units=None), - mode=MockValue(data="test1", data_items=[0, 1, 2], node=node), + mode=MockValue( + data=HVAC_MODE_HEAT, + data_items=[ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + ], + node=node, + ), fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), - operating_state=MockValue(data=6, node=node), + operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node), fan_state=MockValue(data=7, node=node), ) device = climate.get_device(hass, node=node, values=values, node_config={}) @@ -37,9 +58,18 @@ def device_zxt_120(hass, mock_openzwave): values = MockEntityValues( primary=MockValue(data=1, node=node), temperature=MockValue(data=5, node=node, units=None), - mode=MockValue(data="test1", data_items=[0, 1, 2], node=node), + mode=MockValue( + data=HVAC_MODE_HEAT, + data_items=[ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + ], + node=node, + ), fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), - operating_state=MockValue(data=6, node=node), + operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node), fan_state=MockValue(data=7, node=node), zxt_120_swing_mode=MockValue(data="test3", data_items=[6, 7, 8], node=node), ) @@ -55,9 +85,13 @@ def device_mapping(hass, mock_openzwave): values = MockEntityValues( primary=MockValue(data=1, node=node), temperature=MockValue(data=5, node=node, units=None), - mode=MockValue(data="Off", data_items=["Off", "Cool", "Heat"], node=node), + mode=MockValue( + data="Heat", + data_items=["Off", "Cool", "Heat", "Full Power", "heat_cool"], + node=node, + ), fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), - operating_state=MockValue(data=6, node=node), + operating_state=MockValue(data="heating", node=node), fan_state=MockValue(data=7, node=node), ) device = climate.get_device(hass, node=node, values=values, node_config={}) @@ -65,6 +99,83 @@ def device_mapping(hass, mock_openzwave): yield device +@pytest.fixture +def device_unknown(hass, mock_openzwave): + """Fixture to provide a precreated climate device. Test state unknown.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=1, node=node), + temperature=MockValue(data=5, node=node, units=None), + mode=MockValue( + data="Heat", + data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"], + node=node, + ), + fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), + operating_state=MockValue(data="test4", node=node), + fan_state=MockValue(data=7, node=node), + ) + device = climate.get_device(hass, node=node, values=values, node_config={}) + + yield device + + +@pytest.fixture +def device_heat_cool(hass, mock_openzwave): + """Fixture to provide a precreated climate device. Test state heat only.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=1, node=node), + temperature=MockValue(data=5, node=node, units=None), + mode=MockValue( + data=HVAC_MODE_HEAT, + data_items=[ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + "Heat Eco", + "Cool Eco", + ], + node=node, + ), + fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node), + operating_state=MockValue(data="test4", node=node), + fan_state=MockValue(data=7, node=node), + ) + device = climate.get_device(hass, node=node, values=values, node_config={}) + + yield device + + +def test_default_hvac_modes(): + """Test wether all hvac modes are included in default_hvac_modes.""" + for hvac_mode in HVAC_MODES: + assert hvac_mode in DEFAULT_HVAC_MODES + + +def test_supported_features(device): + """Test supported features flags.""" + assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + + +def test_supported_features_preset_mode(device_mapping): + """Test supported features flags with swing mode.""" + device = device_mapping + assert ( + device.supported_features + == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_PRESET_MODE + ) + + +def test_supported_features_swing_mode(device_zxt_120): + """Test supported features flags with swing mode.""" + device = device_zxt_120 + assert ( + device.supported_features + == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_SWING_MODE + ) + + def test_zxt_120_swing_mode(device_zxt_120): """Test operation of the zxt 120 swing mode.""" device = device_zxt_120 @@ -107,7 +218,24 @@ def test_default_target_temperature(device): def test_data_lists(device): """Test data lists from zwave value items.""" assert device.fan_modes == [3, 4, 5] - assert device.hvac_modes == [0, 1, 2] + assert device.hvac_modes == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + ] + assert device.preset_modes == [] + device.values.mode = None + assert device.preset_modes == [] + + +def test_data_lists_mapping(device_mapping): + """Test data lists from zwave value items.""" + device = device_mapping + assert device.hvac_modes == ["off", "cool", "heat", "heat_cool"] + assert device.preset_modes == ["boost", "none"] + device.values.mode = None + assert device.preset_modes == [] def test_target_value_set(device): @@ -121,21 +249,56 @@ def test_target_value_set(device): def test_operation_value_set(device): """Test values changed for climate device.""" - assert device.values.mode.data == "test1" - device.set_hvac_mode("test_set") - assert device.values.mode.data == "test_set" + assert device.values.mode.data == HVAC_MODE_HEAT + device.set_hvac_mode(HVAC_MODE_COOL) + assert device.values.mode.data == HVAC_MODE_COOL + device.set_preset_mode(PRESET_ECO) + assert device.values.mode.data == PRESET_ECO + device.set_preset_mode(PRESET_NONE) + assert device.values.mode.data == HVAC_MODE_HEAT_COOL + device.values.mode = None + device.set_hvac_mode("test_set_failes") + assert device.values.mode is None + device.set_preset_mode("test_set_failes") + assert device.values.mode is None def test_operation_value_set_mapping(device_mapping): """Test values changed for climate device. Mapping.""" device = device_mapping - assert device.values.mode.data == "Off" - device.set_hvac_mode(HVAC_MODE_HEAT) assert device.values.mode.data == "Heat" device.set_hvac_mode(HVAC_MODE_COOL) assert device.values.mode.data == "Cool" device.set_hvac_mode(HVAC_MODE_OFF) assert device.values.mode.data == "Off" + device.set_preset_mode(PRESET_BOOST) + assert device.values.mode.data == "Full Power" + device.set_preset_mode(PRESET_ECO) + assert device.values.mode.data == "eco" + + +def test_operation_value_set_unknown(device_unknown): + """Test values changed for climate device. Unknown.""" + device = device_unknown + assert device.values.mode.data == "Heat" + device.set_preset_mode("Abcdefg") + assert device.values.mode.data == "Abcdefg" + device.set_preset_mode(PRESET_NONE) + assert device.values.mode.data == HVAC_MODE_HEAT_COOL + + +def test_operation_value_set_heat_cool(device_heat_cool): + """Test values changed for climate device. Heat/Cool only.""" + device = device_heat_cool + assert device.values.mode.data == HVAC_MODE_HEAT + device.set_preset_mode("Heat Eco") + assert device.values.mode.data == "Heat Eco" + device.set_preset_mode(PRESET_NONE) + assert device.values.mode.data == HVAC_MODE_HEAT + device.set_preset_mode("Cool Eco") + assert device.values.mode.data == "Cool Eco" + device.set_preset_mode(PRESET_NONE) + assert device.values.mode.data == HVAC_MODE_COOL def test_fan_mode_value_set(device): @@ -143,6 +306,9 @@ def test_fan_mode_value_set(device): assert device.values.fan_mode.data == "test2" device.set_fan_mode("test_fan_set") assert device.values.fan_mode.data == "test_fan_set" + device.values.fan_mode = None + device.set_fan_mode("test_fan_set_failes") + assert device.values.fan_mode is None def test_target_value_changed(device): @@ -163,25 +329,85 @@ def test_temperature_value_changed(device): def test_operation_value_changed(device): """Test values changed for climate device.""" - assert device.hvac_mode == "test1" - device.values.mode.data = "test_updated" + assert device.hvac_mode == HVAC_MODE_HEAT + assert device.preset_mode == PRESET_NONE + device.values.mode.data = HVAC_MODE_COOL value_changed(device.values.mode) - assert device.hvac_mode == "test_updated" + assert device.hvac_mode == HVAC_MODE_COOL + assert device.preset_mode == PRESET_NONE + device.values.mode.data = HVAC_MODE_OFF + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_OFF + assert device.preset_mode == PRESET_NONE + device.values.mode = None + assert device.hvac_mode == HVAC_MODE_HEAT_COOL + assert device.preset_mode == PRESET_NONE + + +def test_operation_value_changed_preset(device_mapping): + """Test preset changed for climate device.""" + device = device_mapping + assert device.hvac_mode == HVAC_MODE_HEAT + assert device.preset_mode == PRESET_NONE + device.values.mode.data = PRESET_ECO + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_HEAT_COOL + assert device.preset_mode == PRESET_ECO def test_operation_value_changed_mapping(device_mapping): """Test values changed for climate device. Mapping.""" device = device_mapping - assert device.hvac_mode == "off" - device.values.mode.data = "Heat" - value_changed(device.values.mode) assert device.hvac_mode == HVAC_MODE_HEAT - device.values.mode.data = "Cool" - value_changed(device.values.mode) - assert device.hvac_mode == HVAC_MODE_COOL + assert device.preset_mode == PRESET_NONE device.values.mode.data = "Off" value_changed(device.values.mode) assert device.hvac_mode == HVAC_MODE_OFF + assert device.preset_mode == PRESET_NONE + device.values.mode.data = "Cool" + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_COOL + assert device.preset_mode == PRESET_NONE + + +def test_operation_value_changed_mapping_preset(device_mapping): + """Test values changed for climate device. Mapping with presets.""" + device = device_mapping + assert device.hvac_mode == HVAC_MODE_HEAT + assert device.preset_mode == PRESET_NONE + device.values.mode.data = "Full Power" + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_HEAT_COOL + assert device.preset_mode == PRESET_BOOST + device.values.mode = None + assert device.hvac_mode == HVAC_MODE_HEAT_COOL + assert device.preset_mode == PRESET_NONE + + +def test_operation_value_changed_unknown(device_unknown): + """Test preset changed for climate device. Unknown.""" + device = device_unknown + assert device.hvac_mode == HVAC_MODE_HEAT + assert device.preset_mode == PRESET_NONE + device.values.mode.data = "Abcdefg" + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_HEAT_COOL + assert device.preset_mode == "Abcdefg" + + +def test_operation_value_changed_heat_cool(device_heat_cool): + """Test preset changed for climate device. Heat/Cool only.""" + device = device_heat_cool + assert device.hvac_mode == HVAC_MODE_HEAT + assert device.preset_mode == PRESET_NONE + device.values.mode.data = "Cool Eco" + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_COOL + assert device.preset_mode == "Cool Eco" + device.values.mode.data = "Heat Eco" + value_changed(device.values.mode) + assert device.hvac_mode == HVAC_MODE_HEAT + assert device.preset_mode == "Heat Eco" def test_fan_mode_value_changed(device): @@ -190,3 +416,29 @@ def test_fan_mode_value_changed(device): device.values.fan_mode.data = "test_updated_fan" value_changed(device.values.fan_mode) assert device.fan_mode == "test_updated_fan" + + +def test_hvac_action_value_changed(device): + """Test values changed for climate device.""" + assert device.hvac_action == CURRENT_HVAC_HEAT + device.values.operating_state.data = CURRENT_HVAC_COOL + value_changed(device.values.operating_state) + assert device.hvac_action == CURRENT_HVAC_COOL + + +def test_hvac_action_value_changed_mapping(device_mapping): + """Test values changed for climate device.""" + device = device_mapping + assert device.hvac_action == CURRENT_HVAC_HEAT + device.values.operating_state.data = "cooling" + value_changed(device.values.operating_state) + assert device.hvac_action == CURRENT_HVAC_COOL + + +def test_hvac_action_value_changed_unknown(device_unknown): + """Test values changed for climate device.""" + device = device_unknown + assert device.hvac_action == "test4" + device.values.operating_state.data = "another_hvac_action" + value_changed(device.values.operating_state) + assert device.hvac_action == "another_hvac_action" From e001b1243086202e5e5206b5875ec172a49e9b43 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Sat, 3 Aug 2019 18:49:34 +0200 Subject: [PATCH 15/52] Add PRESET_AWAY to HomematicIP Cloud climate (#25641) * enable climate away_mode and home.refresh * Add Party eco modes --- .../components/homematicip_cloud/climate.py | 17 ++++++++++++++--- .../components/homematicip_cloud/hap.py | 11 ++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index 53e7403ce56..794a8b44cbc 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -5,16 +5,19 @@ from typing import Awaitable from homematicip.aio.device import AsyncHeatingThermostat, AsyncHeatingThermostatCompact from homematicip.aio.group import AsyncHeatingGroup from homematicip.aio.home import AsyncHome +from homematicip.base.enums import AbsenceType +from homematicip.functionalHomes import IndoorClimateHome from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, + PRESET_AWAY, PRESET_BOOST, PRESET_ECO, + PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, - PRESET_NONE, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -116,9 +119,17 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice): if self._device.boostMode: return PRESET_BOOST if self._device.controlMode == HMIP_ECO_CM: - return PRESET_ECO + absence_type = self._home.get_functionalHome(IndoorClimateHome).absenceType + if absence_type == AbsenceType.VACATION: + return PRESET_AWAY + if absence_type in [ + AbsenceType.PERIOD, + AbsenceType.PERMANENT, + AbsenceType.PARTY, + ]: + return PRESET_ECO - return None + return PRESET_NONE @property def preset_modes(self): diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 7418aa94d89..23973efb07b 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -110,10 +110,13 @@ class HomematicipHAP: Triggered when the HMIP HOME_CHANGED event has fired. There are several occasions for this event to happen. - We are only interested to check whether the access point + 1. We are interested to check whether the access point is still connected. If not, device state changes cannot be forwarded to hass. So if access point is disconnected all devices are set to unavailable. + 2. We need to update home including devices and groups after a reconnect. + 3. We need to update home without devices and groups in all other cases. + """ if not self.home.connected: _LOGGER.error("HMIP access point has lost connection with the cloud") @@ -127,6 +130,12 @@ class HomematicipHAP: job = self.hass.async_create_task(self.get_state()) job.add_done_callback(self.get_state_finished) + self._accesspoint_connected = True + else: + # Update home with the given json from arg[0], + # without devices and groups. + + self.home.update_home_only(args[0]) async def get_state(self): """Update HMIP state and tell Home Assistant.""" From b40d324e0ef7f744ff71c32ec945a2d44fe801fc Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 2 Aug 2019 10:13:00 +0200 Subject: [PATCH 16/52] UniFi - allow configuration to not track clients or devices (#25642) * Allow configuration to not track clients or devices --- homeassistant/components/unifi/__init__.py | 4 ++ homeassistant/components/unifi/const.py | 2 + .../components/unifi/device_tracker.py | 72 ++++++++++--------- tests/components/unifi/test_device_tracker.py | 39 +++++++++- 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 726d4793085..f4df139001d 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -11,6 +11,8 @@ from .const import ( CONF_BLOCK_CLIENT, CONF_CONTROLLER, CONF_DETECTION_TIME, + CONF_DONT_TRACK_CLIENTS, + CONF_DONT_TRACK_DEVICES, CONF_SITE_ID, CONF_SSID_FILTER, CONTROLLER_ID, @@ -28,6 +30,8 @@ CONTROLLER_SCHEMA = vol.Schema( vol.Optional(CONF_BLOCK_CLIENT, default=[]): vol.All( cv.ensure_list, [cv.string] ), + vol.Optional(CONF_DONT_TRACK_CLIENTS): cv.boolean, + vol.Optional(CONF_DONT_TRACK_DEVICES): cv.boolean, vol.Optional(CONF_DETECTION_TIME): vol.All( cv.time_period, cv.positive_timedelta ), diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index 383b018264a..1295849704c 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -13,6 +13,8 @@ UNIFI_CONFIG = "unifi_config" CONF_BLOCK_CLIENT = "block_client" CONF_DETECTION_TIME = "detection_time" +CONF_DONT_TRACK_CLIENTS = "dont_track_clients" +CONF_DONT_TRACK_DEVICES = "dont_track_devices" CONF_SSID_FILTER = "ssid_filter" ATTR_MANUFACTURER = "Ubiquiti Networks" diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 4046f5f63d2..ce5a1a7f608 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -28,6 +28,8 @@ from .const import ( ATTR_MANUFACTURER, CONF_CONTROLLER, CONF_DETECTION_TIME, + CONF_DONT_TRACK_CLIENTS, + CONF_DONT_TRACK_DEVICES, CONF_SITE_ID, CONF_SSID_FILTER, CONTROLLER_ID, @@ -154,46 +156,52 @@ def update_items(controller, async_add_entities, tracked): """Update tracked device state from the controller.""" new_tracked = [] - for client_id in controller.api.clients: + if not controller.unifi_config.get(CONF_DONT_TRACK_CLIENTS, False): - if client_id in tracked: + for client_id in controller.api.clients: + + if client_id in tracked: + LOGGER.debug( + "Updating UniFi tracked client %s (%s)", + tracked[client_id].entity_id, + tracked[client_id].client.mac, + ) + tracked[client_id].async_schedule_update_ha_state() + continue + + client = controller.api.clients[client_id] + + if ( + not client.is_wired + and CONF_SSID_FILTER in controller.unifi_config + and client.essid not in controller.unifi_config[CONF_SSID_FILTER] + ): + continue + + tracked[client_id] = UniFiClientTracker(client, controller) + new_tracked.append(tracked[client_id]) LOGGER.debug( - "Updating UniFi tracked client %s (%s)", - tracked[client_id].entity_id, - tracked[client_id].client.mac, + "New UniFi client tracker %s (%s)", client.hostname, client.mac ) - tracked[client_id].async_schedule_update_ha_state() - continue - client = controller.api.clients[client_id] + if not controller.unifi_config.get(CONF_DONT_TRACK_DEVICES, False): - if ( - not client.is_wired - and CONF_SSID_FILTER in controller.unifi_config - and client.essid not in controller.unifi_config[CONF_SSID_FILTER] - ): - continue + for device_id in controller.api.devices: - tracked[client_id] = UniFiClientTracker(client, controller) - new_tracked.append(tracked[client_id]) - LOGGER.debug("New UniFi client tracker %s (%s)", client.hostname, client.mac) + if device_id in tracked: + LOGGER.debug( + "Updating UniFi tracked device %s (%s)", + tracked[device_id].entity_id, + tracked[device_id].device.mac, + ) + tracked[device_id].async_schedule_update_ha_state() + continue - for device_id in controller.api.devices: + device = controller.api.devices[device_id] - if device_id in tracked: - LOGGER.debug( - "Updating UniFi tracked device %s (%s)", - tracked[device_id].entity_id, - tracked[device_id].device.mac, - ) - tracked[device_id].async_schedule_update_ha_state() - continue - - device = controller.api.devices[device_id] - - tracked[device_id] = UniFiDeviceTracker(device, controller) - new_tracked.append(tracked[device_id]) - LOGGER.debug("New UniFi device tracker %s (%s)", device.name, device.mac) + tracked[device_id] = UniFiDeviceTracker(device, controller) + new_tracked.append(tracked[device_id]) + LOGGER.debug("New UniFi device tracker %s (%s)", device.name, device.mac) if new_tracked: async_add_entities(new_tracked) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 5accbb584b4..fb13bef42aa 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -22,6 +22,7 @@ from homeassistant.const import ( CONF_PORT, CONF_USERNAME, CONF_VERIFY_SSL, + STATE_UNAVAILABLE, ) from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component @@ -211,7 +212,7 @@ async def test_tracked_devices(hass, mock_controller): await hass.async_block_till_done() device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "unavailable" + assert device_1.state == STATE_UNAVAILABLE async def test_restoring_client(hass, mock_controller): @@ -243,3 +244,39 @@ async def test_restoring_client(hass, mock_controller): device_1 = hass.states.get("device_tracker.client_1") assert device_1 is not None + + +async def test_dont_track_clients(hass, mock_controller): + """Test dont track clients config works.""" + mock_controller.mock_client_responses.append([CLIENT_1]) + mock_controller.mock_device_responses.append([DEVICE_1]) + mock_controller.unifi_config = {unifi.CONF_DONT_TRACK_CLIENTS: True} + + await setup_controller(hass, mock_controller) + assert len(mock_controller.mock_requests) == 2 + assert len(hass.states.async_all()) == 3 + + client_1 = hass.states.get("device_tracker.client_1") + assert client_1 is None + + device_1 = hass.states.get("device_tracker.device_1") + assert device_1 is not None + assert device_1.state == "not_home" + + +async def test_dont_track_devices(hass, mock_controller): + """Test dont track devices config works.""" + mock_controller.mock_client_responses.append([CLIENT_1]) + mock_controller.mock_device_responses.append([DEVICE_1]) + mock_controller.unifi_config = {unifi.CONF_DONT_TRACK_DEVICES: True} + + await setup_controller(hass, mock_controller) + assert len(mock_controller.mock_requests) == 2 + assert len(hass.states.async_all()) == 3 + + client_1 = hass.states.get("device_tracker.client_1") + assert client_1 is not None + assert client_1.state == "not_home" + + device_1 = hass.states.get("device_tracker.device_1") + assert device_1 is None From d95c86e96462cc41e685cf56f230890d71537ef0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Aug 2019 16:32:43 -0700 Subject: [PATCH 17/52] Add preset to be away and eco (#25643) --- homeassistant/components/nest/climate.py | 27 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index ac13f2b004f..dc4b0bd33ae 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -61,7 +61,9 @@ ACTION_NEST_TO_HASS = { "cooling": CURRENT_HVAC_COOL, } -PRESET_MODES = [PRESET_NONE, PRESET_AWAY, PRESET_ECO] +PRESET_AWAY_AND_ECO = "Away and Eco" + +PRESET_MODES = [PRESET_NONE, PRESET_AWAY, PRESET_ECO, PRESET_AWAY_AND_ECO] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -259,6 +261,9 @@ class NestThermostat(ClimateDevice): @property def preset_mode(self): """Return current preset mode.""" + if self._away and self._mode == NEST_MODE_ECO: + return PRESET_AWAY_AND_ECO + if self._away: return PRESET_AWAY @@ -277,15 +282,19 @@ class NestThermostat(ClimateDevice): if preset_mode == self.preset_mode: return - if self._away: - self.structure.away = False - elif preset_mode == PRESET_AWAY: - self.structure.away = True + need_away = preset_mode in (PRESET_AWAY, PRESET_AWAY_AND_ECO) + need_eco = preset_mode in (PRESET_ECO, PRESET_AWAY_AND_ECO) + is_away = self._away + is_eco = self._mode == NEST_MODE_ECO - if self.preset_mode == PRESET_ECO: - self.device.mode = MODE_HASS_TO_NEST[self._operation_list[0]] - elif preset_mode == PRESET_ECO: - self.device.mode = NEST_MODE_ECO + if is_away != need_away: + self.structure.away = need_away + + if is_eco != need_eco: + if need_eco: + self.device.mode = NEST_MODE_ECO + else: + self.device.mode = MODE_HASS_TO_NEST[self._operation_list[0]] @property def fan_mode(self): From 476607787a807cd113def5741af8f6cf704d64e4 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 2 Aug 2019 17:00:22 +0200 Subject: [PATCH 18/52] Revert flux_led to 0.89 (#25653) * Revert Black * Revert "Introduce support for color temperature (#25503)" This reverts commit e1d884a4841f853bb4247a76fca417c0b4f4e7c1. * Revert "Fix flux_led only-white controllers (#22210)" This reverts commit 48138189b3c24261fe62a78b6ec854c761d5ce63. * Revert "Fix MagicHome LEDs with flux_led component (#20733)" This reverts commit 1444a684e02fab99648f6e5daea9f28b6cf45c10. * Re-Black * Use mode detection for scanned bulbs --- homeassistant/components/flux_led/light.py | 138 ++++++++------------- 1 file changed, 55 insertions(+), 83 deletions(-) diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index cef0387111a..23fdb38aa05 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -2,8 +2,6 @@ import logging import socket import random -from asyncio import sleep -from functools import partial import voluptuous as vol @@ -13,14 +11,12 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_EFFECT, ATTR_WHITE_VALUE, - ATTR_COLOR_TEMP, EFFECT_COLORLOOP, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, - SUPPORT_COLOR_TEMP, Light, PLATFORM_SCHEMA, ) @@ -38,9 +34,7 @@ ATTR_MODE = "mode" DOMAIN = "flux_led" -SUPPORT_FLUX_LED = ( - SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR | SUPPORT_COLOR_TEMP -) +SUPPORT_FLUX_LED = SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR MODE_RGB = "rgb" MODE_RGBW = "rgbw" @@ -49,11 +43,6 @@ MODE_RGBW = "rgbw" # RGB value is ignored when this mode is specified. MODE_WHITE = "w" -# Constant color temp values for 2 flux_led special modes -# Warm-white and Cool-white. Details on #23704 -COLOR_TEMP_WARM_WHITE = 333 -COLOR_TEMP_COOL_WHITE = 250 - # List of supported effects which aren't already declared in LIGHT EFFECT_RED_FADE = "red_fade" EFFECT_GREEN_FADE = "green_fade" @@ -196,8 +185,6 @@ class FluxLight(Light): self._custom_effect = device[CONF_CUSTOM_EFFECT] self._bulb = None self._error_reported = False - self._color = (0, 0, 100) - self._white_value = 0 def _connect(self): """Connect to Flux light.""" @@ -238,14 +225,14 @@ class FluxLight(Light): def brightness(self): """Return the brightness of this light between 0..255.""" if self._mode == MODE_WHITE: - return self._white_value + return self.white_value - return int(self._color[2] / 100 * 255) + return self._bulb.brightness @property def hs_color(self): """Return the color property.""" - return self._color[0:2] + return color_util.color_RGB_to_hs(*self._bulb.getRgb()) @property def supported_features(self): @@ -261,7 +248,7 @@ class FluxLight(Light): @property def white_value(self): """Return the white value of this light between 0..255.""" - return self._white_value + return self._bulb.getRgbw()[3] @property def effect_list(self): @@ -282,85 +269,75 @@ class FluxLight(Light): for effect, code in EFFECT_MAP.items(): if current_mode == code: return effect + return None - async def async_turn_on(self, **kwargs): - """Turn the specified or all lights on and wait for state.""" - await self.hass.async_add_executor_job(partial(self._turn_on, **kwargs)) - # The bulb needs a bit to tell its new values, - # so we wait 1 second before updating - await sleep(1) - - def _turn_on(self, **kwargs): + def turn_on(self, **kwargs): """Turn the specified or all lights on.""" - self._bulb.turnOn() + if not self.is_on: + self._bulb.turnOn() hs_color = kwargs.get(ATTR_HS_COLOR) + + if hs_color: + rgb = color_util.color_hs_to_RGB(*hs_color) + else: + rgb = None + brightness = kwargs.get(ATTR_BRIGHTNESS) effect = kwargs.get(ATTR_EFFECT) white = kwargs.get(ATTR_WHITE_VALUE) - color_temp = kwargs.get(ATTR_COLOR_TEMP) - if all( - item is None for item in [hs_color, brightness, effect, white, color_temp] - ): + # Show warning if effect set with rgb, brightness, or white level + if effect and (brightness or white or rgb): + _LOGGER.warning( + "RGB, brightness and white level are ignored when" + " an effect is specified for a flux bulb" + ) + + # Random color effect + if effect == EFFECT_RANDOM: + self._bulb.setRgb( + random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) + ) return - # handle W only mode (use brightness instead of white value) - if self._mode == MODE_WHITE: - if brightness is not None: - self._bulb.setWarmWhite255(brightness) - return - - # handle effects - if effect is not None: - # Random color effect - if effect == EFFECT_RANDOM: - self._bulb.setRgb( - random.randint(0, 255), - random.randint(0, 255), - random.randint(0, 255), + if effect == EFFECT_CUSTOM: + if self._custom_effect: + self._bulb.setCustomPattern( + self._custom_effect[CONF_COLORS], + self._custom_effect[CONF_SPEED_PCT], + self._custom_effect[CONF_TRANSITION], ) - elif effect == EFFECT_CUSTOM: - if self._custom_effect: - self._bulb.setCustomPattern( - self._custom_effect[CONF_COLORS], - self._custom_effect[CONF_SPEED_PCT], - self._custom_effect[CONF_TRANSITION], - ) - # Effect selection - elif effect in EFFECT_MAP: - self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) return - # handle special modes - if color_temp is not None: - if brightness is None: - brightness = self.brightness - if color_temp == COLOR_TEMP_WARM_WHITE: - self._bulb.setRgbw(w=brightness) - elif color_temp == COLOR_TEMP_COOL_WHITE: - self._bulb.setRgbw(w2=brightness) - else: - self._bulb.setRgbw(*color_util.color_temperature_to_rgb(color_temp)) + # Effect selection + if effect in EFFECT_MAP: + self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) return # Preserve current brightness on color/white level change - if hs_color is not None: - if brightness is None: - brightness = self.brightness - color = (hs_color[0], hs_color[1], brightness / 255 * 100) - elif brightness is not None: - color = (self._color[0], self._color[1], brightness / 255 * 100) + if brightness is None: + brightness = self.brightness + + # Preserve color on brightness/white level change + if rgb is None: + rgb = self._bulb.getRgb() + + if white is None and self._mode == MODE_RGBW: + white = self.white_value + + # handle W only mode (use brightness instead of white value) + if self._mode == MODE_WHITE: + self._bulb.setRgbw(0, 0, 0, w=brightness) + # handle RGBW mode - if self._mode == MODE_RGBW: - if white is None: - self._bulb.setRgbw(*color_util.color_hsv_to_RGB(*color)) - else: - self._bulb.setRgbw(w=white) + elif self._mode == MODE_RGBW: + self._bulb.setRgbw(*tuple(rgb), w=white, brightness=brightness) + # handle RGB mode else: - self._bulb.setRgb(*color_util.color_hsv_to_RGB(*color)) + self._bulb.setRgb(*tuple(rgb), brightness=brightness) def turn_off(self, **kwargs): """Turn the specified or all lights off.""" @@ -380,10 +357,5 @@ class FluxLight(Light): ) self._error_reported = True return + self._bulb.update_state(retry=2) - if self._mode != MODE_WHITE and self._bulb.getRgb() != (0, 0, 0): - color = self._bulb.getRgbw() - self._color = color_util.color_RGB_to_hsv(*color[0:3]) - self._white_value = color[3] - elif self._mode == MODE_WHITE: - self._white_value = self._bulb.getRgbw()[3] From 7a71669027f9667552c403c938b7015012560bbc Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 2 Aug 2019 23:51:06 +0200 Subject: [PATCH 19/52] Options to not track wired clients (#25669) --- homeassistant/components/unifi/__init__.py | 2 ++ homeassistant/components/unifi/const.py | 1 + .../components/unifi/device_tracker.py | 7 +++++++ tests/components/unifi/test_device_tracker.py | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index f4df139001d..4ca6f68c301 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -13,6 +13,7 @@ from .const import ( CONF_DETECTION_TIME, CONF_DONT_TRACK_CLIENTS, CONF_DONT_TRACK_DEVICES, + CONF_DONT_TRACK_WIRED_CLIENTS, CONF_SITE_ID, CONF_SSID_FILTER, CONTROLLER_ID, @@ -32,6 +33,7 @@ CONTROLLER_SCHEMA = vol.Schema( ), vol.Optional(CONF_DONT_TRACK_CLIENTS): cv.boolean, vol.Optional(CONF_DONT_TRACK_DEVICES): cv.boolean, + vol.Optional(CONF_DONT_TRACK_WIRED_CLIENTS): cv.boolean, vol.Optional(CONF_DETECTION_TIME): vol.All( cv.time_period, cv.positive_timedelta ), diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index 1295849704c..b4864421cb9 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -15,6 +15,7 @@ CONF_BLOCK_CLIENT = "block_client" CONF_DETECTION_TIME = "detection_time" CONF_DONT_TRACK_CLIENTS = "dont_track_clients" CONF_DONT_TRACK_DEVICES = "dont_track_devices" +CONF_DONT_TRACK_WIRED_CLIENTS = "dont_track_wired_clients" CONF_SSID_FILTER = "ssid_filter" ATTR_MANUFACTURER = "Ubiquiti Networks" diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index ce5a1a7f608..9c645a072a5 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -30,6 +30,7 @@ from .const import ( CONF_DETECTION_TIME, CONF_DONT_TRACK_CLIENTS, CONF_DONT_TRACK_DEVICES, + CONF_DONT_TRACK_WIRED_CLIENTS, CONF_SITE_ID, CONF_SSID_FILTER, CONTROLLER_ID, @@ -178,6 +179,12 @@ def update_items(controller, async_add_entities, tracked): ): continue + if ( + controller.unifi_config.get(CONF_DONT_TRACK_WIRED_CLIENTS, False) + and client.is_wired + ): + continue + tracked[client_id] = UniFiClientTracker(client, controller) new_tracked.append(tracked[client_id]) LOGGER.debug( diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index fb13bef42aa..9fca9d21a5b 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -280,3 +280,21 @@ async def test_dont_track_devices(hass, mock_controller): device_1 = hass.states.get("device_tracker.device_1") assert device_1 is None + + +async def test_dont_track_wired_clients(hass, mock_controller): + """Test dont track wired clients config works.""" + mock_controller.mock_client_responses.append([CLIENT_1, CLIENT_2]) + mock_controller.mock_device_responses.append({}) + mock_controller.unifi_config = {unifi.CONF_DONT_TRACK_WIRED_CLIENTS: True} + + await setup_controller(hass, mock_controller) + assert len(mock_controller.mock_requests) == 2 + assert len(hass.states.async_all()) == 3 + + client_1 = hass.states.get("device_tracker.client_1") + assert client_1 is not None + assert client_1.state == "not_home" + + client_2 = hass.states.get("device_tracker.client_2") + assert client_2 is None From f8753a0c92bc9e8646e44eb315a3772c060451a7 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 4 Aug 2019 16:11:28 -0600 Subject: [PATCH 20/52] Fix issue with incorrect Notion bridge IDs (#25683) * Fix issue with incorrect Notion bridge IDs * Less aggressive * Member comments --- homeassistant/components/notion/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index d2c45330bdb..62deb4999d9 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -235,7 +235,7 @@ class NotionEntity(Entity): @property def device_info(self): """Return device registry information for this entity.""" - bridge = self._notion.bridges[self._bridge_id] + bridge = self._notion.bridges.get(self._bridge_id, {}) sensor = self._notion.sensors[self._sensor_id] return { @@ -244,7 +244,7 @@ class NotionEntity(Entity): "model": sensor["hardware_revision"], "name": sensor["name"], "sw_version": sensor["firmware_version"], - "via_device": (DOMAIN, bridge["hardware_id"]), + "via_device": (DOMAIN, bridge.get("hardware_id")), } @property @@ -271,7 +271,14 @@ class NotionEntity(Entity): Sensors can move to other bridges based on signal strength, etc. """ sensor = self._notion.sensors[self._sensor_id] - if self._bridge_id == sensor["bridge"]["id"]: + + # If the sensor's bridge ID is the same as what we had before or if it points + # to a bridge that doesn't exist (which can happen due to a Notion API bug), + # return immediately: + if ( + self._bridge_id == sensor["bridge"]["id"] + or sensor["bridge"]["id"] not in self._notion.bridges + ): return self._bridge_id = sensor["bridge"]["id"] From 2925f9e57a1ee6f9c4004b0c322a09b7d45ef5e0 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 4 Aug 2019 16:12:16 +0200 Subject: [PATCH 21/52] In some circumstances device.last_seen can be None (#25690) --- homeassistant/components/unifi/device_tracker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 9c645a072a5..8ab5140dc48 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -296,7 +296,9 @@ class UniFiDeviceTracker(ScannerEntity): ) if ( - dt_util.utcnow() - dt_util.utc_from_timestamp(float(self.device.last_seen)) + self.device.last_seen + and dt_util.utcnow() + - dt_util.utc_from_timestamp(float(self.device.last_seen)) ) < detection_time: return True return False From 4e2094c89383b1901de204bda8fa136d193d6d6a Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 4 Aug 2019 16:57:36 +0200 Subject: [PATCH 22/52] UniFi - reverse connectivity logic (#25691) * Make connectivity control in line with other implementations --- homeassistant/components/unifi/switch.py | 12 ++++++------ tests/components/unifi/test_switch.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 236ca02ab2d..2b7965d1095 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -266,8 +266,8 @@ class UniFiBlockClientSwitch(UniFiClient, SwitchDevice): @property def is_on(self): - """Return true if client is blocked.""" - return self.client.blocked + """Return true if client is allowed to connect.""" + return not self.client.blocked @property def available(self): @@ -275,9 +275,9 @@ class UniFiBlockClientSwitch(UniFiClient, SwitchDevice): return self.controller.available async def async_turn_on(self, **kwargs): - """Block client.""" - await self.controller.api.clients.async_block(self.client.mac) + """Turn on connectivity for client.""" + await self.controller.api.clients.async_unblock(self.client.mac) async def async_turn_off(self, **kwargs): - """Unblock client.""" - await self.controller.api.clients.async_unblock(self.client.mac) + """Turn off connectivity for client.""" + await self.controller.api.clients.async_block(self.client.mac) diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 915646b9856..f84efa5dada 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -342,11 +342,11 @@ async def test_switches(hass, mock_controller): blocked = hass.states.get("switch.block_client_1") assert blocked is not None - assert blocked.state == "on" + assert blocked.state == "off" unblocked = hass.states.get("switch.block_client_2") assert unblocked is not None - assert unblocked.state == "off" + assert unblocked.state == "on" async def test_new_client_discovered(hass, mock_controller): From 868c6f4f718194ee3d1bba5e03b7e3253410507a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 5 Aug 2019 00:13:27 +0200 Subject: [PATCH 23/52] Fix roku lxml requirement (#25696) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 6bdc1f6bf3d..477bcb105f7 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -3,7 +3,7 @@ "name": "Roku", "documentation": "https://www.home-assistant.io/components/roku", "requirements": [ - "roku==3.0.0" + "roku==3.1" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index d70c0602c4c..14367506aa8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1640,7 +1640,7 @@ rjpl==0.3.5 rocketchat-API==0.6.1 # homeassistant.components.roku -roku==3.0.0 +roku==3.1 # homeassistant.components.roomba roombapy==1.3.1 From 6e61b21919276403871b7e8f49482df6d8baea9c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 4 Aug 2019 22:50:46 -0700 Subject: [PATCH 24/52] Bumped version to 0.97.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4de4048d4f9..be27daee2a0 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From 387323a8c1caa670954482b4390070b3314cd3d1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Aug 2019 22:31:57 -0700 Subject: [PATCH 25/52] Updated frontend to 20190805.0 --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 4cea624b4cd..6d25e846db9 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/components/frontend", "requirements": [ - "home-assistant-frontend==20190804.0" + "home-assistant-frontend==20190805.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 13c5b1a0144..c51b13100db 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ certifi>=2019.6.16 cryptography==2.7 distro==1.4.0 hass-nabucasa==0.16 -home-assistant-frontend==20190804.0 +home-assistant-frontend==20190805.0 importlib-metadata==0.18 jinja2>=2.10.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 14367506aa8..895441862bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -622,7 +622,7 @@ hole==0.3.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190804.0 +home-assistant-frontend==20190805.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0d71d23b74f..2f1177f0be5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -169,7 +169,7 @@ hdate==0.8.8 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190804.0 +home-assistant-frontend==20190805.0 # homeassistant.components.homekit_controller homekit[IP]==0.15.0 From e1c23b168620840b402a00d5a38d6c0998e72233 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 2 Aug 2019 01:43:08 +0200 Subject: [PATCH 26/52] Add HmIP-SCI to Homematic IP Cloud, Fix HmIP-SWDM (#25639) * Add HmIP-SCI to Homematic IP Cloud * Bump upstream dependency * Fix HmIP-SWDM --- .../components/homematicip_cloud/binary_sensor.py | 9 +++++++-- homeassistant/components/homematicip_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 9580b803596..7bb7718f0b3 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -2,6 +2,7 @@ import logging from homematicip.aio.device import ( + AsyncContactInterface, AsyncDevice, AsyncFullFlushContactInterface, AsyncMotionDetectorIndoor, @@ -10,6 +11,7 @@ from homematicip.aio.device import ( AsyncPresenceDetectorIndoor, AsyncRotaryHandleSensor, AsyncShutterContact, + AsyncShutterContactMagnetic, AsyncSmokeDetector, AsyncWaterSensor, AsyncWeatherSensor, @@ -63,9 +65,12 @@ async def async_setup_entry( home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] for device in home.devices: - if isinstance(device, AsyncFullFlushContactInterface): + if isinstance(device, (AsyncContactInterface, AsyncFullFlushContactInterface)): devices.append(HomematicipContactInterface(home, device)) - if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)): + if isinstance( + device, + (AsyncShutterContact, AsyncShutterContactMagnetic, AsyncRotaryHandleSensor), + ): devices.append(HomematicipShutterContact(home, device)) if isinstance( device, diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index b679130ce05..ee0d2cb1271 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/components/homematicip_cloud", "requirements": [ - "homematicip==0.10.9" + "homematicip==0.10.10" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 895441862bc..3cbac909845 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -631,7 +631,7 @@ homeassistant-pyozw==0.1.4 homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud -homematicip==0.10.9 +homematicip==0.10.10 # homeassistant.components.horizon horimote==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2f1177f0be5..baa0763d38e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -175,7 +175,7 @@ home-assistant-frontend==20190805.0 homekit[IP]==0.15.0 # homeassistant.components.homematicip_cloud -homematicip==0.10.9 +homematicip==0.10.10 # homeassistant.components.google # homeassistant.components.remember_the_milk From b011dd0b0229cf0520fe430f2b1c77a700fdf4cf Mon Sep 17 00:00:00 2001 From: Jesse Rizzo <32472573+jesserizzo@users.noreply.github.com> Date: Mon, 5 Aug 2019 16:15:42 -0500 Subject: [PATCH 27/52] Bump envoy_reader to 0.8.6, fix missing dependency (#25679) * Bump envoy_reader to 0.8.6, fix missing dependency * Bump envoy_reader to 0.8.6, fix missing dependency --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 60f252c59a6..86d2d69cf9b 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -3,7 +3,7 @@ "name": "Enphase envoy", "documentation": "https://www.home-assistant.io/components/enphase_envoy", "requirements": [ - "envoy_reader==0.8" + "envoy_reader==0.8.6" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 3cbac909845..e986776fdb1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -442,7 +442,7 @@ env_canada==0.0.20 # envirophat==0.0.6 # homeassistant.components.enphase_envoy -envoy_reader==0.8 +envoy_reader==0.8.6 # homeassistant.components.season ephem==3.7.6.0 From eceef82ffa3352dd6607247fb2b7f169c6974847 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Aug 2019 14:04:20 -0700 Subject: [PATCH 28/52] Add service to reload scenes from configuration.yaml (#25680) * Allow reloading scenes * Update requirements * address comments * fix typing * fix tests * Update homeassistant/components/homeassistant/scene.py Co-Authored-By: Martin Hjelmare * Address comments --- .../components/homeassistant/scene.py | 68 +++++++++++++-- homeassistant/components/scene/__init__.py | 5 ++ homeassistant/helpers/entity_component.py | 9 +- homeassistant/helpers/entity_platform.py | 8 ++ homeassistant/package_constraints.txt | 1 + requirements_all.txt | 1 + setup.py | 86 +++++++++---------- tests/components/homeassistant/test_scene.py | 30 +++++++ tests/helpers/test_entity_component.py | 2 +- 9 files changed, 153 insertions(+), 57 deletions(-) create mode 100644 tests/components/homeassistant/test_scene.py diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index de8a4dc88e7..66b04109640 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -1,5 +1,6 @@ """Allow users to set and activate scenes.""" from collections import namedtuple +import logging import voluptuous as vol @@ -11,12 +12,19 @@ from homeassistant.const import ( CONF_PLATFORM, STATE_OFF, STATE_ON, + SERVICE_RELOAD, +) +from homeassistant.core import State, DOMAIN +from homeassistant import config as conf_util +from homeassistant.exceptions import HomeAssistantError +from homeassistant.loader import async_get_integration +from homeassistant.helpers import ( + config_per_platform, + config_validation as cv, + entity_platform, ) -from homeassistant.core import State -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state -from homeassistant.components.scene import STATES, Scene - +from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene PLATFORM_SCHEMA = vol.Schema( { @@ -37,19 +45,63 @@ PLATFORM_SCHEMA = vol.Schema( ) SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES]) +_LOGGER = logging.getLogger(__name__) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up home assistant scene entries.""" - scene_config = config.get(STATES) + _process_scenes_config(hass, async_add_entities, config) + + # This platform can be loaded multiple times. Only first time register the service. + if hass.services.has_service(SCENE_DOMAIN, SERVICE_RELOAD): + return + + # Store platform for later. + platform = entity_platform.current_platform.get() + + async def reload_config(call): + """Reload the scene config.""" + try: + conf = await conf_util.async_hass_config_yaml(hass) + except HomeAssistantError as err: + _LOGGER.error(err) + return + + integration = await async_get_integration(hass, SCENE_DOMAIN) + + conf = await conf_util.async_process_component_config(hass, conf, integration) + + if not conf or not platform: + return + + await platform.async_reset() + + # Extract only the config for the Home Assistant platform, ignore the rest. + for p_type, p_config in config_per_platform(conf, SCENE_DOMAIN): + if p_type != DOMAIN: + continue + + _process_scenes_config(hass, async_add_entities, p_config) + + hass.helpers.service.async_register_admin_service( + SCENE_DOMAIN, SERVICE_RELOAD, reload_config + ) + + +def _process_scenes_config(hass, async_add_entities, config): + """Process multiple scenes and add them.""" + scene_config = config[STATES] + + # Check empty list + if not scene_config: + return async_add_entities( - HomeAssistantScene(hass, _process_config(scene)) for scene in scene_config + HomeAssistantScene(hass, _process_scene_config(scene)) for scene in scene_config ) - return True -def _process_config(scene_config): +def _process_scene_config(scene_config): """Process passed in config into a format to work with. Async friendly. diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 0d00c2c5ea2..5ddb1116d8f 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -5,6 +5,7 @@ import logging import voluptuous as vol +from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA from homeassistant.helpers.entity import Entity @@ -60,6 +61,10 @@ async def async_setup(hass, config): component = hass.data[DOMAIN] = EntityComponent(logger, DOMAIN, hass) await component.async_setup(config) + # Ensure Home Assistant platform always loaded. + await component.async_setup_platform( + HA_DOMAIN, {"platform": "homeasistant", STATES: []} + ) async def async_handle_scene_service(service): """Handle calls to the switch services.""" diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index ed1b41a0abd..b28beeaea72 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -114,7 +114,7 @@ class EntityComponent: # Look in config for Domain, Domain 2, Domain 3 etc and load them tasks = [] for p_type, p_config in config_per_platform(config, self.domain): - tasks.append(self._async_setup_platform(p_type, p_config)) + tasks.append(self.async_setup_platform(p_type, p_config)) if tasks: await asyncio.wait(tasks) @@ -123,7 +123,7 @@ class EntityComponent: # Refer to: homeassistant.components.discovery.load_platform() async def component_platform_discovered(platform, info): """Handle the loading of a platform.""" - await self._async_setup_platform(platform, {}, info) + await self.async_setup_platform(platform, {}, info) discovery.async_listen_platform( self.hass, self.domain, component_platform_discovered @@ -212,10 +212,13 @@ class EntityComponent: self.hass.services.async_register(self.domain, name, handle_service, schema) - async def _async_setup_platform( + async def async_setup_platform( self, platform_type, platform_config, discovery_info=None ): """Set up a platform for this component.""" + if self.config is None: + raise RuntimeError("async_setup needs to be called first") + platform = await async_prepare_setup_platform( self.hass, self.config, self.domain, platform_type ) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 5012f578106..ea71828f21a 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -1,5 +1,7 @@ """Class to manage the entities for a single platform.""" import asyncio +from contextvars import ContextVar +from typing import Optional from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.core import callback, valid_entity_id, split_entity_id @@ -127,6 +129,7 @@ class EntityPlatform: async_create_setup_task creates a coroutine that sets up platform. """ + current_platform.set(self) logger = self.logger hass = self.hass full_name = "{}.{}".format(self.domain, self.platform_name) @@ -457,3 +460,8 @@ class EntityPlatform: if tasks: await asyncio.wait(tasks) + + +current_platform: ContextVar[Optional[EntityPlatform]] = ContextVar( + "current_platform", default=None +) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c51b13100db..a1148063aee 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -7,6 +7,7 @@ async_timeout==3.0.1 attrs==19.1.0 bcrypt==3.1.7 certifi>=2019.6.16 +contextvars==2.4;python_version<"3.7" cryptography==2.7 distro==1.4.0 hass-nabucasa==0.16 diff --git a/requirements_all.txt b/requirements_all.txt index e986776fdb1..1fd445ea570 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,6 +5,7 @@ async_timeout==3.0.1 attrs==19.1.0 bcrypt==3.1.7 certifi>=2019.6.16 +contextvars==2.4;python_version<"3.7" importlib-metadata==0.18 jinja2>=2.10.1 PyJWT==1.7.1 diff --git a/setup.py b/setup.py index 14162a86c12..da50b5f988c 100755 --- a/setup.py +++ b/setup.py @@ -5,55 +5,55 @@ from setuptools import setup, find_packages import homeassistant.const as hass_const -PROJECT_NAME = 'Home Assistant' -PROJECT_PACKAGE_NAME = 'homeassistant' -PROJECT_LICENSE = 'Apache License 2.0' -PROJECT_AUTHOR = 'The Home Assistant Authors' -PROJECT_COPYRIGHT = ' 2013-{}, {}'.format(dt.now().year, PROJECT_AUTHOR) -PROJECT_URL = 'https://home-assistant.io/' -PROJECT_EMAIL = 'hello@home-assistant.io' +PROJECT_NAME = "Home Assistant" +PROJECT_PACKAGE_NAME = "homeassistant" +PROJECT_LICENSE = "Apache License 2.0" +PROJECT_AUTHOR = "The Home Assistant Authors" +PROJECT_COPYRIGHT = " 2013-{}, {}".format(dt.now().year, PROJECT_AUTHOR) +PROJECT_URL = "https://home-assistant.io/" +PROJECT_EMAIL = "hello@home-assistant.io" -PROJECT_GITHUB_USERNAME = 'home-assistant' -PROJECT_GITHUB_REPOSITORY = 'home-assistant' +PROJECT_GITHUB_USERNAME = "home-assistant" +PROJECT_GITHUB_REPOSITORY = "home-assistant" -PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME) -GITHUB_PATH = '{}/{}'.format( - PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) -GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH) +PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) +GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) +GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) -DOWNLOAD_URL = '{}/archive/{}.zip'.format(GITHUB_URL, hass_const.__version__) +DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, hass_const.__version__) PROJECT_URLS = { - 'Bug Reports': '{}/issues'.format(GITHUB_URL), - 'Dev Docs': 'https://developers.home-assistant.io/', - 'Discord': 'https://discordapp.com/invite/c5DvZ4e', - 'Forum': 'https://community.home-assistant.io/', + "Bug Reports": "{}/issues".format(GITHUB_URL), + "Dev Docs": "https://developers.home-assistant.io/", + "Discord": "https://discordapp.com/invite/c5DvZ4e", + "Forum": "https://community.home-assistant.io/", } -PACKAGES = find_packages(exclude=['tests', 'tests.*']) +PACKAGES = find_packages(exclude=["tests", "tests.*"]) REQUIRES = [ - 'aiohttp==3.5.4', - 'astral==1.10.1', - 'async_timeout==3.0.1', - 'attrs==19.1.0', - 'bcrypt==3.1.7', - 'certifi>=2019.6.16', - 'importlib-metadata==0.18', - 'jinja2>=2.10.1', - 'PyJWT==1.7.1', + "aiohttp==3.5.4", + "astral==1.10.1", + "async_timeout==3.0.1", + "attrs==19.1.0", + "bcrypt==3.1.7", + "certifi>=2019.6.16", + 'contextvars==2.4;python_version<"3.7"', + "importlib-metadata==0.18", + "jinja2>=2.10.1", + "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. - 'cryptography==2.7', - 'pip>=8.0.3', - 'python-slugify==3.0.2', - 'pytz>=2019.01', - 'pyyaml==5.1.1', - 'requests==2.22.0', - 'ruamel.yaml==0.15.99', - 'voluptuous==0.11.5', - 'voluptuous-serialize==2.1.0', + "cryptography==2.7", + "pip>=8.0.3", + "python-slugify==3.0.2", + "pytz>=2019.01", + "pyyaml==5.1.1", + "requests==2.22.0", + "ruamel.yaml==0.15.99", + "voluptuous==0.11.5", + "voluptuous-serialize==2.1.0", ] -MIN_PY_VERSION = '.'.join(map(str, hass_const.REQUIRED_PYTHON_VER)) +MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER)) setup( name=PROJECT_PACKAGE_NAME, @@ -67,11 +67,7 @@ setup( include_package_data=True, zip_safe=False, install_requires=REQUIRES, - python_requires='>={}'.format(MIN_PY_VERSION), - test_suite='tests', - entry_points={ - 'console_scripts': [ - 'hass = homeassistant.__main__:main' - ] - }, + python_requires=">={}".format(MIN_PY_VERSION), + test_suite="tests", + entry_points={"console_scripts": ["hass = homeassistant.__main__:main"]}, ) diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py new file mode 100644 index 00000000000..02c018a0b49 --- /dev/null +++ b/tests/components/homeassistant/test_scene.py @@ -0,0 +1,30 @@ +"""Test Home Assistant scenes.""" +from unittest.mock import patch + +from homeassistant.setup import async_setup_component + + +async def test_reload_config_service(hass): + """Test the reload config service.""" + assert await async_setup_component(hass, "scene", {}) + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={"scene": {"name": "Hallo", "entities": {"light.kitchen": "on"}}}, + ), patch("homeassistant.config.find_config_file", return_value=""): + await hass.services.async_call("scene", "reload", blocking=True) + await hass.async_block_till_done() + + assert hass.states.get("scene.hallo") is not None + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={"scene": {"name": "Bye", "entities": {"light.kitchen": "on"}}}, + ), patch("homeassistant.config.find_config_file", return_value=""): + await hass.services.async_call("scene", "reload", blocking=True) + await hass.async_block_till_done() + + assert hass.states.get("scene.hallo") is None + assert hass.states.get("scene.bye") is not None diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 3dd6ca8b55f..0d52f430ff5 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -116,7 +116,7 @@ async def test_setup_recovers_when_setup_raises(hass): @asynctest.patch( - "homeassistant.helpers.entity_component.EntityComponent" "._async_setup_platform", + "homeassistant.helpers.entity_component.EntityComponent" ".async_setup_platform", return_value=mock_coro(), ) @asynctest.patch( From fee1568a856497a4f9718c0ec05a88be536692fd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 4 Aug 2019 23:24:54 -0700 Subject: [PATCH 29/52] Update HTTP defaults (#25702) * Update HTTP defaults * Fix tests --- homeassistant/components/http/__init__.py | 4 +++- homeassistant/components/http/cors.py | 3 +++ tests/components/http/test_init.py | 9 +++++++++ tests/scripts/test_check_config.py | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 84c7d15a580..6d31c3fc700 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -51,6 +51,8 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_SERVER_HOST = "0.0.0.0" DEFAULT_DEVELOPMENT = "0" +# To be able to load custom cards. +DEFAULT_CORS = "https://cast.home-assistant.io" NO_LOGIN_ATTEMPT_THRESHOLD = -1 @@ -91,7 +93,7 @@ HTTP_SCHEMA = vol.Schema( vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_PEER_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_KEY): cv.isfile, - vol.Optional(CONF_CORS_ORIGINS, default=[]): vol.All( + vol.Optional(CONF_CORS_ORIGINS, default=[DEFAULT_CORS]): vol.All( cv.ensure_list, [cv.string] ), vol.Inclusive(CONF_USE_X_FORWARDED_FOR, "proxy"): cv.boolean, diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index 5c24ecbebed..19fe88c5cde 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -45,6 +45,9 @@ def setup_cors(app, origins): path = path.canonical + if path.startswith("/api/hassio_ingress/"): + return + if path in cors_added: return diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index a3837a0b745..d8e613df6df 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -232,3 +232,12 @@ async def test_ssl_profile_change_modern(hass): await hass.async_block_till_done() assert len(mock_context.mock_calls) == 1 + + +async def test_cors_defaults(hass): + """Test the CORS default settings.""" + with patch("homeassistant.components.http.setup_cors") as mock_setup: + assert await async_setup_component(hass, "http", {}) + + assert len(mock_setup.mock_calls) == 1 + assert mock_setup.mock_calls[0][1][1] == ["https://cast.home-assistant.io"] diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 98c634cd400..a07b812bc96 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -103,7 +103,7 @@ def test_secrets(isfile_patch, loop): assert res["components"].keys() == {"homeassistant", "http"} assert res["components"]["http"] == { "api_password": "abc123", - "cors_allowed_origins": [], + "cors_allowed_origins": ["https://cast.home-assistant.io"], "ip_ban_enabled": True, "login_attempts_threshold": -1, "server_host": "0.0.0.0", From 27cfda11f7341dc85fcc4fa45f1dfe215d0aabf2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 6 Aug 2019 07:00:06 +0200 Subject: [PATCH 30/52] UniFi - handle device not having a name (#25713) * Handle device not having a name --- .../components/unifi/device_tracker.py | 20 ++++++++++++++----- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 8ab5140dc48..d0c2684ff53 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -188,7 +188,9 @@ def update_items(controller, async_add_entities, tracked): tracked[client_id] = UniFiClientTracker(client, controller) new_tracked.append(tracked[client_id]) LOGGER.debug( - "New UniFi client tracker %s (%s)", client.hostname, client.mac + "New UniFi client tracker %s (%s)", + client.name or client.hostname, + client.mac, ) if not controller.unifi_config.get(CONF_DONT_TRACK_DEVICES, False): @@ -208,7 +210,11 @@ def update_items(controller, async_add_entities, tracked): tracked[device_id] = UniFiDeviceTracker(device, controller) new_tracked.append(tracked[device_id]) - LOGGER.debug("New UniFi device tracker %s (%s)", device.name, device.mac) + LOGGER.debug( + "New UniFi device tracker %s (%s)", + device.name or device.model, + device.mac, + ) if new_tracked: async_add_entities(new_tracked) @@ -311,7 +317,7 @@ class UniFiDeviceTracker(ScannerEntity): @property def name(self) -> str: """Return the name of the device.""" - return self.device.name + return self.device.name or self.device.model @property def unique_id(self) -> str: @@ -326,14 +332,18 @@ class UniFiDeviceTracker(ScannerEntity): @property def device_info(self): """Return a device description for device registry.""" - return { + info = { "connections": {(CONNECTION_NETWORK_MAC, self.device.mac)}, "manufacturer": ATTR_MANUFACTURER, "model": self.device.model, - "name": self.device.name, "sw_version": self.device.version, } + if self.device.name: + info["name"] = self.device.name + + return info + @property def device_state_attributes(self): """Return the device state attributes.""" diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index e849fd34d25..bcee022e1c4 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/components/unifi", "requirements": [ - "aiounifi==9" + "aiounifi==10" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 1fd445ea570..3e46f05b5a6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -170,7 +170,7 @@ aiopvapi==1.6.14 aioswitcher==2019.4.26 # homeassistant.components.unifi -aiounifi==9 +aiounifi==10 # homeassistant.components.wwlln aiowwlln==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index baa0763d38e..0fba87e7d4b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -68,7 +68,7 @@ aionotion==1.1.0 aioswitcher==2019.4.26 # homeassistant.components.unifi -aiounifi==9 +aiounifi==10 # homeassistant.components.wwlln aiowwlln==1.0.0 From d702b17a4f14736a97c35b2239dc1591379758ec Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Aug 2019 09:00:20 -0700 Subject: [PATCH 31/52] Bumped version to 0.97.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index be27daee2a0..a00925c0025 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From 3a78250cad1b594a8863c18146e4d11a08986ae0 Mon Sep 17 00:00:00 2001 From: Tsvi Mostovicz Date: Tue, 6 Aug 2019 19:03:52 +0300 Subject: [PATCH 32/52] Bump hdate==0.9.0 (use pytz instead of dateutil) (#25726) Use new hdate version of library which uses pytz for timezones. dateutil expects /usr/share/timezone files, as these are not available in the docker image and in HASSIO, the timezone offsets are broken. This should fix - #23032 - #18731 --- homeassistant/components/jewish_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/jewish_calendar/manifest.json b/homeassistant/components/jewish_calendar/manifest.json index 5c3eee48ead..fdc1d2943e6 100644 --- a/homeassistant/components/jewish_calendar/manifest.json +++ b/homeassistant/components/jewish_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Jewish calendar", "documentation": "https://www.home-assistant.io/components/jewish_calendar", "requirements": [ - "hdate==0.8.8" + "hdate==0.9.0" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 3e46f05b5a6..5e583269562 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -599,7 +599,7 @@ hass-nabucasa==0.16 hbmqtt==0.9.4 # homeassistant.components.jewish_calendar -hdate==0.8.8 +hdate==0.9.0 # homeassistant.components.heatmiser heatmiserV3==0.9.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0fba87e7d4b..e2da65e24cd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -163,7 +163,7 @@ hass-nabucasa==0.16 hbmqtt==0.9.4 # homeassistant.components.jewish_calendar -hdate==0.8.8 +hdate==0.9.0 # homeassistant.components.workday holidays==0.9.11 From a1302a9dfbbcbbc64f72bb83b3b57b3fe9294b6a Mon Sep 17 00:00:00 2001 From: David Bonnes Date: Tue, 6 Aug 2019 17:03:08 +0100 Subject: [PATCH 33/52] initial commit (#25731) --- homeassistant/components/incomfort/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/incomfort/manifest.json b/homeassistant/components/incomfort/manifest.json index 13c77cd33ff..8b5f461c8af 100644 --- a/homeassistant/components/incomfort/manifest.json +++ b/homeassistant/components/incomfort/manifest.json @@ -3,7 +3,7 @@ "name": "Intergas InComfort/Intouch Lan2RF gateway", "documentation": "https://www.home-assistant.io/components/incomfort", "requirements": [ - "incomfort-client==0.3.0" + "incomfort-client==0.3.1" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 5e583269562..06a7ada8200 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -665,7 +665,7 @@ iglo==1.2.7 ihcsdk==2.3.0 # homeassistant.components.incomfort -incomfort-client==0.3.0 +incomfort-client==0.3.1 # homeassistant.components.influxdb influxdb==5.2.0 From 52de2f4ffb6431c6fb2e0ab6b41288a05c3b6887 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Aug 2019 11:46:07 -0700 Subject: [PATCH 34/52] Revert emulated hue changes (#25732) --- .../components/emulated_hue/hue_api.py | 26 ++++++------------- tests/components/emulated_hue/test_hue_api.py | 14 ---------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 6d59d777e8b..1b08b43c9af 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -562,25 +562,15 @@ def get_entity_state(config, entity): def entity_to_json(config, entity, state): """Convert an entity to its Hue bridge JSON representation.""" - entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity_features & SUPPORT_BRIGHTNESS: - return { - "state": { - HUE_API_STATE_ON: state[STATE_ON], - HUE_API_STATE_BRI: state[STATE_BRIGHTNESS], - HUE_API_STATE_HUE: state[STATE_HUE], - HUE_API_STATE_SAT: state[STATE_SATURATION], - "reachable": True, - }, - "type": "Dimmable light", - "name": config.get_entity_name(entity), - "modelid": "HASS123", - "uniqueid": entity.entity_id, - "swversion": "123", - } return { - "state": {HUE_API_STATE_ON: state[STATE_ON], "reachable": True}, - "type": "On/off light", + "state": { + HUE_API_STATE_ON: state[STATE_ON], + HUE_API_STATE_BRI: state[STATE_BRIGHTNESS], + HUE_API_STATE_HUE: state[STATE_HUE], + HUE_API_STATE_SAT: state[STATE_SATURATION], + "reachable": True, + }, + "type": "Dimmable light", "name": config.get_entity_name(entity), "modelid": "HASS123", "uniqueid": entity.entity_id, diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 02f24f5afba..57f29a4ef61 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -128,9 +128,6 @@ def hass_hue(loop, hass): kitchen_light_entity.entity_id, kitchen_light_entity.state, attributes=attrs ) - # create a lamp without brightness support - hass.states.async_set("light.no_brightness", "on", {}) - # Ceiling Fan is explicitly excluded from being exposed ceiling_fan_entity = hass.states.get("fan.ceiling_fan") attrs = dict(ceiling_fan_entity.attributes) @@ -221,17 +218,6 @@ def test_discover_lights(hue_client): assert "climate.ecobee" not in devices -@asyncio.coroutine -def test_light_without_brightness_supported(hass_hue, hue_client): - """Test that light without brightness is supported.""" - light_without_brightness_json = yield from perform_get_light_state( - hue_client, "light.no_brightness", 200 - ) - - assert light_without_brightness_json["state"][HUE_API_STATE_ON] is True - assert light_without_brightness_json["type"] == "On/off light" - - @asyncio.coroutine def test_get_light_state(hass_hue, hue_client): """Test the getting of light state.""" From 609118d3ac10101f531314ba6515bc1f95636901 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 6 Aug 2019 23:55:36 +0200 Subject: [PATCH 35/52] Fix last seen not available on certain devices (#25735) --- homeassistant/components/unifi/device_tracker.py | 12 +++++++----- tests/components/unifi/test_device_tracker.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index d0c2684ff53..89d3fce515e 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -301,11 +301,10 @@ class UniFiDeviceTracker(ScannerEntity): CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME ) - if ( - self.device.last_seen - and dt_util.utcnow() - - dt_util.utc_from_timestamp(float(self.device.last_seen)) - ) < detection_time: + if self.device.last_seen and ( + dt_util.utcnow() - dt_util.utc_from_timestamp(float(self.device.last_seen)) + < detection_time + ): return True return False @@ -347,6 +346,9 @@ class UniFiDeviceTracker(ScannerEntity): @property def device_state_attributes(self): """Return the device state attributes.""" + if not self.device.last_seen: + return {} + attributes = {} attributes["upgradable"] = self.device.upgradable diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 9fca9d21a5b..0d8d631d8ff 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -73,6 +73,17 @@ DEVICE_1 = { "upgradable": False, "version": "4.0.42.10433", } +DEVICE_2 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "ip": "10.0.1.1", + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "device_1", + "type": "usw", + "version": "4.0.42.10433", +} CONTROLLER_DATA = { CONF_HOST: "mock-host", @@ -167,7 +178,7 @@ async def test_no_clients(hass, mock_controller): async def test_tracked_devices(hass, mock_controller): """Test the update_items function with some clients.""" mock_controller.mock_client_responses.append([CLIENT_1, CLIENT_2, CLIENT_3]) - mock_controller.mock_device_responses.append([DEVICE_1]) + mock_controller.mock_device_responses.append([DEVICE_1, DEVICE_2]) mock_controller.unifi_config = {unifi_dt.CONF_SSID_FILTER: ["ssid"]} await setup_controller(hass, mock_controller) From 0f8f4f4b547ee387476a0d3c456cef219808567f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 7 Aug 2019 09:25:10 -0700 Subject: [PATCH 36/52] Bumped version to 0.97.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a00925c0025..4e9d5868f6c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From a20c63141006d35bc029ec351ee2a6b9f99a0259 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 7 Aug 2019 10:35:24 -0700 Subject: [PATCH 37/52] Update requirements --- script/gen_requirements_all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 14cb165e6cf..965f881b9c7 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -215,7 +215,7 @@ def core_requirements(): """Gather core requirements out of setup.py.""" with open("setup.py") as inp: reqs_raw = re.search(r"REQUIRES = \[(.*?)\]", inp.read(), re.S).group(1) - return re.findall(r"'(.*?)'", reqs_raw) + return [x[1] for x in re.findall(r"(['\"])(.*?)\1", reqs_raw)] def gather_recursive_requirements(domain, seen=None): From e35501222983753db1c6fed93cfe501af28b311d Mon Sep 17 00:00:00 2001 From: Dustin Essington Date: Fri, 9 Aug 2019 10:54:33 -0700 Subject: [PATCH 38/52] Update HIBP sensor to use API v3 and API Key (#25699) * Update HIBP sensor to use API v3 and API Key * ran black code formatter * fixed stray , that was invalid in multiple json formatters --- .../components/haveibeenpwned/manifest.json | 12 ++++----- .../components/haveibeenpwned/sensor.py | 27 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/haveibeenpwned/manifest.json b/homeassistant/components/haveibeenpwned/manifest.json index f0b0561e170..40572f82ea8 100644 --- a/homeassistant/components/haveibeenpwned/manifest.json +++ b/homeassistant/components/haveibeenpwned/manifest.json @@ -1,8 +1,8 @@ { - "domain": "haveibeenpwned", - "name": "Haveibeenpwned", - "documentation": "https://www.home-assistant.io/components/haveibeenpwned", - "requirements": [], - "dependencies": [], - "codeowners": [] + "domain": "haveibeenpwned", + "name": "Haveibeenpwned", + "documentation": "https://www.home-assistant.io/components/haveibeenpwned", + "requirements": [], + "dependencies": [], + "codeowners": [] } diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index d78756b9543..ec43d9444a2 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -7,7 +7,7 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_EMAIL, ATTR_ATTRIBUTION +from homeassistant.const import CONF_EMAIL, CONF_API_KEY, ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_point_in_time @@ -25,17 +25,21 @@ HA_USER_AGENT = "Home Assistant HaveIBeenPwned Sensor Component" MIN_TIME_BETWEEN_FORCED_UPDATES = timedelta(seconds=5) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) -URL = "https://haveibeenpwned.com/api/v2/breachedaccount/" +URL = "https://haveibeenpwned.com/api/v3/breachedaccount/" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string])} + { + vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string]), + vol.Required(CONF_API_KEY): cv.string, + } ) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the HaveIBeenPwned sensor.""" emails = config.get(CONF_EMAIL) - data = HaveIBeenPwnedData(emails) + api_key = config[CONF_API_KEY] + data = HaveIBeenPwnedData(emails, api_key) devices = [] for email in emails: @@ -125,13 +129,14 @@ class HaveIBeenPwnedSensor(Entity): class HaveIBeenPwnedData: """Class for handling the data retrieval.""" - def __init__(self, emails): + def __init__(self, emails, api_key): """Initialize the data object.""" self._email_count = len(emails) self._current_index = 0 self.data = {} self._email = emails[0] self._emails = emails + self._api_key = api_key def set_next_email(self): """Set the next email to be looked up.""" @@ -146,16 +151,10 @@ class HaveIBeenPwnedData: def update(self, **kwargs): """Get the latest data for current email from REST service.""" try: - url = "{}{}".format(URL, self._email) - + url = "{}{}?truncateResponse=false".format(URL, self._email) + header = {USER_AGENT: HA_USER_AGENT, "hibp-api-key": self._api_key} _LOGGER.debug("Checking for breaches for email: %s", self._email) - - req = requests.get( - url, - headers={USER_AGENT: HA_USER_AGENT}, - allow_redirects=True, - timeout=5, - ) + req = requests.get(url, headers=header, allow_redirects=True, timeout=5) except requests.exceptions.RequestException: _LOGGER.error("Failed fetching data for %s", self._email) From ebf8d5fc662270fcc9acfc9b5ecc5efbcd9ae888 Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Thu, 8 Aug 2019 21:59:33 +0100 Subject: [PATCH 39/52] Update Cisco Mobility Express module version (#25770) * Update manifest.json * Update requirements_all.txt --- homeassistant/components/cisco_mobility_express/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cisco_mobility_express/manifest.json b/homeassistant/components/cisco_mobility_express/manifest.json index 1d80076793d..abdd2400311 100644 --- a/homeassistant/components/cisco_mobility_express/manifest.json +++ b/homeassistant/components/cisco_mobility_express/manifest.json @@ -3,7 +3,7 @@ "name": "Cisco mobility express", "documentation": "https://www.home-assistant.io/components/cisco_mobility_express", "requirements": [ - "ciscomobilityexpress==0.3.1" + "ciscomobilityexpress==0.3.3" ], "dependencies": [], "codeowners": ["@fbradyirl"] diff --git a/requirements_all.txt b/requirements_all.txt index 06a7ada8200..5404b514a58 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -321,7 +321,7 @@ buienradar==1.0.1 caldav==0.6.1 # homeassistant.components.cisco_mobility_express -ciscomobilityexpress==0.3.1 +ciscomobilityexpress==0.3.3 # homeassistant.components.ciscospark ciscosparkapi==0.4.2 From 34b5083c278d25f6bdd6693606ac23a85643ba06 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 8 Aug 2019 14:43:53 -0500 Subject: [PATCH 40/52] Don't track unstable attributes (#25787) --- homeassistant/components/unifi/device_tracker.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 89d3fce515e..d9f90de7888 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -41,11 +41,7 @@ LOGGER = logging.getLogger(__name__) DEVICE_ATTRIBUTES = [ "_is_guest_by_uap", - "ap_mac", "authorized", - "bssid", - "ccq", - "channel", "essid", "hostname", "ip", @@ -54,14 +50,11 @@ DEVICE_ATTRIBUTES = [ "is_wired", "mac", "name", - "noise", "noted", "oui", "qos_policy_applied", "radio", "radio_proto", - "rssi", - "signal", "site_id", "vlan", ] From 20e279a7ac1449d59054217e298cfeb7503eb5d5 Mon Sep 17 00:00:00 2001 From: Nikolay Vasilchuk Date: Fri, 9 Aug 2019 20:31:58 +0300 Subject: [PATCH 41/52] Fix deconz allow_clip_sensor and allow_deconz_groups options (#25811) --- homeassistant/components/deconz/gateway.py | 4 ++-- tests/components/deconz/test_binary_sensor.py | 10 +++++++--- tests/components/deconz/test_climate.py | 10 +++++++--- tests/components/deconz/test_light.py | 10 +++++++--- tests/components/deconz/test_sensor.py | 10 +++++++--- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 8eca227f0cd..0ed3ffd2a56 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -63,12 +63,12 @@ class DeconzGateway: @property def allow_clip_sensor(self) -> bool: """Allow loading clip sensor from gateway.""" - return self.config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True) + return self.config_entry.options.get(CONF_ALLOW_CLIP_SENSOR, True) @property def allow_deconz_groups(self) -> bool: """Allow loading deCONZ groups from gateway.""" - return self.config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True) + return self.config_entry.options.get(CONF_ALLOW_DECONZ_GROUPS, True) async def async_update_device_registry(self): """Update device registry.""" diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 4978a6f75d0..9eb408ba4f1 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -31,14 +31,17 @@ SENSOR = { ENTRY_CONFIG = { - deconz.const.CONF_ALLOW_CLIP_SENSOR: True, - deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, deconz.config_flow.CONF_API_KEY: "ABCDEF", deconz.config_flow.CONF_BRIDGEID: "0123456789", deconz.config_flow.CONF_HOST: "1.2.3.4", deconz.config_flow.CONF_PORT: 80, } +ENTRY_OPTIONS = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, +} + async def setup_gateway(hass, data, allow_clip_sensor=True): """Load the deCONZ binary sensor platform.""" @@ -47,7 +50,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): loop = Mock() session = Mock() - ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor + ENTRY_OPTIONS[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor config_entry = config_entries.ConfigEntry( 1, @@ -56,6 +59,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): ENTRY_CONFIG, "test", config_entries.CONN_CLASS_LOCAL_PUSH, + ENTRY_OPTIONS, ) gateway = deconz.DeconzGateway(hass, config_entry) gateway.api = DeconzSession(loop, session, **config_entry.data) diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 2f2bcbed255..264c3b8761f 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -39,14 +39,17 @@ SENSOR = { } ENTRY_CONFIG = { - deconz.const.CONF_ALLOW_CLIP_SENSOR: True, - deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, deconz.config_flow.CONF_API_KEY: "ABCDEF", deconz.config_flow.CONF_BRIDGEID: "0123456789", deconz.config_flow.CONF_HOST: "1.2.3.4", deconz.config_flow.CONF_PORT: 80, } +ENTRY_OPTIONS = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, +} + async def setup_gateway(hass, data, allow_clip_sensor=True): """Load the deCONZ sensor platform.""" @@ -59,7 +62,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): session = Mock(put=asynctest.CoroutineMock(return_value=response)) - ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor + ENTRY_OPTIONS[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor config_entry = config_entries.ConfigEntry( 1, @@ -68,6 +71,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): ENTRY_CONFIG, "test", config_entries.CONN_CLASS_LOCAL_PUSH, + ENTRY_OPTIONS, ) gateway = deconz.DeconzGateway(hass, config_entry) gateway.api = DeconzSession(hass.loop, session, **config_entry.data) diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 2d5ba57b6de..77e983e34b4 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -62,14 +62,17 @@ SWITCH = { ENTRY_CONFIG = { - deconz.const.CONF_ALLOW_CLIP_SENSOR: True, - deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, deconz.config_flow.CONF_API_KEY: "ABCDEF", deconz.config_flow.CONF_BRIDGEID: "0123456789", deconz.config_flow.CONF_HOST: "1.2.3.4", deconz.config_flow.CONF_PORT: 80, } +ENTRY_OPTIONS = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, +} + async def setup_gateway(hass, data, allow_deconz_groups=True): """Load the deCONZ light platform.""" @@ -78,7 +81,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True): loop = Mock() session = Mock() - ENTRY_CONFIG[deconz.const.CONF_ALLOW_DECONZ_GROUPS] = allow_deconz_groups + ENTRY_OPTIONS[deconz.const.CONF_ALLOW_DECONZ_GROUPS] = allow_deconz_groups config_entry = config_entries.ConfigEntry( 1, @@ -87,6 +90,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True): ENTRY_CONFIG, "test", config_entries.CONN_CLASS_LOCAL_PUSH, + ENTRY_OPTIONS, ) gateway = deconz.DeconzGateway(hass, config_entry) gateway.api = DeconzSession(loop, session, **config_entry.data) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index d881a87a6e6..9c03f3e9a90 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -75,14 +75,17 @@ SENSOR = { ENTRY_CONFIG = { - deconz.const.CONF_ALLOW_CLIP_SENSOR: True, - deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, deconz.config_flow.CONF_API_KEY: "ABCDEF", deconz.config_flow.CONF_BRIDGEID: "0123456789", deconz.config_flow.CONF_HOST: "1.2.3.4", deconz.config_flow.CONF_PORT: 80, } +ENTRY_OPTIONS = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, +} + async def setup_gateway(hass, data, allow_clip_sensor=True): """Load the deCONZ sensor platform.""" @@ -91,7 +94,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): loop = Mock() session = Mock() - ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor + ENTRY_OPTIONS[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor config_entry = config_entries.ConfigEntry( 1, @@ -100,6 +103,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): ENTRY_CONFIG, "test", config_entries.CONN_CLASS_LOCAL_PUSH, + ENTRY_OPTIONS, ) gateway = deconz.DeconzGateway(hass, config_entry) gateway.api = DeconzSession(loop, session, **config_entry.data) From e1fee1bd455dcc6a2f7da930fb33f8d3aa79c53b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 9 Aug 2019 11:17:31 -0700 Subject: [PATCH 42/52] Bumped version to 0.97.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4e9d5868f6c..51549b8f07c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From e57ecc9d7dec1e6accc14c27dfc8d353d3a53306 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Fri, 9 Aug 2019 13:41:50 -0500 Subject: [PATCH 43/52] Fix brightness type (#25818) --- homeassistant/components/smartthings/light.py | 4 +++- tests/components/smartthings/test_light.py | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 9ec4634ab36..4bc3f487790 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -133,7 +133,9 @@ class SmartThingsLight(SmartThingsEntity, Light): """Update entity attributes when the device status has changed.""" # Brightness and transition if self._supported_features & SUPPORT_BRIGHTNESS: - self._brightness = convert_scale(self._device.status.level, 100, 255) + self._brightness = int( + convert_scale(self._device.status.level, 100, 255, 0) + ) # Color Temperature if self._supported_features & SUPPORT_COLOR_TEMP: self._color_temp = color_util.color_temperature_kelvin_to_mired( diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index b0f7268217c..e9004031e7d 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -84,6 +84,7 @@ async def test_entity_state(hass, light_devices): state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION ) + assert isinstance(state.attributes[ATTR_BRIGHTNESS], int) assert state.attributes[ATTR_BRIGHTNESS] == 255 # Color Dimmer 1 @@ -103,6 +104,7 @@ async def test_entity_state(hass, light_devices): ) assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_HS_COLOR] == (273.6, 55.0) + assert isinstance(state.attributes[ATTR_COLOR_TEMP], int) assert state.attributes[ATTR_COLOR_TEMP] == 222 @@ -191,7 +193,7 @@ async def test_turn_on_with_brightness(hass, light_devices): assert state is not None assert state.state == "on" # round-trip rounding error (expected) - assert state.attributes[ATTR_BRIGHTNESS] == 73.95 + assert state.attributes[ATTR_BRIGHTNESS] == 74 async def test_turn_on_with_minimal_brightness(hass, light_devices): @@ -216,7 +218,7 @@ async def test_turn_on_with_minimal_brightness(hass, light_devices): assert state is not None assert state.state == "on" # round-trip rounding error (expected) - assert state.attributes[ATTR_BRIGHTNESS] == 2.55 + assert state.attributes[ATTR_BRIGHTNESS] == 3 async def test_turn_on_with_color(hass, light_devices): From 77d984e9801f674c712746040f64705c3eb7d6f2 Mon Sep 17 00:00:00 2001 From: Santobert Date: Fri, 9 Aug 2019 21:08:35 +0200 Subject: [PATCH 44/52] Add script to install locale (#25791) --- Dockerfile | 1 + virtualization/Docker/Dockerfile.dev | 1 + virtualization/Docker/scripts/locales | 12 ++++++++++++ virtualization/Docker/setup_docker_prereqs | 5 +++++ 4 files changed, 19 insertions(+) create mode 100755 virtualization/Docker/scripts/locales diff --git a/Dockerfile b/Dockerfile index 09c16707541..a9e73699558 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ LABEL maintainer="Paulus Schoutsen " #ENV INSTALL_SSOCR no #ENV INSTALL_DLIB no #ENV INSTALL_IPERF3 no +#ENV INSTALL_LOCALES no VOLUME /config diff --git a/virtualization/Docker/Dockerfile.dev b/virtualization/Docker/Dockerfile.dev index 2191d8ad920..260a29cb3d0 100644 --- a/virtualization/Docker/Dockerfile.dev +++ b/virtualization/Docker/Dockerfile.dev @@ -14,6 +14,7 @@ LABEL maintainer="Paulus Schoutsen " #ENV INSTALL_SSOCR no #ENV INSTALL_DLIB no #ENV INSTALL_IPERF3 no +#ENV INSTALL_LOCALES no VOLUME /config diff --git a/virtualization/Docker/scripts/locales b/virtualization/Docker/scripts/locales new file mode 100755 index 00000000000..cbbe0341575 --- /dev/null +++ b/virtualization/Docker/scripts/locales @@ -0,0 +1,12 @@ +#!/bin/bash +# Sets up locales. + +# Stop on errors +set -e + +apt-get update +apt-get install -y --no-install-recommends locales + +# Set the locale +sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen +locale-gen diff --git a/virtualization/Docker/setup_docker_prereqs b/virtualization/Docker/setup_docker_prereqs index 9f3fc81d045..62ac73d366e 100755 --- a/virtualization/Docker/setup_docker_prereqs +++ b/virtualization/Docker/setup_docker_prereqs @@ -9,6 +9,7 @@ INSTALL_OPENALPR="${INSTALL_OPENALPR:-yes}" INSTALL_LIBCEC="${INSTALL_LIBCEC:-yes}" INSTALL_SSOCR="${INSTALL_SSOCR:-yes}" INSTALL_DLIB="${INSTALL_DLIB:-yes}" +INSTALL_LOCALES="${INSTALL_LOCALES:-yes}" # Required debian packages for running hass or components PACKAGES=( @@ -70,6 +71,10 @@ if [ "$INSTALL_DLIB" == "yes" ]; then pip3 install --no-cache-dir "dlib>=19.5" fi +if [ "$INSTALL_LOCALES" == "yes" ]; then + virtualization/Docker/scripts/locales +fi + # Remove packages apt-get remove -y --purge ${PACKAGES_DEV[@]} apt-get -y --purge autoremove From 48e42d8595db279979173fa8117d43990382faa3 Mon Sep 17 00:00:00 2001 From: Cameron Morris <636871+cameronrmorris@users.noreply.github.com> Date: Fri, 9 Aug 2019 20:05:05 -0400 Subject: [PATCH 45/52] Fix eco preset for Wink Air Conditioner (#25763) * Add preset support for device * Provide mappings between preset changes --- homeassistant/components/wink/climate.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index ed2d2482802..38f25ef0a83 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -27,6 +27,7 @@ from homeassistant.components.climate.const import ( SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, + SUPPORT_PRESET_MODE, PRESET_NONE, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS @@ -62,7 +63,7 @@ SUPPORT_FLAGS_THERMOSTAT = ( SUPPORT_FAN_THERMOSTAT = [FAN_AUTO, FAN_ON] SUPPORT_PRESET_THERMOSTAT = [PRESET_AWAY, PRESET_ECO] -SUPPORT_FLAGS_AC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE +SUPPORT_FLAGS_AC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_PRESET_MODE SUPPORT_FAN_AC = [FAN_HIGH, FAN_LOW, FAN_MEDIUM] SUPPORT_PRESET_AC = [PRESET_NONE, PRESET_ECO] @@ -415,10 +416,13 @@ class WinkAC(WinkDevice, ClimateDevice): @property def preset_mode(self): """Return the current preset mode, e.g., home, away, temp.""" + if not self.wink.is_on(): + return PRESET_NONE + mode = self.wink.current_mode() if mode == "auto_eco": return PRESET_ECO - return None + return PRESET_NONE @property def preset_modes(self): @@ -436,7 +440,7 @@ class WinkAC(WinkDevice, ClimateDevice): wink_mode = self.wink.current_mode() if wink_mode == "auto_eco": - return HVAC_MODE_AUTO + return HVAC_MODE_COOL return WINK_HVAC_TO_HA.get(wink_mode) @property @@ -476,6 +480,8 @@ class WinkAC(WinkDevice, ClimateDevice): """Set new preset mode.""" if preset_mode == PRESET_ECO: self.wink.set_operation_mode("auto_eco") + elif self.hvac_mode == HVAC_MODE_COOL and preset_mode == PRESET_NONE: + self.set_hvac_mode(HVAC_MODE_COOL) @property def target_temperature(self): From 0eb93db67ea132969b42bc0b1b5b3d566ffb4bb0 Mon Sep 17 00:00:00 2001 From: Brandon Davidson Date: Fri, 9 Aug 2019 13:20:26 -0700 Subject: [PATCH 46/52] Update pyvera to 0.3.3 (#25820) Fixes #24987 --- homeassistant/components/vera/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 5fddce7efe7..07ae7ab3d36 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -3,7 +3,7 @@ "name": "Vera", "documentation": "https://www.home-assistant.io/components/vera", "requirements": [ - "pyvera==0.3.2" + "pyvera==0.3.3" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index 5404b514a58..01c213b8b4e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1566,7 +1566,7 @@ pyuptimerobot==0.0.5 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.3.2 +pyvera==0.3.3 # homeassistant.components.vesync pyvesync==1.1.0 From 9afb6c3876182cac717b92e7cbac4d6e71e59971 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Sat, 10 Aug 2019 15:49:29 +0200 Subject: [PATCH 47/52] Fix Netatmo climate issue (#25830) * Bump pyatmo to v2.2.1 * Fix issue 25778 --- homeassistant/components/netatmo/climate.py | 4 ++-- homeassistant/components/netatmo/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 109f12a87fc..9656d4a37a4 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -109,8 +109,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): auth = hass.data[DATA_NETATMO_AUTH] + home_data = HomeData(auth) try: - home_data = HomeData(auth) + home_data.setup() except pyatmo.NoDevice: return @@ -352,7 +353,6 @@ class HomeData: def get_home_ids(self): """Get all the home ids returned by NetAtmo API.""" - self.setup() if self.homedata is None: return [] for home_id in self.homedata.homes: diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 66b0efc61ff..82f32c34407 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -3,7 +3,7 @@ "name": "Netatmo", "documentation": "https://www.home-assistant.io/components/netatmo", "requirements": [ - "pyatmo==2.2.0" + "pyatmo==2.2.1" ], "dependencies": [ "webhook" diff --git a/requirements_all.txt b/requirements_all.txt index 01c213b8b4e..4a7ef886714 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1046,7 +1046,7 @@ pyalarmdotcom==0.3.2 pyarlo==0.2.3 # homeassistant.components.netatmo -pyatmo==2.2.0 +pyatmo==2.2.1 # homeassistant.components.apple_tv pyatv==0.3.12 From 0c815ea84337c100c40ef0532486b95cda8c731d Mon Sep 17 00:00:00 2001 From: tombbo <53979375+tombbo@users.noreply.github.com> Date: Sat, 10 Aug 2019 22:24:03 +0200 Subject: [PATCH 48/52] Fix KNX Climate mode change callback (#25851) - fix KNX Climate not updating UI after receiving mode change telegram from KNX bus --- homeassistant/components/knx/climate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index a3183c3b34d..07aac11b972 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -209,6 +209,7 @@ class KNXClimate(ClimateDevice): await self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) + self.device.mode.register_device_updated_cb(after_update_callback) @property def name(self) -> str: From a061310e78965058c43dae1a6a943f79c6d355f5 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sat, 10 Aug 2019 23:47:36 -0500 Subject: [PATCH 49/52] Always populate hvac_modes in SmartThings climate platform (#25859) * Always return list for hvac_modes * Use climate constants --- .../components/smartthings/climate.py | 13 +++++----- tests/components/smartthings/test_climate.py | 25 ++++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index e9fefeb2995..bb307523e97 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -228,35 +228,34 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): self._hvac_mode = MODE_TO_STATE.get(thermostat_mode) if self._hvac_mode is None: _LOGGER.debug( - "Device %s (%s) returned an invalid" "hvac mode: %s", + "Device %s (%s) returned an invalid hvac mode: %s", self._device.label, self._device.device_id, thermostat_mode, ) + modes = set() supported_modes = self._device.status.supported_thermostat_modes if isinstance(supported_modes, Iterable): - operations = set() for mode in supported_modes: state = MODE_TO_STATE.get(mode) if state is not None: - operations.add(state) + modes.add(state) else: _LOGGER.debug( - "Device %s (%s) returned an invalid " - "supported thermostat mode: %s", + "Device %s (%s) returned an invalid supported thermostat mode: %s", self._device.label, self._device.device_id, mode, ) - self._hvac_modes = operations else: _LOGGER.debug( - "Device %s (%s) returned invalid supported " "thermostat modes: %s", + "Device %s (%s) returned invalid supported thermostat modes: %s", self._device.label, self._device.device_id, supported_modes, ) + self._hvac_modes = list(modes) @property def current_humidity(self): diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 01206ded062..c366761ea1f 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -214,14 +214,14 @@ async def test_legacy_thermostat_entity_state(hass, legacy_thermostat): | SUPPORT_TARGET_TEMPERATURE_RANGE | SUPPORT_TARGET_TEMPERATURE ) - assert state.attributes[ATTR_HVAC_ACTIONS] == "idle" - assert state.attributes[ATTR_HVAC_MODES] == { + assert state.attributes[ATTR_HVAC_ACTIONS] == CURRENT_HVAC_IDLE + assert sorted(state.attributes[ATTR_HVAC_MODES]) == [ HVAC_MODE_AUTO, HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - } + ] assert state.attributes[ATTR_FAN_MODE] == "auto" assert state.attributes[ATTR_FAN_MODES] == ["auto", "on"] assert state.attributes[ATTR_TARGET_TEMP_LOW] == 20 # celsius @@ -239,12 +239,12 @@ async def test_basic_thermostat_entity_state(hass, basic_thermostat): == SUPPORT_TARGET_TEMPERATURE_RANGE | SUPPORT_TARGET_TEMPERATURE ) assert ATTR_HVAC_ACTIONS not in state.attributes - assert state.attributes[ATTR_HVAC_MODES] == { - HVAC_MODE_OFF, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_HEAT, + assert sorted(state.attributes[ATTR_HVAC_MODES]) == [ HVAC_MODE_COOL, - } + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.1 # celsius @@ -260,13 +260,13 @@ async def test_thermostat_entity_state(hass, thermostat): | SUPPORT_TARGET_TEMPERATURE ) assert state.attributes[ATTR_HVAC_ACTIONS] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_HVAC_MODES] == { + assert sorted(state.attributes[ATTR_HVAC_MODES]) == [ HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - } + ] assert state.attributes[ATTR_FAN_MODE] == "on" assert state.attributes[ATTR_FAN_MODES] == ["auto", "on"] assert state.attributes[ATTR_TEMPERATURE] == 20 # celsius @@ -286,6 +286,7 @@ async def test_buggy_thermostat_entity_state(hass, buggy_thermostat): assert state.state is STATE_UNKNOWN assert state.attributes[ATTR_TEMPERATURE] is None assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.1 # celsius + assert state.attributes[ATTR_HVAC_MODES] == [] async def test_buggy_thermostat_invalid_mode(hass, buggy_thermostat): @@ -295,7 +296,7 @@ async def test_buggy_thermostat_invalid_mode(hass, buggy_thermostat): ) await setup_platform(hass, CLIMATE_DOMAIN, devices=[buggy_thermostat]) state = hass.states.get("climate.buggy_thermostat") - assert state.attributes[ATTR_HVAC_MODES] == {"heat"} + assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] async def test_air_conditioner_entity_state(hass, air_conditioner): From f03538f8666b021818b771ae950f08c849156460 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 11 Aug 2019 22:40:44 +0200 Subject: [PATCH 50/52] UniFi - Use state to know if device is online (#25876) --- homeassistant/components/unifi/device_tracker.py | 13 ++++++++----- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/unifi/test_device_tracker.py | 2 ++ 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index d9f90de7888..42a6f496a2a 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -294,7 +294,7 @@ class UniFiDeviceTracker(ScannerEntity): CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME ) - if self.device.last_seen and ( + if self.device.state == 1 and ( dt_util.utcnow() - dt_util.utc_from_timestamp(float(self.device.last_seen)) < detection_time ): @@ -339,15 +339,18 @@ class UniFiDeviceTracker(ScannerEntity): @property def device_state_attributes(self): """Return the device state attributes.""" - if not self.device.last_seen: + if self.device.state == 0: return {} attributes = {} - attributes["upgradable"] = self.device.upgradable - attributes["overheating"] = self.device.overheating - if self.device.has_fan: attributes["fan_level"] = self.device.fan_level + if self.device.overheating: + attributes["overheating"] = self.device.overheating + + if self.device.upgradable: + attributes["upgradable"] = self.device.upgradable + return attributes diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index bcee022e1c4..d182806c4ac 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/components/unifi", "requirements": [ - "aiounifi==10" + "aiounifi==11" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 4a7ef886714..423409b7054 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -170,7 +170,7 @@ aiopvapi==1.6.14 aioswitcher==2019.4.26 # homeassistant.components.unifi -aiounifi==10 +aiounifi==11 # homeassistant.components.wwlln aiowwlln==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2da65e24cd..26630c11f67 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -68,7 +68,7 @@ aionotion==1.1.0 aioswitcher==2019.4.26 # homeassistant.components.unifi -aiounifi==10 +aiounifi==11 # homeassistant.components.wwlln aiowwlln==1.0.0 diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 0d8d631d8ff..d5783e58818 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -69,6 +69,7 @@ DEVICE_1 = { "model": "US16P150", "name": "device_1", "overheating": False, + "state": 1, "type": "usw", "upgradable": False, "version": "4.0.42.10433", @@ -81,6 +82,7 @@ DEVICE_2 = { "mac": "00:00:00:00:01:01", "model": "US16P150", "name": "device_1", + "state": 0, "type": "usw", "version": "4.0.42.10433", } From 38412fd88027fa2403a788b80910834e5038bf4d Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 12 Aug 2019 05:48:56 +0200 Subject: [PATCH 51/52] Fix issue with nuki new available state (#25881) --- homeassistant/components/nuki/lock.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 38e42fcc1b5..31a655dfedd 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -144,10 +144,12 @@ class NukiLock(LockDevice): self._nuki_lock.update(aggressive=False) except requests.exceptions.RequestException: self._available = False - else: - self._name = self._nuki_lock.name - self._locked = self._nuki_lock.is_locked - self._battery_critical = self._nuki_lock.battery_critical + return + + self._available = self._nuki_lock.state != 255 + self._name = self._nuki_lock.name + self._locked = self._nuki_lock.is_locked + self._battery_critical = self._nuki_lock.battery_critical def lock(self, **kwargs): """Lock the device.""" From 38c67389b26a168c1440a6a6d491361f285086f6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Aug 2019 20:49:31 -0700 Subject: [PATCH 52/52] Bumped version to 0.97.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 51549b8f07c..7078ee62d07 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 97 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0)