Merge pull request #54853 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-08-18 14:28:51 -07:00 committed by GitHub
commit 14b74fbf71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 93 additions and 23 deletions

View File

@ -2,7 +2,7 @@
"domain": "bmw_connected_drive", "domain": "bmw_connected_drive",
"name": "BMW Connected Drive", "name": "BMW Connected Drive",
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"requirements": ["bimmer_connected==0.7.18"], "requirements": ["bimmer_connected==0.7.19"],
"codeowners": ["@gerard33", "@rikroe"], "codeowners": ["@gerard33", "@rikroe"],
"config_flow": true, "config_flow": true,
"iot_class": "cloud_polling" "iot_class": "cloud_polling"

View File

@ -134,10 +134,15 @@ def get_accessory(hass, driver, state, aid, config): # noqa: C901
and features & cover.SUPPORT_SET_POSITION and features & cover.SUPPORT_SET_POSITION
): ):
a_type = "Window" a_type = "Window"
elif features & (cover.SUPPORT_SET_POSITION | cover.SUPPORT_SET_TILT_POSITION): elif features & cover.SUPPORT_SET_POSITION:
a_type = "WindowCovering" a_type = "WindowCovering"
elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE):
a_type = "WindowCoveringBasic" a_type = "WindowCoveringBasic"
elif features & cover.SUPPORT_SET_TILT_POSITION:
# WindowCovering and WindowCoveringBasic both support tilt
# only WindowCovering can handle the covers that are missing
# SUPPORT_SET_POSITION, SUPPORT_OPEN, and SUPPORT_CLOSE
a_type = "WindowCovering"
elif state.domain == "fan": elif state.domain == "fan":
a_type = "Fan" a_type = "Fan"

View File

@ -3,7 +3,7 @@
"name": "Met Éireann", "name": "Met Éireann",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/met_eireann", "documentation": "https://www.home-assistant.io/integrations/met_eireann",
"requirements": ["pyMetEireann==0.2"], "requirements": ["pyMetEireann==2021.8.0"],
"codeowners": ["@DylanGore"], "codeowners": ["@DylanGore"],
"iot_class": "cloud_polling" "iot_class": "cloud_polling"
} }

View File

