mirror of
https://github.com/home-assistant/core.git
synced 2025-10-25 11:39:27 +00:00
Compare commits
2 Commits
add-includ
...
drop-ignor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
642ffa45c3 | ||
|
|
1bfac54e56 |
2
.github/workflows/builder.yml
vendored
2
.github/workflows/builder.yml
vendored
@@ -326,7 +326,7 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
with:
|
||||
cosign-release: "v2.2.3"
|
||||
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -689,14 +689,14 @@ jobs:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint --ignore-missing-annotations=y homeassistant
|
||||
pylint homeassistant
|
||||
- name: Run pylint (partially)
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint --ignore-missing-annotations=y homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||
pylint homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||
|
||||
pylint-tests:
|
||||
name: Check pylint on tests
|
||||
|
||||
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -619,8 +619,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/greeneye_monitor/ @jkeljo
|
||||
/homeassistant/components/group/ @home-assistant/core
|
||||
/tests/components/group/ @home-assistant/core
|
||||
/homeassistant/components/growatt_server/ @johanzander
|
||||
/tests/components/growatt_server/ @johanzander
|
||||
/homeassistant/components/guardian/ @bachya
|
||||
/tests/components/guardian/ @bachya
|
||||
/homeassistant/components/habitica/ @tr4nt0r
|
||||
|
||||
10
build.yaml
10
build.yaml
@@ -1,10 +1,10 @@
|
||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.10.1
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.10.1
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.10.1
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.10.1
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.10.1
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.10.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.10.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.10.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.10.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.10.0
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Final, final
|
||||
from typing import Final, final
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
@@ -133,9 +133,9 @@ class AirQualityEntity(Entity):
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, str | int | float]:
|
||||
"""Return the state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
data: dict[str, str | int | float] = {}
|
||||
|
||||
for prop, attr in PROP_TO_ATTR.items():
|
||||
if (value := getattr(self, prop)) is not None:
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"return_average": "air-Q allows to poll both the noisy sensor readings as well as the values averaged on the device (default)",
|
||||
"clip_negatives": "For baseline calibration purposes, certain sensor values may briefly become negative. The default behavior is to clip such values to 0"
|
||||
"clip_negatives": "For baseline calibration purposes, certain sensor values may briefly become negative. The default behaviour is to clip such values to 0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,12 +301,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
data[ATTR_CODE_FORMAT] = self.code_format
|
||||
data[ATTR_CHANGED_BY] = self.changed_by
|
||||
data[ATTR_CODE_ARM_REQUIRED] = self.code_arm_required
|
||||
return data
|
||||
return {
|
||||
ATTR_CODE_FORMAT: self.code_format,
|
||||
ATTR_CHANGED_BY: self.changed_by,
|
||||
ATTR_CODE_ARM_REQUIRED: self.code_arm_required,
|
||||
}
|
||||
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Call when the alarm control panel entity is added to hass."""
|
||||
|
||||
@@ -41,8 +41,6 @@ from .pipeline import (
|
||||
async_setup_pipeline_store,
|
||||
async_update_pipeline,
|
||||
)
|
||||
from .select import AssistPipelineSelect, VadSensitivitySelect
|
||||
from .vad import VadSensitivity
|
||||
from .websocket_api import async_register_websocket_api
|
||||
|
||||
__all__ = (
|
||||
@@ -53,14 +51,11 @@ __all__ = (
|
||||
"SAMPLE_CHANNELS",
|
||||
"SAMPLE_RATE",
|
||||
"SAMPLE_WIDTH",
|
||||
"AssistPipelineSelect",
|
||||
"AudioSettings",
|
||||
"Pipeline",
|
||||
"PipelineEvent",
|
||||
"PipelineEventType",
|
||||
"PipelineNotFound",
|
||||
"VadSensitivity",
|
||||
"VadSensitivitySelect",
|
||||
"WakeWordSettings",
|
||||
"async_create_default_pipeline",
|
||||
"async_get_pipelines",
|
||||
|
||||
@@ -72,16 +72,7 @@ class WrtDevice(NamedTuple):
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type _FuncType[_T] = Callable[
|
||||
[_T],
|
||||
Awaitable[
|
||||
list[str]
|
||||
| tuple[float | None, float | None]
|
||||
| list[float]
|
||||
| dict[str, float | str | None]
|
||||
| dict[str, float]
|
||||
],
|
||||
]
|
||||
type _FuncType[_T] = Callable[[_T], Awaitable[list[Any] | tuple[Any] | dict[str, Any]]]
|
||||
type _ReturnFuncType[_T] = Callable[[_T], Coroutine[Any, Any, dict[str, Any]]]
|
||||
|
||||
|
||||
@@ -96,9 +87,7 @@ def handle_errors_and_zip[_AsusWrtBridgeT: AsusWrtBridge](
|
||||
"""Run library methods and zip results or manage exceptions."""
|
||||
|
||||
@functools.wraps(func)
|
||||
async def _wrapper(
|
||||
self: _AsusWrtBridgeT,
|
||||
) -> dict[str, float | str | None] | dict[str, float]:
|
||||
async def _wrapper(self: _AsusWrtBridgeT) -> dict[str, str]:
|
||||
try:
|
||||
data = await func(self)
|
||||
except exceptions as exc:
|
||||
@@ -324,22 +313,22 @@ class AsusWrtLegacyBridge(AsusWrtBridge):
|
||||
return [SENSORS_TEMPERATURES_LEGACY[i] for i in range(3) if availability[i]]
|
||||
|
||||
@handle_errors_and_zip((IndexError, OSError, ValueError), SENSORS_BYTES)
|
||||
async def _get_bytes(self) -> tuple[float | None, float | None]:
|
||||
async def _get_bytes(self) -> Any:
|
||||
"""Fetch byte information from the router."""
|
||||
return await self._api.async_get_bytes_total()
|
||||
|
||||
@handle_errors_and_zip((IndexError, OSError, ValueError), SENSORS_RATES)
|
||||
async def _get_rates(self) -> tuple[float, float]:
|
||||
async def _get_rates(self) -> Any:
|
||||
"""Fetch rates information from the router."""
|
||||
return await self._api.async_get_current_transfer_rates()
|
||||
|
||||
@handle_errors_and_zip((IndexError, OSError, ValueError), SENSORS_LOAD_AVG)
|
||||
async def _get_load_avg(self) -> list[float]:
|
||||
async def _get_load_avg(self) -> Any:
|
||||
"""Fetch load average information from the router."""
|
||||
return await self._api.async_get_loadavg()
|
||||
|
||||
@handle_errors_and_zip((OSError, ValueError), None)
|
||||
async def _get_temperatures(self) -> dict[str, float]:
|
||||
async def _get_temperatures(self) -> Any:
|
||||
"""Fetch temperatures information from the router."""
|
||||
return await self._api.async_get_temperature()
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
},
|
||||
"state": {
|
||||
"title": "Add a Bayesian sensor",
|
||||
"description": "Add an observation which evaluates to `True` when the value of the sensor exactly matches *'To state'*. When `False`, it will update the prior with probabilities that are the inverse of those set below. This behavior can be overridden by adding observations for the same entity's other states.",
|
||||
"description": "Add an observation which evaluates to `True` when the value of the sensor exactly matches *'To state'*. When `False`, it will update the prior with probabilities that are the inverse of those set below. This behaviour can be overridden by adding observations for the same entity's other states.",
|
||||
|
||||
"data": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
|
||||
@@ -113,6 +113,7 @@ __all__ = [
|
||||
"BluetoothServiceInfo",
|
||||
"BluetoothServiceInfoBleak",
|
||||
"HaBluetoothConnector",
|
||||
"HomeAssistantRemoteScanner",
|
||||
"async_address_present",
|
||||
"async_ble_device_from_address",
|
||||
"async_clear_address_from_match_history",
|
||||
|
||||
@@ -525,18 +525,17 @@ class CalendarEntity(Entity):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the entity state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
if (event := self.event) is None:
|
||||
return data or None
|
||||
return None
|
||||
|
||||
data["message"] = event.summary
|
||||
data["all_day"] = event.all_day
|
||||
data["start_time"] = event.start_datetime_local.strftime(DATE_STR_FORMAT)
|
||||
data["end_time"] = event.end_datetime_local.strftime(DATE_STR_FORMAT)
|
||||
data["location"] = event.location if event.location else ""
|
||||
data["description"] = event.description if event.description else ""
|
||||
return data
|
||||
return {
|
||||
"message": event.summary,
|
||||
"all_day": event.all_day,
|
||||
"start_time": event.start_datetime_local.strftime(DATE_STR_FORMAT),
|
||||
"end_time": event.end_datetime_local.strftime(DATE_STR_FORMAT),
|
||||
"location": event.location if event.location else "",
|
||||
"description": event.description if event.description else "",
|
||||
}
|
||||
|
||||
@final
|
||||
@property
|
||||
|
||||
@@ -74,10 +74,7 @@ from .const import (
|
||||
StreamType,
|
||||
)
|
||||
from .helper import get_camera_from_entity_id
|
||||
from .img_util import (
|
||||
TurboJPEGSingleton, # noqa: F401
|
||||
scale_jpeg_camera_image,
|
||||
)
|
||||
from .img_util import scale_jpeg_camera_image
|
||||
from .prefs import (
|
||||
CameraPreferences,
|
||||
DynamicStreamSettings, # noqa: F401
|
||||
@@ -664,9 +661,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, str | None]:
|
||||
"""Return the camera state attributes."""
|
||||
attrs: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
attrs["access_token"] = self.access_tokens[-1]
|
||||
attrs = {"access_token": self.access_tokens[-1]}
|
||||
|
||||
if model := self.model:
|
||||
attrs["model_name"] = model
|
||||
|
||||
@@ -341,16 +341,16 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the optional state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
supported_features = self.supported_features
|
||||
temperature_unit = self.temperature_unit
|
||||
precision = self.precision
|
||||
hass = self.hass
|
||||
|
||||
data[ATTR_CURRENT_TEMPERATURE] = show_temp(
|
||||
data: dict[str, str | float | None] = {
|
||||
ATTR_CURRENT_TEMPERATURE: show_temp(
|
||||
hass, self.current_temperature, temperature_unit, precision
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE in supported_features:
|
||||
data[ATTR_TEMPERATURE] = show_temp(
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.components.alexa import (
|
||||
errors as alexa_errors,
|
||||
smart_home as alexa_smart_home,
|
||||
)
|
||||
from homeassistant.components.camera import async_register_ice_servers
|
||||
from homeassistant.components.camera.webrtc import async_register_ice_servers
|
||||
from homeassistant.components.google_assistant import smart_home as ga
|
||||
from homeassistant.const import __version__ as HA_VERSION
|
||||
from homeassistant.core import Context, HassJob, HomeAssistant, callback
|
||||
|
||||
@@ -12,9 +12,7 @@ from hass_nabucasa.google_report_state import ErrorResponse
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.google_assistant import DOMAIN as GOOGLE_DOMAIN
|
||||
from homeassistant.components.google_assistant.helpers import ( # pylint: disable=hass-component-root-import
|
||||
AbstractConfig,
|
||||
)
|
||||
from homeassistant.components.google_assistant.helpers import AbstractConfig
|
||||
from homeassistant.components.homeassistant.exposed_entities import (
|
||||
async_expose_entity,
|
||||
async_get_assistant_settings,
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["acme", "hass_nabucasa", "snitun"],
|
||||
"requirements": ["hass-nabucasa==1.4.0"],
|
||||
"requirements": ["hass-nabucasa==1.3.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ from hass_nabucasa.voice import MAP_VOICE, Gender
|
||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.auth.models import User
|
||||
from homeassistant.components import webhook
|
||||
from homeassistant.components.google_assistant.http import ( # pylint: disable=hass-component-root-import
|
||||
from homeassistant.components.google_assistant.http import (
|
||||
async_get_users as async_get_google_assistant_users,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
@@ -6,9 +6,7 @@ from typing import Any
|
||||
import uuid
|
||||
|
||||
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
|
||||
from homeassistant.components.automation.config import ( # pylint: disable=hass-component-root-import
|
||||
async_validate_config_item,
|
||||
)
|
||||
from homeassistant.components.automation.config import async_validate_config_item
|
||||
from homeassistant.config import AUTOMATION_CONFIG_PATH
|
||||
from homeassistant.const import CONF_ID, SERVICE_RELOAD
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
@@ -5,9 +5,7 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN
|
||||
from homeassistant.components.script.config import ( # pylint: disable=hass-component-root-import
|
||||
async_validate_config_item,
|
||||
)
|
||||
from homeassistant.components.script.config import async_validate_config_item
|
||||
from homeassistant.config import SCRIPT_CONFIG_PATH
|
||||
from homeassistant.const import SERVICE_RELOAD
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
@@ -569,13 +569,10 @@ class ChatLog:
|
||||
if llm_api:
|
||||
prompt_parts.append(llm_api.api_prompt)
|
||||
|
||||
# Append current date and time to the prompt if the corresponding tool is not provided
|
||||
llm_tools: list[llm.Tool] = llm_api.tools if llm_api else []
|
||||
if not any(tool.name.endswith("GetDateTime") for tool in llm_tools):
|
||||
prompt_parts.append(
|
||||
await self._async_expand_prompt_template(
|
||||
llm_context,
|
||||
llm.DATE_TIME_PROMPT,
|
||||
llm.BASE_PROMPT,
|
||||
llm_context.language,
|
||||
user_name,
|
||||
)
|
||||
|
||||
@@ -267,7 +267,7 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
data = {}
|
||||
|
||||
if (current := self.current_cover_position) is not None:
|
||||
data[ATTR_CURRENT_POSITION] = current
|
||||
|
||||
@@ -5,9 +5,7 @@ from __future__ import annotations
|
||||
import datetime
|
||||
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
|
||||
from homeassistant.components.manual.alarm_control_panel import ( # pylint: disable=hass-component-root-import
|
||||
ManualAlarm,
|
||||
)
|
||||
from homeassistant.components.manual.alarm_control_panel import ManualAlarm
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ARMING_TIME, CONF_DELAY_TIME, CONF_TRIGGER_TIME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -139,7 +139,6 @@ class DemoCover(CoverEntity):
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
self._is_opening = False
|
||||
self._is_closing = True
|
||||
self._listen_cover()
|
||||
self._requested_closing = True
|
||||
@@ -163,7 +162,6 @@ class DemoCover(CoverEntity):
|
||||
return
|
||||
|
||||
self._is_opening = True
|
||||
self._is_closing = False
|
||||
self._listen_cover()
|
||||
self._requested_closing = False
|
||||
self.async_write_ha_state()
|
||||
@@ -183,14 +181,10 @@ class DemoCover(CoverEntity):
|
||||
if self._position == position:
|
||||
return
|
||||
|
||||
self._is_closing = position < (self._position or 0)
|
||||
self._is_opening = not self._is_closing
|
||||
|
||||
self._listen_cover()
|
||||
self._requested_closing = (
|
||||
self._position is not None and position < self._position
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
||||
"""Move the cover til to a specific position."""
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any, final
|
||||
from typing import final
|
||||
|
||||
from propcache.api import cached_property
|
||||
|
||||
@@ -28,6 +28,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import (
|
||||
@@ -188,11 +189,9 @@ class BaseTrackerEntity(Entity):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, StateType]:
|
||||
"""Return the device state attributes."""
|
||||
attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
attr[ATTR_SOURCE_TYPE] = self.source_type
|
||||
attr: dict[str, StateType] = {ATTR_SOURCE_TYPE: self.source_type}
|
||||
|
||||
if self.battery_level is not None:
|
||||
attr[ATTR_BATTERY_LEVEL] = self.battery_level
|
||||
@@ -279,9 +278,9 @@ class TrackerEntity(
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, StateType]:
|
||||
"""Return the device state attributes."""
|
||||
attr: dict[str, Any] = {}
|
||||
attr: dict[str, StateType] = {}
|
||||
attr.update(super().state_attributes)
|
||||
|
||||
if self.latitude is not None and self.longitude is not None:
|
||||
@@ -432,10 +431,9 @@ class ScannerEntity(
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, StateType]:
|
||||
"""Return the device state attributes."""
|
||||
attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
attr.update(super().state_attributes)
|
||||
attr = super().state_attributes
|
||||
|
||||
if ip_address := self.ip_address:
|
||||
attr[ATTR_IP] = ip_address
|
||||
|
||||
@@ -48,7 +48,7 @@ from homeassistant.helpers.event import (
|
||||
async_track_utc_time_change,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType, GPSType
|
||||
from homeassistant.helpers.typing import ConfigType, GPSType, StateType
|
||||
from homeassistant.setup import (
|
||||
SetupPhases,
|
||||
async_notify_setup_error,
|
||||
@@ -842,11 +842,9 @@ class Device(RestoreEntity):
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, StateType]:
|
||||
"""Return the device state attributes."""
|
||||
attributes: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
attributes[ATTR_SOURCE_TYPE] = self.source_type
|
||||
attributes: dict[str, StateType] = {ATTR_SOURCE_TYPE: self.source_type}
|
||||
|
||||
if self.gps is not None:
|
||||
attributes[ATTR_LATITUDE] = self.gps[0]
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pydoods"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["pydoods==1.0.2", "Pillow==12.0.0"]
|
||||
"requirements": ["pydoods==1.0.2", "Pillow==11.3.0"]
|
||||
}
|
||||
|
||||
@@ -8,11 +8,8 @@ from eheimdigital.classic_vario import EheimDigitalClassicVario
|
||||
from eheimdigital.device import EheimDigitalDevice
|
||||
from eheimdigital.types import FilterErrorCode
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.components.sensor.const import SensorDeviceClass
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -16,9 +16,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sensor.recorder import ( # pylint: disable=hass-component-root-import
|
||||
reset_detected,
|
||||
)
|
||||
from homeassistant.components.sensor.recorder import reset_detected
|
||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, UnitOfEnergy, UnitOfVolume
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
|
||||
@@ -10,8 +10,8 @@ from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sensor.const import SensorStateClass
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -6,7 +6,7 @@ from dataclasses import replace
|
||||
|
||||
from aioesphomeapi import EntityInfo, SelectInfo, SelectState
|
||||
|
||||
from homeassistant.components.assist_pipeline import (
|
||||
from homeassistant.components.assist_pipeline.select import (
|
||||
AssistPipelineSelect,
|
||||
VadSensitivitySelect,
|
||||
)
|
||||
|
||||
@@ -180,9 +180,7 @@ class EventEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
attributes: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
attributes[ATTR_EVENT_TYPE] = self.__last_event_type
|
||||
attributes = {ATTR_EVENT_TYPE: self.__last_event_type}
|
||||
if last_event_attributes := self.__last_event_attributes:
|
||||
attributes |= last_event_attributes
|
||||
return attributes
|
||||
|
||||
@@ -385,10 +385,9 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, float | str | None]:
|
||||
"""Return optional state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
data: dict[str, float | str | None] = {}
|
||||
supported_features = self.supported_features
|
||||
|
||||
if FanEntityFeature.DIRECTION in supported_features:
|
||||
|
||||
@@ -19,9 +19,7 @@ from homeassistant.components.ffmpeg import (
|
||||
FFmpegManager,
|
||||
get_ffmpeg_manager,
|
||||
)
|
||||
from homeassistant.components.ffmpeg_motion.binary_sensor import ( # pylint: disable=hass-component-root-import
|
||||
FFmpegBinarySensor,
|
||||
)
|
||||
from homeassistant.components.ffmpeg_motion.binary_sensor import FFmpegBinarySensor
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
@@ -4,12 +4,8 @@ from __future__ import annotations
|
||||
|
||||
from pyfirefly.models import Account, Category
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
StateType,
|
||||
)
|
||||
from homeassistant.components.sensor import SensorEntity, SensorStateClass, StateType
|
||||
from homeassistant.components.sensor.const import SensorDeviceClass
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -6,8 +6,9 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.camera import CameraEntityFeature
|
||||
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, CONF_INPUT
|
||||
from homeassistant.components.ffmpeg.camera import ( # pylint: disable=hass-component-root-import
|
||||
from homeassistant.components.ffmpeg.camera import (
|
||||
CONF_EXTRA_ARGUMENTS,
|
||||
CONF_INPUT,
|
||||
DEFAULT_ARGUMENTS,
|
||||
FFmpegCamera,
|
||||
)
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/generic",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["av==13.1.0", "Pillow==12.0.0"]
|
||||
"requirements": ["av==13.1.0", "Pillow==11.3.0"]
|
||||
}
|
||||
|
||||
@@ -101,9 +101,7 @@ class GeolocationEvent(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of this external event."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
data[ATTR_SOURCE] = self.source
|
||||
data: dict[str, Any] = {ATTR_SOURCE: self.source}
|
||||
if self.latitude is not None:
|
||||
data[ATTR_LATITUDE] = round(self.latitude, 5)
|
||||
if self.longitude is not None:
|
||||
|
||||
@@ -30,8 +30,8 @@ from homeassistant.components.camera import (
|
||||
WebRTCMessage,
|
||||
WebRTCSendMessage,
|
||||
async_register_webrtc_provider,
|
||||
get_dynamic_camera_stream_settings,
|
||||
)
|
||||
from homeassistant.components.camera.prefs import get_dynamic_camera_stream_settings
|
||||
from homeassistant.components.default_config import DOMAIN as DEFAULT_CONFIG_DOMAIN
|
||||
from homeassistant.components.stream import Orientation
|
||||
from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "growatt_server",
|
||||
"name": "Growatt",
|
||||
"codeowners": ["@johanzander"],
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/growatt_server",
|
||||
"iot_class": "cloud_polling",
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["habiticalib"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["habiticalib==0.4.6"]
|
||||
"requirements": ["habiticalib==0.4.5"]
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
)
|
||||
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
|
||||
from homeassistant.components.device_automation.trigger import ( # pylint: disable=hass-component-root-import
|
||||
from homeassistant.components.device_automation.trigger import (
|
||||
async_validate_trigger_config,
|
||||
)
|
||||
from homeassistant.components.event import DOMAIN as EVENT_DOMAIN, EventDeviceClass
|
||||
|
||||
@@ -188,7 +188,7 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the optional state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
data: dict[str, Any] = {}
|
||||
|
||||
if self.action is not None:
|
||||
data[ATTR_ACTION] = self.action if self.is_on else HumidifierAction.OFF
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioautomower"],
|
||||
"quality_scale": "silver",
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["automower-ble==0.2.8", "gardena-bluetooth==1.6.0"]
|
||||
"requirements": ["automower-ble==0.2.7", "gardena-bluetooth==1.6.0"]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from datetime import datetime, timedelta
|
||||
import logging
|
||||
import os
|
||||
from random import SystemRandom
|
||||
from typing import Any, Final, final
|
||||
from typing import Final, final
|
||||
|
||||
from aiohttp import hdrs, web
|
||||
import httpx
|
||||
@@ -281,12 +281,9 @@ class ImageEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, str | None]:
|
||||
"""Return the state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
data["access_token"] = self.access_tokens[-1]
|
||||
return data
|
||||
return {"access_token": self.access_tokens[-1]}
|
||||
|
||||
@callback
|
||||
def async_update_token(self) -> None:
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/image_upload",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["Pillow==12.0.0"]
|
||||
"requirements": ["Pillow==11.3.0"]
|
||||
}
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
from typing import Any, Final
|
||||
|
||||
from iometer import (
|
||||
IOmeterClient,
|
||||
IOmeterConnectionError,
|
||||
IOmeterNoReadingsError,
|
||||
IOmeterNoStatusError,
|
||||
)
|
||||
from iometer import IOmeterClient, IOmeterConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
@@ -39,11 +34,6 @@ class IOMeterConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
client = IOmeterClient(host=host, session=session)
|
||||
try:
|
||||
status = await client.get_current_status()
|
||||
_ = await client.get_current_reading()
|
||||
except IOmeterNoStatusError:
|
||||
return self.async_abort(reason="no_status")
|
||||
except IOmeterNoReadingsError:
|
||||
return self.async_abort(reason="no_readings")
|
||||
except IOmeterConnectionError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
@@ -80,11 +70,6 @@ class IOMeterConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
client = IOmeterClient(host=self._host, session=session)
|
||||
try:
|
||||
status = await client.get_current_status()
|
||||
_ = await client.get_current_reading()
|
||||
except IOmeterNoStatusError:
|
||||
errors["base"] = "no_status"
|
||||
except IOmeterNoReadingsError:
|
||||
errors["base"] = "no_readings"
|
||||
except IOmeterConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]"
|
||||
},
|
||||
"error": {
|
||||
"no_status": "No status received from the IOmeter. Check your device status in the IOmeter app",
|
||||
"no_readings": "No readings received from the IOmeter. Please attach the IOmeter Core to the electricity meter and wait for the first reading.",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class IPPEntity(CoordinatorEntity[IPPDataUpdateCoordinator]):
|
||||
manufacturer=self.coordinator.data.info.manufacturer,
|
||||
model=self.coordinator.data.info.model,
|
||||
name=self.coordinator.data.info.name,
|
||||
serial_number=self.coordinator.data.info.serial,
|
||||
sw_version=self.coordinator.data.info.version,
|
||||
configuration_url=self.coordinator.data.info.more_info,
|
||||
)
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
"entity_label": "Entity name",
|
||||
"entity_description": "Optional if a device is selected, otherwise required. If the entity is assigned to a device, the device name is used as prefix.",
|
||||
"entity_category_title": "Entity category",
|
||||
"entity_category_description": "Classification of a non-primary entity. Leave empty for standard behavior."
|
||||
"entity_category_description": "Classification of a non-primary entity. Leave empty for standard behaviour."
|
||||
},
|
||||
"knx": {
|
||||
"title": "KNX configuration",
|
||||
|
||||
@@ -1261,8 +1261,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features_compat
|
||||
supported_color_modes = self.supported_color_modes
|
||||
legacy_supported_color_modes = (
|
||||
|
||||
@@ -25,7 +25,7 @@ from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.typing import ConfigType, StateType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import DOMAIN, LockState
|
||||
@@ -244,10 +244,9 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
def state_attributes(self) -> dict[str, StateType]:
|
||||
"""Return the state attributes."""
|
||||
state_attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
state_attr = {}
|
||||
for prop, attr in PROP_TO_ATTR.items():
|
||||
if (value := getattr(self, prop)) is not None:
|
||||
state_attr[attr] = value
|
||||
|
||||
@@ -8,7 +8,7 @@ from pychromecast import Chromecast
|
||||
from pychromecast.const import CAST_TYPE_CHROMECAST
|
||||
|
||||
from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
|
||||
from homeassistant.components.cast.home_assistant_cast import ( # pylint: disable=hass-component-root-import
|
||||
from homeassistant.components.cast.home_assistant_cast import (
|
||||
ATTR_URL_PATH,
|
||||
ATTR_VIEW_PATH,
|
||||
NO_URL_AVAILABLE_ERROR,
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["matrix_client"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["matrix-nio==0.25.2", "Pillow==12.0.0"]
|
||||
"requirements": ["matrix-nio==0.25.2", "Pillow==11.3.0"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiomealie==1.0.1"]
|
||||
"requirements": ["aiomealie==1.0.0"]
|
||||
}
|
||||
|
||||
@@ -1123,7 +1123,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
state_attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
state_attr: dict[str, Any] = {}
|
||||
|
||||
if self.support_grouping:
|
||||
state_attr[ATTR_GROUP_MEMBERS] = self.group_members
|
||||
|
||||
@@ -5,8 +5,9 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from datapoint.Forecast import Forecast
|
||||
from datapoint.Manager import Manager
|
||||
import datapoint
|
||||
import datapoint.Forecast
|
||||
import datapoint.Manager
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -47,19 +48,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
coordinates = f"{latitude}_{longitude}"
|
||||
|
||||
connection = Manager(api_key=api_key)
|
||||
connection = datapoint.Manager.Manager(api_key=api_key)
|
||||
|
||||
async def async_update_hourly() -> Forecast:
|
||||
async def async_update_hourly() -> datapoint.Forecast:
|
||||
return await hass.async_add_executor_job(
|
||||
fetch_data, connection, latitude, longitude, "hourly"
|
||||
)
|
||||
|
||||
async def async_update_daily() -> Forecast:
|
||||
async def async_update_daily() -> datapoint.Forecast:
|
||||
return await hass.async_add_executor_job(
|
||||
fetch_data, connection, latitude, longitude, "daily"
|
||||
)
|
||||
|
||||
async def async_update_twice_daily() -> Forecast:
|
||||
async def async_update_twice_daily() -> datapoint.Forecast:
|
||||
return await hass.async_add_executor_job(
|
||||
fetch_data, connection, latitude, longitude, "twice-daily"
|
||||
)
|
||||
|
||||
@@ -6,8 +6,9 @@ from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import datapoint
|
||||
from datapoint.exceptions import APIException
|
||||
from datapoint.Manager import Manager
|
||||
import datapoint.Manager
|
||||
from requests import HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -30,7 +31,7 @@ async def validate_input(
|
||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
errors = {}
|
||||
connection = Manager(api_key=api_key)
|
||||
connection = datapoint.Manager.Manager(api_key=api_key)
|
||||
|
||||
try:
|
||||
forecast = await hass.async_add_executor_job(
|
||||
|
||||
@@ -5,9 +5,8 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
|
||||
from datapoint.exceptions import APIException
|
||||
import datapoint
|
||||
from datapoint.Forecast import Forecast
|
||||
from datapoint.Manager import Manager
|
||||
from requests import HTTPError
|
||||
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
@@ -17,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def fetch_data(
|
||||
connection: Manager,
|
||||
connection: datapoint.Manager,
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
frequency: Literal["daily", "twice-daily", "hourly"],
|
||||
@@ -27,7 +26,7 @@ def fetch_data(
|
||||
return connection.get_forecast(
|
||||
latitude, longitude, frequency, convert_weather_code=False
|
||||
)
|
||||
except (ValueError, APIException) as err:
|
||||
except (ValueError, datapoint.exceptions.APIException) as err:
|
||||
_LOGGER.error("Check Met Office connection: %s", err.args)
|
||||
raise UpdateFailed from err
|
||||
except HTTPError as err:
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any, cast
|
||||
|
||||
from datapoint.Forecast import Forecast
|
||||
from datapoint.Forecast import Forecast as ForecastData
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_CONDITION,
|
||||
@@ -22,7 +22,7 @@ from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
DOMAIN as WEATHER_DOMAIN,
|
||||
CoordinatorWeatherEntity,
|
||||
Forecast as WeatherForecast,
|
||||
Forecast,
|
||||
WeatherEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -85,20 +85,20 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
def _build_hourly_forecast_data(timestep: dict[str, Any]) -> WeatherForecast:
|
||||
data = WeatherForecast(datetime=timestep["time"].isoformat())
|
||||
def _build_hourly_forecast_data(timestep: dict[str, Any]) -> Forecast:
|
||||
data = Forecast(datetime=timestep["time"].isoformat())
|
||||
_populate_forecast_data(data, timestep, HOURLY_FORECAST_ATTRIBUTE_MAP)
|
||||
return data
|
||||
|
||||
|
||||
def _build_daily_forecast_data(timestep: dict[str, Any]) -> WeatherForecast:
|
||||
data = WeatherForecast(datetime=timestep["time"].isoformat())
|
||||
def _build_daily_forecast_data(timestep: dict[str, Any]) -> Forecast:
|
||||
data = Forecast(datetime=timestep["time"].isoformat())
|
||||
_populate_forecast_data(data, timestep, DAILY_FORECAST_ATTRIBUTE_MAP)
|
||||
return data
|
||||
|
||||
|
||||
def _build_twice_daily_forecast_data(timestep: dict[str, Any]) -> WeatherForecast:
|
||||
data = WeatherForecast(datetime=timestep["time"].isoformat())
|
||||
def _build_twice_daily_forecast_data(timestep: dict[str, Any]) -> Forecast:
|
||||
data = Forecast(datetime=timestep["time"].isoformat())
|
||||
|
||||
# day and night forecasts have slightly different format
|
||||
if "daySignificantWeatherCode" in timestep:
|
||||
@@ -111,7 +111,7 @@ def _build_twice_daily_forecast_data(timestep: dict[str, Any]) -> WeatherForecas
|
||||
|
||||
|
||||
def _populate_forecast_data(
|
||||
forecast: WeatherForecast, timestep: dict[str, Any], mapping: dict[str, str]
|
||||
forecast: Forecast, timestep: dict[str, Any], mapping: dict[str, str]
|
||||
) -> None:
|
||||
def get_mapped_attribute(attr: str) -> Any:
|
||||
if attr not in mapping:
|
||||
@@ -153,9 +153,9 @@ def _populate_forecast_data(
|
||||
|
||||
class MetOfficeWeather(
|
||||
CoordinatorWeatherEntity[
|
||||
TimestampDataUpdateCoordinator[Forecast],
|
||||
TimestampDataUpdateCoordinator[Forecast],
|
||||
TimestampDataUpdateCoordinator[Forecast],
|
||||
TimestampDataUpdateCoordinator[ForecastData],
|
||||
TimestampDataUpdateCoordinator[ForecastData],
|
||||
TimestampDataUpdateCoordinator[ForecastData],
|
||||
]
|
||||
):
|
||||
"""Implementation of a Met Office weather condition."""
|
||||
@@ -177,9 +177,9 @@ class MetOfficeWeather(
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator_daily: TimestampDataUpdateCoordinator[Forecast],
|
||||
coordinator_hourly: TimestampDataUpdateCoordinator[Forecast],
|
||||
coordinator_twice_daily: TimestampDataUpdateCoordinator[Forecast],
|
||||
coordinator_daily: TimestampDataUpdateCoordinator[ForecastData],
|
||||
coordinator_hourly: TimestampDataUpdateCoordinator[ForecastData],
|
||||
coordinator_twice_daily: TimestampDataUpdateCoordinator[ForecastData],
|
||||
hass_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialise the platform with a data instance."""
|
||||
@@ -263,10 +263,10 @@ class MetOfficeWeather(
|
||||
return float(value) if value is not None else None
|
||||
|
||||
@callback
|
||||
def _async_forecast_daily(self) -> list[WeatherForecast] | None:
|
||||
def _async_forecast_daily(self) -> list[Forecast] | None:
|
||||
"""Return the daily forecast in native units."""
|
||||
coordinator = cast(
|
||||
TimestampDataUpdateCoordinator[Forecast],
|
||||
TimestampDataUpdateCoordinator[ForecastData],
|
||||
self.forecast_coordinators["daily"],
|
||||
)
|
||||
timesteps = coordinator.data.timesteps
|
||||
@@ -277,10 +277,10 @@ class MetOfficeWeather(
|
||||
]
|
||||
|
||||
@callback
|
||||
def _async_forecast_hourly(self) -> list[WeatherForecast] | None:
|
||||
def _async_forecast_hourly(self) -> list[Forecast] | None:
|
||||
"""Return the hourly forecast in native units."""
|
||||
coordinator = cast(
|
||||
TimestampDataUpdateCoordinator[Forecast],
|
||||
TimestampDataUpdateCoordinator[ForecastData],
|
||||
self.forecast_coordinators["hourly"],
|
||||
)
|
||||
|
||||
@@ -292,10 +292,10 @@ class MetOfficeWeather(
|
||||
]
|
||||
|
||||
@callback
|
||||
def _async_forecast_twice_daily(self) -> list[WeatherForecast] | None:
|
||||
def _async_forecast_twice_daily(self) -> list[Forecast] | None:
|
||||
"""Return the twice daily forecast in native units."""
|
||||
coordinator = cast(
|
||||
TimestampDataUpdateCoordinator[Forecast],
|
||||
TimestampDataUpdateCoordinator[ForecastData],
|
||||
self.forecast_coordinators["twice_daily"],
|
||||
)
|
||||
timesteps = coordinator.data.timesteps
|
||||
|
||||
@@ -8,7 +8,6 @@ from typing import Any
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .onewirehub import OneWireConfigEntry
|
||||
|
||||
@@ -27,28 +26,7 @@ async def async_get_config_entry_diagnostics(
|
||||
"data": async_redact_data(entry.data, TO_REDACT),
|
||||
"options": {**entry.options},
|
||||
},
|
||||
"devices": [asdict(device_details) for device_details in onewire_hub.devices],
|
||||
}
|
||||
|
||||
|
||||
async def async_get_device_diagnostics(
|
||||
hass: HomeAssistant, entry: OneWireConfigEntry, device_entry: dr.DeviceEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a device."""
|
||||
|
||||
onewire_hub = entry.runtime_data
|
||||
|
||||
return {
|
||||
"entry": {
|
||||
"title": entry.title,
|
||||
"data": async_redact_data(entry.data, TO_REDACT),
|
||||
"options": {**entry.options},
|
||||
},
|
||||
"device": asdict(
|
||||
next(
|
||||
device_details
|
||||
for device_details in onewire_hub.devices
|
||||
if device_details.id[3:] == device_entry.serial_number
|
||||
)
|
||||
),
|
||||
"devices": [asdict(device_details) for device_details in onewire_hub.devices]
|
||||
if onewire_hub.devices
|
||||
else [],
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aio_ownet"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["aio-ownet==0.0.4"],
|
||||
"zeroconf": ["_owserver._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
rules:
|
||||
## Bronze
|
||||
config-flow: done
|
||||
config-flow:
|
||||
status: todo
|
||||
comment: missing data_description on options flow
|
||||
test-before-configure: done
|
||||
unique-config-entry:
|
||||
status: done
|
||||
@@ -14,19 +16,27 @@ rules:
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: entities do not subscribe to events
|
||||
dependency-transparency: done
|
||||
dependency-transparency:
|
||||
status: todo
|
||||
comment: The package is not built and published inside a CI pipeline
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: No service actions currently available
|
||||
common-modules:
|
||||
status: done
|
||||
comment: base entity available, but no coordinator
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-high-level-description:
|
||||
status: todo
|
||||
comment: Under review
|
||||
docs-installation-instructions:
|
||||
status: todo
|
||||
comment: Under review
|
||||
docs-removal-instructions:
|
||||
status: todo
|
||||
comment: Under review
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: No service actions currently available
|
||||
status: todo
|
||||
comment: Under review
|
||||
brands: done
|
||||
|
||||
## Silver
|
||||
@@ -42,8 +52,12 @@ rules:
|
||||
parallel-updates: done
|
||||
test-coverage: done
|
||||
integration-owner: done
|
||||
docs-installation-parameters: done
|
||||
docs-configuration-parameters: done
|
||||
docs-installation-parameters:
|
||||
status: todo
|
||||
comment: Under review
|
||||
docs-configuration-parameters:
|
||||
status: todo
|
||||
comment: Under review
|
||||
|
||||
## Gold
|
||||
entity-translations: done
|
||||
@@ -59,7 +73,9 @@ rules:
|
||||
comment: >
|
||||
Manual removal, as it is not possible to distinguish
|
||||
between a flaky device and a device that has been removed
|
||||
diagnostics: done
|
||||
diagnostics:
|
||||
status: todo
|
||||
comment: config-entry diagnostics level available, might be nice to have device-level diagnostics
|
||||
exception-translations:
|
||||
status: todo
|
||||
comment: Under review
|
||||
|
||||
@@ -139,12 +139,8 @@
|
||||
"step": {
|
||||
"device_selection": {
|
||||
"data": {
|
||||
"clear_device_options": "Reset all device customizations",
|
||||
"device_selection": "Customize specific devices"
|
||||
},
|
||||
"data_description": {
|
||||
"clear_device_options": "Use this to reset all device specific options to default values.",
|
||||
"device_selection": "Customize behavior of individual devices."
|
||||
"clear_device_options": "Clear all device configurations",
|
||||
"device_selection": "[%key:component::onewire::options::error::device_not_selected%]"
|
||||
},
|
||||
"description": "Select what configuration steps to process",
|
||||
"title": "1-Wire device options"
|
||||
@@ -153,9 +149,6 @@
|
||||
"data": {
|
||||
"precision": "Sensor precision"
|
||||
},
|
||||
"data_description": {
|
||||
"precision": "The lower the precision, the faster the sensor will respond, but with less accuracy."
|
||||
},
|
||||
"description": "Select sensor precision for {sensor_id}",
|
||||
"title": "1-Wire sensor precision"
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ async def async_setup_entry(
|
||||
vol.Optional(ATTR_TILT): vol.In([DIR_UP, DIR_DOWN]),
|
||||
vol.Optional(ATTR_ZOOM): vol.In([ZOOM_OUT, ZOOM_IN]),
|
||||
vol.Optional(ATTR_DISTANCE, default=0.1): cv.small_float,
|
||||
vol.Optional(ATTR_SPEED): cv.small_float,
|
||||
vol.Optional(ATTR_SPEED, default=0.5): cv.small_float,
|
||||
vol.Optional(ATTR_MOVE_MODE, default=RELATIVE_MOVE): vol.In(
|
||||
[
|
||||
CONTINUOUS_MOVE,
|
||||
@@ -210,10 +210,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
|
||||
async def async_perform_ptz(
|
||||
self,
|
||||
distance,
|
||||
speed,
|
||||
move_mode,
|
||||
continuous_duration,
|
||||
preset,
|
||||
speed=None,
|
||||
pan=None,
|
||||
tilt=None,
|
||||
zoom=None,
|
||||
|
||||
@@ -602,7 +602,6 @@ class ONVIFDevice:
|
||||
return
|
||||
|
||||
req.PresetToken = preset_val
|
||||
if speed_val is not None:
|
||||
req.Speed = {
|
||||
"PanTilt": {"x": speed_val, "y": speed_val},
|
||||
"Zoom": {"x": speed_val},
|
||||
|
||||
@@ -30,6 +30,7 @@ ptz:
|
||||
max: 1
|
||||
step: 0.01
|
||||
speed:
|
||||
default: 0.5
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
|
||||
@@ -25,13 +25,6 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, host: str, port: int) -> None:
|
||||
"""Validate the user input allows us to connect."""
|
||||
@@ -46,48 +39,6 @@ async def validate_input(hass: HomeAssistant, host: str, port: int) -> None:
|
||||
class OpenRGBConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for OpenRGB."""
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration of the OpenRGB SDK Server."""
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
host = user_input[CONF_HOST]
|
||||
port = user_input[CONF_PORT]
|
||||
|
||||
# Prevent duplicate entries
|
||||
self._async_abort_entries_match({CONF_HOST: host, CONF_PORT: port})
|
||||
|
||||
try:
|
||||
await validate_input(self.hass, host, port)
|
||||
except CONNECTION_ERRORS:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception(
|
||||
"Unknown error while connecting to OpenRGB SDK server at %s",
|
||||
f"{host}:{port}",
|
||||
)
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_update_reload_and_abort(
|
||||
reconfigure_entry,
|
||||
data_updates={CONF_HOST: host, CONF_PORT: port},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
data_schema=STEP_RECONFIGURE_DATA_SCHEMA,
|
||||
suggested_values={
|
||||
CONF_HOST: reconfigure_entry.data[CONF_HOST],
|
||||
CONF_PORT: reconfigure_entry.data[CONF_PORT],
|
||||
},
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
|
||||
@@ -68,7 +68,7 @@ rules:
|
||||
entity-translations: todo
|
||||
exception-translations: done
|
||||
icon-translations: todo
|
||||
reconfiguration-flow: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
stale-devices: todo
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up OpenRGB SDK server",
|
||||
"description": "Set up your OpenRGB SDK server to allow control from within Home Assistant.",
|
||||
"data": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
@@ -14,18 +13,6 @@
|
||||
"host": "The IP address or hostname of the computer running the OpenRGB SDK server.",
|
||||
"port": "The port number that the OpenRGB SDK server is running on."
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"title": "Reconfigure OpenRGB SDK server",
|
||||
"description": "Update the connection settings for your OpenRGB SDK server.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "[%key:component::openrgb::config::step::user::data_description::host%]",
|
||||
"port": "[%key:component::openrgb::config::step::user::data_description::port%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -34,7 +21,6 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["plugwise"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["plugwise==1.8.1"],
|
||||
"requirements": ["plugwise==1.8.0"],
|
||||
"zeroconf": ["_plugwise._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/proxy",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.0.0"]
|
||||
"requirements": ["Pillow==11.3.0"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "calculated",
|
||||
"loggers": ["pyzbar"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.0.0", "pyzbar==0.1.7"]
|
||||
"requirements": ["Pillow==11.3.0", "pyzbar==0.1.7"]
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class RAPTPillConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
title=self._discovered_devices[address], data={}
|
||||
)
|
||||
|
||||
current_addresses = self._async_current_ids(include_ignore=False)
|
||||
current_addresses = self._async_current_ids()
|
||||
for discovery_info in async_discovered_service_info(self.hass, False):
|
||||
address = discovery_info.address
|
||||
if address in current_addresses or address in self._discovered_devices:
|
||||
|
||||
@@ -184,14 +184,13 @@ class RemoteEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return optional state attributes."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
if RemoteEntityFeature.ACTIVITY not in self.supported_features:
|
||||
return data or None
|
||||
return None
|
||||
|
||||
data[ATTR_ACTIVITY_LIST] = self.activity_list
|
||||
data[ATTR_CURRENT_ACTIVITY] = self.current_activity
|
||||
return data
|
||||
return {
|
||||
ATTR_ACTIVITY_LIST: self.activity_list,
|
||||
ATTR_CURRENT_ACTIVITY: self.current_activity,
|
||||
}
|
||||
|
||||
def send_command(self, command: Iterable[str], **kwargs: Any) -> None:
|
||||
"""Send commands to a device."""
|
||||
|
||||
@@ -437,8 +437,6 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@override
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return state attributes."""
|
||||
state_attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
if last_reset := self.last_reset:
|
||||
state_class = self.state_class
|
||||
if state_class != SensorStateClass.TOTAL:
|
||||
@@ -450,9 +448,9 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
)
|
||||
|
||||
if state_class == SensorStateClass.TOTAL:
|
||||
state_attr[ATTR_LAST_RESET] = last_reset.isoformat()
|
||||
return {ATTR_LAST_RESET: last_reset.isoformat()}
|
||||
|
||||
return state_attr or None
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
def native_value(self) -> StateType | date | datetime | Decimal:
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/seven_segments",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.0.0"]
|
||||
"requirements": ["Pillow==11.3.0"]
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ from .coordinator import (
|
||||
)
|
||||
from .repairs import (
|
||||
async_manage_ble_scanner_firmware_unsupported_issue,
|
||||
async_manage_deprecated_firmware_issue,
|
||||
async_manage_outbound_websocket_incorrectly_enabled_issue,
|
||||
async_manage_wall_display_firmware_unsupported_issue,
|
||||
)
|
||||
from .utils import (
|
||||
async_create_issue_unsupported_firmware,
|
||||
@@ -337,7 +337,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry)
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
entry, runtime_data.platforms
|
||||
)
|
||||
async_manage_deprecated_firmware_issue(hass, entry)
|
||||
async_manage_wall_display_firmware_unsupported_issue(hass, entry)
|
||||
async_manage_ble_scanner_firmware_unsupported_issue(
|
||||
hass,
|
||||
entry,
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from enum import StrEnum
|
||||
from logging import Logger, getLogger
|
||||
import re
|
||||
from typing import Final, TypedDict
|
||||
from typing import Final
|
||||
|
||||
from aioshelly.const import (
|
||||
MODEL_BULB,
|
||||
@@ -232,6 +232,7 @@ class BLEScannerMode(StrEnum):
|
||||
|
||||
|
||||
BLE_SCANNER_MIN_FIRMWARE = "1.5.1"
|
||||
WALL_DISPLAY_MIN_FIRMWARE = "2.3.0"
|
||||
|
||||
MAX_PUSH_UPDATE_FAILURES = 5
|
||||
PUSH_UPDATE_ISSUE_ID = "push_update_{unique}"
|
||||
@@ -244,28 +245,9 @@ BLE_SCANNER_FIRMWARE_UNSUPPORTED_ISSUE_ID = "ble_scanner_firmware_unsupported_{u
|
||||
OUTBOUND_WEBSOCKET_INCORRECTLY_ENABLED_ISSUE_ID = (
|
||||
"outbound_websocket_incorrectly_enabled_{unique}"
|
||||
)
|
||||
DEPRECATED_FIRMWARE_ISSUE_ID = "deprecated_firmware_{unique}"
|
||||
|
||||
|
||||
class DeprecatedFirmwareInfo(TypedDict):
|
||||
"""TypedDict for Deprecated Firmware Info."""
|
||||
|
||||
min_firmware: str
|
||||
ha_version: str
|
||||
|
||||
|
||||
# Provide firmware deprecation data:
|
||||
# key: device model
|
||||
# value: dict with:
|
||||
# min_firmware: minimum supported firmware version
|
||||
# ha_version: Home Assistant version when older firmware will be deprecated
|
||||
# Example:
|
||||
# DEPRECATED_FIRMWARES: dict[str, DeprecatedFirmwareInfo] = {
|
||||
# MODEL_WALL_DISPLAY: DeprecatedFirmwareInfo(
|
||||
# {"min_firmware": "2.3.0", "ha_version": "2025.10.0"}
|
||||
# ),
|
||||
# }
|
||||
DEPRECATED_FIRMWARES: dict[str, DeprecatedFirmwareInfo] = {}
|
||||
WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID = (
|
||||
"wall_display_firmware_unsupported_{unique}"
|
||||
)
|
||||
|
||||
GAS_VALVE_OPEN_STATES = ("opening", "opened")
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aioshelly.const import MODEL_OUT_PLUG_S_G3, MODEL_PLUG_S_G3
|
||||
from aioshelly.const import MODEL_OUT_PLUG_S_G3, MODEL_PLUG_S_G3, MODEL_WALL_DISPLAY
|
||||
from aioshelly.exceptions import DeviceConnectionError, RpcCallError
|
||||
from aioshelly.rpc_device import RpcDevice
|
||||
from awesomeversion import AwesomeVersion
|
||||
@@ -19,10 +19,10 @@ from .const import (
|
||||
BLE_SCANNER_FIRMWARE_UNSUPPORTED_ISSUE_ID,
|
||||
BLE_SCANNER_MIN_FIRMWARE,
|
||||
CONF_BLE_SCANNER_MODE,
|
||||
DEPRECATED_FIRMWARE_ISSUE_ID,
|
||||
DEPRECATED_FIRMWARES,
|
||||
DOMAIN,
|
||||
OUTBOUND_WEBSOCKET_INCORRECTLY_ENABLED_ISSUE_ID,
|
||||
WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID,
|
||||
WALL_DISPLAY_MIN_FIRMWARE,
|
||||
BLEScannerMode,
|
||||
)
|
||||
from .coordinator import ShellyConfigEntry
|
||||
@@ -70,25 +70,21 @@ def async_manage_ble_scanner_firmware_unsupported_issue(
|
||||
|
||||
|
||||
@callback
|
||||
def async_manage_deprecated_firmware_issue(
|
||||
def async_manage_wall_display_firmware_unsupported_issue(
|
||||
hass: HomeAssistant,
|
||||
entry: ShellyConfigEntry,
|
||||
) -> None:
|
||||
"""Manage deprecated firmware issue."""
|
||||
issue_id = DEPRECATED_FIRMWARE_ISSUE_ID.format(unique=entry.unique_id)
|
||||
"""Manage the Wall Display firmware unsupported issue."""
|
||||
issue_id = WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert entry.runtime_data.rpc is not None
|
||||
|
||||
device = entry.runtime_data.rpc.device
|
||||
model = entry.data["model"]
|
||||
|
||||
if model in DEPRECATED_FIRMWARES:
|
||||
min_firmware = DEPRECATED_FIRMWARES[model]["min_firmware"]
|
||||
ha_version = DEPRECATED_FIRMWARES[model]["ha_version"]
|
||||
|
||||
if entry.data["model"] == MODEL_WALL_DISPLAY:
|
||||
firmware = AwesomeVersion(device.shelly["ver"])
|
||||
if firmware < min_firmware:
|
||||
if firmware < WALL_DISPLAY_MIN_FIRMWARE:
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
@@ -96,12 +92,11 @@ def async_manage_deprecated_firmware_issue(
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="deprecated_firmware",
|
||||
translation_key="wall_display_firmware_unsupported",
|
||||
translation_placeholders={
|
||||
"device_name": device.name,
|
||||
"ip_address": device.ip_address,
|
||||
"firmware": firmware,
|
||||
"ha_version": ha_version,
|
||||
},
|
||||
data={"entry_id": entry.entry_id},
|
||||
)
|
||||
@@ -246,7 +241,7 @@ async def async_create_fix_flow(
|
||||
|
||||
if (
|
||||
"ble_scanner_firmware_unsupported" in issue_id
|
||||
or "deprecated_firmware" in issue_id
|
||||
or "wall_display_firmware_unsupported" in issue_id
|
||||
):
|
||||
return FirmwareUpdateFlow(device)
|
||||
|
||||
|
||||
@@ -312,13 +312,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated_firmware": {
|
||||
"wall_display_firmware_unsupported": {
|
||||
"title": "{device_name} is running outdated firmware",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "{device_name} is running outdated firmware",
|
||||
"description": "Your Shelly device {device_name} with IP address {ip_address} is running firmware {firmware}. This firmware version will not be supported by Shelly integration starting from Home Assistant {ha_version}.\n\nSelect **Submit** button to start the OTA update to the latest stable firmware version."
|
||||
"description": "Your Shelly device {device_name} with IP address {ip_address} is running firmware {firmware}. This firmware version will not be supported by Shelly integration starting from Home Assistant 2025.11.0.\n\nSelect **Submit** button to start the OTA update to the latest stable firmware version."
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["simplehound"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.0.0", "simplehound==0.3"]
|
||||
"requirements": ["Pillow==11.3.0", "simplehound==0.3"]
|
||||
}
|
||||
|
||||
@@ -39,9 +39,7 @@ from homeassistant.components.media_player import (
|
||||
async_process_play_media_url,
|
||||
)
|
||||
from homeassistant.components.plex import PLEX_URI_SCHEME
|
||||
from homeassistant.components.plex.services import ( # pylint: disable=hass-component-root-import
|
||||
process_plex_payload,
|
||||
)
|
||||
from homeassistant.components.plex.services import process_plex_payload
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
@@ -441,7 +441,9 @@ class KeyFrameConverter:
|
||||
|
||||
# Keep import here so that we can import stream integration
|
||||
# without installing reqs
|
||||
from homeassistant.components.camera import TurboJPEGSingleton # noqa: PLC0415
|
||||
from homeassistant.components.camera.img_util import ( # noqa: PLC0415
|
||||
TurboJPEGSingleton,
|
||||
)
|
||||
|
||||
self._packet: Packet | None = None
|
||||
self._event: asyncio.Event = asyncio.Event()
|
||||
|
||||
@@ -7,7 +7,7 @@ import io
|
||||
import logging
|
||||
from ssl import SSLContext
|
||||
from types import MappingProxyType
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from telegram import (
|
||||
@@ -23,7 +23,6 @@ from telegram import (
|
||||
InputMediaVideo,
|
||||
InputPollOption,
|
||||
Message,
|
||||
PhotoSize,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
Update,
|
||||
@@ -57,10 +56,6 @@ from .const import (
|
||||
ATTR_DISABLE_NOTIF,
|
||||
ATTR_DISABLE_WEB_PREV,
|
||||
ATTR_FILE,
|
||||
ATTR_FILE_ID,
|
||||
ATTR_FILE_MIME_TYPE,
|
||||
ATTR_FILE_NAME,
|
||||
ATTR_FILE_SIZE,
|
||||
ATTR_FROM_FIRST,
|
||||
ATTR_FROM_LAST,
|
||||
ATTR_INLINE_MESSAGE_ID,
|
||||
@@ -91,7 +86,6 @@ from .const import (
|
||||
CONF_CHAT_ID,
|
||||
CONF_PROXY_URL,
|
||||
DOMAIN,
|
||||
EVENT_TELEGRAM_ATTACHMENT,
|
||||
EVENT_TELEGRAM_CALLBACK,
|
||||
EVENT_TELEGRAM_COMMAND,
|
||||
EVENT_TELEGRAM_SENT,
|
||||
@@ -189,10 +183,6 @@ class BaseTelegramBot:
|
||||
# This is a command message - set event type to command and split data into command and args
|
||||
event_type = EVENT_TELEGRAM_COMMAND
|
||||
event_data.update(self._get_command_event_data(message.text))
|
||||
elif filters.ATTACHMENT.filter(message):
|
||||
event_type = EVENT_TELEGRAM_ATTACHMENT
|
||||
event_data[ATTR_TEXT] = message.caption
|
||||
event_data.update(self._get_file_id_event_data(message))
|
||||
else:
|
||||
event_type = EVENT_TELEGRAM_TEXT
|
||||
event_data[ATTR_TEXT] = message.text
|
||||
@@ -202,26 +192,6 @@ class BaseTelegramBot:
|
||||
|
||||
return event_type, event_data
|
||||
|
||||
def _get_file_id_event_data(self, message: Message) -> dict[str, Any]:
|
||||
"""Extract file_id from a message attachment, if any."""
|
||||
if filters.PHOTO.filter(message):
|
||||
photos = cast(Sequence[PhotoSize], message.effective_attachment)
|
||||
return {
|
||||
ATTR_FILE_ID: photos[-1].file_id,
|
||||
ATTR_FILE_MIME_TYPE: "image/jpeg", # telegram always uses jpeg for photos
|
||||
ATTR_FILE_SIZE: photos[-1].file_size,
|
||||
}
|
||||
return {
|
||||
k: getattr(message.effective_attachment, v)
|
||||
for k, v in (
|
||||
(ATTR_FILE_ID, "file_id"),
|
||||
(ATTR_FILE_NAME, "file_name"),
|
||||
(ATTR_FILE_MIME_TYPE, "mime_type"),
|
||||
(ATTR_FILE_SIZE, "file_size"),
|
||||
)
|
||||
if hasattr(message.effective_attachment, v)
|
||||
}
|
||||
|
||||
def _get_user_event_data(self, user: User) -> dict[str, Any]:
|
||||
return {
|
||||
ATTR_USER_ID: user.id,
|
||||
@@ -578,7 +548,6 @@ class TelegramNotificationService:
|
||||
"Error sending message",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
text,
|
||||
target=target,
|
||||
parse_mode=params[ATTR_PARSER],
|
||||
disable_web_page_preview=params[ATTR_DISABLE_WEB_PREV],
|
||||
disable_notification=params[ATTR_DISABLE_NOTIF],
|
||||
|
||||
@@ -54,7 +54,6 @@ SERVICE_LEAVE_CHAT = "leave_chat"
|
||||
EVENT_TELEGRAM_CALLBACK = "telegram_callback"
|
||||
EVENT_TELEGRAM_COMMAND = "telegram_command"
|
||||
EVENT_TELEGRAM_TEXT = "telegram_text"
|
||||
EVENT_TELEGRAM_ATTACHMENT = "telegram_attachment"
|
||||
EVENT_TELEGRAM_SENT = "telegram_sent"
|
||||
|
||||
PARSER_HTML = "html"
|
||||
@@ -91,10 +90,6 @@ ATTR_DISABLE_NOTIF = "disable_notification"
|
||||
ATTR_DISABLE_WEB_PREV = "disable_web_page_preview"
|
||||
ATTR_EDITED_MSG = "edited_message"
|
||||
ATTR_FILE = "file"
|
||||
ATTR_FILE_ID = "file_id"
|
||||
ATTR_FILE_MIME_TYPE = "file_mime_type"
|
||||
ATTR_FILE_NAME = "file_name"
|
||||
ATTR_FILE_SIZE = "file_size"
|
||||
ATTR_FROM_FIRST = "from_first"
|
||||
ATTR_FROM_LAST = "from_last"
|
||||
ATTR_KEYBOARD = "keyboard"
|
||||
|
||||
@@ -21,9 +21,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sensor.helpers import ( # pylint: disable=hass-component-root-import
|
||||
async_parse_date_datetime,
|
||||
)
|
||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"tf-models-official==2.5.0",
|
||||
"pycocotools==2.0.6",
|
||||
"numpy==2.3.2",
|
||||
"Pillow==12.0.0"
|
||||
"Pillow==11.3.0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -433,8 +433,6 @@ class UpdateEntity(
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return state attributes."""
|
||||
state_attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
if (release_summary := self.release_summary) is not None:
|
||||
release_summary = release_summary[:255]
|
||||
|
||||
@@ -461,17 +459,18 @@ class UpdateEntity(
|
||||
skipped_version = None
|
||||
self.__skipped_version = None
|
||||
|
||||
state_attr[ATTR_AUTO_UPDATE] = self.auto_update
|
||||
state_attr[ATTR_DISPLAY_PRECISION] = self.display_precision
|
||||
state_attr[ATTR_INSTALLED_VERSION] = installed_version
|
||||
state_attr[ATTR_IN_PROGRESS] = in_progress
|
||||
state_attr[ATTR_LATEST_VERSION] = latest_version
|
||||
state_attr[ATTR_RELEASE_SUMMARY] = release_summary
|
||||
state_attr[ATTR_RELEASE_URL] = self.release_url
|
||||
state_attr[ATTR_SKIPPED_VERSION] = skipped_version
|
||||
state_attr[ATTR_TITLE] = self.title
|
||||
state_attr[ATTR_UPDATE_PERCENTAGE] = update_percentage
|
||||
return state_attr
|
||||
return {
|
||||
ATTR_AUTO_UPDATE: self.auto_update,
|
||||
ATTR_DISPLAY_PRECISION: self.display_precision,
|
||||
ATTR_INSTALLED_VERSION: installed_version,
|
||||
ATTR_IN_PROGRESS: in_progress,
|
||||
ATTR_LATEST_VERSION: latest_version,
|
||||
ATTR_RELEASE_SUMMARY: release_summary,
|
||||
ATTR_RELEASE_URL: self.release_url,
|
||||
ATTR_SKIPPED_VERSION: skipped_version,
|
||||
ATTR_TITLE: self.title,
|
||||
ATTR_UPDATE_PERCENTAGE: update_percentage,
|
||||
}
|
||||
|
||||
@final
|
||||
async def async_install_with_progress(
|
||||
|
||||
@@ -20,9 +20,7 @@ from homeassistant.components.sensor import (
|
||||
SensorExtraStoredData,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sensor.recorder import ( # pylint: disable=hass-component-root-import
|
||||
_suggest_report_issue,
|
||||
)
|
||||
from homeassistant.components.sensor.recorder import _suggest_report_issue
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
|
||||
@@ -364,12 +364,10 @@ class StateVacuumEntity(
|
||||
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
||||
return self._attr_fan_speed_list
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the vacuum cleaner."""
|
||||
data: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
|
||||
if VacuumEntityFeature.BATTERY in supported_features:
|
||||
|
||||
@@ -191,11 +191,9 @@ class ValveEntity(Entity):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the state attributes."""
|
||||
state_attr: dict[str, Any] = self.generate_entity_state_attributes()
|
||||
|
||||
if self.reports_position:
|
||||
state_attr[ATTR_CURRENT_POSITION] = self.current_valve_position
|
||||
return state_attr or None
|
||||
if not self.reports_position:
|
||||
return None
|
||||
return {ATTR_CURRENT_POSITION: self.current_valve_position}
|
||||
|
||||
@property
|
||||
def supported_features(self) -> ValveEntityFeature:
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/vesync",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyvesync"],
|
||||
"requirements": ["pyvesync==3.1.2"]
|
||||
"requirements": ["pyvesync==3.1.0"]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -118,32 +117,6 @@ GLOBAL_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = (
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
value_getter=lambda api: len(api.getDeviceErrors()) > 0,
|
||||
),
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key="identification_mode",
|
||||
translation_key="identification_mode",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_getter=lambda api: api.getIdentification(),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key="mounting_mode",
|
||||
translation_key="mounting_mode",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_getter=lambda api: api.getMountingMode(),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key="child_safety_lock_mode",
|
||||
translation_key="child_safety_lock_mode",
|
||||
value_getter=lambda api: api.getChildLock() == "active",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ViCareBinarySensorEntityDescription(
|
||||
key="valve",
|
||||
translation_key="valve",
|
||||
device_class=BinarySensorDeviceClass.DOOR,
|
||||
value_getter=lambda api: api.isValveOpen(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -21,15 +21,6 @@
|
||||
},
|
||||
"one_time_charge": {
|
||||
"default": "mdi:shower-head"
|
||||
},
|
||||
"mounting_mode": {
|
||||
"default": "mdi:wrench"
|
||||
},
|
||||
"child_safety_lock_mode": {
|
||||
"default": "mdi:lock"
|
||||
},
|
||||
"valve": {
|
||||
"default": "mdi:pipe-valve"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
@@ -99,12 +90,6 @@
|
||||
},
|
||||
"ventilation_level": {
|
||||
"default": "mdi:fan"
|
||||
},
|
||||
"zigbee_signal_strength": {
|
||||
"default": "mdi:wifi"
|
||||
},
|
||||
"valve_position": {
|
||||
"default": "mdi:pipe-valve"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -943,23 +943,6 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_getter=lambda api: api.getBatteryLevel(),
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key="zigbee_signal_strength",
|
||||
translation_key="zigbee_signal_strength",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_getter=lambda api: api.getZigbeeSignalStrength(),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key="valve_position",
|
||||
translation_key="valve_position",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_getter=lambda api: api.getValvePosition(),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ViCareSensorEntityDescription(
|
||||
key="fuel_need",
|
||||
translation_key="fuel_need",
|
||||
|
||||
@@ -66,18 +66,6 @@
|
||||
},
|
||||
"one_time_charge": {
|
||||
"name": "One-time charge"
|
||||
},
|
||||
"identification_mode": {
|
||||
"name": "Identification mode"
|
||||
},
|
||||
"mounting_mode": {
|
||||
"name": "Mounting mode"
|
||||
},
|
||||
"child_safety_lock_mode": {
|
||||
"name": "Child safety lock"
|
||||
},
|
||||
"valve": {
|
||||
"name": "Valve"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
@@ -514,12 +502,6 @@
|
||||
},
|
||||
"fuel_need": {
|
||||
"name": "Fuel need"
|
||||
},
|
||||
"zigbee_signal_strength": {
|
||||
"name": "[%key:component::sensor::entity_component::signal_strength::name%]"
|
||||
},
|
||||
"valve_position": {
|
||||
"name": "Valve position"
|
||||
}
|
||||
},
|
||||
"water_heater": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.assist_pipeline.repair_flows import ( # pylint: disable=hass-component-root-import
|
||||
from homeassistant.components.assist_pipeline.repair_flows import (
|
||||
AssistInProgressDeprecatedRepairFlow,
|
||||
)
|
||||
from homeassistant.components.repairs import RepairsFlow
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components.assist_pipeline import (
|
||||
from homeassistant.components.assist_pipeline.select import (
|
||||
AssistPipelineSelect,
|
||||
VadSensitivitySelect,
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user