mirror of
https://github.com/home-assistant/core.git
synced 2025-07-31 17:18:23 +00:00
Merge branch 'dev' into epenet-20250602-1736
This commit is contained in:
commit
4996d5c3cf
@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from .const import CONNECTION_TYPE, LOCAL
|
from .const import CONNECTION_TYPE, LOCAL
|
||||||
from .coordinator import AdaxCloudCoordinator, AdaxConfigEntry, AdaxLocalCoordinator
|
from .coordinator import AdaxCloudCoordinator, AdaxConfigEntry, AdaxLocalCoordinator
|
||||||
|
|
||||||
PLATFORMS = [Platform.CLIMATE]
|
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: AdaxConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: AdaxConfigEntry) -> bool:
|
||||||
|
@ -41,7 +41,30 @@ class AdaxCloudCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
|
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
|
||||||
"""Fetch data from the Adax."""
|
"""Fetch data from the Adax."""
|
||||||
rooms = await self.adax_data_handler.get_rooms() or []
|
try:
|
||||||
|
if hasattr(self.adax_data_handler, "fetch_rooms_info"):
|
||||||
|
rooms = await self.adax_data_handler.fetch_rooms_info() or []
|
||||||
|
_LOGGER.debug("fetch_rooms_info returned: %s", rooms)
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("fetch_rooms_info method not available, using get_rooms")
|
||||||
|
rooms = []
|
||||||
|
|
||||||
|
if not rooms:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"No rooms from fetch_rooms_info, trying get_rooms as fallback"
|
||||||
|
)
|
||||||
|
rooms = await self.adax_data_handler.get_rooms() or []
|
||||||
|
_LOGGER.debug("get_rooms fallback returned: %s", rooms)
|
||||||
|
|
||||||
|
if not rooms:
|
||||||
|
raise UpdateFailed("No rooms available from Adax API")
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
raise UpdateFailed(f"Error communicating with API: {e}") from e
|
||||||
|
|
||||||
|
for room in rooms:
|
||||||
|
room["energyWh"] = int(room.get("energyWh", 0))
|
||||||
|
|
||||||
return {r["id"]: r for r in rooms}
|
return {r["id"]: r for r in rooms}
|
||||||
|
|
||||||
|
|
||||||
|
77
homeassistant/components/adax/sensor.py
Normal file
77
homeassistant/components/adax/sensor.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""Support for Adax energy sensors."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.const import UnitOfEnergy
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from . import AdaxConfigEntry
|
||||||
|
from .const import CONNECTION_TYPE, DOMAIN, LOCAL
|
||||||
|
from .coordinator import AdaxCloudCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: AdaxConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Adax energy sensors with config flow."""
|
||||||
|
if entry.data.get(CONNECTION_TYPE) != LOCAL:
|
||||||
|
cloud_coordinator = cast(AdaxCloudCoordinator, entry.runtime_data)
|
||||||
|
|
||||||
|
# Create individual energy sensors for each device
|
||||||
|
async_add_entities(
|
||||||
|
AdaxEnergySensor(cloud_coordinator, device_id)
|
||||||
|
for device_id in cloud_coordinator.data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AdaxEnergySensor(CoordinatorEntity[AdaxCloudCoordinator], SensorEntity):
|
||||||
|
"""Representation of an Adax energy sensor."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_translation_key = "energy"
|
||||||
|
_attr_device_class = SensorDeviceClass.ENERGY
|
||||||
|
_attr_native_unit_of_measurement = UnitOfEnergy.WATT_HOUR
|
||||||
|
_attr_suggested_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
|
||||||
|
_attr_state_class = SensorStateClass.TOTAL_INCREASING
|
||||||
|
_attr_suggested_display_precision = 3
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AdaxCloudCoordinator,
|
||||||
|
device_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the energy sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._device_id = device_id
|
||||||
|
room = coordinator.data[device_id]
|
||||||
|
|
||||||
|
self._attr_unique_id = f"{room['homeId']}_{device_id}_energy"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, device_id)},
|
||||||
|
name=room["name"],
|
||||||
|
manufacturer="Adax",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return (
|
||||||
|
super().available and "energyWh" in self.coordinator.data[self._device_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> int:
|
||||||
|
"""Return the native value of the sensor."""
|
||||||
|
return int(self.coordinator.data[self._device_id]["energyWh"])
|
@ -21,7 +21,7 @@ from .const import (
|
|||||||
HMIPC_NAME,
|
HMIPC_NAME,
|
||||||
)
|
)
|
||||||
from .hap import HomematicIPConfigEntry, HomematicipHAP
|
from .hap import HomematicIPConfigEntry, HomematicipHAP
|
||||||
from .services import async_setup_services, async_unload_services
|
from .services import async_setup_services
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -116,8 +116,6 @@ async def async_unload_entry(
|
|||||||
assert hap.reset_connection_listener is not None
|
assert hap.reset_connection_listener is not None
|
||||||
hap.reset_connection_listener()
|
hap.reset_connection_listener()
|
||||||
|
|
||||||
await async_unload_services(hass)
|
|
||||||
|
|
||||||
return await hap.async_reset()
|
return await hap.async_reset()
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,32 +123,29 @@ SCHEMA_SET_HOME_COOLING_MODE = vol.Schema(
|
|||||||
async def async_setup_services(hass: HomeAssistant) -> None:
|
async def async_setup_services(hass: HomeAssistant) -> None:
|
||||||
"""Set up the HomematicIP Cloud services."""
|
"""Set up the HomematicIP Cloud services."""
|
||||||
|
|
||||||
if hass.services.async_services_for_domain(DOMAIN):
|
@verify_domain_entity_control(hass, DOMAIN)
|
||||||
return
|
|
||||||
|
|
||||||
@verify_domain_entity_control(DOMAIN)
|
|
||||||
async def async_call_hmipc_service(service: ServiceCall) -> None:
|
async def async_call_hmipc_service(service: ServiceCall) -> None:
|
||||||
"""Call correct HomematicIP Cloud service."""
|
"""Call correct HomematicIP Cloud service."""
|
||||||
service_name = service.service
|
service_name = service.service
|
||||||
|
|
||||||
if service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION:
|
if service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION:
|
||||||
await _async_activate_eco_mode_with_duration(hass, service)
|
await _async_activate_eco_mode_with_duration(service)
|
||||||
elif service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD:
|
elif service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD:
|
||||||
await _async_activate_eco_mode_with_period(hass, service)
|
await _async_activate_eco_mode_with_period(service)
|
||||||
elif service_name == SERVICE_ACTIVATE_VACATION:
|
elif service_name == SERVICE_ACTIVATE_VACATION:
|
||||||
await _async_activate_vacation(hass, service)
|
await _async_activate_vacation(service)
|
||||||
elif service_name == SERVICE_DEACTIVATE_ECO_MODE:
|
elif service_name == SERVICE_DEACTIVATE_ECO_MODE:
|
||||||
await _async_deactivate_eco_mode(hass, service)
|
await _async_deactivate_eco_mode(service)
|
||||||
elif service_name == SERVICE_DEACTIVATE_VACATION:
|
elif service_name == SERVICE_DEACTIVATE_VACATION:
|
||||||
await _async_deactivate_vacation(hass, service)
|
await _async_deactivate_vacation(service)
|
||||||
elif service_name == SERVICE_DUMP_HAP_CONFIG:
|
elif service_name == SERVICE_DUMP_HAP_CONFIG:
|
||||||
await _async_dump_hap_config(hass, service)
|
await _async_dump_hap_config(service)
|
||||||
elif service_name == SERVICE_RESET_ENERGY_COUNTER:
|
elif service_name == SERVICE_RESET_ENERGY_COUNTER:
|
||||||
await _async_reset_energy_counter(hass, service)
|
await _async_reset_energy_counter(service)
|
||||||
elif service_name == SERVICE_SET_ACTIVE_CLIMATE_PROFILE:
|
elif service_name == SERVICE_SET_ACTIVE_CLIMATE_PROFILE:
|
||||||
await _set_active_climate_profile(hass, service)
|
await _set_active_climate_profile(service)
|
||||||
elif service_name == SERVICE_SET_HOME_COOLING_MODE:
|
elif service_name == SERVICE_SET_HOME_COOLING_MODE:
|
||||||
await _async_set_home_cooling_mode(hass, service)
|
await _async_set_home_cooling_mode(service)
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
@ -217,90 +214,75 @@ async def async_setup_services(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_services(hass: HomeAssistant):
|
async def _async_activate_eco_mode_with_duration(service: ServiceCall) -> None:
|
||||||
"""Unload HomematicIP Cloud services."""
|
|
||||||
if hass.config_entries.async_loaded_entries(DOMAIN):
|
|
||||||
return
|
|
||||||
|
|
||||||
for hmipc_service in HMIPC_SERVICES:
|
|
||||||
hass.services.async_remove(domain=DOMAIN, service=hmipc_service)
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_activate_eco_mode_with_duration(
|
|
||||||
hass: HomeAssistant, service: ServiceCall
|
|
||||||
) -> None:
|
|
||||||
"""Service to activate eco mode with duration."""
|
"""Service to activate eco mode with duration."""
|
||||||
duration = service.data[ATTR_DURATION]
|
duration = service.data[ATTR_DURATION]
|
||||||
|
|
||||||
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
||||||
if home := _get_home(hass, hapid):
|
if home := _get_home(service.hass, hapid):
|
||||||
await home.activate_absence_with_duration_async(duration)
|
await home.activate_absence_with_duration_async(duration)
|
||||||
else:
|
else:
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
await entry.runtime_data.home.activate_absence_with_duration_async(duration)
|
await entry.runtime_data.home.activate_absence_with_duration_async(duration)
|
||||||
|
|
||||||
|
|
||||||
async def _async_activate_eco_mode_with_period(
|
async def _async_activate_eco_mode_with_period(service: ServiceCall) -> None:
|
||||||
hass: HomeAssistant, service: ServiceCall
|
|
||||||
) -> None:
|
|
||||||
"""Service to activate eco mode with period."""
|
"""Service to activate eco mode with period."""
|
||||||
endtime = service.data[ATTR_ENDTIME]
|
endtime = service.data[ATTR_ENDTIME]
|
||||||
|
|
||||||
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
||||||
if home := _get_home(hass, hapid):
|
if home := _get_home(service.hass, hapid):
|
||||||
await home.activate_absence_with_period_async(endtime)
|
await home.activate_absence_with_period_async(endtime)
|
||||||
else:
|
else:
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
await entry.runtime_data.home.activate_absence_with_period_async(endtime)
|
await entry.runtime_data.home.activate_absence_with_period_async(endtime)
|
||||||
|
|
||||||
|
|
||||||
async def _async_activate_vacation(hass: HomeAssistant, service: ServiceCall) -> None:
|
async def _async_activate_vacation(service: ServiceCall) -> None:
|
||||||
"""Service to activate vacation."""
|
"""Service to activate vacation."""
|
||||||
endtime = service.data[ATTR_ENDTIME]
|
endtime = service.data[ATTR_ENDTIME]
|
||||||
temperature = service.data[ATTR_TEMPERATURE]
|
temperature = service.data[ATTR_TEMPERATURE]
|
||||||
|
|
||||||
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
||||||
if home := _get_home(hass, hapid):
|
if home := _get_home(service.hass, hapid):
|
||||||
await home.activate_vacation_async(endtime, temperature)
|
await home.activate_vacation_async(endtime, temperature)
|
||||||
else:
|
else:
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
await entry.runtime_data.home.activate_vacation_async(endtime, temperature)
|
await entry.runtime_data.home.activate_vacation_async(endtime, temperature)
|
||||||
|
|
||||||
|
|
||||||
async def _async_deactivate_eco_mode(hass: HomeAssistant, service: ServiceCall) -> None:
|
async def _async_deactivate_eco_mode(service: ServiceCall) -> None:
|
||||||
"""Service to deactivate eco mode."""
|
"""Service to deactivate eco mode."""
|
||||||
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
||||||
if home := _get_home(hass, hapid):
|
if home := _get_home(service.hass, hapid):
|
||||||
await home.deactivate_absence_async()
|
await home.deactivate_absence_async()
|
||||||
else:
|
else:
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
await entry.runtime_data.home.deactivate_absence_async()
|
await entry.runtime_data.home.deactivate_absence_async()
|
||||||
|
|
||||||
|
|
||||||
async def _async_deactivate_vacation(hass: HomeAssistant, service: ServiceCall) -> None:
|
async def _async_deactivate_vacation(service: ServiceCall) -> None:
|
||||||
"""Service to deactivate vacation."""
|
"""Service to deactivate vacation."""
|
||||||
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
||||||
if home := _get_home(hass, hapid):
|
if home := _get_home(service.hass, hapid):
|
||||||
await home.deactivate_vacation_async()
|
await home.deactivate_vacation_async()
|
||||||
else:
|
else:
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
await entry.runtime_data.home.deactivate_vacation_async()
|
await entry.runtime_data.home.deactivate_vacation_async()
|
||||||
|
|
||||||
|
|
||||||
async def _set_active_climate_profile(
|
async def _set_active_climate_profile(service: ServiceCall) -> None:
|
||||||
hass: HomeAssistant, service: ServiceCall
|
|
||||||
) -> None:
|
|
||||||
"""Service to set the active climate profile."""
|
"""Service to set the active climate profile."""
|
||||||
entity_id_list = service.data[ATTR_ENTITY_ID]
|
entity_id_list = service.data[ATTR_ENTITY_ID]
|
||||||
climate_profile_index = service.data[ATTR_CLIMATE_PROFILE_INDEX] - 1
|
climate_profile_index = service.data[ATTR_CLIMATE_PROFILE_INDEX] - 1
|
||||||
|
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
if entity_id_list != "all":
|
if entity_id_list != "all":
|
||||||
for entity_id in entity_id_list:
|
for entity_id in entity_id_list:
|
||||||
group = entry.runtime_data.hmip_device_by_entity_id.get(entity_id)
|
group = entry.runtime_data.hmip_device_by_entity_id.get(entity_id)
|
||||||
@ -312,16 +294,16 @@ async def _set_active_climate_profile(
|
|||||||
await group.set_active_profile_async(climate_profile_index)
|
await group.set_active_profile_async(climate_profile_index)
|
||||||
|
|
||||||
|
|
||||||
async def _async_dump_hap_config(hass: HomeAssistant, service: ServiceCall) -> None:
|
async def _async_dump_hap_config(service: ServiceCall) -> None:
|
||||||
"""Service to dump the configuration of a Homematic IP Access Point."""
|
"""Service to dump the configuration of a Homematic IP Access Point."""
|
||||||
config_path: str = (
|
config_path: str = (
|
||||||
service.data.get(ATTR_CONFIG_OUTPUT_PATH) or hass.config.config_dir
|
service.data.get(ATTR_CONFIG_OUTPUT_PATH) or service.hass.config.config_dir
|
||||||
)
|
)
|
||||||
config_file_prefix = service.data[ATTR_CONFIG_OUTPUT_FILE_PREFIX]
|
config_file_prefix = service.data[ATTR_CONFIG_OUTPUT_FILE_PREFIX]
|
||||||
anonymize = service.data[ATTR_ANONYMIZE]
|
anonymize = service.data[ATTR_ANONYMIZE]
|
||||||
|
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
hap_sgtin = entry.unique_id
|
hap_sgtin = entry.unique_id
|
||||||
assert hap_sgtin is not None
|
assert hap_sgtin is not None
|
||||||
|
|
||||||
@ -338,12 +320,12 @@ async def _async_dump_hap_config(hass: HomeAssistant, service: ServiceCall) -> N
|
|||||||
config_file.write_text(json_state, encoding="utf8")
|
config_file.write_text(json_state, encoding="utf8")
|
||||||
|
|
||||||
|
|
||||||
async def _async_reset_energy_counter(hass: HomeAssistant, service: ServiceCall):
|
async def _async_reset_energy_counter(service: ServiceCall):
|
||||||
"""Service to reset the energy counter."""
|
"""Service to reset the energy counter."""
|
||||||
entity_id_list = service.data[ATTR_ENTITY_ID]
|
entity_id_list = service.data[ATTR_ENTITY_ID]
|
||||||
|
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
if entity_id_list != "all":
|
if entity_id_list != "all":
|
||||||
for entity_id in entity_id_list:
|
for entity_id in entity_id_list:
|
||||||
device = entry.runtime_data.hmip_device_by_entity_id.get(entity_id)
|
device = entry.runtime_data.hmip_device_by_entity_id.get(entity_id)
|
||||||
@ -355,16 +337,16 @@ async def _async_reset_energy_counter(hass: HomeAssistant, service: ServiceCall)
|
|||||||
await device.reset_energy_counter_async()
|
await device.reset_energy_counter_async()
|
||||||
|
|
||||||
|
|
||||||
async def _async_set_home_cooling_mode(hass: HomeAssistant, service: ServiceCall):
|
async def _async_set_home_cooling_mode(service: ServiceCall):
|
||||||
"""Service to set the cooling mode."""
|
"""Service to set the cooling mode."""
|
||||||
cooling = service.data[ATTR_COOLING]
|
cooling = service.data[ATTR_COOLING]
|
||||||
|
|
||||||
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
|
||||||
if home := _get_home(hass, hapid):
|
if home := _get_home(service.hass, hapid):
|
||||||
await home.set_cooling_async(cooling)
|
await home.set_cooling_async(cooling)
|
||||||
else:
|
else:
|
||||||
entry: HomematicIPConfigEntry
|
entry: HomematicIPConfigEntry
|
||||||
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
await entry.runtime_data.home.set_cooling_async(cooling)
|
await entry.runtime_data.home.set_cooling_async(cooling)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,19 +6,32 @@ from typing import Any
|
|||||||
|
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .account import IcloudAccount, IcloudConfigEntry
|
from .account import IcloudAccount, IcloudConfigEntry
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_GPS_ACCURACY_THRESHOLD,
|
CONF_GPS_ACCURACY_THRESHOLD,
|
||||||
CONF_MAX_INTERVAL,
|
CONF_MAX_INTERVAL,
|
||||||
CONF_WITH_FAMILY,
|
CONF_WITH_FAMILY,
|
||||||
|
DOMAIN,
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
STORAGE_KEY,
|
STORAGE_KEY,
|
||||||
STORAGE_VERSION,
|
STORAGE_VERSION,
|
||||||
)
|
)
|
||||||
from .services import register_services
|
from .services import register_services
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
"""Set up iCloud integration."""
|
||||||
|
|
||||||
|
register_services(hass)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: IcloudConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: IcloudConfigEntry) -> bool:
|
||||||
"""Set up an iCloud account from a config entry."""
|
"""Set up an iCloud account from a config entry."""
|
||||||
@ -51,8 +64,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IcloudConfigEntry) -> bo
|
|||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
register_services(hass)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@ from .const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
INSTEON_PLATFORMS,
|
INSTEON_PLATFORMS,
|
||||||
)
|
)
|
||||||
|
from .services import async_register_services
|
||||||
from .utils import (
|
from .utils import (
|
||||||
add_insteon_events,
|
add_insteon_events,
|
||||||
async_register_services,
|
|
||||||
get_device_platforms,
|
get_device_platforms,
|
||||||
register_new_device_callback,
|
register_new_device_callback,
|
||||||
)
|
)
|
||||||
|
291
homeassistant/components/insteon/services.py
Normal file
291
homeassistant/components/insteon/services.py
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
"""Utilities used by insteon component."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyinsteon import devices
|
||||||
|
from pyinsteon.address import Address
|
||||||
|
from pyinsteon.managers.link_manager import (
|
||||||
|
async_enter_linking_mode,
|
||||||
|
async_enter_unlinking_mode,
|
||||||
|
)
|
||||||
|
from pyinsteon.managers.scene_manager import (
|
||||||
|
async_trigger_scene_off,
|
||||||
|
async_trigger_scene_on,
|
||||||
|
)
|
||||||
|
from pyinsteon.managers.x10_manager import (
|
||||||
|
async_x10_all_lights_off,
|
||||||
|
async_x10_all_lights_on,
|
||||||
|
async_x10_all_units_off,
|
||||||
|
)
|
||||||
|
from pyinsteon.x10_address import create as create_x10_address
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ADDRESS,
|
||||||
|
CONF_ENTITY_ID,
|
||||||
|
CONF_PLATFORM,
|
||||||
|
ENTITY_MATCH_ALL,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.helpers.dispatcher import (
|
||||||
|
async_dispatcher_connect,
|
||||||
|
async_dispatcher_send,
|
||||||
|
dispatcher_send,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_CAT,
|
||||||
|
CONF_DIM_STEPS,
|
||||||
|
CONF_HOUSECODE,
|
||||||
|
CONF_SUBCAT,
|
||||||
|
CONF_UNITCODE,
|
||||||
|
DOMAIN,
|
||||||
|
SIGNAL_ADD_DEFAULT_LINKS,
|
||||||
|
SIGNAL_ADD_DEVICE_OVERRIDE,
|
||||||
|
SIGNAL_ADD_X10_DEVICE,
|
||||||
|
SIGNAL_LOAD_ALDB,
|
||||||
|
SIGNAL_PRINT_ALDB,
|
||||||
|
SIGNAL_REMOVE_DEVICE_OVERRIDE,
|
||||||
|
SIGNAL_REMOVE_ENTITY,
|
||||||
|
SIGNAL_REMOVE_HA_DEVICE,
|
||||||
|
SIGNAL_REMOVE_INSTEON_DEVICE,
|
||||||
|
SIGNAL_REMOVE_X10_DEVICE,
|
||||||
|
SIGNAL_SAVE_DEVICES,
|
||||||
|
SRV_ADD_ALL_LINK,
|
||||||
|
SRV_ADD_DEFAULT_LINKS,
|
||||||
|
SRV_ALL_LINK_GROUP,
|
||||||
|
SRV_ALL_LINK_MODE,
|
||||||
|
SRV_CONTROLLER,
|
||||||
|
SRV_DEL_ALL_LINK,
|
||||||
|
SRV_HOUSECODE,
|
||||||
|
SRV_LOAD_ALDB,
|
||||||
|
SRV_LOAD_DB_RELOAD,
|
||||||
|
SRV_PRINT_ALDB,
|
||||||
|
SRV_PRINT_IM_ALDB,
|
||||||
|
SRV_SCENE_OFF,
|
||||||
|
SRV_SCENE_ON,
|
||||||
|
SRV_X10_ALL_LIGHTS_OFF,
|
||||||
|
SRV_X10_ALL_LIGHTS_ON,
|
||||||
|
SRV_X10_ALL_UNITS_OFF,
|
||||||
|
)
|
||||||
|
from .schemas import (
|
||||||
|
ADD_ALL_LINK_SCHEMA,
|
||||||
|
ADD_DEFAULT_LINKS_SCHEMA,
|
||||||
|
DEL_ALL_LINK_SCHEMA,
|
||||||
|
LOAD_ALDB_SCHEMA,
|
||||||
|
PRINT_ALDB_SCHEMA,
|
||||||
|
TRIGGER_SCENE_SCHEMA,
|
||||||
|
X10_HOUSECODE_SCHEMA,
|
||||||
|
)
|
||||||
|
from .utils import print_aldb_to_log
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_register_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||||
|
"""Register services used by insteon component."""
|
||||||
|
|
||||||
|
save_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
async def async_srv_add_all_link(service: ServiceCall) -> None:
|
||||||
|
"""Add an INSTEON All-Link between two devices."""
|
||||||
|
group = service.data[SRV_ALL_LINK_GROUP]
|
||||||
|
mode = service.data[SRV_ALL_LINK_MODE]
|
||||||
|
link_mode = mode.lower() == SRV_CONTROLLER
|
||||||
|
await async_enter_linking_mode(link_mode, group)
|
||||||
|
|
||||||
|
async def async_srv_del_all_link(service: ServiceCall) -> None:
|
||||||
|
"""Delete an INSTEON All-Link between two devices."""
|
||||||
|
group = service.data.get(SRV_ALL_LINK_GROUP)
|
||||||
|
await async_enter_unlinking_mode(group)
|
||||||
|
|
||||||
|
async def async_srv_load_aldb(service: ServiceCall) -> None:
|
||||||
|
"""Load the device All-Link database."""
|
||||||
|
entity_id = service.data[CONF_ENTITY_ID]
|
||||||
|
reload = service.data[SRV_LOAD_DB_RELOAD]
|
||||||
|
if entity_id.lower() == ENTITY_MATCH_ALL:
|
||||||
|
await async_srv_load_aldb_all(reload)
|
||||||
|
else:
|
||||||
|
signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}"
|
||||||
|
async_dispatcher_send(hass, signal, reload)
|
||||||
|
|
||||||
|
async def async_srv_load_aldb_all(reload):
|
||||||
|
"""Load the All-Link database for all devices."""
|
||||||
|
# Cannot be done concurrently due to issues with the underlying protocol.
|
||||||
|
for address in devices:
|
||||||
|
device = devices[address]
|
||||||
|
if device != devices.modem and device.cat != 0x03:
|
||||||
|
await device.aldb.async_load(refresh=reload)
|
||||||
|
await async_srv_save_devices()
|
||||||
|
|
||||||
|
async def async_srv_save_devices():
|
||||||
|
"""Write the Insteon device configuration to file."""
|
||||||
|
async with save_lock:
|
||||||
|
_LOGGER.debug("Saving Insteon devices")
|
||||||
|
await devices.async_save(hass.config.config_dir)
|
||||||
|
|
||||||
|
def print_aldb(service: ServiceCall) -> None:
|
||||||
|
"""Print the All-Link Database for a device."""
|
||||||
|
# For now this sends logs to the log file.
|
||||||
|
# Future direction is to create an INSTEON control panel.
|
||||||
|
entity_id = service.data[CONF_ENTITY_ID]
|
||||||
|
signal = f"{entity_id}_{SIGNAL_PRINT_ALDB}"
|
||||||
|
dispatcher_send(hass, signal)
|
||||||
|
|
||||||
|
def print_im_aldb(service: ServiceCall) -> None:
|
||||||
|
"""Print the All-Link Database for a device."""
|
||||||
|
# For now this sends logs to the log file.
|
||||||
|
# Future direction is to create an INSTEON control panel.
|
||||||
|
print_aldb_to_log(devices.modem.aldb)
|
||||||
|
|
||||||
|
async def async_srv_x10_all_units_off(service: ServiceCall) -> None:
|
||||||
|
"""Send the X10 All Units Off command."""
|
||||||
|
housecode = service.data.get(SRV_HOUSECODE)
|
||||||
|
await async_x10_all_units_off(housecode)
|
||||||
|
|
||||||
|
async def async_srv_x10_all_lights_off(service: ServiceCall) -> None:
|
||||||
|
"""Send the X10 All Lights Off command."""
|
||||||
|
housecode = service.data.get(SRV_HOUSECODE)
|
||||||
|
await async_x10_all_lights_off(housecode)
|
||||||
|
|
||||||
|
async def async_srv_x10_all_lights_on(service: ServiceCall) -> None:
|
||||||
|
"""Send the X10 All Lights On command."""
|
||||||
|
housecode = service.data.get(SRV_HOUSECODE)
|
||||||
|
await async_x10_all_lights_on(housecode)
|
||||||
|
|
||||||
|
async def async_srv_scene_on(service: ServiceCall) -> None:
|
||||||
|
"""Trigger an INSTEON scene ON."""
|
||||||
|
group = service.data.get(SRV_ALL_LINK_GROUP)
|
||||||
|
await async_trigger_scene_on(group)
|
||||||
|
|
||||||
|
async def async_srv_scene_off(service: ServiceCall) -> None:
|
||||||
|
"""Trigger an INSTEON scene ON."""
|
||||||
|
group = service.data.get(SRV_ALL_LINK_GROUP)
|
||||||
|
await async_trigger_scene_off(group)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_add_default_links(service: ServiceCall) -> None:
|
||||||
|
"""Add the default All-Link entries to a device."""
|
||||||
|
entity_id = service.data[CONF_ENTITY_ID]
|
||||||
|
signal = f"{entity_id}_{SIGNAL_ADD_DEFAULT_LINKS}"
|
||||||
|
async_dispatcher_send(hass, signal)
|
||||||
|
|
||||||
|
async def async_add_device_override(override):
|
||||||
|
"""Remove an Insten device and associated entities."""
|
||||||
|
address = Address(override[CONF_ADDRESS])
|
||||||
|
await async_remove_ha_device(address)
|
||||||
|
devices.set_id(address, override[CONF_CAT], override[CONF_SUBCAT], 0)
|
||||||
|
await async_srv_save_devices()
|
||||||
|
|
||||||
|
async def async_remove_device_override(address):
|
||||||
|
"""Remove an Insten device and associated entities."""
|
||||||
|
address = Address(address)
|
||||||
|
await async_remove_ha_device(address)
|
||||||
|
devices.set_id(address, None, None, None)
|
||||||
|
await devices.async_identify_device(address)
|
||||||
|
await async_srv_save_devices()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_add_x10_device(x10_config):
|
||||||
|
"""Add X10 device."""
|
||||||
|
housecode = x10_config[CONF_HOUSECODE]
|
||||||
|
unitcode = x10_config[CONF_UNITCODE]
|
||||||
|
platform = x10_config[CONF_PLATFORM]
|
||||||
|
steps = x10_config.get(CONF_DIM_STEPS, 22)
|
||||||
|
x10_type = "on_off"
|
||||||
|
if platform == "light":
|
||||||
|
x10_type = "dimmable"
|
||||||
|
elif platform == "binary_sensor":
|
||||||
|
x10_type = "sensor"
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Adding X10 device to Insteon: %s %d %s", housecode, unitcode, x10_type
|
||||||
|
)
|
||||||
|
# This must be run in the event loop
|
||||||
|
devices.add_x10_device(housecode, unitcode, x10_type, steps)
|
||||||
|
|
||||||
|
async def async_remove_x10_device(housecode, unitcode):
|
||||||
|
"""Remove an X10 device and associated entities."""
|
||||||
|
address = create_x10_address(housecode, unitcode)
|
||||||
|
devices.pop(address)
|
||||||
|
await async_remove_ha_device(address)
|
||||||
|
|
||||||
|
async def async_remove_ha_device(address: Address, remove_all_refs: bool = False):
|
||||||
|
"""Remove the device and all entities from hass."""
|
||||||
|
signal = f"{address.id}_{SIGNAL_REMOVE_ENTITY}"
|
||||||
|
async_dispatcher_send(hass, signal)
|
||||||
|
dev_registry = dr.async_get(hass)
|
||||||
|
device = dev_registry.async_get_device(identifiers={(DOMAIN, str(address))})
|
||||||
|
if device:
|
||||||
|
dev_registry.async_remove_device(device.id)
|
||||||
|
|
||||||
|
async def async_remove_insteon_device(
|
||||||
|
address: Address, remove_all_refs: bool = False
|
||||||
|
):
|
||||||
|
"""Remove the underlying Insteon device from the network."""
|
||||||
|
await devices.async_remove_device(
|
||||||
|
address=address, force=False, remove_all_refs=remove_all_refs
|
||||||
|
)
|
||||||
|
await async_srv_save_devices()
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SRV_DEL_ALL_LINK, async_srv_del_all_link, schema=DEL_ALL_LINK_SCHEMA
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SRV_LOAD_ALDB, async_srv_load_aldb, schema=LOAD_ALDB_SCHEMA
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SRV_PRINT_ALDB, print_aldb, schema=PRINT_ALDB_SCHEMA
|
||||||
|
)
|
||||||
|
hass.services.async_register(DOMAIN, SRV_PRINT_IM_ALDB, print_im_aldb, schema=None)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SRV_X10_ALL_UNITS_OFF,
|
||||||
|
async_srv_x10_all_units_off,
|
||||||
|
schema=X10_HOUSECODE_SCHEMA,
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SRV_X10_ALL_LIGHTS_OFF,
|
||||||
|
async_srv_x10_all_lights_off,
|
||||||
|
schema=X10_HOUSECODE_SCHEMA,
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SRV_X10_ALL_LIGHTS_ON,
|
||||||
|
async_srv_x10_all_lights_on,
|
||||||
|
schema=X10_HOUSECODE_SCHEMA,
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SRV_SCENE_ON, async_srv_scene_on, schema=TRIGGER_SCENE_SCHEMA
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SRV_ADD_DEFAULT_LINKS,
|
||||||
|
async_add_default_links,
|
||||||
|
schema=ADD_DEFAULT_LINKS_SCHEMA,
|
||||||
|
)
|
||||||
|
async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices)
|
||||||
|
async_dispatcher_connect(
|
||||||
|
hass, SIGNAL_ADD_DEVICE_OVERRIDE, async_add_device_override
|
||||||
|
)
|
||||||
|
async_dispatcher_connect(
|
||||||
|
hass, SIGNAL_REMOVE_DEVICE_OVERRIDE, async_remove_device_override
|
||||||
|
)
|
||||||
|
async_dispatcher_connect(hass, SIGNAL_ADD_X10_DEVICE, async_add_x10_device)
|
||||||
|
async_dispatcher_connect(hass, SIGNAL_REMOVE_X10_DEVICE, async_remove_x10_device)
|
||||||
|
async_dispatcher_connect(hass, SIGNAL_REMOVE_HA_DEVICE, async_remove_ha_device)
|
||||||
|
async_dispatcher_connect(
|
||||||
|
hass, SIGNAL_REMOVE_INSTEON_DEVICE, async_remove_insteon_device
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Insteon Services registered")
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
@ -12,90 +11,25 @@ from pyinsteon.address import Address
|
|||||||
from pyinsteon.constants import ALDBStatus, DeviceAction
|
from pyinsteon.constants import ALDBStatus, DeviceAction
|
||||||
from pyinsteon.device_types.device_base import Device
|
from pyinsteon.device_types.device_base import Device
|
||||||
from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT, Event
|
from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT, Event
|
||||||
from pyinsteon.managers.link_manager import (
|
|
||||||
async_enter_linking_mode,
|
|
||||||
async_enter_unlinking_mode,
|
|
||||||
)
|
|
||||||
from pyinsteon.managers.scene_manager import (
|
|
||||||
async_trigger_scene_off,
|
|
||||||
async_trigger_scene_on,
|
|
||||||
)
|
|
||||||
from pyinsteon.managers.x10_manager import (
|
|
||||||
async_x10_all_lights_off,
|
|
||||||
async_x10_all_lights_on,
|
|
||||||
async_x10_all_units_off,
|
|
||||||
)
|
|
||||||
from pyinsteon.x10_address import create as create_x10_address
|
|
||||||
from serial.tools import list_ports
|
from serial.tools import list_ports
|
||||||
|
|
||||||
from homeassistant.components import usb
|
from homeassistant.components import usb
|
||||||
from homeassistant.const import (
|
from homeassistant.const import CONF_ADDRESS, Platform
|
||||||
CONF_ADDRESS,
|
from homeassistant.core import HomeAssistant, callback
|
||||||
CONF_ENTITY_ID,
|
|
||||||
CONF_PLATFORM,
|
|
||||||
ENTITY_MATCH_ALL,
|
|
||||||
Platform,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
async_dispatcher_connect,
|
|
||||||
async_dispatcher_send,
|
|
||||||
dispatcher_send,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CAT,
|
|
||||||
CONF_DIM_STEPS,
|
|
||||||
CONF_HOUSECODE,
|
|
||||||
CONF_SUBCAT,
|
|
||||||
CONF_UNITCODE,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_CONF_BUTTON,
|
EVENT_CONF_BUTTON,
|
||||||
EVENT_GROUP_OFF,
|
EVENT_GROUP_OFF,
|
||||||
EVENT_GROUP_OFF_FAST,
|
EVENT_GROUP_OFF_FAST,
|
||||||
EVENT_GROUP_ON,
|
EVENT_GROUP_ON,
|
||||||
EVENT_GROUP_ON_FAST,
|
EVENT_GROUP_ON_FAST,
|
||||||
SIGNAL_ADD_DEFAULT_LINKS,
|
|
||||||
SIGNAL_ADD_DEVICE_OVERRIDE,
|
|
||||||
SIGNAL_ADD_ENTITIES,
|
SIGNAL_ADD_ENTITIES,
|
||||||
SIGNAL_ADD_X10_DEVICE,
|
|
||||||
SIGNAL_LOAD_ALDB,
|
|
||||||
SIGNAL_PRINT_ALDB,
|
|
||||||
SIGNAL_REMOVE_DEVICE_OVERRIDE,
|
|
||||||
SIGNAL_REMOVE_ENTITY,
|
|
||||||
SIGNAL_REMOVE_HA_DEVICE,
|
|
||||||
SIGNAL_REMOVE_INSTEON_DEVICE,
|
|
||||||
SIGNAL_REMOVE_X10_DEVICE,
|
|
||||||
SIGNAL_SAVE_DEVICES,
|
|
||||||
SRV_ADD_ALL_LINK,
|
|
||||||
SRV_ADD_DEFAULT_LINKS,
|
|
||||||
SRV_ALL_LINK_GROUP,
|
|
||||||
SRV_ALL_LINK_MODE,
|
|
||||||
SRV_CONTROLLER,
|
|
||||||
SRV_DEL_ALL_LINK,
|
|
||||||
SRV_HOUSECODE,
|
|
||||||
SRV_LOAD_ALDB,
|
|
||||||
SRV_LOAD_DB_RELOAD,
|
|
||||||
SRV_PRINT_ALDB,
|
|
||||||
SRV_PRINT_IM_ALDB,
|
|
||||||
SRV_SCENE_OFF,
|
|
||||||
SRV_SCENE_ON,
|
|
||||||
SRV_X10_ALL_LIGHTS_OFF,
|
|
||||||
SRV_X10_ALL_LIGHTS_ON,
|
|
||||||
SRV_X10_ALL_UNITS_OFF,
|
|
||||||
)
|
)
|
||||||
from .ipdb import get_device_platform_groups, get_device_platforms
|
from .ipdb import get_device_platform_groups, get_device_platforms
|
||||||
from .schemas import (
|
|
||||||
ADD_ALL_LINK_SCHEMA,
|
|
||||||
ADD_DEFAULT_LINKS_SCHEMA,
|
|
||||||
DEL_ALL_LINK_SCHEMA,
|
|
||||||
LOAD_ALDB_SCHEMA,
|
|
||||||
PRINT_ALDB_SCHEMA,
|
|
||||||
TRIGGER_SCENE_SCHEMA,
|
|
||||||
X10_HOUSECODE_SCHEMA,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .entity import InsteonEntity
|
from .entity import InsteonEntity
|
||||||
@ -154,7 +88,7 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None:
|
|||||||
_register_event(event, async_fire_insteon_event)
|
_register_event(event, async_fire_insteon_event)
|
||||||
|
|
||||||
|
|
||||||
def register_new_device_callback(hass):
|
def register_new_device_callback(hass: HomeAssistant) -> None:
|
||||||
"""Register callback for new Insteon device."""
|
"""Register callback for new Insteon device."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -180,212 +114,6 @@ def register_new_device_callback(hass):
|
|||||||
devices.subscribe(async_new_insteon_device, force_strong_ref=True)
|
devices.subscribe(async_new_insteon_device, force_strong_ref=True)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_register_services(hass): # noqa: C901
|
|
||||||
"""Register services used by insteon component."""
|
|
||||||
|
|
||||||
save_lock = asyncio.Lock()
|
|
||||||
|
|
||||||
async def async_srv_add_all_link(service: ServiceCall) -> None:
|
|
||||||
"""Add an INSTEON All-Link between two devices."""
|
|
||||||
group = service.data[SRV_ALL_LINK_GROUP]
|
|
||||||
mode = service.data[SRV_ALL_LINK_MODE]
|
|
||||||
link_mode = mode.lower() == SRV_CONTROLLER
|
|
||||||
await async_enter_linking_mode(link_mode, group)
|
|
||||||
|
|
||||||
async def async_srv_del_all_link(service: ServiceCall) -> None:
|
|
||||||
"""Delete an INSTEON All-Link between two devices."""
|
|
||||||
group = service.data.get(SRV_ALL_LINK_GROUP)
|
|
||||||
await async_enter_unlinking_mode(group)
|
|
||||||
|
|
||||||
async def async_srv_load_aldb(service: ServiceCall) -> None:
|
|
||||||
"""Load the device All-Link database."""
|
|
||||||
entity_id = service.data[CONF_ENTITY_ID]
|
|
||||||
reload = service.data[SRV_LOAD_DB_RELOAD]
|
|
||||||
if entity_id.lower() == ENTITY_MATCH_ALL:
|
|
||||||
await async_srv_load_aldb_all(reload)
|
|
||||||
else:
|
|
||||||
signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}"
|
|
||||||
async_dispatcher_send(hass, signal, reload)
|
|
||||||
|
|
||||||
async def async_srv_load_aldb_all(reload):
|
|
||||||
"""Load the All-Link database for all devices."""
|
|
||||||
# Cannot be done concurrently due to issues with the underlying protocol.
|
|
||||||
for address in devices:
|
|
||||||
device = devices[address]
|
|
||||||
if device != devices.modem and device.cat != 0x03:
|
|
||||||
await device.aldb.async_load(refresh=reload)
|
|
||||||
await async_srv_save_devices()
|
|
||||||
|
|
||||||
async def async_srv_save_devices():
|
|
||||||
"""Write the Insteon device configuration to file."""
|
|
||||||
async with save_lock:
|
|
||||||
_LOGGER.debug("Saving Insteon devices")
|
|
||||||
await devices.async_save(hass.config.config_dir)
|
|
||||||
|
|
||||||
def print_aldb(service: ServiceCall) -> None:
|
|
||||||
"""Print the All-Link Database for a device."""
|
|
||||||
# For now this sends logs to the log file.
|
|
||||||
# Future direction is to create an INSTEON control panel.
|
|
||||||
entity_id = service.data[CONF_ENTITY_ID]
|
|
||||||
signal = f"{entity_id}_{SIGNAL_PRINT_ALDB}"
|
|
||||||
dispatcher_send(hass, signal)
|
|
||||||
|
|
||||||
def print_im_aldb(service: ServiceCall) -> None:
|
|
||||||
"""Print the All-Link Database for a device."""
|
|
||||||
# For now this sends logs to the log file.
|
|
||||||
# Future direction is to create an INSTEON control panel.
|
|
||||||
print_aldb_to_log(devices.modem.aldb)
|
|
||||||
|
|
||||||
async def async_srv_x10_all_units_off(service: ServiceCall) -> None:
|
|
||||||
"""Send the X10 All Units Off command."""
|
|
||||||
housecode = service.data.get(SRV_HOUSECODE)
|
|
||||||
await async_x10_all_units_off(housecode)
|
|
||||||
|
|
||||||
async def async_srv_x10_all_lights_off(service: ServiceCall) -> None:
|
|
||||||
"""Send the X10 All Lights Off command."""
|
|
||||||
housecode = service.data.get(SRV_HOUSECODE)
|
|
||||||
await async_x10_all_lights_off(housecode)
|
|
||||||
|
|
||||||
async def async_srv_x10_all_lights_on(service: ServiceCall) -> None:
|
|
||||||
"""Send the X10 All Lights On command."""
|
|
||||||
housecode = service.data.get(SRV_HOUSECODE)
|
|
||||||
await async_x10_all_lights_on(housecode)
|
|
||||||
|
|
||||||
async def async_srv_scene_on(service: ServiceCall) -> None:
|
|
||||||
"""Trigger an INSTEON scene ON."""
|
|
||||||
group = service.data.get(SRV_ALL_LINK_GROUP)
|
|
||||||
await async_trigger_scene_on(group)
|
|
||||||
|
|
||||||
async def async_srv_scene_off(service: ServiceCall) -> None:
|
|
||||||
"""Trigger an INSTEON scene ON."""
|
|
||||||
group = service.data.get(SRV_ALL_LINK_GROUP)
|
|
||||||
await async_trigger_scene_off(group)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_add_default_links(service: ServiceCall) -> None:
|
|
||||||
"""Add the default All-Link entries to a device."""
|
|
||||||
entity_id = service.data[CONF_ENTITY_ID]
|
|
||||||
signal = f"{entity_id}_{SIGNAL_ADD_DEFAULT_LINKS}"
|
|
||||||
async_dispatcher_send(hass, signal)
|
|
||||||
|
|
||||||
async def async_add_device_override(override):
|
|
||||||
"""Remove an Insten device and associated entities."""
|
|
||||||
address = Address(override[CONF_ADDRESS])
|
|
||||||
await async_remove_ha_device(address)
|
|
||||||
devices.set_id(address, override[CONF_CAT], override[CONF_SUBCAT], 0)
|
|
||||||
await async_srv_save_devices()
|
|
||||||
|
|
||||||
async def async_remove_device_override(address):
|
|
||||||
"""Remove an Insten device and associated entities."""
|
|
||||||
address = Address(address)
|
|
||||||
await async_remove_ha_device(address)
|
|
||||||
devices.set_id(address, None, None, None)
|
|
||||||
await devices.async_identify_device(address)
|
|
||||||
await async_srv_save_devices()
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_add_x10_device(x10_config):
|
|
||||||
"""Add X10 device."""
|
|
||||||
housecode = x10_config[CONF_HOUSECODE]
|
|
||||||
unitcode = x10_config[CONF_UNITCODE]
|
|
||||||
platform = x10_config[CONF_PLATFORM]
|
|
||||||
steps = x10_config.get(CONF_DIM_STEPS, 22)
|
|
||||||
x10_type = "on_off"
|
|
||||||
if platform == "light":
|
|
||||||
x10_type = "dimmable"
|
|
||||||
elif platform == "binary_sensor":
|
|
||||||
x10_type = "sensor"
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Adding X10 device to Insteon: %s %d %s", housecode, unitcode, x10_type
|
|
||||||
)
|
|
||||||
# This must be run in the event loop
|
|
||||||
devices.add_x10_device(housecode, unitcode, x10_type, steps)
|
|
||||||
|
|
||||||
async def async_remove_x10_device(housecode, unitcode):
|
|
||||||
"""Remove an X10 device and associated entities."""
|
|
||||||
address = create_x10_address(housecode, unitcode)
|
|
||||||
devices.pop(address)
|
|
||||||
await async_remove_ha_device(address)
|
|
||||||
|
|
||||||
async def async_remove_ha_device(address: Address, remove_all_refs: bool = False):
|
|
||||||
"""Remove the device and all entities from hass."""
|
|
||||||
signal = f"{address.id}_{SIGNAL_REMOVE_ENTITY}"
|
|
||||||
async_dispatcher_send(hass, signal)
|
|
||||||
dev_registry = dr.async_get(hass)
|
|
||||||
device = dev_registry.async_get_device(identifiers={(DOMAIN, str(address))})
|
|
||||||
if device:
|
|
||||||
dev_registry.async_remove_device(device.id)
|
|
||||||
|
|
||||||
async def async_remove_insteon_device(
|
|
||||||
address: Address, remove_all_refs: bool = False
|
|
||||||
):
|
|
||||||
"""Remove the underlying Insteon device from the network."""
|
|
||||||
await devices.async_remove_device(
|
|
||||||
address=address, force=False, remove_all_refs=remove_all_refs
|
|
||||||
)
|
|
||||||
await async_srv_save_devices()
|
|
||||||
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SRV_DEL_ALL_LINK, async_srv_del_all_link, schema=DEL_ALL_LINK_SCHEMA
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SRV_LOAD_ALDB, async_srv_load_aldb, schema=LOAD_ALDB_SCHEMA
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SRV_PRINT_ALDB, print_aldb, schema=PRINT_ALDB_SCHEMA
|
|
||||||
)
|
|
||||||
hass.services.async_register(DOMAIN, SRV_PRINT_IM_ALDB, print_im_aldb, schema=None)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN,
|
|
||||||
SRV_X10_ALL_UNITS_OFF,
|
|
||||||
async_srv_x10_all_units_off,
|
|
||||||
schema=X10_HOUSECODE_SCHEMA,
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN,
|
|
||||||
SRV_X10_ALL_LIGHTS_OFF,
|
|
||||||
async_srv_x10_all_lights_off,
|
|
||||||
schema=X10_HOUSECODE_SCHEMA,
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN,
|
|
||||||
SRV_X10_ALL_LIGHTS_ON,
|
|
||||||
async_srv_x10_all_lights_on,
|
|
||||||
schema=X10_HOUSECODE_SCHEMA,
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SRV_SCENE_ON, async_srv_scene_on, schema=TRIGGER_SCENE_SCHEMA
|
|
||||||
)
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA
|
|
||||||
)
|
|
||||||
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN,
|
|
||||||
SRV_ADD_DEFAULT_LINKS,
|
|
||||||
async_add_default_links,
|
|
||||||
schema=ADD_DEFAULT_LINKS_SCHEMA,
|
|
||||||
)
|
|
||||||
async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices)
|
|
||||||
async_dispatcher_connect(
|
|
||||||
hass, SIGNAL_ADD_DEVICE_OVERRIDE, async_add_device_override
|
|
||||||
)
|
|
||||||
async_dispatcher_connect(
|
|
||||||
hass, SIGNAL_REMOVE_DEVICE_OVERRIDE, async_remove_device_override
|
|
||||||
)
|
|
||||||
async_dispatcher_connect(hass, SIGNAL_ADD_X10_DEVICE, async_add_x10_device)
|
|
||||||
async_dispatcher_connect(hass, SIGNAL_REMOVE_X10_DEVICE, async_remove_x10_device)
|
|
||||||
async_dispatcher_connect(hass, SIGNAL_REMOVE_HA_DEVICE, async_remove_ha_device)
|
|
||||||
async_dispatcher_connect(
|
|
||||||
hass, SIGNAL_REMOVE_INSTEON_DEVICE, async_remove_insteon_device
|
|
||||||
)
|
|
||||||
_LOGGER.debug("Insteon Services registered")
|
|
||||||
|
|
||||||
|
|
||||||
def print_aldb_to_log(aldb):
|
def print_aldb_to_log(aldb):
|
||||||
"""Print the All-Link Database to the log file."""
|
"""Print the All-Link Database to the log file."""
|
||||||
logger = logging.getLogger(f"{__name__}.links")
|
logger = logging.getLogger(f"{__name__}.links")
|
||||||
|
@ -1,30 +1,25 @@
|
|||||||
"""The NZBGet integration."""
|
"""The NZBGet integration."""
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import DATA_COORDINATOR, DATA_UNDO_UPDATE_LISTENER, DOMAIN
|
||||||
ATTR_SPEED,
|
|
||||||
DATA_COORDINATOR,
|
|
||||||
DATA_UNDO_UPDATE_LISTENER,
|
|
||||||
DEFAULT_SPEED_LIMIT,
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_PAUSE,
|
|
||||||
SERVICE_RESUME,
|
|
||||||
SERVICE_SET_SPEED,
|
|
||||||
)
|
|
||||||
from .coordinator import NZBGetDataUpdateCoordinator
|
from .coordinator import NZBGetDataUpdateCoordinator
|
||||||
|
from .services import async_register_services
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
|
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
|
||||||
|
|
||||||
|
|
||||||
SPEED_LIMIT_SCHEMA = vol.Schema(
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
{vol.Optional(ATTR_SPEED, default=DEFAULT_SPEED_LIMIT): cv.positive_int}
|
"""Set up NZBGet integration."""
|
||||||
)
|
|
||||||
|
async_register_services(hass)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
@ -44,8 +39,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
_async_register_services(hass, coordinator)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -60,31 +53,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
def _async_register_services(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
coordinator: NZBGetDataUpdateCoordinator,
|
|
||||||
) -> None:
|
|
||||||
"""Register integration-level services."""
|
|
||||||
|
|
||||||
def pause(call: ServiceCall) -> None:
|
|
||||||
"""Service call to pause downloads in NZBGet."""
|
|
||||||
coordinator.nzbget.pausedownload()
|
|
||||||
|
|
||||||
def resume(call: ServiceCall) -> None:
|
|
||||||
"""Service call to resume downloads in NZBGet."""
|
|
||||||
coordinator.nzbget.resumedownload()
|
|
||||||
|
|
||||||
def set_speed(call: ServiceCall) -> None:
|
|
||||||
"""Service call to rate limit speeds in NZBGet."""
|
|
||||||
coordinator.nzbget.rate(call.data[ATTR_SPEED])
|
|
||||||
|
|
||||||
hass.services.async_register(DOMAIN, SERVICE_PAUSE, pause, schema=vol.Schema({}))
|
|
||||||
hass.services.async_register(DOMAIN, SERVICE_RESUME, resume, schema=vol.Schema({}))
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SERVICE_SET_SPEED, set_speed, schema=SPEED_LIMIT_SCHEMA
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Handle options update."""
|
"""Handle options update."""
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
58
homeassistant/components/nzbget/services.py
Normal file
58
homeassistant/components/nzbget/services.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""The NZBGet integration."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
ATTR_SPEED,
|
||||||
|
DATA_COORDINATOR,
|
||||||
|
DEFAULT_SPEED_LIMIT,
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_PAUSE,
|
||||||
|
SERVICE_RESUME,
|
||||||
|
SERVICE_SET_SPEED,
|
||||||
|
)
|
||||||
|
from .coordinator import NZBGetDataUpdateCoordinator
|
||||||
|
|
||||||
|
SPEED_LIMIT_SCHEMA = vol.Schema(
|
||||||
|
{vol.Optional(ATTR_SPEED, default=DEFAULT_SPEED_LIMIT): cv.positive_int}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_coordinator(call: ServiceCall) -> NZBGetDataUpdateCoordinator:
|
||||||
|
"""Service call to pause downloads in NZBGet."""
|
||||||
|
entries = call.hass.config_entries.async_loaded_entries(DOMAIN)
|
||||||
|
if not entries:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="invalid_config_entry",
|
||||||
|
)
|
||||||
|
return call.hass.data[DOMAIN][entries[0].entry_id][DATA_COORDINATOR]
|
||||||
|
|
||||||
|
|
||||||
|
def pause(call: ServiceCall) -> None:
|
||||||
|
"""Service call to pause downloads in NZBGet."""
|
||||||
|
_get_coordinator(call).nzbget.pausedownload()
|
||||||
|
|
||||||
|
|
||||||
|
def resume(call: ServiceCall) -> None:
|
||||||
|
"""Service call to resume downloads in NZBGet."""
|
||||||
|
_get_coordinator(call).nzbget.resumedownload()
|
||||||
|
|
||||||
|
|
||||||
|
def set_speed(call: ServiceCall) -> None:
|
||||||
|
"""Service call to rate limit speeds in NZBGet."""
|
||||||
|
_get_coordinator(call).nzbget.rate(call.data[ATTR_SPEED])
|
||||||
|
|
||||||
|
|
||||||
|
def async_register_services(hass: HomeAssistant) -> None:
|
||||||
|
"""Register integration-level services."""
|
||||||
|
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_PAUSE, pause, schema=vol.Schema({}))
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_RESUME, resume, schema=vol.Schema({}))
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SERVICE_SET_SPEED, set_speed, schema=SPEED_LIMIT_SCHEMA
|
||||||
|
)
|
@ -64,6 +64,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"invalid_config_entry": {
|
||||||
|
"message": "Config entry not found or not loaded!"
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"pause": {
|
"pause": {
|
||||||
"name": "[%key:common::action::pause%]",
|
"name": "[%key:common::action::pause%]",
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/picnic",
|
"documentation": "https://www.home-assistant.io/integrations/picnic",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["python_picnic_api2"],
|
"loggers": ["python_picnic_api2"],
|
||||||
"requirements": ["python-picnic-api2==1.2.4"]
|
"requirements": ["python-picnic-api2==1.3.1"]
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
|
|||||||
key="charge_state_charging_state",
|
key="charge_state_charging_state",
|
||||||
polling=True,
|
polling=True,
|
||||||
streaming_listener=lambda vehicle, callback: vehicle.listen_DetailedChargeState(
|
streaming_listener=lambda vehicle, callback: vehicle.listen_DetailedChargeState(
|
||||||
lambda value: None if value is None else callback(value.lower())
|
lambda value: callback(None if value is None else CHARGE_STATES.get(value))
|
||||||
),
|
),
|
||||||
polling_value_fn=lambda value: CHARGE_STATES.get(str(value)),
|
polling_value_fn=lambda value: CHARGE_STATES.get(str(value)),
|
||||||
options=list(CHARGE_STATES.values()),
|
options=list(CHARGE_STATES.values()),
|
||||||
@ -533,7 +533,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
|
|||||||
TeslemetryVehicleSensorEntityDescription(
|
TeslemetryVehicleSensorEntityDescription(
|
||||||
key="bms_state",
|
key="bms_state",
|
||||||
streaming_listener=lambda vehicle, callback: vehicle.listen_BMSState(
|
streaming_listener=lambda vehicle, callback: vehicle.listen_BMSState(
|
||||||
lambda value: None if value is None else callback(BMS_STATES.get(value))
|
lambda value: callback(None if value is None else BMS_STATES.get(value))
|
||||||
),
|
),
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
options=list(BMS_STATES.values()),
|
options=list(BMS_STATES.values()),
|
||||||
|
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@ -2486,7 +2486,7 @@ python-otbr-api==2.7.0
|
|||||||
python-overseerr==0.7.1
|
python-overseerr==0.7.1
|
||||||
|
|
||||||
# homeassistant.components.picnic
|
# homeassistant.components.picnic
|
||||||
python-picnic-api2==1.2.4
|
python-picnic-api2==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.rabbitair
|
# homeassistant.components.rabbitair
|
||||||
python-rabbitair==0.0.8
|
python-rabbitair==0.0.8
|
||||||
|
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@ -2053,7 +2053,7 @@ python-otbr-api==2.7.0
|
|||||||
python-overseerr==0.7.1
|
python-overseerr==0.7.1
|
||||||
|
|
||||||
# homeassistant.components.picnic
|
# homeassistant.components.picnic
|
||||||
python-picnic-api2==1.2.4
|
python-picnic-api2==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.rabbitair
|
# homeassistant.components.rabbitair
|
||||||
python-rabbitair==0.0.8
|
python-rabbitair==0.0.8
|
||||||
|
@ -43,6 +43,7 @@ CLOUD_DEVICE_DATA: dict[str, Any] = [
|
|||||||
"temperature": 15,
|
"temperature": 15,
|
||||||
"targetTemperature": 20,
|
"targetTemperature": 20,
|
||||||
"heatingEnabled": True,
|
"heatingEnabled": True,
|
||||||
|
"energyWh": 1500,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -70,9 +71,17 @@ def mock_adax_cloud():
|
|||||||
with patch("homeassistant.components.adax.coordinator.Adax") as mock_adax:
|
with patch("homeassistant.components.adax.coordinator.Adax") as mock_adax:
|
||||||
mock_adax_class = mock_adax.return_value
|
mock_adax_class = mock_adax.return_value
|
||||||
|
|
||||||
|
mock_adax_class.fetch_rooms_info = AsyncMock()
|
||||||
|
mock_adax_class.fetch_rooms_info.return_value = CLOUD_DEVICE_DATA
|
||||||
|
|
||||||
mock_adax_class.get_rooms = AsyncMock()
|
mock_adax_class.get_rooms = AsyncMock()
|
||||||
mock_adax_class.get_rooms.return_value = CLOUD_DEVICE_DATA
|
mock_adax_class.get_rooms.return_value = CLOUD_DEVICE_DATA
|
||||||
|
|
||||||
|
mock_adax_class.fetch_energy_info = AsyncMock()
|
||||||
|
mock_adax_class.fetch_energy_info.return_value = [
|
||||||
|
{"deviceId": "1", "energyWh": 1500}
|
||||||
|
]
|
||||||
|
|
||||||
mock_adax_class.update = AsyncMock()
|
mock_adax_class.update = AsyncMock()
|
||||||
mock_adax_class.update.return_value = None
|
mock_adax_class.update.return_value = None
|
||||||
yield mock_adax_class
|
yield mock_adax_class
|
||||||
|
237
tests/components/adax/snapshots/test_sensor.ambr
Normal file
237
tests/components/adax/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_fallback_to_get_rooms[sensor.room_1_energy-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.room_1_energy',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 3,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Energy',
|
||||||
|
'platform': 'adax',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'energy',
|
||||||
|
'unique_id': '1_1_energy',
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_fallback_to_get_rooms[sensor.room_1_energy-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy',
|
||||||
|
'friendly_name': 'Room 1 Energy',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.room_1_energy',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_multiple_devices_create_individual_sensors[sensor.room_1_energy-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.room_1_energy',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 3,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Energy',
|
||||||
|
'platform': 'adax',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'energy',
|
||||||
|
'unique_id': '1_1_energy',
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_multiple_devices_create_individual_sensors[sensor.room_1_energy-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy',
|
||||||
|
'friendly_name': 'Room 1 Energy',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.room_1_energy',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '1.5',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_multiple_devices_create_individual_sensors[sensor.room_2_energy-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.room_2_energy',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 3,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Energy',
|
||||||
|
'platform': 'adax',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'energy',
|
||||||
|
'unique_id': '1_2_energy',
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_multiple_devices_create_individual_sensors[sensor.room_2_energy-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy',
|
||||||
|
'friendly_name': 'Room 2 Energy',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.room_2_energy',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '2.5',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor_cloud[sensor.room_1_energy-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.room_1_energy',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 3,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Energy',
|
||||||
|
'platform': 'adax',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'energy',
|
||||||
|
'unique_id': '1_1_energy',
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor_cloud[sensor.room_1_energy-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy',
|
||||||
|
'friendly_name': 'Room 1 Energy',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.room_1_energy',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '1.5',
|
||||||
|
})
|
||||||
|
# ---
|
@ -20,7 +20,7 @@ async def test_climate_cloud(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test states of the (cloud) Climate entity."""
|
"""Test states of the (cloud) Climate entity."""
|
||||||
await setup_integration(hass, mock_cloud_config_entry)
|
await setup_integration(hass, mock_cloud_config_entry)
|
||||||
mock_adax_cloud.get_rooms.assert_called_once()
|
mock_adax_cloud.fetch_rooms_info.assert_called_once()
|
||||||
|
|
||||||
assert len(hass.states.async_entity_ids(Platform.CLIMATE)) == 1
|
assert len(hass.states.async_entity_ids(Platform.CLIMATE)) == 1
|
||||||
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
||||||
@ -37,7 +37,7 @@ async def test_climate_cloud(
|
|||||||
== CLOUD_DEVICE_DATA[0]["temperature"]
|
== CLOUD_DEVICE_DATA[0]["temperature"]
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_adax_cloud.get_rooms.side_effect = Exception()
|
mock_adax_cloud.fetch_rooms_info.side_effect = Exception()
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
121
tests/components/adax/test_sensor.py
Normal file
121
tests/components/adax/test_sensor.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
"""Test Adax sensor entity."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import setup_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, snapshot_platform
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_cloud(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_adax_cloud: AsyncMock,
|
||||||
|
mock_cloud_config_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test sensor setup for cloud connection."""
|
||||||
|
with patch("homeassistant.components.adax.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
await setup_integration(hass, mock_cloud_config_entry)
|
||||||
|
# Now we use fetch_rooms_info as primary method
|
||||||
|
mock_adax_cloud.fetch_rooms_info.assert_called_once()
|
||||||
|
|
||||||
|
await snapshot_platform(
|
||||||
|
hass, entity_registry, snapshot, mock_cloud_config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_local_not_created(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_adax_local: AsyncMock,
|
||||||
|
mock_local_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test that sensors are not created for local connection."""
|
||||||
|
with patch("homeassistant.components.adax.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
await setup_integration(hass, mock_local_config_entry)
|
||||||
|
|
||||||
|
# No sensor entities should be created for local connection
|
||||||
|
sensor_entities = hass.states.async_entity_ids("sensor")
|
||||||
|
adax_sensors = [e for e in sensor_entities if "adax" in e or "room" in e]
|
||||||
|
assert len(adax_sensors) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_multiple_devices_create_individual_sensors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_adax_cloud: AsyncMock,
|
||||||
|
mock_cloud_config_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test that multiple devices create individual sensors."""
|
||||||
|
# Mock multiple devices for both fetch_rooms_info and get_rooms (fallback)
|
||||||
|
multiple_devices_data = [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"homeId": "1",
|
||||||
|
"name": "Room 1",
|
||||||
|
"temperature": 15,
|
||||||
|
"targetTemperature": 20,
|
||||||
|
"heatingEnabled": True,
|
||||||
|
"energyWh": 1500,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"homeId": "1",
|
||||||
|
"name": "Room 2",
|
||||||
|
"temperature": 18,
|
||||||
|
"targetTemperature": 22,
|
||||||
|
"heatingEnabled": True,
|
||||||
|
"energyWh": 2500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_adax_cloud.fetch_rooms_info.return_value = multiple_devices_data
|
||||||
|
mock_adax_cloud.get_rooms.return_value = multiple_devices_data
|
||||||
|
|
||||||
|
with patch("homeassistant.components.adax.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
await setup_integration(hass, mock_cloud_config_entry)
|
||||||
|
|
||||||
|
await snapshot_platform(
|
||||||
|
hass, entity_registry, snapshot, mock_cloud_config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_fallback_to_get_rooms(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_adax_cloud: AsyncMock,
|
||||||
|
mock_cloud_config_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test fallback to get_rooms when fetch_rooms_info returns empty list."""
|
||||||
|
# Mock fetch_rooms_info to return empty list, get_rooms to return data
|
||||||
|
mock_adax_cloud.fetch_rooms_info.return_value = []
|
||||||
|
mock_adax_cloud.get_rooms.return_value = [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"homeId": "1",
|
||||||
|
"name": "Room 1",
|
||||||
|
"temperature": 15,
|
||||||
|
"targetTemperature": 20,
|
||||||
|
"heatingEnabled": True,
|
||||||
|
"energyWh": 0, # No energy data from get_rooms
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
with patch("homeassistant.components.adax.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
await setup_integration(hass, mock_cloud_config_entry)
|
||||||
|
|
||||||
|
# Should call both methods
|
||||||
|
mock_adax_cloud.fetch_rooms_info.assert_called_once()
|
||||||
|
mock_adax_cloud.get_rooms.assert_called_once()
|
||||||
|
|
||||||
|
await snapshot_platform(
|
||||||
|
hass, entity_registry, snapshot, mock_cloud_config_entry.entry_id
|
||||||
|
)
|
@ -54,7 +54,7 @@ from homeassistant.setup import async_setup_component
|
|||||||
|
|
||||||
from .test_init import MOCK_ENVIRON
|
from .test_init import MOCK_ENVIRON
|
||||||
|
|
||||||
from tests.common import load_json_object_fixture, mock_platform
|
from tests.common import async_load_json_object_fixture, mock_platform
|
||||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||||
|
|
||||||
TEST_BACKUP = supervisor_backups.Backup(
|
TEST_BACKUP = supervisor_backups.Backup(
|
||||||
@ -1018,8 +1018,10 @@ async def test_reader_writer_create_addon_folder_error(
|
|||||||
supervisor_client.jobs.get_job.side_effect = [
|
supervisor_client.jobs.get_job.side_effect = [
|
||||||
TEST_JOB_NOT_DONE,
|
TEST_JOB_NOT_DONE,
|
||||||
supervisor_jobs.Job.from_dict(
|
supervisor_jobs.Job.from_dict(
|
||||||
load_json_object_fixture(
|
(
|
||||||
"backup_done_with_addon_folder_errors.json", DOMAIN
|
await async_load_json_object_fixture(
|
||||||
|
hass, "backup_done_with_addon_folder_errors.json", DOMAIN
|
||||||
|
)
|
||||||
)["data"]
|
)["data"]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -176,8 +176,8 @@ async def test_hmip_dump_hap_config_services(
|
|||||||
assert write_mock.mock_calls
|
assert write_mock.mock_calls
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_services_and_unload_services(hass: HomeAssistant) -> None:
|
async def test_setup_services(hass: HomeAssistant) -> None:
|
||||||
"""Test setup services and unload services."""
|
"""Test setup services."""
|
||||||
mock_config = {HMIPC_AUTHTOKEN: "123", HMIPC_HAPID: "ABC123", HMIPC_NAME: "name"}
|
mock_config = {HMIPC_AUTHTOKEN: "123", HMIPC_HAPID: "ABC123", HMIPC_NAME: "name"}
|
||||||
MockConfigEntry(domain=DOMAIN, data=mock_config).add_to_hass(hass)
|
MockConfigEntry(domain=DOMAIN, data=mock_config).add_to_hass(hass)
|
||||||
|
|
||||||
@ -201,46 +201,3 @@ async def test_setup_services_and_unload_services(hass: HomeAssistant) -> None:
|
|||||||
assert len(config_entries) == 1
|
assert len(config_entries) == 1
|
||||||
|
|
||||||
await hass.config_entries.async_unload(config_entries[0].entry_id)
|
await hass.config_entries.async_unload(config_entries[0].entry_id)
|
||||||
# Check services are removed
|
|
||||||
assert not hass.services.async_services().get(DOMAIN)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_two_haps_unload_one_by_one(hass: HomeAssistant) -> None:
|
|
||||||
"""Test setup two access points and unload one by one and check services."""
|
|
||||||
|
|
||||||
# Setup AP1
|
|
||||||
mock_config = {HMIPC_AUTHTOKEN: "123", HMIPC_HAPID: "ABC123", HMIPC_NAME: "name"}
|
|
||||||
MockConfigEntry(domain=DOMAIN, data=mock_config).add_to_hass(hass)
|
|
||||||
# Setup AP2
|
|
||||||
mock_config2 = {HMIPC_AUTHTOKEN: "123", HMIPC_HAPID: "ABC1234", HMIPC_NAME: "name2"}
|
|
||||||
MockConfigEntry(domain=DOMAIN, data=mock_config2).add_to_hass(hass)
|
|
||||||
|
|
||||||
with patch("homeassistant.components.homematicip_cloud.HomematicipHAP") as mock_hap:
|
|
||||||
instance = mock_hap.return_value
|
|
||||||
instance.async_setup = AsyncMock(return_value=True)
|
|
||||||
instance.home.id = "1"
|
|
||||||
instance.home.modelType = "mock-type"
|
|
||||||
instance.home.name = "mock-name"
|
|
||||||
instance.home.label = "mock-label"
|
|
||||||
instance.home.currentAPVersion = "mock-ap-version"
|
|
||||||
instance.async_reset = AsyncMock(return_value=True)
|
|
||||||
|
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
|
||||||
|
|
||||||
hmipc_services = hass.services.async_services()[DOMAIN]
|
|
||||||
assert len(hmipc_services) == 9
|
|
||||||
|
|
||||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(config_entries) == 2
|
|
||||||
# unload the first AP
|
|
||||||
await hass.config_entries.async_unload(config_entries[0].entry_id)
|
|
||||||
|
|
||||||
# services still exists
|
|
||||||
hmipc_services = hass.services.async_services()[DOMAIN]
|
|
||||||
assert len(hmipc_services) == 9
|
|
||||||
|
|
||||||
# unload the second AP
|
|
||||||
await hass.config_entries.async_unload(config_entries[1].entry_id)
|
|
||||||
|
|
||||||
# Check services are removed
|
|
||||||
assert not hass.services.async_services().get(DOMAIN)
|
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
|||||||
|
|
||||||
from .const import DHCP_DATA, DISCOVERY_DATA, HOMEKIT_DATA, MOCK_SERIAL
|
from .const import DHCP_DATA, DISCOVERY_DATA, HOMEKIT_DATA, MOCK_SERIAL
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
from tests.common import MockConfigEntry, async_load_json_object_fixture
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_hunterdouglas_hub")
|
@pytest.mark.usefixtures("mock_hunterdouglas_hub")
|
||||||
@ -330,7 +330,9 @@ async def test_form_unsupported_device(
|
|||||||
# Simulate a gen 3 secondary hub
|
# Simulate a gen 3 secondary hub
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.hunterdouglas_powerview.util.Hub.request_raw_data",
|
"homeassistant.components.hunterdouglas_powerview.util.Hub.request_raw_data",
|
||||||
return_value=load_json_object_fixture("gen3/gateway/secondary.json", DOMAIN),
|
return_value=await async_load_json_object_fixture(
|
||||||
|
hass, "gen3/gateway/secondary.json", DOMAIN
|
||||||
|
),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -29,7 +29,7 @@ async def test_nexia_sensor_switch(
|
|||||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test NexiaRoomIQSensorSwitch."""
|
"""Test NexiaRoomIQSensorSwitch."""
|
||||||
await async_init_integration(hass, house_fixture="nexia/sensors_xl1050_house.json")
|
await async_init_integration(hass, house_fixture="sensors_xl1050_house.json")
|
||||||
sw1_id = f"{Platform.SWITCH}.center_nativezone_include_center"
|
sw1_id = f"{Platform.SWITCH}.center_nativezone_include_center"
|
||||||
sw1 = {ATTR_ENTITY_ID: sw1_id}
|
sw1 = {ATTR_ENTITY_ID: sw1_id}
|
||||||
sw2_id = f"{Platform.SWITCH}.center_nativezone_include_upstairs"
|
sw2_id = f"{Platform.SWITCH}.center_nativezone_include_upstairs"
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components.nexia.const import DOMAIN
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_fixture
|
from tests.common import MockConfigEntry, async_load_fixture
|
||||||
from tests.test_util.aiohttp import mock_aiohttp_client
|
from tests.test_util.aiohttp import mock_aiohttp_client
|
||||||
|
|
||||||
|
|
||||||
@ -18,13 +18,13 @@ async def async_init_integration(
|
|||||||
skip_setup: bool = False,
|
skip_setup: bool = False,
|
||||||
exception: Exception | None = None,
|
exception: Exception | None = None,
|
||||||
*,
|
*,
|
||||||
house_fixture="nexia/mobile_houses_123456.json",
|
house_fixture="mobile_houses_123456.json",
|
||||||
) -> MockConfigEntry:
|
) -> MockConfigEntry:
|
||||||
"""Set up the nexia integration in Home Assistant."""
|
"""Set up the nexia integration in Home Assistant."""
|
||||||
|
|
||||||
session_fixture = "nexia/session_123456.json"
|
session_fixture = "session_123456.json"
|
||||||
sign_in_fixture = "nexia/sign_in.json"
|
sign_in_fixture = "sign_in.json"
|
||||||
set_fan_speed_fixture = "nexia/set_fan_speed_2293892.json"
|
set_fan_speed_fixture = "set_fan_speed_2293892.json"
|
||||||
with (
|
with (
|
||||||
mock_aiohttp_client() as mock_session,
|
mock_aiohttp_client() as mock_session,
|
||||||
patch("nexia.home.load_or_create_uuid", return_value=uuid.uuid4()),
|
patch("nexia.home.load_or_create_uuid", return_value=uuid.uuid4()),
|
||||||
@ -40,19 +40,20 @@ async def async_init_integration(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
mock_session.post(
|
mock_session.post(
|
||||||
nexia.API_MOBILE_SESSION_URL, text=load_fixture(session_fixture)
|
nexia.API_MOBILE_SESSION_URL,
|
||||||
|
text=await async_load_fixture(hass, session_fixture, DOMAIN),
|
||||||
)
|
)
|
||||||
mock_session.get(
|
mock_session.get(
|
||||||
nexia.API_MOBILE_HOUSES_URL.format(house_id=123456),
|
nexia.API_MOBILE_HOUSES_URL.format(house_id=123456),
|
||||||
text=load_fixture(house_fixture),
|
text=await async_load_fixture(hass, house_fixture, DOMAIN),
|
||||||
)
|
)
|
||||||
mock_session.post(
|
mock_session.post(
|
||||||
nexia.API_MOBILE_ACCOUNTS_SIGN_IN_URL,
|
nexia.API_MOBILE_ACCOUNTS_SIGN_IN_URL,
|
||||||
text=load_fixture(sign_in_fixture),
|
text=await async_load_fixture(hass, sign_in_fixture, DOMAIN),
|
||||||
)
|
)
|
||||||
mock_session.post(
|
mock_session.post(
|
||||||
"https://www.mynexia.com/mobile/xxl_thermostats/2293892/fan_speed",
|
"https://www.mynexia.com/mobile/xxl_thermostats/2293892/fan_speed",
|
||||||
text=load_fixture(set_fan_speed_fixture),
|
text=await async_load_fixture(hass, set_fan_speed_fixture, DOMAIN),
|
||||||
)
|
)
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
@ -9,8 +9,8 @@ from .mock import MOCK_INFO, setup_nuki_integration
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
load_json_array_fixture,
|
async_load_json_array_fixture,
|
||||||
load_json_object_fixture,
|
async_load_json_object_fixture,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -21,15 +21,19 @@ async def init_integration(hass: HomeAssistant) -> MockConfigEntry:
|
|||||||
mock.get("http://1.1.1.1:8080/info", json=MOCK_INFO)
|
mock.get("http://1.1.1.1:8080/info", json=MOCK_INFO)
|
||||||
mock.get(
|
mock.get(
|
||||||
"http://1.1.1.1:8080/list",
|
"http://1.1.1.1:8080/list",
|
||||||
json=load_json_array_fixture("list.json", DOMAIN),
|
json=await async_load_json_array_fixture(hass, "list.json", DOMAIN),
|
||||||
)
|
)
|
||||||
mock.get(
|
mock.get(
|
||||||
"http://1.1.1.1:8080/callback/list",
|
"http://1.1.1.1:8080/callback/list",
|
||||||
json=load_json_object_fixture("callback_list.json", DOMAIN),
|
json=await async_load_json_object_fixture(
|
||||||
|
hass, "callback_list.json", DOMAIN
|
||||||
|
),
|
||||||
)
|
)
|
||||||
mock.get(
|
mock.get(
|
||||||
"http://1.1.1.1:8080/callback/add",
|
"http://1.1.1.1:8080/callback/add",
|
||||||
json=load_json_object_fixture("callback_add.json", DOMAIN),
|
json=await async_load_json_object_fixture(
|
||||||
|
hass, "callback_add.json", DOMAIN
|
||||||
|
),
|
||||||
)
|
)
|
||||||
entry = await setup_nuki_integration(hass)
|
entry = await setup_nuki_integration(hass)
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.overkiz.const import DOMAIN
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
from tests.common import MockConfigEntry, async_load_json_object_fixture
|
||||||
from tests.components.diagnostics import (
|
from tests.components.diagnostics import (
|
||||||
get_diagnostics_for_config_entry,
|
get_diagnostics_for_config_entry,
|
||||||
get_diagnostics_for_device,
|
get_diagnostics_for_device,
|
||||||
@ -23,7 +23,9 @@ async def test_diagnostics(
|
|||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test diagnostics."""
|
"""Test diagnostics."""
|
||||||
diagnostic_data = load_json_object_fixture("overkiz/setup_tahoma_switch.json")
|
diagnostic_data = await async_load_json_object_fixture(
|
||||||
|
hass, "setup_tahoma_switch.json", DOMAIN
|
||||||
|
)
|
||||||
|
|
||||||
with patch.multiple(
|
with patch.multiple(
|
||||||
"pyoverkiz.client.OverkizClient",
|
"pyoverkiz.client.OverkizClient",
|
||||||
@ -44,7 +46,9 @@ async def test_device_diagnostics(
|
|||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test device diagnostics."""
|
"""Test device diagnostics."""
|
||||||
diagnostic_data = load_json_object_fixture("overkiz/setup_tahoma_switch.json")
|
diagnostic_data = await async_load_json_object_fixture(
|
||||||
|
hass, "setup_tahoma_switch.json", DOMAIN
|
||||||
|
)
|
||||||
|
|
||||||
device = device_registry.async_get_device(
|
device = device_registry.async_get_device(
|
||||||
identifiers={(DOMAIN, "rts://****-****-6867/16756006")}
|
identifiers={(DOMAIN, "rts://****-****-6867/16756006")}
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_fixture
|
from tests.common import MockConfigEntry, async_load_fixture
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
USERNAME = "user"
|
USERNAME = "user"
|
||||||
@ -53,39 +53,41 @@ def create_entry(hass: HomeAssistant) -> MockConfigEntry:
|
|||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
async def set_aioclient_responses(aioclient_mock: AiohttpClientMocker) -> None:
|
async def set_aioclient_responses(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
|
) -> None:
|
||||||
"""Set AioClient responses."""
|
"""Set AioClient responses."""
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{BASE_URL}devices/{DEVICE_ID}/info/",
|
f"{BASE_URL}devices/{DEVICE_ID}/info/",
|
||||||
text=load_fixture("skybell/device_info.json"),
|
text=await async_load_fixture(hass, "device_info.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{BASE_URL}devices/{DEVICE_ID}/settings/",
|
f"{BASE_URL}devices/{DEVICE_ID}/settings/",
|
||||||
text=load_fixture("skybell/device_settings.json"),
|
text=await async_load_fixture(hass, "device_settings.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{BASE_URL}devices/{DEVICE_ID}/activities/",
|
f"{BASE_URL}devices/{DEVICE_ID}/activities/",
|
||||||
text=load_fixture("skybell/activities.json"),
|
text=await async_load_fixture(hass, "activities.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{BASE_URL}devices/",
|
f"{BASE_URL}devices/",
|
||||||
text=load_fixture("skybell/device.json"),
|
text=await async_load_fixture(hass, "device.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
USERS_ME_URL,
|
USERS_ME_URL,
|
||||||
text=load_fixture("skybell/me.json"),
|
text=await async_load_fixture(hass, "me.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.post(
|
aioclient_mock.post(
|
||||||
f"{BASE_URL}login/",
|
f"{BASE_URL}login/",
|
||||||
text=load_fixture("skybell/login.json"),
|
text=await async_load_fixture(hass, "login.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{BASE_URL}devices/{DEVICE_ID}/activities/1234567890ab1234567890ac/video/",
|
f"{BASE_URL}devices/{DEVICE_ID}/activities/1234567890ab1234567890ac/video/",
|
||||||
text=load_fixture("skybell/video.json"),
|
text=await async_load_fixture(hass, "video.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{BASE_URL}devices/{DEVICE_ID}/avatar/",
|
f"{BASE_URL}devices/{DEVICE_ID}/avatar/",
|
||||||
text=load_fixture("skybell/avatar.json"),
|
text=await async_load_fixture(hass, "avatar.json", DOMAIN),
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"https://v3-production-devices-avatar.s3.us-west-2.amazonaws.com/{DEVICE_ID}.jpg",
|
f"https://v3-production-devices-avatar.s3.us-west-2.amazonaws.com/{DEVICE_ID}.jpg",
|
||||||
@ -96,12 +98,12 @@ async def set_aioclient_responses(aioclient_mock: AiohttpClientMocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def connection(aioclient_mock: AiohttpClientMocker) -> None:
|
async def connection(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
||||||
"""Fixture for good connection responses."""
|
"""Fixture for good connection responses."""
|
||||||
await set_aioclient_responses(aioclient_mock)
|
await set_aioclient_responses(hass, aioclient_mock)
|
||||||
|
|
||||||
|
|
||||||
def create_skybell(hass: HomeAssistant) -> Skybell:
|
async def create_skybell(hass: HomeAssistant) -> Skybell:
|
||||||
"""Create Skybell object."""
|
"""Create Skybell object."""
|
||||||
skybell = Skybell(
|
skybell = Skybell(
|
||||||
username=USERNAME,
|
username=USERNAME,
|
||||||
@ -109,14 +111,15 @@ def create_skybell(hass: HomeAssistant) -> Skybell:
|
|||||||
get_devices=True,
|
get_devices=True,
|
||||||
session=async_get_clientsession(hass),
|
session=async_get_clientsession(hass),
|
||||||
)
|
)
|
||||||
skybell._cache = orjson.loads(load_fixture("skybell/cache.json"))
|
skybell._cache = orjson.loads(await async_load_fixture(hass, "cache.json", DOMAIN))
|
||||||
return skybell
|
return skybell
|
||||||
|
|
||||||
|
|
||||||
def mock_skybell(hass: HomeAssistant):
|
async def mock_skybell(hass: HomeAssistant):
|
||||||
"""Mock Skybell object."""
|
"""Mock Skybell object."""
|
||||||
return patch(
|
return patch(
|
||||||
"homeassistant.components.skybell.Skybell", return_value=create_skybell(hass)
|
"homeassistant.components.skybell.Skybell",
|
||||||
|
return_value=await create_skybell(hass),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -124,7 +127,7 @@ async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry:
|
|||||||
"""Set up the Skybell integration in Home Assistant."""
|
"""Set up the Skybell integration in Home Assistant."""
|
||||||
config_entry = create_entry(hass)
|
config_entry = create_entry(hass)
|
||||||
|
|
||||||
with mock_skybell(hass), patch("aioskybell.utils.async_save_cache"):
|
with await mock_skybell(hass), patch("aioskybell.utils.async_save_cache"):
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
|
|
||||||
from . import configure_integration
|
from . import configure_integration
|
||||||
|
|
||||||
from tests.common import load_json_object_fixture, snapshot_platform
|
from tests.common import async_load_json_object_fixture, snapshot_platform
|
||||||
|
|
||||||
|
|
||||||
async def test_meter(
|
async def test_meter(
|
||||||
@ -33,7 +33,9 @@ async def test_meter(
|
|||||||
hubDeviceId="test-hub-id",
|
hubDeviceId="test-hub-id",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
mock_get_status.return_value = load_json_object_fixture("meter_status.json", DOMAIN)
|
mock_get_status.return_value = await async_load_json_object_fixture(
|
||||||
|
hass, "meter_status.json", DOMAIN
|
||||||
|
)
|
||||||
|
|
||||||
with patch("homeassistant.components.switchbot_cloud.PLATFORMS", [Platform.SENSOR]):
|
with patch("homeassistant.components.switchbot_cloud.PLATFORMS", [Platform.SENSOR]):
|
||||||
entry = await configure_integration(hass)
|
entry = await configure_integration(hass)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Test fixtures for TP-Link Omada integration."""
|
"""Test fixtures for TP-Link Omada integration."""
|
||||||
|
|
||||||
from collections.abc import AsyncIterable, Generator
|
from collections.abc import AsyncGenerator, Generator
|
||||||
|
from functools import partial
|
||||||
import json
|
import json
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ from homeassistant.components.tplink_omada.const import DOMAIN
|
|||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_fixture
|
from tests.common import MockConfigEntry, async_load_fixture
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -53,29 +54,33 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_omada_site_client() -> Generator[AsyncMock]:
|
async def mock_omada_site_client(hass: HomeAssistant) -> AsyncGenerator[AsyncMock]:
|
||||||
"""Mock Omada site client."""
|
"""Mock Omada site client."""
|
||||||
site_client = MagicMock()
|
site_client = MagicMock()
|
||||||
|
|
||||||
gateway_data = json.loads(load_fixture("gateway-TL-ER7212PC.json", DOMAIN))
|
gateway_data = json.loads(
|
||||||
|
await async_load_fixture(hass, "gateway-TL-ER7212PC.json", DOMAIN)
|
||||||
|
)
|
||||||
gateway = OmadaGateway(gateway_data)
|
gateway = OmadaGateway(gateway_data)
|
||||||
site_client.get_gateway = AsyncMock(return_value=gateway)
|
site_client.get_gateway = AsyncMock(return_value=gateway)
|
||||||
|
|
||||||
switch1_data = json.loads(load_fixture("switch-TL-SG3210XHP-M2.json", DOMAIN))
|
switch1_data = json.loads(
|
||||||
|
await async_load_fixture(hass, "switch-TL-SG3210XHP-M2.json", DOMAIN)
|
||||||
|
)
|
||||||
switch1 = OmadaSwitch(switch1_data)
|
switch1 = OmadaSwitch(switch1_data)
|
||||||
site_client.get_switches = AsyncMock(return_value=[switch1])
|
site_client.get_switches = AsyncMock(return_value=[switch1])
|
||||||
|
|
||||||
devices_data = json.loads(load_fixture("devices.json", DOMAIN))
|
devices_data = json.loads(await async_load_fixture(hass, "devices.json", DOMAIN))
|
||||||
devices = [OmadaListDevice(d) for d in devices_data]
|
devices = [OmadaListDevice(d) for d in devices_data]
|
||||||
site_client.get_devices = AsyncMock(return_value=devices)
|
site_client.get_devices = AsyncMock(return_value=devices)
|
||||||
|
|
||||||
switch1_ports_data = json.loads(
|
switch1_ports_data = json.loads(
|
||||||
load_fixture("switch-ports-TL-SG3210XHP-M2.json", DOMAIN)
|
await async_load_fixture(hass, "switch-ports-TL-SG3210XHP-M2.json", DOMAIN)
|
||||||
)
|
)
|
||||||
switch1_ports = [OmadaSwitchPortDetails(p) for p in switch1_ports_data]
|
switch1_ports = [OmadaSwitchPortDetails(p) for p in switch1_ports_data]
|
||||||
site_client.get_switch_ports = AsyncMock(return_value=switch1_ports)
|
site_client.get_switch_ports = AsyncMock(return_value=switch1_ports)
|
||||||
|
|
||||||
async def async_empty() -> AsyncIterable:
|
async def async_empty() -> AsyncGenerator:
|
||||||
for c in ():
|
for c in ():
|
||||||
yield c
|
yield c
|
||||||
|
|
||||||
@ -85,24 +90,30 @@ def mock_omada_site_client() -> Generator[AsyncMock]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_omada_clients_only_site_client() -> Generator[AsyncMock]:
|
def mock_omada_clients_only_site_client(hass: HomeAssistant) -> Generator[AsyncMock]:
|
||||||
"""Mock Omada site client containing only client connection data."""
|
"""Mock Omada site client containing only client connection data."""
|
||||||
site_client = MagicMock()
|
site_client = MagicMock()
|
||||||
|
|
||||||
site_client.get_switches = AsyncMock(return_value=[])
|
site_client.get_switches = AsyncMock(return_value=[])
|
||||||
site_client.get_devices = AsyncMock(return_value=[])
|
site_client.get_devices = AsyncMock(return_value=[])
|
||||||
site_client.get_switch_ports = AsyncMock(return_value=[])
|
site_client.get_switch_ports = AsyncMock(return_value=[])
|
||||||
site_client.get_client = AsyncMock(side_effect=_get_mock_client)
|
site_client.get_client = AsyncMock(side_effect=partial(_get_mock_client, hass))
|
||||||
|
|
||||||
site_client.get_known_clients.side_effect = _get_mock_known_clients
|
site_client.get_known_clients.side_effect = partial(_get_mock_known_clients, hass)
|
||||||
site_client.get_connected_clients.side_effect = _get_mock_connected_clients
|
site_client.get_connected_clients.side_effect = partial(
|
||||||
|
_get_mock_connected_clients, hass
|
||||||
|
)
|
||||||
|
|
||||||
return site_client
|
return site_client
|
||||||
|
|
||||||
|
|
||||||
async def _get_mock_known_clients() -> AsyncIterable[OmadaNetworkClient]:
|
async def _get_mock_known_clients(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> AsyncGenerator[OmadaNetworkClient]:
|
||||||
"""Mock known clients of the Omada network."""
|
"""Mock known clients of the Omada network."""
|
||||||
known_clients_data = json.loads(load_fixture("known-clients.json", DOMAIN))
|
known_clients_data = json.loads(
|
||||||
|
await async_load_fixture(hass, "known-clients.json", DOMAIN)
|
||||||
|
)
|
||||||
for c in known_clients_data:
|
for c in known_clients_data:
|
||||||
if c["wireless"]:
|
if c["wireless"]:
|
||||||
yield OmadaWirelessClient(c)
|
yield OmadaWirelessClient(c)
|
||||||
@ -110,9 +121,13 @@ async def _get_mock_known_clients() -> AsyncIterable[OmadaNetworkClient]:
|
|||||||
yield OmadaWiredClient(c)
|
yield OmadaWiredClient(c)
|
||||||
|
|
||||||
|
|
||||||
async def _get_mock_connected_clients() -> AsyncIterable[OmadaConnectedClient]:
|
async def _get_mock_connected_clients(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> AsyncGenerator[OmadaConnectedClient]:
|
||||||
"""Mock connected clients of the Omada network."""
|
"""Mock connected clients of the Omada network."""
|
||||||
connected_clients_data = json.loads(load_fixture("connected-clients.json", DOMAIN))
|
connected_clients_data = json.loads(
|
||||||
|
await async_load_fixture(hass, "connected-clients.json", DOMAIN)
|
||||||
|
)
|
||||||
for c in connected_clients_data:
|
for c in connected_clients_data:
|
||||||
if c["wireless"]:
|
if c["wireless"]:
|
||||||
yield OmadaWirelessClient(c)
|
yield OmadaWirelessClient(c)
|
||||||
@ -120,9 +135,11 @@ async def _get_mock_connected_clients() -> AsyncIterable[OmadaConnectedClient]:
|
|||||||
yield OmadaWiredClient(c)
|
yield OmadaWiredClient(c)
|
||||||
|
|
||||||
|
|
||||||
def _get_mock_client(mac: str) -> OmadaNetworkClient:
|
async def _get_mock_client(hass: HomeAssistant, mac: str) -> OmadaNetworkClient:
|
||||||
"""Mock an Omada client."""
|
"""Mock an Omada client."""
|
||||||
connected_clients_data = json.loads(load_fixture("connected-clients.json", DOMAIN))
|
connected_clients_data = json.loads(
|
||||||
|
await async_load_fixture(hass, "connected-clients.json", DOMAIN)
|
||||||
|
)
|
||||||
|
|
||||||
for c in connected_clients_data:
|
for c in connected_clients_data:
|
||||||
if c["mac"] == mac:
|
if c["mac"] == mac:
|
||||||
|
@ -8,8 +8,8 @@ from homeassistant.core import HomeAssistant
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
load_json_array_fixture,
|
async_load_json_array_fixture,
|
||||||
load_json_object_fixture,
|
async_load_json_object_fixture,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -18,16 +18,22 @@ async def init_component(hass: HomeAssistant) -> MockConfigEntry:
|
|||||||
with requests_mock.Mocker() as mock:
|
with requests_mock.Mocker() as mock:
|
||||||
mock.get(
|
mock.get(
|
||||||
"http://1.1.1.1/d",
|
"http://1.1.1.1/d",
|
||||||
json=load_json_object_fixture("device.json", youless.DOMAIN),
|
json=await async_load_json_object_fixture(
|
||||||
|
hass, "device.json", youless.DOMAIN
|
||||||
|
),
|
||||||
)
|
)
|
||||||
mock.get(
|
mock.get(
|
||||||
"http://1.1.1.1/e",
|
"http://1.1.1.1/e",
|
||||||
json=load_json_array_fixture("enologic.json", youless.DOMAIN),
|
json=await async_load_json_array_fixture(
|
||||||
|
hass, "enologic.json", youless.DOMAIN
|
||||||
|
),
|
||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
)
|
)
|
||||||
mock.get(
|
mock.get(
|
||||||
"http://1.1.1.1/f",
|
"http://1.1.1.1/f",
|
||||||
json=load_json_object_fixture("phase.json", youless.DOMAIN),
|
json=await async_load_json_object_fixture(
|
||||||
|
hass, "phase.json", youless.DOMAIN
|
||||||
|
),
|
||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user