mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
2024.6.2 (#119376)
This commit is contained in:
commit
090d296135
@ -163,7 +163,6 @@ homeassistant.components.easyenergy.*
|
|||||||
homeassistant.components.ecovacs.*
|
homeassistant.components.ecovacs.*
|
||||||
homeassistant.components.ecowitt.*
|
homeassistant.components.ecowitt.*
|
||||||
homeassistant.components.efergy.*
|
homeassistant.components.efergy.*
|
||||||
homeassistant.components.electrasmart.*
|
|
||||||
homeassistant.components.electric_kiwi.*
|
homeassistant.components.electric_kiwi.*
|
||||||
homeassistant.components.elgato.*
|
homeassistant.components.elgato.*
|
||||||
homeassistant.components.elkm1.*
|
homeassistant.components.elkm1.*
|
||||||
|
@ -1486,8 +1486,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/unifi/ @Kane610
|
/tests/components/unifi/ @Kane610
|
||||||
/homeassistant/components/unifi_direct/ @tofuSCHNITZEL
|
/homeassistant/components/unifi_direct/ @tofuSCHNITZEL
|
||||||
/homeassistant/components/unifiled/ @florisvdk
|
/homeassistant/components/unifiled/ @florisvdk
|
||||||
/homeassistant/components/unifiprotect/ @bdraco
|
|
||||||
/tests/components/unifiprotect/ @bdraco
|
|
||||||
/homeassistant/components/upb/ @gwww
|
/homeassistant/components/upb/ @gwww
|
||||||
/tests/components/upb/ @gwww
|
/tests/components/upb/ @gwww
|
||||||
/homeassistant/components/upc_connect/ @pvizeli @fabaff
|
/homeassistant/components/upc_connect/ @pvizeli @fabaff
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
DOMAIN = "aladdin_connect"
|
DOMAIN = "aladdin_connect"
|
||||||
|
|
||||||
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.com/login.html"
|
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.net/login.html"
|
||||||
OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token"
|
OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token"
|
||||||
|
@ -62,13 +62,12 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool:
|
|||||||
|
|
||||||
Adds an empty filter to hass data.
|
Adds an empty filter to hass data.
|
||||||
Tries to get a filter from yaml, if present set to hass data.
|
Tries to get a filter from yaml, if present set to hass data.
|
||||||
If config is empty after getting the filter, return, otherwise emit
|
|
||||||
deprecated warning and pass the rest to the config flow.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {DATA_FILTER: {}})
|
hass.data.setdefault(DOMAIN, {DATA_FILTER: FILTER_SCHEMA({})})
|
||||||
if DOMAIN in yaml_config:
|
if DOMAIN in yaml_config:
|
||||||
hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN][CONF_FILTER]
|
hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN].pop(CONF_FILTER)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -207,6 +206,6 @@ class AzureDataExplorer:
|
|||||||
if "\n" in state.state:
|
if "\n" in state.state:
|
||||||
return None, dropped + 1
|
return None, dropped + 1
|
||||||
|
|
||||||
json_event = str(json.dumps(obj=state, cls=JSONEncoder).encode("utf-8"))
|
json_event = json.dumps(obj=state, cls=JSONEncoder)
|
||||||
|
|
||||||
return (json_event, dropped)
|
return (json_event, dropped)
|
||||||
|
@ -23,7 +23,7 @@ from .const import (
|
|||||||
CONF_APP_REG_ID,
|
CONF_APP_REG_ID,
|
||||||
CONF_APP_REG_SECRET,
|
CONF_APP_REG_SECRET,
|
||||||
CONF_AUTHORITY_ID,
|
CONF_AUTHORITY_ID,
|
||||||
CONF_USE_FREE,
|
CONF_USE_QUEUED_CLIENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -35,7 +35,6 @@ class AzureDataExplorerClient:
|
|||||||
def __init__(self, data: Mapping[str, Any]) -> None:
|
def __init__(self, data: Mapping[str, Any]) -> None:
|
||||||
"""Create the right class."""
|
"""Create the right class."""
|
||||||
|
|
||||||
self._cluster_ingest_uri = data[CONF_ADX_CLUSTER_INGEST_URI]
|
|
||||||
self._database = data[CONF_ADX_DATABASE_NAME]
|
self._database = data[CONF_ADX_DATABASE_NAME]
|
||||||
self._table = data[CONF_ADX_TABLE_NAME]
|
self._table = data[CONF_ADX_TABLE_NAME]
|
||||||
self._ingestion_properties = IngestionProperties(
|
self._ingestion_properties = IngestionProperties(
|
||||||
@ -45,24 +44,36 @@ class AzureDataExplorerClient:
|
|||||||
ingestion_mapping_reference="ha_json_mapping",
|
ingestion_mapping_reference="ha_json_mapping",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create cLient for ingesting and querying data
|
# Create client for ingesting data
|
||||||
kcsb = KustoConnectionStringBuilder.with_aad_application_key_authentication(
|
kcsb_ingest = (
|
||||||
self._cluster_ingest_uri,
|
KustoConnectionStringBuilder.with_aad_application_key_authentication(
|
||||||
|
data[CONF_ADX_CLUSTER_INGEST_URI],
|
||||||
data[CONF_APP_REG_ID],
|
data[CONF_APP_REG_ID],
|
||||||
data[CONF_APP_REG_SECRET],
|
data[CONF_APP_REG_SECRET],
|
||||||
data[CONF_AUTHORITY_ID],
|
data[CONF_AUTHORITY_ID],
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if data[CONF_USE_FREE] is True:
|
# Create client for querying data
|
||||||
|
kcsb_query = (
|
||||||
|
KustoConnectionStringBuilder.with_aad_application_key_authentication(
|
||||||
|
data[CONF_ADX_CLUSTER_INGEST_URI].replace("ingest-", ""),
|
||||||
|
data[CONF_APP_REG_ID],
|
||||||
|
data[CONF_APP_REG_SECRET],
|
||||||
|
data[CONF_AUTHORITY_ID],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if data[CONF_USE_QUEUED_CLIENT] is True:
|
||||||
# Queded is the only option supported on free tear of ADX
|
# Queded is the only option supported on free tear of ADX
|
||||||
self.write_client = QueuedIngestClient(kcsb)
|
self.write_client = QueuedIngestClient(kcsb_ingest)
|
||||||
else:
|
else:
|
||||||
self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb)
|
self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb_ingest)
|
||||||
|
|
||||||
self.query_client = KustoClient(kcsb)
|
self.query_client = KustoClient(kcsb_query)
|
||||||
|
|
||||||
def test_connection(self) -> None:
|
def test_connection(self) -> None:
|
||||||
"""Test connection, will throw Exception when it cannot connect."""
|
"""Test connection, will throw Exception if it cannot connect."""
|
||||||
|
|
||||||
query = f"{self._table} | take 1"
|
query = f"{self._table} | take 1"
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.config_entries import ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlowResult
|
||||||
|
from homeassistant.helpers.selector import BooleanSelector
|
||||||
|
|
||||||
from . import AzureDataExplorerClient
|
from . import AzureDataExplorerClient
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -19,7 +20,7 @@ from .const import (
|
|||||||
CONF_APP_REG_ID,
|
CONF_APP_REG_ID,
|
||||||
CONF_APP_REG_SECRET,
|
CONF_APP_REG_SECRET,
|
||||||
CONF_AUTHORITY_ID,
|
CONF_AUTHORITY_ID,
|
||||||
CONF_USE_FREE,
|
CONF_USE_QUEUED_CLIENT,
|
||||||
DEFAULT_OPTIONS,
|
DEFAULT_OPTIONS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
@ -34,7 +35,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
|||||||
vol.Required(CONF_APP_REG_ID): str,
|
vol.Required(CONF_APP_REG_ID): str,
|
||||||
vol.Required(CONF_APP_REG_SECRET): str,
|
vol.Required(CONF_APP_REG_SECRET): str,
|
||||||
vol.Required(CONF_AUTHORITY_ID): str,
|
vol.Required(CONF_AUTHORITY_ID): str,
|
||||||
vol.Optional(CONF_USE_FREE, default=False): bool,
|
vol.Required(CONF_USE_QUEUED_CLIENT, default=False): BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ CONF_AUTHORITY_ID = "authority_id"
|
|||||||
CONF_SEND_INTERVAL = "send_interval"
|
CONF_SEND_INTERVAL = "send_interval"
|
||||||
CONF_MAX_DELAY = "max_delay"
|
CONF_MAX_DELAY = "max_delay"
|
||||||
CONF_FILTER = DATA_FILTER = "filter"
|
CONF_FILTER = DATA_FILTER = "filter"
|
||||||
CONF_USE_FREE = "use_queued_ingestion"
|
CONF_USE_QUEUED_CLIENT = "use_queued_ingestion"
|
||||||
DATA_HUB = "hub"
|
DATA_HUB = "hub"
|
||||||
STEP_USER = "user"
|
STEP_USER = "user"
|
||||||
|
|
||||||
|
@ -3,15 +3,19 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Setup your Azure Data Explorer integration",
|
"title": "Setup your Azure Data Explorer integration",
|
||||||
"description": "Enter connection details.",
|
"description": "Enter connection details",
|
||||||
"data": {
|
"data": {
|
||||||
"cluster_ingest_uri": "Cluster ingest URI",
|
"cluster_ingest_uri": "Cluster Ingest URI",
|
||||||
"database": "Database name",
|
"authority_id": "Authority ID",
|
||||||
"table": "Table name",
|
|
||||||
"client_id": "Client ID",
|
"client_id": "Client ID",
|
||||||
"client_secret": "Client secret",
|
"client_secret": "Client secret",
|
||||||
"authority_id": "Authority ID",
|
"database": "Database name",
|
||||||
|
"table": "Table name",
|
||||||
"use_queued_ingestion": "Use queued ingestion"
|
"use_queued_ingestion": "Use queued ingestion"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"cluster_ingest_uri": "Ingest-URI of the cluster",
|
||||||
|
"use_queued_ingestion": "Must be enabled when using ADX free cluster"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -120,7 +120,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
director_all_items = json.loads(director_all_items)
|
director_all_items = json.loads(director_all_items)
|
||||||
entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items
|
entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items
|
||||||
|
|
||||||
entry_data[CONF_UI_CONFIGURATION] = json.loads(await director.getUiConfiguration())
|
# Check if OS version is 3 or higher to get UI configuration
|
||||||
|
entry_data[CONF_UI_CONFIGURATION] = None
|
||||||
|
if int(entry_data[CONF_DIRECTOR_SW_VERSION].split(".")[0]) >= 3:
|
||||||
|
entry_data[CONF_UI_CONFIGURATION] = json.loads(
|
||||||
|
await director.getUiConfiguration()
|
||||||
|
)
|
||||||
|
|
||||||
# Load options from config entry
|
# Load options from config entry
|
||||||
entry_data[CONF_SCAN_INTERVAL] = entry.options.get(
|
entry_data[CONF_SCAN_INTERVAL] = entry.options.get(
|
||||||
|
@ -81,11 +81,18 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Control4 rooms from a config entry."""
|
"""Set up Control4 rooms from a config entry."""
|
||||||
|
entry_data = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
ui_config = entry_data[CONF_UI_CONFIGURATION]
|
||||||
|
|
||||||
|
# OS 2 will not have a ui_configuration
|
||||||
|
if not ui_config:
|
||||||
|
_LOGGER.debug("No UI Configuration found for Control4")
|
||||||
|
return
|
||||||
|
|
||||||
all_rooms = await get_rooms(hass, entry)
|
all_rooms = await get_rooms(hass, entry)
|
||||||
if not all_rooms:
|
if not all_rooms:
|
||||||
return
|
return
|
||||||
|
|
||||||
entry_data = hass.data[DOMAIN][entry.entry_id]
|
|
||||||
scan_interval = entry_data[CONF_SCAN_INTERVAL]
|
scan_interval = entry_data[CONF_SCAN_INTERVAL]
|
||||||
_LOGGER.debug("Scan interval = %s", scan_interval)
|
_LOGGER.debug("Scan interval = %s", scan_interval)
|
||||||
|
|
||||||
@ -119,8 +126,6 @@ async def async_setup_entry(
|
|||||||
if "parentId" in item and k > 1
|
if "parentId" in item and k > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_config = entry_data[CONF_UI_CONFIGURATION]
|
|
||||||
|
|
||||||
entity_list = []
|
entity_list = []
|
||||||
for room in all_rooms:
|
for room in all_rooms:
|
||||||
room_id = room["id"]
|
room_id = room["id"]
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/electrasmart",
|
"documentation": "https://www.home-assistant.io/integrations/electrasmart",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["pyElectra==1.2.0"]
|
"requirements": ["pyElectra==1.2.1"]
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,15 @@ class ElgatoLight(ElgatoEntity, LightEntity):
|
|||||||
self._attr_unique_id = coordinator.data.info.serial_number
|
self._attr_unique_id = coordinator.data.info.serial_number
|
||||||
|
|
||||||
# Elgato Light supporting color, have a different temperature range
|
# Elgato Light supporting color, have a different temperature range
|
||||||
if self.coordinator.data.settings.power_on_hue is not None:
|
if (
|
||||||
|
self.coordinator.data.info.product_name
|
||||||
|
in (
|
||||||
|
"Elgato Light Strip",
|
||||||
|
"Elgato Light Strip Pro",
|
||||||
|
)
|
||||||
|
or self.coordinator.data.settings.power_on_hue
|
||||||
|
or self.coordinator.data.state.hue is not None
|
||||||
|
):
|
||||||
self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS}
|
self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS}
|
||||||
self._attr_min_mireds = 153
|
self._attr_min_mireds = 153
|
||||||
self._attr_max_mireds = 285
|
self._attr_max_mireds = 285
|
||||||
|
@ -141,10 +141,10 @@ class Enigma2Device(MediaPlayerEntity):
|
|||||||
self._device: OpenWebIfDevice = device
|
self._device: OpenWebIfDevice = device
|
||||||
self._entry = entry
|
self._entry = entry
|
||||||
|
|
||||||
self._attr_unique_id = device.mac_address
|
self._attr_unique_id = device.mac_address or entry.entry_id
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, device.mac_address)},
|
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||||
manufacturer=about["info"]["brand"],
|
manufacturer=about["info"]["brand"],
|
||||||
model=about["info"]["model"],
|
model=about["info"]["model"],
|
||||||
configuration_url=device.base,
|
configuration_url=device.base,
|
||||||
|
@ -116,8 +116,9 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
|
|||||||
):
|
):
|
||||||
"""Initialize the alarm panel."""
|
"""Initialize the alarm panel."""
|
||||||
self._partition_number = partition_number
|
self._partition_number = partition_number
|
||||||
self._code = code
|
|
||||||
self._panic_type = panic_type
|
self._panic_type = panic_type
|
||||||
|
self._alarm_control_panel_option_default_code = code
|
||||||
|
self._attr_code_format = CodeFormat.NUMBER
|
||||||
|
|
||||||
_LOGGER.debug("Setting up alarm: %s", alarm_name)
|
_LOGGER.debug("Setting up alarm: %s", alarm_name)
|
||||||
super().__init__(alarm_name, info, controller)
|
super().__init__(alarm_name, info, controller)
|
||||||
@ -141,13 +142,6 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
|
|||||||
if partition is None or int(partition) == self._partition_number:
|
if partition is None or int(partition) == self._partition_number:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def code_format(self) -> CodeFormat | None:
|
|
||||||
"""Regex for code format or None if no code is required."""
|
|
||||||
if self._code:
|
|
||||||
return None
|
|
||||||
return CodeFormat.NUMBER
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> str:
|
def state(self) -> str:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
@ -169,34 +163,15 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
|
|||||||
|
|
||||||
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
if code:
|
self.hass.data[DATA_EVL].disarm_partition(code, self._partition_number)
|
||||||
self.hass.data[DATA_EVL].disarm_partition(str(code), self._partition_number)
|
|
||||||
else:
|
|
||||||
self.hass.data[DATA_EVL].disarm_partition(
|
|
||||||
str(self._code), self._partition_number
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_alarm_arm_home(self, code: str | None = None) -> None:
|
async def async_alarm_arm_home(self, code: str | None = None) -> None:
|
||||||
"""Send arm home command."""
|
"""Send arm home command."""
|
||||||
if code:
|
self.hass.data[DATA_EVL].arm_stay_partition(code, self._partition_number)
|
||||||
self.hass.data[DATA_EVL].arm_stay_partition(
|
|
||||||
str(code), self._partition_number
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.hass.data[DATA_EVL].arm_stay_partition(
|
|
||||||
str(self._code), self._partition_number
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_alarm_arm_away(self, code: str | None = None) -> None:
|
async def async_alarm_arm_away(self, code: str | None = None) -> None:
|
||||||
"""Send arm away command."""
|
"""Send arm away command."""
|
||||||
if code:
|
self.hass.data[DATA_EVL].arm_away_partition(code, self._partition_number)
|
||||||
self.hass.data[DATA_EVL].arm_away_partition(
|
|
||||||
str(code), self._partition_number
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.hass.data[DATA_EVL].arm_away_partition(
|
|
||||||
str(self._code), self._partition_number
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_alarm_trigger(self, code: str | None = None) -> None:
|
async def async_alarm_trigger(self, code: str | None = None) -> None:
|
||||||
"""Alarm trigger command. Will be used to trigger a panic alarm."""
|
"""Alarm trigger command. Will be used to trigger a panic alarm."""
|
||||||
@ -204,9 +179,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
|
|||||||
|
|
||||||
async def async_alarm_arm_night(self, code: str | None = None) -> None:
|
async def async_alarm_arm_night(self, code: str | None = None) -> None:
|
||||||
"""Send arm night command."""
|
"""Send arm night command."""
|
||||||
self.hass.data[DATA_EVL].arm_night_partition(
|
self.hass.data[DATA_EVL].arm_night_partition(code, self._partition_number)
|
||||||
str(code) if code else str(self._code), self._partition_number
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_alarm_keypress(self, keypress=None):
|
def async_alarm_keypress(self, keypress=None):
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20240605.0"]
|
"requirements": ["home-assistant-frontend==20240610.0"]
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth",
|
"documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["gardena-bluetooth==1.4.1"]
|
"requirements": ["gardena-bluetooth==1.4.2"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/glances",
|
"documentation": "https://www.home-assistant.io/integrations/glances",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["glances_api"],
|
"loggers": ["glances_api"],
|
||||||
"requirements": ["glances-api==0.7.0"]
|
"requirements": ["glances-api==0.8.0"]
|
||||||
}
|
}
|
||||||
|
@ -1586,6 +1586,17 @@ class ArmDisArmTrait(_Trait):
|
|||||||
if features & required_feature != 0
|
if features & required_feature != 0
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def _default_arm_state(self):
|
||||||
|
states = self._supported_states()
|
||||||
|
|
||||||
|
if STATE_ALARM_TRIGGERED in states:
|
||||||
|
states.remove(STATE_ALARM_TRIGGERED)
|
||||||
|
|
||||||
|
if len(states) != 1:
|
||||||
|
raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing")
|
||||||
|
|
||||||
|
return states[0]
|
||||||
|
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
"""Return ArmDisarm attributes for a sync request."""
|
"""Return ArmDisarm attributes for a sync request."""
|
||||||
response = {}
|
response = {}
|
||||||
@ -1609,10 +1620,13 @@ class ArmDisArmTrait(_Trait):
|
|||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
"""Return ArmDisarm query attributes."""
|
"""Return ArmDisarm query attributes."""
|
||||||
armed_state = self.state.attributes.get("next_state", self.state.state)
|
armed_state = self.state.attributes.get("next_state", self.state.state)
|
||||||
response = {"isArmed": armed_state in self.state_to_service}
|
|
||||||
if response["isArmed"]:
|
if armed_state in self.state_to_service:
|
||||||
response.update({"currentArmLevel": armed_state})
|
return {"isArmed": True, "currentArmLevel": armed_state}
|
||||||
return response
|
return {
|
||||||
|
"isArmed": False,
|
||||||
|
"currentArmLevel": self._default_arm_state(),
|
||||||
|
}
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute an ArmDisarm command."""
|
"""Execute an ArmDisarm command."""
|
||||||
@ -1620,15 +1634,7 @@ class ArmDisArmTrait(_Trait):
|
|||||||
# If no arm level given, we can only arm it if there is
|
# If no arm level given, we can only arm it if there is
|
||||||
# only one supported arm type. We never default to triggered.
|
# only one supported arm type. We never default to triggered.
|
||||||
if not (arm_level := params.get("armLevel")):
|
if not (arm_level := params.get("armLevel")):
|
||||||
states = self._supported_states()
|
arm_level = self._default_arm_state()
|
||||||
|
|
||||||
if STATE_ALARM_TRIGGERED in states:
|
|
||||||
states.remove(STATE_ALARM_TRIGGERED)
|
|
||||||
|
|
||||||
if len(states) != 1:
|
|
||||||
raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing")
|
|
||||||
|
|
||||||
arm_level = states[0]
|
|
||||||
|
|
||||||
if self.state.state == arm_level:
|
if self.state.state == arm_level:
|
||||||
raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed")
|
raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed")
|
||||||
|
@ -71,7 +71,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
try:
|
try:
|
||||||
response = await model.generate_content_async(prompt_parts)
|
response = await model.generate_content_async(prompt_parts)
|
||||||
except (
|
except (
|
||||||
ClientError,
|
GoogleAPICallError,
|
||||||
ValueError,
|
ValueError,
|
||||||
genai_types.BlockedPromptException,
|
genai_types.BlockedPromptException,
|
||||||
genai_types.StopCandidateException,
|
genai_types.StopCandidateException,
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import codecs
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
import google.ai.generativelanguage as glm
|
|
||||||
from google.api_core.exceptions import GoogleAPICallError
|
from google.api_core.exceptions import GoogleAPICallError
|
||||||
import google.generativeai as genai
|
import google.generativeai as genai
|
||||||
|
from google.generativeai import protos
|
||||||
import google.generativeai.types as genai_types
|
import google.generativeai.types as genai_types
|
||||||
from google.protobuf.json_format import MessageToDict
|
from google.protobuf.json_format import MessageToDict
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -93,7 +94,7 @@ def _format_tool(tool: llm.Tool) -> dict[str, Any]:
|
|||||||
|
|
||||||
parameters = _format_schema(convert(tool.parameters))
|
parameters = _format_schema(convert(tool.parameters))
|
||||||
|
|
||||||
return glm.Tool(
|
return protos.Tool(
|
||||||
{
|
{
|
||||||
"function_declarations": [
|
"function_declarations": [
|
||||||
{
|
{
|
||||||
@ -106,14 +107,14 @@ def _format_tool(tool: llm.Tool) -> dict[str, Any]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _adjust_value(value: Any) -> Any:
|
def _escape_decode(value: Any) -> Any:
|
||||||
"""Reverse unnecessary single quotes escaping."""
|
"""Recursively call codecs.escape_decode on all values."""
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return value.replace("\\'", "'")
|
return codecs.escape_decode(bytes(value, "utf-8"))[0].decode("utf-8") # type: ignore[attr-defined]
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
return [_adjust_value(item) for item in value]
|
return [_escape_decode(item) for item in value]
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return {k: _adjust_value(v) for k, v in value.items()}
|
return {k: _escape_decode(v) for k, v in value.items()}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@ -334,10 +335,7 @@ class GoogleGenerativeAIConversationEntity(
|
|||||||
for function_call in function_calls:
|
for function_call in function_calls:
|
||||||
tool_call = MessageToDict(function_call._pb) # noqa: SLF001
|
tool_call = MessageToDict(function_call._pb) # noqa: SLF001
|
||||||
tool_name = tool_call["name"]
|
tool_name = tool_call["name"]
|
||||||
tool_args = {
|
tool_args = _escape_decode(tool_call["args"])
|
||||||
key: _adjust_value(value)
|
|
||||||
for key, value in tool_call["args"].items()
|
|
||||||
}
|
|
||||||
LOGGER.debug("Tool call: %s(%s)", tool_name, tool_args)
|
LOGGER.debug("Tool call: %s(%s)", tool_name, tool_args)
|
||||||
tool_input = llm.ToolInput(tool_name=tool_name, tool_args=tool_args)
|
tool_input = llm.ToolInput(tool_name=tool_name, tool_args=tool_args)
|
||||||
try:
|
try:
|
||||||
@ -349,13 +347,13 @@ class GoogleGenerativeAIConversationEntity(
|
|||||||
|
|
||||||
LOGGER.debug("Tool response: %s", function_response)
|
LOGGER.debug("Tool response: %s", function_response)
|
||||||
tool_responses.append(
|
tool_responses.append(
|
||||||
glm.Part(
|
protos.Part(
|
||||||
function_response=glm.FunctionResponse(
|
function_response=protos.FunctionResponse(
|
||||||
name=tool_name, response=function_response
|
name=tool_name, response=function_response
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
chat_request = glm.Content(parts=tool_responses)
|
chat_request = protos.Content(parts=tool_responses)
|
||||||
|
|
||||||
intent_response.async_set_speech(
|
intent_response.async_set_speech(
|
||||||
" ".join([part.text.strip() for part in chat_response.parts if part.text])
|
" ".join([part.text.strip() for part in chat_response.parts if part.text])
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["google-generativeai==0.5.4", "voluptuous-openapi==0.0.4"]
|
"requirements": ["google-generativeai==0.6.0", "voluptuous-openapi==0.0.4"]
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,14 @@ from homeassistant.const import (
|
|||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, State, callback
|
from homeassistant.core import (
|
||||||
|
CALLBACK_TYPE,
|
||||||
|
Event,
|
||||||
|
EventStateChangedData,
|
||||||
|
HomeAssistant,
|
||||||
|
State,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
from homeassistant.helpers.entity import (
|
from homeassistant.helpers.entity import (
|
||||||
@ -45,6 +52,7 @@ from homeassistant.helpers.entity import (
|
|||||||
get_unit_of_measurement,
|
get_unit_of_measurement,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.event import async_track_state_change_event
|
||||||
from homeassistant.helpers.issue_registry import (
|
from homeassistant.helpers.issue_registry import (
|
||||||
IssueSeverity,
|
IssueSeverity,
|
||||||
async_create_issue,
|
async_create_issue,
|
||||||
@ -329,6 +337,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
|||||||
self._native_unit_of_measurement = unit_of_measurement
|
self._native_unit_of_measurement = unit_of_measurement
|
||||||
self._valid_units: set[str | None] = set()
|
self._valid_units: set[str | None] = set()
|
||||||
self._can_convert: bool = False
|
self._can_convert: bool = False
|
||||||
|
self.calculate_attributes_later: CALLBACK_TYPE | None = None
|
||||||
self._attr_name = name
|
self._attr_name = name
|
||||||
if name == DEFAULT_NAME:
|
if name == DEFAULT_NAME:
|
||||||
self._attr_name = f"{DEFAULT_NAME} {sensor_type}".capitalize()
|
self._attr_name = f"{DEFAULT_NAME} {sensor_type}".capitalize()
|
||||||
@ -345,13 +354,32 @@ class SensorGroup(GroupEntity, SensorEntity):
|
|||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""When added to hass."""
|
"""When added to hass."""
|
||||||
|
for entity_id in self._entity_ids:
|
||||||
|
if self.hass.states.get(entity_id) is None:
|
||||||
|
self.calculate_attributes_later = async_track_state_change_event(
|
||||||
|
self.hass, self._entity_ids, self.calculate_state_attributes
|
||||||
|
)
|
||||||
|
break
|
||||||
|
if not self.calculate_attributes_later:
|
||||||
|
await self.calculate_state_attributes()
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
async def calculate_state_attributes(
|
||||||
|
self, event: Event[EventStateChangedData] | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Calculate state attributes."""
|
||||||
|
for entity_id in self._entity_ids:
|
||||||
|
if self.hass.states.get(entity_id) is None:
|
||||||
|
return
|
||||||
|
if self.calculate_attributes_later:
|
||||||
|
self.calculate_attributes_later()
|
||||||
|
self.calculate_attributes_later = None
|
||||||
self._attr_state_class = self._calculate_state_class(self._state_class)
|
self._attr_state_class = self._calculate_state_class(self._state_class)
|
||||||
self._attr_device_class = self._calculate_device_class(self._device_class)
|
self._attr_device_class = self._calculate_device_class(self._device_class)
|
||||||
self._attr_native_unit_of_measurement = self._calculate_unit_of_measurement(
|
self._attr_native_unit_of_measurement = self._calculate_unit_of_measurement(
|
||||||
self._native_unit_of_measurement
|
self._native_unit_of_measurement
|
||||||
)
|
)
|
||||||
self._valid_units = self._get_valid_units()
|
self._valid_units = self._get_valid_units()
|
||||||
await super().async_added_to_hass()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_group_state(self) -> None:
|
def async_update_group_state(self) -> None:
|
||||||
|
@ -64,7 +64,7 @@ class IdasenDeskConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
desk = Desk(None, monitor_height=False)
|
desk = Desk(None, monitor_height=False)
|
||||||
try:
|
try:
|
||||||
await desk.connect(discovery_info.device, auto_reconnect=False)
|
await desk.connect(discovery_info.device, retry=False)
|
||||||
except AuthFailedError:
|
except AuthFailedError:
|
||||||
errors["base"] = "auth_failed"
|
errors["base"] = "auth_failed"
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["imgw_pib==1.0.4"]
|
"requirements": ["imgw_pib==1.0.5"]
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["google_nest_sdm"],
|
"loggers": ["google_nest_sdm"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["google-nest-sdm==4.0.4"]
|
"requirements": ["google-nest-sdm==4.0.5"]
|
||||||
}
|
}
|
||||||
|
@ -388,12 +388,12 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
|
|
||||||
async def async_turn_off(self) -> None:
|
async def async_turn_off(self) -> None:
|
||||||
"""Turn off the zone."""
|
"""Turn off the zone."""
|
||||||
await self.async_set_hvac_mode(OPERATION_MODE_OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
async def async_turn_on(self) -> None:
|
async def async_turn_on(self) -> None:
|
||||||
"""Turn on the zone."""
|
"""Turn on the zone."""
|
||||||
await self.async_set_hvac_mode(OPERATION_MODE_AUTO)
|
await self.async_set_hvac_mode(HVACMode.AUTO)
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
|
@ -73,7 +73,7 @@ def async_create_issue(hass: HomeAssistant, entry_id: str) -> None:
|
|||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
issue_id=_get_issue_id(entry_id),
|
issue_id=_get_issue_id(entry_id),
|
||||||
is_fixable=True,
|
is_fixable=True,
|
||||||
is_persistent=True,
|
is_persistent=False,
|
||||||
severity=ir.IssueSeverity.WARNING,
|
severity=ir.IssueSeverity.WARNING,
|
||||||
learn_more_url="https://www.home-assistant.io/integrations/openweathermap/",
|
learn_more_url="https://www.home-assistant.io/integrations/openweathermap/",
|
||||||
translation_key="deprecated_v25",
|
translation_key="deprecated_v25",
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["opower"],
|
"loggers": ["opower"],
|
||||||
"requirements": ["opower==0.4.6"]
|
"requirements": ["opower==0.4.7"]
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
from icmplib import NameLookupError, async_ping
|
from icmplib import NameLookupError, async_ping
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.update_coordinator import UpdateFailed
|
||||||
|
|
||||||
from .const import ICMP_TIMEOUT, PING_TIMEOUT
|
from .const import ICMP_TIMEOUT, PING_TIMEOUT
|
||||||
|
|
||||||
@ -58,9 +59,16 @@ class PingDataICMPLib(PingData):
|
|||||||
timeout=ICMP_TIMEOUT,
|
timeout=ICMP_TIMEOUT,
|
||||||
privileged=self._privileged,
|
privileged=self._privileged,
|
||||||
)
|
)
|
||||||
except NameLookupError:
|
except NameLookupError as err:
|
||||||
self.is_alive = False
|
self.is_alive = False
|
||||||
return
|
raise UpdateFailed(f"Error resolving host: {self.ip_address}") from err
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"async_ping returned: reachable=%s sent=%i received=%s",
|
||||||
|
data.is_alive,
|
||||||
|
data.packets_sent,
|
||||||
|
data.packets_received,
|
||||||
|
)
|
||||||
|
|
||||||
self.is_alive = data.is_alive
|
self.is_alive = data.is_alive
|
||||||
if not self.is_alive:
|
if not self.is_alive:
|
||||||
@ -94,6 +102,10 @@ class PingDataSubProcess(PingData):
|
|||||||
|
|
||||||
async def async_ping(self) -> dict[str, Any] | None:
|
async def async_ping(self) -> dict[str, Any] | None:
|
||||||
"""Send ICMP echo request and return details if success."""
|
"""Send ICMP echo request and return details if success."""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Pinging %s with: `%s`", self.ip_address, " ".join(self._ping_cmd)
|
||||||
|
)
|
||||||
|
|
||||||
pinger = await asyncio.create_subprocess_exec(
|
pinger = await asyncio.create_subprocess_exec(
|
||||||
*self._ping_cmd,
|
*self._ping_cmd,
|
||||||
stdin=None,
|
stdin=None,
|
||||||
@ -140,20 +152,17 @@ class PingDataSubProcess(PingData):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
assert match is not None
|
assert match is not None
|
||||||
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
|
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
|
||||||
except TimeoutError:
|
except TimeoutError as err:
|
||||||
_LOGGER.exception(
|
|
||||||
"Timed out running command: `%s`, after: %ss",
|
|
||||||
self._ping_cmd,
|
|
||||||
self._count + PING_TIMEOUT,
|
|
||||||
)
|
|
||||||
if pinger:
|
if pinger:
|
||||||
with suppress(TypeError):
|
with suppress(TypeError):
|
||||||
await pinger.kill() # type: ignore[func-returns-value]
|
await pinger.kill() # type: ignore[func-returns-value]
|
||||||
del pinger
|
del pinger
|
||||||
|
|
||||||
return None
|
raise UpdateFailed(
|
||||||
except AttributeError:
|
f"Timed out running command: `{self._ping_cmd}`, after: {self._count + PING_TIMEOUT}s"
|
||||||
return None
|
) from err
|
||||||
|
except AttributeError as err:
|
||||||
|
raise UpdateFailed from err
|
||||||
return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": rtt_mdev}
|
return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": rtt_mdev}
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
|
@ -1245,7 +1245,7 @@ def _first_statistic(
|
|||||||
table: type[StatisticsBase],
|
table: type[StatisticsBase],
|
||||||
metadata_id: int,
|
metadata_id: int,
|
||||||
) -> datetime | None:
|
) -> datetime | None:
|
||||||
"""Return the data of the oldest statistic row for a given metadata id."""
|
"""Return the date of the oldest statistic row for a given metadata id."""
|
||||||
stmt = lambda_stmt(
|
stmt = lambda_stmt(
|
||||||
lambda: select(table.start_ts)
|
lambda: select(table.start_ts)
|
||||||
.filter(table.metadata_id == metadata_id)
|
.filter(table.metadata_id == metadata_id)
|
||||||
@ -1257,12 +1257,30 @@ def _first_statistic(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _last_statistic(
|
||||||
|
session: Session,
|
||||||
|
table: type[StatisticsBase],
|
||||||
|
metadata_id: int,
|
||||||
|
) -> datetime | None:
|
||||||
|
"""Return the date of the newest statistic row for a given metadata id."""
|
||||||
|
stmt = lambda_stmt(
|
||||||
|
lambda: select(table.start_ts)
|
||||||
|
.filter(table.metadata_id == metadata_id)
|
||||||
|
.order_by(table.start_ts.desc())
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
if stats := cast(Sequence[Row], execute_stmt_lambda_element(session, stmt)):
|
||||||
|
return dt_util.utc_from_timestamp(stats[0].start_ts)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_oldest_sum_statistic(
|
def _get_oldest_sum_statistic(
|
||||||
session: Session,
|
session: Session,
|
||||||
head_start_time: datetime | None,
|
head_start_time: datetime | None,
|
||||||
main_start_time: datetime | None,
|
main_start_time: datetime | None,
|
||||||
tail_start_time: datetime | None,
|
tail_start_time: datetime | None,
|
||||||
oldest_stat: datetime | None,
|
oldest_stat: datetime | None,
|
||||||
|
oldest_5_min_stat: datetime | None,
|
||||||
tail_only: bool,
|
tail_only: bool,
|
||||||
metadata_id: int,
|
metadata_id: int,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
@ -1307,6 +1325,15 @@ def _get_oldest_sum_statistic(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
head_start_time is not None
|
head_start_time is not None
|
||||||
|
and oldest_5_min_stat is not None
|
||||||
|
and (
|
||||||
|
# If we want stats older than the short term purge window, don't lookup
|
||||||
|
# the oldest sum in the short term table, as it would be prioritized
|
||||||
|
# over older LongTermStats.
|
||||||
|
(oldest_stat is None)
|
||||||
|
or (oldest_5_min_stat < oldest_stat)
|
||||||
|
or (oldest_5_min_stat <= head_start_time)
|
||||||
|
)
|
||||||
and (
|
and (
|
||||||
oldest_sum := _get_oldest_sum_statistic_in_sub_period(
|
oldest_sum := _get_oldest_sum_statistic_in_sub_period(
|
||||||
session, head_start_time, StatisticsShortTerm, metadata_id
|
session, head_start_time, StatisticsShortTerm, metadata_id
|
||||||
@ -1477,13 +1504,16 @@ def statistic_during_period(
|
|||||||
tail_start_time: datetime | None = None
|
tail_start_time: datetime | None = None
|
||||||
tail_end_time: datetime | None = None
|
tail_end_time: datetime | None = None
|
||||||
if end_time is None:
|
if end_time is None:
|
||||||
|
tail_start_time = _last_statistic(session, Statistics, metadata_id)
|
||||||
|
if tail_start_time:
|
||||||
|
tail_start_time += Statistics.duration
|
||||||
|
else:
|
||||||
tail_start_time = now.replace(minute=0, second=0, microsecond=0)
|
tail_start_time = now.replace(minute=0, second=0, microsecond=0)
|
||||||
|
elif tail_only:
|
||||||
|
tail_start_time = start_time
|
||||||
|
tail_end_time = end_time
|
||||||
elif end_time.minute:
|
elif end_time.minute:
|
||||||
tail_start_time = (
|
tail_start_time = end_time.replace(minute=0, second=0, microsecond=0)
|
||||||
start_time
|
|
||||||
if tail_only
|
|
||||||
else end_time.replace(minute=0, second=0, microsecond=0)
|
|
||||||
)
|
|
||||||
tail_end_time = end_time
|
tail_end_time = end_time
|
||||||
|
|
||||||
# Calculate the main period
|
# Calculate the main period
|
||||||
@ -1518,6 +1548,7 @@ def statistic_during_period(
|
|||||||
main_start_time,
|
main_start_time,
|
||||||
tail_start_time,
|
tail_start_time,
|
||||||
oldest_stat,
|
oldest_stat,
|
||||||
|
oldest_5_min_stat,
|
||||||
tail_only,
|
tail_only,
|
||||||
metadata_id,
|
metadata_id,
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["roborock"],
|
"loggers": ["roborock"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"python-roborock==2.2.3",
|
"python-roborock==2.3.0",
|
||||||
"vacuum-map-parser-roborock==0.1.2"
|
"vacuum-map-parser-roborock==0.1.2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -297,16 +297,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||||||
if version == 2:
|
if version == 2:
|
||||||
if minor_version < 2:
|
if minor_version < 2:
|
||||||
# Cleanup invalid MAC addresses - see #103512
|
# Cleanup invalid MAC addresses - see #103512
|
||||||
dev_reg = dr.async_get(hass)
|
# Reverted due to device registry collisions - see #119082 / #119249
|
||||||
for device in dr.async_entries_for_config_entry(
|
|
||||||
dev_reg, config_entry.entry_id
|
|
||||||
):
|
|
||||||
new_connections = device.connections.copy()
|
|
||||||
new_connections.discard((dr.CONNECTION_NETWORK_MAC, "none"))
|
|
||||||
if new_connections != device.connections:
|
|
||||||
dev_reg.async_update_device(
|
|
||||||
device.id, new_connections=new_connections
|
|
||||||
)
|
|
||||||
|
|
||||||
minor_version = 2
|
minor_version = 2
|
||||||
hass.config_entries.async_update_entry(config_entry, minor_version=2)
|
hass.config_entries.async_update_entry(config_entry, minor_version=2)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import contextlib
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from aioshelly.block_device import BlockDevice
|
from aioshelly.block_device import BlockDevice
|
||||||
@ -301,12 +300,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ShellyConfigEntry) -> b
|
|||||||
entry, platforms
|
entry, platforms
|
||||||
):
|
):
|
||||||
if shelly_entry_data.rpc:
|
if shelly_entry_data.rpc:
|
||||||
with contextlib.suppress(DeviceConnectionError):
|
|
||||||
# If the device is restarting or has gone offline before
|
|
||||||
# the ping/pong timeout happens, the shutdown command
|
|
||||||
# will fail, but we don't care since we are unloading
|
|
||||||
# and if we setup again, we will fix anything that is
|
|
||||||
# in an inconsistent state at that time.
|
|
||||||
await shelly_entry_data.rpc.shutdown()
|
await shelly_entry_data.rpc.shutdown()
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -625,7 +625,13 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
|||||||
if self.connected: # Already connected
|
if self.connected: # Already connected
|
||||||
return
|
return
|
||||||
self.connected = True
|
self.connected = True
|
||||||
|
try:
|
||||||
await self._async_run_connected_events()
|
await self._async_run_connected_events()
|
||||||
|
except DeviceConnectionError as err:
|
||||||
|
LOGGER.error(
|
||||||
|
"Error running connected events for device %s: %s", self.name, err
|
||||||
|
)
|
||||||
|
self.last_update_success = False
|
||||||
|
|
||||||
async def _async_run_connected_events(self) -> None:
|
async def _async_run_connected_events(self) -> None:
|
||||||
"""Run connected events.
|
"""Run connected events.
|
||||||
@ -699,10 +705,18 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
|||||||
if self.device.connected:
|
if self.device.connected:
|
||||||
try:
|
try:
|
||||||
await async_stop_scanner(self.device)
|
await async_stop_scanner(self.device)
|
||||||
|
await super().shutdown()
|
||||||
except InvalidAuthError:
|
except InvalidAuthError:
|
||||||
self.entry.async_start_reauth(self.hass)
|
self.entry.async_start_reauth(self.hass)
|
||||||
return
|
return
|
||||||
await super().shutdown()
|
except DeviceConnectionError as err:
|
||||||
|
# If the device is restarting or has gone offline before
|
||||||
|
# the ping/pong timeout happens, the shutdown command
|
||||||
|
# will fail, but we don't care since we are unloading
|
||||||
|
# and if we setup again, we will fix anything that is
|
||||||
|
# in an inconsistent state at that time.
|
||||||
|
LOGGER.debug("Error during shutdown for device %s: %s", self.name, err)
|
||||||
|
return
|
||||||
await self._async_disconnected(False)
|
await self._async_disconnected(False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aioshelly"],
|
"loggers": ["aioshelly"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aioshelly==10.0.0"],
|
"requirements": ["aioshelly==10.0.1"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_http._tcp.local.",
|
"type": "_http._tcp.local.",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from aiohttp import ClientTimeout
|
||||||
from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED
|
from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED
|
||||||
from synology_dsm.exceptions import (
|
from synology_dsm.exceptions import (
|
||||||
SynologyDSMAPIErrorException,
|
SynologyDSMAPIErrorException,
|
||||||
@ -40,7 +41,7 @@ DEFAULT_PORT = 5000
|
|||||||
DEFAULT_PORT_SSL = 5001
|
DEFAULT_PORT_SSL = 5001
|
||||||
# Options
|
# Options
|
||||||
DEFAULT_SCAN_INTERVAL = 15 # min
|
DEFAULT_SCAN_INTERVAL = 15 # min
|
||||||
DEFAULT_TIMEOUT = 30 # sec
|
DEFAULT_TIMEOUT = ClientTimeout(total=60, connect=15)
|
||||||
DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
|
DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
|
||||||
|
|
||||||
ENTITY_UNIT_LOAD = "load"
|
ENTITY_UNIT_LOAD = "load"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
|
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["synology_dsm"],
|
"loggers": ["synology_dsm"],
|
||||||
"requirements": ["py-synologydsm-api==2.4.2"],
|
"requirements": ["py-synologydsm-api==2.4.4"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Synology",
|
"manufacturer": "Synology",
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/thethingsnetwork",
|
"documentation": "https://www.home-assistant.io/integrations/thethingsnetwork",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["ttn_client==0.0.4"]
|
"requirements": ["ttn_client==1.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,14 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ServerDisconnectedError
|
from aiohttp.client_exceptions import ServerDisconnectedError
|
||||||
from pyunifiprotect.data import Bootstrap
|
from uiprotect.data import Bootstrap
|
||||||
from pyunifiprotect.data.types import FirmwareReleaseChannel
|
from uiprotect.data.types import FirmwareReleaseChannel
|
||||||
from pyunifiprotect.exceptions import ClientError, NotAuthorized
|
from uiprotect.exceptions import ClientError, NotAuthorized
|
||||||
|
|
||||||
# Import the test_util.anonymize module from the pyunifiprotect package
|
# Import the test_util.anonymize module from the uiprotect package
|
||||||
# in __init__ to ensure it gets imported in the executor since the
|
# in __init__ to ensure it gets imported in the executor since the
|
||||||
# diagnostics module will not be imported in the executor.
|
# diagnostics module will not be imported in the executor.
|
||||||
from pyunifiprotect.test_util.anonymize import anonymize_data # noqa: F401
|
from uiprotect.test_util.anonymize import anonymize_data # noqa: F401
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
@ -6,7 +6,7 @@ import dataclasses
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
NVR,
|
NVR,
|
||||||
Camera,
|
Camera,
|
||||||
Light,
|
Light,
|
||||||
@ -16,7 +16,7 @@ from pyunifiprotect.data import (
|
|||||||
ProtectModelWithId,
|
ProtectModelWithId,
|
||||||
Sensor,
|
Sensor,
|
||||||
)
|
)
|
||||||
from pyunifiprotect.data.nvr import UOSDisk
|
from uiprotect.data.nvr import UOSDisk
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
|
@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|||||||
import logging
|
import logging
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from pyunifiprotect.data import ProtectAdoptableDeviceModel, ProtectModelWithId
|
from uiprotect.data import ProtectAdoptableDeviceModel, ProtectModelWithId
|
||||||
|
|
||||||
from homeassistant.components.button import (
|
from homeassistant.components.button import (
|
||||||
ButtonDeviceClass,
|
ButtonDeviceClass,
|
||||||
|
@ -6,7 +6,7 @@ from collections.abc import Generator
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Camera as UFPCamera,
|
Camera as UFPCamera,
|
||||||
CameraChannel,
|
CameraChannel,
|
||||||
ModelType,
|
ModelType,
|
||||||
|
@ -8,9 +8,9 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import CookieJar
|
from aiohttp import CookieJar
|
||||||
from pyunifiprotect import ProtectApiClient
|
from uiprotect import ProtectApiClient
|
||||||
from pyunifiprotect.data import NVR
|
from uiprotect.data import NVR
|
||||||
from pyunifiprotect.exceptions import ClientError, NotAuthorized
|
from uiprotect.exceptions import ClientError, NotAuthorized
|
||||||
from unifi_discovery import async_console_is_alive
|
from unifi_discovery import async_console_is_alive
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Constant definitions for UniFi Protect Integration."""
|
"""Constant definitions for UniFi Protect Integration."""
|
||||||
|
|
||||||
from pyunifiprotect.data import ModelType, Version
|
from uiprotect.data import ModelType, Version
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ from functools import partial
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pyunifiprotect import ProtectApiClient
|
from uiprotect import ProtectApiClient
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
NVR,
|
NVR,
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
Camera,
|
Camera,
|
||||||
@ -20,8 +20,8 @@ from pyunifiprotect.data import (
|
|||||||
ProtectAdoptableDeviceModel,
|
ProtectAdoptableDeviceModel,
|
||||||
WSSubscriptionMessage,
|
WSSubscriptionMessage,
|
||||||
)
|
)
|
||||||
from pyunifiprotect.exceptions import ClientError, NotAuthorized
|
from uiprotect.exceptions import ClientError, NotAuthorized
|
||||||
from pyunifiprotect.utils import log_event
|
from uiprotect.utils import log_event
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pyunifiprotect.test_util.anonymize import anonymize_data
|
from uiprotect.test_util.anonymize import anonymize_data
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -6,7 +6,7 @@ from collections.abc import Callable, Sequence
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
NVR,
|
NVR,
|
||||||
Camera,
|
Camera,
|
||||||
Chime,
|
Chime,
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Light,
|
Light,
|
||||||
ModelType,
|
ModelType,
|
||||||
ProtectAdoptableDeviceModel,
|
ProtectAdoptableDeviceModel,
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Doorlock,
|
Doorlock,
|
||||||
LockStatusType,
|
LockStatusType,
|
||||||
ModelType,
|
ModelType,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "unifiprotect",
|
"domain": "unifiprotect",
|
||||||
"name": "UniFi Protect",
|
"name": "UniFi Protect",
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": [],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["http", "repairs"],
|
"dependencies": ["http", "repairs"],
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
@ -39,9 +39,8 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyunifiprotect", "unifi_discovery"],
|
"loggers": ["uiprotect", "unifi_discovery"],
|
||||||
"quality_scale": "platinum",
|
"requirements": ["uiprotect==0.4.1", "unifi-discovery==1.1.8"],
|
||||||
"requirements": ["pyunifiprotect==5.1.2", "unifi-discovery==1.1.8"],
|
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Ubiquiti Networks",
|
"manufacturer": "Ubiquiti Networks",
|
||||||
|
@ -5,14 +5,14 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Camera,
|
Camera,
|
||||||
ModelType,
|
ModelType,
|
||||||
ProtectAdoptableDeviceModel,
|
ProtectAdoptableDeviceModel,
|
||||||
ProtectModelWithId,
|
ProtectModelWithId,
|
||||||
StateType,
|
StateType,
|
||||||
)
|
)
|
||||||
from pyunifiprotect.exceptions import StreamError
|
from uiprotect.exceptions import StreamError
|
||||||
|
|
||||||
from homeassistant.components import media_source
|
from homeassistant.components import media_source
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
|
@ -7,15 +7,9 @@ from datetime import date, datetime, timedelta
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, NoReturn, cast
|
from typing import Any, NoReturn, cast
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import Camera, Event, EventType, ModelType, SmartDetectObjectType
|
||||||
Camera,
|
from uiprotect.exceptions import NvrError
|
||||||
Event,
|
from uiprotect.utils import from_js_time
|
||||||
EventType,
|
|
||||||
ModelType,
|
|
||||||
SmartDetectObjectType,
|
|
||||||
)
|
|
||||||
from pyunifiprotect.exceptions import NvrError
|
|
||||||
from pyunifiprotect.utils import from_js_time
|
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from homeassistant.components.camera import CameraImageView
|
from homeassistant.components.camera import CameraImageView
|
||||||
|
@ -6,8 +6,8 @@ from itertools import chain
|
|||||||
import logging
|
import logging
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from pyunifiprotect import ProtectApiClient
|
from uiprotect import ProtectApiClient
|
||||||
from pyunifiprotect.data import Bootstrap
|
from uiprotect.data import Bootstrap
|
||||||
|
|
||||||
from homeassistant.components.automation import automations_with_entity
|
from homeassistant.components.automation import automations_with_entity
|
||||||
from homeassistant.components.script import scripts_with_entity
|
from homeassistant.components.script import scripts_with_entity
|
||||||
|
@ -8,7 +8,7 @@ from enum import Enum
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
||||||
|
|
||||||
from pyunifiprotect.data import NVR, Event, ProtectAdoptableDeviceModel
|
from uiprotect.data import NVR, Event, ProtectAdoptableDeviceModel
|
||||||
|
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Camera,
|
Camera,
|
||||||
Doorlock,
|
Doorlock,
|
||||||
Light,
|
Light,
|
||||||
|
@ -4,9 +4,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from pyunifiprotect import ProtectApiClient
|
from uiprotect import ProtectApiClient
|
||||||
from pyunifiprotect.data import Bootstrap, Camera, ModelType
|
from uiprotect.data import Bootstrap, Camera, ModelType
|
||||||
from pyunifiprotect.data.types import FirmwareReleaseChannel
|
from uiprotect.data.types import FirmwareReleaseChannel
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
|
@ -8,8 +8,8 @@ from enum import Enum
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from pyunifiprotect.api import ProtectApiClient
|
from uiprotect.api import ProtectApiClient
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Camera,
|
Camera,
|
||||||
ChimeType,
|
ChimeType,
|
||||||
DoorbellMessageType,
|
DoorbellMessageType,
|
||||||
|
@ -7,7 +7,7 @@ from datetime import datetime
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
NVR,
|
NVR,
|
||||||
Camera,
|
Camera,
|
||||||
Light,
|
Light,
|
||||||
|
@ -7,9 +7,9 @@ import functools
|
|||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from pyunifiprotect.api import ProtectApiClient
|
from uiprotect.api import ProtectApiClient
|
||||||
from pyunifiprotect.data import Camera, Chime
|
from uiprotect.data import Camera, Chime
|
||||||
from pyunifiprotect.exceptions import ClientError
|
from uiprotect.exceptions import ClientError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
|
@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
NVR,
|
NVR,
|
||||||
Camera,
|
Camera,
|
||||||
ProtectAdoptableDeviceModel,
|
ProtectAdoptableDeviceModel,
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Camera,
|
Camera,
|
||||||
DoorbellMessageType,
|
DoorbellMessageType,
|
||||||
ProtectAdoptableDeviceModel,
|
ProtectAdoptableDeviceModel,
|
||||||
|
@ -10,8 +10,8 @@ import socket
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import CookieJar
|
from aiohttp import CookieJar
|
||||||
from pyunifiprotect import ProtectApiClient
|
from uiprotect import ProtectApiClient
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
CameraChannel,
|
CameraChannel,
|
||||||
Light,
|
Light,
|
||||||
|
@ -9,8 +9,8 @@ from typing import Any
|
|||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from pyunifiprotect.data import Camera, Event
|
from uiprotect.data import Camera, Event
|
||||||
from pyunifiprotect.exceptions import ClientError
|
from uiprotect.exceptions import ClientError
|
||||||
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/waqi",
|
"documentation": "https://www.home-assistant.io/integrations/waqi",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aiowaqi"],
|
"loggers": ["aiowaqi"],
|
||||||
"requirements": ["aiowaqi==3.0.1"]
|
"requirements": ["aiowaqi==3.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ class IsWorkdaySensor(BinarySensorEntity):
|
|||||||
|
|
||||||
def _update_state_and_setup_listener(self) -> None:
|
def _update_state_and_setup_listener(self) -> None:
|
||||||
"""Update state and setup listener for next interval."""
|
"""Update state and setup listener for next interval."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.now()
|
||||||
self.update_data(now)
|
self.update_data(now)
|
||||||
self.unsub = async_track_point_in_utc_time(
|
self.unsub = async_track_point_in_utc_time(
|
||||||
self.hass, self.point_in_time_listener, self.get_next_interval(now)
|
self.hass, self.point_in_time_listener, self.get_next_interval(now)
|
||||||
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 6
|
MINOR_VERSION: Final = 6
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
@ -5,8 +5,10 @@ from __future__ import annotations
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from functools import cache, partial
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import slugify as unicode_slug
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate.intent import INTENT_GET_TEMPERATURE
|
from homeassistant.components.climate.intent import INTENT_GET_TEMPERATURE
|
||||||
@ -175,10 +177,11 @@ class IntentTool(Tool):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
name: str,
|
||||||
intent_handler: intent.IntentHandler,
|
intent_handler: intent.IntentHandler,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Init the class."""
|
"""Init the class."""
|
||||||
self.name = intent_handler.intent_type
|
self.name = name
|
||||||
self.description = (
|
self.description = (
|
||||||
intent_handler.description or f"Execute Home Assistant {self.name} intent"
|
intent_handler.description or f"Execute Home Assistant {self.name} intent"
|
||||||
)
|
)
|
||||||
@ -261,6 +264,9 @@ class AssistAPI(API):
|
|||||||
id=LLM_API_ASSIST,
|
id=LLM_API_ASSIST,
|
||||||
name="Assist",
|
name="Assist",
|
||||||
)
|
)
|
||||||
|
self.cached_slugify = cache(
|
||||||
|
partial(unicode_slug.slugify, separator="_", lowercase=False)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_get_api_instance(self, llm_context: LLMContext) -> APIInstance:
|
async def async_get_api_instance(self, llm_context: LLMContext) -> APIInstance:
|
||||||
"""Return the instance of the API."""
|
"""Return the instance of the API."""
|
||||||
@ -373,7 +379,10 @@ class AssistAPI(API):
|
|||||||
or intent_handler.platforms & exposed_domains
|
or intent_handler.platforms & exposed_domains
|
||||||
]
|
]
|
||||||
|
|
||||||
return [IntentTool(intent_handler) for intent_handler in intent_handlers]
|
return [
|
||||||
|
IntentTool(self.cached_slugify(intent_handler.intent_type), intent_handler)
|
||||||
|
for intent_handler in intent_handlers
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _get_exposed_entities(
|
def _get_exposed_entities(
|
||||||
|
@ -1758,10 +1758,6 @@ class Script:
|
|||||||
# runs before sleeping as otherwise if two runs are started at the exact
|
# runs before sleeping as otherwise if two runs are started at the exact
|
||||||
# same time they will cancel each other out.
|
# same time they will cancel each other out.
|
||||||
self._log("Restarting")
|
self._log("Restarting")
|
||||||
# Important: yield to the event loop to allow the script to start in case
|
|
||||||
# the script is restarting itself so it ends up in the script stack and
|
|
||||||
# the recursion check above will prevent the script from running.
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
await self.async_stop(update_state=False, spare=run)
|
await self.async_stop(update_state=False, spare=run)
|
||||||
|
|
||||||
if started_action:
|
if started_action:
|
||||||
|
@ -32,7 +32,7 @@ habluetooth==3.1.1
|
|||||||
hass-nabucasa==0.81.1
|
hass-nabucasa==0.81.1
|
||||||
hassil==1.7.1
|
hassil==1.7.1
|
||||||
home-assistant-bluetooth==1.12.0
|
home-assistant-bluetooth==1.12.0
|
||||||
home-assistant-frontend==20240605.0
|
home-assistant-frontend==20240610.0
|
||||||
home-assistant-intents==2024.6.5
|
home-assistant-intents==2024.6.5
|
||||||
httpx==0.27.0
|
httpx==0.27.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
10
mypy.ini
10
mypy.ini
@ -1393,16 +1393,6 @@ disallow_untyped_defs = true
|
|||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.electrasmart.*]
|
|
||||||
check_untyped_defs = true
|
|
||||||
disallow_incomplete_defs = true
|
|
||||||
disallow_subclassing_any = true
|
|
||||||
disallow_untyped_calls = true
|
|
||||||
disallow_untyped_decorators = true
|
|
||||||
disallow_untyped_defs = true
|
|
||||||
warn_return_any = true
|
|
||||||
warn_unreachable = true
|
|
||||||
|
|
||||||
[mypy-homeassistant.components.electric_kiwi.*]
|
[mypy-homeassistant.components.electric_kiwi.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.6.1"
|
version = "2024.6.2"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -353,7 +353,7 @@ aioruuvigateway==0.1.0
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==10.0.0
|
aioshelly==10.0.1
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
@ -389,7 +389,7 @@ aiovlc==0.3.2
|
|||||||
aiovodafone==0.6.0
|
aiovodafone==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==3.0.1
|
aiowaqi==3.1.0
|
||||||
|
|
||||||
# homeassistant.components.watttime
|
# homeassistant.components.watttime
|
||||||
aiowatttime==0.1.1
|
aiowatttime==0.1.1
|
||||||
@ -912,7 +912,7 @@ fyta_cli==0.4.1
|
|||||||
gTTS==2.2.4
|
gTTS==2.2.4
|
||||||
|
|
||||||
# homeassistant.components.gardena_bluetooth
|
# homeassistant.components.gardena_bluetooth
|
||||||
gardena-bluetooth==1.4.1
|
gardena-bluetooth==1.4.2
|
||||||
|
|
||||||
# homeassistant.components.google_assistant_sdk
|
# homeassistant.components.google_assistant_sdk
|
||||||
gassist-text==0.0.11
|
gassist-text==0.0.11
|
||||||
@ -955,7 +955,7 @@ gios==4.0.0
|
|||||||
gitterpy==0.1.7
|
gitterpy==0.1.7
|
||||||
|
|
||||||
# homeassistant.components.glances
|
# homeassistant.components.glances
|
||||||
glances-api==0.7.0
|
glances-api==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.goalzero
|
# homeassistant.components.goalzero
|
||||||
goalzero==0.2.2
|
goalzero==0.2.2
|
||||||
@ -974,10 +974,10 @@ google-cloud-pubsub==2.13.11
|
|||||||
google-cloud-texttospeech==2.12.3
|
google-cloud-texttospeech==2.12.3
|
||||||
|
|
||||||
# homeassistant.components.google_generative_ai_conversation
|
# homeassistant.components.google_generative_ai_conversation
|
||||||
google-generativeai==0.5.4
|
google-generativeai==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==4.0.4
|
google-nest-sdm==4.0.5
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -1087,7 +1087,7 @@ hole==0.8.0
|
|||||||
holidays==0.50
|
holidays==0.50
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20240605.0
|
home-assistant-frontend==20240610.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.6.5
|
home-assistant-intents==2024.6.5
|
||||||
@ -1146,7 +1146,7 @@ iglo==1.2.7
|
|||||||
ihcsdk==2.8.5
|
ihcsdk==2.8.5
|
||||||
|
|
||||||
# homeassistant.components.imgw_pib
|
# homeassistant.components.imgw_pib
|
||||||
imgw_pib==1.0.4
|
imgw_pib==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.5.0
|
incomfort-client==0.5.0
|
||||||
@ -1501,7 +1501,7 @@ openwrt-luci-rpc==1.1.17
|
|||||||
openwrt-ubus-rpc==0.0.2
|
openwrt-ubus-rpc==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.4.6
|
opower==0.4.7
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1649,7 +1649,7 @@ py-schluter==0.1.7
|
|||||||
py-sucks==0.9.10
|
py-sucks==0.9.10
|
||||||
|
|
||||||
# homeassistant.components.synology_dsm
|
# homeassistant.components.synology_dsm
|
||||||
py-synologydsm-api==2.4.2
|
py-synologydsm-api==2.4.4
|
||||||
|
|
||||||
# homeassistant.components.zabbix
|
# homeassistant.components.zabbix
|
||||||
py-zabbix==1.1.7
|
py-zabbix==1.1.7
|
||||||
@ -1670,7 +1670,7 @@ pyControl4==1.1.0
|
|||||||
pyDuotecno==2024.5.1
|
pyDuotecno==2024.5.1
|
||||||
|
|
||||||
# homeassistant.components.electrasmart
|
# homeassistant.components.electrasmart
|
||||||
pyElectra==1.2.0
|
pyElectra==1.2.1
|
||||||
|
|
||||||
# homeassistant.components.emby
|
# homeassistant.components.emby
|
||||||
pyEmby==1.9
|
pyEmby==1.9
|
||||||
@ -2306,7 +2306,7 @@ python-rabbitair==0.0.8
|
|||||||
python-ripple-api==0.0.3
|
python-ripple-api==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==2.2.3
|
python-roborock==2.3.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.36
|
python-smarttub==0.0.36
|
||||||
@ -2357,9 +2357,6 @@ pytrydan==0.6.1
|
|||||||
# homeassistant.components.usb
|
# homeassistant.components.usb
|
||||||
pyudev==0.24.1
|
pyudev==0.24.1
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
|
||||||
pyunifiprotect==5.1.2
|
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
|
|
||||||
@ -2764,7 +2761,7 @@ transmission-rpc==7.0.3
|
|||||||
ttls==1.5.1
|
ttls==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.thethingsnetwork
|
# homeassistant.components.thethingsnetwork
|
||||||
ttn_client==0.0.4
|
ttn_client==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.tuya
|
# homeassistant.components.tuya
|
||||||
tuya-device-sharing-sdk==0.1.9
|
tuya-device-sharing-sdk==0.1.9
|
||||||
@ -2781,6 +2778,9 @@ twitchAPI==4.0.0
|
|||||||
# homeassistant.components.ukraine_alarm
|
# homeassistant.components.ukraine_alarm
|
||||||
uasiren==0.0.1
|
uasiren==0.0.1
|
||||||
|
|
||||||
|
# homeassistant.components.unifiprotect
|
||||||
|
uiprotect==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.landisgyr_heat_meter
|
# homeassistant.components.landisgyr_heat_meter
|
||||||
ultraheat-api==0.5.7
|
ultraheat-api==0.5.7
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ aioruuvigateway==0.1.0
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==10.0.0
|
aioshelly==10.0.1
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
@ -362,7 +362,7 @@ aiovlc==0.3.2
|
|||||||
aiovodafone==0.6.0
|
aiovodafone==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==3.0.1
|
aiowaqi==3.1.0
|
||||||
|
|
||||||
# homeassistant.components.watttime
|
# homeassistant.components.watttime
|
||||||
aiowatttime==0.1.1
|
aiowatttime==0.1.1
|
||||||
@ -747,7 +747,7 @@ fyta_cli==0.4.1
|
|||||||
gTTS==2.2.4
|
gTTS==2.2.4
|
||||||
|
|
||||||
# homeassistant.components.gardena_bluetooth
|
# homeassistant.components.gardena_bluetooth
|
||||||
gardena-bluetooth==1.4.1
|
gardena-bluetooth==1.4.2
|
||||||
|
|
||||||
# homeassistant.components.google_assistant_sdk
|
# homeassistant.components.google_assistant_sdk
|
||||||
gassist-text==0.0.11
|
gassist-text==0.0.11
|
||||||
@ -784,7 +784,7 @@ getmac==0.9.4
|
|||||||
gios==4.0.0
|
gios==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.glances
|
# homeassistant.components.glances
|
||||||
glances-api==0.7.0
|
glances-api==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.goalzero
|
# homeassistant.components.goalzero
|
||||||
goalzero==0.2.2
|
goalzero==0.2.2
|
||||||
@ -800,10 +800,10 @@ google-api-python-client==2.71.0
|
|||||||
google-cloud-pubsub==2.13.11
|
google-cloud-pubsub==2.13.11
|
||||||
|
|
||||||
# homeassistant.components.google_generative_ai_conversation
|
# homeassistant.components.google_generative_ai_conversation
|
||||||
google-generativeai==0.5.4
|
google-generativeai==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==4.0.4
|
google-nest-sdm==4.0.5
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -889,7 +889,7 @@ hole==0.8.0
|
|||||||
holidays==0.50
|
holidays==0.50
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20240605.0
|
home-assistant-frontend==20240610.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.6.5
|
home-assistant-intents==2024.6.5
|
||||||
@ -933,7 +933,7 @@ idasen-ha==2.5.3
|
|||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.imgw_pib
|
# homeassistant.components.imgw_pib
|
||||||
imgw_pib==1.0.4
|
imgw_pib==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
influxdb-client==1.24.0
|
influxdb-client==1.24.0
|
||||||
@ -1201,7 +1201,7 @@ openhomedevice==2.2.0
|
|||||||
openwebifpy==4.2.4
|
openwebifpy==4.2.4
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.4.6
|
opower==0.4.7
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1311,7 +1311,7 @@ py-nightscout==1.2.2
|
|||||||
py-sucks==0.9.10
|
py-sucks==0.9.10
|
||||||
|
|
||||||
# homeassistant.components.synology_dsm
|
# homeassistant.components.synology_dsm
|
||||||
py-synologydsm-api==2.4.2
|
py-synologydsm-api==2.4.4
|
||||||
|
|
||||||
# homeassistant.components.seventeentrack
|
# homeassistant.components.seventeentrack
|
||||||
py17track==2021.12.2
|
py17track==2021.12.2
|
||||||
@ -1326,7 +1326,7 @@ pyControl4==1.1.0
|
|||||||
pyDuotecno==2024.5.1
|
pyDuotecno==2024.5.1
|
||||||
|
|
||||||
# homeassistant.components.electrasmart
|
# homeassistant.components.electrasmart
|
||||||
pyElectra==1.2.0
|
pyElectra==1.2.1
|
||||||
|
|
||||||
# homeassistant.components.rfxtrx
|
# homeassistant.components.rfxtrx
|
||||||
pyRFXtrx==0.31.1
|
pyRFXtrx==0.31.1
|
||||||
@ -1794,7 +1794,7 @@ python-qbittorrent==0.4.3
|
|||||||
python-rabbitair==0.0.8
|
python-rabbitair==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==2.2.3
|
python-roborock==2.3.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.36
|
python-smarttub==0.0.36
|
||||||
@ -1836,9 +1836,6 @@ pytrydan==0.6.1
|
|||||||
# homeassistant.components.usb
|
# homeassistant.components.usb
|
||||||
pyudev==0.24.1
|
pyudev==0.24.1
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
|
||||||
pyunifiprotect==5.1.2
|
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
|
|
||||||
@ -2138,7 +2135,7 @@ transmission-rpc==7.0.3
|
|||||||
ttls==1.5.1
|
ttls==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.thethingsnetwork
|
# homeassistant.components.thethingsnetwork
|
||||||
ttn_client==0.0.4
|
ttn_client==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.tuya
|
# homeassistant.components.tuya
|
||||||
tuya-device-sharing-sdk==0.1.9
|
tuya-device-sharing-sdk==0.1.9
|
||||||
@ -2155,6 +2152,9 @@ twitchAPI==4.0.0
|
|||||||
# homeassistant.components.ukraine_alarm
|
# homeassistant.components.ukraine_alarm
|
||||||
uasiren==0.0.1
|
uasiren==0.0.1
|
||||||
|
|
||||||
|
# homeassistant.components.unifiprotect
|
||||||
|
uiprotect==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.landisgyr_heat_meter
|
# homeassistant.components.landisgyr_heat_meter
|
||||||
ultraheat-api==0.5.7
|
ultraheat-api==0.5.7
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ PIP_VERSION_RANGE_SEPARATOR = re.compile(r"^(==|>=|<=|~=|!=|<|>|===)?(.*)$")
|
|||||||
|
|
||||||
IGNORE_STANDARD_LIBRARY_VIOLATIONS = {
|
IGNORE_STANDARD_LIBRARY_VIOLATIONS = {
|
||||||
# Integrations which have standard library requirements.
|
# Integrations which have standard library requirements.
|
||||||
"electrasmart",
|
|
||||||
"slide",
|
"slide",
|
||||||
"suez_water",
|
"suez_water",
|
||||||
}
|
}
|
||||||
|
@ -2771,6 +2771,7 @@ async def test_recursive_automation_starting_script(
|
|||||||
],
|
],
|
||||||
"action": [
|
"action": [
|
||||||
{"service": "test.automation_started"},
|
{"service": "test.automation_started"},
|
||||||
|
{"delay": 0.001},
|
||||||
{"service": "script.script1"},
|
{"service": "script.script1"},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@ -2817,7 +2818,10 @@ async def test_recursive_automation_starting_script(
|
|||||||
assert script_warning_msg in caplog.text
|
assert script_warning_msg in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("automation_mode", SCRIPT_MODE_CHOICES)
|
@pytest.mark.parametrize(
|
||||||
|
"automation_mode",
|
||||||
|
[mode for mode in SCRIPT_MODE_CHOICES if mode != SCRIPT_MODE_RESTART],
|
||||||
|
)
|
||||||
@pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True])
|
@pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True])
|
||||||
async def test_recursive_automation(
|
async def test_recursive_automation(
|
||||||
hass: HomeAssistant, automation_mode, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, automation_mode, caplog: pytest.LogCaptureFixture
|
||||||
@ -2878,6 +2882,68 @@ async def test_recursive_automation(
|
|||||||
assert "Disallowed recursion detected" not in caplog.text
|
assert "Disallowed recursion detected" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True])
|
||||||
|
async def test_recursive_automation_restart_mode(
|
||||||
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""Test automation restarting itself.
|
||||||
|
|
||||||
|
The automation is an infinite loop since it keeps restarting itself
|
||||||
|
|
||||||
|
- Illegal recursion detection should not be triggered
|
||||||
|
- Home Assistant should not hang on shut down
|
||||||
|
"""
|
||||||
|
stop_scripts_at_shutdown_called = asyncio.Event()
|
||||||
|
real_stop_scripts_at_shutdown = _async_stop_scripts_at_shutdown
|
||||||
|
|
||||||
|
async def stop_scripts_at_shutdown(*args):
|
||||||
|
await real_stop_scripts_at_shutdown(*args)
|
||||||
|
stop_scripts_at_shutdown_called.set()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.helpers.script._async_stop_scripts_at_shutdown",
|
||||||
|
wraps=stop_scripts_at_shutdown,
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"mode": SCRIPT_MODE_RESTART,
|
||||||
|
"trigger": [
|
||||||
|
{"platform": "event", "event_type": "trigger_automation"},
|
||||||
|
],
|
||||||
|
"action": [
|
||||||
|
{"event": "trigger_automation"},
|
||||||
|
{"service": "test.automation_done"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
service_called = asyncio.Event()
|
||||||
|
|
||||||
|
async def async_service_handler(service):
|
||||||
|
if service.service == "automation_done":
|
||||||
|
service_called.set()
|
||||||
|
|
||||||
|
hass.services.async_register("test", "automation_done", async_service_handler)
|
||||||
|
|
||||||
|
hass.bus.async_fire("trigger_automation")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
# Trigger 1st stage script shutdown
|
||||||
|
hass.set_state(CoreState.stopping)
|
||||||
|
hass.bus.async_fire("homeassistant_stop")
|
||||||
|
await asyncio.wait_for(stop_scripts_at_shutdown_called.wait(), 1)
|
||||||
|
|
||||||
|
# Trigger 2nd stage script shutdown
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=90))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "Disallowed recursion detected" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_websocket_config(
|
async def test_websocket_config(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -3097,3 +3163,72 @@ async def test_two_automations_call_restart_script_same_time(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(events) == 2
|
assert len(events) == 2
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_two_automation_call_restart_script_right_after_each_other(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test two automations call a restart script right after each other."""
|
||||||
|
|
||||||
|
events = async_capture_events(hass, "repeat_test_script_finished")
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
input_boolean.DOMAIN,
|
||||||
|
{
|
||||||
|
input_boolean.DOMAIN: {
|
||||||
|
"test_1": None,
|
||||||
|
"test_2": None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: [
|
||||||
|
{
|
||||||
|
"trigger": {
|
||||||
|
"platform": "state",
|
||||||
|
"entity_id": ["input_boolean.test_1", "input_boolean.test_1"],
|
||||||
|
"from": "off",
|
||||||
|
"to": "on",
|
||||||
|
},
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"repeat": {
|
||||||
|
"count": 2,
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"delay": {
|
||||||
|
"hours": 0,
|
||||||
|
"minutes": 0,
|
||||||
|
"seconds": 0,
|
||||||
|
"milliseconds": 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"event": "repeat_test_script_finished", "event_data": {}},
|
||||||
|
],
|
||||||
|
"id": "automation_0",
|
||||||
|
"mode": "restart",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
hass.states.async_set("input_boolean.test_1", "off")
|
||||||
|
hass.states.async_set("input_boolean.test_2", "off")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
hass.states.async_set("input_boolean.test_1", "on")
|
||||||
|
hass.states.async_set("input_boolean.test_2", "on")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
hass.states.async_set("input_boolean.test_1", "off")
|
||||||
|
hass.states.async_set("input_boolean.test_2", "off")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
hass.states.async_set("input_boolean.test_1", "on")
|
||||||
|
hass.states.async_set("input_boolean.test_2", "on")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 1
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.azure_data_explorer.const import (
|
|||||||
CONF_APP_REG_SECRET,
|
CONF_APP_REG_SECRET,
|
||||||
CONF_AUTHORITY_ID,
|
CONF_AUTHORITY_ID,
|
||||||
CONF_SEND_INTERVAL,
|
CONF_SEND_INTERVAL,
|
||||||
CONF_USE_FREE,
|
CONF_USE_QUEUED_CLIENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
AZURE_DATA_EXPLORER_PATH = "homeassistant.components.azure_data_explorer"
|
AZURE_DATA_EXPLORER_PATH = "homeassistant.components.azure_data_explorer"
|
||||||
@ -29,7 +29,7 @@ BASE_CONFIG_URI = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BASIC_OPTIONS = {
|
BASIC_OPTIONS = {
|
||||||
CONF_USE_FREE: False,
|
CONF_USE_QUEUED_CLIENT: False,
|
||||||
CONF_SEND_INTERVAL: 5,
|
CONF_SEND_INTERVAL: 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,10 +39,10 @@ BASE_CONFIG_FULL = BASE_CONFIG | BASIC_OPTIONS | BASE_CONFIG_URI
|
|||||||
|
|
||||||
BASE_CONFIG_IMPORT = {
|
BASE_CONFIG_IMPORT = {
|
||||||
CONF_ADX_CLUSTER_INGEST_URI: "https://cluster.region.kusto.windows.net",
|
CONF_ADX_CLUSTER_INGEST_URI: "https://cluster.region.kusto.windows.net",
|
||||||
CONF_USE_FREE: False,
|
CONF_USE_QUEUED_CLIENT: False,
|
||||||
CONF_SEND_INTERVAL: 5,
|
CONF_SEND_INTERVAL: 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
FREE_OPTIONS = {CONF_USE_FREE: True, CONF_SEND_INTERVAL: 5}
|
FREE_OPTIONS = {CONF_USE_QUEUED_CLIENT: True, CONF_SEND_INTERVAL: 5}
|
||||||
|
|
||||||
BASE_CONFIG_FREE = BASE_CONFIG | FREE_OPTIONS
|
BASE_CONFIG_FREE = BASE_CONFIG | FREE_OPTIONS
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"productName": "Elgato Key Light",
|
"productName": "Elgato Light Strip",
|
||||||
"hardwareBoardType": 53,
|
"hardwareBoardType": 53,
|
||||||
"firmwareBuildNumber": 192,
|
"firmwareBuildNumber": 192,
|
||||||
"firmwareVersion": "1.0.3",
|
"firmwareVersion": "1.0.3",
|
||||||
|
@ -218,7 +218,7 @@
|
|||||||
'labels': set({
|
'labels': set({
|
||||||
}),
|
}),
|
||||||
'manufacturer': 'Elgato',
|
'manufacturer': 'Elgato',
|
||||||
'model': 'Elgato Key Light',
|
'model': 'Elgato Light Strip',
|
||||||
'name': 'Frenck',
|
'name': 'Frenck',
|
||||||
'name_by_user': None,
|
'name_by_user': None,
|
||||||
'serial_number': 'CN11A1A00001',
|
'serial_number': 'CN11A1A00001',
|
||||||
@ -333,7 +333,7 @@
|
|||||||
'labels': set({
|
'labels': set({
|
||||||
}),
|
}),
|
||||||
'manufacturer': 'Elgato',
|
'manufacturer': 'Elgato',
|
||||||
'model': 'Elgato Key Light',
|
'model': 'Elgato Light Strip',
|
||||||
'name': 'Frenck',
|
'name': 'Frenck',
|
||||||
'name_by_user': None,
|
'name_by_user': None,
|
||||||
'serial_number': 'CN11A1A00001',
|
'serial_number': 'CN11A1A00001',
|
||||||
|
@ -1931,7 +1931,10 @@ async def test_arm_disarm_disarm(hass: HomeAssistant) -> None:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trt.query_attributes() == {"isArmed": False}
|
assert trt.query_attributes() == {
|
||||||
|
"currentArmLevel": "armed_custom_bypass",
|
||||||
|
"isArmed": False,
|
||||||
|
}
|
||||||
|
|
||||||
assert trt.can_execute(trait.COMMAND_ARMDISARM, {"arm": False})
|
assert trt.can_execute(trait.COMMAND_ARMDISARM, {"arm": False})
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components import conversation
|
from homeassistant.components import conversation
|
||||||
from homeassistant.components.conversation import trace
|
from homeassistant.components.conversation import trace
|
||||||
|
from homeassistant.components.google_generative_ai_conversation.conversation import (
|
||||||
|
_escape_decode,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_LLM_HASS_API
|
from homeassistant.const import CONF_LLM_HASS_API
|
||||||
from homeassistant.core import Context, HomeAssistant
|
from homeassistant.core import Context, HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
@ -504,3 +507,18 @@ async def test_conversation_agent(
|
|||||||
mock_config_entry.entry_id
|
mock_config_entry.entry_id
|
||||||
)
|
)
|
||||||
assert agent.supported_languages == "*"
|
assert agent.supported_languages == "*"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_escape_decode() -> None:
|
||||||
|
"""Test _escape_decode."""
|
||||||
|
assert _escape_decode(
|
||||||
|
{
|
||||||
|
"param1": ["test_value", "param1\\'s value"],
|
||||||
|
"param2": "param2\\'s value",
|
||||||
|
"param3": {"param31": "Cheminée", "param32": "Chemin\\303\\251e"},
|
||||||
|
}
|
||||||
|
) == {
|
||||||
|
"param1": ["test_value", "param1's value"],
|
||||||
|
"param2": "param2's value",
|
||||||
|
"param3": {"param31": "Cheminée", "param32": "Cheminée"},
|
||||||
|
}
|
||||||
|
@ -763,3 +763,52 @@ async def test_last_sensor(hass: HomeAssistant) -> None:
|
|||||||
state = hass.states.get("sensor.test_last")
|
state = hass.states.get("sensor.test_last")
|
||||||
assert str(float(value)) == state.state
|
assert str(float(value)) == state.state
|
||||||
assert entity_id == state.attributes.get("last_entity_id")
|
assert entity_id == state.attributes.get("last_entity_id")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_attributes_added_when_entity_info_available(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor calculate attributes once all entities attributes are available."""
|
||||||
|
config = {
|
||||||
|
SENSOR_DOMAIN: {
|
||||||
|
"platform": GROUP_DOMAIN,
|
||||||
|
"name": DEFAULT_NAME,
|
||||||
|
"type": "sum",
|
||||||
|
"entities": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
|
||||||
|
"unique_id": "very_unique_id",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_ids = config["sensor"]["entities"]
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "sensor", config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.sensor_group_sum")
|
||||||
|
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
assert state.attributes.get(ATTR_ENTITY_ID) is None
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) is None
|
||||||
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
||||||
|
|
||||||
|
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
value,
|
||||||
|
{
|
||||||
|
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
|
||||||
|
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: "L",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.sensor_group_sum")
|
||||||
|
|
||||||
|
assert float(state.state) == pytest.approx(float(SUM_VALUE))
|
||||||
|
assert state.attributes.get(ATTR_ENTITY_ID) == entity_ids
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLUME
|
||||||
|
assert state.attributes.get(ATTR_ICON) is None
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL
|
||||||
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "L"
|
||||||
|
@ -305,4 +305,4 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None:
|
|||||||
}
|
}
|
||||||
assert result2["result"].unique_id == IDASEN_DISCOVERY_INFO.address
|
assert result2["result"].unique_id == IDASEN_DISCOVERY_INFO.address
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
desk_connect.assert_called_with(ANY, auto_reconnect=False)
|
desk_connect.assert_called_with(ANY, retry=False)
|
||||||
|
@ -7,6 +7,7 @@ import threading
|
|||||||
from unittest.mock import ANY, patch
|
from unittest.mock import ANY, patch
|
||||||
|
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import recorder
|
from homeassistant.components import recorder
|
||||||
@ -794,6 +795,347 @@ async def test_statistic_during_period_hole(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"frozen_time",
|
||||||
|
[
|
||||||
|
# This is the normal case, all statistics runs are available
|
||||||
|
datetime.datetime(2022, 10, 21, 6, 31, tzinfo=datetime.UTC),
|
||||||
|
# Statistic only available up until 6:25, this can happen if
|
||||||
|
# core has been shut down for an hour
|
||||||
|
datetime.datetime(2022, 10, 21, 7, 31, tzinfo=datetime.UTC),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_statistic_during_period_partial_overlap(
|
||||||
|
recorder_mock: Recorder,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
frozen_time: datetime,
|
||||||
|
) -> None:
|
||||||
|
"""Test statistic_during_period."""
|
||||||
|
client = await hass_ws_client()
|
||||||
|
|
||||||
|
freezer.move_to(frozen_time)
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
|
zero = now
|
||||||
|
start = zero.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
# Sum shall be tracking a hypothetical sensor that is 0 at midnight, and grows by 1 per minute.
|
||||||
|
# The test will have 4 hours of LTS-only data (0:00-3:59:59), followed by 2 hours of overlapping STS/LTS (4:00-5:59:59), followed by 30 minutes of STS only (6:00-6:29:59)
|
||||||
|
# similar to how a real recorder might look after purging STS.
|
||||||
|
|
||||||
|
# The datapoint at i=0 (start = 0:00) will be 60 as that is the growth during the hour starting at the start period
|
||||||
|
imported_stats_hours = [
|
||||||
|
{
|
||||||
|
"start": (start + timedelta(hours=i)),
|
||||||
|
"min": i * 60,
|
||||||
|
"max": i * 60 + 60,
|
||||||
|
"mean": i * 60 + 30,
|
||||||
|
"sum": (i + 1) * 60,
|
||||||
|
}
|
||||||
|
for i in range(6)
|
||||||
|
]
|
||||||
|
|
||||||
|
# The datapoint at i=0 (start = 4:00) would be the sensor's value at t=4:05, or 245
|
||||||
|
imported_stats_5min = [
|
||||||
|
{
|
||||||
|
"start": (start + timedelta(hours=4, minutes=5 * i)),
|
||||||
|
"min": 4 * 60 + i * 5,
|
||||||
|
"max": 4 * 60 + i * 5 + 5,
|
||||||
|
"mean": 4 * 60 + i * 5 + 2.5,
|
||||||
|
"sum": 4 * 60 + (i + 1) * 5,
|
||||||
|
}
|
||||||
|
for i in range(30)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert imported_stats_hours[-1]["sum"] == 360
|
||||||
|
assert imported_stats_hours[-1]["start"] == start.replace(
|
||||||
|
hour=5, minute=0, second=0, microsecond=0
|
||||||
|
)
|
||||||
|
assert imported_stats_5min[-1]["sum"] == 390
|
||||||
|
assert imported_stats_5min[-1]["start"] == start.replace(
|
||||||
|
hour=6, minute=25, second=0, microsecond=0
|
||||||
|
)
|
||||||
|
|
||||||
|
statId = "sensor.test_overlapping"
|
||||||
|
imported_metadata = {
|
||||||
|
"has_mean": False,
|
||||||
|
"has_sum": True,
|
||||||
|
"name": "Total imported energy overlapping",
|
||||||
|
"source": "recorder",
|
||||||
|
"statistic_id": statId,
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder.get_instance(hass).async_import_statistics(
|
||||||
|
imported_metadata,
|
||||||
|
imported_stats_hours,
|
||||||
|
Statistics,
|
||||||
|
)
|
||||||
|
recorder.get_instance(hass).async_import_statistics(
|
||||||
|
imported_metadata,
|
||||||
|
imported_stats_5min,
|
||||||
|
StatisticsShortTerm,
|
||||||
|
)
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
metadata = get_metadata(hass, statistic_ids={statId})
|
||||||
|
metadata_id = metadata[statId][0]
|
||||||
|
run_cache = get_short_term_statistics_run_cache(hass)
|
||||||
|
# Verify the import of the short term statistics
|
||||||
|
# also updates the run cache
|
||||||
|
assert run_cache.get_latest_ids({metadata_id}) is not None
|
||||||
|
|
||||||
|
# Get all the stats, should consider all hours and 5mins
|
||||||
|
await client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "recorder/statistic_during_period",
|
||||||
|
"statistic_id": statId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {
|
||||||
|
"change": 390,
|
||||||
|
"max": 390,
|
||||||
|
"min": 0,
|
||||||
|
"mean": 195,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def assert_stat_during_fixed(client, start_time, end_time, expect):
|
||||||
|
json = {
|
||||||
|
"type": "recorder/statistic_during_period",
|
||||||
|
"types": list(expect.keys()),
|
||||||
|
"statistic_id": statId,
|
||||||
|
"fixed_period": {},
|
||||||
|
}
|
||||||
|
if start_time:
|
||||||
|
json["fixed_period"]["start_time"] = start_time.isoformat()
|
||||||
|
if end_time:
|
||||||
|
json["fixed_period"]["end_time"] = end_time.isoformat()
|
||||||
|
|
||||||
|
await client.send_json_auto_id(json)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == expect
|
||||||
|
|
||||||
|
# One hours worth of growth in LTS-only
|
||||||
|
start_time = start.replace(hour=1)
|
||||||
|
end_time = start.replace(hour=2)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client, start_time, end_time, {"change": 60, "min": 60, "max": 120, "mean": 90}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Five minutes of growth in STS-only
|
||||||
|
start_time = start.replace(hour=6, minute=15)
|
||||||
|
end_time = start.replace(hour=6, minute=20)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 5,
|
||||||
|
"min": 6 * 60 + 15,
|
||||||
|
"max": 6 * 60 + 20,
|
||||||
|
"mean": 6 * 60 + (15 + 20) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Six minutes of growth in STS-only
|
||||||
|
start_time = start.replace(hour=6, minute=14)
|
||||||
|
end_time = start.replace(hour=6, minute=20)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 5,
|
||||||
|
"min": 6 * 60 + 15,
|
||||||
|
"max": 6 * 60 + 20,
|
||||||
|
"mean": 6 * 60 + (15 + 20) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Six minutes of growth in STS-only
|
||||||
|
# 5-minute Change includes start times exactly on or before a statistics start, but end times are not counted unless they are greater than start.
|
||||||
|
start_time = start.replace(hour=6, minute=15)
|
||||||
|
end_time = start.replace(hour=6, minute=21)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 10,
|
||||||
|
"min": 6 * 60 + 15,
|
||||||
|
"max": 6 * 60 + 25,
|
||||||
|
"mean": 6 * 60 + (15 + 25) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Five minutes of growth in overlapping LTS+STS
|
||||||
|
start_time = start.replace(hour=5, minute=15)
|
||||||
|
end_time = start.replace(hour=5, minute=20)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 5,
|
||||||
|
"min": 5 * 60 + 15,
|
||||||
|
"max": 5 * 60 + 20,
|
||||||
|
"mean": 5 * 60 + (15 + 20) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Five minutes of growth in overlapping LTS+STS (start of hour)
|
||||||
|
start_time = start.replace(hour=5, minute=0)
|
||||||
|
end_time = start.replace(hour=5, minute=5)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 5, "min": 5 * 60, "max": 5 * 60 + 5, "mean": 5 * 60 + (5) / 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Five minutes of growth in overlapping LTS+STS (end of hour)
|
||||||
|
start_time = start.replace(hour=4, minute=55)
|
||||||
|
end_time = start.replace(hour=5, minute=0)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 5,
|
||||||
|
"min": 4 * 60 + 55,
|
||||||
|
"max": 5 * 60,
|
||||||
|
"mean": 4 * 60 + (55 + 60) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Five minutes of growth in STS-only, with a minute offset. Despite that this does not cover the full period, result is still 5
|
||||||
|
start_time = start.replace(hour=6, minute=16)
|
||||||
|
end_time = start.replace(hour=6, minute=21)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 5,
|
||||||
|
"min": 6 * 60 + 20,
|
||||||
|
"max": 6 * 60 + 25,
|
||||||
|
"mean": 6 * 60 + (20 + 25) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 7 minutes of growth in STS-only, spanning two intervals
|
||||||
|
start_time = start.replace(hour=6, minute=14)
|
||||||
|
end_time = start.replace(hour=6, minute=21)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 10,
|
||||||
|
"min": 6 * 60 + 15,
|
||||||
|
"max": 6 * 60 + 25,
|
||||||
|
"mean": 6 * 60 + (15 + 25) / 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# One hours worth of growth in LTS-only, with arbitrary minute offsets
|
||||||
|
# Since this does not fully cover the hour, result is None?
|
||||||
|
start_time = start.replace(hour=1, minute=40)
|
||||||
|
end_time = start.replace(hour=2, minute=12)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": None, "min": None, "max": None, "mean": None},
|
||||||
|
)
|
||||||
|
|
||||||
|
# One hours worth of growth in LTS-only, with arbitrary minute offsets, covering a whole 1-hour period
|
||||||
|
start_time = start.replace(hour=1, minute=40)
|
||||||
|
end_time = start.replace(hour=3, minute=12)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 60, "min": 120, "max": 180, "mean": 150},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 90 minutes of growth in window overlapping LTS+STS/STS-only (4:41 - 6:11)
|
||||||
|
start_time = start.replace(hour=4, minute=41)
|
||||||
|
end_time = start_time + timedelta(minutes=90)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{
|
||||||
|
"change": 90,
|
||||||
|
"min": 4 * 60 + 45,
|
||||||
|
"max": 4 * 60 + 45 + 90,
|
||||||
|
"mean": 4 * 60 + 45 + 45,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4 hours of growth in overlapping LTS-only/LTS+STS (2:01-6:01)
|
||||||
|
start_time = start.replace(hour=2, minute=1)
|
||||||
|
end_time = start_time + timedelta(minutes=240)
|
||||||
|
# 60 from LTS (3:00-3:59), 125 from STS (25 intervals) (4:00-6:01)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 185, "min": 3 * 60, "max": 3 * 60 + 185, "mean": 3 * 60 + 185 / 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4 hours of growth in overlapping LTS-only/LTS+STS (1:31-5:31)
|
||||||
|
start_time = start.replace(hour=1, minute=31)
|
||||||
|
end_time = start_time + timedelta(minutes=240)
|
||||||
|
# 120 from LTS (2:00-3:59), 95 from STS (19 intervals) 4:00-5:31
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 215, "min": 2 * 60, "max": 2 * 60 + 215, "mean": 2 * 60 + 215 / 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5 hours of growth, start time only (1:31-end)
|
||||||
|
start_time = start.replace(hour=1, minute=31)
|
||||||
|
end_time = None
|
||||||
|
# will be actually 2:00 - end
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 4 * 60 + 30, "min": 120, "max": 390, "mean": (390 + 120) / 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5 hours of growth, end_time_only (0:00-5:00)
|
||||||
|
start_time = None
|
||||||
|
end_time = start.replace(hour=5)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 5 * 60, "min": 0, "max": 5 * 60, "mean": (5 * 60) / 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5 hours 1 minute of growth, end_time_only (0:00-5:01)
|
||||||
|
start_time = None
|
||||||
|
end_time = start.replace(hour=5, minute=1)
|
||||||
|
# 4 hours LTS, 1 hour and 5 minutes STS (4:00-5:01)
|
||||||
|
await assert_stat_during_fixed(
|
||||||
|
client,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
{"change": 5 * 60 + 5, "min": 0, "max": 5 * 60 + 5, "mean": (5 * 60 + 5) / 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 7, 25, tzinfo=datetime.UTC))
|
@pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 7, 25, tzinfo=datetime.UTC))
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("calendar_period", "start_time", "end_time"),
|
("calendar_period", "start_time", "end_time"),
|
||||||
|
@ -220,10 +220,14 @@ async def test_incorrectly_formatted_mac_fixed(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("remotews", "rest_api")
|
@pytest.mark.usefixtures("remotews", "rest_api")
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_cleanup_mac(
|
async def test_cleanup_mac(
|
||||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry, snapshot: SnapshotAssertion
|
hass: HomeAssistant, device_registry: dr.DeviceRegistry, snapshot: SnapshotAssertion
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test for `none` mac cleanup #103512."""
|
"""Test for `none` mac cleanup #103512.
|
||||||
|
|
||||||
|
Reverted due to device registry collisions in #119249 / #119082
|
||||||
|
"""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=SAMSUNGTV_DOMAIN,
|
domain=SAMSUNGTV_DOMAIN,
|
||||||
data=MOCK_ENTRY_WS_WITH_MAC,
|
data=MOCK_ENTRY_WS_WITH_MAC,
|
||||||
|
@ -15,12 +15,14 @@ from homeassistant.components.shelly.const import (
|
|||||||
ATTR_CLICK_TYPE,
|
ATTR_CLICK_TYPE,
|
||||||
ATTR_DEVICE,
|
ATTR_DEVICE,
|
||||||
ATTR_GENERATION,
|
ATTR_GENERATION,
|
||||||
|
CONF_BLE_SCANNER_MODE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ENTRY_RELOAD_COOLDOWN,
|
ENTRY_RELOAD_COOLDOWN,
|
||||||
MAX_PUSH_UPDATE_FAILURES,
|
MAX_PUSH_UPDATE_FAILURES,
|
||||||
RPC_RECONNECT_INTERVAL,
|
RPC_RECONNECT_INTERVAL,
|
||||||
SLEEP_PERIOD_MULTIPLIER,
|
SLEEP_PERIOD_MULTIPLIER,
|
||||||
UPDATE_PERIOD_MULTIPLIER,
|
UPDATE_PERIOD_MULTIPLIER,
|
||||||
|
BLEScannerMode,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE
|
from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE
|
||||||
@ -485,6 +487,25 @@ async def test_rpc_reload_with_invalid_auth(
|
|||||||
assert flow["context"].get("entry_id") == entry.entry_id
|
assert flow["context"].get("entry_id") == entry.entry_id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rpc_connection_error_during_unload(
|
||||||
|
hass: HomeAssistant, mock_rpc_device: Mock, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""Test RPC DeviceConnectionError suppressed during config entry unload."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.shelly.coordinator.async_stop_scanner",
|
||||||
|
side_effect=DeviceConnectionError,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "Error during shutdown for device" in caplog.text
|
||||||
|
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_rpc_click_event(
|
async def test_rpc_click_event(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_rpc_device: Mock,
|
mock_rpc_device: Mock,
|
||||||
@ -713,6 +734,32 @@ async def test_rpc_reconnect_error(
|
|||||||
assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE
|
assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rpc_error_running_connected_events(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test RPC error while running connected events."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.shelly.coordinator.async_ensure_ble_enabled",
|
||||||
|
side_effect=DeviceConnectionError,
|
||||||
|
):
|
||||||
|
await init_integration(
|
||||||
|
hass, 2, options={CONF_BLE_SCANNER_MODE: BLEScannerMode.ACTIVE}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Error running connected events for device" in caplog.text
|
||||||
|
assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Move time to generate reconnect without error
|
||||||
|
freezer.tick(timedelta(seconds=RPC_RECONNECT_INTERVAL))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
assert get_entity_state(hass, "switch.test_switch_0") == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
async def test_rpc_polling_connection_error(
|
async def test_rpc_polling_connection_error(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
freezer: FrozenDateTimeFactory,
|
freezer: FrozenDateTimeFactory,
|
||||||
|
@ -13,8 +13,8 @@ from typing import Any
|
|||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect import ProtectApiClient
|
from uiprotect import ProtectApiClient
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
NVR,
|
NVR,
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
Camera,
|
Camera,
|
||||||
|
@ -5,8 +5,8 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from pyunifiprotect.data import Camera, Event, EventType, Light, MountType, Sensor
|
from uiprotect.data import Camera, Event, EventType, Light, MountType, Sensor
|
||||||
from pyunifiprotect.data.nvr import EventMetadata
|
from uiprotect.data.nvr import EventMetadata
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
from homeassistant.components.unifiprotect.binary_sensor import (
|
from homeassistant.components.unifiprotect.binary_sensor import (
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
from pyunifiprotect.data.devices import Camera, Chime, Doorlock
|
from uiprotect.data.devices import Camera, Chime, Doorlock
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform
|
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform
|
||||||
|
@ -4,8 +4,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
from pyunifiprotect.data import Camera as ProtectCamera, CameraChannel, StateType
|
from uiprotect.data import Camera as ProtectCamera, CameraChannel, StateType
|
||||||
from pyunifiprotect.exceptions import NvrError
|
from uiprotect.exceptions import NvrError
|
||||||
|
|
||||||
from homeassistant.components.camera import (
|
from homeassistant.components.camera import (
|
||||||
CameraEntityFeature,
|
CameraEntityFeature,
|
||||||
|
@ -7,8 +7,8 @@ import socket
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
|
from uiprotect import NotAuthorized, NvrError, ProtectApiClient
|
||||||
from pyunifiprotect.data import NVR, Bootstrap, CloudAccount
|
from uiprotect.data import NVR, Bootstrap, CloudAccount
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import dhcp, ssdp
|
from homeassistant.components import dhcp, ssdp
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Test UniFi Protect diagnostics."""
|
"""Test UniFi Protect diagnostics."""
|
||||||
|
|
||||||
from pyunifiprotect.data import NVR, Light
|
from uiprotect.data import NVR, Light
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import CONF_ALLOW_EA
|
from homeassistant.components.unifiprotect.const import CONF_ALLOW_EA
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -4,8 +4,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
|
from uiprotect import NotAuthorized, NvrError, ProtectApiClient
|
||||||
from pyunifiprotect.data import NVR, Bootstrap, CloudAccount, Light
|
from uiprotect.data import NVR, Bootstrap, CloudAccount, Light
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import (
|
from homeassistant.components.unifiprotect.const import (
|
||||||
AUTH_RETRIES,
|
AUTH_RETRIES,
|
||||||
|
@ -4,8 +4,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
from pyunifiprotect.data import Light
|
from uiprotect.data import Light
|
||||||
from pyunifiprotect.data.types import LEDLevel
|
from uiprotect.data.types import LEDLevel
|
||||||
|
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||||
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
from pyunifiprotect.data import Doorlock, LockStatusType
|
from uiprotect.data import Doorlock, LockStatusType
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -5,8 +5,8 @@ from __future__ import annotations
|
|||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import Camera
|
from uiprotect.data import Camera
|
||||||
from pyunifiprotect.exceptions import StreamError
|
from uiprotect.exceptions import StreamError
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
ATTR_MEDIA_CONTENT_TYPE,
|
ATTR_MEDIA_CONTENT_TYPE,
|
||||||
|
@ -5,7 +5,7 @@ from ipaddress import IPv4Address
|
|||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import (
|
from uiprotect.data import (
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
Camera,
|
Camera,
|
||||||
Event,
|
Event,
|
||||||
@ -13,7 +13,7 @@ from pyunifiprotect.data import (
|
|||||||
Permission,
|
Permission,
|
||||||
SmartDetectObjectType,
|
SmartDetectObjectType,
|
||||||
)
|
)
|
||||||
from pyunifiprotect.exceptions import NvrError
|
from uiprotect.exceptions import NvrError
|
||||||
|
|
||||||
from homeassistant.components.media_player import BrowseError, MediaClass
|
from homeassistant.components.media_player import BrowseError, MediaClass
|
||||||
from homeassistant.components.media_source import MediaSourceItem
|
from homeassistant.components.media_source import MediaSourceItem
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from pyunifiprotect.data import Camera
|
from uiprotect.data import Camera
|
||||||
|
|
||||||
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
|
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
|
||||||
from homeassistant.components.repairs.issue_handler import (
|
from homeassistant.components.repairs.issue_handler import (
|
||||||
|
@ -6,7 +6,7 @@ from datetime import timedelta
|
|||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import Camera, Doorlock, IRLEDMode, Light
|
from uiprotect.data import Camera, Doorlock, IRLEDMode, Light
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
||||||
from homeassistant.components.unifiprotect.number import (
|
from homeassistant.components.unifiprotect.number import (
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from pyunifiprotect.data import Camera, Event, EventType
|
from uiprotect.data import Camera, Event, EventType
|
||||||
|
|
||||||
from homeassistant.components.recorder import Recorder
|
from homeassistant.components.recorder import Recorder
|
||||||
from homeassistant.components.recorder.history import get_significant_states
|
from homeassistant.components.recorder.history import get_significant_states
|
||||||
|
@ -6,7 +6,7 @@ from copy import copy, deepcopy
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
from pyunifiprotect.data import Camera, CloudAccount, ModelType, Version
|
from uiprotect.data import Camera, CloudAccount, ModelType, Version
|
||||||
|
|
||||||
from homeassistant.components.repairs.issue_handler import (
|
from homeassistant.components.repairs.issue_handler import (
|
||||||
async_process_repairs_platforms,
|
async_process_repairs_platforms,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user