@ -28,9 +28,14 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import as_local, utc_from_timestamp
from .common import SmartDevices, async_discover_devices, get_static_devices from .common import (
SmartDevices,
async_discover_devices,
get_static_devices,
get_time_offset,
)
from .const import ( from .const import (
ATTR_CONFIG, ATTR_CONFIG,
ATTR_CURRENT_A, ATTR_CURRENT_A,
@ -156,7 +161,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for device in unavailable_devices: for device in unavailable_devices:
try: try:
device.get_sysinfo() await hass.async_add_executor_job(device.get_sysinfo)
except SmartDeviceException: except SmartDeviceException:
continue continue
_LOGGER.debug( _LOGGER.debug(
@ -170,7 +175,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for switch in switches: for switch in switches:
try: try:
await hass.async_add_executor_job(switch.get_sysinfo) info = await hass.async_add_executor_job(switch.get_sysinfo)
except SmartDeviceException: except SmartDeviceException:
_LOGGER.warning( _LOGGER.warning(
"Device at '%s' not reachable during setup, will retry later", "Device at '%s' not reachable during setup, will retry later",
@ -181,7 +186,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass_data[COORDINATORS][ hass_data[COORDINATORS][
switch.context or switch.mac switch.context or switch.mac
] = coordinator = SmartPlugDataUpdateCoordinator(hass, switch) ] = coordinator = SmartPlugDataUpdateCoordinator(hass, switch, info["alias"])
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if unavailable_devices: if unavailable_devices:
@ -217,16 +222,20 @@ class SmartPlugDataUpdateCoordinator(DataUpdateCoordinator):
self, self,
hass: HomeAssistant, hass: HomeAssistant,
smartplug: SmartPlug, smartplug: SmartPlug,
alias: str,
) -> None: ) -> None:
"""Initialize DataUpdateCoordinator to gather data for specific SmartPlug.""" """Initialize DataUpdateCoordinator to gather data for specific SmartPlug."""
self.smartplug = smartplug self.smartplug = smartplug
update_interval = timedelta(seconds=30) update_interval = timedelta(seconds=30)
super().__init__( super().__init__(
hass, _LOGGER, name=smartplug.alias, update_interval=update_interval hass,
_LOGGER,
name=alias,
update_interval=update_interval,
) )
async def _async_update_data(self) -> dict: def _update_data(self) -> dict:
"""Fetch all device and sensor data from api.""" """Fetch all device and sensor data from api."""
try: try:
info = self.smartplug.sys_info info = self.smartplug.sys_info
@ -239,9 +248,7 @@ class SmartPlugDataUpdateCoordinator(DataUpdateCoordinator):
if self.smartplug.context is None: if self.smartplug.context is None:
data[CONF_ALIAS] = info["alias"] data[CONF_ALIAS] = info["alias"]
data[CONF_DEVICE_ID] = info["mac"] data[CONF_DEVICE_ID] = info["mac"]
data[CONF_STATE] = ( data[CONF_STATE] = bool(info["relay_state"])
self.smartplug.state == self.smartplug.SWITCH_STATE_ON
)
else: else:
plug_from_context = next( plug_from_context = next(
c c
@ -251,7 +258,9 @@ class SmartPlugDataUpdateCoordinator(DataUpdateCoordinator):
data[CONF_ALIAS] = plug_from_context["alias"] data[CONF_ALIAS] = plug_from_context["alias"]
data[CONF_DEVICE_ID] = self.smartplug.context data[CONF_DEVICE_ID] = self.smartplug.context
data[CONF_STATE] = plug_from_context["state"] == 1 data[CONF_STATE] = plug_from_context["state"] == 1
if self.smartplug.has_emeter:
# Check if the device has emeter
if "ENE" in info["feature"]:
emeter_readings = self.smartplug.get_emeter_realtime() emeter_readings = self.smartplug.get_emeter_realtime()
data[CONF_EMETER_PARAMS] = { data[CONF_EMETER_PARAMS] = {
ATTR_CURRENT_POWER_W: round(float(emeter_readings["power"]), 2), ATTR_CURRENT_POWER_W: round(float(emeter_readings["power"]), 2),
@ -261,9 +270,16 @@ class SmartPlugDataUpdateCoordinator(DataUpdateCoordinator):
ATTR_LAST_RESET: {ATTR_TOTAL_ENERGY_KWH: utc_from_timestamp(0)}, ATTR_LAST_RESET: {ATTR_TOTAL_ENERGY_KWH: utc_from_timestamp(0)},
} }
emeter_statics = self.smartplug.get_emeter_daily() emeter_statics = self.smartplug.get_emeter_daily()
last_reset = datetime.now() - get_time_offset(self.smartplug)
last_reset_local = as_local(last_reset.replace(second=0, microsecond=0))
_LOGGER.debug(
"%s last reset time as local to server is %s",
self.smartplug.alias,
last_reset_local.strftime("%Y/%m/%d %H:%M:%S"),
)
data[CONF_EMETER_PARAMS][ATTR_LAST_RESET][ data[CONF_EMETER_PARAMS][ATTR_LAST_RESET][
ATTR_TODAY_ENERGY_KWH ATTR_TODAY_ENERGY_KWH
] = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) ] = last_reset_local
if emeter_statics.get(int(time.strftime("%e"))): if emeter_statics.get(int(time.strftime("%e"))):
data[CONF_EMETER_PARAMS][ATTR_TODAY_ENERGY_KWH] = round( data[CONF_EMETER_PARAMS][ATTR_TODAY_ENERGY_KWH] = round(
float(emeter_statics[int(time.strftime("%e"))]), 3 float(emeter_statics[int(time.strftime("%e"))]), 3
@ -276,3 +292,7 @@ class SmartPlugDataUpdateCoordinator(DataUpdateCoordinator):
self.name = data[CONF_ALIAS] self.name = data[CONF_ALIAS]
return data return data
async def _async_update_data(self) -> dict:
"""Fetch all device and sensor data from api."""
return await self.hass.async_add_executor_job(self._update_data)

View File

@ -1,6 +1,7 @@
"""Common code for tplink.""" """Common code for tplink."""
from __future__ import annotations from __future__ import annotations
from datetime import timedelta
import logging import logging
from typing import Callable from typing import Callable
@ -184,3 +185,16 @@ def add_available_devices(
hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] = devices_unavailable hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] = devices_unavailable
return entities_ready return entities_ready
def get_time_offset(device: SmartDevice) -> timedelta:
"""Get the time offset since last device reset (local midnight)."""
device_time = device.time.replace(microsecond=0)
offset = device_time - device_time.replace(hour=0, minute=0, second=0)
_LOGGER.debug(
"%s local time is %s, offset from midnight is %s",
device.alias,
device_time.strftime("%Y/%m/%d %H:%M:%S"),
str(offset),
)
return offset

View File

@ -1,6 +1,7 @@
"""Support for TPLink HS100/HS110/HS200 smart switch energy sensors.""" """Support for TPLink HS100/HS110/HS200 smart switch energy sensors."""
from __future__ import annotations from __future__ import annotations
from datetime import datetime
from typing import Any, Final from typing import Any, Final
from pyHS100 import SmartPlug from pyHS100 import SmartPlug
@ -156,3 +157,10 @@ class SmartPlugSensor(CoordinatorEntity, SensorEntity):
"connections": {(dr.CONNECTION_NETWORK_MAC, self.data[CONF_MAC])}, "connections": {(dr.CONNECTION_NETWORK_MAC, self.data[CONF_MAC])},
"sw_version": self.data[CONF_SW_VERSION], "sw_version": self.data[CONF_SW_VERSION],
} }
@property
def last_reset(self) -> datetime | None:
"""Return the last reset time for emeter."""
return self.data[CONF_EMETER_PARAMS][ATTR_LAST_RESET].get(
self.entity_description.key
)

View File

@ -46,7 +46,7 @@ def _async_setup_entities(devices, async_add_entities):
for dev in devices: for dev in devices:
if DEV_TYPE_TO_HA.get(dev.device_type) in ("walldimmer", "bulb-dimmable"): if DEV_TYPE_TO_HA.get(dev.device_type) in ("walldimmer", "bulb-dimmable"):
entities.append(VeSyncDimmableLightHA(dev)) entities.append(VeSyncDimmableLightHA(dev))
elif DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white"): elif DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white",):
entities.append(VeSyncTunableWhiteLightHA(dev)) entities.append(VeSyncTunableWhiteLightHA(dev))
else: else:
_LOGGER.debug( _LOGGER.debug(
@ -82,7 +82,7 @@ class VeSyncBaseLight(VeSyncDevice, LightEntity):
"""Turn the device on.""" """Turn the device on."""
attribute_adjustment_only = False attribute_adjustment_only = False
# set white temperature # set white temperature
if self.color_mode in (COLOR_MODE_COLOR_TEMP) and ATTR_COLOR_TEMP in kwargs: if self.color_mode in (COLOR_MODE_COLOR_TEMP,) and ATTR_COLOR_TEMP in kwargs:
# get white temperature from HA data # get white temperature from HA data
color_temp = int(kwargs[ATTR_COLOR_TEMP]) color_temp = int(kwargs[ATTR_COLOR_TEMP])
# ensure value between min-max supported Mireds # ensure value between min-max supported Mireds

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021 MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 8 MINOR_VERSION: Final = 8
PATCH_VERSION: Final = "7" PATCH_VERSION: Final = "8"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)

View File

@ -365,7 +365,7 @@ beautifulsoup4==4.9.3
bellows==0.26.0 bellows==0.26.0
# homeassistant.components.bmw_connected_drive # homeassistant.components.bmw_connected_drive
bimmer_connected==0.7.18 bimmer_connected==0.7.19
# homeassistant.components.bizkaibus # homeassistant.components.bizkaibus
bizkaibus==0.1.1 bizkaibus==0.1.1
@ -1272,7 +1272,7 @@ pyControl4==0.0.6
pyHS100==0.3.5.2 pyHS100==0.3.5.2
# homeassistant.components.met_eireann # homeassistant.components.met_eireann
pyMetEireann==0.2 pyMetEireann==2021.8.0
# homeassistant.components.met # homeassistant.components.met
# homeassistant.components.norway_air # homeassistant.components.norway_air

View File

@ -220,7 +220,7 @@ base36==0.1.1
bellows==0.26.0 bellows==0.26.0
# homeassistant.components.bmw_connected_drive # homeassistant.components.bmw_connected_drive
bimmer_connected==0.7.18 bimmer_connected==0.7.19
# homeassistant.components.blebox # homeassistant.components.blebox
blebox_uniapi==1.3.3 blebox_uniapi==1.3.3
@ -714,7 +714,7 @@ pyControl4==0.0.6
pyHS100==0.3.5.2 pyHS100==0.3.5.2
# homeassistant.components.met_eireann # homeassistant.components.met_eireann
pyMetEireann==0.2 pyMetEireann==2021.8.0
# homeassistant.components.met # homeassistant.components.met
# homeassistant.components.norway_air # homeassistant.components.norway_air

View File

@ -149,6 +149,18 @@ def test_types(type_name, entity_id, state, attrs, config):
"open", "open",
{ATTR_SUPPORTED_FEATURES: (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE)}, {ATTR_SUPPORTED_FEATURES: (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE)},
), ),
(
"WindowCoveringBasic",
"cover.open_window",
"open",
{
ATTR_SUPPORTED_FEATURES: (
cover.SUPPORT_OPEN
| cover.SUPPORT_CLOSE
| cover.SUPPORT_SET_TILT_POSITION
)
},
),
], ],
) )
def test_type_covers(type_name, entity_id, state, attrs): def test_type_covers(type_name, entity_id, state, attrs):

