mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
2023.8.1 (#97772)
This commit is contained in:
commit
489860a28b
@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyatv", "srptools"],
|
||||
"requirements": ["pyatv==0.13.2"],
|
||||
"requirements": ["pyatv==0.13.3"],
|
||||
"zeroconf": [
|
||||
"_mediaremotetv._tcp.local.",
|
||||
"_companion-link._tcp.local.",
|
||||
|
@ -19,6 +19,6 @@
|
||||
"bluetooth-adapters==0.16.0",
|
||||
"bluetooth-auto-recovery==1.2.1",
|
||||
"bluetooth-data-tools==1.6.1",
|
||||
"dbus-fast==1.87.5"
|
||||
"dbus-fast==1.90.1"
|
||||
]
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ from homeassistant.components.sensor import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
PLATFORM_SCHEMA,
|
||||
STATE_CLASSES_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
||||
from homeassistant.const import (
|
||||
CONF_COMMAND,
|
||||
CONF_DEVICE_CLASS,
|
||||
@ -206,15 +208,25 @@ class CommandSensor(ManualTriggerEntity, SensorEntity):
|
||||
return
|
||||
|
||||
if self._value_template is not None:
|
||||
self._attr_native_value = (
|
||||
self._value_template.async_render_with_possible_json_value(
|
||||
value = self._value_template.async_render_with_possible_json_value(
|
||||
value,
|
||||
None,
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
||||
if self.device_class not in {
|
||||
SensorDeviceClass.DATE,
|
||||
SensorDeviceClass.TIMESTAMP,
|
||||
}:
|
||||
self._attr_native_value = value
|
||||
self._process_manual_data(value)
|
||||
return
|
||||
|
||||
self._attr_native_value = None
|
||||
if value is not None:
|
||||
self._attr_native_value = async_parse_date_datetime(
|
||||
value, self.entity_id, self.device_class
|
||||
)
|
||||
self._process_manual_data(value)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==1.2.5", "home-assistant-intents==2023.7.25"]
|
||||
"requirements": ["hassil==1.2.5", "home-assistant-intents==2023.8.2"]
|
||||
}
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/duotecno",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["pyduotecno==2023.8.0"]
|
||||
"requirements": ["pyduotecno==2023.8.3"]
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import logging
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.http import HomeAssistantAccessLogger
|
||||
from homeassistant.components.network import async_get_source_ip
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITIES,
|
||||
@ -100,7 +101,7 @@ async def start_emulated_hue_bridge(
|
||||
config.advertise_port or config.listen_port,
|
||||
)
|
||||
|
||||
runner = web.AppRunner(app)
|
||||
runner = web.AppRunner(app, access_log_class=HomeAssistantAccessLogger)
|
||||
await runner.setup()
|
||||
|
||||
site = web.TCPSite(runner, config.host_ip_addr, config.listen_port)
|
||||
|
@ -18,9 +18,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Remove forked-daapd component."""
|
||||
status = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if status and hass.data.get(DOMAIN) and hass.data[DOMAIN].get(entry.entry_id):
|
||||
hass.data[DOMAIN][entry.entry_id][
|
||||
if websocket_handler := hass.data[DOMAIN][entry.entry_id][
|
||||
HASS_DATA_UPDATER_KEY
|
||||
].websocket_handler.cancel()
|
||||
].websocket_handler:
|
||||
websocket_handler.cancel()
|
||||
for remove_listener in hass.data[DOMAIN][entry.entry_id][
|
||||
HASS_DATA_REMOVE_LISTENERS_KEY
|
||||
]:
|
||||
|
@ -31,6 +31,7 @@ from homeassistant.components.spotify import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
@ -127,10 +128,10 @@ async def async_setup_entry(
|
||||
forked_daapd_updater = ForkedDaapdUpdater(
|
||||
hass, forked_daapd_api, config_entry.entry_id
|
||||
)
|
||||
await forked_daapd_updater.async_init()
|
||||
hass.data[DOMAIN][config_entry.entry_id][
|
||||
HASS_DATA_UPDATER_KEY
|
||||
] = forked_daapd_updater
|
||||
await forked_daapd_updater.async_init()
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
@ -914,7 +915,8 @@ class ForkedDaapdUpdater:
|
||||
|
||||
async def async_init(self):
|
||||
"""Perform async portion of class initialization."""
|
||||
server_config = await self._api.get_request("config")
|
||||
if not (server_config := await self._api.get_request("config")):
|
||||
raise PlatformNotReady
|
||||
if websocket_port := server_config.get("websocket_port"):
|
||||
self.websocket_handler = asyncio.create_task(
|
||||
self._api.start_websocket_handler(
|
||||
|
@ -161,8 +161,11 @@ class FreeboxRouter:
|
||||
async def _update_raids_sensors(self) -> None:
|
||||
"""Update Freebox raids."""
|
||||
# None at first request
|
||||
try:
|
||||
fbx_raids: list[dict[str, Any]] = await self._api.storage.get_raids() or []
|
||||
|
||||
except HttpRequestError:
|
||||
_LOGGER.warning("Unable to enumerate raid disks")
|
||||
else:
|
||||
for fbx_raid in fbx_raids:
|
||||
self.raids[fbx_raid["id"]] = fbx_raid
|
||||
|
||||
|
@ -469,6 +469,11 @@ class FritzBoxTools(
|
||||
if not host.get("MACAddress"):
|
||||
continue
|
||||
|
||||
if (wan_access := host.get("X_AVM-DE_WANAccess")) is not None:
|
||||
wan_access_result = "granted" in wan_access
|
||||
else:
|
||||
wan_access_result = None
|
||||
|
||||
hosts[host["MACAddress"]] = Device(
|
||||
name=host["HostName"],
|
||||
connected=host["Active"],
|
||||
@ -476,7 +481,7 @@ class FritzBoxTools(
|
||||
connection_type="",
|
||||
ip_address=host["IPAddress"],
|
||||
ssid=None,
|
||||
wan_access="granted" in host["X_AVM-DE_WANAccess"],
|
||||
wan_access=wan_access_result,
|
||||
)
|
||||
|
||||
if not self.fritz_status.device_has_mesh_support or (
|
||||
|
@ -89,6 +89,7 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
self._supported_color_modes.add(ColorMode.BRIGHTNESS)
|
||||
# support transition if brightness control
|
||||
self._attr_supported_features |= LightEntityFeature.TRANSITION
|
||||
self._color_temp_active: bool = False
|
||||
# get list of supported effects (combine effects and timed_effects)
|
||||
self._attr_effect_list = []
|
||||
if effects := resource.effects:
|
||||
@ -121,9 +122,7 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
@property
|
||||
def color_mode(self) -> ColorMode:
|
||||
"""Return the color mode of the light."""
|
||||
if color_temp := self.resource.color_temperature:
|
||||
# Hue lights return `mired_valid` to indicate CT is active
|
||||
if color_temp.mirek is not None:
|
||||
if self.color_temp_active:
|
||||
return ColorMode.COLOR_TEMP
|
||||
if self.resource.supports_color:
|
||||
return ColorMode.XY
|
||||
@ -132,6 +131,18 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
# fallback to on_off
|
||||
return ColorMode.ONOFF
|
||||
|
||||
@property
|
||||
def color_temp_active(self) -> bool:
|
||||
"""Return if the light is in Color Temperature mode."""
|
||||
color_temp = self.resource.color_temperature
|
||||
if color_temp is None or color_temp.mirek is None:
|
||||
return False
|
||||
# Official Hue lights return `mirek_valid` to indicate CT is active
|
||||
# while non-official lights do not.
|
||||
if self.device.product_data.certified:
|
||||
return self.resource.color_temperature.mirek_valid
|
||||
return self._color_temp_active
|
||||
|
||||
@property
|
||||
def xy_color(self) -> tuple[float, float] | None:
|
||||
"""Return the xy color."""
|
||||
@ -193,6 +204,7 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
xy_color = kwargs.get(ATTR_XY_COLOR)
|
||||
color_temp = normalize_hue_colortemp(kwargs.get(ATTR_COLOR_TEMP))
|
||||
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
|
||||
self._color_temp_active = color_temp is not None
|
||||
flash = kwargs.get(ATTR_FLASH)
|
||||
effect = effect_str = kwargs.get(ATTR_EFFECT)
|
||||
if effect_str in (EFFECT_NONE, EFFECT_NONE.lower()):
|
||||
|
@ -138,7 +138,7 @@ class MicroBotConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await self._client.connect(init=True)
|
||||
return self.async_show_form(step_id="link")
|
||||
|
||||
if not self._client.is_connected():
|
||||
if not await self._client.is_connected():
|
||||
errors["base"] = "linking"
|
||||
else:
|
||||
await self._client.disconnect()
|
||||
|
@ -342,10 +342,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
entry,
|
||||
[
|
||||
Platform.SENSOR, # always unload system entities (telegram counter, etc.)
|
||||
*[
|
||||
platform
|
||||
for platform in SUPPORTED_PLATFORMS
|
||||
if platform in hass.data[DATA_KNX_CONFIG]
|
||||
and platform is not Platform.NOTIFY
|
||||
and platform not in (Platform.SENSOR, Platform.NOTIFY)
|
||||
],
|
||||
],
|
||||
)
|
||||
if unload_ok:
|
||||
|
@ -111,7 +111,7 @@ class PlenticoreDataSelect(
|
||||
self.platform_name = platform_name
|
||||
self.module_id = description.module_id
|
||||
self.data_id = description.key
|
||||
self._device_info = device_info
|
||||
self._attr_device_info = device_info
|
||||
self._attr_unique_id = f"{entry_id}_{description.module_id}"
|
||||
|
||||
@property
|
||||
|
@ -26,7 +26,6 @@ CONF_STATION = "station"
|
||||
ATTRIBUTION = "Data from National Weather Service/NOAA"
|
||||
|
||||
ATTR_FORECAST_DETAILED_DESCRIPTION = "detailed_description"
|
||||
ATTR_FORECAST_DAYTIME = "daytime"
|
||||
|
||||
CONDITION_CLASSES: dict[str, list[str]] = {
|
||||
ATTR_CONDITION_EXCEPTIONAL: [
|
||||
|
@ -9,6 +9,7 @@ from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_SUNNY,
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_HUMIDITY,
|
||||
ATTR_FORECAST_IS_DAYTIME,
|
||||
ATTR_FORECAST_NATIVE_DEW_POINT,
|
||||
ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
@ -36,7 +37,6 @@ from homeassistant.util.unit_system import UnitSystem
|
||||
|
||||
from . import base_unique_id, device_info
|
||||
from .const import (
|
||||
ATTR_FORECAST_DAYTIME,
|
||||
ATTR_FORECAST_DETAILED_DESCRIPTION,
|
||||
ATTRIBUTION,
|
||||
CONDITION_CLASSES,
|
||||
@ -101,7 +101,6 @@ if TYPE_CHECKING:
|
||||
"""Forecast with extra fields needed for NWS."""
|
||||
|
||||
detailed_description: str | None
|
||||
daytime: bool | None
|
||||
|
||||
|
||||
class NWSWeather(WeatherEntity):
|
||||
@ -268,7 +267,7 @@ class NWSWeather(WeatherEntity):
|
||||
data[ATTR_FORECAST_HUMIDITY] = forecast_entry.get("relativeHumidity")
|
||||
|
||||
if self.mode == DAYNIGHT:
|
||||
data[ATTR_FORECAST_DAYTIME] = forecast_entry.get("isDaytime")
|
||||
data[ATTR_FORECAST_IS_DAYTIME] = forecast_entry.get("isDaytime")
|
||||
|
||||
time = forecast_entry.get("iconTime")
|
||||
weather = forecast_entry.get("iconWeather")
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["recorder"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["opower==0.0.18"]
|
||||
"requirements": ["opower==0.0.20"]
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
icon="mdi:battery",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["full", "normal", "low", "verylow"],
|
||||
options=["full", "normal", "medium", "low", "verylow"],
|
||||
translation_key="battery",
|
||||
),
|
||||
OverkizSensorDescription(
|
||||
|
@ -77,6 +77,7 @@
|
||||
"full": "Full",
|
||||
"low": "Low",
|
||||
"normal": "Normal",
|
||||
"medium": "Medium",
|
||||
"verylow": "Very low"
|
||||
}
|
||||
},
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": ["python-roborock==0.30.2"]
|
||||
"requirements": ["python-roborock==0.31.1"]
|
||||
}
|
||||
|
@ -218,6 +218,8 @@ async def async_setup_entry(
|
||||
class SolarlogSensor(CoordinatorEntity[SolarlogData], SensorEntity):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
entity_description: SolarLogSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
@ -228,7 +230,6 @@ class SolarlogSensor(CoordinatorEntity[SolarlogData], SensorEntity):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{coordinator.name} {description.name}"
|
||||
self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.unique_id)},
|
||||
|
@ -57,25 +57,29 @@ def async_discovery_data_from_service(
|
||||
except UnicodeDecodeError:
|
||||
return None
|
||||
|
||||
ext_addr = service.properties.get(b"xa")
|
||||
ext_pan_id = service.properties.get(b"xp")
|
||||
network_name = try_decode(service.properties.get(b"nn"))
|
||||
model_name = try_decode(service.properties.get(b"mn"))
|
||||
# Service properties are always bytes if they are set from the network.
|
||||
# For legacy backwards compatibility zeroconf allows properties to be set
|
||||
# as strings but we never do that so we can safely cast here.
|
||||
service_properties = cast(dict[bytes, bytes | None], service.properties)
|
||||
ext_addr = service_properties.get(b"xa")
|
||||
ext_pan_id = service_properties.get(b"xp")
|
||||
network_name = try_decode(service_properties.get(b"nn"))
|
||||
model_name = try_decode(service_properties.get(b"mn"))
|
||||
server = service.server
|
||||
vendor_name = try_decode(service.properties.get(b"vn"))
|
||||
thread_version = try_decode(service.properties.get(b"tv"))
|
||||
vendor_name = try_decode(service_properties.get(b"vn"))
|
||||
thread_version = try_decode(service_properties.get(b"tv"))
|
||||
unconfigured = None
|
||||
brand = KNOWN_BRANDS.get(vendor_name)
|
||||
if brand == "homeassistant":
|
||||
# Attempt to detect incomplete configuration
|
||||
if (state_bitmap_b := service.properties.get(b"sb")) is not None:
|
||||
if (state_bitmap_b := service_properties.get(b"sb")) is not None:
|
||||
try:
|
||||
state_bitmap = StateBitmap.from_bytes(state_bitmap_b)
|
||||
if not state_bitmap.is_active:
|
||||
unconfigured = True
|
||||
except ValueError:
|
||||
_LOGGER.debug("Failed to decode state bitmap in service %s", service)
|
||||
if service.properties.get(b"at") is None:
|
||||
if service_properties.get(b"at") is None:
|
||||
unconfigured = True
|
||||
|
||||
return ThreadRouterDiscoveryData(
|
||||
@ -168,10 +172,19 @@ class ThreadRouterDiscovery:
|
||||
return
|
||||
|
||||
_LOGGER.debug("_add_update_service %s %s", name, service)
|
||||
# Service properties are always bytes if they are set from the network.
|
||||
# For legacy backwards compatibility zeroconf allows properties to be set
|
||||
# as strings but we never do that so we can safely cast here.
|
||||
service_properties = cast(dict[bytes, bytes | None], service.properties)
|
||||
|
||||
if not (xa := service_properties.get(b"xa")):
|
||||
_LOGGER.debug("_add_update_service failed to find xa in %s", service)
|
||||
return
|
||||
|
||||
# We use the extended mac address as key, bail out if it's missing
|
||||
try:
|
||||
extended_mac_address = service.properties[b"xa"].hex()
|
||||
except (KeyError, UnicodeDecodeError) as err:
|
||||
extended_mac_address = xa.hex()
|
||||
except UnicodeDecodeError as err:
|
||||
_LOGGER.debug("_add_update_service failed to parse service %s", err)
|
||||
return
|
||||
|
||||
|
@ -154,7 +154,7 @@
|
||||
},
|
||||
{
|
||||
"hostname": "k[lps]*",
|
||||
"macaddress": "788C5B*"
|
||||
"macaddress": "788CB5*"
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/tplink",
|
||||
|
@ -116,7 +116,7 @@ class SmartPlugSwitchChild(SmartPlugSwitch):
|
||||
coordinator: TPLinkDataUpdateCoordinator,
|
||||
plug: SmartDevice,
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
"""Initialize the child switch."""
|
||||
super().__init__(device, coordinator)
|
||||
self._plug = plug
|
||||
self._attr_unique_id = legacy_device_id(plug)
|
||||
@ -124,10 +124,15 @@ class SmartPlugSwitchChild(SmartPlugSwitch):
|
||||
|
||||
@async_refresh_after
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
"""Turn the child switch on."""
|
||||
await self._plug.turn_on()
|
||||
|
||||
@async_refresh_after
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
"""Turn the child switch off."""
|
||||
await self._plug.turn_off()
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if child switch is on."""
|
||||
return bool(self._plug.is_on)
|
||||
|
@ -41,7 +41,7 @@ class UnifiImageEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
||||
"""Validate and load entities from different UniFi handlers."""
|
||||
|
||||
image_fn: Callable[[UniFiController, ApiItemT], bytes]
|
||||
value_fn: Callable[[ApiItemT], str]
|
||||
value_fn: Callable[[ApiItemT], str | None]
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -99,7 +99,7 @@ class UnifiImageEntity(UnifiEntity[HandlerT, ApiItemT], ImageEntity):
|
||||
_attr_content_type = "image/png"
|
||||
|
||||
current_image: bytes | None = None
|
||||
previous_value = ""
|
||||
previous_value: str | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiounifi"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiounifi==50"],
|
||||
"requirements": ["aiounifi==51"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
@ -86,10 +86,10 @@ def _setup_entities(devices, async_add_entities):
|
||||
class VeSyncFanHA(VeSyncDevice, FanEntity):
|
||||
"""Representation of a VeSync fan."""
|
||||
|
||||
_attr_supported_features = FanEntityFeature.SET_SPEED
|
||||
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, fan):
|
||||
def __init__(self, fan) -> None:
|
||||
"""Initialize the VeSync fan device."""
|
||||
super().__init__(fan)
|
||||
self.smartfan = fan
|
||||
|
@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
@ -141,8 +142,9 @@ class WaqiSensor(SensorEntity):
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the device."""
|
||||
if self._data is not None:
|
||||
return self._data.get("aqi")
|
||||
if (value := self._data.get("aqi")) is not None:
|
||||
with suppress(ValueError):
|
||||
return float(value)
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -553,11 +553,17 @@ def info_from_service(service: AsyncServiceInfo) -> ZeroconfServiceInfo | None:
|
||||
break
|
||||
if not host:
|
||||
return None
|
||||
|
||||
# Service properties are always bytes if they are set from the network.
|
||||
# For legacy backwards compatibility zeroconf allows properties to be set
|
||||
# as strings but we never do that so we can safely cast here.
|
||||
service_properties = cast(dict[bytes, bytes | None], service.properties)
|
||||
|
||||
properties: dict[str, Any] = {
|
||||
k.decode("ascii", "replace"): None
|
||||
if v is None
|
||||
else v.decode("utf-8", "replace")
|
||||
for k, v in service.properties.items()
|
||||
for k, v in service_properties.items()
|
||||
}
|
||||
|
||||
assert service.server is not None, "server cannot be none if there are addresses"
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["zeroconf"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["zeroconf==0.72.0"]
|
||||
"requirements": ["zeroconf==0.74.0"]
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
||||
return
|
||||
|
||||
if (
|
||||
(brightness is not None or transition)
|
||||
(brightness is not None or transition is not None)
|
||||
and not new_color_provided_while_off
|
||||
and brightness_supported(self._attr_supported_color_modes)
|
||||
):
|
||||
@ -350,11 +350,11 @@ class BaseLight(LogMixin, light.LightEntity):
|
||||
self._attr_brightness = level
|
||||
|
||||
if (
|
||||
brightness is None
|
||||
(brightness is None and transition is None)
|
||||
and not new_color_provided_while_off
|
||||
or (self._FORCE_ON and brightness)
|
||||
or (self._FORCE_ON and brightness != 0)
|
||||
):
|
||||
# since some lights don't always turn on with move_to_level_with_on_off,
|
||||
# since FORCE_ON lights don't turn on with move_to_level_with_on_off,
|
||||
# we should call the on command on the on_off cluster
|
||||
# if brightness is not 0.
|
||||
result = await self._on_off_cluster_handler.on()
|
||||
@ -385,7 +385,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
||||
return
|
||||
|
||||
if new_color_provided_while_off:
|
||||
# The light is has the correct color, so we can now transition
|
||||
# The light has the correct color, so we can now transition
|
||||
# it to the correct brightness level.
|
||||
result = await self._level_cluster_handler.move_to_level(
|
||||
level=level, transition_time=int(10 * duration)
|
||||
@ -1076,7 +1076,7 @@ class HueLight(Light):
|
||||
manufacturers={"Jasco", "Quotra-Vision", "eWeLight", "eWeLink"},
|
||||
)
|
||||
class ForceOnLight(Light):
|
||||
"""Representation of a light which does not respect move_to_level_with_on_off."""
|
||||
"""Representation of a light which does not respect on/off for move_to_level_with_on_off commands."""
|
||||
|
||||
_attr_name: str = "Light"
|
||||
_FORCE_ON = True
|
||||
|
@ -25,10 +25,10 @@
|
||||
"pyserial-asyncio==0.6",
|
||||
"zha-quirks==0.0.102",
|
||||
"zigpy-deconz==0.21.0",
|
||||
"zigpy==0.56.2",
|
||||
"zigpy==0.56.4",
|
||||
"zigpy-xbee==0.18.1",
|
||||
"zigpy-zigate==0.11.0",
|
||||
"zigpy-znp==0.11.3"
|
||||
"zigpy-znp==0.11.4"
|
||||
],
|
||||
"usb": [
|
||||
{
|
||||
|
@ -7,7 +7,7 @@ from typing import Final
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 8
|
||||
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, 11, 0)
|
||||
|
@ -782,7 +782,7 @@ DHCP: list[dict[str, str | bool]] = [
|
||||
{
|
||||
"domain": "tplink",
|
||||
"hostname": "k[lps]*",
|
||||
"macaddress": "788C5B*",
|
||||
"macaddress": "788CB5*",
|
||||
},
|
||||
{
|
||||
"domain": "tuya",
|
||||
|
@ -15,15 +15,15 @@ bluetooth-auto-recovery==1.2.1
|
||||
bluetooth-data-tools==1.6.1
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.3.0
|
||||
cryptography==41.0.2
|
||||
dbus-fast==1.87.5
|
||||
cryptography==41.0.3
|
||||
dbus-fast==1.90.1
|
||||
fnv-hash-fast==0.4.0
|
||||
ha-av==10.1.1
|
||||
hass-nabucasa==0.69.0
|
||||
hassil==1.2.5
|
||||
home-assistant-bluetooth==1.10.2
|
||||
home-assistant-frontend==20230802.0
|
||||
home-assistant-intents==2023.7.25
|
||||
home-assistant-intents==2023.8.2
|
||||
httpx==0.24.1
|
||||
ifaddr==0.2.0
|
||||
janus==1.0.0
|
||||
@ -52,7 +52,7 @@ voluptuous-serialize==2.6.0
|
||||
voluptuous==0.13.1
|
||||
webrtcvad==2.0.10
|
||||
yarl==1.9.2
|
||||
zeroconf==0.72.0
|
||||
zeroconf==0.74.0
|
||||
|
||||
# Constrain pycryptodome to avoid vulnerability
|
||||
# see https://github.com/home-assistant/core/pull/16238
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.8.0"
|
||||
version = "2023.8.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
@ -41,7 +41,7 @@ dependencies = [
|
||||
"lru-dict==1.2.0",
|
||||
"PyJWT==2.8.0",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
"cryptography==41.0.2",
|
||||
"cryptography==41.0.3",
|
||||
# pyOpenSSL 23.2.0 is required to work with cryptography 41+
|
||||
"pyOpenSSL==23.2.0",
|
||||
"orjson==3.9.2",
|
||||
|
@ -16,7 +16,7 @@ ifaddr==0.2.0
|
||||
Jinja2==3.1.2
|
||||
lru-dict==1.2.0
|
||||
PyJWT==2.8.0
|
||||
cryptography==41.0.2
|
||||
cryptography==41.0.3
|
||||
pyOpenSSL==23.2.0
|
||||
orjson==3.9.2
|
||||
pip>=21.3.1
|
||||
|
@ -360,7 +360,7 @@ aiosyncthing==0.5.1
|
||||
aiotractive==0.5.5
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==50
|
||||
aiounifi==51
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
@ -632,7 +632,7 @@ datadog==0.15.0
|
||||
datapoint==0.9.8
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
dbus-fast==1.87.5
|
||||
dbus-fast==1.90.1
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.6.7
|
||||
@ -991,7 +991,7 @@ holidays==0.28
|
||||
home-assistant-frontend==20230802.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.7.25
|
||||
home-assistant-intents==2023.8.2
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
homeconnect==0.7.2
|
||||
@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16
|
||||
openwrt-ubus-rpc==0.0.2
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.0.18
|
||||
opower==0.0.20
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
@ -1572,7 +1572,7 @@ pyatag==0.3.5.3
|
||||
pyatmo==7.5.0
|
||||
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.13.2
|
||||
pyatv==0.13.3
|
||||
|
||||
# homeassistant.components.aussie_broadband
|
||||
pyaussiebb==0.0.15
|
||||
@ -1650,7 +1650,7 @@ pydrawise==2023.7.1
|
||||
pydroid-ipcam==2.0.0
|
||||
|
||||
# homeassistant.components.duotecno
|
||||
pyduotecno==2023.8.0
|
||||
pyduotecno==2023.8.3
|
||||
|
||||
# homeassistant.components.ebox
|
||||
pyebox==1.1.4
|
||||
@ -2150,7 +2150,7 @@ python-qbittorrent==0.4.3
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.30.2
|
||||
python-roborock==0.31.1
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
@ -2749,7 +2749,7 @@ zamg==0.2.4
|
||||
zengge==0.2
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.72.0
|
||||
zeroconf==0.74.0
|
||||
|
||||
# homeassistant.components.zeversolar
|
||||
zeversolar==0.3.1
|
||||
@ -2773,10 +2773,10 @@ zigpy-xbee==0.18.1
|
||||
zigpy-zigate==0.11.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-znp==0.11.3
|
||||
zigpy-znp==0.11.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy==0.56.2
|
||||
zigpy==0.56.4
|
||||
|
||||
# homeassistant.components.zoneminder
|
||||
zm-py==0.5.2
|
||||
|
@ -335,7 +335,7 @@ aiosyncthing==0.5.1
|
||||
aiotractive==0.5.5
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==50
|
||||
aiounifi==51
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
@ -515,7 +515,7 @@ datadog==0.15.0
|
||||
datapoint==0.9.8
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
dbus-fast==1.87.5
|
||||
dbus-fast==1.90.1
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.6.7
|
||||
@ -777,7 +777,7 @@ holidays==0.28
|
||||
home-assistant-frontend==20230802.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.7.25
|
||||
home-assistant-intents==2023.8.2
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
homeconnect==0.7.2
|
||||
@ -1037,7 +1037,7 @@ openerz-api==0.2.0
|
||||
openhomedevice==2.2.0
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.0.18
|
||||
opower==0.0.20
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
@ -1178,7 +1178,7 @@ pyatag==0.3.5.3
|
||||
pyatmo==7.5.0
|
||||
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.13.2
|
||||
pyatv==0.13.3
|
||||
|
||||
# homeassistant.components.aussie_broadband
|
||||
pyaussiebb==0.0.15
|
||||
@ -1223,7 +1223,7 @@ pydiscovergy==2.0.1
|
||||
pydroid-ipcam==2.0.0
|
||||
|
||||
# homeassistant.components.duotecno
|
||||
pyduotecno==2023.8.0
|
||||
pyduotecno==2023.8.3
|
||||
|
||||
# homeassistant.components.econet
|
||||
pyeconet==0.1.20
|
||||
@ -1579,7 +1579,7 @@ python-picnic-api==1.1.0
|
||||
python-qbittorrent==0.4.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.30.2
|
||||
python-roborock==0.31.1
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
@ -2022,7 +2022,7 @@ youtubeaio==1.1.5
|
||||
zamg==0.2.4
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.72.0
|
||||
zeroconf==0.74.0
|
||||
|
||||
# homeassistant.components.zeversolar
|
||||
zeversolar==0.3.1
|
||||
@ -2040,10 +2040,10 @@ zigpy-xbee==0.18.1
|
||||
zigpy-zigate==0.11.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-znp==0.11.3
|
||||
zigpy-znp==0.11.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy==0.56.2
|
||||
zigpy==0.56.4
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.49.0
|
||||
|
@ -61,8 +61,9 @@ def allow_name_translation(integration: Integration) -> bool:
|
||||
"""Validate that the translation name is not the same as the integration name."""
|
||||
# Only enforce for core because custom integrations can't be
|
||||
# added to allow list.
|
||||
return integration.core and (
|
||||
integration.domain in ALLOW_NAME_TRANSLATION
|
||||
return (
|
||||
not integration.core
|
||||
or integration.domain in ALLOW_NAME_TRANSLATION
|
||||
or integration.quality_scale == "internal"
|
||||
)
|
||||
|
||||
|
@ -646,3 +646,54 @@ async def test_updating_manually(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert called
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"get_config",
|
||||
[
|
||||
{
|
||||
"command_line": [
|
||||
{
|
||||
"sensor": {
|
||||
"name": "Test",
|
||||
"command": "echo 2022-12-22T13:15:30Z",
|
||||
"device_class": "timestamp",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
)
|
||||
async def test_scrape_sensor_device_timestamp(
|
||||
hass: HomeAssistant, load_yaml_integration: None
|
||||
) -> None:
|
||||
"""Test Command Line sensor with a device of type TIMESTAMP."""
|
||||
entity_state = hass.states.get("sensor.test")
|
||||
assert entity_state
|
||||
assert entity_state.state == "2022-12-22T13:15:30+00:00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"get_config",
|
||||
[
|
||||
{
|
||||
"command_line": [
|
||||
{
|
||||
"sensor": {
|
||||
"name": "Test",
|
||||
"command": "echo January 17, 2022",
|
||||
"device_class": "date",
|
||||
"value_template": "{{ strptime(value, '%B %d, %Y').strftime('%Y-%m-%d') }}",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
)
|
||||
async def test_scrape_sensor_device_date(
|
||||
hass: HomeAssistant, load_yaml_integration: None
|
||||
) -> None:
|
||||
"""Test Command Line sensor with a device of type DATE."""
|
||||
entity_state = hass.states.get("sensor.test")
|
||||
assert entity_state
|
||||
assert entity_state.state == "2022-01-17"
|
||||
|
@ -30,6 +30,7 @@
|
||||
'id': 'homeassistant',
|
||||
'name': 'Home Assistant',
|
||||
'supported_languages': list([
|
||||
'af',
|
||||
'ar',
|
||||
'bg',
|
||||
'bn',
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""The config flow tests for the forked_daapd media player platform."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@ -12,9 +12,11 @@ from homeassistant.components.forked_daapd.const import (
|
||||
CONF_TTS_VOLUME,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.forked_daapd.media_player import async_setup_entry
|
||||
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -242,3 +244,18 @@ async def test_options_flow(hass: HomeAssistant, config_entry) -> None:
|
||||
},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_async_setup_entry_not_ready(hass: HomeAssistant, config_entry) -> None:
|
||||
"""Test that a PlatformNotReady exception is thrown during platform setup."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.forked_daapd.media_player.ForkedDaapdAPI",
|
||||
autospec=True,
|
||||
) as mock_api:
|
||||
mock_api.return_value.get_request.return_value = None
|
||||
config_entry.add_to_hass(hass)
|
||||
with pytest.raises(PlatformNotReady):
|
||||
await async_setup_entry(hass, config_entry, MagicMock())
|
||||
await hass.async_block_till_done()
|
||||
mock_api.return_value.get_request.assert_called_once()
|
||||
|
@ -77,6 +77,6 @@ class MockMicroBotApiClientFail:
|
||||
async def disconnect(self):
|
||||
"""Mock disconnect."""
|
||||
|
||||
def is_connected(self):
|
||||
async def is_connected(self):
|
||||
"""Mock disconnected."""
|
||||
return False
|
||||
|
@ -180,12 +180,14 @@ def _mocked_strip() -> SmartStrip:
|
||||
plug0.alias = "Plug0"
|
||||
plug0.device_id = "bb:bb:cc:dd:ee:ff_PLUG0DEVICEID"
|
||||
plug0.mac = "bb:bb:cc:dd:ee:ff"
|
||||
plug0.is_on = True
|
||||
plug0.protocol = _mock_protocol()
|
||||
plug1 = _mocked_plug()
|
||||
plug1.device_id = "cc:bb:cc:dd:ee:ff_PLUG1DEVICEID"
|
||||
plug1.mac = "cc:bb:cc:dd:ee:ff"
|
||||
plug1.alias = "Plug1"
|
||||
plug1.protocol = _mock_protocol()
|
||||
plug1.is_on = False
|
||||
strip.children = [plug0, plug1]
|
||||
return strip
|
||||
|
||||
|
@ -9,7 +9,7 @@ import pytest
|
||||
from homeassistant.components import tplink
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.components.tplink.const import DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, STATE_UNAVAILABLE
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -146,22 +146,37 @@ async def test_strip(hass: HomeAssistant) -> None:
|
||||
# since this is what the previous version did
|
||||
assert hass.states.get("switch.my_strip") is None
|
||||
|
||||
for plug_id in range(2):
|
||||
entity_id = f"switch.my_strip_plug{plug_id}"
|
||||
entity_id = "switch.my_strip_plug0"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
strip.children[plug_id].turn_off.assert_called_once()
|
||||
strip.children[plug_id].turn_off.reset_mock()
|
||||
strip.children[0].turn_off.assert_called_once()
|
||||
strip.children[0].turn_off.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
strip.children[plug_id].turn_on.assert_called_once()
|
||||
strip.children[plug_id].turn_on.reset_mock()
|
||||
strip.children[0].turn_on.assert_called_once()
|
||||
strip.children[0].turn_on.reset_mock()
|
||||
|
||||
entity_id = "switch.my_strip_plug1"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
strip.children[1].turn_off.assert_called_once()
|
||||
strip.children[1].turn_off.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
strip.children[1].turn_on.assert_called_once()
|
||||
strip.children[1].turn_on.reset_mock()
|
||||
|
||||
|
||||
async def test_strip_unique_ids(hass: HomeAssistant) -> None:
|
||||
|
@ -200,7 +200,7 @@
|
||||
'auto',
|
||||
'sleep',
|
||||
]),
|
||||
'supported_features': 1,
|
||||
'supported_features': 9,
|
||||
}),
|
||||
'entity_id': 'fan.fan',
|
||||
'last_changed': str,
|
||||
|
@ -58,7 +58,7 @@
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'vesync',
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'air-purifier',
|
||||
'unit_of_measurement': None,
|
||||
@ -73,7 +73,7 @@
|
||||
'auto',
|
||||
'sleep',
|
||||
]),
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.air_purifier_131s',
|
||||
@ -140,7 +140,7 @@
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'vesync',
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'asd_sdfKIHG7IJHGwJGJ7GJ_ag5h3G55',
|
||||
'unit_of_measurement': None,
|
||||
@ -161,7 +161,7 @@
|
||||
'sleep',
|
||||
]),
|
||||
'screen_status': True,
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.air_purifier_200s',
|
||||
@ -229,7 +229,7 @@
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'vesync',
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
'translation_key': None,
|
||||
'unique_id': '400s-purifier',
|
||||
'unit_of_measurement': None,
|
||||
@ -251,7 +251,7 @@
|
||||
'sleep',
|
||||
]),
|
||||
'screen_status': True,
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.air_purifier_400s',
|
||||
@ -319,7 +319,7 @@
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'vesync',
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
'translation_key': None,
|
||||
'unique_id': '600s-purifier',
|
||||
'unit_of_measurement': None,
|
||||
@ -341,7 +341,7 @@
|
||||
'sleep',
|
||||
]),
|
||||
'screen_status': True,
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'supported_features': <FanEntityFeature: 9>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.air_purifier_600s',
|
||||
|
@ -530,7 +530,78 @@ async def test_transitions(
|
||||
light2_state = hass.states.get(device_2_entity_id)
|
||||
assert light2_state.state == STATE_OFF
|
||||
|
||||
# first test 0 length transition with no color provided
|
||||
# first test 0 length transition with no color and no brightness provided
|
||||
dev1_cluster_on_off.request.reset_mock()
|
||||
dev1_cluster_level.request.reset_mock()
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
"turn_on",
|
||||
{"entity_id": device_1_entity_id, "transition": 0},
|
||||
blocking=True,
|
||||
)
|
||||
assert dev1_cluster_on_off.request.call_count == 0
|
||||
assert dev1_cluster_on_off.request.await_count == 0
|
||||
assert dev1_cluster_color.request.call_count == 0
|
||||
assert dev1_cluster_color.request.await_count == 0
|
||||
assert dev1_cluster_level.request.call_count == 1
|
||||
assert dev1_cluster_level.request.await_count == 1
|
||||
assert dev1_cluster_level.request.call_args == call(
|
||||
False,
|
||||
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
|
||||
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||
level=254, # default "full on" brightness
|
||||
transition_time=0,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tsn=None,
|
||||
)
|
||||
|
||||
light1_state = hass.states.get(device_1_entity_id)
|
||||
assert light1_state.state == STATE_ON
|
||||
assert light1_state.attributes["brightness"] == 254
|
||||
|
||||
# test 0 length transition with no color and no brightness provided again, but for "force on" lights
|
||||
eWeLink_cluster_on_off.request.reset_mock()
|
||||
eWeLink_cluster_level.request.reset_mock()
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
"turn_on",
|
||||
{"entity_id": eWeLink_light_entity_id, "transition": 0},
|
||||
blocking=True,
|
||||
)
|
||||
assert eWeLink_cluster_on_off.request.call_count == 1
|
||||
assert eWeLink_cluster_on_off.request.await_count == 1
|
||||
assert eWeLink_cluster_on_off.request.call_args_list[0] == call(
|
||||
False,
|
||||
eWeLink_cluster_on_off.commands_by_name["on"].id,
|
||||
eWeLink_cluster_on_off.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tsn=None,
|
||||
)
|
||||
assert eWeLink_cluster_color.request.call_count == 0
|
||||
assert eWeLink_cluster_color.request.await_count == 0
|
||||
assert eWeLink_cluster_level.request.call_count == 1
|
||||
assert eWeLink_cluster_level.request.await_count == 1
|
||||
assert eWeLink_cluster_level.request.call_args == call(
|
||||
False,
|
||||
eWeLink_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
|
||||
eWeLink_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||
level=254, # default "full on" brightness
|
||||
transition_time=0,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tsn=None,
|
||||
)
|
||||
|
||||
eWeLink_state = hass.states.get(eWeLink_light_entity_id)
|
||||
assert eWeLink_state.state == STATE_ON
|
||||
assert eWeLink_state.attributes["brightness"] == 254
|
||||
|
||||
eWeLink_cluster_on_off.request.reset_mock()
|
||||
eWeLink_cluster_level.request.reset_mock()
|
||||
|
||||
# test 0 length transition with brightness, but no color provided
|
||||
dev1_cluster_on_off.request.reset_mock()
|
||||
dev1_cluster_level.request.reset_mock()
|
||||
await hass.services.async_call(
|
||||
@ -1423,18 +1494,10 @@ async def async_test_level_on_off_from_hass(
|
||||
{"entity_id": entity_id, "transition": 10},
|
||||
blocking=True,
|
||||
)
|
||||
assert on_off_cluster.request.call_count == 1
|
||||
assert on_off_cluster.request.await_count == 1
|
||||
assert on_off_cluster.request.call_count == 0
|
||||
assert on_off_cluster.request.await_count == 0
|
||||
assert level_cluster.request.call_count == 1
|
||||
assert level_cluster.request.await_count == 1
|
||||
assert on_off_cluster.request.call_args == call(
|
||||
False,
|
||||
on_off_cluster.commands_by_name["on"].id,
|
||||
on_off_cluster.commands_by_name["on"].schema,
|
||||
expect_reply=True,
|
||||
manufacturer=None,
|
||||
tsn=None,
|
||||
)
|
||||
assert level_cluster.request.call_args == call(
|
||||
False,
|
||||
level_cluster.commands_by_name["move_to_level_with_on_off"].id,
|
||||
|
Loading…
x
Reference in New Issue
Block a user