forked from home-assistant/core
Compare commits
15 Commits
2024.8.0b1
...
2024.8.0b2
| Author | SHA1 | Date | |
|---|---|---|---|
| 35a3d2306c | |||
| cdb378066c | |||
| 85700fd80f | |||
| 73a2ad7304 | |||
| f6c4b6b045 | |||
| 0b4d921762 | |||
| c8a0e5228d | |||
| 832bac8c63 | |||
| eccce7017f | |||
| fdb1baadbe | |||
| 7623ee49e4 | |||
| fa241dcd04 | |||
| bee77041e8 | |||
| 50b7eb44d1 | |||
| 7b1bf82e3c |
@@ -255,7 +255,7 @@ class BluesoundPlayer(MediaPlayerEntity):
|
||||
|
||||
self._attr_unique_id = format_unique_id(sync_status.mac, port)
|
||||
# there should always be one player with the default port per mac
|
||||
if port is DEFAULT_PORT:
|
||||
if port == DEFAULT_PORT:
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, format_mac(sync_status.mac))},
|
||||
connections={(CONNECTION_NETWORK_MAC, format_mac(sync_status.mac))},
|
||||
|
||||
@@ -439,7 +439,9 @@ def rename_old_gas_to_mbus(
|
||||
entries = er.async_entries_for_device(ent_reg, device_id)
|
||||
|
||||
for entity in entries:
|
||||
if entity.unique_id.endswith("belgium_5min_gas_meter_reading"):
|
||||
if entity.unique_id.endswith(
|
||||
"belgium_5min_gas_meter_reading"
|
||||
) or entity.unique_id.endswith("hourly_gas_meter_reading"):
|
||||
try:
|
||||
ent_reg.async_update_entity(
|
||||
entity.entity_id,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"requirements": ["pyenphase==1.20.6"],
|
||||
"requirements": ["pyenphase==1.22.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
||||
@@ -398,6 +398,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
static_paths_configs: list[StaticPathConfig] = []
|
||||
|
||||
for path, should_cache in (
|
||||
("service_worker.js", False),
|
||||
("sw-modern.js", False),
|
||||
("sw-modern.js.map", False),
|
||||
("sw-legacy.js", False),
|
||||
|
||||
@@ -89,9 +89,9 @@ def _format_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
key = "type_"
|
||||
val = val.upper()
|
||||
elif key == "format":
|
||||
if (schema.get("type") == "string" and val != "enum") or (
|
||||
schema.get("type") not in ("number", "integer", "string")
|
||||
):
|
||||
if schema.get("type") == "string" and val != "enum":
|
||||
continue
|
||||
if schema.get("type") not in ("number", "integer", "string"):
|
||||
continue
|
||||
key = "format_"
|
||||
elif key == "items":
|
||||
@@ -100,11 +100,19 @@ def _format_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
val = {k: _format_schema(v) for k, v in val.items()}
|
||||
result[key] = val
|
||||
|
||||
if result.get("enum") and result.get("type_") != "STRING":
|
||||
# enum is only allowed for STRING type. This is safe as long as the schema
|
||||
# contains vol.Coerce for the respective type, for example:
|
||||
# vol.All(vol.Coerce(int), vol.In([1, 2, 3]))
|
||||
result["type_"] = "STRING"
|
||||
result["enum"] = [str(item) for item in result["enum"]]
|
||||
|
||||
if result.get("type_") == "OBJECT" and not result.get("properties"):
|
||||
# An object with undefined properties is not supported by Gemini API.
|
||||
# Fallback to JSON string. This will probably fail for most tools that want it,
|
||||
# but we don't have a better fallback strategy so far.
|
||||
result["properties"] = {"json": {"type_": "STRING"}}
|
||||
result["required"] = []
|
||||
return result
|
||||
|
||||
|
||||
@@ -164,6 +172,10 @@ class GoogleGenerativeAIConversationEntity(
|
||||
model="Generative AI",
|
||||
entry_type=dr.DeviceEntryType.SERVICE,
|
||||
)
|
||||
if self.entry.options.get(CONF_LLM_HASS_API):
|
||||
self._attr_supported_features = (
|
||||
conversation.ConversationEntityFeature.CONTROL
|
||||
)
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str] | Literal["*"]:
|
||||
@@ -177,6 +189,9 @@ class GoogleGenerativeAIConversationEntity(
|
||||
self.hass, "conversation", self.entry.entry_id, self.entity_id
|
||||
)
|
||||
conversation.async_set_agent(self.hass, self.entry, self)
|
||||
self.entry.async_on_unload(
|
||||
self.entry.add_update_listener(self._async_entry_update_listener)
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""When entity will be removed from Home Assistant."""
|
||||
@@ -397,3 +412,10 @@ class GoogleGenerativeAIConversationEntity(
|
||||
parts.append(llm_api.api_prompt)
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
async def _async_entry_update_listener(
|
||||
self, hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
# Reload as we update device info + entity name + supported features
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
@@ -18,3 +18,5 @@ FAN_MEDIUM_HIGH = "medium high"
|
||||
MAX_ERRORS = 2
|
||||
|
||||
TARGET_TEMPERATURE_STEP = 1
|
||||
|
||||
UPDATE_INTERVAL = 60
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from greeclimate.device import Device, DeviceInfo
|
||||
from greeclimate.discovery import Discovery, Listener
|
||||
from greeclimate.exceptions import DeviceNotBoundError, DeviceTimeoutError
|
||||
from greeclimate.network import Response
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .const import (
|
||||
COORDINATORS,
|
||||
@@ -19,12 +23,13 @@ from .const import (
|
||||
DISPATCH_DEVICE_DISCOVERED,
|
||||
DOMAIN,
|
||||
MAX_ERRORS,
|
||||
UPDATE_INTERVAL,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Manages polling for state changes from the device."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, device: Device) -> None:
|
||||
@@ -34,28 +39,68 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{DOMAIN}-{device.device_info.name}",
|
||||
update_interval=timedelta(seconds=60),
|
||||
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
||||
always_update=False,
|
||||
)
|
||||
self.device = device
|
||||
self._error_count = 0
|
||||
self.device.add_handler(Response.DATA, self.device_state_updated)
|
||||
self.device.add_handler(Response.RESULT, self.device_state_updated)
|
||||
|
||||
async def _async_update_data(self):
|
||||
self._error_count: int = 0
|
||||
self._last_response_time: datetime = utcnow()
|
||||
self._last_error_time: datetime | None = None
|
||||
|
||||
def device_state_updated(self, *args: Any) -> None:
|
||||
"""Handle device state updates."""
|
||||
_LOGGER.debug("Device state updated: %s", json_dumps(args))
|
||||
self._error_count = 0
|
||||
self._last_response_time = utcnow()
|
||||
self.async_set_updated_data(self.device.raw_properties)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update the state of the device."""
|
||||
_LOGGER.debug(
|
||||
"Updating device state: %s, error count: %d", self.name, self._error_count
|
||||
)
|
||||
try:
|
||||
await self.device.update_state()
|
||||
except DeviceNotBoundError as error:
|
||||
raise UpdateFailed(f"Device {self.name} is unavailable") from error
|
||||
raise UpdateFailed(
|
||||
f"Device {self.name} is unavailable, device is not bound."
|
||||
) from error
|
||||
except DeviceTimeoutError as error:
|
||||
self._error_count += 1
|
||||
|
||||
# Under normal conditions GREE units timeout every once in a while
|
||||
if self.last_update_success and self._error_count >= MAX_ERRORS:
|
||||
_LOGGER.warning(
|
||||
"Device is unavailable: %s (%s)",
|
||||
self.name,
|
||||
self.device.device_info,
|
||||
"Device %s is unavailable: %s", self.name, self.device.device_info
|
||||
)
|
||||
raise UpdateFailed(f"Device {self.name} is unavailable") from error
|
||||
raise UpdateFailed(
|
||||
f"Device {self.name} is unavailable, could not send update request"
|
||||
) from error
|
||||
else:
|
||||
# raise update failed if time for more than MAX_ERRORS has passed since last update
|
||||
now = utcnow()
|
||||
elapsed_success = now - self._last_response_time
|
||||
if self.update_interval and elapsed_success >= self.update_interval:
|
||||
if not self._last_error_time or (
|
||||
(now - self.update_interval) >= self._last_error_time
|
||||
):
|
||||
self._last_error_time = now
|
||||
self._error_count += 1
|
||||
|
||||
_LOGGER.warning(
|
||||
"Device %s is unresponsive for %s seconds",
|
||||
self.name,
|
||||
elapsed_success,
|
||||
)
|
||||
if self.last_update_success and self._error_count >= MAX_ERRORS:
|
||||
raise UpdateFailed(
|
||||
f"Device {self.name} is unresponsive for too long and now unavailable"
|
||||
)
|
||||
|
||||
return self.device.raw_properties
|
||||
|
||||
async def push_state_update(self):
|
||||
"""Send state updates to the physical device."""
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/gree",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["greeclimate"],
|
||||
"requirements": ["greeclimate==1.4.6"]
|
||||
"requirements": ["greeclimate==2.0.0"]
|
||||
}
|
||||
|
||||
@@ -46,4 +46,8 @@ class IronOSCoordinator(DataUpdateCoordinator[LiveDataResponse]):
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
|
||||
self.device_info = await self.device.get_device_info()
|
||||
try:
|
||||
self.device_info = await self.device.get_device_info()
|
||||
|
||||
except CommunicationError as e:
|
||||
raise UpdateFailed("Cannot connect to device") from e
|
||||
|
||||
@@ -54,6 +54,7 @@ TRANSITION_BLOCKLIST = (
|
||||
(4488, 514, "1.0", "1.0.0"),
|
||||
(4488, 260, "1.0", "1.0.0"),
|
||||
(5010, 769, "3.0", "1.0.0"),
|
||||
(4999, 24875, "1.0", "27.0"),
|
||||
(4999, 25057, "1.0", "27.0"),
|
||||
(4448, 36866, "V1", "V1.0.0.5"),
|
||||
(5009, 514, "1.0", "1.0.0"),
|
||||
|
||||
@@ -279,6 +279,8 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self, discovery_info: ZeroconfServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
if discovery_info.ip_address.version == 6:
|
||||
return self.async_abort(reason="ipv6_not_supported")
|
||||
host = discovery_info.host
|
||||
# First try to get the mac address from the name
|
||||
# so we can avoid making another connection to the
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reauth_unsuccessful": "Re-authentication was unsuccessful, please remove the integration and set it up again.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"another_device": "Re-configuration was unsuccessful, the IP address/hostname of another Shelly device was used."
|
||||
"another_device": "Re-configuration was unsuccessful, the IP address/hostname of another Shelly device was used.",
|
||||
"ipv6_not_supported": "IPv6 is not supported."
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
|
||||
@@ -86,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslaFleetConfigEntry) -
|
||||
vehicles: list[TeslaFleetVehicleData] = []
|
||||
energysites: list[TeslaFleetEnergyData] = []
|
||||
for product in products:
|
||||
if "vin" in product and tesla.vehicle:
|
||||
if "vin" in product and hasattr(tesla, "vehicle"):
|
||||
# Remove the protobuff 'cached_data' that we do not use to save memory
|
||||
product.pop("cached_data", None)
|
||||
vin = product["vin"]
|
||||
@@ -111,7 +111,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslaFleetConfigEntry) -
|
||||
device=device,
|
||||
)
|
||||
)
|
||||
elif "energy_site_id" in product and tesla.energy:
|
||||
elif "energy_site_id" in product and hasattr(tesla, "energy"):
|
||||
site_id = product["energy_site_id"]
|
||||
if not (
|
||||
product["components"]["battery"]
|
||||
|
||||
@@ -44,6 +44,7 @@ from homeassistant.core import Event as core_Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import slugify
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import UnifiConfigEntry
|
||||
@@ -247,8 +248,9 @@ def make_wan_latency_sensors() -> tuple[UnifiSensorEntityDescription, ...]:
|
||||
def make_wan_latency_entity_description(
|
||||
wan: Literal["WAN", "WAN2"], name: str, monitor_target: str
|
||||
) -> UnifiSensorEntityDescription:
|
||||
name_wan = f"{name} {wan}"
|
||||
return UnifiSensorEntityDescription[Devices, Device](
|
||||
key=f"{name} {wan} latency",
|
||||
key=f"{name_wan} latency",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -257,13 +259,12 @@ def make_wan_latency_sensors() -> tuple[UnifiSensorEntityDescription, ...]:
|
||||
api_handler_fn=lambda api: api.devices,
|
||||
available_fn=async_device_available_fn,
|
||||
device_info_fn=async_device_device_info_fn,
|
||||
name_fn=lambda _: f"{name} {wan} latency",
|
||||
name_fn=lambda device: f"{name_wan} latency",
|
||||
object_fn=lambda api, obj_id: api.devices[obj_id],
|
||||
supported_fn=partial(
|
||||
async_device_wan_latency_supported_fn, wan, monitor_target
|
||||
),
|
||||
unique_id_fn=lambda hub,
|
||||
obj_id: f"{name.lower}_{wan.lower}_latency-{obj_id}",
|
||||
unique_id_fn=lambda hub, obj_id: f"{slugify(name_wan)}_latency-{obj_id}",
|
||||
value_fn=partial(async_device_wan_latency_value_fn, wan, monitor_target),
|
||||
)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"zha",
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.25"],
|
||||
"requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.27"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10C4",
|
||||
|
||||
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2024
|
||||
MINOR_VERSION: Final = 8
|
||||
PATCH_VERSION: Final = "0b1"
|
||||
PATCH_VERSION: Final = "0b2"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||
|
||||
@@ -4,7 +4,7 @@ aiodhcpwatcher==1.0.2
|
||||
aiodiscover==2.1.0
|
||||
aiodns==3.2.0
|
||||
aiohttp-fast-zlib==0.1.1
|
||||
aiohttp==3.10.0
|
||||
aiohttp==3.10.1
|
||||
aiohttp_cors==0.7.0
|
||||
aiozoneinfo==0.2.1
|
||||
astral==2.2
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2024.8.0b1"
|
||||
version = "2024.8.0b2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
@@ -24,7 +24,7 @@ classifiers = [
|
||||
requires-python = ">=3.12.0"
|
||||
dependencies = [
|
||||
"aiodns==3.2.0",
|
||||
"aiohttp==3.10.0",
|
||||
"aiohttp==3.10.1",
|
||||
"aiohttp_cors==0.7.0",
|
||||
"aiohttp-fast-zlib==0.1.1",
|
||||
"aiozoneinfo==0.2.1",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
# Home Assistant Core
|
||||
aiodns==3.2.0
|
||||
aiohttp==3.10.0
|
||||
aiohttp==3.10.1
|
||||
aiohttp_cors==0.7.0
|
||||
aiohttp-fast-zlib==0.1.1
|
||||
aiozoneinfo==0.2.1
|
||||
|
||||
@@ -1007,7 +1007,7 @@ gpiozero==1.6.2
|
||||
gps3==0.33.3
|
||||
|
||||
# homeassistant.components.gree
|
||||
greeclimate==1.4.6
|
||||
greeclimate==2.0.0
|
||||
|
||||
# homeassistant.components.greeneye_monitor
|
||||
greeneye_monitor==3.0.3
|
||||
@@ -1840,7 +1840,7 @@ pyeiscp==0.0.7
|
||||
pyemoncms==0.0.7
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.20.6
|
||||
pyenphase==1.22.0
|
||||
|
||||
# homeassistant.components.envisalink
|
||||
pyenvisalink==4.7
|
||||
@@ -2986,7 +2986,7 @@ zeroconf==0.132.2
|
||||
zeversolar==0.3.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.25
|
||||
zha==0.0.27
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.12
|
||||
|
||||
@@ -848,7 +848,7 @@ govee-local-api==1.5.1
|
||||
gps3==0.33.3
|
||||
|
||||
# homeassistant.components.gree
|
||||
greeclimate==1.4.6
|
||||
greeclimate==2.0.0
|
||||
|
||||
# homeassistant.components.greeneye_monitor
|
||||
greeneye_monitor==3.0.3
|
||||
@@ -1469,7 +1469,7 @@ pyegps==0.2.5
|
||||
pyemoncms==0.0.7
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.20.6
|
||||
pyenphase==1.22.0
|
||||
|
||||
# homeassistant.components.everlights
|
||||
pyeverlights==0.1.0
|
||||
@@ -2360,7 +2360,7 @@ zeroconf==0.132.2
|
||||
zeversolar==0.3.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.25
|
||||
zha==0.0.27
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.57.0
|
||||
|
||||
@@ -119,6 +119,106 @@ async def test_migrate_gas_to_mbus(
|
||||
)
|
||||
|
||||
|
||||
async def test_migrate_hourly_gas_to_mbus(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
dsmr_connection_fixture: tuple[MagicMock, MagicMock, MagicMock],
|
||||
) -> None:
|
||||
"""Test migration of unique_id."""
|
||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="/dev/ttyUSB0",
|
||||
data={
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5",
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "4730303738353635363037343639323231",
|
||||
},
|
||||
options={
|
||||
"time_between_update": 0,
|
||||
},
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
old_unique_id = "4730303738353635363037343639323231_hourly_gas_meter_reading"
|
||||
|
||||
device = device_registry.async_get_or_create(
|
||||
config_entry_id=mock_entry.entry_id,
|
||||
identifiers={(DOMAIN, mock_entry.entry_id)},
|
||||
name="Gas Meter",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity: er.RegistryEntry = entity_registry.async_get_or_create(
|
||||
suggested_object_id="gas_meter_reading",
|
||||
disabled_by=None,
|
||||
domain=SENSOR_DOMAIN,
|
||||
platform=DOMAIN,
|
||||
device_id=device.id,
|
||||
unique_id=old_unique_id,
|
||||
config_entry=mock_entry,
|
||||
)
|
||||
assert entity.unique_id == old_unique_id
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram = Telegram()
|
||||
telegram.add(
|
||||
MBUS_DEVICE_TYPE,
|
||||
CosemObject((0, 1), [{"value": "003", "unit": ""}]),
|
||||
"MBUS_DEVICE_TYPE",
|
||||
)
|
||||
telegram.add(
|
||||
MBUS_EQUIPMENT_IDENTIFIER,
|
||||
CosemObject(
|
||||
(0, 1),
|
||||
[{"value": "4730303738353635363037343639323231", "unit": ""}],
|
||||
),
|
||||
"MBUS_EQUIPMENT_IDENTIFIER",
|
||||
)
|
||||
telegram.add(
|
||||
MBUS_METER_READING,
|
||||
MBusObject(
|
||||
(0, 1),
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1722749707)},
|
||||
{"value": Decimal(778.963), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
"MBUS_METER_READING",
|
||||
)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
|
||||
telegram_callback(telegram)
|
||||
|
||||
# after receiving telegram entities need to have the chance to be created
|
||||
await hass.async_block_till_done()
|
||||
|
||||
dev_entities = er.async_entries_for_device(
|
||||
entity_registry, device.id, include_disabled_entities=True
|
||||
)
|
||||
assert not dev_entities
|
||||
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(SENSOR_DOMAIN, DOMAIN, old_unique_id)
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, "4730303738353635363037343639323231"
|
||||
)
|
||||
== "sensor.gas_meter_reading"
|
||||
)
|
||||
|
||||
|
||||
async def test_migrate_gas_to_mbus_exists(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_default_prompt[config_entry_options0-None]
|
||||
# name: test_default_prompt[config_entry_options0-0-None]
|
||||
list([
|
||||
tuple(
|
||||
'',
|
||||
@@ -263,7 +263,7 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_default_prompt[config_entry_options0-conversation.google_generative_ai_conversation]
|
||||
# name: test_default_prompt[config_entry_options0-0-conversation.google_generative_ai_conversation]
|
||||
list([
|
||||
tuple(
|
||||
'',
|
||||
@@ -311,7 +311,7 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_default_prompt[config_entry_options1-None]
|
||||
# name: test_default_prompt[config_entry_options1-1-None]
|
||||
list([
|
||||
tuple(
|
||||
'',
|
||||
@@ -360,7 +360,7 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_default_prompt[config_entry_options1-conversation.google_generative_ai_conversation]
|
||||
# name: test_default_prompt[config_entry_options1-1-conversation.google_generative_ai_conversation]
|
||||
list([
|
||||
tuple(
|
||||
'',
|
||||
|
||||
@@ -17,8 +17,9 @@ from homeassistant.components.google_generative_ai_conversation.const import (
|
||||
)
|
||||
from homeassistant.components.google_generative_ai_conversation.conversation import (
|
||||
_escape_decode,
|
||||
_format_schema,
|
||||
)
|
||||
from homeassistant.const import CONF_LLM_HASS_API
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_LLM_HASS_API
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import intent, llm
|
||||
@@ -38,10 +39,13 @@ def freeze_the_time():
|
||||
"agent_id", [None, "conversation.google_generative_ai_conversation"]
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"config_entry_options",
|
||||
("config_entry_options", "expected_features"),
|
||||
[
|
||||
{},
|
||||
{CONF_LLM_HASS_API: llm.LLM_API_ASSIST},
|
||||
({}, 0),
|
||||
(
|
||||
{CONF_LLM_HASS_API: llm.LLM_API_ASSIST},
|
||||
conversation.ConversationEntityFeature.CONTROL,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
@@ -51,6 +55,7 @@ async def test_default_prompt(
|
||||
snapshot: SnapshotAssertion,
|
||||
agent_id: str | None,
|
||||
config_entry_options: {},
|
||||
expected_features: conversation.ConversationEntityFeature,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test that the default prompt works."""
|
||||
@@ -97,6 +102,9 @@ async def test_default_prompt(
|
||||
assert [tuple(mock_call) for mock_call in mock_model.mock_calls] == snapshot
|
||||
assert mock_get_tools.called == (CONF_LLM_HASS_API in config_entry_options)
|
||||
|
||||
state = hass.states.get("conversation.google_generative_ai_conversation")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == expected_features
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model_name", "supports_system_instruction"),
|
||||
@@ -622,3 +630,61 @@ async def test_escape_decode() -> None:
|
||||
"param2": "param2's value",
|
||||
"param3": {"param31": "Cheminée", "param32": "Cheminée"},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("openapi", "protobuf"),
|
||||
[
|
||||
(
|
||||
{"type": "string", "enum": ["a", "b", "c"]},
|
||||
{"type_": "STRING", "enum": ["a", "b", "c"]},
|
||||
),
|
||||
(
|
||||
{"type": "integer", "enum": [1, 2, 3]},
|
||||
{"type_": "STRING", "enum": ["1", "2", "3"]},
|
||||
),
|
||||
({"anyOf": [{"type": "integer"}, {"type": "number"}]}, {"type_": "INTEGER"}),
|
||||
(
|
||||
{
|
||||
"anyOf": [
|
||||
{"anyOf": [{"type": "integer"}, {"type": "number"}]},
|
||||
{"anyOf": [{"type": "integer"}, {"type": "number"}]},
|
||||
]
|
||||
},
|
||||
{"type_": "INTEGER"},
|
||||
),
|
||||
({"type": "string", "format": "lower"}, {"type_": "STRING"}),
|
||||
({"type": "boolean", "format": "bool"}, {"type_": "BOOLEAN"}),
|
||||
(
|
||||
{"type": "number", "format": "percent"},
|
||||
{"type_": "NUMBER", "format_": "percent"},
|
||||
),
|
||||
(
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {"var": {"type": "string"}},
|
||||
"required": [],
|
||||
},
|
||||
{
|
||||
"type_": "OBJECT",
|
||||
"properties": {"var": {"type_": "STRING"}},
|
||||
"required": [],
|
||||
},
|
||||
),
|
||||
(
|
||||
{"type": "object", "additionalProperties": True},
|
||||
{
|
||||
"type_": "OBJECT",
|
||||
"properties": {"json": {"type_": "STRING"}},
|
||||
"required": [],
|
||||
},
|
||||
),
|
||||
(
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type_": "ARRAY", "items": {"type_": "STRING"}},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_format_schema(openapi, protobuf) -> None:
|
||||
"""Test _format_schema."""
|
||||
assert _format_schema(openapi) == protobuf
|
||||
|
||||
@@ -5,8 +5,12 @@ from datetime import timedelta
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.climate import DOMAIN
|
||||
from homeassistant.components.gree.const import COORDINATORS, DOMAIN as GREE
|
||||
from homeassistant.components.climate import DOMAIN, HVACMode
|
||||
from homeassistant.components.gree.const import (
|
||||
COORDINATORS,
|
||||
DOMAIN as GREE,
|
||||
UPDATE_INTERVAL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
@@ -69,3 +73,30 @@ async def test_discovery_after_setup(
|
||||
device_infos = [x.device.device_info for x in hass.data[GREE][COORDINATORS]]
|
||||
assert device_infos[0].ip == "1.1.1.2"
|
||||
assert device_infos[1].ip == "2.2.2.1"
|
||||
|
||||
|
||||
async def test_coordinator_updates(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Test gree devices update their state."""
|
||||
await async_setup_gree(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all(DOMAIN)) == 1
|
||||
|
||||
callback = device().add_handler.call_args_list[0][0][1]
|
||||
|
||||
async def fake_update_state(*args) -> None:
|
||||
"""Fake update state."""
|
||||
device().power = True
|
||||
callback()
|
||||
|
||||
device().update_state.side_effect = fake_update_state
|
||||
|
||||
freezer.tick(timedelta(seconds=UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID_1)
|
||||
assert state is not None
|
||||
assert state.state != HVACMode.OFF
|
||||
|
||||
@@ -48,7 +48,12 @@ from homeassistant.components.gree.climate import (
|
||||
HVAC_MODES_REVERSE,
|
||||
GreeClimateEntity,
|
||||
)
|
||||
from homeassistant.components.gree.const import FAN_MEDIUM_HIGH, FAN_MEDIUM_LOW
|
||||
from homeassistant.components.gree.const import (
|
||||
DISCOVERY_SCAN_INTERVAL,
|
||||
FAN_MEDIUM_HIGH,
|
||||
FAN_MEDIUM_LOW,
|
||||
UPDATE_INTERVAL,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_TEMPERATURE,
|
||||
@@ -61,7 +66,6 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .common import async_setup_gree, build_device_mock
|
||||
|
||||
@@ -70,12 +74,6 @@ from tests.common import async_fire_time_changed
|
||||
ENTITY_ID = f"{DOMAIN}.fake_device_1"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_now():
|
||||
"""Fixture for dtutil.now."""
|
||||
return dt_util.utcnow()
|
||||
|
||||
|
||||
async def test_discovery_called_once(hass: HomeAssistant, discovery, device) -> None:
|
||||
"""Test discovery is only ever called once."""
|
||||
await async_setup_gree(hass)
|
||||
@@ -104,7 +102,7 @@ async def test_discovery_setup(hass: HomeAssistant, discovery, device) -> None:
|
||||
|
||||
|
||||
async def test_discovery_setup_connection_error(
|
||||
hass: HomeAssistant, discovery, device, mock_now
|
||||
hass: HomeAssistant, discovery, device
|
||||
) -> None:
|
||||
"""Test gree integration is setup."""
|
||||
MockDevice1 = build_device_mock(
|
||||
@@ -126,7 +124,7 @@ async def test_discovery_setup_connection_error(
|
||||
|
||||
|
||||
async def test_discovery_after_setup(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device, mock_now
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Test gree devices don't change after multiple discoveries."""
|
||||
MockDevice1 = build_device_mock(
|
||||
@@ -142,8 +140,7 @@ async def test_discovery_after_setup(
|
||||
discovery.return_value.mock_devices = [MockDevice1, MockDevice2]
|
||||
device.side_effect = [MockDevice1, MockDevice2]
|
||||
|
||||
await async_setup_gree(hass)
|
||||
await hass.async_block_till_done()
|
||||
await async_setup_gree(hass) # Update 1
|
||||
|
||||
assert discovery.return_value.scan_count == 1
|
||||
assert len(hass.states.async_all(DOMAIN)) == 2
|
||||
@@ -152,9 +149,8 @@ async def test_discovery_after_setup(
|
||||
discovery.return_value.mock_devices = [MockDevice1, MockDevice2]
|
||||
device.side_effect = [MockDevice1, MockDevice2]
|
||||
|
||||
next_update = mock_now + timedelta(minutes=6)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
freezer.tick(timedelta(seconds=DISCOVERY_SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert discovery.return_value.scan_count == 2
|
||||
@@ -162,7 +158,7 @@ async def test_discovery_after_setup(
|
||||
|
||||
|
||||
async def test_discovery_add_device_after_setup(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device, mock_now
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Test gree devices can be added after initial setup."""
|
||||
MockDevice1 = build_device_mock(
|
||||
@@ -178,6 +174,8 @@ async def test_discovery_add_device_after_setup(
|
||||
discovery.return_value.mock_devices = [MockDevice1]
|
||||
device.side_effect = [MockDevice1]
|
||||
|
||||
await async_setup_gree(hass) # Update 1
|
||||
|
||||
await async_setup_gree(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -188,9 +186,8 @@ async def test_discovery_add_device_after_setup(
|
||||
discovery.return_value.mock_devices = [MockDevice2]
|
||||
device.side_effect = [MockDevice2]
|
||||
|
||||
next_update = mock_now + timedelta(minutes=6)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
freezer.tick(timedelta(seconds=DISCOVERY_SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert discovery.return_value.scan_count == 2
|
||||
@@ -198,7 +195,7 @@ async def test_discovery_add_device_after_setup(
|
||||
|
||||
|
||||
async def test_discovery_device_bind_after_setup(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device, mock_now
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Test gree devices can be added after a late device bind."""
|
||||
MockDevice1 = build_device_mock(
|
||||
@@ -210,8 +207,7 @@ async def test_discovery_device_bind_after_setup(
|
||||
discovery.return_value.mock_devices = [MockDevice1]
|
||||
device.return_value = MockDevice1
|
||||
|
||||
await async_setup_gree(hass)
|
||||
await hass.async_block_till_done()
|
||||
await async_setup_gree(hass) # Update 1
|
||||
|
||||
assert len(hass.states.async_all(DOMAIN)) == 1
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
@@ -222,9 +218,8 @@ async def test_discovery_device_bind_after_setup(
|
||||
MockDevice1.bind.side_effect = None
|
||||
MockDevice1.update_state.side_effect = None
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
freezer.tick(timedelta(seconds=DISCOVERY_SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
@@ -232,7 +227,7 @@ async def test_discovery_device_bind_after_setup(
|
||||
|
||||
|
||||
async def test_update_connection_failure(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, device, mock_now
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Testing update hvac connection failure exception."""
|
||||
device().update_state.side_effect = [
|
||||
@@ -241,36 +236,32 @@ async def test_update_connection_failure(
|
||||
DeviceTimeoutError,
|
||||
]
|
||||
|
||||
await async_setup_gree(hass)
|
||||
await async_setup_gree(hass) # Update 1
|
||||
|
||||
async def run_update():
|
||||
freezer.tick(timedelta(seconds=UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# First update to make the device available
|
||||
# Update 2
|
||||
await run_update()
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
next_update = mock_now + timedelta(minutes=10)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
# Update 3
|
||||
await run_update()
|
||||
|
||||
next_update = mock_now + timedelta(minutes=15)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Then two more update failures to make the device unavailable
|
||||
# Update 4
|
||||
await run_update()
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_update_connection_failure_recovery(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device, mock_now
|
||||
async def test_update_connection_send_failure_recovery(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Testing update hvac connection failure recovery."""
|
||||
device().update_state.side_effect = [
|
||||
@@ -279,31 +270,27 @@ async def test_update_connection_failure_recovery(
|
||||
DEFAULT_MOCK,
|
||||
]
|
||||
|
||||
await async_setup_gree(hass)
|
||||
await async_setup_gree(hass) # Update 1
|
||||
|
||||
async def run_update():
|
||||
freezer.tick(timedelta(seconds=UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
# First update becomes unavailable
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await run_update() # Update 2
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Second update restores the connection
|
||||
next_update = mock_now + timedelta(minutes=10)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await run_update() # Update 3
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_update_unhandled_exception(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device, mock_now
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Testing update hvac connection unhandled response exception."""
|
||||
device().update_state.side_effect = [DEFAULT_MOCK, Exception]
|
||||
@@ -314,9 +301,8 @@ async def test_update_unhandled_exception(
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
next_update = mock_now + timedelta(minutes=10)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
freezer.tick(timedelta(seconds=UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
@@ -325,15 +311,13 @@ async def test_update_unhandled_exception(
|
||||
|
||||
|
||||
async def test_send_command_device_timeout(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device, mock_now
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Test for sending power on command to the device with a device timeout."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
# First update to make the device available
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
freezer.move_to(next_update)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
freezer.tick(timedelta(seconds=UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
@@ -355,7 +339,40 @@ async def test_send_command_device_timeout(
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_send_power_on(hass: HomeAssistant, discovery, device, mock_now) -> None:
|
||||
async def test_unresponsive_device(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, discovery, device
|
||||
) -> None:
|
||||
"""Test for unresponsive device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
async def run_update():
|
||||
freezer.tick(timedelta(seconds=UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Update 2
|
||||
await run_update()
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
# Update 3, 4, 5
|
||||
await run_update()
|
||||
await run_update()
|
||||
await run_update()
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Receiving update from device will reset the state to available again
|
||||
device().device_state_updated("test")
|
||||
await run_update()
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_send_power_on(hass: HomeAssistant, discovery, device) -> None:
|
||||
"""Test for sending power on command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
@@ -372,7 +389,7 @@ async def test_send_power_on(hass: HomeAssistant, discovery, device, mock_now) -
|
||||
|
||||
|
||||
async def test_send_power_off_device_timeout(
|
||||
hass: HomeAssistant, discovery, device, mock_now
|
||||
hass: HomeAssistant, discovery, device
|
||||
) -> None:
|
||||
"""Test for sending power off command to the device with a device timeout."""
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
@@ -543,9 +560,7 @@ async def test_update_target_temperature(
|
||||
@pytest.mark.parametrize(
|
||||
"preset", [PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE]
|
||||
)
|
||||
async def test_send_preset_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, preset
|
||||
) -> None:
|
||||
async def test_send_preset_mode(hass: HomeAssistant, discovery, device, preset) -> None:
|
||||
"""Test for sending preset mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
@@ -561,9 +576,7 @@ async def test_send_preset_mode(
|
||||
assert state.attributes.get(ATTR_PRESET_MODE) == preset
|
||||
|
||||
|
||||
async def test_send_invalid_preset_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now
|
||||
) -> None:
|
||||
async def test_send_invalid_preset_mode(hass: HomeAssistant, discovery, device) -> None:
|
||||
"""Test for sending preset mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
@@ -584,7 +597,7 @@ async def test_send_invalid_preset_mode(
|
||||
"preset", [PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE]
|
||||
)
|
||||
async def test_send_preset_mode_device_timeout(
|
||||
hass: HomeAssistant, discovery, device, mock_now, preset
|
||||
hass: HomeAssistant, discovery, device, preset
|
||||
) -> None:
|
||||
"""Test for sending preset mode command to the device with a device timeout."""
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
@@ -607,7 +620,7 @@ async def test_send_preset_mode_device_timeout(
|
||||
"preset", [PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE]
|
||||
)
|
||||
async def test_update_preset_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, preset
|
||||
hass: HomeAssistant, discovery, device, preset
|
||||
) -> None:
|
||||
"""Test for updating preset mode from the device."""
|
||||
device().steady_heat = preset == PRESET_AWAY
|
||||
@@ -634,7 +647,7 @@ async def test_update_preset_mode(
|
||||
],
|
||||
)
|
||||
async def test_send_hvac_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, hvac_mode
|
||||
hass: HomeAssistant, discovery, device, hvac_mode
|
||||
) -> None:
|
||||
"""Test for sending hvac mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
@@ -656,7 +669,7 @@ async def test_send_hvac_mode(
|
||||
[HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.FAN_ONLY, HVACMode.HEAT],
|
||||
)
|
||||
async def test_send_hvac_mode_device_timeout(
|
||||
hass: HomeAssistant, discovery, device, mock_now, hvac_mode
|
||||
hass: HomeAssistant, discovery, device, hvac_mode
|
||||
) -> None:
|
||||
"""Test for sending hvac mode command to the device with a device timeout."""
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
@@ -687,7 +700,7 @@ async def test_send_hvac_mode_device_timeout(
|
||||
],
|
||||
)
|
||||
async def test_update_hvac_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, hvac_mode
|
||||
hass: HomeAssistant, discovery, device, hvac_mode
|
||||
) -> None:
|
||||
"""Test for updating hvac mode from the device."""
|
||||
device().power = hvac_mode != HVACMode.OFF
|
||||
@@ -704,9 +717,7 @@ async def test_update_hvac_mode(
|
||||
"fan_mode",
|
||||
[FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH],
|
||||
)
|
||||
async def test_send_fan_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, fan_mode
|
||||
) -> None:
|
||||
async def test_send_fan_mode(hass: HomeAssistant, discovery, device, fan_mode) -> None:
|
||||
"""Test for sending fan mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
@@ -722,9 +733,7 @@ async def test_send_fan_mode(
|
||||
assert state.attributes.get(ATTR_FAN_MODE) == fan_mode
|
||||
|
||||
|
||||
async def test_send_invalid_fan_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now
|
||||
) -> None:
|
||||
async def test_send_invalid_fan_mode(hass: HomeAssistant, discovery, device) -> None:
|
||||
"""Test for sending fan mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
@@ -746,7 +755,7 @@ async def test_send_invalid_fan_mode(
|
||||
[FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH],
|
||||
)
|
||||
async def test_send_fan_mode_device_timeout(
|
||||
hass: HomeAssistant, discovery, device, mock_now, fan_mode
|
||||
hass: HomeAssistant, discovery, device, fan_mode
|
||||
) -> None:
|
||||
"""Test for sending fan mode command to the device with a device timeout."""
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
@@ -770,7 +779,7 @@ async def test_send_fan_mode_device_timeout(
|
||||
[FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH],
|
||||
)
|
||||
async def test_update_fan_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, fan_mode
|
||||
hass: HomeAssistant, discovery, device, fan_mode
|
||||
) -> None:
|
||||
"""Test for updating fan mode from the device."""
|
||||
device().fan_speed = FAN_MODES_REVERSE.get(fan_mode)
|
||||
@@ -786,7 +795,7 @@ async def test_update_fan_mode(
|
||||
"swing_mode", [SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL]
|
||||
)
|
||||
async def test_send_swing_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, swing_mode
|
||||
hass: HomeAssistant, discovery, device, swing_mode
|
||||
) -> None:
|
||||
"""Test for sending swing mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
@@ -803,9 +812,7 @@ async def test_send_swing_mode(
|
||||
assert state.attributes.get(ATTR_SWING_MODE) == swing_mode
|
||||
|
||||
|
||||
async def test_send_invalid_swing_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now
|
||||
) -> None:
|
||||
async def test_send_invalid_swing_mode(hass: HomeAssistant, discovery, device) -> None:
|
||||
"""Test for sending swing mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
@@ -826,7 +833,7 @@ async def test_send_invalid_swing_mode(
|
||||
"swing_mode", [SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL]
|
||||
)
|
||||
async def test_send_swing_mode_device_timeout(
|
||||
hass: HomeAssistant, discovery, device, mock_now, swing_mode
|
||||
hass: HomeAssistant, discovery, device, swing_mode
|
||||
) -> None:
|
||||
"""Test for sending swing mode command to the device with a device timeout."""
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
@@ -849,7 +856,7 @@ async def test_send_swing_mode_device_timeout(
|
||||
"swing_mode", [SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL]
|
||||
)
|
||||
async def test_update_swing_mode(
|
||||
hass: HomeAssistant, discovery, device, mock_now, swing_mode
|
||||
hass: HomeAssistant, discovery, device, swing_mode
|
||||
) -> None:
|
||||
"""Test for updating swing mode from the device."""
|
||||
device().horizontal_swing = (
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Test init of IronOS integration."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from pynecil import CommunicationError
|
||||
import pytest
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("ble_device")
|
||||
async def test_setup_config_entry_not_ready(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
mock_pynecil: AsyncMock,
|
||||
) -> None:
|
||||
"""Test config entry not ready."""
|
||||
mock_pynecil.get_device_info.side_effect = CommunicationError
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
@@ -1305,3 +1305,22 @@ async def test_reconfigure_with_exception(
|
||||
)
|
||||
|
||||
assert result["errors"] == {"base": base_error}
|
||||
|
||||
|
||||
async def test_zeroconf_rejects_ipv6(hass: HomeAssistant) -> None:
|
||||
"""Test zeroconf discovery rejects ipv6."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("fd00::b27c:63bb:cc85:4ea0"),
|
||||
ip_addresses=[ip_address("fd00::b27c:63bb:cc85:4ea0")],
|
||||
hostname="mock_hostname",
|
||||
name="shelly1pm-12345",
|
||||
port=None,
|
||||
properties={zeroconf.ATTR_PROPERTIES_ID: "shelly1pm-12345"},
|
||||
type="mock_type",
|
||||
),
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "ipv6_not_supported"
|
||||
|
||||
Reference in New Issue
Block a user