forked from home-assistant/core
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ac944c537 | |||
| 7d3dd2dd6b | |||
| 48538ef5d5 | |||
| 5365439fd4 | |||
| 9c28a4e8a0 | |||
| 565203047c | |||
| b9795a2ae7 | |||
| 4e4f8ee3a4 | |||
| b8fd921c81 | |||
| fcf91954ff | |||
| 49708196ac | |||
| 8c8a2eef21 | |||
| 749a5b37c9 | |||
| 60079a14e7 | |||
| df6edd09c0 | |||
| 88ff94dd69 | |||
| 067b81a60b | |||
| 03553b8bb9 | |||
| bce7552d4d | |||
| 53a2777831 | |||
| 507492947a | |||
| 41b3eb9f79 | |||
| f2c746122e | |||
| e25a54aef4 | |||
| 5c42e45048 | |||
| c8b92bc858 | |||
| 9d059fcfaa | |||
| ce5f193219 | |||
| 92023ecbe6 | |||
| 1e0164a96a | |||
| 6f5eac3143 | |||
| 60dfccb747 | |||
| e9dc09755e | |||
| 1ce2b18aaf | |||
| abd351e326 | |||
| d3e6069095 | |||
| f0c3900842 | |||
| 25247de6a6 | |||
| f3a72dda7b | |||
| b6af6ddea2 | |||
| a2cd17ef0a | |||
| b8ed449944 | |||
| dc79299301 | |||
| fa295b93a7 | |||
| 725c361e9c | |||
| a8f25b1b93 | |||
| 3ee85b3356 | |||
| 22c85bf5f7 | |||
| 0a18838fb0 | |||
| 62629a0b34 | |||
| b42848fd7a | |||
| 9070806172 | |||
| 4e11797d72 | |||
| 8f47b63762 | |||
| daa13235e6 | |||
| 084c2d976e | |||
| 75363b609b | |||
| 8d09982f3b | |||
| aa5e8eaf19 | |||
| fc97eb8151 | |||
| a68d7c9b9d | |||
| 3bb13f76fa | |||
| f57ce96ff0 | |||
| 105d7952fc | |||
| 6f4a488308 | |||
| 23a11dddb3 | |||
| 28aff1a90a | |||
| 9a56381e28 | |||
| 2d1708e5e8 | |||
| 73deb076fe | |||
| c4f189863c | |||
| 02e15a4ce7 | |||
| ba8e9bc168 | |||
| 46d3bda80a | |||
| 222006d106 | |||
| 7925aee91f | |||
| 4e3b012f3e | |||
| b606b50cec | |||
| 57028a0807 | |||
| 840cc483b0 | |||
| 8d1f944096 | |||
| a45c4ec8e9 | |||
| 0f3f50e817 | |||
| a4ff292231 | |||
| e8636670d4 | |||
| bae6d679aa | |||
| a3e3edb9a2 | |||
| e66dd63516 | |||
| ec66c7e534 | |||
| 2749b1f057 | |||
| b079a94bef | |||
| 3d1bd626b0 | |||
| 60641d5a4e | |||
| 28d491e997 | |||
| bb73529770 | |||
| ebfd442b51 | |||
| fdd8c0969b | |||
| bdc548b464 | |||
| b60e6082f7 | |||
| 9a7254e4ee | |||
| 73e56e292a | |||
| 42a4a89793 | |||
| bb7803b020 | |||
| dd0fc0688d | |||
| 1380ed7328 | |||
| 9d6569d515 | |||
| 20be8fd2d3 | |||
| 9d48c77861 | |||
| 7ab93a70dc | |||
| a435095e76 | |||
| eb763563f2 | |||
| 9bf0b5bff1 | |||
| 638dd37545 | |||
| 11cc718273 | |||
| cf6b07630b | |||
| 17e0db9da3 | |||
| 53cf8628fa |
@@ -27,7 +27,7 @@ jobs:
|
||||
publish: ${{ steps.version.outputs.publish }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download nightly wheels of frontend
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
- green
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Set build additional args
|
||||
run: |
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Initialize git
|
||||
uses: home-assistant/actions/helpers/git-init@master
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.6.0
|
||||
@@ -451,7 +451,7 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -499,7 +499,7 @@ jobs:
|
||||
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
|
||||
+21
-21
@@ -40,7 +40,7 @@ env:
|
||||
CACHE_VERSION: 10
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 9
|
||||
HA_SHORT_VERSION: "2024.11"
|
||||
HA_SHORT_VERSION: "2024.10"
|
||||
DEFAULT_PYTHON: "3.12"
|
||||
ALL_PYTHON_VERSIONS: "['3.12']"
|
||||
# 10.3 is the oldest supported version
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Generate partial Python venv restore key
|
||||
id: generate_python_cache_key
|
||||
run: |
|
||||
@@ -231,7 +231,7 @@ jobs:
|
||||
- info
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -277,7 +277,7 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
id: python
|
||||
@@ -317,7 +317,7 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
id: python
|
||||
@@ -357,7 +357,7 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
id: python
|
||||
@@ -447,7 +447,7 @@ jobs:
|
||||
- script/hassfest/docker/Dockerfile
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Register hadolint problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
||||
@@ -466,7 +466,7 @@ jobs:
|
||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -550,7 +550,7 @@ jobs:
|
||||
sudo apt-get -y install \
|
||||
libturbojpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -583,7 +583,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -617,7 +617,7 @@ jobs:
|
||||
&& needs.info.outputs.requirements == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -660,7 +660,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -707,7 +707,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -752,7 +752,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -827,7 +827,7 @@ jobs:
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -891,7 +891,7 @@ jobs:
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -1011,7 +1011,7 @@ jobs:
|
||||
libturbojpeg \
|
||||
libmariadb-dev-compat
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -1137,7 +1137,7 @@ jobs:
|
||||
libturbojpeg \
|
||||
postgresql-server-dev-14
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -1232,7 +1232,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
@@ -1283,7 +1283,7 @@ jobs:
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
@@ -1370,7 +1370,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.26.9
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
architectures: ${{ steps.info.outputs.architectures }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
|
||||
@@ -4,8 +4,10 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from jaraco.abode.client import Client as Abode
|
||||
import jaraco.abode.config
|
||||
from jaraco.abode.exceptions import (
|
||||
AuthenticationException as AbodeAuthenticationException,
|
||||
Exception as AbodeException,
|
||||
@@ -93,6 +95,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
polling = entry.data[CONF_POLLING]
|
||||
|
||||
# Configure abode library to use config directory for storing data
|
||||
jaraco.abode.config.paths.override(user_data=Path(hass.config.path("Abode")))
|
||||
|
||||
# For previous config entries where unique_id is None
|
||||
if entry.unique_id is None:
|
||||
hass.config_entries.async_update_entry(
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
},
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["jaraco.abode", "lomond"],
|
||||
"requirements": ["jaraco.abode==6.2.0"]
|
||||
"requirements": ["jaraco.abode==6.2.1"]
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ from typing import TYPE_CHECKING
|
||||
from airgradient import AirGradientClient, AirGradientError, Config, Measures
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import LOGGER
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import AirGradientConfigEntry
|
||||
@@ -29,6 +30,7 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
|
||||
"""Class to manage fetching AirGradient data."""
|
||||
|
||||
config_entry: AirGradientConfigEntry
|
||||
_current_version: str
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: AirGradientClient) -> None:
|
||||
"""Initialize coordinator."""
|
||||
@@ -42,11 +44,27 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
|
||||
assert self.config_entry.unique_id
|
||||
self.serial_number = self.config_entry.unique_id
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
self._current_version = (
|
||||
await self.client.get_current_measures()
|
||||
).firmware_version
|
||||
|
||||
async def _async_update_data(self) -> AirGradientData:
|
||||
try:
|
||||
measures = await self.client.get_current_measures()
|
||||
config = await self.client.get_config()
|
||||
except AirGradientError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
else:
|
||||
return AirGradientData(measures, config)
|
||||
if measures.firmware_version != self._current_version:
|
||||
device_registry = dr.async_get(self.hass)
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, self.serial_number)}
|
||||
)
|
||||
assert device_entry
|
||||
device_registry.async_update_device(
|
||||
device_entry.id,
|
||||
sw_version=measures.firmware_version,
|
||||
)
|
||||
self._current_version = measures.firmware_version
|
||||
return AirGradientData(measures, config)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Diagnostics support for Airgradient."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import AirGradientConfigEntry
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: AirGradientConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
|
||||
return asdict(entry.runtime_data.data)
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"select": {
|
||||
"pipeline": {
|
||||
"name": "Assist pipeline",
|
||||
"name": "Assistant",
|
||||
"state": {
|
||||
"preferred": "Preferred"
|
||||
}
|
||||
|
||||
Executable → Regular
BIN
Binary file not shown.
@@ -41,10 +41,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class AssistSatelliteState(StrEnum):
|
||||
"""Valid states of an Assist satellite entity."""
|
||||
|
||||
LISTENING_WAKE_WORD = "listening_wake_word"
|
||||
"""Device is streaming audio for wake word detection to Home Assistant."""
|
||||
IDLE = "idle"
|
||||
"""Device is waiting for user input, such as a wake word or a button press."""
|
||||
|
||||
LISTENING_COMMAND = "listening_command"
|
||||
LISTENING = "listening"
|
||||
"""Device is streaming audio with the voice command to Home Assistant."""
|
||||
|
||||
PROCESSING = "processing"
|
||||
@@ -117,7 +117,7 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
_attr_tts_options: dict[str, Any] | None = None
|
||||
_pipeline_task: asyncio.Task | None = None
|
||||
|
||||
__assist_satellite_state = AssistSatelliteState.LISTENING_WAKE_WORD
|
||||
__assist_satellite_state = AssistSatelliteState.IDLE
|
||||
|
||||
@final
|
||||
@property
|
||||
@@ -242,7 +242,7 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
)
|
||||
finally:
|
||||
self._is_announcing = False
|
||||
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
|
||||
self._set_state(AssistSatelliteState.IDLE)
|
||||
|
||||
async def async_announce(self, announcement: AssistSatelliteAnnouncement) -> None:
|
||||
"""Announce media on the satellite.
|
||||
@@ -363,9 +363,9 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
def _internal_on_pipeline_event(self, event: PipelineEvent) -> None:
|
||||
"""Set state based on pipeline stage."""
|
||||
if event.type is PipelineEventType.WAKE_WORD_START:
|
||||
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
|
||||
self._set_state(AssistSatelliteState.IDLE)
|
||||
elif event.type is PipelineEventType.STT_START:
|
||||
self._set_state(AssistSatelliteState.LISTENING_COMMAND)
|
||||
self._set_state(AssistSatelliteState.LISTENING)
|
||||
elif event.type is PipelineEventType.INTENT_START:
|
||||
self._set_state(AssistSatelliteState.PROCESSING)
|
||||
elif event.type is PipelineEventType.INTENT_END:
|
||||
@@ -379,7 +379,7 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
self._set_state(AssistSatelliteState.RESPONDING)
|
||||
elif event.type is PipelineEventType.RUN_END:
|
||||
if not self._run_has_tts:
|
||||
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
|
||||
self._set_state(AssistSatelliteState.IDLE)
|
||||
|
||||
self.on_pipeline_event(event)
|
||||
|
||||
@@ -392,7 +392,7 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
@callback
|
||||
def tts_response_finished(self) -> None:
|
||||
"""Tell entity that the text-to-speech response has finished playing."""
|
||||
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
|
||||
self._set_state(AssistSatelliteState.IDLE)
|
||||
|
||||
@callback
|
||||
def _resolve_pipeline(self) -> str | None:
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"_": {
|
||||
"name": "Assist satellite",
|
||||
"state": {
|
||||
"listening_wake_word": "Wake word",
|
||||
"listening_command": "Voice command",
|
||||
"idle": "[%key:common::state::idle%]",
|
||||
"listening": "Listening",
|
||||
"responding": "Responding",
|
||||
"processing": "Processing"
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
"hostname": "connect",
|
||||
"macaddress": "2C9FFB*"
|
||||
},
|
||||
{
|
||||
"hostname": "connect",
|
||||
"macaddress": "789C85*"
|
||||
},
|
||||
{
|
||||
"hostname": "august*",
|
||||
"macaddress": "E076D0*"
|
||||
|
||||
@@ -13,4 +13,5 @@ EXCLUDE_FROM_BACKUP = [
|
||||
"*.log",
|
||||
"backups/*.tar",
|
||||
"OZW_Log.txt",
|
||||
"tts/*",
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -61,6 +62,12 @@ class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle integration reconfiguration."""
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle integration reconfiguration."""
|
||||
@@ -83,5 +90,7 @@ class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
errors["base"] = "cannot_connect"
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=STEP_USER_DATA_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"filename": "[%key:component::bryant_evolution::config::step::user::data::filename%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"filename": "Serial port filename"
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_service_calendar_list_events": {
|
||||
"title": "Detected use of deprecated action `calendar.list_events`",
|
||||
"title": "Detected use of deprecated action calendar.list_events",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["hass_nabucasa"],
|
||||
"requirements": ["hass-nabucasa==0.82.0"]
|
||||
"requirements": ["hass-nabucasa==0.81.1"]
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_gender": {
|
||||
"title": "The `{deprecated_option}` text-to-speech option is deprecated",
|
||||
"title": "The {deprecated_option} text-to-speech option is deprecated",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
||||
@@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .const import CONF_MODEL
|
||||
|
||||
@@ -41,7 +42,10 @@ type EleventLabsConfigEntry = ConfigEntry[ElevenLabsData]
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: EleventLabsConfigEntry) -> bool:
|
||||
"""Set up ElevenLabs text-to-speech from a config entry."""
|
||||
entry.add_update_listener(update_listener)
|
||||
client = AsyncElevenLabs(api_key=entry.data[CONF_API_KEY])
|
||||
httpx_client = get_async_client(hass)
|
||||
client = AsyncElevenLabs(
|
||||
api_key=entry.data[CONF_API_KEY], httpx_client=httpx_client
|
||||
)
|
||||
model_id = entry.options[CONF_MODEL]
|
||||
try:
|
||||
model = await get_model_by_id(client, model_id)
|
||||
|
||||
@@ -17,6 +17,8 @@ from homeassistant.config_entries import (
|
||||
OptionsFlowWithConfigEntry,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
@@ -47,9 +49,12 @@ USER_STEP_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str})
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_voices_models(api_key: str) -> tuple[dict[str, str], dict[str, str]]:
|
||||
async def get_voices_models(
|
||||
hass: HomeAssistant, api_key: str
|
||||
) -> tuple[dict[str, str], dict[str, str]]:
|
||||
"""Get available voices and models as dicts."""
|
||||
client = AsyncElevenLabs(api_key=api_key)
|
||||
httpx_client = get_async_client(hass)
|
||||
client = AsyncElevenLabs(api_key=api_key, httpx_client=httpx_client)
|
||||
voices = (await client.voices.get_all()).voices
|
||||
models = await client.models.get_all()
|
||||
voices_dict = {
|
||||
@@ -77,7 +82,7 @@ class ElevenLabsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
voices, _ = await get_voices_models(user_input[CONF_API_KEY])
|
||||
voices, _ = await get_voices_models(self.hass, user_input[CONF_API_KEY])
|
||||
except ApiError:
|
||||
errors["base"] = "invalid_api_key"
|
||||
else:
|
||||
@@ -116,7 +121,7 @@ class ElevenLabsOptionsFlow(OptionsFlowWithConfigEntry):
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the options."""
|
||||
if not self.voices or not self.models:
|
||||
self.voices, self.models = await get_voices_models(self.api_key)
|
||||
self.voices, self.models = await get_voices_models(self.hass, self.api_key)
|
||||
|
||||
assert self.models and self.voices
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
_reconnect_entry: ConfigEntry
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize an envoy flow."""
|
||||
self.ip_address: str | None = None
|
||||
@@ -233,17 +235,22 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Add reconfigure step to allow to manually reconfigure a config entry."""
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
assert entry
|
||||
self._reconnect_entry = entry
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Add reconfigure step to allow to manually reconfigure a config entry."""
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
assert entry
|
||||
|
||||
suggested_values: dict[str, Any] | MappingProxyType[str, Any] = (
|
||||
user_input or entry.data
|
||||
user_input or self._reconnect_entry.data
|
||||
)
|
||||
|
||||
host: Any = suggested_values.get(CONF_HOST)
|
||||
@@ -284,7 +291,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
error="reconfigure_successful",
|
||||
)
|
||||
if not self.unique_id:
|
||||
await self.async_set_unique_id(entry.unique_id)
|
||||
await self.async_set_unique_id(self._reconnect_entry.unique_id)
|
||||
|
||||
self.context["title_placeholders"] = {
|
||||
CONF_SERIAL: self.unique_id,
|
||||
@@ -292,7 +299,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
self._async_generate_schema(), suggested_values
|
||||
),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"host": "The hostname or IP address of your Enphase Envoy gateway."
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"reconfigure_confirm": {
|
||||
"description": "[%key:component::enphase_envoy::config::step::user::description%]",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
|
||||
@@ -133,7 +133,7 @@ class EsphomeAssistSatellite(
|
||||
|
||||
# Empty config. Updated when added to HA.
|
||||
self._satellite_config = assist_satellite.AssistSatelliteConfiguration(
|
||||
available_wake_words=[], active_wake_words=[], max_active_wake_words=0
|
||||
available_wake_words=[], active_wake_words=[], max_active_wake_words=1
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -179,7 +179,13 @@ class EsphomeAssistSatellite(
|
||||
|
||||
async def _update_satellite_config(self) -> None:
|
||||
"""Get the latest satellite configuration from the device."""
|
||||
config = await self.cli.get_voice_assistant_configuration(_CONFIG_TIMEOUT_SEC)
|
||||
try:
|
||||
config = await self.cli.get_voice_assistant_configuration(
|
||||
_CONFIG_TIMEOUT_SEC
|
||||
)
|
||||
except TimeoutError:
|
||||
# Placeholder config will be used
|
||||
return
|
||||
|
||||
# Update available/active wake words
|
||||
self._satellite_config.available_wake_words = [
|
||||
@@ -206,7 +212,7 @@ class EsphomeAssistSatellite(
|
||||
)
|
||||
if feature_flags & VoiceAssistantFeature.API_AUDIO:
|
||||
# TCP audio
|
||||
self.entry_data.disconnect_callbacks.add(
|
||||
self.async_on_remove(
|
||||
self.cli.subscribe_voice_assistant(
|
||||
handle_start=self.handle_pipeline_start,
|
||||
handle_stop=self.handle_pipeline_stop,
|
||||
@@ -216,7 +222,7 @@ class EsphomeAssistSatellite(
|
||||
)
|
||||
else:
|
||||
# UDP audio
|
||||
self.entry_data.disconnect_callbacks.add(
|
||||
self.async_on_remove(
|
||||
self.cli.subscribe_voice_assistant(
|
||||
handle_start=self.handle_pipeline_start,
|
||||
handle_stop=self.handle_pipeline_stop,
|
||||
@@ -229,7 +235,7 @@ class EsphomeAssistSatellite(
|
||||
assert (self.registry_entry is not None) and (
|
||||
self.registry_entry.device_id is not None
|
||||
)
|
||||
self.entry_data.disconnect_callbacks.add(
|
||||
self.async_on_remove(
|
||||
async_register_timer_handler(
|
||||
self.hass, self.registry_entry.device_id, self.handle_timer_event
|
||||
)
|
||||
@@ -315,6 +321,10 @@ class EsphomeAssistSatellite(
|
||||
"code": event.data["code"],
|
||||
"message": event.data["message"],
|
||||
}
|
||||
elif event_type == VoiceAssistantEventType.VOICE_ASSISTANT_RUN_END:
|
||||
if self._tts_streaming_task is None:
|
||||
# No TTS
|
||||
self.entry_data.async_set_assist_pipeline_state(False)
|
||||
|
||||
self.cli.send_voice_assistant_event(event_type, data_to_send)
|
||||
|
||||
@@ -413,7 +423,6 @@ class EsphomeAssistSatellite(
|
||||
|
||||
# Run the pipeline
|
||||
_LOGGER.debug("Running pipeline from %s to %s", start_stage, end_stage)
|
||||
self.entry_data.async_set_assist_pipeline_state(True)
|
||||
self._pipeline_task = self.config_entry.async_create_background_task(
|
||||
self.hass,
|
||||
self.async_accept_pipeline_from_satellite(
|
||||
@@ -443,7 +452,6 @@ class EsphomeAssistSatellite(
|
||||
|
||||
def handle_pipeline_finished(self) -> None:
|
||||
"""Handle when pipeline has finished running."""
|
||||
self.entry_data.async_set_assist_pipeline_state(False)
|
||||
self._stop_udp_server()
|
||||
_LOGGER.debug("Pipeline finished")
|
||||
|
||||
@@ -561,6 +569,7 @@ class EsphomeAssistSatellite(
|
||||
|
||||
# State change
|
||||
self.tts_response_finished()
|
||||
self.entry_data.async_set_assist_pipeline_state(False)
|
||||
|
||||
async def _wrap_audio_stream(self) -> AsyncIterable[bytes]:
|
||||
"""Yield audio chunks from the queue until None."""
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"assist_satellite": {
|
||||
"assist_satellite": {
|
||||
"name": "[%key:component::assist_satellite::entity_component::_::name%]"
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
"assist_in_progress": {
|
||||
"name": "[%key:component::assist_pipeline::entity::binary_sensor::assist_in_progress::name%]"
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20240925.0"]
|
||||
"requirements": ["home-assistant-frontend==20241002.1"]
|
||||
}
|
||||
|
||||
@@ -57,17 +57,44 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
|
||||
|
||||
def __init__(self, device, gps=None, location_name=None, attributes=None):
|
||||
"""Set up Geofency entity."""
|
||||
self._attr_extra_state_attributes = attributes or {}
|
||||
self._attributes = attributes or {}
|
||||
self._name = device
|
||||
self._attr_location_name = location_name
|
||||
if gps:
|
||||
self._attr_latitude = gps[0]
|
||||
self._attr_longitude = gps[1]
|
||||
self._location_name = location_name
|
||||
self._gps = gps
|
||||
self._unsub_dispatcher = None
|
||||
self._attr_unique_id = device
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(GF_DOMAIN, device)},
|
||||
name=device,
|
||||
self._unique_id = device
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return device specific attributes."""
|
||||
return self._attributes
|
||||
|
||||
@property
|
||||
def latitude(self):
|
||||
"""Return latitude value of the device."""
|
||||
return self._gps[0]
|
||||
|
||||
@property
|
||||
def longitude(self):
|
||||
"""Return longitude value of the device."""
|
||||
return self._gps[1]
|
||||
|
||||
@property
|
||||
def location_name(self):
|
||||
"""Return a location name for the current location of the device."""
|
||||
return self._location_name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return DeviceInfo(
|
||||
identifiers={(GF_DOMAIN, self._unique_id)},
|
||||
name=self._name,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
@@ -77,23 +104,21 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
|
||||
self.hass, TRACKER_UPDATE, self._async_receive_data
|
||||
)
|
||||
|
||||
if self._attr_extra_state_attributes:
|
||||
if self._attributes:
|
||||
return
|
||||
|
||||
if (state := await self.async_get_last_state()) is None:
|
||||
self._attr_latitude = None
|
||||
self._attr_longitude = None
|
||||
self._gps = (None, None)
|
||||
return
|
||||
|
||||
attr = state.attributes
|
||||
self._attr_latitude = attr.get(ATTR_LATITUDE)
|
||||
self._attr_longitude = attr.get(ATTR_LONGITUDE)
|
||||
self._gps = (attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Clean up after entity before removal."""
|
||||
await super().async_will_remove_from_hass()
|
||||
self._unsub_dispatcher()
|
||||
self.hass.data[GF_DOMAIN]["devices"].remove(self.unique_id)
|
||||
self.hass.data[GF_DOMAIN]["devices"].remove(self._unique_id)
|
||||
|
||||
@callback
|
||||
def _async_receive_data(self, device, gps, location_name, attributes):
|
||||
@@ -101,8 +126,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
|
||||
if device != self._name:
|
||||
return
|
||||
|
||||
self._attr_extra_state_attributes.update(attributes)
|
||||
self._attr_location_name = location_name
|
||||
self._attr_latitude = gps[0]
|
||||
self._attr_longitude = gps[1]
|
||||
self._attributes.update(attributes)
|
||||
self._location_name = location_name
|
||||
self._gps = gps
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["googleapiclient"],
|
||||
"requirements": ["gcal-sync==6.1.4", "oauth2client==4.1.3", "ical==8.1.1"]
|
||||
"requirements": ["gcal-sync==6.1.5", "oauth2client==4.1.3", "ical==8.2.0"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Media source for Google Photos."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
import logging
|
||||
@@ -48,7 +46,7 @@ class PhotosIdentifierType(StrEnum):
|
||||
ALBUM = "a"
|
||||
|
||||
@classmethod
|
||||
def of(cls, name: str) -> PhotosIdentifierType:
|
||||
def of(cls, name: str) -> "PhotosIdentifierType":
|
||||
"""Parse a PhotosIdentifierType by string value."""
|
||||
for enum in PhotosIdentifierType:
|
||||
if enum.value == name:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -207,6 +208,8 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
_context_entry: ConfigEntry
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
@@ -235,28 +238,33 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration."""
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
if TYPE_CHECKING:
|
||||
assert entry
|
||||
self._context_entry = entry
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration."""
|
||||
errors: dict[str, str] | None = None
|
||||
user_input = user_input or {}
|
||||
if user_input:
|
||||
if user_input is not None:
|
||||
errors = await validate_input(self.hass, user_input)
|
||||
if not errors:
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
self._context_entry,
|
||||
data=user_input,
|
||||
reason="reconfigure_successful",
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
RECONFIGURE_SCHEMA, entry.data.copy()
|
||||
RECONFIGURE_SCHEMA, self._context_entry.data.copy()
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"destination": "Destination"
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"reconfigure_confirm": {
|
||||
"description": "[%key:component::google_travel_time::config::step::user::description%]",
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_task_entity": {
|
||||
"title": "The Habitica `{task_name}` sensor is deprecated",
|
||||
"title": "The Habitica {task_name} sensor is deprecated",
|
||||
"description": "The Habitica entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your automations and scripts to replace the sensor entity with the newly added todo entity.\nWhen you are done migrating you can disable `{entity}`."
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/hassio",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["aiohasupervisor==0.1.0b1"]
|
||||
"requirements": ["aiohasupervisor==0.1.0"]
|
||||
}
|
||||
|
||||
@@ -127,5 +127,5 @@ class HiveSensorEntity(HiveEntity, SensorEntity):
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.sensor.getSensor(self.device)
|
||||
self._attr_native_value = self.entity_description.fn(
|
||||
self.device["status"]["state"]
|
||||
self.device.get("status", {}).get("state")
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"description": "The currency {currency} is no longer in use, please reconfigure the currency configuration."
|
||||
},
|
||||
"legacy_templates_false": {
|
||||
"title": "`legacy_templates` config key is being removed",
|
||||
"title": "legacy_templates config key is being removed",
|
||||
"description": "Nothing will change with your templates.\n\nRemove the `legacy_templates` key from the `homeassistant` configuration in your configuration.yaml file and restart Home Assistant to fix this issue."
|
||||
},
|
||||
"legacy_templates_true": {
|
||||
@@ -43,7 +43,7 @@
|
||||
"description": "It's not possible to configure {platform} {domain} by adding `{platform_key}` to the {domain} configuration. Please check the documentation for more information on how to set up this integration.\n\nTo resolve this:\n1. Remove `{platform_key}` occurences from the `{domain}:` configuration in your YAML configuration file.\n2. Restart Home Assistant.\n\nExample that should be removed:\n{yaml_example}"
|
||||
},
|
||||
"storage_corruption": {
|
||||
"title": "Storage corruption detected for `{storage_key}`",
|
||||
"title": "Storage corruption detected for {storage_key}",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -557,6 +558,8 @@ OPTIONS_FLOW = {
|
||||
class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for Lutron Homeworks."""
|
||||
|
||||
_context_entry: ConfigEntry
|
||||
|
||||
async def _validate_edit_controller(
|
||||
self, user_input: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
@@ -580,18 +583,24 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return user_input
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfigure flow."""
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
assert entry
|
||||
self._context_entry = entry
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfigure flow."""
|
||||
errors = {}
|
||||
suggested_values = {
|
||||
CONF_HOST: entry.options[CONF_HOST],
|
||||
CONF_PORT: entry.options[CONF_PORT],
|
||||
CONF_USERNAME: entry.data.get(CONF_USERNAME),
|
||||
CONF_PASSWORD: entry.data.get(CONF_PASSWORD),
|
||||
CONF_HOST: self._context_entry.options[CONF_HOST],
|
||||
CONF_PORT: self._context_entry.options[CONF_PORT],
|
||||
CONF_USERNAME: self._context_entry.data.get(CONF_USERNAME),
|
||||
CONF_PASSWORD: self._context_entry.data.get(CONF_PASSWORD),
|
||||
}
|
||||
|
||||
if user_input:
|
||||
@@ -608,16 +617,16 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
password = user_input.pop(CONF_PASSWORD, None)
|
||||
username = user_input.pop(CONF_USERNAME, None)
|
||||
new_data = entry.data | {
|
||||
new_data = self._context_entry.data | {
|
||||
CONF_PASSWORD: password,
|
||||
CONF_USERNAME: username,
|
||||
}
|
||||
new_options = entry.options | {
|
||||
new_options = self._context_entry.options | {
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
}
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
self._context_entry,
|
||||
data=new_data,
|
||||
options=new_options,
|
||||
reason="reconfigure_successful",
|
||||
@@ -625,7 +634,7 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
DATA_SCHEMA_EDIT_CONTROLLER, suggested_values
|
||||
),
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"name": "[%key:component::homeworks::config::step::user::data_description::name%]"
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"reconfigure_confirm": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
@@ -45,8 +45,8 @@
|
||||
},
|
||||
"data_description": {
|
||||
"name": "A unique name identifying the Lutron Homeworks controller",
|
||||
"password": "[%key:component::homeworks::config::step::reconfigure::data_description::password%]",
|
||||
"username": "[%key:component::homeworks::config::step::reconfigure::data_description::username%]"
|
||||
"password": "[%key:component::homeworks::config::step::reconfigure_confirm::data_description::password%]",
|
||||
"username": "[%key:component::homeworks::config::step::reconfigure_confirm::data_description::username%]"
|
||||
},
|
||||
"description": "Add a Lutron Homeworks controller"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Config flow for the html5 component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import binascii
|
||||
from typing import Any, cast
|
||||
|
||||
@@ -44,7 +42,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
@callback
|
||||
def _async_create_html5_entry(
|
||||
self: HTML5ConfigFlow, data: dict[str, str]
|
||||
self: "HTML5ConfigFlow", data: dict[str, str]
|
||||
) -> tuple[dict[str, str], ConfigFlowResult | None]:
|
||||
"""Create an HTML5 entry."""
|
||||
errors = {}
|
||||
@@ -70,7 +68,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return errors, flow_result
|
||||
|
||||
async def async_step_user(
|
||||
self: HTML5ConfigFlow, user_input: dict[str, Any] | None = None
|
||||
self: "HTML5ConfigFlow", user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors: dict[str, str] = {}
|
||||
@@ -94,7 +92,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def async_step_import(
|
||||
self: HTML5ConfigFlow, import_config: dict
|
||||
self: "HTML5ConfigFlow", import_config: dict
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle config import from yaml."""
|
||||
_, flow_result = self._async_create_html5_entry(import_config)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"xknx==3.2.0",
|
||||
"xknxproject==3.7.1",
|
||||
"xknxproject==3.8.0",
|
||||
"knx-frontend==2024.9.10.221729"
|
||||
],
|
||||
"single_config_entry": true
|
||||
|
||||
@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
STORAGE_VERSION: Final = 1
|
||||
STORAGE_KEY: Final = f"{DOMAIN}/config_store.json"
|
||||
|
||||
type KNXPlatformStoreModel = dict[str, dict[str, Any]] # unique_id: configuration
|
||||
type KNXEntityStoreModel = dict[
|
||||
KNXPlatformStoreModel = dict[str, dict[str, Any]] # unique_id: configuration
|
||||
KNXEntityStoreModel = dict[
|
||||
str, KNXPlatformStoreModel
|
||||
] # platform: KNXPlatformStoreModel
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
|
||||
bluetooth_client=bluetooth_client,
|
||||
)
|
||||
|
||||
await coordinator.async_setup()
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
self._last_statistics_data_update: float | None = None
|
||||
self._local_client = local_client
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
if self._local_client is not None:
|
||||
_LOGGER.debug("Init WebSocket in background task")
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from laundrify_aio import LaundrifyAPI
|
||||
from laundrify_aio.exceptions import ApiConnectionException, UnauthorizedException
|
||||
|
||||
@@ -14,6 +16,8 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from .const import DEFAULT_POLL_INTERVAL, DOMAIN
|
||||
from .coordinator import LaundrifyUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
|
||||
|
||||
@@ -51,3 +55,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Migrate entry."""
|
||||
|
||||
_LOGGER.debug("Migrating from version %s", entry.version)
|
||||
|
||||
if entry.version == 1:
|
||||
# 1 -> 2: Unique ID from integer to string
|
||||
if entry.minor_version == 1:
|
||||
minor_version = 2
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, unique_id=str(entry.unique_id), minor_version=minor_version
|
||||
)
|
||||
|
||||
_LOGGER.debug("Migration successful")
|
||||
|
||||
return True
|
||||
|
||||
@@ -29,6 +29,7 @@ class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for laundrify."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -64,7 +65,7 @@ class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
entry_data = {CONF_ACCESS_TOKEN: access_token}
|
||||
|
||||
await self.async_set_unique_id(account_id)
|
||||
await self.async_set_unique_id(str(account_id))
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
# Create a new entry if it doesn't exist
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -9,7 +10,7 @@ import pypck
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigFlowResult
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
|
||||
from homeassistant.const import (
|
||||
CONF_BASE,
|
||||
CONF_DEVICES,
|
||||
@@ -113,6 +114,8 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
_context_entry: ConfigEntry
|
||||
|
||||
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Import existing configuration from LCN."""
|
||||
# validate the imported connection parameters
|
||||
@@ -193,31 +196,41 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return self.async_create_entry(title=data[CONF_HOST], data=data)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> config_entries.ConfigFlowResult:
|
||||
"""Reconfigure LCN configuration."""
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
assert entry
|
||||
self._context_entry = entry
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> config_entries.ConfigFlowResult:
|
||||
"""Reconfigure LCN configuration."""
|
||||
errors = None
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
assert entry
|
||||
|
||||
if user_input is not None:
|
||||
user_input[CONF_HOST] = entry.data[CONF_HOST]
|
||||
user_input[CONF_HOST] = self._context_entry.data[CONF_HOST]
|
||||
|
||||
await self.hass.config_entries.async_unload(entry.entry_id)
|
||||
await self.hass.config_entries.async_unload(self._context_entry.entry_id)
|
||||
if (error := await validate_connection(user_input)) is not None:
|
||||
errors = {CONF_BASE: error}
|
||||
|
||||
if errors is None:
|
||||
data = entry.data.copy()
|
||||
data = self._context_entry.data.copy()
|
||||
data.update(user_input)
|
||||
self.hass.config_entries.async_update_entry(entry, data=data)
|
||||
await self.hass.config_entries.async_setup(entry.entry_id)
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._context_entry, data=data
|
||||
)
|
||||
await self.hass.config_entries.async_setup(self._context_entry.entry_id)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
|
||||
await self.hass.config_entries.async_setup(entry.entry_id)
|
||||
await self.hass.config_entries.async_setup(self._context_entry.entry_id)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
data_schema=self.add_suggested_values_to_schema(CONFIG_SCHEMA, entry.data),
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
CONFIG_SCHEMA, self._context_entry.data
|
||||
),
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"acknowledge": "Retry sendig commands if no response is received (increases bus traffic)."
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"reconfigure_confirm": {
|
||||
"title": "Reconfigure LCN host",
|
||||
"description": "Reconfigure connection to LCN host.",
|
||||
"data": {
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/linkplay",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["python-linkplay==0.0.9"],
|
||||
"requirements": ["python-linkplay==0.0.12"],
|
||||
"zeroconf": ["_linkplay._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pylitejet"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylitejet==0.6.2"]
|
||||
"requirements": ["pylitejet==0.6.3"]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import date, datetime, timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -74,6 +75,7 @@ class LocalCalendarEntity(CalendarEntity):
|
||||
"""Initialize LocalCalendarEntity."""
|
||||
self._store = store
|
||||
self._calendar = calendar
|
||||
self._calendar_lock = asyncio.Lock()
|
||||
self._event: CalendarEvent | None = None
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = unique_id
|
||||
@@ -110,8 +112,10 @@ class LocalCalendarEntity(CalendarEntity):
|
||||
async def async_create_event(self, **kwargs: Any) -> None:
|
||||
"""Add a new event to calendar."""
|
||||
event = _parse_event(kwargs)
|
||||
EventStore(self._calendar).add(event)
|
||||
await self._async_store()
|
||||
async with self._calendar_lock:
|
||||
event_store = EventStore(self._calendar)
|
||||
await self.hass.async_add_executor_job(event_store.add, event)
|
||||
await self._async_store()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
async def async_delete_event(
|
||||
@@ -124,15 +128,16 @@ class LocalCalendarEntity(CalendarEntity):
|
||||
range_value: Range = Range.NONE
|
||||
if recurrence_range == Range.THIS_AND_FUTURE:
|
||||
range_value = Range.THIS_AND_FUTURE
|
||||
try:
|
||||
EventStore(self._calendar).delete(
|
||||
uid,
|
||||
recurrence_id=recurrence_id,
|
||||
recurrence_range=range_value,
|
||||
)
|
||||
except EventStoreError as err:
|
||||
raise HomeAssistantError(f"Error while deleting event: {err}") from err
|
||||
await self._async_store()
|
||||
async with self._calendar_lock:
|
||||
try:
|
||||
EventStore(self._calendar).delete(
|
||||
uid,
|
||||
recurrence_id=recurrence_id,
|
||||
recurrence_range=range_value,
|
||||
)
|
||||
except EventStoreError as err:
|
||||
raise HomeAssistantError(f"Error while deleting event: {err}") from err
|
||||
await self._async_store()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
async def async_update_event(
|
||||
@@ -147,16 +152,23 @@ class LocalCalendarEntity(CalendarEntity):
|
||||
range_value: Range = Range.NONE
|
||||
if recurrence_range == Range.THIS_AND_FUTURE:
|
||||
range_value = Range.THIS_AND_FUTURE
|
||||
try:
|
||||
EventStore(self._calendar).edit(
|
||||
uid,
|
||||
new_event,
|
||||
recurrence_id=recurrence_id,
|
||||
recurrence_range=range_value,
|
||||
)
|
||||
except EventStoreError as err:
|
||||
raise HomeAssistantError(f"Error while updating event: {err}") from err
|
||||
await self._async_store()
|
||||
|
||||
async with self._calendar_lock:
|
||||
event_store = EventStore(self._calendar)
|
||||
|
||||
def apply_edit() -> None:
|
||||
event_store.edit(
|
||||
uid,
|
||||
new_event,
|
||||
recurrence_id=recurrence_id,
|
||||
recurrence_range=range_value,
|
||||
)
|
||||
|
||||
try:
|
||||
await self.hass.async_add_executor_job(apply_edit)
|
||||
except EventStoreError as err:
|
||||
raise HomeAssistantError(f"Error while updating event: {err}") from err
|
||||
await self._async_store()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["ical"],
|
||||
"requirements": ["ical==8.1.1"]
|
||||
"requirements": ["ical==8.2.0"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["ical==8.1.1"]
|
||||
"requirements": ["ical==8.2.0"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""A Local To-do todo platform."""
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
@@ -130,6 +131,7 @@ class LocalTodoListEntity(TodoListEntity):
|
||||
"""Initialize LocalTodoListEntity."""
|
||||
self._store = store
|
||||
self._calendar = calendar
|
||||
self._calendar_lock = asyncio.Lock()
|
||||
self._attr_name = name.capitalize()
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@@ -159,23 +161,28 @@ class LocalTodoListEntity(TodoListEntity):
|
||||
async def async_create_todo_item(self, item: TodoItem) -> None:
|
||||
"""Add an item to the To-do list."""
|
||||
todo = _convert_item(item)
|
||||
self._new_todo_store().add(todo)
|
||||
await self.async_save()
|
||||
async with self._calendar_lock:
|
||||
todo_store = self._new_todo_store()
|
||||
await self.hass.async_add_executor_job(todo_store.add, todo)
|
||||
await self.async_save()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
async def async_update_todo_item(self, item: TodoItem) -> None:
|
||||
"""Update an item to the To-do list."""
|
||||
todo = _convert_item(item)
|
||||
self._new_todo_store().edit(todo.uid, todo)
|
||||
await self.async_save()
|
||||
async with self._calendar_lock:
|
||||
todo_store = self._new_todo_store()
|
||||
await self.hass.async_add_executor_job(todo_store.edit, todo.uid, todo)
|
||||
await self.async_save()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
async def async_delete_todo_items(self, uids: list[str]) -> None:
|
||||
"""Delete an item from the To-do list."""
|
||||
store = self._new_todo_store()
|
||||
for uid in uids:
|
||||
store.delete(uid)
|
||||
await self.async_save()
|
||||
async with self._calendar_lock:
|
||||
for uid in uids:
|
||||
store.delete(uid)
|
||||
await self.async_save()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
async def async_move_todo_item(
|
||||
@@ -184,23 +191,24 @@ class LocalTodoListEntity(TodoListEntity):
|
||||
"""Re-order an item to the To-do list."""
|
||||
if uid == previous_uid:
|
||||
return
|
||||
todos = self._calendar.todos
|
||||
item_idx: dict[str, int] = {itm.uid: idx for idx, itm in enumerate(todos)}
|
||||
if uid not in item_idx:
|
||||
raise HomeAssistantError(
|
||||
"Item '{uid}' not found in todo list {self.entity_id}"
|
||||
)
|
||||
if previous_uid and previous_uid not in item_idx:
|
||||
raise HomeAssistantError(
|
||||
"Item '{previous_uid}' not found in todo list {self.entity_id}"
|
||||
)
|
||||
dst_idx = item_idx[previous_uid] + 1 if previous_uid else 0
|
||||
src_idx = item_idx[uid]
|
||||
src_item = todos.pop(src_idx)
|
||||
if dst_idx > src_idx:
|
||||
dst_idx -= 1
|
||||
todos.insert(dst_idx, src_item)
|
||||
await self.async_save()
|
||||
async with self._calendar_lock:
|
||||
todos = self._calendar.todos
|
||||
item_idx: dict[str, int] = {itm.uid: idx for idx, itm in enumerate(todos)}
|
||||
if uid not in item_idx:
|
||||
raise HomeAssistantError(
|
||||
"Item '{uid}' not found in todo list {self.entity_id}"
|
||||
)
|
||||
if previous_uid and previous_uid not in item_idx:
|
||||
raise HomeAssistantError(
|
||||
"Item '{previous_uid}' not found in todo list {self.entity_id}"
|
||||
)
|
||||
dst_idx = item_idx[previous_uid] + 1 if previous_uid else 0
|
||||
src_idx = item_idx[uid]
|
||||
src_item = todos.pop(src_idx)
|
||||
if dst_idx > src_idx:
|
||||
dst_idx -= 1
|
||||
todos.insert(dst_idx, src_item)
|
||||
await self.async_save()
|
||||
await self.async_update_ha_state(force_refresh=True)
|
||||
|
||||
async def async_save(self) -> None:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Config flow for the integration."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -41,17 +42,17 @@ class MadVRConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return await self._handle_config_step(user_input)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration of the device."""
|
||||
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
return await self.async_step_reconfigure_confirm(user_input)
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
return await self._handle_config_step(user_input, step_id="reconfigure")
|
||||
return await self._handle_config_step(user_input, step_id="reconfigure_confirm")
|
||||
|
||||
async def _handle_config_step(
|
||||
self, user_input: dict[str, Any] | None = None, step_id: str = "user"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"port": "The port your madVR Envy is listening on. In 99% of cases, leave this as the default."
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"reconfigure_confirm": {
|
||||
"title": "Reconfigure madVR Envy",
|
||||
"description": "Your device needs to be on in order to reconfigure the integation.",
|
||||
"data": {
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"iot_class": "calculated",
|
||||
"loggers": ["yt_dlp"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["yt-dlp==2024.08.06"],
|
||||
"requirements": ["yt-dlp==2024.09.27"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ async def async_setup_entry(
|
||||
ATTR_SENSOR_UOM: entry.unit_of_measurement,
|
||||
ATTR_SENSOR_ENTITY_CATEGORY: entry.entity_category,
|
||||
}
|
||||
if capabilities := entry.capabilities:
|
||||
config[ATTR_SENSOR_STATE_CLASS] = capabilities.get(ATTR_SENSOR_STATE_CLASS)
|
||||
entities.append(MobileAppSensor(config, config_entry))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@@ -71,15 +71,15 @@
|
||||
},
|
||||
"issues": {
|
||||
"removed_lazy_error_count": {
|
||||
"title": "`{config_key}` configuration key is being removed",
|
||||
"title": "{config_key} configuration key is being removed",
|
||||
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue. All errors will be reported, as lazy_error_count is accepted but ignored"
|
||||
},
|
||||
"deprecated_retries": {
|
||||
"title": "`{config_key}` configuration key is being removed",
|
||||
"title": "{config_key} configuration key is being removed",
|
||||
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3."
|
||||
},
|
||||
"missing_modbus_name": {
|
||||
"title": "Modbus entry with host `{sub_2}` missing name",
|
||||
"title": "Modbus entry with host {sub_2} missing name",
|
||||
"description": "Please add `{sub_1}` key to the {integration} entry with host `{sub_2}` in your configuration.yaml file and restart Home Assistant to fix this issue\n\n. `{sub_1}: {sub_3}` have been added."
|
||||
},
|
||||
"duplicate_modbus_entry": {
|
||||
@@ -99,7 +99,7 @@
|
||||
"description": "Please add at least one entity to Modbus {sub_1} in your configuration.yaml file and restart Home Assistant to fix this issue."
|
||||
},
|
||||
"deprecated_restart": {
|
||||
"title": "`modbus.restart` is being removed",
|
||||
"title": "modbus.restart is being removed",
|
||||
"description": "Please use reload yaml via the developer tools in the UI instead of via the `modbus.restart` action."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ from homeassistant.const import CONF_NAME, Platform
|
||||
from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaCommonFlowHandler,
|
||||
SchemaConfigFlowHandler,
|
||||
SchemaFlowError,
|
||||
SchemaFlowFormStep,
|
||||
)
|
||||
from homeassistant.helpers.selector import (
|
||||
@@ -33,18 +34,20 @@ from .const import (
|
||||
)
|
||||
|
||||
|
||||
async def validate_duplicate(
|
||||
async def validate_input(
|
||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Validate already existing entry."""
|
||||
handler.parent_handler._async_abort_entries_match({**handler.options, **user_input}) # noqa: SLF001
|
||||
if user_input[CONF_CALIBRATION_FACTOR] == 0.0:
|
||||
raise SchemaFlowError("calibration_is_zero")
|
||||
return user_input
|
||||
|
||||
|
||||
DATA_SCHEMA_OPTIONS = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CALIBRATION_FACTOR): NumberSelector(
|
||||
NumberSelectorConfig(min=0, step="any", mode=NumberSelectorMode.BOX)
|
||||
NumberSelectorConfig(step=0.1, mode=NumberSelectorMode.BOX)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -74,13 +77,13 @@ DATA_SCHEMA_CONFIG = vol.Schema(
|
||||
CONFIG_FLOW = {
|
||||
"user": SchemaFlowFormStep(
|
||||
schema=DATA_SCHEMA_CONFIG,
|
||||
validate_user_input=validate_duplicate,
|
||||
validate_user_input=validate_input,
|
||||
),
|
||||
}
|
||||
OPTIONS_FLOW = {
|
||||
"init": SchemaFlowFormStep(
|
||||
DATA_SCHEMA_OPTIONS,
|
||||
validate_user_input=validate_duplicate,
|
||||
validate_user_input=validate_input,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ async def async_setup_platform(
|
||||
outdoor_temp_sensor,
|
||||
indoor_humidity_sensor,
|
||||
calib_factor,
|
||||
None,
|
||||
)
|
||||
],
|
||||
False,
|
||||
@@ -118,6 +119,7 @@ async def async_setup_entry(
|
||||
outdoor_temp_sensor,
|
||||
indoor_humidity_sensor,
|
||||
calib_factor,
|
||||
entry.entry_id,
|
||||
)
|
||||
],
|
||||
False,
|
||||
@@ -141,10 +143,12 @@ class MoldIndicator(SensorEntity):
|
||||
outdoor_temp_sensor: str,
|
||||
indoor_humidity_sensor: str,
|
||||
calib_factor: float,
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self._state: str | None = None
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = unique_id
|
||||
self._indoor_temp_sensor = indoor_temp_sensor
|
||||
self._indoor_humidity_sensor = indoor_humidity_sensor
|
||||
self._outdoor_temp_sensor = outdoor_temp_sensor
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"error": {
|
||||
"calibration_is_zero": "Calibration factor can't be zero."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Add Mold indicator helper",
|
||||
@@ -27,6 +30,9 @@
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"error": {
|
||||
"calibration_is_zero": "Calibration factor can't be zero."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Adjust the calibration factor as required",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from gql.transport.exceptions import TransportServerError
|
||||
@@ -63,9 +63,13 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]):
|
||||
async def _async_update_data(self) -> MonarchData:
|
||||
"""Fetch data for all accounts."""
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
account_data, cashflow_summary = await asyncio.gather(
|
||||
self.client.get_accounts_as_dict_with_id_key(),
|
||||
self.client.get_cashflow_summary(),
|
||||
self.client.get_cashflow_summary(
|
||||
start_date=f"{now.year}-01-01", end_date=f"{now.year}-12-31"
|
||||
),
|
||||
)
|
||||
|
||||
return MonarchData(account_data=account_data, cashflow_summary=cashflow_summary)
|
||||
|
||||
@@ -393,8 +393,7 @@ async def async_start( # noqa: C901
|
||||
if (
|
||||
result
|
||||
and result["type"] == FlowResultType.ABORT
|
||||
and result["reason"]
|
||||
in ("already_configured", "single_instance_allowed")
|
||||
and result["reason"] == "single_instance_allowed"
|
||||
):
|
||||
integration_unsubscribe.pop(key)()
|
||||
|
||||
|
||||
@@ -260,14 +260,18 @@ class MqttSensor(MqttEntity, RestoreSensor):
|
||||
msg.topic,
|
||||
)
|
||||
return
|
||||
|
||||
if payload == PAYLOAD_NONE:
|
||||
self._attr_native_value = None
|
||||
return
|
||||
|
||||
if self._numeric_state_expected:
|
||||
if payload == "":
|
||||
_LOGGER.debug("Ignore empty state from '%s'", msg.topic)
|
||||
elif payload == PAYLOAD_NONE:
|
||||
self._attr_native_value = None
|
||||
else:
|
||||
self._attr_native_value = payload
|
||||
return
|
||||
|
||||
if self.options and payload not in self.options:
|
||||
_LOGGER.warning(
|
||||
"Ignoring invalid option received on topic '%s', got '%s', allowed: %s",
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ness_alarm",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["nessclient"],
|
||||
"requirements": ["nessclient==1.0.0"]
|
||||
"requirements": ["nessclient==1.1.2"]
|
||||
}
|
||||
|
||||
@@ -86,3 +86,21 @@ async def async_remove_config_entry_device(
|
||||
if zone_id in dev_ids:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: NexiaConfigEntry) -> bool:
|
||||
"""Migrate entry."""
|
||||
|
||||
_LOGGER.debug("Migrating from version %s", entry.version)
|
||||
|
||||
if entry.version == 1:
|
||||
# 1 -> 2: Unique ID from integer to string
|
||||
if entry.minor_version == 1:
|
||||
minor_version = 2
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, unique_id=str(entry.unique_id), minor_version=minor_version
|
||||
)
|
||||
|
||||
_LOGGER.debug("Migration successful")
|
||||
|
||||
return True
|
||||
|
||||
@@ -81,6 +81,7 @@ class NexiaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Nexia."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -99,7 +100,7 @@ class NexiaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "unknown"
|
||||
|
||||
if "base" not in errors:
|
||||
await self.async_set_unique_id(info["house_id"])
|
||||
await self.async_set_unique_id(str(info["house_id"]))
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
}
|
||||
},
|
||||
"migrate_notify_service": {
|
||||
"title": "Legacy action `notify.{service_name}` stll being used",
|
||||
"title": "Legacy action notify.{service_name} stll being used",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
||||
@@ -9,7 +9,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_TOKEN
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
@@ -30,6 +30,7 @@ class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except NYTGamesError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception: # noqa: BLE001
|
||||
LOGGER.exception("Unexpected error")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(str(user_id))
|
||||
|
||||
@@ -22,7 +22,7 @@ class NYTGamesData:
|
||||
"""Class for NYT Games data."""
|
||||
|
||||
wordle: Wordle
|
||||
spelling_bee: SpellingBee
|
||||
spelling_bee: SpellingBee | None
|
||||
connections: Connections
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"default": "mdi:table-large"
|
||||
},
|
||||
"last_played": {
|
||||
"default": "mdi:beehive-outline"
|
||||
"default": "mdi:calendar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/nyt_games",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["nyt_games==0.4.0"]
|
||||
"requirements": ["nyt_games==0.4.2"]
|
||||
}
|
||||
|
||||
@@ -156,10 +156,11 @@ async def async_setup_entry(
|
||||
entities: list[SensorEntity] = [
|
||||
NYTGamesWordleSensor(coordinator, description) for description in WORDLE_SENSORS
|
||||
]
|
||||
entities.extend(
|
||||
NYTGamesSpellingBeeSensor(coordinator, description)
|
||||
for description in SPELLING_BEE_SENSORS
|
||||
)
|
||||
if coordinator.data.spelling_bee is not None:
|
||||
entities.extend(
|
||||
NYTGamesSpellingBeeSensor(coordinator, description)
|
||||
for description in SPELLING_BEE_SENSORS
|
||||
)
|
||||
entities.extend(
|
||||
NYTGamesConnectionsSensor(coordinator, description)
|
||||
for description in CONNECTIONS_SENSORS
|
||||
@@ -211,6 +212,7 @@ class NYTGamesSpellingBeeSensor(SpellingBeeEntity, SensorEntity):
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
assert self.coordinator.data.spelling_bee is not None
|
||||
return self.entity_description.value_fn(self.coordinator.data.spelling_bee)
|
||||
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyotgw"],
|
||||
"requirements": ["pyotgw==2.2.0"]
|
||||
"requirements": ["pyotgw==2.2.1"]
|
||||
}
|
||||
|
||||
@@ -81,8 +81,14 @@ class OverkizExecutor:
|
||||
|
||||
return None
|
||||
|
||||
async def async_execute_command(self, command_name: str, *args: Any) -> None:
|
||||
"""Execute device command in async context."""
|
||||
async def async_execute_command(
|
||||
self, command_name: str, *args: Any, refresh_afterwards: bool = True
|
||||
) -> None:
|
||||
"""Execute device command in async context.
|
||||
|
||||
:param refresh_afterwards: Whether to refresh the device state after the command is executed.
|
||||
If several commands are executed, it will be refreshed only once.
|
||||
"""
|
||||
parameters = [arg for arg in args if arg is not None]
|
||||
# Set the execution duration to 0 seconds for RTS devices on supported commands
|
||||
# Default execution duration is 30 seconds and will block consecutive commands
|
||||
@@ -107,8 +113,8 @@ class OverkizExecutor:
|
||||
"device_url": self.device.device_url,
|
||||
"command_name": command_name,
|
||||
}
|
||||
|
||||
await self.coordinator.async_refresh()
|
||||
if refresh_afterwards:
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_cancel_command(
|
||||
self, commands_to_cancel: list[OverkizCommand]
|
||||
|
||||
+36
-6
@@ -97,9 +97,9 @@ class AtlanticDomesticHotWaterProductionMBLComponent(OverkizEntity, WaterHeaterE
|
||||
@property
|
||||
def is_away_mode_on(self) -> bool:
|
||||
"""Return true if away mode is on."""
|
||||
return (
|
||||
self.executor.select_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE)
|
||||
== OverkizCommandParam.ON
|
||||
return self.executor.select_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE) in (
|
||||
OverkizCommandParam.ON,
|
||||
OverkizCommandParam.PROG,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -151,10 +151,40 @@ class AtlanticDomesticHotWaterProductionMBLComponent(OverkizEntity, WaterHeaterE
|
||||
await self.async_turn_away_mode_on()
|
||||
|
||||
async def async_turn_away_mode_on(self) -> None:
|
||||
"""Turn away mode on."""
|
||||
await self.executor.async_execute_command(
|
||||
OverkizCommand.SET_ABSENCE_MODE, OverkizCommandParam.ON
|
||||
"""Turn away mode on.
|
||||
|
||||
This requires the start date and the end date to be also set.
|
||||
The API accepts setting dates in the format of the core:DateTimeState state for the DHW
|
||||
{'day': 11, 'hour': 21, 'minute': 12, 'month': 7, 'second': 53, 'weekday': 3, 'year': 2024})
|
||||
The dict is then passed as an away mode start date, and then as an end date, but with the year incremented by 1,
|
||||
so the away mode is getting turned on for the next year.
|
||||
The weekday number seems to have no effect so the calculation of the future date's weekday number is redundant,
|
||||
but possible via homeassistant dt_util to form both start and end dates dictionaries from scratch
|
||||
based on datetime.now() and datetime.timedelta into the future.
|
||||
If you execute `setAbsenceStartDate`, `setAbsenceEndDate` and `setAbsenceMode`,
|
||||
the API answers with "too many requests", as there's a polling update after each command execution,
|
||||
and the device becomes unavailable until the API is available again.
|
||||
With `refresh_afterwards=False` on the first commands, and `refresh_afterwards=True` only the last command,
|
||||
the API is not choking and the transition is smooth without the unavailability state.
|
||||
"""
|
||||
now_date = cast(
|
||||
dict,
|
||||
self.executor.select_state(OverkizState.CORE_DATETIME),
|
||||
)
|
||||
await self.executor.async_execute_command(
|
||||
OverkizCommand.SET_ABSENCE_MODE,
|
||||
OverkizCommandParam.PROG,
|
||||
refresh_afterwards=False,
|
||||
)
|
||||
await self.executor.async_execute_command(
|
||||
OverkizCommand.SET_ABSENCE_START_DATE, now_date, refresh_afterwards=False
|
||||
)
|
||||
now_date["year"] = now_date["year"] + 1
|
||||
await self.executor.async_execute_command(
|
||||
OverkizCommand.SET_ABSENCE_END_DATE, now_date, refresh_afterwards=False
|
||||
)
|
||||
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_turn_away_mode_off(self) -> None:
|
||||
"""Turn away mode off."""
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"codeowners": ["@home-assistant/frontend"],
|
||||
"dependencies": ["frontend"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/panel_custom",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/prometheus",
|
||||
"iot_class": "assumed_state",
|
||||
"loggers": ["prometheus_client"],
|
||||
"requirements": ["prometheus-client==0.17.1"]
|
||||
"requirements": ["prometheus-client==0.21.0"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/python_script",
|
||||
"loggers": ["RestrictedPython"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["RestrictedPython==7.2"]
|
||||
"requirements": ["RestrictedPython==7.3"]
|
||||
}
|
||||
|
||||
@@ -570,9 +570,11 @@ class Recorder(threading.Thread):
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_clear_statistics(self, statistic_ids: list[str]) -> None:
|
||||
def async_clear_statistics(
|
||||
self, statistic_ids: list[str], *, on_done: Callable[[], None] | None = None
|
||||
) -> None:
|
||||
"""Clear statistics for a list of statistic_ids."""
|
||||
self.queue_task(ClearStatisticsTask(statistic_ids))
|
||||
self.queue_task(ClearStatisticsTask(on_done, statistic_ids))
|
||||
|
||||
@callback
|
||||
def async_update_statistics_metadata(
|
||||
@@ -581,11 +583,12 @@ class Recorder(threading.Thread):
|
||||
*,
|
||||
new_statistic_id: str | UndefinedType = UNDEFINED,
|
||||
new_unit_of_measurement: str | None | UndefinedType = UNDEFINED,
|
||||
on_done: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""Update statistics metadata for a statistic_id."""
|
||||
self.queue_task(
|
||||
UpdateStatisticsMetadataTask(
|
||||
statistic_id, new_statistic_id, new_unit_of_measurement
|
||||
on_done, statistic_id, new_statistic_id, new_unit_of_measurement
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -375,9 +375,8 @@ class EventData(Base):
|
||||
event: Event, dialect: SupportedDialect | None
|
||||
) -> bytes:
|
||||
"""Create shared_data from an event."""
|
||||
if dialect == SupportedDialect.POSTGRESQL:
|
||||
bytes_result = json_bytes_strip_null(event.data)
|
||||
bytes_result = json_bytes(event.data)
|
||||
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
||||
bytes_result = encoder(event.data)
|
||||
if len(bytes_result) > MAX_EVENT_DATA_BYTES:
|
||||
_LOGGER.warning(
|
||||
"Event data for %s exceed maximum size of %s bytes. "
|
||||
|
||||
@@ -60,17 +60,21 @@ class ChangeStatisticsUnitTask(RecorderTask):
|
||||
class ClearStatisticsTask(RecorderTask):
|
||||
"""Object to store statistics_ids which for which to remove statistics."""
|
||||
|
||||
on_done: Callable[[], None] | None
|
||||
statistic_ids: list[str]
|
||||
|
||||
def run(self, instance: Recorder) -> None:
|
||||
"""Handle the task."""
|
||||
statistics.clear_statistics(instance, self.statistic_ids)
|
||||
if self.on_done:
|
||||
self.on_done()
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class UpdateStatisticsMetadataTask(RecorderTask):
|
||||
"""Object to store statistics_id and unit for update of statistics metadata."""
|
||||
|
||||
on_done: Callable[[], None] | None
|
||||
statistic_id: str
|
||||
new_statistic_id: str | None | UndefinedType
|
||||
new_unit_of_measurement: str | None | UndefinedType
|
||||
@@ -83,6 +87,8 @@ class UpdateStatisticsMetadataTask(RecorderTask):
|
||||
self.new_statistic_id,
|
||||
self.new_unit_of_measurement,
|
||||
)
|
||||
if self.on_done:
|
||||
self.on_done()
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime as dt
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
@@ -48,6 +49,9 @@ from .statistics import (
|
||||
)
|
||||
from .util import PERIOD_SCHEMA, get_instance, resolve_period
|
||||
|
||||
CLEAR_STATISTICS_TIME_OUT = 10
|
||||
UPDATE_STATISTICS_METADATA_TIME_OUT = 10
|
||||
|
||||
UNIT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional("conductivity"): vol.In(ConductivityConverter.VALID_UNITS),
|
||||
@@ -319,8 +323,8 @@ async def ws_update_statistics_issues(
|
||||
vol.Required("statistic_ids"): [str],
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def ws_clear_statistics(
|
||||
@websocket_api.async_response
|
||||
async def ws_clear_statistics(
|
||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Clear statistics for a list of statistic_ids.
|
||||
@@ -328,7 +332,23 @@ def ws_clear_statistics(
|
||||
Note: The WS call posts a job to the recorder's queue and then returns, it doesn't
|
||||
wait until the job is completed.
|
||||
"""
|
||||
get_instance(hass).async_clear_statistics(msg["statistic_ids"])
|
||||
done_event = asyncio.Event()
|
||||
|
||||
def clear_statistics_done() -> None:
|
||||
hass.loop.call_soon_threadsafe(done_event.set)
|
||||
|
||||
get_instance(hass).async_clear_statistics(
|
||||
msg["statistic_ids"], on_done=clear_statistics_done
|
||||
)
|
||||
try:
|
||||
async with asyncio.timeout(CLEAR_STATISTICS_TIME_OUT):
|
||||
await done_event.wait()
|
||||
except TimeoutError:
|
||||
connection.send_error(
|
||||
msg["id"], websocket_api.ERR_TIMEOUT, "clear_statistics timed out"
|
||||
)
|
||||
return
|
||||
|
||||
connection.send_result(msg["id"])
|
||||
|
||||
|
||||
@@ -357,17 +377,33 @@ async def ws_get_statistics_metadata(
|
||||
vol.Required("unit_of_measurement"): vol.Any(str, None),
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def ws_update_statistics_metadata(
|
||||
@websocket_api.async_response
|
||||
async def ws_update_statistics_metadata(
|
||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Update statistics metadata for a statistic_id.
|
||||
|
||||
Only the normalized unit of measurement can be updated.
|
||||
"""
|
||||
done_event = asyncio.Event()
|
||||
|
||||
def update_statistics_metadata_done() -> None:
|
||||
hass.loop.call_soon_threadsafe(done_event.set)
|
||||
|
||||
get_instance(hass).async_update_statistics_metadata(
|
||||
msg["statistic_id"], new_unit_of_measurement=msg["unit_of_measurement"]
|
||||
msg["statistic_id"],
|
||||
new_unit_of_measurement=msg["unit_of_measurement"],
|
||||
on_done=update_statistics_metadata_done,
|
||||
)
|
||||
try:
|
||||
async with asyncio.timeout(UPDATE_STATISTICS_METADATA_TIME_OUT):
|
||||
await done_event.wait()
|
||||
except TimeoutError:
|
||||
connection.send_error(
|
||||
msg["id"], websocket_api.ERR_TIMEOUT, "update_statistics_metadata timed out"
|
||||
)
|
||||
return
|
||||
|
||||
connection.send_result(msg["id"])
|
||||
|
||||
|
||||
|
||||
@@ -155,6 +155,11 @@ class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity):
|
||||
configuration_url=self._conf_url,
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return super().available and self._host.api.camera_online(self._channel)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Entity created."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_entity": {
|
||||
"title": "Detected deprecated `{platform}` entity usage",
|
||||
"title": "Detected deprecated {platform} entity usage",
|
||||
"description": "We detected that entity `{entity}` is being used in `{info}`\n\nWe have created a new `{new_platform}` entity and you should migrate `{info}` to use this new entity.\n\nWhen you are done migrating `{info}` and are ready to have the deprecated `{entity}` entity removed, disable the entity and restart Home Assistant."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,6 @@ class RoborockCurrentMapSelectEntity(RoborockCoordinatedEntityV1, SelectEntity):
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Get the current status of the select entity from device_status."""
|
||||
if current_map := self.coordinator.current_map:
|
||||
if (current_map := self.coordinator.current_map) is not None:
|
||||
return self.coordinator.maps[current_map].name
|
||||
return None
|
||||
|
||||
@@ -41,7 +41,9 @@ DEFAULT_OPTIONS = {CONF_CONTINUOUS: DEFAULT_CONTINUOUS, CONF_DELAY: DEFAULT_DELA
|
||||
MAX_NUM_DEVICES_TO_DISCOVER = 25
|
||||
|
||||
AUTH_HELP_URL_KEY = "auth_help_url"
|
||||
AUTH_HELP_URL_VALUE = "https://www.home-assistant.io/integrations/roomba/#manually-retrieving-your-credentials"
|
||||
AUTH_HELP_URL_VALUE = (
|
||||
"https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials"
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -249,7 +249,7 @@ async def _async_create_bridge_with_updated_data(
|
||||
updated_data[CONF_MODEL] = model
|
||||
|
||||
if model_requires_encryption(model) and method != METHOD_ENCRYPTED_WEBSOCKET:
|
||||
LOGGER.warning(
|
||||
LOGGER.debug(
|
||||
(
|
||||
"Detected model %s for %s. Some televisions from H and J series use "
|
||||
"an encrypted protocol but you are using %s which may not be supported"
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterable
|
||||
from contextlib import suppress
|
||||
import datetime
|
||||
from functools import partial
|
||||
import itertools
|
||||
@@ -179,6 +180,14 @@ def _entity_history_to_float_and_state(
|
||||
return float_states
|
||||
|
||||
|
||||
def _is_numeric(state: State) -> bool:
|
||||
"""Return if the state is numeric."""
|
||||
with suppress(ValueError, TypeError):
|
||||
if (num_state := float(state.state)) is not None and math.isfinite(num_state):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _normalize_states(
|
||||
hass: HomeAssistant,
|
||||
old_metadatas: dict[str, tuple[int, StatisticMetaData]],
|
||||
@@ -684,29 +693,27 @@ def _update_issues(
|
||||
"""Update repair issues."""
|
||||
for state in sensor_states:
|
||||
entity_id = state.entity_id
|
||||
numeric = _is_numeric(state)
|
||||
state_class = try_parse_enum(
|
||||
SensorStateClass, state.attributes.get(ATTR_STATE_CLASS)
|
||||
)
|
||||
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
if metadata := metadatas.get(entity_id):
|
||||
if state_class is None:
|
||||
if numeric and state_class is None:
|
||||
# Sensor no longer has a valid state class
|
||||
report_issue(
|
||||
"unsupported_state_class",
|
||||
"state_class_removed",
|
||||
entity_id,
|
||||
{
|
||||
"statistic_id": entity_id,
|
||||
"state_class": state_class,
|
||||
},
|
||||
{"statistic_id": entity_id},
|
||||
)
|
||||
else:
|
||||
clear_issue("unsupported_state_class", entity_id)
|
||||
clear_issue("state_class_removed", entity_id)
|
||||
|
||||
metadata_unit = metadata[1]["unit_of_measurement"]
|
||||
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
|
||||
if not converter:
|
||||
if not _equivalent_units({state_unit, metadata_unit}):
|
||||
if numeric and not _equivalent_units({state_unit, metadata_unit}):
|
||||
# The unit has changed, and it's not possible to convert
|
||||
report_issue(
|
||||
"units_changed",
|
||||
@@ -720,7 +727,7 @@ def _update_issues(
|
||||
)
|
||||
else:
|
||||
clear_issue("units_changed", entity_id)
|
||||
elif state_unit not in converter.VALID_UNITS:
|
||||
elif numeric and state_unit not in converter.VALID_UNITS:
|
||||
# The state unit can't be converted to the unit in metadata
|
||||
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
|
||||
valid_units_str = ", ".join(sorted(valid_units))
|
||||
|
||||
@@ -289,12 +289,12 @@
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"units_changed": {
|
||||
"title": "The unit of {statistic_id} has changed",
|
||||
"state_class_removed": {
|
||||
"title": "{statistic_id} no longer has a state class",
|
||||
"description": ""
|
||||
},
|
||||
"unsupported_state_class": {
|
||||
"title": "The state class of {statistic_id} is not supported",
|
||||
"units_changed": {
|
||||
"title": "The unit of {statistic_id} has changed",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ async def async_create_fix_flow(
|
||||
hass: HomeAssistant, issue_id: str, data: dict
|
||||
) -> RepairsFlow:
|
||||
"""Create flow."""
|
||||
if issue_id.startswith("deprecate_sensor_"):
|
||||
entry = hass.config_entries.async_get_entry(data["entry_id"])
|
||||
assert entry
|
||||
if issue_id.startswith("deprecate_sensor_") and (
|
||||
entry := hass.config_entries.async_get_entry(data["entry_id"])
|
||||
):
|
||||
return SensorDeprecationRepairFlow(entry)
|
||||
return ConfirmRepairFlow()
|
||||
|
||||
@@ -89,7 +89,7 @@ def setup_services(hass: HomeAssistant) -> None:
|
||||
ATTR_TRACKING_NUMBER: package.tracking_number,
|
||||
ATTR_LOCATION: package.location,
|
||||
ATTR_STATUS: package.status,
|
||||
ATTR_TIMESTAMP: package.timestamp,
|
||||
ATTR_TIMESTAMP: package.timestamp.isoformat(),
|
||||
ATTR_INFO_TEXT: package.info_text,
|
||||
ATTR_FRIENDLY_NAME: package.friendly_name,
|
||||
}
|
||||
|
||||
@@ -138,40 +138,47 @@ class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
|
||||
return await self.async_step_reconfigure_confirm()
|
||||
|
||||
async def async_step_reconfigure_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert entry is not None
|
||||
assert self._entry is not None
|
||||
|
||||
if user_input is not None:
|
||||
if not user_input[CONF_HAS_PWD] or user_input.get(CONF_PASSWORD, "") == "":
|
||||
user_input[CONF_PASSWORD] = ""
|
||||
user_input[CONF_HAS_PWD] = False
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
self._entry,
|
||||
reason="reconfigure_successful",
|
||||
data={**entry.data, **user_input},
|
||||
data={**self._entry.data, **user_input},
|
||||
)
|
||||
|
||||
if await self._test_extended_data(
|
||||
entry.data[CONF_HOST], user_input.get(CONF_PASSWORD, "")
|
||||
self._entry.data[CONF_HOST], user_input.get(CONF_PASSWORD, "")
|
||||
):
|
||||
# if password has been provided, only save if extended data is available
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
self._entry,
|
||||
reason="reconfigure_successful",
|
||||
data={**entry.data, **user_input},
|
||||
data={**self._entry.data, **user_input},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
step_id="reconfigure_confirm",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_HAS_PWD, default=entry.data[CONF_HAS_PWD]): bool,
|
||||
vol.Optional(
|
||||
CONF_HAS_PWD, default=self._entry.data[CONF_HAS_PWD]
|
||||
): bool,
|
||||
vol.Optional(CONF_PASSWORD): str,
|
||||
}
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
},
|
||||
"reconfigure": {
|
||||
"reconfigure_confirm": {
|
||||
"title": "Configure SolarLog",
|
||||
"data": {
|
||||
"has_password": "[%key:component::solarlog::config::step::user::data::has_password%]"
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/squeezebox",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pysqueezebox"],
|
||||
"requirements": ["pysqueezebox==0.9.2"]
|
||||
"requirements": ["pysqueezebox==0.9.3"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["synology_dsm"],
|
||||
"requirements": ["py-synologydsm-api==2.5.2"],
|
||||
"requirements": ["py-synologydsm-api==2.5.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Synology",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Support for the (unofficial) Tado API."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import requests.exceptions
|
||||
|
||||
@@ -22,9 +20,6 @@ from .const import (
|
||||
CONST_OVERLAY_TADO_MODE,
|
||||
CONST_OVERLAY_TADO_OPTIONS,
|
||||
DOMAIN,
|
||||
UPDATE_LISTENER,
|
||||
UPDATE_MOBILE_DEVICE_TRACK,
|
||||
UPDATE_TRACK,
|
||||
)
|
||||
from .services import setup_services
|
||||
from .tado_connector import TadoConnector
|
||||
@@ -55,17 +50,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
type TadoConfigEntry = ConfigEntry[TadoRuntimeData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class TadoRuntimeData:
|
||||
"""Dataclass for Tado runtime data."""
|
||||
|
||||
tadoconnector: TadoConnector
|
||||
update_track: Any
|
||||
update_mobile_device_track: Any
|
||||
update_listener: Any
|
||||
type TadoConfigEntry = ConfigEntry[TadoConnector]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: TadoConfigEntry) -> bool:
|
||||
@@ -99,26 +84,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: TadoConfigEntry) -> bool
|
||||
await hass.async_add_executor_job(tadoconnector.update)
|
||||
|
||||
# Poll for updates in the background
|
||||
update_track = async_track_time_interval(
|
||||
hass,
|
||||
lambda now: tadoconnector.update(),
|
||||
SCAN_INTERVAL,
|
||||
entry.async_on_unload(
|
||||
async_track_time_interval(
|
||||
hass,
|
||||
lambda now: tadoconnector.update(),
|
||||
SCAN_INTERVAL,
|
||||
)
|
||||
)
|
||||
|
||||
update_mobile_devices = async_track_time_interval(
|
||||
hass,
|
||||
lambda now: tadoconnector.update_mobile_devices(),
|
||||
SCAN_MOBILE_DEVICE_INTERVAL,
|
||||
entry.async_on_unload(
|
||||
async_track_time_interval(
|
||||
hass,
|
||||
lambda now: tadoconnector.update_mobile_devices(),
|
||||
SCAN_MOBILE_DEVICE_INTERVAL,
|
||||
)
|
||||
)
|
||||
|
||||
update_listener = entry.add_update_listener(_async_update_listener)
|
||||
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
|
||||
|
||||
entry.runtime_data = TadoRuntimeData(
|
||||
tadoconnector=tadoconnector,
|
||||
update_track=update_track,
|
||||
update_mobile_device_track=update_mobile_devices,
|
||||
update_listener=update_listener,
|
||||
)
|
||||
entry.runtime_data = tadoconnector
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -147,15 +131,6 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> Non
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: TadoConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][UPDATE_TRACK]()
|
||||
hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER]()
|
||||
hass.data[DOMAIN][entry.entry_id][UPDATE_MOBILE_DEVICE_TRACK]()
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -121,7 +121,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Tado sensor platform."""
|
||||
|
||||
tado: TadoConnector = entry.runtime_data.tadoconnector
|
||||
tado = entry.runtime_data
|
||||
devices = tado.devices
|
||||
zones = tado.zones
|
||||
entities: list[BinarySensorEntity] = []
|
||||
|
||||
@@ -105,7 +105,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Tado climate platform."""
|
||||
|
||||
tado: TadoConnector = entry.runtime_data.tadoconnector
|
||||
tado = entry.runtime_data
|
||||
entities = await hass.async_add_executor_job(_generate_entities, tado)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
@@ -38,8 +38,6 @@ TADO_HVAC_ACTION_TO_HA_HVAC_ACTION = {
|
||||
CONF_FALLBACK = "fallback"
|
||||
CONF_HOME_ID = "home_id"
|
||||
DATA = "data"
|
||||
UPDATE_TRACK = "update_track"
|
||||
UPDATE_MOBILE_DEVICE_TRACK = "update_mobile_device_track"
|
||||
|
||||
# Weather
|
||||
CONDITIONS_MAP = {
|
||||
@@ -207,8 +205,6 @@ DEFAULT_NAME = "Tado"
|
||||
TADO_HOME = "Home"
|
||||
TADO_ZONE = "Zone"
|
||||
|
||||
UPDATE_LISTENER = "update_listener"
|
||||
|
||||
# Constants for Temperature Offset
|
||||
INSIDE_TEMPERATURE_MEASUREMENT = "INSIDE_TEMPERATURE_MEASUREMENT"
|
||||
TEMP_OFFSET = "temperatureOffset"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user