Skip to content

Commit

Permalink
Merge pull request home-assistant#57294 from home-assistant/rc
Browse files Browse the repository at this point in the history
  • Loading branch information
balloob authored Oct 8, 2021
2 parents 32889db + 387249b commit 5bb4bc3
Show file tree
Hide file tree
Showing 25 changed files with 403 additions and 78 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": [
"home-assistant-frontend==20211006.0"
"home-assistant-frontend==20211007.0"
],
"dependencies": [
"api",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/homekit/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "HomeKit",
"documentation": "https://www.home-assistant.io/integrations/homekit",
"requirements": [
"HAP-python==4.2.1",
"HAP-python==4.3.0",
"fnvhash==0.1.0",
"PyQRCode==1.2.1",
"base36==0.1.1"
Expand Down
25 changes: 25 additions & 0 deletions homeassistant/components/input_datetime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,30 @@ def has_date_or_time(conf):
raise vol.Invalid("Entity needs at least a date or a time")


def valid_initial(conf):
"""Check the initial value is valid."""
initial = conf.get(CONF_INITIAL)
if not initial:
return conf

if conf[CONF_HAS_DATE] and conf[CONF_HAS_TIME]:
parsed_value = dt_util.parse_datetime(initial)
if parsed_value is not None:
return conf
raise vol.Invalid(f"Initial value '{initial}' can't be parsed as a datetime")

if conf[CONF_HAS_DATE]:
parsed_value = dt_util.parse_date(initial)
if parsed_value is not None:
return conf
raise vol.Invalid(f"Initial value '{initial}' can't be parsed as a date")

parsed_value = dt_util.parse_time(initial)
if parsed_value is not None:
return conf
raise vol.Invalid(f"Initial value '{initial}' can't be parsed as a time")


CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: cv.schema_with_slug_keys(
Expand All @@ -93,6 +117,7 @@ def has_date_or_time(conf):
vol.Optional(CONF_INITIAL): cv.string,
},
has_date_or_time,
valid_initial,
)
)
},
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mill/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "mill",
"name": "Mill",
"documentation": "https://www.home-assistant.io/integrations/mill",
"requirements": ["millheater==0.6.0"],
"requirements": ["millheater==0.6.1"],
"codeowners": ["@danielhiversen"],
"config_flow": true,
"iot_class": "cloud_polling"
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/netgear/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import voluptuous as vol

from homeassistant.components.device_tracker import (
DOMAIN as DEVICE_TRACKER_DOMAIN,
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
SOURCE_TYPE_ROUTER,
)
Expand Down Expand Up @@ -50,7 +51,7 @@ async def async_get_scanner(hass, config):
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
data=config[DEVICE_TRACKER_DOMAIN],
)
)

Expand Down
33 changes: 23 additions & 10 deletions homeassistant/components/powerwall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry_id = entry.entry_id

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN].setdefault(entry_id, {})
http_session = requests.Session()
ip_address = entry.data[CONF_IP_ADDRESS]

password = entry.data.get(CONF_PASSWORD)
power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session)
power_wall = Powerwall(ip_address, http_session=http_session)
try:
powerwall_data = await hass.async_add_executor_job(
_login_and_fetch_base_info, power_wall, password
Expand All @@ -115,13 +115,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await _migrate_old_unique_ids(hass, entry_id, powerwall_data)
login_failed_count = 0

runtime_data = hass.data[DOMAIN][entry.entry_id] = {
POWERWALL_API_CHANGED: False,
POWERWALL_HTTP_SESSION: http_session,
}

def _recreate_powerwall_login():
nonlocal http_session
nonlocal power_wall
http_session.close()
http_session = requests.Session()
power_wall = Powerwall(ip_address, http_session=http_session)
runtime_data[POWERWALL_OBJECT] = power_wall
runtime_data[POWERWALL_HTTP_SESSION] = http_session
power_wall.login("", password)

async def async_update_data():
"""Fetch data from API endpoint."""
# Check if we had an error before
nonlocal login_failed_count
_LOGGER.debug("Checking if update failed")
if hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]:
return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data
if runtime_data[POWERWALL_API_CHANGED]:
return runtime_data[POWERWALL_COORDINATOR].data

_LOGGER.debug("Updating data")
try:
Expand All @@ -130,9 +145,9 @@ async def async_update_data():
if password is None:
raise ConfigEntryAuthFailed from err

