mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 08:07:45 +00:00
Merge pull request #62182 from home-assistant/rc
This commit is contained in:
commit
7fc36c4fe0
@ -3,7 +3,7 @@
|
||||
"name": "Brunt Blind Engine",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/brunt",
|
||||
"requirements": ["brunt==1.0.1"],
|
||||
"requirements": ["brunt==1.0.2"],
|
||||
"codeowners": ["@eavanvalkenburg"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "ebusd",
|
||||
"name": "ebusd",
|
||||
"documentation": "https://www.home-assistant.io/integrations/ebusd",
|
||||
"requirements": ["ebusdpy==0.0.16"],
|
||||
"requirements": ["ebusdpy==0.0.17"],
|
||||
"codeowners": [],
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
@ -94,6 +94,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
firmware_date=None,
|
||||
model_info=None,
|
||||
model_description=None,
|
||||
remote_access_enabled=None,
|
||||
remote_access_host=None,
|
||||
remote_access_port=None,
|
||||
)
|
||||
return await self._async_handle_discovery()
|
||||
|
||||
@ -261,6 +264,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
firmware_date=None,
|
||||
model_info=None,
|
||||
model_description=bulb.model_data.description,
|
||||
remote_access_enabled=None,
|
||||
remote_access_host=None,
|
||||
remote_access_port=None,
|
||||
)
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Flux LED/MagicHome",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||
"requirements": ["flux_led==0.26.7"],
|
||||
"requirements": ["flux_led==0.26.15"],
|
||||
"quality_scale": "platinum",
|
||||
"codeowners": ["@icemanch"],
|
||||
"iot_class": "local_push",
|
||||
|
@ -120,10 +120,11 @@ class Light(HomeAccessory):
|
||||
if self._event_timer:
|
||||
self._event_timer()
|
||||
self._event_timer = async_call_later(
|
||||
self.hass, CHANGE_COALESCE_TIME_WINDOW, self._send_events
|
||||
self.hass, CHANGE_COALESCE_TIME_WINDOW, self._async_send_events
|
||||
)
|
||||
|
||||
def _send_events(self, *_):
|
||||
@callback
|
||||
def _async_send_events(self, *_):
|
||||
"""Process all changes at once."""
|
||||
_LOGGER.debug("Coalesced _set_chars: %s", self._pending_events)
|
||||
char_values = self._pending_events
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Philips Hue",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/hue",
|
||||
"requirements": ["aiohue==3.0.5"],
|
||||
"requirements": ["aiohue==3.0.6"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
|
@ -146,8 +146,10 @@ async def hue_activate_scene_v2(
|
||||
continue
|
||||
# found match!
|
||||
if transition:
|
||||
transition = transition * 100 # in steps of 100ms
|
||||
await api.scenes.recall(scene.id, dynamic=dynamic, duration=transition)
|
||||
transition = transition * 1000 # transition is in ms
|
||||
await bridge.async_request_call(
|
||||
api.scenes.recall, scene.id, dynamic=dynamic, duration=transition
|
||||
)
|
||||
return True
|
||||
LOGGER.debug(
|
||||
"Unable to find scene %s for group %s on bridge %s",
|
||||
|
@ -29,6 +29,7 @@ from homeassistant.const import (
|
||||
CONF_PORT,
|
||||
CONF_TYPE,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||
@ -44,6 +45,7 @@ from .const import (
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS,
|
||||
CONF_KNX_ROUTING,
|
||||
CONF_KNX_TUNNELING,
|
||||
DATA_HASS_CONFIG,
|
||||
DATA_KNX_CONFIG,
|
||||
DOMAIN,
|
||||
KNX_ADDRESS,
|
||||
@ -195,6 +197,7 @@ SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any(
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Start the KNX integration."""
|
||||
hass.data[DATA_HASS_CONFIG] = config
|
||||
conf: ConfigType | None = config.get(DOMAIN)
|
||||
|
||||
if conf is None:
|
||||
@ -251,15 +254,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
|
||||
hass.config_entries.async_setup_platforms(
|
||||
entry, [platform for platform in SUPPORTED_PLATFORMS if platform in config]
|
||||
entry,
|
||||
[
|
||||
platform
|
||||
for platform in SUPPORTED_PLATFORMS
|
||||
if platform in config and platform is not Platform.NOTIFY
|
||||
],
|
||||
)
|
||||
|
||||
# set up notify platform, no entry support for notify component yet,
|
||||
# have to use discovery to load platform.
|
||||
if NotifySchema.PLATFORM in conf:
|
||||
# set up notify platform, no entry support for notify component yet
|
||||
if NotifySchema.PLATFORM in config:
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
hass, "notify", DOMAIN, conf[NotifySchema.PLATFORM], config
|
||||
hass, "notify", DOMAIN, {}, hass.data[DATA_HASS_CONFIG]
|
||||
)
|
||||
)
|
||||
|
||||
@ -312,6 +319,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
platform
|
||||
for platform in SUPPORTED_PLATFORMS
|
||||
if platform in hass.data[DATA_KNX_CONFIG]
|
||||
and platform is not Platform.NOTIFY
|
||||
],
|
||||
)
|
||||
if unload_ok:
|
||||
|
@ -42,7 +42,10 @@ CONF_STATE_ADDRESS: Final = "state_address"
|
||||
CONF_SYNC_STATE: Final = "sync_state"
|
||||
CONF_KNX_INITIAL_CONNECTION_TYPES: Final = [CONF_KNX_TUNNELING, CONF_KNX_ROUTING]
|
||||
|
||||
# yaml config merged with config entry data
|
||||
DATA_KNX_CONFIG: Final = "knx_config"
|
||||
# original hass yaml config
|
||||
DATA_HASS_CONFIG: Final = "knx_hass_config"
|
||||
|
||||
ATTR_COUNTER: Final = "counter"
|
||||
ATTR_SOURCE: Final = "source"
|
||||
|
@ -11,7 +11,8 @@ from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DOMAIN, KNX_ADDRESS
|
||||
from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
|
||||
from .schema import NotifySchema
|
||||
|
||||
|
||||
async def async_get_service(
|
||||
@ -20,10 +21,10 @@ async def async_get_service(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> KNXNotificationService | None:
|
||||
"""Get the KNX notification service."""
|
||||
if not discovery_info:
|
||||
if discovery_info is None:
|
||||
return None
|
||||
|
||||
platform_config: dict = discovery_info
|
||||
if platform_config := hass.data[DATA_KNX_CONFIG].get(NotifySchema.PLATFORM):
|
||||
xknx: XKNX = hass.data[DOMAIN].xknx
|
||||
|
||||
notification_devices = []
|
||||
@ -36,9 +37,13 @@ async def async_get_service(
|
||||
)
|
||||
)
|
||||
return (
|
||||
KNXNotificationService(notification_devices) if notification_devices else None
|
||||
KNXNotificationService(notification_devices)
|
||||
if notification_devices
|
||||
else None
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class KNXNotificationService(BaseNotificationService):
|
||||
"""Implement demo notification service."""
|
||||
|
@ -112,6 +112,7 @@ def log_entry(hass, name, message, domain=None, entity_id=None, context=None):
|
||||
hass.add_job(async_log_entry, hass, name, message, domain, entity_id, context)
|
||||
|
||||
|
||||
@callback
|
||||
@bind_hass
|
||||
def async_log_entry(hass, name, message, domain=None, entity_id=None, context=None):
|
||||
"""Add an entry to the logbook."""
|
||||
|
@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["ffmpeg", "http", "media_source"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/nest",
|
||||
"requirements": ["python-nest==4.1.0", "google-nest-sdm==0.4.8"],
|
||||
"requirements": ["python-nest==4.1.0", "google-nest-sdm==0.4.9"],
|
||||
"codeowners": ["@allenporter"],
|
||||
"quality_scale": "platinum",
|
||||
"dhcp": [
|
||||
|
@ -214,7 +214,7 @@ class NextBusDepartureSensor(SensorEntity):
|
||||
|
||||
# Generate list of upcoming times
|
||||
self._attributes["upcoming"] = ", ".join(
|
||||
sorted(p["minutes"] for p in predictions)
|
||||
sorted((p["minutes"] for p in predictions), key=int)
|
||||
)
|
||||
|
||||
latest_prediction = maybe_first(predictions)
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""The 1-Wire component."""
|
||||
import logging
|
||||
|
||||
from pyownet import protocol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
@ -18,7 +20,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
onewirehub = OneWireHub(hass)
|
||||
try:
|
||||
await onewirehub.initialize(entry)
|
||||
except CannotConnect as exc:
|
||||
except (
|
||||
CannotConnect, # Failed to connect to the server
|
||||
protocol.OwnetError, # Connected to server, but failed to list the devices
|
||||
) as exc:
|
||||
raise ConfigEntryNotReady() from exc
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = onewirehub
|
||||
|
@ -68,13 +68,12 @@ from .utils import (
|
||||
BLOCK_PLATFORMS: Final = [
|
||||
"binary_sensor",
|
||||
"button",
|
||||
"climate",
|
||||
"cover",
|
||||
"light",
|
||||
"sensor",
|
||||
"switch",
|
||||
]
|
||||
BLOCK_SLEEPING_PLATFORMS: Final = ["binary_sensor", "sensor"]
|
||||
BLOCK_SLEEPING_PLATFORMS: Final = ["binary_sensor", "climate", "sensor"]
|
||||
RPC_PLATFORMS: Final = ["binary_sensor", "button", "light", "sensor", "switch"]
|
||||
_LOGGER: Final = logging.getLogger(__name__)
|
||||
|
||||
|
@ -3,12 +3,13 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Final, cast
|
||||
|
||||
from aioshelly.block_device import Block
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_IDLE,
|
||||
@ -20,11 +21,12 @@ from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.components.shelly import BlockDeviceWrapper
|
||||
from homeassistant.components.shelly.entity import ShellyBlockEntity
|
||||
from homeassistant.components.shelly.utils import get_device_entry_gen
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, State, callback
|
||||
from homeassistant.helpers import device_registry, entity, entity_registry
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
@ -49,10 +51,30 @@ async def async_setup_entry(
|
||||
if get_device_entry_gen(config_entry) == 2:
|
||||
return
|
||||
|
||||
wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
][BLOCK]
|
||||
|
||||
if wrapper.device.initialized:
|
||||
await async_setup_climate_entities(async_add_entities, wrapper)
|
||||
else:
|
||||
await async_restore_climate_entities(
|
||||
hass, config_entry, async_add_entities, wrapper
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_climate_entities(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
) -> None:
|
||||
"""Set up online climate devices."""
|
||||
|
||||
_LOGGER.info("Setup online climate device %s", wrapper.name)
|
||||
device_block: Block | None = None
|
||||
sensor_block: Block | None = None
|
||||
|
||||
wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][BLOCK]
|
||||
assert wrapper.device.blocks
|
||||
|
||||
for block in wrapper.device.blocks:
|
||||
if block.type == "device":
|
||||
device_block = block
|
||||
@ -60,10 +82,37 @@ async def async_setup_entry(
|
||||
sensor_block = block
|
||||
|
||||
if sensor_block and device_block:
|
||||
async_add_entities([ShellyClimate(wrapper, sensor_block, device_block)])
|
||||
async_add_entities([BlockSleepingClimate(wrapper, sensor_block, device_block)])
|
||||
|
||||
|
||||
class ShellyClimate(ShellyBlockEntity, RestoreEntity, ClimateEntity):
|
||||
async def async_restore_climate_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
) -> None:
|
||||
"""Restore sleeping climate devices."""
|
||||
_LOGGER.info("Setup sleeping climate device %s", wrapper.name)
|
||||
|
||||
ent_reg = await entity_registry.async_get_registry(hass)
|
||||
entries = entity_registry.async_entries_for_config_entry(
|
||||
ent_reg, config_entry.entry_id
|
||||
)
|
||||
|
||||
for entry in entries:
|
||||
|
||||
if entry.domain != CLIMATE_DOMAIN:
|
||||
continue
|
||||
|
||||
_LOGGER.debug("Found entry %s [%s]", entry.original_name, entry.domain)
|
||||
async_add_entities([BlockSleepingClimate(wrapper, None, None, entry)])
|
||||
|
||||
|
||||
class BlockSleepingClimate(
|
||||
RestoreEntity,
|
||||
ClimateEntity,
|
||||
entity.Entity,
|
||||
):
|
||||
"""Representation of a Shelly climate device."""
|
||||
|
||||
_attr_hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_HEAT]
|
||||
@ -74,45 +123,77 @@ class ShellyClimate(ShellyBlockEntity, RestoreEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"]
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(
|
||||
self, wrapper: BlockDeviceWrapper, sensor_block: Block, device_block: Block
|
||||
self,
|
||||
wrapper: BlockDeviceWrapper,
|
||||
sensor_block: Block | None,
|
||||
device_block: Block | None,
|
||||
entry: entity_registry.RegistryEntry | None = None,
|
||||
) -> None:
|
||||
"""Initialize climate."""
|
||||
super().__init__(wrapper, sensor_block)
|
||||
|
||||
self.device_block = device_block
|
||||
|
||||
assert self.block.channel
|
||||
|
||||
self.wrapper = wrapper
|
||||
self.block: Block | None = sensor_block
|
||||
self.control_result: dict[str, Any] | None = None
|
||||
self.device_block: Block | None = device_block
|
||||
self.last_state: State | None = None
|
||||
self.last_state_attributes: MappingProxyType[str, Any]
|
||||
self._preset_modes: list[str] = []
|
||||
|
||||
self._attr_name = self.wrapper.name
|
||||
self._attr_unique_id = self.wrapper.mac
|
||||
self._attr_preset_modes: list[str] = [
|
||||
if self.block is not None and self.device_block is not None:
|
||||
self._unique_id = f"{self.wrapper.mac}-{self.block.description}"
|
||||
assert self.block.channel
|
||||
self._preset_modes = [
|
||||
PRESET_NONE,
|
||||
*wrapper.device.settings["thermostats"][int(self.block.channel)][
|
||||
"schedule_profile_names"
|
||||
],
|
||||
]
|
||||
elif entry is not None:
|
||||
self._unique_id = entry.unique_id
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Set unique id of entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Name of entity."""
|
||||
return self.wrapper.name
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""If device should be polled."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
"""Set target temperature."""
|
||||
if self.block is not None:
|
||||
return cast(float, self.block.targetTemp)
|
||||
return self.last_state_attributes.get("temperature")
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return current temperature."""
|
||||
if self.block is not None:
|
||||
return cast(float, self.block.temp)
|
||||
return self.last_state_attributes.get("current_temperature")
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Device availability."""
|
||||
if self.device_block is not None:
|
||||
return not cast(bool, self.device_block.valveError)
|
||||
return self.wrapper.last_update_success
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""HVAC current mode."""
|
||||
if self.device_block is None:
|
||||
return self.last_state.state if self.last_state else HVAC_MODE_OFF
|
||||
if self.device_block.mode is None or self._check_is_off():
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@ -121,20 +202,45 @@ class ShellyClimate(ShellyBlockEntity, RestoreEntity, ClimateEntity):
|
||||
@property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Preset current mode."""
|
||||
if self.device_block is None:
|
||||
return self.last_state_attributes.get("preset_mode")
|
||||
if self.device_block.mode is None:
|
||||
return None
|
||||
return self._attr_preset_modes[cast(int, self.device_block.mode)]
|
||||
return PRESET_NONE
|
||||
return self._preset_modes[cast(int, self.device_block.mode)]
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> str | None:
|
||||
"""HVAC current action."""
|
||||
if self.device_block.status is None or self._check_is_off():
|
||||
if (
|
||||
self.device_block is None
|
||||
or self.device_block.status is None
|
||||
or self._check_is_off()
|
||||
):
|
||||
return CURRENT_HVAC_OFF
|
||||
|
||||
return (
|
||||
CURRENT_HVAC_IDLE if self.device_block.status == "0" else CURRENT_HVAC_HEAT
|
||||
)
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> list[str]:
|
||||
"""Preset available modes."""
|
||||
return self._preset_modes
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Device info."""
|
||||
return {
|
||||
"connections": {(device_registry.CONNECTION_NETWORK_MAC, self.wrapper.mac)}
|
||||
}
|
||||
|
||||
@property
|
||||
def channel(self) -> str | None:
|
||||
"""Device channel."""
|
||||
if self.block is not None:
|
||||
return self.block.channel
|
||||
return self.last_state_attributes.get("channel")
|
||||
|
||||
def _check_is_off(self) -> bool:
|
||||
"""Return if valve is off or on."""
|
||||
return bool(
|
||||
@ -148,7 +254,7 @@ class ShellyClimate(ShellyBlockEntity, RestoreEntity, ClimateEntity):
|
||||
try:
|
||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||
return await self.wrapper.device.http_request(
|
||||
"get", f"thermostat/{self.block.channel}", kwargs
|
||||
"get", f"thermostat/{self.channel}", kwargs
|
||||
)
|
||||
except (asyncio.TimeoutError, OSError) as err:
|
||||
_LOGGER.error(
|
||||
@ -186,3 +292,41 @@ class ShellyClimate(ShellyBlockEntity, RestoreEntity, ClimateEntity):
|
||||
await self.set_state_full_path(
|
||||
schedule=1, schedule_profile=f"{preset_index}"
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity which will be added."""
|
||||
_LOGGER.info("Restoring entity %s", self.name)
|
||||
|
||||
last_state = await self.async_get_last_state()
|
||||
|
||||
if last_state is not None:
|
||||
self.last_state = last_state
|
||||
self.last_state_attributes = self.last_state.attributes
|
||||
self._preset_modes = cast(
|
||||
list, self.last_state.attributes.get("preset_modes")
|
||||
)
|
||||
|
||||
self.async_on_remove(self.wrapper.async_add_listener(self._update_callback))
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update entity with latest info."""
|
||||
await self.wrapper.async_request_refresh()
|
||||
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
"""Handle device update."""
|
||||
if not self.wrapper.device.initialized:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
assert self.wrapper.device.blocks
|
||||
|
||||
for block in self.wrapper.device.blocks:
|
||||
if block.type == "device":
|
||||
self.device_block = block
|
||||
if hasattr(block, "targetTemp"):
|
||||
self.block = block
|
||||
|
||||
_LOGGER.debug("Entity %s attached to block", self.name)
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
@ -641,9 +641,13 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
@callback
|
||||
def _update_color(self, render):
|
||||
"""Update the hs_color from the template."""
|
||||
if render is None:
|
||||
self._color = None
|
||||
return
|
||||
|
||||
h_str = s_str = None
|
||||
if isinstance(render, str):
|
||||
if render in (None, "None", ""):
|
||||
if render in ("None", ""):
|
||||
self._color = None
|
||||
return
|
||||
h_str, s_str = map(
|
||||
|
@ -22,6 +22,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import ValloxDataUpdateCoordinator
|
||||
from .const import (
|
||||
@ -107,7 +108,7 @@ class ValloxFilterRemainingSensor(ValloxSensor):
|
||||
days_remaining_delta = timedelta(days=days_remaining)
|
||||
now = datetime.utcnow().replace(hour=13, minute=0, second=0, microsecond=0)
|
||||
|
||||
return now + days_remaining_delta
|
||||
return (now + days_remaining_delta).astimezone(dt_util.UTC)
|
||||
|
||||
|
||||
class ValloxCellStateSensor(ValloxSensor):
|
||||
|
@ -234,6 +234,7 @@ class GroupProbe:
|
||||
unsub()
|
||||
self._unsubs.remove(unsub)
|
||||
|
||||
@callback
|
||||
def _reprobe_group(self, group_id: int) -> None:
|
||||
"""Reprobe a group for entities after its members change."""
|
||||
zha_gateway = self._hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY]
|
||||
|
@ -7,7 +7,7 @@ from homeassistant.backports.enum import StrEnum
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 12
|
||||
PATCH_VERSION: Final = "2"
|
||||
PATCH_VERSION: Final = "3"
|
||||
__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)
|
||||
|
@ -1052,6 +1052,7 @@ class Script:
|
||||
if self._change_listener_job:
|
||||
self._hass.async_run_hass_job(self._change_listener_job)
|
||||
|
||||
@callback
|
||||
def _chain_change_listener(self, sub_script: Script) -> None:
|
||||
if sub_script.is_running:
|
||||
self.last_action = sub_script.last_action
|
||||
|
@ -186,7 +186,7 @@ aiohomekit==0.6.4
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==3.0.5
|
||||
aiohue==3.0.6
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==0.9.0
|
||||
@ -440,7 +440,7 @@ brother==1.1.0
|
||||
brottsplatskartan==0.0.1
|
||||
|
||||
# homeassistant.components.brunt
|
||||
brunt==1.0.1
|
||||
brunt==1.0.2
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
bsblan==0.4.0
|
||||
@ -573,7 +573,7 @@ dweepy==0.3.0
|
||||
dynalite_devices==0.1.46
|
||||
|
||||
# homeassistant.components.ebusd
|
||||
ebusdpy==0.0.16
|
||||
ebusdpy==0.0.17
|
||||
|
||||
# homeassistant.components.ecoal_boiler
|
||||
ecoaliface==0.4.0
|
||||
@ -658,7 +658,7 @@ fjaraskupan==1.0.2
|
||||
flipr-api==1.4.1
|
||||
|
||||
# homeassistant.components.flux_led
|
||||
flux_led==0.26.7
|
||||
flux_led==0.26.15
|
||||
|
||||
# homeassistant.components.homekit
|
||||
fnvhash==0.1.0
|
||||
@ -738,7 +738,7 @@ google-cloud-pubsub==2.1.0
|
||||
google-cloud-texttospeech==0.4.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
google-nest-sdm==0.4.8
|
||||
google-nest-sdm==0.4.9
|
||||
|
||||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
|
@ -131,7 +131,7 @@ aiohomekit==0.6.4
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==3.0.5
|
||||
aiohue==3.0.6
|
||||
|
||||
# homeassistant.components.apache_kafka
|
||||
aiokafka==0.6.0
|
||||
@ -281,7 +281,7 @@ broadlink==0.18.0
|
||||
brother==1.1.0
|
||||
|
||||
# homeassistant.components.brunt
|
||||
brunt==1.0.1
|
||||
brunt==1.0.2
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
bsblan==0.4.0
|
||||
@ -399,7 +399,7 @@ fjaraskupan==1.0.2
|
||||
flipr-api==1.4.1
|
||||
|
||||
# homeassistant.components.flux_led
|
||||
flux_led==0.26.7
|
||||
flux_led==0.26.15
|
||||
|
||||
# homeassistant.components.homekit
|
||||
fnvhash==0.1.0
|
||||
@ -461,7 +461,7 @@ google-api-python-client==1.6.4
|
||||
google-cloud-pubsub==2.1.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
google-nest-sdm==0.4.8
|
||||
google-nest-sdm==0.4.9
|
||||
|
||||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
|
@ -40,6 +40,7 @@ BASIC_RESULTS = {
|
||||
{"minutes": "1", "epochTime": "1553807371000"},
|
||||
{"minutes": "2", "epochTime": "1553807372000"},
|
||||
{"minutes": "3", "epochTime": "1553807373000"},
|
||||
{"minutes": "10", "epochTime": "1553807380000"},
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -128,7 +129,7 @@ async def test_verify_valid_state(
|
||||
assert state.attributes["route"] == VALID_ROUTE_TITLE
|
||||
assert state.attributes["stop"] == VALID_STOP_TITLE
|
||||
assert state.attributes["direction"] == "Outbound"
|
||||
assert state.attributes["upcoming"] == "1, 2, 3"
|
||||
assert state.attributes["upcoming"] == "1, 2, 3, 10"
|
||||
|
||||
|
||||
async def test_message_dict(
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Tests for 1-Wire config flow."""
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from pyownet import protocol
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.onewire.const import DOMAIN
|
||||
@ -19,6 +21,20 @@ async def test_owserver_connect_failure(hass: HomeAssistant, config_entry: Confi
|
||||
assert not hass.data.get(DOMAIN)
|
||||
|
||||
|
||||
async def test_owserver_listing_failure(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock
|
||||
):
|
||||
"""Test listing failure raises ConfigEntryNotReady."""
|
||||
owproxy.return_value.dir.side_effect = protocol.OwnetError()
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert not hass.data.get(DOMAIN)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("owproxy")
|
||||
async def test_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
"""Test being able to unload an entry."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user