Merge pull request #54117 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-08-05 23:18:48 -07:00 committed by GitHub
commit a07048aacf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 145 additions and 44 deletions

View File

@ -71,9 +71,6 @@ from homeassistant.loader import bind_hass
from homeassistant.util.dt import parse_datetime
from .config import AutomationConfig, async_validate_config_item
# Not used except by packages to check config structure
from .config import PLATFORM_SCHEMA # noqa: F401
from .const import (
CONF_ACTION,
CONF_INITIAL_STATE,

View File

@ -37,6 +37,8 @@ from .helpers import async_get_blueprints
# mypy: allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
PACKAGE_MERGE_HINT = "list"
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.All(

View File

@ -81,12 +81,12 @@ def _retrieve_max_kb_s_received_state(status: FritzStatus, last_value: str) -> f
def _retrieve_gb_sent_state(status: FritzStatus, last_value: str) -> float:
"""Return upload total data."""
return round(status.bytes_sent * 8 / 1000 / 1000 / 1000, 1) # type: ignore[no-any-return]
return round(status.bytes_sent / 1000 / 1000 / 1000, 1) # type: ignore[no-any-return]
def _retrieve_gb_received_state(status: FritzStatus, last_value: str) -> float:
"""Return download total data."""
return round(status.bytes_received * 8 / 1000 / 1000 / 1000, 1) # type: ignore[no-any-return]
return round(status.bytes_received / 1000 / 1000 / 1000, 1) # type: ignore[no-any-return]
class SensorData(TypedDict, total=False):

View File

@ -13,7 +13,6 @@ from fritzconnection.core.exceptions import (
FritzSecurityError,
FritzServiceError,
)
import slugify as unicode_slug
import xmltodict
from homeassistant.components.network import async_get_source_ip
@ -248,10 +247,18 @@ def wifi_entities_list(
)
if network_info:
ssid = network_info["NewSSID"]
if unicode_slug.slugify(ssid, lowercase=False) in networks.values():
_LOGGER.debug("SSID from device: <%s>", ssid)
if (
slugify(
ssid,
)
in [slugify(v) for v in networks.values()]
):
_LOGGER.debug("SSID duplicated, adding suffix")
networks[i] = f'{ssid} {std_table[network_info["NewStandard"]]}'
else:
networks[i] = ssid
_LOGGER.debug("SSID normalized: <%s>", networks[i])
return [
FritzBoxWifiSwitch(fritzbox_tools, device_friendly_name, net, network_name)

View File

@ -228,17 +228,17 @@ class HomeAccessory(Accessory):
self.config = config or {}
domain = split_entity_id(entity_id)[0].replace("_", " ")
if ATTR_MANUFACTURER in self.config:
if self.config.get(ATTR_MANUFACTURER) is not None:
manufacturer = self.config[ATTR_MANUFACTURER]
elif ATTR_INTEGRATION in self.config:
elif self.config.get(ATTR_INTEGRATION) is not None:
manufacturer = self.config[ATTR_INTEGRATION].replace("_", " ").title()
else:
manufacturer = f"{MANUFACTURER} {domain}".title()
if ATTR_MODEL in self.config:
if self.config.get(ATTR_MODEL) is not None:
model = self.config[ATTR_MODEL]
else:
model = domain.title()
if ATTR_SOFTWARE_VERSION in self.config:
if self.config.get(ATTR_SOFTWARE_VERSION) is not None:
sw_version = format_sw_version(self.config[ATTR_SOFTWARE_VERSION])
else:
sw_version = __version__

View File

@ -57,6 +57,8 @@ VALVE_TYPE = {
ACTIVATE_ONLY_SWITCH_DOMAINS = {"scene", "script"}
ACTIVATE_ONLY_RESET_SECONDS = 10
@TYPES.register("Outlet")
class Outlet(HomeAccessory):
@ -141,7 +143,7 @@ class Switch(HomeAccessory):
self.async_call_service(self._domain, service, params)
if self.activate_only:
async_call_later(self.hass, 1, self.reset_switch)
async_call_later(self.hass, ACTIVATE_ONLY_RESET_SECONDS, self.reset_switch)
@callback
def async_update_state(self, new_state):

View File

@ -3,7 +3,7 @@
"name": "Litter-Robot",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
"requirements": ["pylitterbot==2021.7.2"],
"requirements": ["pylitterbot==2021.8.0"],
"codeowners": ["@natekspencer"],
"iot_class": "cloud_polling"
}

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_TYPE,
CONF_USERNAME,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
ENERGY_KILO_WATT_HOUR,
@ -204,6 +205,8 @@ class SAJsensor(SensorEntity):
"""Return the device class the sensor belongs to."""
if self.unit_of_measurement == POWER_WATT:
return DEVICE_CLASS_POWER
if self.unit_of_measurement == ENERGY_KILO_WATT_HOUR:
return DEVICE_CLASS_ENERGY
if (
self.unit_of_measurement == TEMP_CELSIUS
or self._sensor.unit == TEMP_FAHRENHEIT

View File

@ -39,6 +39,8 @@ from .const import (
)
from .helpers import async_get_blueprints
PACKAGE_MERGE_HINT = "dict"
SCRIPT_ENTITY_SCHEMA = make_script_schema(
{
vol.Optional(CONF_ALIAS): cv.string,

View File

@ -285,7 +285,6 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
self._unit: None | str | Callable[[dict], str] = unit
self._unique_id: str = f"{super().unique_id}-{self.attribute}"
self._name = get_entity_name(wrapper.device, block, self.description.name)
self._last_value: str | None = None
@property
def unique_id(self) -> str:

View File

@ -1,9 +1,12 @@
"""Sensor for Shelly."""
from __future__ import annotations
from datetime import datetime
from datetime import timedelta
import logging
from typing import Final, cast
import aioshelly
from homeassistant.components import sensor
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
@ -23,6 +26,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util import dt
from . import ShellyDeviceWrapper
from .const import LAST_RESET_NEVER, LAST_RESET_UPTIME, SHAIR_MAX_WORK_HOURS
from .entity import (
BlockAttributeDescription,
@ -35,6 +39,8 @@ from .entity import (
)
from .utils import get_device_uptime, temperature_unit
_LOGGER: Final = logging.getLogger(__name__)
SENSORS: Final = {
("device", "battery"): BlockAttributeDescription(
name="Battery",
@ -255,9 +261,39 @@ async def async_setup_entry(
class ShellySensor(ShellyBlockAttributeEntity, SensorEntity):
"""Represent a shelly sensor."""
def __init__(
self,
wrapper: ShellyDeviceWrapper,
block: aioshelly.Block,
attribute: str,
description: BlockAttributeDescription,
) -> None:
"""Initialize sensor."""
super().__init__(wrapper, block, attribute, description)
self._last_value: float | None = None
if description.last_reset == LAST_RESET_NEVER:
self._attr_last_reset = dt.utc_from_timestamp(0)
elif description.last_reset == LAST_RESET_UPTIME:
self._attr_last_reset = (
dt.utcnow() - timedelta(seconds=wrapper.device.status["uptime"])
).replace(second=0, microsecond=0)
@property
def state(self) -> StateType:
"""Return value of sensor."""
if (
self.description.last_reset == LAST_RESET_UPTIME
and self.attribute_value is not None
):
value = cast(float, self.attribute_value)
if self._last_value and self._last_value > value:
self._attr_last_reset = dt.utcnow().replace(second=0, microsecond=0)
_LOGGER.info("Energy reset detected for entity %s", self.name)
self._last_value = value
return self.attribute_value
@property
@ -265,20 +301,6 @@ class ShellySensor(ShellyBlockAttributeEntity, SensorEntity):
"""State class of sensor."""
return self.description.state_class
@property
def last_reset(self) -> datetime | None:
"""State class of sensor."""
if self.description.last_reset == LAST_RESET_UPTIME:
self._last_value = get_device_uptime(
self.wrapper.device.status, self._last_value
)
return dt.parse_datetime(self._last_value)
if self.description.last_reset == LAST_RESET_NEVER:
return dt.utc_from_timestamp(0)
return None
@property
def unit_of_measurement(self) -> str | None:
"""Return unit of sensor."""

View File

@ -13,6 +13,8 @@ from homeassistant.helpers.trigger import async_validate_trigger_config
from . import binary_sensor as binary_sensor_platform, sensor as sensor_platform
from .const import CONF_TRIGGER, DOMAIN
PACKAGE_MERGE_HINT = "list"
CONFIG_SECTION_SCHEMA = vol.Schema(
{
vol.Optional(CONF_UNIQUE_ID): cv.string,

View File

@ -2,7 +2,7 @@
"domain": "zeroconf",
"name": "Zero-configuration networking (zeroconf)",
"documentation": "https://www.home-assistant.io/integrations/zeroconf",
"requirements": ["zeroconf==0.33.2"],
"requirements": ["zeroconf==0.33.3"],
"dependencies": ["network", "api"],
"codeowners": ["@bdraco"],
"quality_scale": "internal",

View File

@ -9,11 +9,11 @@
"pyserial-asyncio==0.5",
"zha-quirks==0.0.59",
"zigpy-cc==0.5.2",
"zigpy-deconz==0.12.0",
"zigpy-deconz==0.12.1",
"zigpy==0.36.1",
"zigpy-xbee==0.13.0",
"zigpy-zigate==0.7.3",
"zigpy-znp==0.5.2"
"zigpy-znp==0.5.3"
],
"codeowners": ["@dmulcahey", "@adminiuga"],
"zeroconf": [

View File

@ -723,7 +723,22 @@ async def merge_packages_config(
_log_pkg_error(pack_name, comp_name, config, str(ex))
continue
merge_list = hasattr(component, "PLATFORM_SCHEMA")
try:
config_platform: ModuleType | None = integration.get_platform("config")
# Test if config platform has a config validator
if not hasattr(config_platform, "async_validate_config"):
config_platform = None
except ImportError:
config_platform = None
merge_list = False
# If integration has a custom config validator, it needs to provide a hint.
if config_platform is not None:
merge_list = config_platform.PACKAGE_MERGE_HINT == "list" # type: ignore[attr-defined]
if not merge_list:
merge_list = hasattr(component, "PLATFORM_SCHEMA")
if not merge_list and hasattr(component, "CONFIG_SCHEMA"):
merge_list = _identify_config_schema(component) == "list"

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 8
PATCH_VERSION: Final = "1"
PATCH_VERSION: Final = "2"
__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)

View File

@ -33,7 +33,7 @@ sqlalchemy==1.4.17
voluptuous-serialize==2.4.0
voluptuous==0.12.1
yarl==1.6.3
zeroconf==0.33.2
zeroconf==0.33.3
pycryptodome>=3.6.6

View File

@ -1562,7 +1562,7 @@ pylibrespot-java==0.1.0
pylitejet==0.3.0
# homeassistant.components.litterrobot
pylitterbot==2021.7.2
pylitterbot==2021.8.0
# homeassistant.components.loopenergy
pyloopenergy==0.2.1
@ -2439,7 +2439,7 @@ zeep[async]==4.0.0
zengge==0.2
# homeassistant.components.zeroconf
zeroconf==0.33.2
zeroconf==0.33.3
# homeassistant.components.zha
zha-quirks==0.0.59
@ -2454,7 +2454,7 @@ ziggo-mediabox-xl==1.1.0
zigpy-cc==0.5.2
# homeassistant.components.zha
zigpy-deconz==0.12.0
zigpy-deconz==0.12.1
# homeassistant.components.zha
zigpy-xbee==0.13.0
@ -2463,7 +2463,7 @@ zigpy-xbee==0.13.0
zigpy-zigate==0.7.3
# homeassistant.components.zha
zigpy-znp==0.5.2
zigpy-znp==0.5.3
# homeassistant.components.zha
zigpy==0.36.1

View File

@ -884,7 +884,7 @@ pylibrespot-java==0.1.0
pylitejet==0.3.0
# homeassistant.components.litterrobot
pylitterbot==2021.7.2
pylitterbot==2021.8.0
# homeassistant.components.lutron_caseta
pylutron-caseta==0.11.0
@ -1341,7 +1341,7 @@ youless-api==0.10
zeep[async]==4.0.0
# homeassistant.components.zeroconf
zeroconf==0.33.2
zeroconf==0.33.3
# homeassistant.components.zha
zha-quirks==0.0.59
@ -1350,7 +1350,7 @@ zha-quirks==0.0.59
zigpy-cc==0.5.2
# homeassistant.components.zha
zigpy-deconz==0.12.0
zigpy-deconz==0.12.1
# homeassistant.components.zha
zigpy-xbee==0.13.0
@ -1359,7 +1359,7 @@ zigpy-xbee==0.13.0
zigpy-zigate==0.7.3
# homeassistant.components.zha
zigpy-znp==0.5.2
zigpy-znp==0.5.3
# homeassistant.components.zha
zigpy==0.36.1

View File

@ -41,6 +41,7 @@ from homeassistant.const import (
STATE_ON,
STATE_UNAVAILABLE,
__version__,
__version__ as hass_version,
)
from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS
@ -130,6 +131,7 @@ async def test_home_accessory(hass, hk_driver):
serv.get_characteristic(CHAR_SERIAL_NUMBER).value
== "light.accessory_that_exceeds_the_maximum_maximum_maximum_maximum"
)
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == "0.4.3"
hass.states.async_set(entity_id, "on")
await hass.async_block_till_done()
@ -157,6 +159,31 @@ async def test_home_accessory(hass, hk_driver):
assert serv.get_characteristic(CHAR_MODEL).value == "Test Model"
async def test_accessory_with_missing_basic_service_info(hass, hk_driver):
"""Test HomeAccessory class."""
entity_id = "sensor.accessory"
hass.states.async_set(entity_id, "on")
acc = HomeAccessory(
hass,
hk_driver,
"Home Accessory",
entity_id,
3,
{
ATTR_MODEL: None,
ATTR_MANUFACTURER: None,
ATTR_SOFTWARE_VERSION: None,
ATTR_INTEGRATION: None,
},
)
serv = acc.get_service(SERV_ACCESSORY_INFO)
assert serv.get_characteristic(CHAR_NAME).value == "Home Accessory"
assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Home Assistant Sensor"
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
async def test_battery_service(hass, hk_driver, caplog):
"""Test battery service."""
entity_id = "homekit.accessory"

View File

@ -329,7 +329,13 @@ async def test_reset_switch(hass, hk_driver, events):
future = dt_util.utcnow() + timedelta(seconds=1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert acc.char_on.value is True
future = dt_util.utcnow() + timedelta(seconds=10)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert acc.char_on.value is False
assert len(events) == 1
assert not call_turn_off
@ -367,7 +373,13 @@ async def test_script_switch(hass, hk_driver, events):
future = dt_util.utcnow() + timedelta(seconds=1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert acc.char_on.value is True
future = dt_util.utcnow() + timedelta(seconds=10)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert acc.char_on.value is False
assert len(events) == 1
assert not call_turn_off

View File

@ -650,19 +650,30 @@ async def test_merge(merge_log_err, hass):
"pack_list": {"light": {"platform": "test"}},
"pack_list2": {"light": [{"platform": "test"}]},
"pack_none": {"wake_on_lan": None},
"pack_special": {
"automation": [{"some": "yay"}],
"script": {"a_script": "yay"},
"template": [{"some": "yay"}],
},
}
config = {
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
"input_boolean": {"ib2": None},
"light": {"platform": "test"},
"automation": [],
"script": {},
"template": [],
}
await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 0
assert len(config) == 5
assert len(config) == 8
assert len(config["input_boolean"]) == 2
assert len(config["input_select"]) == 1
assert len(config["light"]) == 3
assert len(config["automation"]) == 1
assert len(config["script"]) == 1
assert len(config["template"]) == 1
assert isinstance(config["wake_on_lan"], OrderedDict)