# If the session expired, relogin, and try again
# If the session expired, recreate, relogin, and try again
try:
await hass.async_add_executor_job(power_wall.login, "", password)
await hass.async_add_executor_job(_recreate_powerwall_login)
return await _async_update_powerwall_data(hass, entry, power_wall)
except AccessDeniedError as ex:
login_failed_count += 1
Expand All @@ -153,13 +168,11 @@ async def async_update_data():
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)

hass.data[DOMAIN][entry.entry_id] = powerwall_data
hass.data[DOMAIN][entry.entry_id].update(
runtime_data.update(
{
**powerwall_data,
POWERWALL_OBJECT: power_wall,
POWERWALL_COORDINATOR: coordinator,
POWERWALL_HTTP_SESSION: http_session,
POWERWALL_API_CHANGED: False,
}
)

Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/recorder/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext import baked
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.sql.expression import true

from homeassistant.const import (
PRESSURE_PA,
Expand Down Expand Up @@ -396,9 +397,9 @@ def _meta(metas: list, wanted_metadata_id: str) -> StatisticMetaData | None:
StatisticsMeta.statistic_id.in_(bindparam("statistic_ids"))
)
if statistic_type == "mean":
baked_query += lambda q: q.filter(StatisticsMeta.has_mean.isnot(False))
baked_query += lambda q: q.filter(StatisticsMeta.has_mean == true())
elif statistic_type == "sum":
baked_query += lambda q: q.filter(StatisticsMeta.has_sum.isnot(False))
baked_query += lambda q: q.filter(StatisticsMeta.has_sum == true())
result = execute(baked_query(session).params(statistic_ids=statistic_ids))
if not result:
return {}
Expand Down
18 changes: 17 additions & 1 deletion homeassistant/components/tplink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import asyncio
from datetime import timedelta
from typing import Any

from kasa import SmartDevice, SmartDeviceException
Expand All @@ -11,9 +12,15 @@
from homeassistant import config_entries
from homeassistant.components import network
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.const import (
CONF_HOST,
CONF_MAC,
CONF_NAME,
EVENT_HOMEASSISTANT_STARTED,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType

from .const import (
Expand All @@ -32,6 +39,8 @@
async_migrate_yaml_entries,
)

DISCOVERY_INTERVAL = timedelta(minutes=15)

TPLINK_HOST_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})

CONFIG_SCHEMA = vol.Schema(
Expand Down Expand Up @@ -118,6 +127,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if discovered_devices:
async_trigger_discovery(hass, discovered_devices)

async def _async_discovery(*_: Any) -> None:
if discovered := await async_discover_devices(hass):
async_trigger_discovery(hass, discovered)

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_discovery)
async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL)

return True