View File

@ -1,6 +1,7 @@
"""Tests for the TP-Link component.""" """Tests for the TP-Link component."""
from __future__ import annotations from __future__ import annotations
from datetime import datetime
import time import time
from typing import Any from typing import Any
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@ -222,6 +223,11 @@ async def test_platforms_are_initialized(hass: HomeAssistant):
), patch( ), patch(
"homeassistant.components.tplink.common.SmartPlug.is_dimmable", "homeassistant.components.tplink.common.SmartPlug.is_dimmable",
False, False,
), patch(
"homeassistant.components.tplink.get_time_offset",
return_value=(
datetime.now() - datetime.now().replace(hour=0, minute=0, second=0)
),
): ):
light = SmartBulb("123.123.123.123") light = SmartBulb("123.123.123.123")
@ -412,7 +418,12 @@ async def test_unload(hass, platform):
), patch( ), patch(
f"homeassistant.components.tplink.{platform}.async_setup_entry", f"homeassistant.components.tplink.{platform}.async_setup_entry",
return_value=mock_coro(True), return_value=mock_coro(True),
) as async_setup_entry: ) as async_setup_entry, patch(
"homeassistant.components.tplink.get_time_offset",
return_value=(
datetime.now() - datetime.now().replace(hour=0, minute=0, second=0)
),
):
config = { config = {
tplink.DOMAIN: { tplink.DOMAIN: {
platform: [{CONF_HOST: "123.123.123.123"}], platform: [{CONF_HOST: "123.123.123.123"}],