mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
2023.8.2 (#98255)
This commit is contained in:
commit
73898daff3
@ -8,5 +8,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyairvisual", "pysmb"],
|
||||
"requirements": ["pyairvisual==2022.12.1"]
|
||||
"requirements": ["pyairvisual==2023.08.1"]
|
||||
}
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyairvisual", "pysmb"],
|
||||
"requirements": ["pyairvisual==2022.12.1"]
|
||||
"requirements": ["pyairvisual==2023.08.1"]
|
||||
}
|
||||
|
@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.6.4"]
|
||||
"requirements": ["aioairzone==0.6.5"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aladdin_connect"],
|
||||
"requirements": ["AIOAladdinConnect==0.1.56"]
|
||||
"requirements": ["AIOAladdinConnect==0.1.57"]
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.const import (
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HassJob, HomeAssistant
|
||||
from homeassistant.exceptions import ServiceNotFound
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
@ -293,9 +294,15 @@ class Alert(Entity):
|
||||
LOGGER.debug(msg_payload)
|
||||
|
||||
for target in self._notifiers:
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, msg_payload, context=self._context
|
||||
)
|
||||
try:
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, msg_payload, context=self._context
|
||||
)
|
||||
except ServiceNotFound:
|
||||
LOGGER.error(
|
||||
"Failed to call notify.%s, retrying at next notification interval",
|
||||
target,
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Async Unacknowledge alert."""
|
||||
|
@ -58,6 +58,7 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
assert self.host
|
||||
api = create_api(self.hass, self.host, enable_ime=False)
|
||||
try:
|
||||
await api.async_generate_cert_if_missing()
|
||||
self.name, self.mac = await api.async_get_name_and_mac()
|
||||
assert self.mac
|
||||
await self.async_set_unique_id(format_mac(self.mac))
|
||||
|
@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyatv", "srptools"],
|
||||
"requirements": ["pyatv==0.13.3"],
|
||||
"requirements": ["pyatv==0.13.4"],
|
||||
"zeroconf": [
|
||||
"_mediaremotetv._tcp.local.",
|
||||
"_companion-link._tcp.local.",
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bimmer_connected"],
|
||||
"requirements": ["bimmer-connected==0.13.8"]
|
||||
"requirements": ["bimmer-connected==0.13.9"]
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ class BroadlinkLight(BroadlinkEntity, LightEntity):
|
||||
"""Representation of a Broadlink light."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize the light."""
|
||||
|
@ -207,7 +207,8 @@ class CommandSensor(ManualTriggerEntity, SensorEntity):
|
||||
self._process_manual_data(value)
|
||||
return
|
||||
|
||||
if self._value_template is not None:
|
||||
self._attr_native_value = None
|
||||
if self._value_template is not None and value is not None:
|
||||
value = self._value_template.async_render_with_possible_json_value(
|
||||
value,
|
||||
None,
|
||||
@ -221,7 +222,6 @@ class CommandSensor(ManualTriggerEntity, SensorEntity):
|
||||
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
|
||||
|
@ -160,6 +160,15 @@ HostAttributes = TypedDict(
|
||||
)
|
||||
|
||||
|
||||
class HostInfo(TypedDict):
|
||||
"""FRITZ!Box host info class."""
|
||||
|
||||
mac: str
|
||||
name: str
|
||||
ip: str
|
||||
status: bool
|
||||
|
||||
|
||||
class UpdateCoordinatorDataType(TypedDict):
|
||||
"""Update coordinator data type."""
|
||||
|
||||
@ -380,16 +389,86 @@ class FritzBoxTools(
|
||||
"""Event specific per FRITZ!Box entry to signal updates in devices."""
|
||||
return f"{DOMAIN}-device-update-{self._unique_id}"
|
||||
|
||||
async def _async_update_hosts_info(self) -> list[HostAttributes]:
|
||||
"""Retrieve latest hosts information from the FRITZ!Box."""
|
||||
async def _async_get_wan_access(self, ip_address: str) -> bool | None:
|
||||
"""Get WAN access rule for given IP address."""
|
||||
try:
|
||||
return await self.hass.async_add_executor_job(
|
||||
self.fritz_hosts.get_hosts_attributes
|
||||
wan_access = await self.hass.async_add_executor_job(
|
||||
partial(
|
||||
self.connection.call_action,
|
||||
"X_AVM-DE_HostFilter:1",
|
||||
"GetWANAccessByIP",
|
||||
NewIPv4Address=ip_address,
|
||||
)
|
||||
)
|
||||
return not wan_access.get("NewDisallow")
|
||||
except FRITZ_EXCEPTIONS as ex:
|
||||
_LOGGER.debug(
|
||||
(
|
||||
"could not get WAN access rule for client device with IP '%s',"
|
||||
" error: %s"
|
||||
),
|
||||
ip_address,
|
||||
ex,
|
||||
)
|
||||
return None
|
||||
|
||||
async def _async_update_hosts_info(self) -> dict[str, Device]:
|
||||
"""Retrieve latest hosts information from the FRITZ!Box."""
|
||||
hosts_attributes: list[HostAttributes] = []
|
||||
hosts_info: list[HostInfo] = []
|
||||
try:
|
||||
try:
|
||||
hosts_attributes = await self.hass.async_add_executor_job(
|
||||
self.fritz_hosts.get_hosts_attributes
|
||||
)
|
||||
except FritzActionError:
|
||||
hosts_info = await self.hass.async_add_executor_job(
|
||||
self.fritz_hosts.get_hosts_info
|
||||
)
|
||||
except Exception as ex: # pylint: disable=[broad-except]
|
||||
if not self.hass.is_stopping:
|
||||
raise HomeAssistantError("Error refreshing hosts info") from ex
|
||||
return []
|
||||
|
||||
hosts: dict[str, Device] = {}
|
||||
if hosts_attributes:
|
||||
for attributes in hosts_attributes:
|
||||
if not attributes.get("MACAddress"):
|
||||
continue
|
||||
|
||||
if (wan_access := attributes.get("X_AVM-DE_WANAccess")) is not None:
|
||||
wan_access_result = "granted" in wan_access
|
||||
else:
|
||||
wan_access_result = None
|
||||
|
||||
hosts[attributes["MACAddress"]] = Device(
|
||||
name=attributes["HostName"],
|
||||
connected=attributes["Active"],
|
||||
connected_to="",
|
||||
connection_type="",
|
||||
ip_address=attributes["IPAddress"],
|
||||
ssid=None,
|
||||
wan_access=wan_access_result,
|
||||
)
|
||||
else:
|
||||
for info in hosts_info:
|
||||
if not info.get("mac"):
|
||||
continue
|
||||
|
||||
if info["ip"]:
|
||||
wan_access_result = await self._async_get_wan_access(info["ip"])
|
||||
else:
|
||||
wan_access_result = None
|
||||
|
||||
hosts[info["mac"]] = Device(
|
||||
name=info["name"],
|
||||
connected=info["status"],
|
||||
connected_to="",
|
||||
connection_type="",
|
||||
ip_address=info["ip"],
|
||||
ssid=None,
|
||||
wan_access=wan_access_result,
|
||||
)
|
||||
return hosts
|
||||
|
||||
def _update_device_info(self) -> tuple[bool, str | None, str | None]:
|
||||
"""Retrieve latest device information from the FRITZ!Box."""
|
||||
@ -464,25 +543,7 @@ class FritzBoxTools(
|
||||
consider_home = _default_consider_home
|
||||
|
||||
new_device = False
|
||||
hosts = {}
|
||||
for host in await self._async_update_hosts_info():
|
||||
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"],
|
||||
connected_to="",
|
||||
connection_type="",
|
||||
ip_address=host["IPAddress"],
|
||||
ssid=None,
|
||||
wan_access=wan_access_result,
|
||||
)
|
||||
hosts = await self._async_update_hosts_info()
|
||||
|
||||
if not self.fritz_status.device_has_mesh_support or (
|
||||
self._options
|
||||
@ -584,9 +645,7 @@ class FritzBoxTools(
|
||||
self, config_entry: ConfigEntry | None = None
|
||||
) -> None:
|
||||
"""Trigger device trackers cleanup."""
|
||||
device_hosts_list = await self.hass.async_add_executor_job(
|
||||
self.fritz_hosts.get_hosts_attributes
|
||||
)
|
||||
device_hosts = await self._async_update_hosts_info()
|
||||
entity_reg: er.EntityRegistry = er.async_get(self.hass)
|
||||
|
||||
if config_entry is None:
|
||||
@ -601,9 +660,9 @@ class FritzBoxTools(
|
||||
|
||||
device_hosts_macs = set()
|
||||
device_hosts_names = set()
|
||||
for device in device_hosts_list:
|
||||
device_hosts_macs.add(device["MACAddress"])
|
||||
device_hosts_names.add(device["HostName"])
|
||||
for mac, device in device_hosts.items():
|
||||
device_hosts_macs.add(mac)
|
||||
device_hosts_names.add(device.name)
|
||||
|
||||
for entry in ha_entity_reg_list:
|
||||
if entry.original_name is None:
|
||||
|
@ -62,11 +62,11 @@ DESCRIPTIONS = (
|
||||
GardenaBluetoothNumberEntityDescription(
|
||||
key=DeviceConfiguration.rain_pause.uuid,
|
||||
translation_key="rain_pause",
|
||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||
mode=NumberMode.BOX,
|
||||
native_min_value=0.0,
|
||||
native_max_value=127.0,
|
||||
native_step=1.0,
|
||||
native_max_value=7 * 24 * 60,
|
||||
native_step=6 * 60.0,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
char=DeviceConfiguration.rain_pause,
|
||||
),
|
||||
|
@ -117,3 +117,8 @@ class GardenaBluetoothRemainSensor(GardenaBluetoothEntity, SensorEntity):
|
||||
self._attr_native_value = time
|
||||
super()._handle_coordinator_update()
|
||||
return
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Sensor only available when open."""
|
||||
return super().available and self._attr_native_value is not None
|
||||
|
@ -15,7 +15,8 @@
|
||||
"cannot_connect": "Failed to connect: {error}"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
|
@ -80,11 +80,11 @@ class TriggerSource:
|
||||
self._iid_trigger_keys.setdefault(iid, set()).add(trigger_key)
|
||||
await connection.add_watchable_characteristics([(aid, iid)])
|
||||
|
||||
def fire(self, iid: int, value: dict[str, Any]) -> None:
|
||||
def fire(self, iid: int, ev: dict[str, Any]) -> None:
|
||||
"""Process events that have been received from a HomeKit accessory."""
|
||||
for trigger_key in self._iid_trigger_keys.get(iid, set()):
|
||||
for event_handler in self._callbacks.get(trigger_key, []):
|
||||
event_handler(value)
|
||||
event_handler(ev)
|
||||
|
||||
def async_get_triggers(self) -> Generator[tuple[str, str], None, None]:
|
||||
"""List device triggers for HomeKit devices."""
|
||||
@ -99,20 +99,23 @@ class TriggerSource:
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Attach a trigger."""
|
||||
trigger_data = trigger_info["trigger_data"]
|
||||
trigger_key = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||
type_: str = config[CONF_TYPE]
|
||||
sub_type: str = config[CONF_SUBTYPE]
|
||||
trigger_key = (type_, sub_type)
|
||||
job = HassJob(action)
|
||||
trigger_callbacks = self._callbacks.setdefault(trigger_key, [])
|
||||
hass = self._hass
|
||||
|
||||
@callback
|
||||
def event_handler(char: dict[str, Any]) -> None:
|
||||
if config[CONF_SUBTYPE] != HK_TO_HA_INPUT_EVENT_VALUES[char["value"]]:
|
||||
def event_handler(ev: dict[str, Any]) -> None:
|
||||
if sub_type != HK_TO_HA_INPUT_EVENT_VALUES[ev["value"]]:
|
||||
return
|
||||
self._hass.async_run_hass_job(job, {"trigger": {**trigger_data, **config}})
|
||||
hass.async_run_hass_job(job, {"trigger": {**trigger_data, **config}})
|
||||
|
||||
self._callbacks.setdefault(trigger_key, []).append(event_handler)
|
||||
trigger_callbacks.append(event_handler)
|
||||
|
||||
def async_remove_handler():
|
||||
if trigger_key in self._callbacks:
|
||||
self._callbacks[trigger_key].remove(event_handler)
|
||||
trigger_callbacks.remove(event_handler)
|
||||
|
||||
return async_remove_handler
|
||||
|
||||
@ -262,7 +265,10 @@ def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], dict[str,
|
||||
if aid in conn.devices:
|
||||
device_id = conn.devices[aid]
|
||||
if source := trigger_sources.get(device_id):
|
||||
source.fire(iid, ev)
|
||||
# If the value is None, we received the event via polling
|
||||
# and we don't want to trigger on that
|
||||
if ev.get("value") is not None:
|
||||
source.fire(iid, ev)
|
||||
|
||||
|
||||
async def async_get_triggers(
|
||||
|
@ -5,6 +5,7 @@ from typing import Any
|
||||
|
||||
from aiohomekit.model import Accessory
|
||||
from aiohomekit.model.characteristics import (
|
||||
EVENT_CHARACTERISTICS,
|
||||
Characteristic,
|
||||
CharacteristicPermissions,
|
||||
CharacteristicsTypes,
|
||||
@ -111,7 +112,10 @@ class HomeKitEntity(Entity):
|
||||
def _setup_characteristic(self, char: Characteristic) -> None:
|
||||
"""Configure an entity based on a HomeKit characteristics metadata."""
|
||||
# Build up a list of (aid, iid) tuples to poll on update()
|
||||
if CharacteristicPermissions.paired_read in char.perms:
|
||||
if (
|
||||
CharacteristicPermissions.paired_read in char.perms
|
||||
and char.type not in EVENT_CHARACTERISTICS
|
||||
):
|
||||
self.pollable_characteristics.append((self._aid, char.iid))
|
||||
|
||||
# Build up a list of (aid, iid) tuples to subscribe to
|
||||
|
@ -14,6 +14,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiohomekit", "commentjson"],
|
||||
"requirements": ["aiohomekit==2.6.12"],
|
||||
"requirements": ["aiohomekit==2.6.15"],
|
||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||
}
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pymazda"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pymazda==0.3.10"]
|
||||
"requirements": ["pymazda==0.3.11"]
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
"fields": {
|
||||
"position": {
|
||||
"name": "Position",
|
||||
"description": "Horizontal vane position. Possible options can be found in the vane_horizontal_positions state attribute.\n."
|
||||
"description": "Horizontal vane position. Possible options can be found in the vane_horizontal_positions state attribute."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -36,7 +36,7 @@
|
||||
"fields": {
|
||||
"position": {
|
||||
"name": "Position",
|
||||
"description": "Vertical vane position. Possible options can be found in the vane_vertical_positions state attribute.\n."
|
||||
"description": "Vertical vane position. Possible options can be found in the vane_vertical_positions state attribute."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,14 +51,12 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="name",
|
||||
name="Station name",
|
||||
device_class=None,
|
||||
icon="mdi:label-outline",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weather",
|
||||
name="Weather",
|
||||
device_class=None,
|
||||
icon="mdi:weather-sunny", # but will adapt to current conditions
|
||||
entity_registry_enabled_default=True,
|
||||
),
|
||||
@ -107,7 +105,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="visibility",
|
||||
name="Visibility",
|
||||
device_class=None,
|
||||
icon="mdi:eye",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
@ -115,14 +112,12 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
key="visibility_distance",
|
||||
name="Visibility distance",
|
||||
native_unit_of_measurement=UnitOfLength.KILOMETERS,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
icon="mdi:eye",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="uv",
|
||||
name="UV index",
|
||||
device_class=None,
|
||||
native_unit_of_measurement=UV_INDEX,
|
||||
icon="mdi:weather-sunny-alert",
|
||||
entity_registry_enabled_default=True,
|
||||
@ -130,7 +125,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="precipitation",
|
||||
name="Probability of precipitation",
|
||||
device_class=None,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
icon="mdi:weather-rainy",
|
||||
entity_registry_enabled_default=True,
|
||||
|
@ -49,7 +49,7 @@ async def async_setup_platform(
|
||||
hub = get_hub(hass, discovery_info[CONF_NAME])
|
||||
for entry in discovery_info[CONF_SENSORS]:
|
||||
slave_count = entry.get(CONF_SLAVE_COUNT, 0)
|
||||
sensor = ModbusRegisterSensor(hub, entry)
|
||||
sensor = ModbusRegisterSensor(hub, entry, slave_count)
|
||||
if slave_count > 0:
|
||||
sensors.extend(await sensor.async_setup_slaves(hass, slave_count, entry))
|
||||
sensors.append(sensor)
|
||||
@ -63,9 +63,12 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
||||
self,
|
||||
hub: ModbusHub,
|
||||
entry: dict[str, Any],
|
||||
slave_count: int,
|
||||
) -> None:
|
||||
"""Initialize the modbus register sensor."""
|
||||
super().__init__(hub, entry)
|
||||
if slave_count:
|
||||
self._count = self._count * slave_count
|
||||
self._coordinator: DataUpdateCoordinator[list[int] | None] | None = None
|
||||
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
self._attr_state_class = entry.get(CONF_STATE_CLASS)
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/nina",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pynina"],
|
||||
"requirements": ["PyNINA==0.3.1"]
|
||||
"requirements": ["PyNINA==0.3.2"]
|
||||
}
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["metar", "pynws"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pynws==1.5.0"]
|
||||
"requirements": ["pynws==1.5.1"]
|
||||
}
|
||||
|
@ -69,12 +69,12 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
|
||||
raise ConfigEntryAuthFailed from err
|
||||
forecasts: list[Forecast] = await self.api.async_get_forecast()
|
||||
_LOGGER.debug("Updating sensor data with: %s", forecasts)
|
||||
await self._insert_statistics([forecast.account for forecast in forecasts])
|
||||
await self._insert_statistics()
|
||||
return {forecast.account.utility_account_id: forecast for forecast in forecasts}
|
||||
|
||||
async def _insert_statistics(self, accounts: list[Account]) -> None:
|
||||
async def _insert_statistics(self) -> None:
|
||||
"""Insert Opower statistics."""
|
||||
for account in accounts:
|
||||
for account in await self.api.async_get_accounts():
|
||||
id_prefix = "_".join(
|
||||
(
|
||||
self.api.utility.subdomain(),
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["recorder"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["opower==0.0.20"]
|
||||
"requirements": ["opower==0.0.26"]
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ async def async_setup_entry(
|
||||
sensors = ELEC_SENSORS
|
||||
elif (
|
||||
forecast.account.meter_type == MeterType.GAS
|
||||
and forecast.unit_of_measure == UnitOfMeasure.THERM
|
||||
and forecast.unit_of_measure in [UnitOfMeasure.THERM, UnitOfMeasure.CCF]
|
||||
):
|
||||
sensors = GAS_SENSORS
|
||||
for sensor in sensors:
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": ["python-roborock==0.31.1"]
|
||||
"requirements": ["python-roborock==0.32.2"]
|
||||
}
|
||||
|
@ -15,5 +15,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysensibo"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pysensibo==1.0.32"]
|
||||
"requirements": ["pysensibo==1.0.33"]
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
config_entry_id=entry.entry_id,
|
||||
configuration_url=printer.url,
|
||||
connections=device_connections(printer),
|
||||
default_manufacturer="Samsung",
|
||||
manufacturer="Samsung",
|
||||
identifiers=device_identifiers(printer),
|
||||
model=printer.model(),
|
||||
name=printer.hostname(),
|
||||
|
@ -163,12 +163,11 @@ class TadoConnector:
|
||||
|
||||
def setup(self):
|
||||
"""Connect to Tado and fetch the zones."""
|
||||
self.tado = Tado(self._username, self._password)
|
||||
self.tado.setDebugging(True)
|
||||
self.tado = Tado(self._username, self._password, None, True)
|
||||
# Load zones and devices
|
||||
self.zones = self.tado.getZones()
|
||||
self.devices = self.tado.getDevices()
|
||||
tado_home = self.tado.getMe()["homes"][0]
|
||||
self.zones = self.tado.get_zones()
|
||||
self.devices = self.tado.get_devices()
|
||||
tado_home = self.tado.get_me()["homes"][0]
|
||||
self.home_id = tado_home["id"]
|
||||
self.home_name = tado_home["name"]
|
||||
|
||||
@ -181,7 +180,7 @@ class TadoConnector:
|
||||
|
||||
def update_devices(self):
|
||||
"""Update the device data from Tado."""
|
||||
devices = self.tado.getDevices()
|
||||
devices = self.tado.get_devices()
|
||||
for device in devices:
|
||||
device_short_serial_no = device["shortSerialNo"]
|
||||
_LOGGER.debug("Updating device %s", device_short_serial_no)
|
||||
@ -190,7 +189,7 @@ class TadoConnector:
|
||||
INSIDE_TEMPERATURE_MEASUREMENT
|
||||
in device["characteristics"]["capabilities"]
|
||||
):
|
||||
device[TEMP_OFFSET] = self.tado.getDeviceInfo(
|
||||
device[TEMP_OFFSET] = self.tado.get_device_info(
|
||||
device_short_serial_no, TEMP_OFFSET
|
||||
)
|
||||
except RuntimeError:
|
||||
@ -218,7 +217,7 @@ class TadoConnector:
|
||||
def update_zones(self):
|
||||
"""Update the zone data from Tado."""
|
||||
try:
|
||||
zone_states = self.tado.getZoneStates()["zoneStates"]
|
||||
zone_states = self.tado.get_zone_states()["zoneStates"]
|
||||
except RuntimeError:
|
||||
_LOGGER.error("Unable to connect to Tado while updating zones")
|
||||
return
|
||||
@ -230,7 +229,7 @@ class TadoConnector:
|
||||
"""Update the internal data from Tado."""
|
||||
_LOGGER.debug("Updating zone %s", zone_id)
|
||||
try:
|
||||
data = self.tado.getZoneState(zone_id)
|
||||
data = self.tado.get_zone_state(zone_id)
|
||||
except RuntimeError:
|
||||
_LOGGER.error("Unable to connect to Tado while updating zone %s", zone_id)
|
||||
return
|
||||
@ -251,8 +250,8 @@ class TadoConnector:
|
||||
def update_home(self):
|
||||
"""Update the home data from Tado."""
|
||||
try:
|
||||
self.data["weather"] = self.tado.getWeather()
|
||||
self.data["geofence"] = self.tado.getHomeState()
|
||||
self.data["weather"] = self.tado.get_weather()
|
||||
self.data["geofence"] = self.tado.get_home_state()
|
||||
dispatcher_send(
|
||||
self.hass,
|
||||
SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "home", "data"),
|
||||
@ -265,15 +264,15 @@ class TadoConnector:
|
||||
|
||||
def get_capabilities(self, zone_id):
|
||||
"""Return the capabilities of the devices."""
|
||||
return self.tado.getCapabilities(zone_id)
|
||||
return self.tado.get_capabilities(zone_id)
|
||||
|
||||
def get_auto_geofencing_supported(self):
|
||||
"""Return whether the Tado Home supports auto geofencing."""
|
||||
return self.tado.getAutoGeofencingSupported()
|
||||
return self.tado.get_auto_geofencing_supported()
|
||||
|
||||
def reset_zone_overlay(self, zone_id):
|
||||
"""Reset the zone back to the default operation."""
|
||||
self.tado.resetZoneOverlay(zone_id)
|
||||
self.tado.reset_zone_overlay(zone_id)
|
||||
self.update_zone(zone_id)
|
||||
|
||||
def set_presence(
|
||||
@ -282,11 +281,11 @@ class TadoConnector:
|
||||
):
|
||||
"""Set the presence to home, away or auto."""
|
||||
if presence == PRESET_AWAY:
|
||||
self.tado.setAway()
|
||||
self.tado.set_away()
|
||||
elif presence == PRESET_HOME:
|
||||
self.tado.setHome()
|
||||
self.tado.set_home()
|
||||
elif presence == PRESET_AUTO:
|
||||
self.tado.setAuto()
|
||||
self.tado.set_auto()
|
||||
|
||||
# Update everything when changing modes
|
||||
self.update_zones()
|
||||
@ -320,7 +319,7 @@ class TadoConnector:
|
||||
)
|
||||
|
||||
try:
|
||||
self.tado.setZoneOverlay(
|
||||
self.tado.set_zone_overlay(
|
||||
zone_id,
|
||||
overlay_mode,
|
||||
temperature,
|
||||
@ -340,7 +339,7 @@ class TadoConnector:
|
||||
def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"):
|
||||
"""Set a zone to off."""
|
||||
try:
|
||||
self.tado.setZoneOverlay(
|
||||
self.tado.set_zone_overlay(
|
||||
zone_id, overlay_mode, None, None, device_type, "OFF"
|
||||
)
|
||||
except RequestException as exc:
|
||||
@ -351,6 +350,6 @@ class TadoConnector:
|
||||
def set_temperature_offset(self, device_id, offset):
|
||||
"""Set temperature offset of device."""
|
||||
try:
|
||||
self.tado.setTempOffset(device_id, offset)
|
||||
self.tado.set_temp_offset(device_id, offset)
|
||||
except RequestException as exc:
|
||||
_LOGGER.error("Could not set temperature offset: %s", exc)
|
||||
|
@ -14,5 +14,5 @@
|
||||
},
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["PyTado"],
|
||||
"requirements": ["python-tado==0.15.0"]
|
||||
"requirements": ["python-tado==0.16.0"]
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ class TomorrowioOptionsConfigFlow(config_entries.OptionsFlow):
|
||||
vol.Required(
|
||||
CONF_TIMESTEP,
|
||||
default=self._config_entry.options[CONF_TIMESTEP],
|
||||
): vol.In([1, 5, 15, 30]),
|
||||
): vol.In([1, 5, 15, 30, 60]),
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
|
@ -25,7 +25,7 @@ LOGGER = logging.getLogger(__package__)
|
||||
CONF_TIMESTEP = "timestep"
|
||||
FORECAST_TYPES = [DAILY, HOURLY, NOWCAST]
|
||||
|
||||
DEFAULT_TIMESTEP = 15
|
||||
DEFAULT_TIMESTEP = 60
|
||||
DEFAULT_FORECAST_TYPE = DAILY
|
||||
DOMAIN = "tomorrowio"
|
||||
INTEGRATION_NAME = "Tomorrow.io"
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/tplink_omada",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["tplink_omada_client==1.2.4"]
|
||||
"requirements": ["tplink_omada_client==1.3.2"]
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class TractiveDeviceTracker(TractiveEntity, TrackerEntity):
|
||||
"""Initialize tracker entity."""
|
||||
super().__init__(user_id, item.trackable, item.tracker_details)
|
||||
|
||||
self._battery_level: int = item.hw_info["battery_level"]
|
||||
self._battery_level: int | None = item.hw_info.get("battery_level")
|
||||
self._latitude: float = item.pos_report["latlong"][0]
|
||||
self._longitude: float = item.pos_report["latlong"][1]
|
||||
self._accuracy: int = item.pos_report["pos_uncertainty"]
|
||||
@ -75,7 +75,7 @@ class TractiveDeviceTracker(TractiveEntity, TrackerEntity):
|
||||
return self._accuracy
|
||||
|
||||
@property
|
||||
def battery_level(self) -> int:
|
||||
def battery_level(self) -> int | None:
|
||||
"""Return the battery level of the device."""
|
||||
return self._battery_level
|
||||
|
||||
|
@ -44,6 +44,7 @@ COVERS: dict[str, tuple[TuyaCoverEntityDescription, ...]] = {
|
||||
"cl": (
|
||||
TuyaCoverEntityDescription(
|
||||
key=DPCode.CONTROL,
|
||||
translation_key="curtain",
|
||||
current_state=DPCode.SITUATION_SET,
|
||||
current_position=(DPCode.PERCENT_CONTROL, DPCode.PERCENT_STATE),
|
||||
set_position=DPCode.PERCENT_CONTROL,
|
||||
@ -65,6 +66,7 @@ COVERS: dict[str, tuple[TuyaCoverEntityDescription, ...]] = {
|
||||
),
|
||||
TuyaCoverEntityDescription(
|
||||
key=DPCode.MACH_OPERATE,
|
||||
translation_key="curtain",
|
||||
current_position=DPCode.POSITION,
|
||||
set_position=DPCode.POSITION,
|
||||
device_class=CoverDeviceClass.CURTAIN,
|
||||
@ -76,6 +78,7 @@ COVERS: dict[str, tuple[TuyaCoverEntityDescription, ...]] = {
|
||||
# It is used by the Kogan Smart Blinds Driver
|
||||
TuyaCoverEntityDescription(
|
||||
key=DPCode.SWITCH_1,
|
||||
translation_key="blind",
|
||||
current_position=DPCode.PERCENT_CONTROL,
|
||||
set_position=DPCode.PERCENT_CONTROL,
|
||||
device_class=CoverDeviceClass.BLIND,
|
||||
@ -111,6 +114,7 @@ COVERS: dict[str, tuple[TuyaCoverEntityDescription, ...]] = {
|
||||
"clkg": (
|
||||
TuyaCoverEntityDescription(
|
||||
key=DPCode.CONTROL,
|
||||
translation_key="curtain",
|
||||
current_position=DPCode.PERCENT_CONTROL,
|
||||
set_position=DPCode.PERCENT_CONTROL,
|
||||
device_class=CoverDeviceClass.CURTAIN,
|
||||
@ -128,6 +132,7 @@ COVERS: dict[str, tuple[TuyaCoverEntityDescription, ...]] = {
|
||||
"jdcljqr": (
|
||||
TuyaCoverEntityDescription(
|
||||
key=DPCode.CONTROL,
|
||||
translation_key="curtain",
|
||||
current_position=DPCode.PERCENT_STATE,
|
||||
set_position=DPCode.PERCENT_CONTROL,
|
||||
device_class=CoverDeviceClass.CURTAIN,
|
||||
|
@ -71,6 +71,12 @@
|
||||
}
|
||||
},
|
||||
"cover": {
|
||||
"blind": {
|
||||
"name": "[%key:component::cover::entity_component::blind::name%]"
|
||||
},
|
||||
"curtain": {
|
||||
"name": "[%key:component::cover::entity_component::curtain::name%]"
|
||||
},
|
||||
"curtain_2": {
|
||||
"name": "Curtain 2"
|
||||
},
|
||||
|
@ -105,11 +105,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = {
|
||||
translation_key="plug",
|
||||
),
|
||||
),
|
||||
# Cirquit Breaker
|
||||
# Circuit Breaker
|
||||
"dlq": (
|
||||
SwitchEntityDescription(
|
||||
key=DPCode.CHILD_LOCK,
|
||||
translation_key="asd",
|
||||
translation_key="child_lock",
|
||||
icon="mdi:account-lock",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
|
@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiounifi"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiounifi==51"],
|
||||
"requirements": ["aiounifi==52"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
@ -79,6 +79,8 @@ def async_wlan_client_value_fn(controller: UniFiController, wlan: Wlan) -> int:
|
||||
client.mac
|
||||
for client in controller.api.clients.values()
|
||||
if client.essid == wlan.name
|
||||
and dt_util.utcnow() - dt_util.utc_from_timestamp(client.last_seen or 0)
|
||||
< controller.option_detection_time
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -145,16 +145,26 @@ async def async_handle_webhook(
|
||||
return Response(status=HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
|
||||
if webhook["local_only"] in (True, None) and not isinstance(request, MockRequest):
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(request, Request)
|
||||
assert request.remote is not None
|
||||
try:
|
||||
remote = ip_address(request.remote)
|
||||
except ValueError:
|
||||
_LOGGER.debug("Unable to parse remote ip %s", request.remote)
|
||||
return Response(status=HTTPStatus.OK)
|
||||
if has_cloud := "cloud" in hass.config.components:
|
||||
from hass_nabucasa import remote # pylint: disable=import-outside-toplevel
|
||||
|
||||
if not network.is_local(remote):
|
||||
is_local = True
|
||||
if has_cloud and remote.is_cloud_request.get():
|
||||
is_local = False
|
||||
else:
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(request, Request)
|
||||
assert request.remote is not None
|
||||
|
||||
try:
|
||||
request_remote = ip_address(request.remote)
|
||||
except ValueError:
|
||||
_LOGGER.debug("Unable to parse remote ip %s", request.remote)
|
||||
return Response(status=HTTPStatus.OK)
|
||||
|
||||
is_local = network.is_local(request_remote)
|
||||
|
||||
if not is_local:
|
||||
_LOGGER.warning("Received remote request for local webhook %s", webhook_id)
|
||||
if webhook["local_only"]:
|
||||
return Response(status=HTTPStatus.OK)
|
||||
|
@ -297,7 +297,7 @@ async def async_setup_entry(
|
||||
_lights_setup_helper(YeelightColorLightWithNightlightSwitch)
|
||||
_lights_setup_helper(YeelightNightLightModeWithoutBrightnessControl)
|
||||
else:
|
||||
_lights_setup_helper(YeelightColorLightWithoutNightlightSwitch)
|
||||
_lights_setup_helper(YeelightColorLightWithoutNightlightSwitchLight)
|
||||
elif device_type == BulbType.WhiteTemp:
|
||||
if nl_switch_light and device.is_nightlight_supported:
|
||||
_lights_setup_helper(YeelightWithNightLight)
|
||||
@ -931,6 +931,14 @@ class YeelightColorLightWithoutNightlightSwitch(
|
||||
"""Representation of a Color Yeelight light."""
|
||||
|
||||
|
||||
class YeelightColorLightWithoutNightlightSwitchLight(
|
||||
YeelightColorLightWithoutNightlightSwitch
|
||||
):
|
||||
"""Representation of a Color Yeelight light."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
|
||||
class YeelightColorLightWithNightlightSwitch(
|
||||
YeelightNightLightSupport, YeelightColorLightSupport, YeelightGenericLight
|
||||
):
|
||||
|
@ -17,7 +17,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["async_upnp_client", "yeelight"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["yeelight==0.7.12", "async-upnp-client==0.34.1"],
|
||||
"requirements": ["yeelight==0.7.13", "async-upnp-client==0.34.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_miio._udp.local.",
|
||||
|
@ -20,7 +20,7 @@
|
||||
"zigpy_znp"
|
||||
],
|
||||
"requirements": [
|
||||
"bellows==0.35.8",
|
||||
"bellows==0.35.9",
|
||||
"pyserial==3.5",
|
||||
"pyserial-asyncio==0.6",
|
||||
"zha-quirks==0.0.102",
|
||||
|
@ -7,7 +7,7 @@ from typing import Final
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
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, 11, 0)
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.8.1"
|
||||
version = "2023.8.2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -5,7 +5,7 @@
|
||||
AEMET-OpenData==0.2.2
|
||||
|
||||
# homeassistant.components.aladdin_connect
|
||||
AIOAladdinConnect==0.1.56
|
||||
AIOAladdinConnect==0.1.57
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
AIOSomecomfort==0.0.15
|
||||
@ -79,7 +79,7 @@ PyMetno==0.10.0
|
||||
PyMicroBot==0.0.9
|
||||
|
||||
# homeassistant.components.nina
|
||||
PyNINA==0.3.1
|
||||
PyNINA==0.3.2
|
||||
|
||||
# homeassistant.components.mobile_app
|
||||
# homeassistant.components.owntracks
|
||||
@ -191,7 +191,7 @@ aioairq==0.2.4
|
||||
aioairzone-cloud==0.2.1
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.6.4
|
||||
aioairzone==0.6.5
|
||||
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2023.04.0
|
||||
@ -249,7 +249,7 @@ aioguardian==2022.07.0
|
||||
aioharmony==0.2.10
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==2.6.12
|
||||
aiohomekit==2.6.15
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
@ -360,7 +360,7 @@ aiosyncthing==0.5.1
|
||||
aiotractive==0.5.5
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==51
|
||||
aiounifi==52
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
@ -500,10 +500,10 @@ beautifulsoup4==4.11.1
|
||||
# beewi-smartclim==0.0.10
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows==0.35.8
|
||||
bellows==0.35.9
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer-connected==0.13.8
|
||||
bimmer-connected==0.13.9
|
||||
|
||||
# homeassistant.components.bizkaibus
|
||||
bizkaibus==0.1.1
|
||||
@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16
|
||||
openwrt-ubus-rpc==0.0.2
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.0.20
|
||||
opower==0.0.26
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
@ -1563,7 +1563,7 @@ pyairnow==1.2.1
|
||||
|
||||
# homeassistant.components.airvisual
|
||||
# homeassistant.components.airvisual_pro
|
||||
pyairvisual==2022.12.1
|
||||
pyairvisual==2023.08.1
|
||||
|
||||
# homeassistant.components.atag
|
||||
pyatag==0.3.5.3
|
||||
@ -1572,7 +1572,7 @@ pyatag==0.3.5.3
|
||||
pyatmo==7.5.0
|
||||
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.13.3
|
||||
pyatv==0.13.4
|
||||
|
||||
# homeassistant.components.aussie_broadband
|
||||
pyaussiebb==0.0.15
|
||||
@ -1824,7 +1824,7 @@ pymailgunner==1.4
|
||||
pymata-express==1.19
|
||||
|
||||
# homeassistant.components.mazda
|
||||
pymazda==0.3.10
|
||||
pymazda==0.3.11
|
||||
|
||||
# homeassistant.components.mediaroom
|
||||
pymediaroom==0.6.5.4
|
||||
@ -1872,7 +1872,7 @@ pynuki==1.6.2
|
||||
pynut2==2.1.2
|
||||
|
||||
# homeassistant.components.nws
|
||||
pynws==1.5.0
|
||||
pynws==1.5.1
|
||||
|
||||
# homeassistant.components.nx584
|
||||
pynx584==0.5
|
||||
@ -1982,7 +1982,7 @@ pysabnzbd==1.1.1
|
||||
pysaj==0.0.16
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.32
|
||||
pysensibo==1.0.33
|
||||
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.zha
|
||||
@ -2150,7 +2150,7 @@ python-qbittorrent==0.4.3
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.31.1
|
||||
python-roborock==0.32.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
@ -2159,7 +2159,7 @@ python-smarttub==0.0.33
|
||||
python-songpal==0.15.2
|
||||
|
||||
# homeassistant.components.tado
|
||||
python-tado==0.15.0
|
||||
python-tado==0.16.0
|
||||
|
||||
# homeassistant.components.telegram_bot
|
||||
python-telegram-bot==13.1
|
||||
@ -2563,7 +2563,7 @@ total-connect-client==2023.2
|
||||
tp-connected==0.0.4
|
||||
|
||||
# homeassistant.components.tplink_omada
|
||||
tplink_omada_client==1.2.4
|
||||
tplink_omada_client==1.3.2
|
||||
|
||||
# homeassistant.components.transmission
|
||||
transmission-rpc==4.1.5
|
||||
@ -2725,7 +2725,7 @@ yalexs-ble==2.2.3
|
||||
yalexs==1.5.1
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.12
|
||||
yeelight==0.7.13
|
||||
|
||||
# homeassistant.components.yeelightsunflower
|
||||
yeelightsunflower==0.0.10
|
||||
|
@ -7,7 +7,7 @@
|
||||
AEMET-OpenData==0.2.2
|
||||
|
||||
# homeassistant.components.aladdin_connect
|
||||
AIOAladdinConnect==0.1.56
|
||||
AIOAladdinConnect==0.1.57
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
AIOSomecomfort==0.0.15
|
||||
@ -69,7 +69,7 @@ PyMetno==0.10.0
|
||||
PyMicroBot==0.0.9
|
||||
|
||||
# homeassistant.components.nina
|
||||
PyNINA==0.3.1
|
||||
PyNINA==0.3.2
|
||||
|
||||
# homeassistant.components.mobile_app
|
||||
# homeassistant.components.owntracks
|
||||
@ -172,7 +172,7 @@ aioairq==0.2.4
|
||||
aioairzone-cloud==0.2.1
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.6.4
|
||||
aioairzone==0.6.5
|
||||
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2023.04.0
|
||||
@ -227,7 +227,7 @@ aioguardian==2022.07.0
|
||||
aioharmony==0.2.10
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==2.6.12
|
||||
aiohomekit==2.6.15
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
@ -335,7 +335,7 @@ aiosyncthing==0.5.1
|
||||
aiotractive==0.5.5
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==51
|
||||
aiounifi==52
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
@ -424,10 +424,10 @@ base36==0.1.1
|
||||
beautifulsoup4==4.11.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows==0.35.8
|
||||
bellows==0.35.9
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer-connected==0.13.8
|
||||
bimmer-connected==0.13.9
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==3.1.1
|
||||
@ -1037,7 +1037,7 @@ openerz-api==0.2.0
|
||||
openhomedevice==2.2.0
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.0.20
|
||||
opower==0.0.26
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
@ -1169,7 +1169,7 @@ pyairnow==1.2.1
|
||||
|
||||
# homeassistant.components.airvisual
|
||||
# homeassistant.components.airvisual_pro
|
||||
pyairvisual==2022.12.1
|
||||
pyairvisual==2023.08.1
|
||||
|
||||
# homeassistant.components.atag
|
||||
pyatag==0.3.5.3
|
||||
@ -1178,7 +1178,7 @@ pyatag==0.3.5.3
|
||||
pyatmo==7.5.0
|
||||
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.13.3
|
||||
pyatv==0.13.4
|
||||
|
||||
# homeassistant.components.aussie_broadband
|
||||
pyaussiebb==0.0.15
|
||||
@ -1352,7 +1352,7 @@ pymailgunner==1.4
|
||||
pymata-express==1.19
|
||||
|
||||
# homeassistant.components.mazda
|
||||
pymazda==0.3.10
|
||||
pymazda==0.3.11
|
||||
|
||||
# homeassistant.components.melcloud
|
||||
pymelcloud==2.5.8
|
||||
@ -1388,7 +1388,7 @@ pynuki==1.6.2
|
||||
pynut2==2.1.2
|
||||
|
||||
# homeassistant.components.nws
|
||||
pynws==1.5.0
|
||||
pynws==1.5.1
|
||||
|
||||
# homeassistant.components.nx584
|
||||
pynx584==0.5
|
||||
@ -1474,7 +1474,7 @@ pyrympro==0.0.7
|
||||
pysabnzbd==1.1.1
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.32
|
||||
pysensibo==1.0.33
|
||||
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.zha
|
||||
@ -1579,7 +1579,7 @@ python-picnic-api==1.1.0
|
||||
python-qbittorrent==0.4.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.31.1
|
||||
python-roborock==0.32.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
@ -1588,7 +1588,7 @@ python-smarttub==0.0.33
|
||||
python-songpal==0.15.2
|
||||
|
||||
# homeassistant.components.tado
|
||||
python-tado==0.15.0
|
||||
python-tado==0.16.0
|
||||
|
||||
# homeassistant.components.telegram_bot
|
||||
python-telegram-bot==13.1
|
||||
@ -1869,7 +1869,7 @@ toonapi==0.2.1
|
||||
total-connect-client==2023.2
|
||||
|
||||
# homeassistant.components.tplink_omada
|
||||
tplink_omada_client==1.2.4
|
||||
tplink_omada_client==1.3.2
|
||||
|
||||
# homeassistant.components.transmission
|
||||
transmission-rpc==4.1.5
|
||||
@ -2007,7 +2007,7 @@ yalexs-ble==2.2.3
|
||||
yalexs==1.5.1
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.12
|
||||
yeelight==0.7.13
|
||||
|
||||
# homeassistant.components.yolink
|
||||
yolink-api==0.3.0
|
||||
|
@ -329,7 +329,7 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None:
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
HVAC_MOCK = {
|
||||
HVAC_MOCK_1 = {
|
||||
API_DATA: [
|
||||
{
|
||||
API_SYSTEM_ID: 1,
|
||||
@ -340,7 +340,7 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None:
|
||||
}
|
||||
with patch(
|
||||
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||
return_value=HVAC_MOCK,
|
||||
return_value=HVAC_MOCK_1,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
@ -407,6 +407,51 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None:
|
||||
state = hass.states.get("climate.airzone_2_1")
|
||||
assert state.state == HVACMode.HEAT_COOL
|
||||
|
||||
HVAC_MOCK_4 = {
|
||||
API_DATA: [
|
||||
{
|
||||
API_SYSTEM_ID: 1,
|
||||
API_ZONE_ID: 1,
|
||||
API_ON: 1,
|
||||
}
|
||||
]
|
||||
}
|
||||
with patch(
|
||||
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||
return_value=HVAC_MOCK_4,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "climate.salon",
|
||||
ATTR_HVAC_MODE: HVACMode.FAN_ONLY,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.salon")
|
||||
assert state.state == HVACMode.FAN_ONLY
|
||||
|
||||
HVAC_MOCK_NO_SET_POINT = {**HVAC_MOCK}
|
||||
del HVAC_MOCK_NO_SET_POINT[API_SYSTEMS][0][API_DATA][0][API_SET_POINT]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.airzone.AirzoneLocalApi.get_hvac",
|
||||
return_value=HVAC_MOCK_NO_SET_POINT,
|
||||
), patch(
|
||||
"homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems",
|
||||
return_value=HVAC_SYSTEMS_MOCK,
|
||||
), patch(
|
||||
"homeassistant.components.airzone.AirzoneLocalApi.get_webserver",
|
||||
return_value=HVAC_WEBSERVER_MOCK,
|
||||
):
|
||||
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("climate.salon")
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) == 19.1
|
||||
|
||||
|
||||
async def test_airzone_climate_set_hvac_slave_error(hass: HomeAssistant) -> None:
|
||||
"""Test setting the HVAC mode for a slave zone."""
|
||||
|
@ -36,6 +36,7 @@ from tests.common import async_mock_service
|
||||
NAME = "alert_test"
|
||||
DONE_MESSAGE = "alert_gone"
|
||||
NOTIFIER = "test"
|
||||
BAD_NOTIFIER = "bad_notifier"
|
||||
TEMPLATE = "{{ states.sensor.test.entity_id }}"
|
||||
TEST_ENTITY = "sensor.test"
|
||||
TITLE = "{{ states.sensor.test.entity_id }}"
|
||||
@ -199,6 +200,26 @@ async def test_notification(
|
||||
assert len(mock_notifier) == 2
|
||||
|
||||
|
||||
async def test_bad_notifier(
|
||||
hass: HomeAssistant, mock_notifier: list[ServiceCall]
|
||||
) -> None:
|
||||
"""Test a broken notifier does not break the alert."""
|
||||
config = deepcopy(TEST_CONFIG)
|
||||
config[DOMAIN][NAME][CONF_NOTIFIERS] = [BAD_NOTIFIER, NOTIFIER]
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
assert len(mock_notifier) == 0
|
||||
|
||||
hass.states.async_set("sensor.test", STATE_ON)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_notifier) == 1
|
||||
assert hass.states.get(ENTITY_ID).state == STATE_ON
|
||||
|
||||
hass.states.async_set("sensor.test", STATE_OFF)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_notifier) == 2
|
||||
assert hass.states.get(ENTITY_ID).state == STATE_IDLE
|
||||
|
||||
|
||||
async def test_no_notifiers(
|
||||
hass: HomeAssistant, mock_notifier: list[ServiceCall]
|
||||
) -> None:
|
||||
|
@ -90,6 +90,7 @@ async def test_user_flow_cannot_connect(
|
||||
|
||||
host = "1.2.3.4"
|
||||
|
||||
mock_api.async_generate_cert_if_missing = AsyncMock(return_value=True)
|
||||
mock_api.async_get_name_and_mac = AsyncMock(side_effect=CannotConnect())
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
@ -101,6 +102,7 @@ async def test_user_flow_cannot_connect(
|
||||
assert "host" in result["data_schema"].schema
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
mock_api.async_generate_cert_if_missing.assert_called()
|
||||
mock_api.async_get_name_and_mac.assert_called()
|
||||
mock_api.async_start_pairing.assert_not_called()
|
||||
|
||||
@ -329,6 +331,7 @@ async def test_user_flow_already_configured_host_changed_reloads_entry(
|
||||
assert "host" in result["data_schema"].schema
|
||||
assert not result["errors"]
|
||||
|
||||
mock_api.async_generate_cert_if_missing = AsyncMock(return_value=True)
|
||||
mock_api.async_get_name_and_mac = AsyncMock(return_value=(name, mac))
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
@ -338,6 +341,7 @@ async def test_user_flow_already_configured_host_changed_reloads_entry(
|
||||
assert result.get("type") == FlowResultType.ABORT
|
||||
assert result.get("reason") == "already_configured"
|
||||
|
||||
mock_api.async_generate_cert_if_missing.assert_called()
|
||||
mock_api.async_get_name_and_mac.assert_called()
|
||||
mock_api.async_start_pairing.assert_not_called()
|
||||
|
||||
@ -386,6 +390,7 @@ async def test_user_flow_already_configured_host_not_changed_no_reload_entry(
|
||||
assert "host" in result["data_schema"].schema
|
||||
assert not result["errors"]
|
||||
|
||||
mock_api.async_generate_cert_if_missing = AsyncMock(return_value=True)
|
||||
mock_api.async_get_name_and_mac = AsyncMock(return_value=(name, mac))
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
@ -395,6 +400,7 @@ async def test_user_flow_already_configured_host_not_changed_no_reload_entry(
|
||||
assert result.get("type") == FlowResultType.ABORT
|
||||
assert result.get("reason") == "already_configured"
|
||||
|
||||
mock_api.async_generate_cert_if_missing.assert_called()
|
||||
mock_api.async_get_name_and_mac.assert_called()
|
||||
mock_api.async_start_pairing.assert_not_called()
|
||||
|
||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import subprocess
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -16,7 +17,7 @@ from homeassistant.components.homeassistant import (
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
@ -697,3 +698,40 @@ async def test_scrape_sensor_device_date(
|
||||
entity_state = hass.states.get("sensor.test")
|
||||
assert entity_state
|
||||
assert entity_state.state == "2022-01-17"
|
||||
|
||||
|
||||
async def test_template_not_error_when_data_is_none(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test command sensor with template not logging error when data is None."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
side_effect=subprocess.CalledProcessError,
|
||||
):
|
||||
await setup.async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{
|
||||
"command_line": [
|
||||
{
|
||||
"sensor": {
|
||||
"name": "Test",
|
||||
"command": "failed command",
|
||||
"unit_of_measurement": "MB",
|
||||
"value_template": "{{ (value.split('\t')[0]|int(0)/1000)|round(3) }}",
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_state = hass.states.get("sensor.test")
|
||||
assert entity_state
|
||||
assert entity_state.state == STATE_UNKNOWN
|
||||
|
||||
assert (
|
||||
"Template variable error: 'None' has no attribute 'split' when rendering"
|
||||
not in caplog.text
|
||||
)
|
||||
|
@ -35,7 +35,7 @@
|
||||
'entity_id': 'sensor.mock_title_valve_closing',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup[98bd2a19-0b0e-421a-84e5-ddbf75dc6de4-raw0-sensor.mock_title_battery]
|
||||
|
@ -425,6 +425,14 @@ async def test_handle_events_late_setup(hass: HomeAssistant, utcnow, calls) -> N
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data["some"] == "device - button1 - single_press - 0"
|
||||
|
||||
# Make sure automation doesn't trigger for a polled None
|
||||
helper.pairing.testing.update_named_service(
|
||||
"Button 1", {CharacteristicsTypes.INPUT_EVENT: None}
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
|
||||
# Make sure automation doesn't trigger for long press
|
||||
helper.pairing.testing.update_named_service(
|
||||
"Button 1", {CharacteristicsTypes.INPUT_EVENT: 1}
|
||||
|
@ -67,6 +67,10 @@ async def setup_entry(
|
||||
return_value=PROP,
|
||||
), patch(
|
||||
"homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message"
|
||||
), patch(
|
||||
"homeassistant.components.roborock.RoborockMqttClient._wait_response"
|
||||
), patch(
|
||||
"homeassistant.components.roborock.coordinator.RoborockLocalClient._wait_response"
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
@ -547,6 +547,163 @@
|
||||
"autoOffEnabled": false,
|
||||
"antiMoldTimer": null,
|
||||
"antiMoldConfig": null
|
||||
},
|
||||
{
|
||||
"isGeofenceOnEnterEnabledForThisUser": false,
|
||||
"isClimateReactGeofenceOnEnterEnabledForThisUser": false,
|
||||
"isMotionGeofenceOnEnterEnabled": false,
|
||||
"isOwner": true,
|
||||
"id": "BBZZBBZZ",
|
||||
"qrId": "AAAAAAAACC",
|
||||
"temperatureUnit": "C",
|
||||
"room": {
|
||||
"uid": "99YY99YY",
|
||||
"name": "Bedroom",
|
||||
"icon": "Diningroom"
|
||||
},
|
||||
"acState": {
|
||||
"timestamp": {
|
||||
"time": "2022-04-30T11:23:30.067312Z",
|
||||
"secondsAgo": -1
|
||||
},
|
||||
"on": false
|
||||
},
|
||||
"lastStateChange": {
|
||||
"time": "2022-04-30T11:21:41Z",
|
||||
"secondsAgo": 108
|
||||
},
|
||||
"lastStateChangeToOn": {
|
||||
"time": "2022-04-30T09:43:26Z",
|
||||
"secondsAgo": 6003
|
||||
},
|
||||
"lastStateChangeToOff": {
|
||||
"time": "2022-04-30T11:21:37Z",
|
||||
"secondsAgo": 112
|
||||
},
|
||||
"location": {
|
||||
"id": "ZZZZZZZZZZYY",
|
||||
"name": "Home",
|
||||
"latLon": [58.9806976, 20.5864297],
|
||||
"address": ["Sealand 99", "Some county"],
|
||||
"country": "United Country",
|
||||
"createTime": {
|
||||
"time": "2020-03-21T15:44:15Z",
|
||||
"secondsAgo": 66543240
|
||||
}
|
||||
},
|
||||
"connectionStatus": {
|
||||
"isAlive": true,
|
||||
"lastSeen": {
|
||||
"time": "2022-04-30T11:23:20.642798Z",
|
||||
"secondsAgo": 9
|
||||
}
|
||||
},
|
||||
"firmwareVersion": "PUR00111",
|
||||
"firmwareType": "pure-esp32",
|
||||
"productModel": "pure",
|
||||
"configGroup": "stable",
|
||||
"currentlyAvailableFirmwareVersion": "PUR00111",
|
||||
"cleanFiltersNotificationEnabled": false,
|
||||
"shouldShowFilterCleaningNotification": false,
|
||||
"isGeofenceOnExitEnabled": false,
|
||||
"isClimateReactGeofenceOnExitEnabled": false,
|
||||
"isMotionGeofenceOnExitEnabled": false,
|
||||
"serial": "0987654321",
|
||||
"sensorsCalibration": {
|
||||
"temperature": 0.0,
|
||||
"humidity": 0.0
|
||||
},
|
||||
"motionSensors": [],
|
||||
"tags": [],
|
||||
"timer": null,
|
||||
"schedules": [],
|
||||
"motionConfig": null,
|
||||
"filtersCleaning": {
|
||||
"acOnSecondsSinceLastFiltersClean": 415560,
|
||||
"filtersCleanSecondsThreshold": 14256000,
|
||||
"lastFiltersCleanTime": {
|
||||
"time": "2022-04-23T15:58:45Z",
|
||||
"secondsAgo": 588284
|
||||
},
|
||||
"shouldCleanFilters": false
|
||||
},
|
||||
"serviceSubscriptions": [],
|
||||
"roomIsOccupied": null,
|
||||
"mainMeasurementsSensor": null,
|
||||
"pureBoostConfig": null,
|
||||
"warrantyEligible": "no",
|
||||
"warrantyEligibleUntil": {
|
||||
"time": "2022-04-10T09:58:58Z",
|
||||
"secondsAgo": 1733071
|
||||
},
|
||||
"features": ["optimusTrial", "softShowPlus"],
|
||||
"runningHealthcheck": null,
|
||||
"lastHealthcheck": null,
|
||||
"lastACStateChange": {
|
||||
"id": "AA22",
|
||||
"time": {
|
||||
"time": "2022-04-30T11:21:37Z",
|
||||
"secondsAgo": 112
|
||||
},
|
||||
"status": "Success",
|
||||
"acState": {
|
||||
"timestamp": {
|
||||
"time": "2022-04-30T11:23:30.090144Z",
|
||||
"secondsAgo": -1
|
||||
},
|
||||
"on": false,
|
||||
"mode": "fan",
|
||||
"fanLevel": "low",
|
||||
"light": "on"
|
||||
},
|
||||
"resultingAcState": {
|
||||
"timestamp": {
|
||||
"time": "2022-04-30T11:23:30.090185Z",
|
||||
"secondsAgo": -1
|
||||
},
|
||||
"on": false,
|
||||
"mode": "fan",
|
||||
"fanLevel": "low",
|
||||
"light": "on"
|
||||
},
|
||||
"changedProperties": ["on"],
|
||||
"reason": "UserRequest",
|
||||
"failureReason": null,
|
||||
"resolveTime": {
|
||||
"time": "2022-04-30T11:21:37Z",
|
||||
"secondsAgo": 112
|
||||
},
|
||||
"causedByScheduleId": null,
|
||||
"causedByScheduleType": null
|
||||
},
|
||||
"homekitSupported": true,
|
||||
"remoteCapabilities": null,
|
||||
"remote": {
|
||||
"toggle": false,
|
||||
"window": false
|
||||
},
|
||||
"remoteFlavor": "Eccentric Eagle",
|
||||
"remoteAlternatives": [],
|
||||
"smartMode": null,
|
||||
"measurements": {
|
||||
"time": {
|
||||
"time": "2022-04-30T11:23:20.642798Z",
|
||||
"secondsAgo": 9
|
||||
},
|
||||
"rssi": -58,
|
||||
"pm25": 1,
|
||||
"motion": false,
|
||||
"roomIsOccupied": null
|
||||
},
|
||||
"accessPoint": {
|
||||
"ssid": "Sensibo-09876",
|
||||
"password": null
|
||||
},
|
||||
"macAddress": "00:01:00:01:00:01",
|
||||
"autoOffMinutes": null,
|
||||
"autoOffEnabled": false,
|
||||
"antiMoldTimer": null,
|
||||
"antiMoldConfig": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -76,12 +76,16 @@ async def test_climate_find_valid_targets() -> None:
|
||||
|
||||
|
||||
async def test_climate(
|
||||
hass: HomeAssistant, load_int: ConfigEntry, get_data: SensiboData
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
get_data: SensiboData,
|
||||
load_int: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test the Sensibo climate."""
|
||||
|
||||
state1 = hass.states.get("climate.hallway")
|
||||
state2 = hass.states.get("climate.kitchen")
|
||||
state3 = hass.states.get("climate.bedroom")
|
||||
|
||||
assert state1.state == "heat"
|
||||
assert state1.attributes == {
|
||||
@ -113,6 +117,19 @@ async def test_climate(
|
||||
|
||||
assert state2.state == "off"
|
||||
|
||||
assert not state3
|
||||
found_log = False
|
||||
logs = caplog.get_records("setup")
|
||||
for log in logs:
|
||||
if (
|
||||
log.message
|
||||
== "Device Bedroom not correctly registered with Sensibo cloud. Skipping device"
|
||||
):
|
||||
found_log = True
|
||||
break
|
||||
|
||||
assert found_log
|
||||
|
||||
|
||||
async def test_climate_fan(
|
||||
hass: HomeAssistant,
|
||||
|
@ -470,6 +470,7 @@ async def test_wlan_client_sensors(
|
||||
wireless_client_1 = {
|
||||
"essid": "SSID 1",
|
||||
"is_wired": False,
|
||||
"last_seen": dt_util.as_timestamp(dt_util.utcnow()),
|
||||
"mac": "00:00:00:00:00:01",
|
||||
"name": "Wireless client",
|
||||
"oui": "Producer",
|
||||
@ -479,6 +480,7 @@ async def test_wlan_client_sensors(
|
||||
wireless_client_2 = {
|
||||
"essid": "SSID 2",
|
||||
"is_wired": False,
|
||||
"last_seen": dt_util.as_timestamp(dt_util.utcnow()),
|
||||
"mac": "00:00:00:00:00:02",
|
||||
"name": "Wireless client2",
|
||||
"oui": "Producer2",
|
||||
@ -526,9 +528,17 @@ async def test_wlan_client_sensors(
|
||||
# Verify state update - decreasing number
|
||||
|
||||
wireless_client_1["essid"] = "SSID"
|
||||
wireless_client_2["essid"] = "SSID"
|
||||
|
||||
mock_unifi_websocket(message=MessageKey.CLIENT, data=wireless_client_1)
|
||||
|
||||
async_fire_time_changed(hass, datetime.utcnow() + DEFAULT_SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
ssid_1 = hass.states.get("sensor.ssid_1")
|
||||
assert ssid_1.state == "1"
|
||||
|
||||
# Verify state update - decreasing number
|
||||
|
||||
wireless_client_2["last_seen"] = 0
|
||||
mock_unifi_websocket(message=MessageKey.CLIENT, data=wireless_client_2)
|
||||
|
||||
async_fire_time_changed(hass, datetime.utcnow() + DEFAULT_SCAN_INTERVAL)
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Test the webhook component."""
|
||||
from http import HTTPStatus
|
||||
from ipaddress import ip_address
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import web
|
||||
import pytest
|
||||
@ -206,6 +206,8 @@ async def test_webhook_not_allowed_method(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_webhook_local_only(hass: HomeAssistant, mock_client) -> None:
|
||||
"""Test posting a webhook with local only."""
|
||||
hass.config.components.add("cloud")
|
||||
|
||||
hooks = []
|
||||
webhook_id = webhook.async_generate_id()
|
||||
|
||||
@ -234,6 +236,16 @@ async def test_webhook_local_only(hass: HomeAssistant, mock_client) -> None:
|
||||
# No hook received
|
||||
assert len(hooks) == 1
|
||||
|
||||
# Request from Home Assistant Cloud remote UI
|
||||
with patch(
|
||||
"hass_nabucasa.remote.is_cloud_request", Mock(get=Mock(return_value=True))
|
||||
):
|
||||
resp = await mock_client.post(f"/api/webhook/{webhook_id}", json={"data": True})
|
||||
|
||||
# No hook received
|
||||
assert resp.status == HTTPStatus.OK
|
||||
assert len(hooks) == 1
|
||||
|
||||
|
||||
async def test_listing_webhook(
|
||||
hass: HomeAssistant,
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""The tests for the webhook automation trigger."""
|
||||
from ipaddress import ip_address
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@ -68,6 +68,9 @@ async def test_webhook_post(
|
||||
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
|
||||
) -> None:
|
||||
"""Test triggering with a POST webhook."""
|
||||
# Set up fake cloud
|
||||
hass.config.components.add("cloud")
|
||||
|
||||
events = []
|
||||
|
||||
@callback
|
||||
@ -114,6 +117,16 @@ async def test_webhook_post(
|
||||
await hass.async_block_till_done()
|
||||
assert len(events) == 1
|
||||
|
||||
# Request from Home Assistant Cloud remote UI
|
||||
with patch(
|
||||
"hass_nabucasa.remote.is_cloud_request", Mock(get=Mock(return_value=True))
|
||||
):
|
||||
await client.post("/api/webhook/post_webhook", data={"hello": "world"})
|
||||
|
||||
# No hook received
|
||||
await hass.async_block_till_done()
|
||||
assert len(events) == 1
|
||||
|
||||
|
||||
async def test_webhook_allowed_methods_internet(
|
||||
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
|
||||
@ -141,7 +154,6 @@ async def test_webhook_allowed_methods_internet(
|
||||
},
|
||||
"action": {
|
||||
"event": "test_success",
|
||||
"event_data_template": {"hello": "yo {{ trigger.data.hello }}"},
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -150,7 +162,7 @@ async def test_webhook_allowed_methods_internet(
|
||||
|
||||
client = await hass_client_no_auth()
|
||||
|
||||
await client.post("/api/webhook/post_webhook", data={"hello": "world"})
|
||||
await client.post("/api/webhook/post_webhook")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(events) == 0
|
||||
@ -160,7 +172,7 @@ async def test_webhook_allowed_methods_internet(
|
||||
"homeassistant.components.webhook.ip_address",
|
||||
return_value=ip_address("123.123.123.123"),
|
||||
):
|
||||
await client.put("/api/webhook/post_webhook", data={"hello": "world"})
|
||||
await client.put("/api/webhook/post_webhook")
|
||||
await hass.async_block_till_done()
|
||||
assert len(events) == 1
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user