Expand Down
18 changes: 15 additions & 3 deletions homeassistant/components/tplink/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,20 @@ def __init__(
@async_refresh_after
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
transition = kwargs.get(ATTR_TRANSITION)
if (transition := kwargs.get(ATTR_TRANSITION)) is not None:
transition = int(transition * 1_000)

if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None:
brightness = round((brightness * 100.0) / 255.0)

if self.device.is_dimmer and transition is None:
# This is a stopgap solution for inconsistent set_brightness handling
# in the upstream library, see #57265.
# This should be removed when the upstream has fixed the issue.
# The device logic is to change the settings without turning it on
# except when transition is defined, so we leverage that here for now.
transition = 1

# Handle turning to temp mode
if ATTR_COLOR_TEMP in kwargs:
color_tmp = mired_to_kelvin(int(kwargs[ATTR_COLOR_TEMP]))
Expand All @@ -92,7 +102,9 @@ async def async_turn_on(self, **kwargs: Any) -> None:
@async_refresh_after
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self.device.turn_off(transition=kwargs.get(ATTR_TRANSITION))
if (transition := kwargs.get(ATTR_TRANSITION)) is not None:
transition = int(transition * 1_000)
await self.device.turn_off(transition=transition)

@property
def min_mireds(self) -> int:
Expand Down Expand Up @@ -145,7 +157,7 @@ def supported_color_modes(self) -> set[str] | None:
def color_mode(self) -> str | None:
"""Return the active color mode."""
if self.device.is_color:
if self.device.color_temp:
if self.device.is_variable_color_temp and self.device.color_temp:
return COLOR_MODE_COLOR_TEMP
return COLOR_MODE_HS
if self.device.is_variable_color_temp:
Expand Down
8 changes: 6 additions & 2 deletions homeassistant/components/xiaomi_miio/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,14 @@ def __init__(self, name, device, entry, unique_id, coordinator, description):
@callback
def _handle_coordinator_update(self):
"""Fetch state from the device."""
self._current_led_brightness = self._extract_value_from_attribute(
led_brightness = self._extract_value_from_attribute(
self.coordinator.data, self.entity_description.key
)
self.async_write_ha_state()
# Sometimes (quite rarely) the device returns None as the LED brightness so we
# check that the value is not None before updating the state.
if led_brightness:
self._current_led_brightness = led_brightness
self.async_write_ha_state()

@property
def current_option(self):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/yeelight/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "yeelight",
"name": "Yeelight",
"documentation": "https://www.home-assistant.io/integrations/yeelight",
"requirements": ["yeelight==0.7.6", "async-upnp-client==0.22.5"],
"requirements": ["yeelight==0.7.7", "async-upnp-client==0.22.5"],
"codeowners": ["@rytilahti", "@zewelor", "@shenxn", "@starkillerOG"],
"config_flow": true,
"dependencies": ["network"],
Expand Down
63 changes: 49 additions & 14 deletions homeassistant/components/zwave_js/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ async def async_poll_value(self, service: ServiceCall) -> None:
async def async_set_value(self, service: ServiceCall) -> None:
"""Set a value on a node."""
# pylint: disable=no-self-use
nodes = service.data[const.ATTR_NODES]
nodes: set[ZwaveNode] = service.data[const.ATTR_NODES]
command_class = service.data[const.ATTR_COMMAND_CLASS]
property_ = service.data[const.ATTR_PROPERTY]
property_key = service.data.get(const.ATTR_PROPERTY_KEY)
Expand All @@ -418,15 +418,27 @@ async def async_set_value(self, service: ServiceCall) -> None:
options = service.data.get(const.ATTR_OPTIONS)

for node in nodes:
value_id = get_value_id(
node,
command_class,
property_,
endpoint=endpoint,
property_key=property_key,
)
# If value has a string type but the new value is not a string, we need to
# convert it to one. We use new variable `new_value_` to convert the data
# so we can preserve the original `new_value` for every node.
if (
value_id in node.values
and node.values[value_id].metadata.type == "string"
and not isinstance(new_value, str)
):
new_value_ = str(new_value)
else:
new_value_ = new_value
success = await node.async_set_value(
get_value_id(
node,
command_class,
property_,
endpoint=endpoint,
property_key=property_key,
),
new_value,
value_id,
new_value_,
options=options,
wait_for_result=wait_for_result,
)
Expand All @@ -452,24 +464,47 @@ async def async_multicast_set_value(self, service: ServiceCall) -> None:
await self.async_set_value(service)
return

command_class = service.data[const.ATTR_COMMAND_CLASS]
property_ = service.data[const.ATTR_PROPERTY]
property_key = service.data.get(const.ATTR_PROPERTY_KEY)
endpoint = service.data.get(const.ATTR_ENDPOINT)

value = {
"commandClass": service.data[const.ATTR_COMMAND_CLASS],
"property": service.data[const.ATTR_PROPERTY],
"propertyKey": service.data.get(const.ATTR_PROPERTY_KEY),
"endpoint": service.data.get(const.ATTR_ENDPOINT),
"commandClass": command_class,
"property": property_,
"propertyKey": property_key,
"endpoint": endpoint,
}
new_value = service.data[const.ATTR_VALUE]

# If there are no nodes, we can assume there is only one config entry due to
# schema validation and can use that to get the client, otherwise we can just
# get the client from the node.
client: ZwaveClient = None
first_node = next((node for node in nodes), None)
first_node: ZwaveNode = next((node for node in nodes), None)
if first_node:
client = first_node.client
else:
entry_id = self._hass.config_entries.async_entries(const.DOMAIN)[0].entry_id
client = self._hass.data[const.DOMAIN][entry_id][const.DATA_CLIENT]
first_node = next(
node
for node in client.driver.controller.nodes.values()
if get_value_id(node, command_class, property_, endpoint, property_key)
in node.values
)

# If value has a string type but the new value is not a string, we need to
# convert it to one
value_id = get_value_id(
first_node, command_class, property_, endpoint, property_key
)
if (
value_id in first_node.values
and first_node.values[value_id].metadata.type == "string"
and not isinstance(new_value, str)
):
new_value = str(new_value)

success = await async_multicast_set_value(
client=client,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 10
PATCH_VERSION: Final = "0"
PATCH_VERSION: Final = "1"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
Expand Down
Loading

0 comments on commit 5bb4bc3

Please sign in to comment.