mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Merge pull request #54117 from home-assistant/rc
This commit is contained in:
commit
a07048aacf
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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__
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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."""
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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": [
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user