mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Merge pull request #54853 from home-assistant/rc
This commit is contained in:
commit
14b74fbf71
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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"}],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user