From bb60286ed950bc9d155553346e842fbd0765c083 Mon Sep 17 00:00:00 2001 From: Kevin Eifinger Date: Sat, 11 Apr 2020 16:47:07 +0200 Subject: [PATCH 01/12] Fix #33995 Use "now" if departure is None (#34017) --- homeassistant/components/here_travel_time/sensor.py | 3 +++ tests/components/here_travel_time/test_sensor.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index c88aeb8e5a0..f73d3bccaa6 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -424,6 +424,9 @@ class HERETravelTimeData: if departure is not None: departure = convert_time_to_isodate(departure) + if departure is None and arrival is None: + departure = "now" + _LOGGER.debug( "Requesting route for origin: %s, destination: %s, route_mode: %s, mode: %s, traffic_mode: %s, arrival: %s, departure: %s", origin, diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 642b774f1e5..d399f5b67aa 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -80,6 +80,8 @@ def _build_mock_url(origin, destination, modes, api_key, departure=None, arrival parameters["arrival"] = arrival if departure is not None: parameters["departure"] = departure + if departure is None and arrival is None: + parameters["departure"] = "now" url = base_url + urllib.parse.urlencode(parameters) print(url) return url From 908e044db1d3ef9ab2c10a88c7aaf6df63fd274d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Apr 2020 08:25:57 -0500 Subject: [PATCH 02/12] Fix nexia fan and hold modes for XL824 thermostats (#34042) * Fix nexia fan and hold modes for XL824 thermostats * Update nexia to 0.9.0 * Update tests to reflect the modes that now come directly in --- homeassistant/components/nexia/climate.py | 3 +-- homeassistant/components/nexia/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/nexia/test_climate.py | 8 ++++---- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 8af1be20b1e..d7e93e511fb 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -2,7 +2,6 @@ import logging from nexia.const import ( - FAN_MODES, OPERATION_MODE_AUTO, OPERATION_MODE_COOL, OPERATION_MODE_HEAT, @@ -192,7 +191,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateDevice): @property def fan_modes(self): """Return the list of available fan modes.""" - return FAN_MODES + return self._thermostat.get_fan_modes() @property def min_temp(self): diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index 2102a2a8225..a58330ad227 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -1,7 +1,7 @@ { "domain": "nexia", "name": "Nexia", - "requirements": ["nexia==0.8.2"], + "requirements": ["nexia==0.9.1"], "codeowners": ["@ryannazaretian", "@bdraco"], "documentation": "https://www.home-assistant.io/integrations/nexia", "config_flow": true diff --git a/requirements_all.txt b/requirements_all.txt index 3d4d5ad1394..cfe84803c54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -922,7 +922,7 @@ netdisco==2.6.0 neurio==0.3.1 # homeassistant.components.nexia -nexia==0.8.2 +nexia==0.9.1 # homeassistant.components.nextcloud nextcloudmonitor==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9e128f83ca1..531b2550dea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -357,7 +357,7 @@ nessclient==0.9.15 netdisco==2.6.0 # homeassistant.components.nexia -nexia==0.8.2 +nexia==0.9.1 # homeassistant.components.nsw_fuel_station nsw-fuel-api-client==1.0.10 diff --git a/tests/components/nexia/test_climate.py b/tests/components/nexia/test_climate.py index 7f3ed900d3c..fe47ceeffe4 100644 --- a/tests/components/nexia/test_climate.py +++ b/tests/components/nexia/test_climate.py @@ -18,8 +18,8 @@ async def test_climate_zones(hass): "current_temperature": 22.8, "dehumidify_setpoint": 45.0, "dehumidify_supported": True, - "fan_mode": "auto", - "fan_modes": ["auto", "on", "circulate"], + "fan_mode": "Auto", + "fan_modes": ["Auto", "On", "Circulate"], "friendly_name": "Nick Office", "humidify_supported": False, "humidity": 45.0, @@ -53,8 +53,8 @@ async def test_climate_zones(hass): "current_temperature": 25.0, "dehumidify_setpoint": 50.0, "dehumidify_supported": True, - "fan_mode": "auto", - "fan_modes": ["auto", "on", "circulate"], + "fan_mode": "Auto", + "fan_modes": ["Auto", "On", "Circulate"], "friendly_name": "Kitchen", "humidify_supported": False, "humidity": 50.0, From fc286900d3c3792d63e9a74a6246626cc26400ad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Apr 2020 17:29:35 -0500 Subject: [PATCH 03/12] Handle all zero serial numbers in NUT (#34045) * Handle all zero serial numbers in NUT * Add additional nut tests * Update homeassistant/components/nut/__init__.py Co-Authored-By: Paulus Schoutsen * remove re Co-authored-by: Paulus Schoutsen --- homeassistant/components/nut/__init__.py | 2 +- tests/fixtures/nut/BACKUPSES600M1.json | 47 ++++++++++++++++++++++++ tests/fixtures/nut/CP1500PFCLCD.json | 43 ++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/nut/BACKUPSES600M1.json create mode 100644 tests/fixtures/nut/CP1500PFCLCD.json diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index a990cdf94b8..b8561dde303 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -109,7 +109,7 @@ def _firmware_from_status(status): def _serial_from_status(status): """Find the best serialvalue from the status.""" serial = status.get("device.serial") or status.get("ups.serial") - if serial and serial == "unknown": + if serial and (serial.lower() == "unknown" or serial.count("0") == len(serial)): return None return serial diff --git a/tests/fixtures/nut/BACKUPSES600M1.json b/tests/fixtures/nut/BACKUPSES600M1.json new file mode 100644 index 00000000000..1acd0ef0444 --- /dev/null +++ b/tests/fixtures/nut/BACKUPSES600M1.json @@ -0,0 +1,47 @@ +{ + "ups.realpower.nominal" : "330", + "input.voltage" : "123.0", + "ups.mfr" : "American Power Conversion", + "driver.version" : "2.7.4", + "ups.test.result" : "No test initiated", + "input.voltage.nominal" : "120", + "input.transfer.low" : "92", + "driver.parameter.pollinterval" : "15", + "driver.version.data" : "APC HID 0.96", + "driver.parameter.pollfreq" : "30", + "battery.mfr.date" : "2017/04/01", + "ups.beeper.status" : "enabled", + "battery.date" : "2001/09/25", + "driver.name" : "usbhid-ups", + "battery.charge" : "100", + "ups.status" : "OL", + "ups.model" : "Back-UPS ES 600M1", + "battery.runtime.low" : "120", + "ups.firmware" : "928.a5 .D", + "ups.delay.shutdown" : "20", + "device.model" : "Back-UPS ES 600M1", + "device.serial" : "4B1713P32195 ", + "input.sensitivity" : "medium", + "ups.firmware.aux" : "a5 ", + "input.transfer.reason" : "input voltage out of range", + "ups.timer.reboot" : "0", + "battery.voltage.nominal" : "12.0", + "ups.vendorid" : "051d", + "input.transfer.high" : "139", + "battery.voltage" : "13.7", + "battery.charge.low" : "10", + "battery.type" : "PbAc", + "ups.mfr.date" : "2017/04/01", + "ups.timer.shutdown" : "-1", + "device.mfr" : "American Power Conversion", + "driver.parameter.port" : "auto", + "battery.charge.warning" : "50", + "device.type" : "ups", + "driver.parameter.vendorid" : "051d", + "ups.serial" : "4B1713P32195 ", + "ups.load" : "22", + "driver.version.internal" : "0.41", + "battery.runtime" : "1968", + "driver.parameter.synchronous" : "no", + "ups.productid" : "0002" +} diff --git a/tests/fixtures/nut/CP1500PFCLCD.json b/tests/fixtures/nut/CP1500PFCLCD.json new file mode 100644 index 00000000000..8f12ae96df6 --- /dev/null +++ b/tests/fixtures/nut/CP1500PFCLCD.json @@ -0,0 +1,43 @@ +{ + "battery.runtime.low" : "300", + "driver.parameter.port" : "auto", + "ups.delay.shutdown" : "20", + "driver.parameter.pollfreq" : "30", + "ups.beeper.status" : "disabled", + "input.voltage.nominal" : "120", + "device.serial" : "000000000000", + "ups.timer.shutdown" : "-60", + "input.voltage" : "122.0", + "ups.status" : "OL", + "ups.model" : "CP1500PFCLCD", + "device.mfr" : "CPS", + "device.model" : "CP1500PFCLCD", + "input.transfer.low" : "88", + "battery.mfr.date" : "CPS", + "driver.version" : "2.7.4", + "driver.version.data" : "CyberPower HID 0.4", + "driver.parameter.synchronous" : "no", + "ups.realpower.nominal" : "900", + "ups.productid" : "0501", + "ups.mfr" : "CPS", + "ups.vendorid" : "0764", + "driver.version.internal" : "0.41", + "output.voltage" : "138.0", + "battery.runtime" : "10530", + "device.type" : "ups", + "battery.charge.low" : "10", + "ups.timer.start" : "-60", + "driver.parameter.pollinterval" : "15", + "ups.load" : "0", + "ups.serial" : "000000000000", + "input.transfer.high" : "139", + "battery.charge.warning" : "20", + "battery.voltage.nominal" : "24", + "driver.parameter.vendorid" : "0764", + "driver.name" : "usbhid-ups", + "battery.type" : "PbAcid", + "ups.delay.start" : "30", + "battery.voltage" : "24.0", + "battery.charge" : "100", + "ups.test.result" : "No test initiated" +} From 4337dd68645dbbd23e3bb787a107e98e50a42249 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 12 Apr 2020 16:18:44 +0200 Subject: [PATCH 04/12] UniFi - Fix unit of measurement from B to MB (#34091) --- homeassistant/components/unifi/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 1b6667f2e80..2e82ecb4f6f 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -2,7 +2,7 @@ import logging from homeassistant.components.unifi.config_flow import get_controller_from_config_entry -from homeassistant.const import DATA_BYTES +from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -116,7 +116,7 @@ class UniFiRxBandwidthSensor(UniFiClient): @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return DATA_BYTES + return DATA_MEGABYTES class UniFiTxBandwidthSensor(UniFiRxBandwidthSensor): From 667a87988d168a4dbd9b0d86267b445d91f1460b Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Mon, 13 Apr 2020 01:53:01 +0200 Subject: [PATCH 05/12] Fix Daikin sensor temperature_unit & cleanup (#34116) --- homeassistant/components/daikin/sensor.py | 31 +++++------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index e3e2e6a0f27..2982abd261c 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -1,17 +1,11 @@ """Support for Daikin AC sensors.""" import logging -from homeassistant.const import CONF_ICON, CONF_NAME, CONF_TYPE +from homeassistant.const import CONF_ICON, CONF_NAME, TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from homeassistant.util.unit_system import UnitSystem from . import DOMAIN as DAIKIN_DOMAIN -from .const import ( - ATTR_INSIDE_TEMPERATURE, - ATTR_OUTSIDE_TEMPERATURE, - SENSOR_TYPE_TEMPERATURE, - SENSOR_TYPES, -) +from .const import ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, SENSOR_TYPES _LOGGER = logging.getLogger(__name__) @@ -31,30 +25,19 @@ async def async_setup_entry(hass, entry, async_add_entities): sensors = [ATTR_INSIDE_TEMPERATURE] if daikin_api.device.support_outside_temperature: sensors.append(ATTR_OUTSIDE_TEMPERATURE) - async_add_entities( - [ - DaikinClimateSensor(daikin_api, sensor, hass.config.units) - for sensor in sensors - ] - ) + async_add_entities([DaikinClimateSensor(daikin_api, sensor) for sensor in sensors]) class DaikinClimateSensor(Entity): """Representation of a Sensor.""" - def __init__(self, api, monitored_state, units: UnitSystem, name=None) -> None: + def __init__(self, api, monitored_state) -> None: """Initialize the sensor.""" self._api = api - self._sensor = SENSOR_TYPES.get(monitored_state) - if name is None: - name = f"{self._sensor[CONF_NAME]} {api.name}" - - self._name = f"{name} {monitored_state.replace('_', ' ')}" + self._sensor = SENSOR_TYPES[monitored_state] + self._name = f"{api.name} {self._sensor[CONF_NAME]}" self._device_attribute = monitored_state - if self._sensor[CONF_TYPE] == SENSOR_TYPE_TEMPERATURE: - self._unit_of_measurement = units.temperature_unit - @property def unique_id(self): """Return a unique ID.""" @@ -82,7 +65,7 @@ class DaikinClimateSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return self._unit_of_measurement + return TEMP_CELSIUS async def async_update(self): """Retrieve latest state.""" From e742711a76cce032b189ebce5319ff857dbd10bc Mon Sep 17 00:00:00 2001 From: James Nimmo Date: Mon, 13 Apr 2020 15:18:27 +1200 Subject: [PATCH 06/12] Bump pyIntesisHome to 1.7.3 (#34125) --- homeassistant/components/intesishome/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/intesishome/manifest.json b/homeassistant/components/intesishome/manifest.json index f1647f5d97e..e38e1a7dd3b 100644 --- a/homeassistant/components/intesishome/manifest.json +++ b/homeassistant/components/intesishome/manifest.json @@ -4,5 +4,5 @@ "documentation": "https://www.home-assistant.io/integrations/intesishome", "dependencies": [], "codeowners": ["@jnimmo"], - "requirements": ["pyintesishome==1.7.1"] + "requirements": ["pyintesishome==1.7.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index cfe84803c54..36c54eab31b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1330,7 +1330,7 @@ pyialarm==0.3 pyicloud==0.9.6.1 # homeassistant.components.intesishome -pyintesishome==1.7.1 +pyintesishome==1.7.3 # homeassistant.components.ipma pyipma==2.0.5 From 87504806b1d98c8587d5543ac25b35ddc4c56f2a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 13 Apr 2020 15:32:23 -0600 Subject: [PATCH 07/12] Fix deprecated icon/username logic in Slack (#34156) * Fix deprecated icon/username logic in Slack * hassfest --- homeassistant/components/slack/notify.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index fe6f7ab0d26..8cfffc1722a 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -74,11 +74,7 @@ class SlackNotificationService(BaseNotificationService): self._default_channel = default_channel self._hass = hass self._icon = icon - - if username or self._icon: - self._as_user = False - else: - self._as_user = True + self._username = username async def _async_send_local_file_message(self, path, targets, message, title): """Upload a local file (with message) to Slack.""" @@ -108,11 +104,11 @@ class SlackNotificationService(BaseNotificationService): target: self._client.chat_postMessage( channel=target, text=message, - as_user=self._as_user, attachments=attachments, blocks=blocks, icon_emoji=self._icon, link_names=True, + username=self._username, ) for target in targets } From e365f807c11aa8d4fdd2d77a8aeef47a454ce9e0 Mon Sep 17 00:00:00 2001 From: Jason Swails Date: Mon, 13 Apr 2020 18:07:32 -0400 Subject: [PATCH 08/12] Improve rounding the light level conversion in Lutron Caseta (#34167) --- homeassistant/components/lutron_caseta/light.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index ba4342ecfce..350c35fffa8 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) def to_lutron_level(level): """Convert the given Home Assistant light level (0-255) to Lutron (0-100).""" - return int((level * 100) // 255) + return int(round((level * 100) / 255)) def to_hass_level(level): From 2d8bb8e6d2b911a38c1f497802039e93eb3c31b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2020 17:51:35 -0500 Subject: [PATCH 09/12] Fix z-wave brightness off by one (#34170) Z-wave would drop the floating point by calling int() instead of round() which would result in the brightness being off by one in many cases. --- homeassistant/components/zwave/light.py | 2 +- tests/components/zwave/test_light.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index b32daf71f54..745400e5c44 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -104,7 +104,7 @@ def byte_to_zwave_brightness(value): `value` -- (int) Brightness byte value from 0-255. """ if value > 0: - return max(1, int((value / 255) * 99)) + return max(1, round((value / 255) * 99)) return 0 diff --git a/tests/components/zwave/test_light.py b/tests/components/zwave/test_light.py index 10efed24bf2..fc62ef880f6 100644 --- a/tests/components/zwave/test_light.py +++ b/tests/components/zwave/test_light.py @@ -100,13 +100,23 @@ def test_dimmer_turn_on(mock_openzwave): node.reset_mock() + device.turn_on(**{ATTR_BRIGHTNESS: 224}) + + assert node.set_dimmer.called + value_id, brightness = node.set_dimmer.mock_calls[0][1] + + assert value_id == value.value_id + assert brightness == 87 # round(224 / 255 * 99) + + node.reset_mock() + device.turn_on(**{ATTR_BRIGHTNESS: 120}) assert node.set_dimmer.called value_id, brightness = node.set_dimmer.mock_calls[0][1] assert value_id == value.value_id - assert brightness == 46 # int(120 / 255 * 99) + assert brightness == 47 # round(120 / 255 * 99) with patch.object(light, "_LOGGER", MagicMock()) as mock_logger: device.turn_on(**{ATTR_TRANSITION: 35}) From 2553b0d1e028ab6a4deee664613a1ac0926293a5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2020 18:16:17 -0500 Subject: [PATCH 10/12] =?UTF-8?q?Increase=20timeout=20and=20log=20the=20ur?= =?UTF-8?q?l=20of=20the=20elkm1=20system=20that=20time=E2=80=A6=20(#34172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Log the url of the elkm1 system that times out * Bump timeout to 120s --- homeassistant/components/elkm1/__init__.py | 6 ++++-- homeassistant/components/elkm1/config_flow.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 183897d306e..5c6fbf71738 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -39,7 +39,7 @@ from .const import ( ELK_ELEMENTS, ) -SYNC_TIMEOUT = 55 +SYNC_TIMEOUT = 120 _LOGGER = logging.getLogger(__name__) @@ -215,7 +215,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not await async_wait_for_elk_to_sync(elk, SYNC_TIMEOUT): _LOGGER.error( - "Timed out after %d seconds while trying to sync with ElkM1", SYNC_TIMEOUT, + "Timed out after %d seconds while trying to sync with ElkM1 at %s", + SYNC_TIMEOUT, + conf[CONF_HOST], ) elk.disconnect() raise ConfigEntryNotReady diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index cad3ecac42a..c96e6e549c0 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -64,8 +64,9 @@ async def validate_input(data): timed_out = False if not await async_wait_for_elk_to_sync(elk, VALIDATE_TIMEOUT): _LOGGER.error( - "Timed out after %d seconds while trying to sync with elkm1", + "Timed out after %d seconds while trying to sync with ElkM1 at %s", VALIDATE_TIMEOUT, + url, ) timed_out = True From 4e3414fc8aa65451e569866bbca16bc0ae78afbe Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 13 Apr 2020 17:03:24 -0700 Subject: [PATCH 11/12] Bumped version to 0.108.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 17af8a6e320..da6dd4bb24e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 108 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From d6e1bc3e75bec21b3a5289416bb694f20e7301a8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2020 19:08:37 -0500 Subject: [PATCH 12/12] Convert sense to use DataUpdateCoordinator for trends data (#34160) * Convert sense to use DataUpdateCoordinator for trends * remove unused * request update right away * clarify * call async refresh later * Update homeassistant/components/sense/__init__.py Co-Authored-By: Paulus Schoutsen * Update homeassistant/components/sense/__init__.py Co-Authored-By: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/components/sense/__init__.py | 18 +++- .../components/sense/binary_sensor.py | 16 ++-- homeassistant/components/sense/const.py | 1 + homeassistant/components/sense/sensor.py | 91 +++++++++---------- 4 files changed, 68 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 80e75bce400..f295b0d926c 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -17,6 +17,7 @@ from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( ACTIVE_UPDATE_RATE, @@ -27,6 +28,7 @@ from .const import ( SENSE_DEVICES_DATA, SENSE_DISCOVERED_DEVICES_DATA, SENSE_TIMEOUT_EXCEPTIONS, + SENSE_TRENDS_COORDINATOR, ) _LOGGER = logging.getLogger(__name__) @@ -111,9 +113,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except SENSE_TIMEOUT_EXCEPTIONS: raise ConfigEntryNotReady + trends_coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"Sense Trends {email}", + update_method=gateway.update_trend_data, + update_interval=timedelta(seconds=300), + ) + + # This can take longer than 60s and we already know + # sense is online since get_discovered_device_data was + # successful so we do it later. + hass.loop.create_task(trends_coordinator.async_request_refresh()) + hass.data[DOMAIN][entry.entry_id] = { SENSE_DATA: gateway, SENSE_DEVICES_DATA: sense_devices_data, + SENSE_TRENDS_COORDINATOR: trends_coordinator, SENSE_DISCOVERED_DEVICES_DATA: sense_discovered_devices, } @@ -122,7 +138,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.config_entries.async_forward_entry_setup(entry, component) ) - async def async_sense_update(now): + async def async_sense_update(_): """Retrieve latest state.""" try: await gateway.update_realtime() diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index 50fb3fd7dc7..af1b2f34b4a 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -71,7 +71,6 @@ class SenseDevice(BinarySensorDevice): self._unique_id = f"{sense_monitor_id}-{self._id}" self._icon = sense_to_mdi(device["icon"]) self._sense_devices_data = sense_devices_data - self._undo_dispatch_subscription = None self._state = None self._available = False @@ -117,17 +116,14 @@ class SenseDevice(BinarySensorDevice): async def async_added_to_hass(self): """Register callbacks.""" - self._undo_dispatch_subscription = async_dispatcher_connect( - self.hass, - f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}", - self._async_update_from_data, + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}", + self._async_update_from_data, + ) ) - async def async_will_remove_from_hass(self): - """Undo subscription.""" - if self._undo_dispatch_subscription: - self._undo_dispatch_subscription() - @callback def _async_update_from_data(self): """Get the latest data, update state. Must not do I/O.""" diff --git a/homeassistant/components/sense/const.py b/homeassistant/components/sense/const.py index 882c3c9d79f..cd1d2bfcf4a 100644 --- a/homeassistant/components/sense/const.py +++ b/homeassistant/components/sense/const.py @@ -12,6 +12,7 @@ SENSE_DATA = "sense_data" SENSE_DEVICE_UPDATE = "sense_devices_update" SENSE_DEVICES_DATA = "sense_devices_data" SENSE_DISCOVERED_DEVICES_DATA = "sense_discovered_devices" +SENSE_TRENDS_COORDINATOR = "sense_trends_coorindator" ACTIVE_NAME = "Energy" ACTIVE_TYPE = "active" diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 06cfb90d2b5..0c6bfd7ee1f 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -1,12 +1,10 @@ """Support for monitoring a Sense energy sensor.""" -from datetime import timedelta import logging from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, POWER_WATT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle from .const import ( ACTIVE_NAME, @@ -22,12 +20,9 @@ from .const import ( SENSE_DEVICE_UPDATE, SENSE_DEVICES_DATA, SENSE_DISCOVERED_DEVICES_DATA, - SENSE_TIMEOUT_EXCEPTIONS, + SENSE_TRENDS_COORDINATOR, ) -MIN_TIME_BETWEEN_DAILY_UPDATES = timedelta(seconds=300) - - _LOGGER = logging.getLogger(__name__) @@ -64,17 +59,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Sense sensor.""" data = hass.data[DOMAIN][config_entry.entry_id][SENSE_DATA] sense_devices_data = hass.data[DOMAIN][config_entry.entry_id][SENSE_DEVICES_DATA] + trends_coordinator = hass.data[DOMAIN][config_entry.entry_id][ + SENSE_TRENDS_COORDINATOR + ] - @Throttle(MIN_TIME_BETWEEN_DAILY_UPDATES) - async def update_trends(): - """Update the daily power usage.""" - await data.update_trend_data() + # Request only in case it takes longer + # than 60s + await trends_coordinator.async_request_refresh() sense_monitor_id = data.sense_monitor_id sense_devices = hass.data[DOMAIN][config_entry.entry_id][ SENSE_DISCOVERED_DEVICES_DATA ] - await data.update_trend_data() devices = [ SenseEnergyDevice(sense_devices_data, device, sense_monitor_id) @@ -108,8 +104,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): name, sensor_type, is_production, - update_trends, - var, + trends_coordinator, unique_id, ) ) @@ -140,7 +135,6 @@ class SenseActiveSensor(Entity): self._sensor_type = sensor_type self._is_production = is_production self._state = None - self._undo_dispatch_subscription = None @property def name(self): @@ -179,17 +173,14 @@ class SenseActiveSensor(Entity): async def async_added_to_hass(self): """Register callbacks.""" - self._undo_dispatch_subscription = async_dispatcher_connect( - self.hass, - f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}", - self._async_update_from_data, + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}", + self._async_update_from_data, + ) ) - async def async_will_remove_from_hass(self): - """Undo subscription.""" - if self._undo_dispatch_subscription: - self._undo_dispatch_subscription() - @callback def _async_update_from_data(self): """Update the sensor from the data. Must not do I/O.""" @@ -206,7 +197,7 @@ class SenseTrendsSensor(Entity): """Implementation of a Sense energy sensor.""" def __init__( - self, data, name, sensor_type, is_production, update_call, sensor_id, unique_id + self, data, name, sensor_type, is_production, trends_coordinator, unique_id, ): """Initialize the Sense sensor.""" name_type = PRODUCTION_NAME if is_production else CONSUMPTION_NAME @@ -215,10 +206,11 @@ class SenseTrendsSensor(Entity): self._available = False self._data = data self._sensor_type = sensor_type - self.update_sensor = update_call + self._coordinator = trends_coordinator self._is_production = is_production self._state = None self._unit_of_measurement = ENERGY_KILO_WATT_HOUR + self._had_any_update = False @property def name(self): @@ -228,12 +220,12 @@ class SenseTrendsSensor(Entity): @property def state(self): """Return the state of the sensor.""" - return self._state + return round(self._data.get_trend(self._sensor_type, self._is_production), 1) @property def available(self): - """Return the availability of the sensor.""" - return self._available + """Return if entity is available.""" + return self._had_any_update and self._coordinator.last_update_success @property def unit_of_measurement(self): @@ -250,18 +242,27 @@ class SenseTrendsSensor(Entity): """Return the unique id.""" return self._unique_id + @property + def should_poll(self): + """No need to poll. Coordinator notifies entity of updates.""" + return False + + @callback + def _async_update(self): + """Track if we had an update so we do not report zero data.""" + self._had_any_update = True + self.async_write_ha_state() + async def async_update(self): - """Get the latest data, update state.""" + """Update the entity. - try: - await self.update_sensor() - except SENSE_TIMEOUT_EXCEPTIONS: - _LOGGER.error("Timeout retrieving data") - return + Only used by the generic entity update service. + """ + await self._coordinator.async_request_refresh() - state = self._data.get_trend(self._sensor_type, self._is_production) - self._state = round(state, 1) - self._available = True + async def async_added_to_hass(self): + """When entity is added to hass.""" + self.async_on_remove(self._coordinator.async_add_listener(self._async_update)) class SenseEnergyDevice(Entity): @@ -276,7 +277,6 @@ class SenseEnergyDevice(Entity): self._unique_id = f"{sense_monitor_id}-{self._id}-{CONSUMPTION_ID}" self._icon = sense_to_mdi(device["icon"]) self._sense_devices_data = sense_devices_data - self._undo_dispatch_subscription = None self._state = None @property @@ -321,17 +321,14 @@ class SenseEnergyDevice(Entity): async def async_added_to_hass(self): """Register callbacks.""" - self._undo_dispatch_subscription = async_dispatcher_connect( - self.hass, - f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}", - self._async_update_from_data, + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}", + self._async_update_from_data, + ) ) - async def async_will_remove_from_hass(self): - """Undo subscription.""" - if self._undo_dispatch_subscription: - self._undo_dispatch_subscription() - @callback def _async_update_from_data(self): """Get the latest data, update state. Must not do I/O."""