mirror of
https://github.com/home-assistant/core.git
synced 2025-09-20 18:39:40 +00:00
Compare commits
319 Commits
hassnabuca
...
openai-mod
Author | SHA1 | Date | |
---|---|---|---|
![]() |
099a480e57 | ||
![]() |
4cc4bd3b9a | ||
![]() |
dbc2b1354b | ||
![]() |
fbe257f997 | ||
![]() |
208dde10e6 | ||
![]() |
b7b733efc3 | ||
![]() |
1d9f779b2a | ||
![]() |
56c53fdb9b | ||
![]() |
5c4862ffe1 | ||
![]() |
5c7913c3bd | ||
![]() |
36a98470cc | ||
![]() |
f2c995cf86 | ||
![]() |
eeca5a8030 | ||
![]() |
56d97f5545 | ||
![]() |
995a99e256 | ||
![]() |
ef7cd815b2 | ||
![]() |
8b8616182d | ||
![]() |
760b69d458 | ||
![]() |
6adcd34521 | ||
![]() |
a0992498c6 | ||
![]() |
d6175fb383 | ||
![]() |
dd3c9ab3af | ||
![]() |
fea2ef1ac1 | ||
![]() |
326bcc3f05 | ||
![]() |
feeef88710 | ||
![]() |
f481c1b92f | ||
![]() |
eea22d8079 | ||
![]() |
393087cf50 | ||
![]() |
f5718e1df6 | ||
![]() |
15f2ae3002 | ||
![]() |
f458ede468 | ||
![]() |
d85ffee27a | ||
![]() |
2e12d67f2f | ||
![]() |
46a01c2060 | ||
![]() |
53d77c4c10 | ||
![]() |
fcd514a06b | ||
![]() |
049a698815 | ||
![]() |
55f01e3485 | ||
![]() |
c2b1932045 | ||
![]() |
5543587527 | ||
![]() |
202d8ac802 | ||
![]() |
7613880645 | ||
![]() |
3f77c13aad | ||
![]() |
b966b59c09 | ||
![]() |
40cf47ae5a | ||
![]() |
da8ce52ed7 | ||
![]() |
b5190788ac | ||
![]() |
bfa7ff3ede | ||
![]() |
1312e04c57 | ||
![]() |
d3771571cd | ||
![]() |
5aa629edd0 | ||
![]() |
3ed297676f | ||
![]() |
d735af505e | ||
![]() |
e337abb12d | ||
![]() |
45edd12f13 | ||
![]() |
5b94f5a99a | ||
![]() |
8b7295cd26 | ||
![]() |
15f7dade5e | ||
![]() |
61807412c4 | ||
![]() |
f679f33c56 | ||
![]() |
2abd203580 | ||
![]() |
ccd22ce0d5 | ||
![]() |
391b144033 | ||
![]() |
b6db10340e | ||
![]() |
23b2936174 | ||
![]() |
fad5f7a47b | ||
![]() |
58ddf4ea95 | ||
![]() |
22fa863984 | ||
![]() |
d9b25770ad | ||
![]() |
1c8ae8a21b | ||
![]() |
6d3872252b | ||
![]() |
4730c5b831 | ||
![]() |
9a9f65dc36 | ||
![]() |
7c83fd0bf9 | ||
![]() |
70e03cdd4e | ||
![]() |
4d5c1b139b | ||
![]() |
6dc5c9beb7 | ||
![]() |
47611619db | ||
![]() |
2a0a31bff8 | ||
![]() |
dcf29d12a7 | ||
![]() |
edf6166a9f | ||
![]() |
eb8ca53a03 | ||
![]() |
3dffd74607 | ||
![]() |
b37273ed33 | ||
![]() |
232b34609c | ||
![]() |
aeeabfcae7 | ||
![]() |
52abab8ae8 | ||
![]() |
7aa4810b0a | ||
![]() |
c4d742f549 | ||
![]() |
51a46a128c | ||
![]() |
9a6ba225e4 | ||
![]() |
a5ab523014 | ||
![]() |
40571dff3d | ||
![]() |
5f2f038609 | ||
![]() |
9fd2ad425c | ||
![]() |
2f6c0a1b7f | ||
![]() |
dde73c05cb | ||
![]() |
993b0bbdd7 | ||
![]() |
45dbf3ef1a | ||
![]() |
71c1837f39 | ||
![]() |
34eb99530f | ||
![]() |
55ac4d8855 | ||
![]() |
ef3fb50018 | ||
![]() |
316ac6253b | ||
![]() |
252a46d141 | ||
![]() |
969ad232aa | ||
![]() |
828a47db06 | ||
![]() |
3947569132 | ||
![]() |
e5f9788d24 | ||
![]() |
dd399ef59f | ||
![]() |
5a771b501d | ||
![]() |
3f67ba4c02 | ||
![]() |
c075134845 | ||
![]() |
e5c7e04329 | ||
![]() |
49807c9fbe | ||
![]() |
e79d42ecfc | ||
![]() |
1f07dd7946 | ||
![]() |
8d1c789ca2 | ||
![]() |
f5d68a4ea4 | ||
![]() |
2315bcbfe3 | ||
![]() |
df4e1411cc | ||
![]() |
3e7974a638 | ||
![]() |
48b8827390 | ||
![]() |
42cf4e8db7 | ||
![]() |
ef2531d28d | ||
![]() |
79dd91ebc6 | ||
![]() |
ecb6cc50b9 | ||
![]() |
b6014da121 | ||
![]() |
941d3c2be4 | ||
![]() |
7d895653fb | ||
![]() |
3bd70a4698 | ||
![]() |
b85ec55abb | ||
![]() |
3f42911af4 | ||
![]() |
3c70932357 | ||
![]() |
40252763d7 | ||
![]() |
80b96b0007 | ||
![]() |
f3db3ba3c8 | ||
![]() |
102ef257a0 | ||
![]() |
2476e7e47c | ||
![]() |
671523feb3 | ||
![]() |
54fa4d635b | ||
![]() |
af0480f2a4 | ||
![]() |
64f190749a | ||
![]() |
6b489e0ab6 | ||
![]() |
1315095b4a | ||
![]() |
2d86fa079e | ||
![]() |
875219ccb5 | ||
![]() |
bc0162cf85 | ||
![]() |
d774de79db | ||
![]() |
be25a7bc70 | ||
![]() |
05566e1621 | ||
![]() |
b59d8b5730 | ||
![]() |
75a90ab568 | ||
![]() |
67c68dedba | ||
![]() |
1fba61973d | ||
![]() |
bf1a660dcb | ||
![]() |
94d077ea41 | ||
![]() |
c22f65bd87 | ||
![]() |
0dba32dbcd | ||
![]() |
8c964e64db | ||
![]() |
c08aa74496 | ||
![]() |
ff9fb6228b | ||
![]() |
6eab118a2d | ||
![]() |
c1e35cc9cf | ||
![]() |
11dd2dc374 | ||
![]() |
00c4b09773 | ||
![]() |
bc9ad5eac6 | ||
![]() |
eca80a1645 | ||
![]() |
bd7cef92c7 | ||
![]() |
27787e0679 | ||
![]() |
0a9fbb215d | ||
![]() |
77a954df9b | ||
![]() |
61ca0b6b86 | ||
![]() |
e3577de9d8 | ||
![]() |
b8d45fba24 | ||
![]() |
44fec53bac | ||
![]() |
ca48b9e375 | ||
![]() |
216e89dc5e | ||
![]() |
72d5578128 | ||
![]() |
e3bdd12dad | ||
![]() |
43dc73c2e1 | ||
![]() |
302b6f03ba | ||
![]() |
b31e17f1f9 | ||
![]() |
1b8f3348b0 | ||
![]() |
0d42b24467 | ||
![]() |
0c858de1af | ||
![]() |
5d653d46c3 | ||
![]() |
b262a5c9b6 | ||
![]() |
ead99c549f | ||
![]() |
1a6bfc0310 | ||
![]() |
507f29a209 | ||
![]() |
d796ab8fe7 | ||
![]() |
d35dca377f | ||
![]() |
96766fc62a | ||
![]() |
afbb0ee2f4 | ||
![]() |
e885ae1b15 | ||
![]() |
51d38f8f05 | ||
![]() |
be644ca96e | ||
![]() |
d266b6f6ab | ||
![]() |
dbdc666a92 | ||
![]() |
2577d9f108 | ||
![]() |
7dfb54c8e8 | ||
![]() |
a50d926e2a | ||
![]() |
0cfb395ab5 | ||
![]() |
b3f049676d | ||
![]() |
7e04a7ec19 | ||
![]() |
c15bf097f0 | ||
![]() |
dba3d98a2b | ||
![]() |
4a5e193ebb | ||
![]() |
1bbd07fe48 | ||
![]() |
b9d19ffb29 | ||
![]() |
22b35030a9 | ||
![]() |
69c26e5f1f | ||
![]() |
cb4d17b24f | ||
![]() |
ff14f6b823 | ||
![]() |
ab964c8bca | ||
![]() |
05f686cb86 | ||
![]() |
440a20340e | ||
![]() |
676a931c48 | ||
![]() |
360da43868 | ||
![]() |
12193587c9 | ||
![]() |
290f19dbd9 | ||
![]() |
13434012e7 | ||
![]() |
7202203f35 | ||
![]() |
31167f5da7 | ||
![]() |
665991a3c1 | ||
![]() |
8a2493e9d2 | ||
![]() |
be6743d4fd | ||
![]() |
284b90d502 | ||
![]() |
d7d2013ec8 | ||
![]() |
b3bd882a80 | ||
![]() |
3a6f23b95f | ||
![]() |
6f59aaebdd | ||
![]() |
f90e06fde1 | ||
![]() |
380c737901 | ||
![]() |
33cc257e75 | ||
![]() |
3877a6211a | ||
![]() |
916b4368dd | ||
![]() |
4c99fe9ae5 | ||
![]() |
353b573707 | ||
![]() |
109663f177 | ||
![]() |
3b89b2cbbe | ||
![]() |
29d0d6cd43 | ||
![]() |
1743766d17 | ||
![]() |
277241c4d3 | ||
![]() |
17034f4d6a | ||
![]() |
ec544b0430 | ||
![]() |
75c803e376 | ||
![]() |
a96e31f1d8 | ||
![]() |
43a30fad96 | ||
![]() |
39d323186f | ||
![]() |
57c024449c | ||
![]() |
414057d455 | ||
![]() |
50688bbd69 | ||
![]() |
073ea813f0 | ||
![]() |
6b959f42f6 | ||
![]() |
0ff0902ccf | ||
![]() |
37a154b1df | ||
![]() |
3c87a3e892 | ||
![]() |
aacaa9a20f | ||
![]() |
c074453763 | ||
![]() |
29afa891ec | ||
![]() |
3b6eb045c6 | ||
![]() |
0d819f2389 | ||
![]() |
9802441fea | ||
![]() |
fb13c8f4f2 | ||
![]() |
a96e38871f | ||
![]() |
17920b6ec3 | ||
![]() |
40cabc8d70 | ||
![]() |
b33a556ca5 | ||
![]() |
9df97fb2e2 | ||
![]() |
ee35fc495d | ||
![]() |
9373bb287c | ||
![]() |
d72fb021c1 | ||
![]() |
0e6a1e3242 | ||
![]() |
79b8e74d87 | ||
![]() |
72d1c3cfc8 | ||
![]() |
3d278b626a | ||
![]() |
5383ff96ef | ||
![]() |
a0991134c4 | ||
![]() |
9def44dca4 | ||
![]() |
656822b39c | ||
![]() |
ae03fc2295 | ||
![]() |
e32e06d7a0 | ||
![]() |
6dc2340c5a | ||
![]() |
83cd2dfef3 | ||
![]() |
a5c301db1b | ||
![]() |
5b41d5a795 | ||
![]() |
9d178ad5f1 | ||
![]() |
e8fca19335 | ||
![]() |
58bb2fa327 | ||
![]() |
fca05f6bcf | ||
![]() |
a5f0f6c8b9 | ||
![]() |
e2340314c6 | ||
![]() |
aab6cd665f | ||
![]() |
1734b316d5 | ||
![]() |
3449863eee | ||
![]() |
b68de0af88 | ||
![]() |
840e0d1388 | ||
![]() |
412035b970 | ||
![]() |
3e465da892 | ||
![]() |
0d79f7db51 | ||
![]() |
62e3802ff2 | ||
![]() |
02a11638b3 | ||
![]() |
26a9af7371 | ||
![]() |
e28f02d163 | ||
![]() |
a6828898d1 | ||
![]() |
29e105b0ef | ||
![]() |
ce4a811b96 | ||
![]() |
fe8384719d | ||
![]() |
a57d48fd31 | ||
![]() |
8a73511b02 | ||
![]() |
033d8b3dfb | ||
![]() |
6833bf1900 | ||
![]() |
84e3dac406 | ||
![]() |
bafd342d5d | ||
![]() |
fae6b375cd | ||
![]() |
d8de6e34dd | ||
![]() |
9db5b0b3b7 |
6
.github/copilot-instructions.md
vendored
6
.github/copilot-instructions.md
vendored
@@ -45,6 +45,12 @@ rules:
|
||||
|
||||
**When Reviewing/Creating Code**: Always check the integration's quality scale level and exemption status before applying rules.
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
**When reviewing code, do NOT comment on:**
|
||||
- **Missing imports** - We use static analysis tooling to catch that
|
||||
- **Code formatting** - We have ruff as a formatting tool that will catch those if needed (unless specifically instructed otherwise in these instructions)
|
||||
|
||||
## Python Requirements
|
||||
|
||||
- **Compatibility**: Python 3.13+
|
||||
|
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -6,3 +6,6 @@ updates:
|
||||
interval: daily
|
||||
time: "06:00"
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- dependency
|
||||
- github_actions
|
||||
|
2
.github/workflows/builder.yml
vendored
2
.github/workflows/builder.yml
vendored
@@ -324,7 +324,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.9.1
|
||||
uses: sigstore/cosign-installer@v3.9.2
|
||||
with:
|
||||
cosign-release: "v2.2.3"
|
||||
|
||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.29.2
|
||||
uses: github/codeql-action/init@v3.29.4
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.29.2
|
||||
uses: github/codeql-action/analyze@v3.29.4
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
@@ -231,7 +231,7 @@ jobs:
|
||||
- name: Detect duplicates using AI
|
||||
id: ai_detection
|
||||
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
|
||||
uses: actions/ai-inference@v1.1.0
|
||||
uses: actions/ai-inference@v1.2.3
|
||||
with:
|
||||
model: openai/gpt-4o
|
||||
system-prompt: |
|
||||
|
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Detect language using AI
|
||||
id: ai_language_detection
|
||||
if: steps.detect_language.outputs.should_continue == 'true'
|
||||
uses: actions/ai-inference@v1.1.0
|
||||
uses: actions/ai-inference@v1.2.3
|
||||
with:
|
||||
model: openai/gpt-4o-mini
|
||||
system-prompt: |
|
||||
|
@@ -377,6 +377,7 @@ homeassistant.components.onedrive.*
|
||||
homeassistant.components.onewire.*
|
||||
homeassistant.components.onkyo.*
|
||||
homeassistant.components.open_meteo.*
|
||||
homeassistant.components.open_router.*
|
||||
homeassistant.components.openai_conversation.*
|
||||
homeassistant.components.openexchangerates.*
|
||||
homeassistant.components.opensky.*
|
||||
|
6
CODEOWNERS
generated
6
CODEOWNERS
generated
@@ -684,8 +684,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/husqvarna_automower/ @Thomas55555
|
||||
/homeassistant/components/husqvarna_automower_ble/ @alistair23
|
||||
/tests/components/husqvarna_automower_ble/ @alistair23
|
||||
/homeassistant/components/huum/ @frwickst
|
||||
/tests/components/huum/ @frwickst
|
||||
/homeassistant/components/huum/ @frwickst @vincentwolsink
|
||||
/tests/components/huum/ @frwickst @vincentwolsink
|
||||
/homeassistant/components/hvv_departures/ @vigonotion
|
||||
/tests/components/hvv_departures/ @vigonotion
|
||||
/homeassistant/components/hydrawise/ @dknowles2 @thomaskistler @ptcryan
|
||||
@@ -1102,6 +1102,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/onvif/ @hunterjm @jterrace
|
||||
/homeassistant/components/open_meteo/ @frenck
|
||||
/tests/components/open_meteo/ @frenck
|
||||
/homeassistant/components/open_router/ @joostlek
|
||||
/tests/components/open_router/ @joostlek
|
||||
/homeassistant/components/openai_conversation/ @balloob
|
||||
/tests/components/openai_conversation/ @balloob
|
||||
/homeassistant/components/openerz/ @misialq
|
||||
|
@@ -695,10 +695,10 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
|
||||
|
||||
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
||||
"""Get domains of components to set up."""
|
||||
# Filter out the repeating and common config section [homeassistant]
|
||||
domains = {
|
||||
domain for key in config if (domain := cv.domain_key(key)) != core.DOMAIN
|
||||
}
|
||||
# The common config section [homeassistant] could be filtered here,
|
||||
# but that is not necessary, since it corresponds to the core integration,
|
||||
# that is always unconditionally loaded.
|
||||
domains = {cv.domain_key(key) for key in config}
|
||||
|
||||
# Add config entry and default domains
|
||||
if not hass.config.recovery_mode:
|
||||
@@ -726,34 +726,28 @@ async def _async_resolve_domains_and_preload(
|
||||
together with all their dependencies.
|
||||
"""
|
||||
domains_to_setup = _get_domains(hass, config)
|
||||
platform_integrations = conf_util.extract_platform_integrations(
|
||||
config, BASE_PLATFORMS
|
||||
)
|
||||
# Ensure base platforms that have platform integrations are added to `domains`,
|
||||
# so they can be setup first instead of discovering them later when a config
|
||||
# entry setup task notices that it's needed and there is already a long line
|
||||
# to use the import executor.
|
||||
|
||||
# Also process all base platforms since we do not require the manifest
|
||||
# to list them as dependencies.
|
||||
# We want to later avoid lock contention when multiple integrations try to load
|
||||
# their manifests at once.
|
||||
#
|
||||
# Additionally process integrations that are defined under base platforms
|
||||
# to speed things up.
|
||||
# For example if we have
|
||||
# sensor:
|
||||
# - platform: template
|
||||
#
|
||||
# `template` has to be loaded to validate the config for sensor
|
||||
# so we want to start loading `sensor` as soon as we know
|
||||
# it will be needed. The more platforms under `sensor:`, the longer
|
||||
# `template` has to be loaded to validate the config for sensor.
|
||||
# The more platforms under `sensor:`, the longer
|
||||
# it will take to finish setup for `sensor` because each of these
|
||||
# platforms has to be imported before we can validate the config.
|
||||
#
|
||||
# Thankfully we are migrating away from the platform pattern
|
||||
# so this will be less of a problem in the future.
|
||||
domains_to_setup.update(platform_integrations)
|
||||
|
||||
# Additionally process base platforms since we do not require the manifest
|
||||
# to list them as dependencies.
|
||||
# We want to later avoid lock contention when multiple integrations try to load
|
||||
# their manifests at once.
|
||||
# Also process integrations that are defined under base platforms
|
||||
# to speed things up.
|
||||
platform_integrations = conf_util.extract_platform_integrations(
|
||||
config, BASE_PLATFORMS
|
||||
)
|
||||
additional_domains_to_process = {
|
||||
*BASE_PLATFORMS,
|
||||
*chain.from_iterable(platform_integrations.values()),
|
||||
|
@@ -6,6 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airgradient",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["airgradient==0.9.2"],
|
||||
"zeroconf": ["_airgradient._tcp.local."]
|
||||
}
|
||||
|
@@ -14,9 +14,9 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-high-level-description: todo
|
||||
docs-installation-instructions: todo
|
||||
docs-removal-instructions: todo
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
@@ -34,7 +34,7 @@ rules:
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: No options to configure
|
||||
docs-installation-parameters: todo
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
@@ -43,23 +43,19 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not require authentication.
|
||||
test-coverage: todo
|
||||
test-coverage: done
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: todo
|
||||
comment: DHCP is still possible
|
||||
discovery:
|
||||
status: todo
|
||||
comment: DHCP is still possible
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations: done
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: done
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
@@ -45,9 +45,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bo
|
||||
# Store Entity and Initialize Platforms
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
# Listen for option changes
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# Clean up unused device entries with no entities
|
||||
@@ -88,8 +85,3 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -13,7 +13,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -126,7 +126,7 @@ class AirNowConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return AirNowOptionsFlowHandler()
|
||||
|
||||
|
||||
class AirNowOptionsFlowHandler(OptionsFlow):
|
||||
class AirNowOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle an options flow for AirNow."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -45,6 +45,8 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
errors = {}
|
||||
await self.async_set_unique_id(user_input[CONF_ID])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
try:
|
||||
await airthings.get_token(
|
||||
@@ -60,9 +62,6 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(user_input[CONF_ID])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(title="Airthings", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
|
@@ -150,7 +150,7 @@ async def async_setup_entry(
|
||||
|
||||
coordinator = entry.runtime_data
|
||||
entities = [
|
||||
AirthingsHeaterEnergySensor(
|
||||
AirthingsDeviceSensor(
|
||||
coordinator,
|
||||
airthings_device,
|
||||
SENSORS[sensor_types],
|
||||
@@ -162,7 +162,7 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AirthingsHeaterEnergySensor(
|
||||
class AirthingsDeviceSensor(
|
||||
CoordinatorEntity[AirthingsDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
"""Representation of a Airthings Sensor device."""
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.6.14"]
|
||||
"requirements": ["aioairzone-cloud==0.7.0"]
|
||||
}
|
||||
|
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["aioamazondevices==3.2.10"]
|
||||
"requirements": ["aioamazondevices==3.5.1"]
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .analytics import Analytics
|
||||
from .const import ATTR_ONBOARDED, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA
|
||||
from .http import AnalyticsDevicesView
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
||||
@@ -55,6 +56,8 @@ async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
|
||||
websocket_api.async_register_command(hass, websocket_analytics)
|
||||
websocket_api.async_register_command(hass, websocket_analytics_preferences)
|
||||
|
||||
hass.http.register_view(AnalyticsDevicesView)
|
||||
|
||||
hass.data[DATA_COMPONENT] = analytics
|
||||
return True
|
||||
|
||||
|
@@ -27,7 +27,7 @@ from homeassistant.config_entries import SOURCE_IGNORE
|
||||
from homeassistant.const import ATTR_DOMAIN, BASE_PLATFORMS, __version__ as HA_VERSION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.storage import Store
|
||||
@@ -77,6 +77,11 @@ from .const import (
|
||||
)
|
||||
|
||||
|
||||
def gen_uuid() -> str:
|
||||
"""Generate a new UUID."""
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalyticsData:
|
||||
"""Analytics data."""
|
||||
@@ -184,7 +189,7 @@ class Analytics:
|
||||
return
|
||||
|
||||
if self._data.uuid is None:
|
||||
self._data.uuid = uuid.uuid4().hex
|
||||
self._data.uuid = gen_uuid()
|
||||
await self._store.async_save(dataclass_asdict(self._data))
|
||||
|
||||
if self.supervisor:
|
||||
@@ -381,3 +386,83 @@ def _domains_from_yaml_config(yaml_configuration: dict[str, Any]) -> set[str]:
|
||||
).values():
|
||||
domains.update(platforms)
|
||||
return domains
|
||||
|
||||
|
||||
async def async_devices_payload(hass: HomeAssistant) -> dict:
|
||||
"""Return the devices payload."""
|
||||
integrations_without_model_id: set[str] = set()
|
||||
devices: list[dict[str, Any]] = []
|
||||
dev_reg = dr.async_get(hass)
|
||||
# Devices that need via device info set
|
||||
new_indexes: dict[str, int] = {}
|
||||
via_devices: dict[str, str] = {}
|
||||
|
||||
seen_integrations = set()
|
||||
|
||||
for device in dev_reg.devices.values():
|
||||
# Ignore services
|
||||
if device.entry_type:
|
||||
continue
|
||||
|
||||
if not device.primary_config_entry:
|
||||
continue
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry(device.primary_config_entry)
|
||||
|
||||
if config_entry is None:
|
||||
continue
|
||||
|
||||
seen_integrations.add(config_entry.domain)
|
||||
|
||||
if not device.model_id:
|
||||
integrations_without_model_id.add(config_entry.domain)
|
||||
continue
|
||||
|
||||
if not device.manufacturer:
|
||||
continue
|
||||
|
||||
new_indexes[device.id] = len(devices)
|
||||
devices.append(
|
||||
{
|
||||
"integration": config_entry.domain,
|
||||
"manufacturer": device.manufacturer,
|
||||
"model_id": device.model_id,
|
||||
"model": device.model,
|
||||
"sw_version": device.sw_version,
|
||||
"hw_version": device.hw_version,
|
||||
"has_suggested_area": device.suggested_area is not None,
|
||||
"has_configuration_url": device.configuration_url is not None,
|
||||
"via_device": None,
|
||||
}
|
||||
)
|
||||
if device.via_device_id:
|
||||
via_devices[device.id] = device.via_device_id
|
||||
|
||||
for from_device, via_device in via_devices.items():
|
||||
if via_device not in new_indexes:
|
||||
continue
|
||||
devices[new_indexes[from_device]]["via_device"] = new_indexes[via_device]
|
||||
|
||||
integrations = {
|
||||
domain: integration
|
||||
for domain, integration in (
|
||||
await async_get_integrations(hass, seen_integrations)
|
||||
).items()
|
||||
if isinstance(integration, Integration)
|
||||
}
|
||||
|
||||
for device_info in devices:
|
||||
if integration := integrations.get(device_info["integration"]):
|
||||
device_info["is_custom_integration"] = not integration.is_built_in
|
||||
|
||||
return {
|
||||
"version": "home-assistant:1",
|
||||
"no_model_id": sorted(
|
||||
[
|
||||
domain
|
||||
for domain in integrations_without_model_id
|
||||
if domain in integrations and integrations[domain].is_built_in
|
||||
]
|
||||
),
|
||||
"devices": devices,
|
||||
}
|
||||
|
27
homeassistant/components/analytics/http.py
Normal file
27
homeassistant/components/analytics/http.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""HTTP endpoints for analytics integration."""
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .analytics import async_devices_payload
|
||||
|
||||
|
||||
class AnalyticsDevicesView(HomeAssistantView):
|
||||
"""View to handle analytics devices payload download requests."""
|
||||
|
||||
url = "/api/analytics/devices"
|
||||
name = "api:analytics:devices"
|
||||
|
||||
@require_admin
|
||||
async def get(self, request: web.Request) -> web.Response:
|
||||
"""Return analytics devices payload as JSON."""
|
||||
hass: HomeAssistant = request.app[KEY_HASS]
|
||||
payload = await async_devices_payload(hass)
|
||||
return self.json(
|
||||
payload,
|
||||
headers={
|
||||
"Content-Disposition": "attachment; filename=analytics_devices.json"
|
||||
},
|
||||
)
|
@@ -3,7 +3,7 @@
|
||||
"name": "Analytics",
|
||||
"after_dependencies": ["energy", "hassio", "recorder"],
|
||||
"codeowners": ["@home-assistant/core", "@ludeeus"],
|
||||
"dependencies": ["api", "websocket_api"],
|
||||
"dependencies": ["api", "websocket_api", "http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/analytics",
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
|
@@ -55,7 +55,6 @@ async def async_setup_entry(
|
||||
entry.runtime_data = AnalyticsInsightsData(coordinator=coordinator, names=names)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
return True
|
||||
|
||||
@@ -65,10 +64,3 @@ async def async_unload_entry(
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(
|
||||
hass: HomeAssistant, entry: AnalyticsInsightsConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -11,7 +11,11 @@ from python_homeassistant_analytics import (
|
||||
from python_homeassistant_analytics.models import Environment, IntegrationType
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import (
|
||||
@@ -129,7 +133,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlow):
|
||||
class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle Homeassistant Analytics options."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -68,7 +68,6 @@ async def async_setup_entry(
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
|
||||
)
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
entry.async_on_unload(api.disconnect)
|
||||
|
||||
return True
|
||||
@@ -80,13 +79,3 @@ async def async_unload_entry(
|
||||
"""Unload a config entry."""
|
||||
_LOGGER.debug("async_unload_entry: %s", entry.data)
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_update_options(
|
||||
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
_LOGGER.debug(
|
||||
"async_update_options: data: %s options: %s", entry.data, entry.options
|
||||
)
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -19,7 +19,7 @@ from homeassistant.config_entries import (
|
||||
SOURCE_RECONFIGURE,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
@@ -116,10 +116,10 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
pin = user_input["pin"]
|
||||
await self.api.async_finish_pairing(pin)
|
||||
if self.source == SOURCE_REAUTH:
|
||||
await self.hass.config_entries.async_reload(
|
||||
self._get_reauth_entry().entry_id
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(), reload_even_if_entry_is_unchanged=True
|
||||
)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
return self.async_create_entry(
|
||||
title=self.name,
|
||||
data={
|
||||
@@ -243,7 +243,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return AndroidTVRemoteOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class AndroidTVRemoteOptionsFlowHandler(OptionsFlow):
|
||||
class AndroidTVRemoteOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Android TV Remote options flow."""
|
||||
|
||||
def __init__(self, config_entry: AndroidTVRemoteConfigEntry) -> None:
|
||||
|
@@ -27,4 +27,4 @@ def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRem
|
||||
|
||||
def get_enable_ime(entry: AndroidTVRemoteConfigEntry) -> bool:
|
||||
"""Get value of enable_ime option or its default value."""
|
||||
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE)
|
||||
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE) # type: ignore[no-any-return]
|
||||
|
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"cannot_receive_deviceinfo": "Failed to retrieve MAC Address. Make sure the device is turned on"
|
||||
"cannot_receive_deviceinfo": "Failed to retrieve MAC address. Make sure the device is turned on"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
|
@@ -6,7 +6,6 @@ from homeassistant.components import conversation
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.const import CONF_LLM_HASS_API, MATCH_ALL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AnthropicConfigEntry
|
||||
@@ -72,13 +71,4 @@ class AnthropicConversationEntity(
|
||||
|
||||
await self._async_handle_chat_log(chat_log)
|
||||
|
||||
response_content = chat_log.content[-1]
|
||||
if not isinstance(response_content, conversation.AssistantContent):
|
||||
raise TypeError("Last message must be an assistant message")
|
||||
intent_response = intent.IntentResponse(language=user_input.language)
|
||||
intent_response.async_set_speech(response_content.content or "")
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response,
|
||||
conversation_id=chat_log.conversation_id,
|
||||
continue_conversation=chat_log.continue_conversation,
|
||||
)
|
||||
return conversation.async_get_result_from_chat_log(user_input, chat_log)
|
||||
|
@@ -311,11 +311,13 @@ def _create_token_stats(
|
||||
class AnthropicBaseLLMEntity(Entity):
|
||||
"""Anthropic base LLM entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, entry: AnthropicConfigEntry, subentry: ConfigSubentry) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.entry = entry
|
||||
self.subentry = subentry
|
||||
self._attr_name = subentry.title
|
||||
self._attr_unique_id = subentry.subentry_id
|
||||
self._attr_device_info = dr.DeviceInfo(
|
||||
identifiers={(DOMAIN, subentry.subentry_id)},
|
||||
|
@@ -29,7 +29,7 @@
|
||||
"set_options": {
|
||||
"data": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"prompt": "Instructions",
|
||||
"prompt": "[%key:common::config_flow::data::prompt%]",
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"max_tokens": "Maximum tokens to return in response",
|
||||
"temperature": "Temperature",
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["arcam"],
|
||||
"requirements": ["arcam-fmj==1.8.1"],
|
||||
"requirements": ["arcam-fmj==1.8.2"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
@@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==8.10.0", "yalexs-ble==3.0.0"]
|
||||
"requirements": ["yalexs==8.10.0", "yalexs-ble==3.1.0"]
|
||||
}
|
||||
|
1
homeassistant/components/bauknecht/__init__.py
Normal file
1
homeassistant/components/bauknecht/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Bauknecht virtual integration."""
|
6
homeassistant/components/bauknecht/manifest.json
Normal file
6
homeassistant/components/bauknecht/manifest.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"domain": "bauknecht",
|
||||
"name": "Bauknecht",
|
||||
"integration_type": "virtual",
|
||||
"supported_by": "whirlpool"
|
||||
}
|
@@ -15,23 +15,31 @@ from bluecurrent_api.exceptions import (
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_NAME, CONF_API_TOKEN, Platform
|
||||
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import DOMAIN, EVSE_ID, LOGGER, MODEL_TYPE
|
||||
from .const import (
|
||||
CHARGEPOINT_SETTINGS,
|
||||
CHARGEPOINT_STATUS,
|
||||
DOMAIN,
|
||||
EVSE_ID,
|
||||
LOGGER,
|
||||
PLUG_AND_CHARGE,
|
||||
VALUE,
|
||||
)
|
||||
|
||||
type BlueCurrentConfigEntry = ConfigEntry[Connector]
|
||||
|
||||
PLATFORMS = [Platform.BUTTON, Platform.SENSOR]
|
||||
PLATFORMS = [Platform.BUTTON, Platform.SENSOR, Platform.SWITCH]
|
||||
CHARGE_POINTS = "CHARGE_POINTS"
|
||||
DATA = "data"
|
||||
DELAY = 5
|
||||
|
||||
GRID = "GRID"
|
||||
OBJECT = "object"
|
||||
VALUE_TYPES = ["CH_STATUS"]
|
||||
VALUE_TYPES = [CHARGEPOINT_STATUS, CHARGEPOINT_SETTINGS]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -94,7 +102,7 @@ class Connector:
|
||||
elif object_name in VALUE_TYPES:
|
||||
value_data: dict = message[DATA]
|
||||
evse_id = value_data.pop(EVSE_ID)
|
||||
self.update_charge_point(evse_id, value_data)
|
||||
self.update_charge_point(evse_id, object_name, value_data)
|
||||
|
||||
# gets grid key / values
|
||||
elif GRID in object_name:
|
||||
@@ -106,26 +114,37 @@ class Connector:
|
||||
"""Handle incoming chargepoint data."""
|
||||
await asyncio.gather(
|
||||
*(
|
||||
self.handle_charge_point(
|
||||
entry[EVSE_ID], entry[MODEL_TYPE], entry[ATTR_NAME]
|
||||
)
|
||||
self.handle_charge_point(entry[EVSE_ID], entry)
|
||||
for entry in charge_points_data
|
||||
),
|
||||
self.client.get_grid_status(charge_points_data[0][EVSE_ID]),
|
||||
)
|
||||
|
||||
async def handle_charge_point(self, evse_id: str, model: str, name: str) -> None:
|
||||
async def handle_charge_point(
|
||||
self, evse_id: str, charge_point: dict[str, Any]
|
||||
) -> None:
|
||||
"""Add the chargepoint and request their data."""
|
||||
self.add_charge_point(evse_id, model, name)
|
||||
self.add_charge_point(evse_id, charge_point)
|
||||
await self.client.get_status(evse_id)
|
||||
|
||||
def add_charge_point(self, evse_id: str, model: str, name: str) -> None:
|
||||
def add_charge_point(self, evse_id: str, charge_point: dict[str, Any]) -> None:
|
||||
"""Add a charge point to charge_points."""
|
||||
self.charge_points[evse_id] = {MODEL_TYPE: model, ATTR_NAME: name}
|
||||
self.charge_points[evse_id] = charge_point
|
||||
|
||||
def update_charge_point(self, evse_id: str, data: dict) -> None:
|
||||
def update_charge_point(self, evse_id: str, update_type: str, data: dict) -> None:
|
||||
"""Update the charge point data."""
|
||||
self.charge_points[evse_id].update(data)
|
||||
charge_point = self.charge_points[evse_id]
|
||||
if update_type == CHARGEPOINT_SETTINGS:
|
||||
# Update the plug and charge object. The library parses this object to a bool instead of an object.
|
||||
plug_and_charge = charge_point.get(PLUG_AND_CHARGE)
|
||||
if plug_and_charge is not None:
|
||||
plug_and_charge[VALUE] = data[PLUG_AND_CHARGE]
|
||||
|
||||
# Remove the plug and charge object from the data list before updating.
|
||||
del data[PLUG_AND_CHARGE]
|
||||
|
||||
charge_point.update(data)
|
||||
|
||||
self.dispatch_charge_point_update_signal(evse_id)
|
||||
|
||||
def dispatch_charge_point_update_signal(self, evse_id: str) -> None:
|
||||
|
@@ -8,3 +8,14 @@ LOGGER = logging.getLogger(__package__)
|
||||
|
||||
EVSE_ID = "evse_id"
|
||||
MODEL_TYPE = "model_type"
|
||||
PLUG_AND_CHARGE = "plug_and_charge"
|
||||
VALUE = "value"
|
||||
PERMISSION = "permission"
|
||||
CHARGEPOINT_STATUS = "CH_STATUS"
|
||||
CHARGEPOINT_SETTINGS = "CH_SETTINGS"
|
||||
BLOCK = "block"
|
||||
UNAVAILABLE = "unavailable"
|
||||
AVAILABLE = "available"
|
||||
LINKED_CHARGE_CARDS = "linked_charge_cards_only"
|
||||
PUBLIC_CHARGING = "public_charging"
|
||||
ACTIVITY = "activity"
|
||||
|
@@ -30,6 +30,17 @@
|
||||
"stop_charge_session": {
|
||||
"default": "mdi:stop"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"plug_and_charge": {
|
||||
"default": "mdi:ev-plug-type2"
|
||||
},
|
||||
"linked_charge_cards": {
|
||||
"default": "mdi:account-group"
|
||||
},
|
||||
"block": {
|
||||
"default": "mdi:lock"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/blue_current",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["bluecurrent_api"],
|
||||
"requirements": ["bluecurrent-api==1.2.3"]
|
||||
"requirements": ["bluecurrent-api==1.2.4"]
|
||||
}
|
||||
|
@@ -124,6 +124,17 @@
|
||||
"reset": {
|
||||
"name": "Reset"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"plug_and_charge": {
|
||||
"name": "Plug & Charge"
|
||||
},
|
||||
"linked_charge_cards_only": {
|
||||
"name": "Linked charging cards only"
|
||||
},
|
||||
"block": {
|
||||
"name": "Block charge point"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
169
homeassistant/components/blue_current/switch.py
Normal file
169
homeassistant/components/blue_current/switch.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""Support for Blue Current switches."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import PLUG_AND_CHARGE, BlueCurrentConfigEntry, Connector
|
||||
from .const import (
|
||||
AVAILABLE,
|
||||
BLOCK,
|
||||
LINKED_CHARGE_CARDS,
|
||||
PUBLIC_CHARGING,
|
||||
UNAVAILABLE,
|
||||
VALUE,
|
||||
)
|
||||
from .entity import ChargepointEntity
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BlueCurrentSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""Describes a Blue Current switch entity."""
|
||||
|
||||
function: Callable[[Connector, str, bool], Any]
|
||||
|
||||
turn_on_off_fn: Callable[[str, Connector], tuple[bool, bool]]
|
||||
"""Update the switch based on the latest data received from the websocket. The first returned boolean is _attr_is_on, the second one has_value."""
|
||||
|
||||
|
||||
def update_on_value_and_activity(
|
||||
key: str, evse_id: str, connector: Connector, reverse_is_on: bool = False
|
||||
) -> tuple[bool, bool]:
|
||||
"""Return the updated state of the switch based on received chargepoint data and activity."""
|
||||
|
||||
data_object = connector.charge_points[evse_id].get(key)
|
||||
is_on = data_object[VALUE] if data_object is not None else None
|
||||
activity = connector.charge_points[evse_id].get("activity")
|
||||
|
||||
if is_on is not None and activity == AVAILABLE:
|
||||
return is_on if not reverse_is_on else not is_on, True
|
||||
return False, False
|
||||
|
||||
|
||||
def update_block_switch(evse_id: str, connector: Connector) -> tuple[bool, bool]:
|
||||
"""Return the updated data for a block switch."""
|
||||
activity = connector.charge_points[evse_id].get("activity")
|
||||
return activity == UNAVAILABLE, activity in [AVAILABLE, UNAVAILABLE]
|
||||
|
||||
|
||||
def update_charge_point(
|
||||
key: str, evse_id: str, connector: Connector, new_switch_value: bool
|
||||
) -> None:
|
||||
"""Change charge point data when the state of the switch changes."""
|
||||
data_objects = connector.charge_points[evse_id].get(key)
|
||||
if data_objects is not None:
|
||||
data_objects[VALUE] = new_switch_value
|
||||
|
||||
|
||||
async def set_plug_and_charge(connector: Connector, evse_id: str, value: bool) -> None:
|
||||
"""Toggle the plug and charge setting for a specific charging point."""
|
||||
await connector.client.set_plug_and_charge(evse_id, value)
|
||||
update_charge_point(PLUG_AND_CHARGE, evse_id, connector, value)
|
||||
|
||||
|
||||
async def set_linked_charge_cards(
|
||||
connector: Connector, evse_id: str, value: bool
|
||||
) -> None:
|
||||
"""Toggle the plug and charge setting for a specific charging point."""
|
||||
await connector.client.set_linked_charge_cards_only(evse_id, value)
|
||||
update_charge_point(PUBLIC_CHARGING, evse_id, connector, not value)
|
||||
|
||||
|
||||
SWITCHES = (
|
||||
BlueCurrentSwitchEntityDescription(
|
||||
key=PLUG_AND_CHARGE,
|
||||
translation_key=PLUG_AND_CHARGE,
|
||||
function=set_plug_and_charge,
|
||||
turn_on_off_fn=lambda evse_id, connector: (
|
||||
update_on_value_and_activity(PLUG_AND_CHARGE, evse_id, connector)
|
||||
),
|
||||
),
|
||||
BlueCurrentSwitchEntityDescription(
|
||||
key=LINKED_CHARGE_CARDS,
|
||||
translation_key=LINKED_CHARGE_CARDS,
|
||||
function=set_linked_charge_cards,
|
||||
turn_on_off_fn=lambda evse_id, connector: (
|
||||
update_on_value_and_activity(
|
||||
PUBLIC_CHARGING, evse_id, connector, reverse_is_on=True
|
||||
)
|
||||
),
|
||||
),
|
||||
BlueCurrentSwitchEntityDescription(
|
||||
key=BLOCK,
|
||||
translation_key=BLOCK,
|
||||
function=lambda connector, evse_id, value: connector.client.block(
|
||||
evse_id, value
|
||||
),
|
||||
turn_on_off_fn=update_block_switch,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: BlueCurrentConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Blue Current switches."""
|
||||
connector = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
ChargePointSwitch(
|
||||
connector,
|
||||
evse_id,
|
||||
switch,
|
||||
)
|
||||
for evse_id in connector.charge_points
|
||||
for switch in SWITCHES
|
||||
)
|
||||
|
||||
|
||||
class ChargePointSwitch(ChargepointEntity, SwitchEntity):
|
||||
"""Base charge point switch."""
|
||||
|
||||
has_value = True
|
||||
entity_description: BlueCurrentSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
connector: Connector,
|
||||
evse_id: str,
|
||||
switch: BlueCurrentSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(connector, evse_id)
|
||||
|
||||
self.key = switch.key
|
||||
self.entity_description = switch
|
||||
self.evse_id = evse_id
|
||||
self._attr_available = True
|
||||
self._attr_unique_id = f"{switch.key}_{evse_id}"
|
||||
|
||||
async def call_function(self, value: bool) -> None:
|
||||
"""Call the function to set setting."""
|
||||
await self.entity_description.function(self.connector, self.evse_id, value)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
await self.call_function(True)
|
||||
self._attr_is_on = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
await self.call_function(False)
|
||||
self._attr_is_on = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Fetch new state data for the switch."""
|
||||
new_state = self.entity_description.turn_on_off_fn(self.evse_id, self.connector)
|
||||
self._attr_is_on = new_state[0]
|
||||
self.has_value = new_state[1]
|
@@ -20,7 +20,7 @@
|
||||
"bluetooth-adapters==2.0.0",
|
||||
"bluetooth-auto-recovery==1.5.2",
|
||||
"bluetooth-data-tools==1.28.2",
|
||||
"dbus-fast==2.43.0",
|
||||
"dbus-fast==2.44.2",
|
||||
"habluetooth==4.0.1"
|
||||
]
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class BTHomePassiveBluetoothProcessorCoordinator(
|
||||
@property
|
||||
def sleepy_device(self) -> bool:
|
||||
"""Return True if the device is a sleepy device."""
|
||||
return self.entry.data.get(CONF_SLEEPY_DEVICE, self.device_data.sleepy_device)
|
||||
return self.entry.data.get(CONF_SLEEPY_DEVICE, self.device_data.sleepy_device) # type: ignore[no-any-return]
|
||||
|
||||
|
||||
class BTHomePassiveBluetoothDataProcessor[_T](
|
||||
|
@@ -70,7 +70,7 @@ def get_event_classes_by_device_id(hass: HomeAssistant, device_id: str) -> list[
|
||||
bthome_config_entry = next(
|
||||
entry for entry in config_entries if entry and entry.domain == DOMAIN
|
||||
)
|
||||
return bthome_config_entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, [])
|
||||
return bthome_config_entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, []) # type: ignore[no-any-return]
|
||||
|
||||
|
||||
def get_event_types_by_event_class(event_class: str) -> set[str]:
|
||||
|
@@ -10,14 +10,8 @@ import random
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
from hass_nabucasa import Cloud, CloudError
|
||||
from hass_nabucasa.api import CloudApiError, CloudApiNonRetryableError
|
||||
from hass_nabucasa.cloud_api import (
|
||||
FilesHandlerListEntry,
|
||||
async_files_delete_file,
|
||||
async_files_list,
|
||||
)
|
||||
from hass_nabucasa.files import FilesError, StorageType, calculate_b64md5
|
||||
from hass_nabucasa import Cloud, CloudApiError, CloudApiNonRetryableError, CloudError
|
||||
from hass_nabucasa.files import FilesError, StorageType, StoredFile, calculate_b64md5
|
||||
|
||||
from homeassistant.components.backup import (
|
||||
AgentBackup,
|
||||
@@ -186,8 +180,7 @@ class CloudBackupAgent(BackupAgent):
|
||||
"""
|
||||
backup = await self._async_get_backup(backup_id)
|
||||
try:
|
||||
await async_files_delete_file(
|
||||
self._cloud,
|
||||
await self._cloud.files.delete(
|
||||
storage_type=StorageType.BACKUP,
|
||||
filename=backup["Key"],
|
||||
)
|
||||
@@ -199,12 +192,10 @@ class CloudBackupAgent(BackupAgent):
|
||||
backups = await self._async_list_backups()
|
||||
return [AgentBackup.from_dict(backup["Metadata"]) for backup in backups]
|
||||
|
||||
async def _async_list_backups(self) -> list[FilesHandlerListEntry]:
|
||||
async def _async_list_backups(self) -> list[StoredFile]:
|
||||
"""List backups."""
|
||||
try:
|
||||
backups = await async_files_list(
|
||||
self._cloud, storage_type=StorageType.BACKUP
|
||||
)
|
||||
backups = await self._cloud.files.list(storage_type=StorageType.BACKUP)
|
||||
except (ClientError, CloudError) as err:
|
||||
raise BackupAgentError("Failed to list backups") from err
|
||||
|
||||
@@ -220,7 +211,7 @@ class CloudBackupAgent(BackupAgent):
|
||||
backup = await self._async_get_backup(backup_id)
|
||||
return AgentBackup.from_dict(backup["Metadata"])
|
||||
|
||||
async def _async_get_backup(self, backup_id: str) -> FilesHandlerListEntry:
|
||||
async def _async_get_backup(self, backup_id: str) -> StoredFile:
|
||||
"""Return a backup."""
|
||||
backups = await self._async_list_backups()
|
||||
|
||||
|
@@ -40,10 +40,11 @@ from .prefs import CloudPreferences
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
VALID_REPAIR_TRANSLATION_KEYS = {
|
||||
"connection_error",
|
||||
"no_subscription",
|
||||
"warn_bad_custom_domain_configuration",
|
||||
"reset_bad_custom_domain_configuration",
|
||||
"subscription_expired",
|
||||
"warn_bad_custom_domain_configuration",
|
||||
}
|
||||
|
||||
|
||||
|
@@ -71,7 +71,7 @@ _CLOUD_ERRORS: dict[
|
||||
] = {
|
||||
TimeoutError: (
|
||||
HTTPStatus.BAD_GATEWAY,
|
||||
"Unable to reach the Home Assistant cloud.",
|
||||
"Unable to reach the Home Assistant Cloud.",
|
||||
),
|
||||
aiohttp.ClientError: (
|
||||
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
|
@@ -13,6 +13,6 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["acme", "hass_nabucasa", "snitun"],
|
||||
"requirements": ["hass-nabucasa==0.107.0"],
|
||||
"requirements": ["hass-nabucasa==0.108.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
@@ -62,6 +62,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"connection_error": {
|
||||
"title": "No connection",
|
||||
"description": "You do not have a connection to Home Assistant Cloud. Check your network."
|
||||
},
|
||||
"no_subscription": {
|
||||
"title": "No subscription detected",
|
||||
"description": "You do not have a Home Assistant Cloud subscription. Subscribe at {account_url}."
|
||||
|
@@ -17,6 +17,8 @@ from homeassistant.components.tts import (
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TextToSpeechEntity,
|
||||
TTSAudioRequest,
|
||||
TTSAudioResponse,
|
||||
TtsAudioType,
|
||||
Voice,
|
||||
)
|
||||
@@ -332,7 +334,7 @@ class CloudTTSEntity(TextToSpeechEntity):
|
||||
def default_options(self) -> dict[str, str]:
|
||||
"""Return a dict include default options."""
|
||||
return {
|
||||
ATTR_AUDIO_OUTPUT: AudioOutput.MP3,
|
||||
ATTR_AUDIO_OUTPUT: AudioOutput.MP3.value,
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -433,6 +435,29 @@ class CloudTTSEntity(TextToSpeechEntity):
|
||||
|
||||
return (options[ATTR_AUDIO_OUTPUT], data)
|
||||
|
||||
async def async_stream_tts_audio(
|
||||
self, request: TTSAudioRequest
|
||||
) -> TTSAudioResponse:
|
||||
"""Generate speech from an incoming message."""
|
||||
data_gen = self.cloud.voice.process_tts_stream(
|
||||
text_stream=request.message_gen,
|
||||
**_prepare_voice_args(
|
||||
hass=self.hass,
|
||||
language=request.language,
|
||||
voice=request.options.get(
|
||||
ATTR_VOICE,
|
||||
(
|
||||
self._voice
|
||||
if request.language == self._language
|
||||
else DEFAULT_VOICES[request.language]
|
||||
),
|
||||
),
|
||||
gender=request.options.get(ATTR_GENDER),
|
||||
),
|
||||
)
|
||||
|
||||
return TTSAudioResponse(AudioOutput.WAV.value, data_gen)
|
||||
|
||||
|
||||
class CloudProvider(Provider):
|
||||
"""Home Assistant Cloud speech API provider."""
|
||||
@@ -526,9 +551,11 @@ class CloudProvider(Provider):
|
||||
language=language,
|
||||
voice=options.get(
|
||||
ATTR_VOICE,
|
||||
self._voice
|
||||
if language == self._language
|
||||
else DEFAULT_VOICES[language],
|
||||
(
|
||||
self._voice
|
||||
if language == self._language
|
||||
else DEFAULT_VOICES[language]
|
||||
),
|
||||
),
|
||||
gender=options.get(ATTR_GENDER),
|
||||
),
|
||||
|
@@ -10,8 +10,6 @@ from typing import Any
|
||||
|
||||
from jsonpath import jsonpath
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
||||
from homeassistant.const import (
|
||||
CONF_COMMAND,
|
||||
CONF_NAME,
|
||||
@@ -188,16 +186,7 @@ class CommandSensor(ManualTriggerSensorEntity):
|
||||
self.entity_id, variables, None
|
||||
)
|
||||
|
||||
if self.device_class not in {
|
||||
SensorDeviceClass.DATE,
|
||||
SensorDeviceClass.TIMESTAMP,
|
||||
}:
|
||||
self._attr_native_value = value
|
||||
elif value is not None:
|
||||
self._attr_native_value = async_parse_date_datetime(
|
||||
value, self.entity_id, self.device_class
|
||||
)
|
||||
|
||||
self._set_native_value_with_possible_timestamp(value)
|
||||
self._process_manual_data(variables)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
@@ -54,16 +54,20 @@ class Control4RuntimeData:
|
||||
type Control4ConfigEntry = ConfigEntry[Control4RuntimeData]
|
||||
|
||||
|
||||
async def call_c4_api_retry(func, *func_args): # noqa: RET503
|
||||
async def call_c4_api_retry(func, *func_args):
|
||||
"""Call C4 API function and retry on failure."""
|
||||
# Ruff doesn't understand this loop - the exception is always raised after the retries
|
||||
exc = None
|
||||
for i in range(API_RETRY_TIMES):
|
||||
try:
|
||||
return await func(*func_args)
|
||||
except client_exceptions.ClientError as exception:
|
||||
_LOGGER.error("Error connecting to Control4 account API: %s", exception)
|
||||
if i == API_RETRY_TIMES - 1:
|
||||
raise ConfigEntryNotReady(exception) from exception
|
||||
_LOGGER.error(
|
||||
"Try: %d, Error connecting to Control4 account API: %s",
|
||||
i + 1,
|
||||
exception,
|
||||
)
|
||||
exc = exception
|
||||
raise ConfigEntryNotReady(exc) from exc
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: Control4ConfigEntry) -> bool:
|
||||
@@ -141,21 +145,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: Control4ConfigEntry) ->
|
||||
ui_configuration=ui_configuration,
|
||||
)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def update_listener(
|
||||
hass: HomeAssistant, config_entry: Control4ConfigEntry
|
||||
) -> None:
|
||||
"""Update when config_entry options update."""
|
||||
_LOGGER.debug("Config entry was updated, rerunning setup")
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: Control4ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@@ -11,7 +11,11 @@ from pyControl4.director import C4Director
|
||||
from pyControl4.error_handling import NotFound, Unauthorized
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@@ -153,7 +157,7 @@ class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return OptionsFlowHandler()
|
||||
|
||||
|
||||
class OptionsFlowHandler(OptionsFlow):
|
||||
class OptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle a option flow for Control4."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -61,6 +61,7 @@ from .entity import ConversationEntity
|
||||
from .http import async_setup as async_setup_conversation_http
|
||||
from .models import AbstractConversationAgent, ConversationInput, ConversationResult
|
||||
from .trace import ConversationTraceEventType, async_conversation_trace_append
|
||||
from .util import async_get_result_from_chat_log
|
||||
|
||||
__all__ = [
|
||||
"DOMAIN",
|
||||
@@ -83,6 +84,7 @@ __all__ = [
|
||||
"async_converse",
|
||||
"async_get_agent_info",
|
||||
"async_get_chat_log",
|
||||
"async_get_result_from_chat_log",
|
||||
"async_set_agent",
|
||||
"async_setup",
|
||||
"async_unset_agent",
|
||||
|
@@ -196,6 +196,7 @@ class ChatLog:
|
||||
extra_system_prompt: str | None = None
|
||||
llm_api: llm.APIInstance | None = None
|
||||
delta_listener: Callable[[ChatLog, dict], None] | None = None
|
||||
llm_input_provided_index = 0
|
||||
|
||||
@property
|
||||
def continue_conversation(self) -> bool:
|
||||
@@ -496,6 +497,7 @@ class ChatLog:
|
||||
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
self.llm_input_provided_index = len(self.content)
|
||||
self.llm_api = llm_api
|
||||
self.extra_system_prompt = extra_system_prompt
|
||||
self.content[0] = SystemContent(content=prompt)
|
||||
|
47
homeassistant/components/conversation/util.py
Normal file
47
homeassistant/components/conversation/util.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Utility functions for conversation integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import intent, llm
|
||||
|
||||
from .chat_log import AssistantContent, ChatLog, ToolResultContent
|
||||
from .models import ConversationInput, ConversationResult
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_result_from_chat_log(
|
||||
user_input: ConversationInput, chat_log: ChatLog
|
||||
) -> ConversationResult:
|
||||
"""Get the result from the chat log."""
|
||||
tool_results = [
|
||||
content.tool_result
|
||||
for content in chat_log.content[chat_log.llm_input_provided_index :]
|
||||
if isinstance(content, ToolResultContent)
|
||||
and isinstance(content.tool_result, llm.IntentResponseDict)
|
||||
]
|
||||
|
||||
if tool_results:
|
||||
intent_response = tool_results[-1].original
|
||||
else:
|
||||
intent_response = intent.IntentResponse(language=user_input.language)
|
||||
|
||||
if not isinstance((last_content := chat_log.content[-1]), AssistantContent):
|
||||
_LOGGER.error(
|
||||
"Last content in chat log is not an AssistantContent: %s. This could be due to the model not returning a valid response",
|
||||
last_content,
|
||||
)
|
||||
raise HomeAssistantError("Unable to get response")
|
||||
|
||||
intent_response.async_set_speech(last_content.content or "")
|
||||
|
||||
return ConversationResult(
|
||||
response=intent_response,
|
||||
conversation_id=chat_log.conversation_id,
|
||||
continue_conversation=chat_log.continue_conversation,
|
||||
)
|
@@ -2,9 +2,10 @@
|
||||
|
||||
import logging
|
||||
|
||||
from datadog import initialize, statsd
|
||||
from datadog import DogStatsd, initialize
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
@@ -17,14 +18,19 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, state as state_helper
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import config_flow as config_flow
|
||||
from .const import (
|
||||
CONF_RATE,
|
||||
DEFAULT_HOST,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_PREFIX,
|
||||
DEFAULT_RATE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_RATE = "rate"
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 8125
|
||||
DEFAULT_PREFIX = "hass"
|
||||
DEFAULT_RATE = 1
|
||||
DOMAIN = "datadog"
|
||||
type DatadogConfigEntry = ConfigEntry[DogStatsd]
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -43,63 +49,85 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Datadog component."""
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Datadog integration from YAML, initiating config flow import."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
host = conf[CONF_HOST]
|
||||
port = conf[CONF_PORT]
|
||||
sample_rate = conf[CONF_RATE]
|
||||
prefix = conf[CONF_PREFIX]
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=config[DOMAIN],
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: DatadogConfigEntry) -> bool:
|
||||
"""Set up Datadog from a config entry."""
|
||||
|
||||
data = entry.data
|
||||
options = entry.options
|
||||
host = data[CONF_HOST]
|
||||
port = data[CONF_PORT]
|
||||
prefix = options[CONF_PREFIX]
|
||||
sample_rate = options[CONF_RATE]
|
||||
|
||||
statsd_client = DogStatsd(host=host, port=port, namespace=prefix)
|
||||
entry.runtime_data = statsd_client
|
||||
|
||||
initialize(statsd_host=host, statsd_port=port)
|
||||
|
||||
def logbook_entry_listener(event):
|
||||
"""Listen for logbook entries and send them as events."""
|
||||
name = event.data.get("name")
|
||||
message = event.data.get("message")
|
||||
|
||||
statsd.event(
|
||||
entry.runtime_data.event(
|
||||
title="Home Assistant",
|
||||
text=f"%%% \n **{name}** {message} \n %%%",
|
||||
message=f"%%% \n **{name}** {message} \n %%%",
|
||||
tags=[
|
||||
f"entity:{event.data.get('entity_id')}",
|
||||
f"domain:{event.data.get('domain')}",
|
||||
],
|
||||
)
|
||||
|
||||
_LOGGER.debug("Sent event %s", event.data.get("entity_id"))
|
||||
|
||||
def state_changed_listener(event):
|
||||
"""Listen for new messages on the bus and sends them to Datadog."""
|
||||
state = event.data.get("new_state")
|
||||
|
||||
if state is None or state.state == STATE_UNKNOWN:
|
||||
return
|
||||
|
||||
states = dict(state.attributes)
|
||||
metric = f"{prefix}.{state.domain}"
|
||||
tags = [f"entity:{state.entity_id}"]
|
||||
|
||||
for key, value in states.items():
|
||||
if isinstance(value, (float, int)):
|
||||
attribute = f"{metric}.{key.replace(' ', '_')}"
|
||||
for key, value in state.attributes.items():
|
||||
if isinstance(value, (float, int, bool)):
|
||||
value = int(value) if isinstance(value, bool) else value
|
||||
statsd.gauge(attribute, value, sample_rate=sample_rate, tags=tags)
|
||||
|
||||
_LOGGER.debug("Sent metric %s: %s (tags: %s)", attribute, value, tags)
|
||||
attribute = f"{metric}.{key.replace(' ', '_')}"
|
||||
entry.runtime_data.gauge(
|
||||
attribute, value, sample_rate=sample_rate, tags=tags
|
||||
)
|
||||
|
||||
try:
|
||||
value = state_helper.state_as_number(state)
|
||||
entry.runtime_data.gauge(metric, value, sample_rate=sample_rate, tags=tags)
|
||||
except ValueError:
|
||||
_LOGGER.debug("Error sending %s: %s (tags: %s)", metric, state.state, tags)
|
||||
return
|
||||
pass
|
||||
|
||||
statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags)
|
||||
|
||||
_LOGGER.debug("Sent metric %s: %s (tags: %s)", metric, value, tags)
|
||||
|
||||
hass.bus.listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener)
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, state_changed_listener)
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener)
|
||||
)
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen(EVENT_STATE_CHANGED, state_changed_listener)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: DatadogConfigEntry) -> bool:
|
||||
"""Unload a Datadog config entry."""
|
||||
runtime = entry.runtime_data
|
||||
runtime.flush()
|
||||
runtime.close_socket()
|
||||
return True
|
||||
|
185
homeassistant/components/datadog/config_flow.py
Normal file
185
homeassistant/components/datadog/config_flow.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""Config flow for Datadog."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from datadog import DogStatsd
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PREFIX
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
|
||||
from .const import (
|
||||
CONF_RATE,
|
||||
DEFAULT_HOST,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_PREFIX,
|
||||
DEFAULT_RATE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
class DatadogConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Datadog."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle user config flow."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input:
|
||||
# Validate connection to Datadog Agent
|
||||
success = await validate_datadog_connection(
|
||||
self.hass,
|
||||
user_input,
|
||||
)
|
||||
self._async_abort_entries_match(
|
||||
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
|
||||
)
|
||||
if not success:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=f"Datadog {user_input['host']}",
|
||||
data={
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
},
|
||||
options={
|
||||
CONF_PREFIX: user_input[CONF_PREFIX],
|
||||
CONF_RATE: user_input[CONF_RATE],
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST, default=DEFAULT_HOST): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
vol.Required(CONF_PREFIX, default=DEFAULT_PREFIX): str,
|
||||
vol.Required(CONF_RATE, default=DEFAULT_RATE): int,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Handle import from configuration.yaml."""
|
||||
# Check for duplicates
|
||||
self._async_abort_entries_match(
|
||||
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
|
||||
)
|
||||
|
||||
result = await self.async_step_user(user_input)
|
||||
|
||||
if errors := result.get("errors"):
|
||||
await deprecate_yaml_issue(self.hass, False)
|
||||
return self.async_abort(reason=errors["base"])
|
||||
|
||||
await deprecate_yaml_issue(self.hass, True)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
|
||||
"""Get the options flow handler."""
|
||||
return DatadogOptionsFlowHandler()
|
||||
|
||||
|
||||
class DatadogOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle Datadog options."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the Datadog options."""
|
||||
errors: dict[str, str] = {}
|
||||
data = self.config_entry.data
|
||||
options = self.config_entry.options
|
||||
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
|
||||
success = await validate_datadog_connection(
|
||||
self.hass,
|
||||
{**data, **user_input},
|
||||
)
|
||||
if success:
|
||||
return self.async_create_entry(
|
||||
data={
|
||||
CONF_PREFIX: user_input[CONF_PREFIX],
|
||||
CONF_RATE: user_input[CONF_RATE],
|
||||
}
|
||||
)
|
||||
|
||||
errors["base"] = "cannot_connect"
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PREFIX, default=options[CONF_PREFIX]): str,
|
||||
vol.Required(CONF_RATE, default=options[CONF_RATE]): int,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
async def validate_datadog_connection(
|
||||
hass: HomeAssistant, user_input: dict[str, Any]
|
||||
) -> bool:
|
||||
"""Attempt to send a test metric to the Datadog agent."""
|
||||
try:
|
||||
client = DogStatsd(user_input[CONF_HOST], user_input[CONF_PORT])
|
||||
await hass.async_add_executor_job(client.increment, "connection_test")
|
||||
except (OSError, ValueError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
async def deprecate_yaml_issue(
|
||||
hass: HomeAssistant,
|
||||
import_success: bool,
|
||||
) -> None:
|
||||
"""Create an issue to deprecate YAML config."""
|
||||
if import_success:
|
||||
async_create_issue(
|
||||
hass,
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
f"deprecated_yaml_{DOMAIN}",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
breaks_in_ha_version="2026.2.0",
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": "Datadog",
|
||||
},
|
||||
)
|
||||
else:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_import_connection_error",
|
||||
breaks_in_ha_version="2026.2.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_import_connection_error",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": "Datadog",
|
||||
"url": f"/config/integrations/dashboard/add?domain={DOMAIN}",
|
||||
},
|
||||
)
|
10
homeassistant/components/datadog/const.py
Normal file
10
homeassistant/components/datadog/const.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Constants for the Datadog integration."""
|
||||
|
||||
DOMAIN = "datadog"
|
||||
|
||||
CONF_RATE = "rate"
|
||||
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 8125
|
||||
DEFAULT_PREFIX = "hass"
|
||||
DEFAULT_RATE = 1
|
@@ -2,6 +2,7 @@
|
||||
"domain": "datadog",
|
||||
"name": "Datadog",
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/datadog",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["datadog"],
|
||||
|
56
homeassistant/components/datadog/strings.json
Normal file
56
homeassistant/components/datadog/strings.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Enter your Datadog Agent's address and port.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
"prefix": "Prefix",
|
||||
"rate": "Rate"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of the Datadog Agent.",
|
||||
"port": "Port the Datadog Agent is listening on",
|
||||
"prefix": "Metric prefix to use",
|
||||
"rate": "The sample rate of UDP packets sent to Datadog."
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Update the Datadog configuration.",
|
||||
"data": {
|
||||
"prefix": "[%key:component::datadog::config::step::user::data::prefix%]",
|
||||
"rate": "[%key:component::datadog::config::step::user::data::rate%]"
|
||||
},
|
||||
"data_description": {
|
||||
"prefix": "[%key:component::datadog::config::step::user::data_description::prefix%]",
|
||||
"rate": "[%key:component::datadog::config::step::user::data_description::rate%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_connection_error": {
|
||||
"title": "{domain} YAML configuration import failed",
|
||||
"description": "There was an error connecting to the Datadog Agent when trying to import the YAML configuration.\n\nEnsure the YAML configuration is correct and restart Home Assistant to try again or remove the {domain} configuration from your `configuration.yaml` file and continue to [set up the integration]({url}) manually."
|
||||
}
|
||||
}
|
||||
}
|
@@ -48,11 +48,11 @@ SUPPORT_ALL_SERVICES = (
|
||||
)
|
||||
|
||||
FAN_SPEEDS = ["min", "medium", "high", "max"]
|
||||
DEMO_VACUUM_COMPLETE = "0_Ground_floor"
|
||||
DEMO_VACUUM_MOST = "1_First_floor"
|
||||
DEMO_VACUUM_BASIC = "2_Second_floor"
|
||||
DEMO_VACUUM_MINIMAL = "3_Third_floor"
|
||||
DEMO_VACUUM_NONE = "4_Fourth_floor"
|
||||
DEMO_VACUUM_COMPLETE = "Demo vacuum 0 ground floor"
|
||||
DEMO_VACUUM_MOST = "Demo vacuum 1 first floor"
|
||||
DEMO_VACUUM_BASIC = "Demo vacuum 2 second floor"
|
||||
DEMO_VACUUM_MINIMAL = "Demo vacuum 3 third floor"
|
||||
DEMO_VACUUM_NONE = "Demo vacuum 4 fourth floor"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@@ -53,8 +53,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: DenonavrConfigEntry) ->
|
||||
raise ConfigEntryNotReady from ex
|
||||
receiver = connect_denonavr.receiver
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
entry.runtime_data = receiver
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
@@ -100,10 +98,3 @@ async def async_unload_entry(
|
||||
_LOGGER.debug("Removing zone3 from DenonAvr")
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def update_listener(
|
||||
hass: HomeAssistant, config_entry: DenonavrConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
@@ -10,7 +10,11 @@ import denonavr
|
||||
from denonavr.exceptions import AvrNetworkError, AvrTimoutError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_TYPE
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
@@ -51,7 +55,7 @@ DEFAULT_USE_TELNET_NEW_INSTALL = True
|
||||
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str})
|
||||
|
||||
|
||||
class OptionsFlowHandler(OptionsFlow):
|
||||
class OptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Options for the component."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -320,7 +320,12 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
# changed state, then we know it will still be zero.
|
||||
return
|
||||
schedule_max_sub_interval_exceeded(new_state)
|
||||
calc_derivative(new_state, new_state.state, event.data["old_last_reported"])
|
||||
calc_derivative(
|
||||
new_state,
|
||||
new_state.state,
|
||||
event.data["last_reported"],
|
||||
event.data["old_last_reported"],
|
||||
)
|
||||
|
||||
@callback
|
||||
def on_state_changed(event: Event[EventStateChangedData]) -> None:
|
||||
@@ -334,19 +339,27 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
schedule_max_sub_interval_exceeded(new_state)
|
||||
old_state = event.data["old_state"]
|
||||
if old_state is not None:
|
||||
calc_derivative(new_state, old_state.state, old_state.last_reported)
|
||||
calc_derivative(
|
||||
new_state,
|
||||
old_state.state,
|
||||
new_state.last_updated,
|
||||
old_state.last_reported,
|
||||
)
|
||||
else:
|
||||
# On first state change from none, update availability
|
||||
self.async_write_ha_state()
|
||||
|
||||
def calc_derivative(
|
||||
new_state: State, old_value: str, old_last_reported: datetime
|
||||
new_state: State,
|
||||
old_value: str,
|
||||
new_timestamp: datetime,
|
||||
old_timestamp: datetime,
|
||||
) -> None:
|
||||
"""Handle the sensor state changes."""
|
||||
if not _is_decimal_state(old_value):
|
||||
if self._last_valid_state_time:
|
||||
old_value = self._last_valid_state_time[0]
|
||||
old_last_reported = self._last_valid_state_time[1]
|
||||
old_timestamp = self._last_valid_state_time[1]
|
||||
else:
|
||||
# Sensor becomes valid for the first time, just keep the restored value
|
||||
self.async_write_ha_state()
|
||||
@@ -358,12 +371,10 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
"" if unit is None else unit
|
||||
)
|
||||
|
||||
self._prune_state_list(new_state.last_reported)
|
||||
self._prune_state_list(new_timestamp)
|
||||
|
||||
try:
|
||||
elapsed_time = (
|
||||
new_state.last_reported - old_last_reported
|
||||
).total_seconds()
|
||||
elapsed_time = (new_timestamp - old_timestamp).total_seconds()
|
||||
delta_value = Decimal(new_state.state) - Decimal(old_value)
|
||||
new_derivative = (
|
||||
delta_value
|
||||
@@ -392,12 +403,10 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
return
|
||||
|
||||
# add latest derivative to the window list
|
||||
self._state_list.append(
|
||||
(old_last_reported, new_state.last_reported, new_derivative)
|
||||
)
|
||||
self._state_list.append((old_timestamp, new_timestamp, new_derivative))
|
||||
self._last_valid_state_time = (
|
||||
new_state.state,
|
||||
new_state.last_reported,
|
||||
new_timestamp,
|
||||
)
|
||||
|
||||
# If outside of time window just report derivative (is the same as modeling it in the window),
|
||||
@@ -405,9 +414,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
if elapsed_time > self._time_window:
|
||||
derivative = new_derivative
|
||||
else:
|
||||
derivative = self._calc_derivative_from_state_list(
|
||||
new_state.last_reported
|
||||
)
|
||||
derivative = self._calc_derivative_from_state_list(new_timestamp)
|
||||
self._write_native_value(derivative)
|
||||
|
||||
source_state = self.hass.states.get(self._sensor_source_id)
|
||||
|
@@ -61,7 +61,7 @@
|
||||
"message": "Failed to connect to devolo Home Control central unit {gateway_id}."
|
||||
},
|
||||
"invalid_auth": {
|
||||
"message": "Authentication failed. Please re-authenticaticate with your mydevolo account."
|
||||
"message": "Authentication failed. Please re-authenticate with your mydevolo account."
|
||||
},
|
||||
"maintenance": {
|
||||
"message": "devolo Home Control is currently in maintenance mode."
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"ip_address": "IP address of your devolo Home Network device. This can be found in the devolo Home Network App on the device dashboard.",
|
||||
"ip_address": "IP address of your devolo Home Network device. This can be found in the devolo Home Network app on the device dashboard.",
|
||||
"password": "Password you protected the device with."
|
||||
}
|
||||
},
|
||||
@@ -22,8 +22,8 @@
|
||||
}
|
||||
},
|
||||
"zeroconf_confirm": {
|
||||
"description": "Do you want to add the devolo home network device with the hostname `{host_name}` to Home Assistant?",
|
||||
"title": "Discovered devolo home network device",
|
||||
"description": "Do you want to add the devolo Home Network device with the hostname `{host_name}` to Home Assistant?",
|
||||
"title": "Discovered devolo Home Network device",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
@@ -105,7 +105,7 @@
|
||||
"message": "Device {title} did not respond"
|
||||
},
|
||||
"password_protected": {
|
||||
"message": "Device {title} requires re-authenticatication to set or change the password"
|
||||
"message": "Device {title} requires re-authentication to set or change the password"
|
||||
},
|
||||
"password_wrong": {
|
||||
"message": "The used password is wrong"
|
||||
|
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/discovergy",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pydiscovergy==3.0.2"]
|
||||
}
|
||||
|
@@ -57,13 +57,16 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration cannot be discovered, it is a connecting to a cloud service.
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations:
|
||||
status: exempt
|
||||
comment: |
|
||||
The integration does not have any known limitations.
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: done
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
@@ -8,7 +8,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["async_upnp_client"],
|
||||
"requirements": ["async-upnp-client==0.44.0", "getmac==0.9.5"],
|
||||
"requirements": ["async-upnp-client==0.45.0", "getmac==0.9.5"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"dependencies": ["ssdp"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/dlna_dms",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["async-upnp-client==0.44.0"],
|
||||
"requirements": ["async-upnp-client==0.45.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
||||
|
@@ -13,15 +13,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up DNS IP from a config entry."""
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
return True
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload dnsip config entry."""
|
||||
|
||||
|
@@ -14,7 +14,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME, CONF_PORT
|
||||
from homeassistant.core import callback
|
||||
@@ -165,7 +165,7 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class DnsIPOptionsFlowHandler(OptionsFlow):
|
||||
class DnsIPOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle a option config flow for dnsip integration."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -2,11 +2,11 @@
|
||||
"services": {
|
||||
"order": {
|
||||
"name": "Order",
|
||||
"description": "Places a set of orders with Dominos Pizza.",
|
||||
"description": "Places a set of orders with Domino's Pizza.",
|
||||
"fields": {
|
||||
"order_entity_id": {
|
||||
"name": "Order entity",
|
||||
"description": "The ID (as specified in the configuration) of an order to place. If provided as an array, all of the identified orders will be placed."
|
||||
"description": "The ID (as specified in the configuration) of an order to place. If provided as an array, all the identified orders will be placed."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -222,7 +222,7 @@
|
||||
"data": {
|
||||
"time_between_update": "Minimum time between entity updates [s]"
|
||||
},
|
||||
"title": "DSMR Options"
|
||||
"title": "DSMR options"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -263,7 +263,7 @@
|
||||
"issues": {
|
||||
"cannot_subscribe_mqtt_topic": {
|
||||
"title": "Cannot subscribe to MQTT topic {topic_title}",
|
||||
"description": "The DSMR Reader integration cannot subscribe to the MQTT topic: `{topic}`. Please check the configuration of the MQTT broker and the topic.\nDSMR Reader needs to be running, before starting this integration."
|
||||
"description": "The DSMR Reader integration cannot subscribe to the MQTT topic: `{topic}`. Please check the configuration of the MQTT broker and the topic.\nDSMR Reader needs to be running before starting this integration."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eheimdigital"],
|
||||
"quality_scale": "bronze",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["eheimdigital==1.3.0"],
|
||||
"zeroconf": [
|
||||
{ "type": "_http._tcp.local.", "name": "eheimdigital._http._tcp.local." }
|
||||
|
@@ -46,22 +46,24 @@ rules:
|
||||
diagnostics: done
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations: done
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: done
|
||||
dynamic-devices: done
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
icon-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues: todo
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: No repairs.
|
||||
stale-devices: done
|
||||
|
||||
# Platinum
|
||||
|
@@ -69,16 +69,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: EmonCMSConfigEntry) -> b
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: EmonCMSConfigEntry):
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: EmonCMSConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@@ -11,7 +11,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_URL
|
||||
from homeassistant.core import callback
|
||||
@@ -221,7 +221,7 @@ class EmoncmsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class EmoncmsOptionsFlow(OptionsFlow):
|
||||
class EmoncmsOptionsFlow(OptionsFlowWithReload):
|
||||
"""Emoncms Options flow handler."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from pyenphase import Envoy
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
@@ -47,17 +46,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: EnphaseConfigEntry) -> b
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# Reload entry when it is updated.
|
||||
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Reload the config entry when it changed."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: EnphaseConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
@@ -14,7 +14,7 @@ from homeassistant.config_entries import (
|
||||
SOURCE_REAUTH,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@@ -335,7 +335,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class EnvoyOptionsFlowHandler(OptionsFlow):
|
||||
class EnvoyOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Envoy config flow options handler."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyenphase==2.2.1"],
|
||||
"requirements": ["pyenphase==2.2.2"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
@@ -295,23 +295,7 @@ class RuntimeEntryData:
|
||||
needed_platforms.add(Platform.BINARY_SENSOR)
|
||||
needed_platforms.add(Platform.SELECT)
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
registry_get_entity = ent_reg.async_get_entity_id
|
||||
for info in infos:
|
||||
platform = INFO_TYPE_TO_PLATFORM[type(info)]
|
||||
needed_platforms.add(platform)
|
||||
# If the unique id is in the old format, migrate it
|
||||
# except if they downgraded and upgraded, there might be a duplicate
|
||||
# so we want to keep the one that was already there.
|
||||
if (
|
||||
(old_unique_id := info.unique_id)
|
||||
and (old_entry := registry_get_entity(platform, DOMAIN, old_unique_id))
|
||||
and (new_unique_id := build_device_unique_id(mac, info))
|
||||
!= old_unique_id
|
||||
and not registry_get_entity(platform, DOMAIN, new_unique_id)
|
||||
):
|
||||
ent_reg.async_update_entity(old_entry, new_unique_id=new_unique_id)
|
||||
|
||||
needed_platforms.update(INFO_TYPE_TO_PLATFORM[type(info)] for info in infos)
|
||||
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
||||
|
||||
# Make a dict of the EntityInfo by type and send
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==35.0.0",
|
||||
"aioesphomeapi==37.0.2",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.1.0"
|
||||
],
|
||||
|
@@ -94,8 +94,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: EzvizConfigEntry) -> boo
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
|
||||
|
||||
# Check EZVIZ cloud account entity is present, reload cloud account entities for camera entity change to take effect.
|
||||
# Cameras are accessed via local RTSP stream with unique credentials per camera.
|
||||
# Separate camera entities allow for credential changes per camera.
|
||||
@@ -120,8 +118,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: EzvizConfigEntry) -> bo
|
||||
return await hass.config_entries.async_unload_platforms(
|
||||
entry, PLATFORMS_BY_TYPE[sensor_type]
|
||||
)
|
||||
|
||||
|
||||
async def _async_update_listener(hass: HomeAssistant, entry: EzvizConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -17,7 +17,11 @@ from pyezvizapi.exceptions import (
|
||||
from pyezvizapi.test_cam_rtsp import TestRTSPAuth
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_CUSTOMIZE,
|
||||
CONF_IP_ADDRESS,
|
||||
@@ -386,7 +390,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class EzvizOptionsFlowHandler(OptionsFlow):
|
||||
class EzvizOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle EZVIZ client options."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -32,8 +32,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry) -
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -46,10 +44,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry)
|
||||
if len(entries) == 1:
|
||||
hass.data.pop(MY_KEY)
|
||||
return await hass.config_entries.async_unload_platforms(entry, [Platform.EVENT])
|
||||
|
||||
|
||||
async def _async_update_listener(
|
||||
hass: HomeAssistant, entry: FeedReaderConfigEntry
|
||||
) -> None:
|
||||
"""Handle reconfiguration."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -14,7 +14,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_URL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -44,7 +44,7 @@ class FeedReaderConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> OptionsFlow:
|
||||
) -> FeedReaderOptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return FeedReaderOptionsFlowHandler()
|
||||
|
||||
@@ -119,11 +119,10 @@ class FeedReaderConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors={"base": "url_error"},
|
||||
)
|
||||
|
||||
self.hass.config_entries.async_update_entry(reconfigure_entry, data=user_input)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
return self.async_update_reload_and_abort(reconfigure_entry, data=user_input)
|
||||
|
||||
|
||||
class FeedReaderOptionsFlowHandler(OptionsFlow):
|
||||
class FeedReaderOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle an options flow."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -29,7 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
entry, [Platform(entry.data[CONF_PLATFORM])]
|
||||
)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
return True
|
||||
|
||||
@@ -41,11 +40,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Migrate config entry."""
|
||||
if config_entry.version > 2:
|
||||
|
@@ -11,7 +11,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_FILE_PATH,
|
||||
@@ -131,7 +131,7 @@ class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return await self._async_handle_step(Platform.SENSOR.value, user_input)
|
||||
|
||||
|
||||
class FileOptionsFlowHandler(OptionsFlow):
|
||||
class FileOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle File options."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -47,8 +47,6 @@ async def async_setup_entry(
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -57,10 +55,3 @@ async def async_unload_entry(
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_update_options(
|
||||
hass: HomeAssistant, entry: ForecastSolarConfigEntry
|
||||
) -> None:
|
||||
"""Update options."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -11,7 +11,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
@@ -88,7 +88,7 @@ class ForecastSolarFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class ForecastSolarOptionFlowHandler(OptionsFlow):
|
||||
class ForecastSolarOptionFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle options."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -75,8 +75,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
|
||||
if FRITZ_DATA_KEY not in hass.data:
|
||||
hass.data[FRITZ_DATA_KEY] = FritzData()
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
# Load the other platforms like switch
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -94,9 +92,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> bo
|
||||
hass.data.pop(FRITZ_DATA_KEY)
|
||||
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: FritzConfigEntry) -> None:
|
||||
"""Update when config_entry options update."""
|
||||
if entry.options:
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@@ -17,7 +17,11 @@ from homeassistant.components.device_tracker import (
|
||||
CONF_CONSIDER_HOME,
|
||||
DEFAULT_CONSIDER_HOME,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@@ -409,7 +413,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class FritzBoxToolsOptionsFlowHandler(OptionsFlow):
|
||||
class FritzBoxToolsOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle an options flow."""
|
||||
|
||||
async def async_step_init(
|
||||
|
@@ -214,7 +214,7 @@
|
||||
"message": "Unable to establish a connection"
|
||||
},
|
||||
"update_failed": {
|
||||
"message": "Error while uptaing the data: {error}"
|
||||
"message": "Error while updating the data: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -48,7 +48,6 @@ async def async_setup_entry(
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
config_entry.runtime_data = fritzbox_phonebook
|
||||
config_entry.async_on_unload(config_entry.add_update_listener(update_listener))
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
@@ -59,10 +58,3 @@ async def async_unload_entry(
|
||||
) -> bool:
|
||||
"""Unloading the fritzbox_callmonitor platforms."""
|
||||
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(
|
||||
hass: HomeAssistant, config_entry: FritzBoxCallMonitorConfigEntry
|
||||
) -> None:
|
||||
"""Update listener to reload after option has changed."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
@@ -15,7 +15,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithReload,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
@@ -263,7 +263,7 @@ class FritzBoxCallMonitorConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
|
||||
class FritzBoxCallMonitorOptionsFlowHandler(OptionsFlow):
|
||||
class FritzBoxCallMonitorOptionsFlowHandler(OptionsFlowWithReload):
|
||||
"""Handle a fritzbox_callmonitor options flow."""
|
||||
|
||||
@classmethod
|
||||
|
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20250702.2"]
|
||||
"requirements": ["home-assistant-frontend==20250702.3"]
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ from homeassistant.components.climate import (
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
FAN_OFF,
|
||||
SWING_BOTH,
|
||||
SWING_HORIZONTAL,
|
||||
SWING_OFF,
|
||||
@@ -31,6 +32,7 @@ from .coordinator import FGLairConfigEntry, FGLairCoordinator
|
||||
from .entity import FGLairEntity
|
||||
|
||||
HA_TO_FUJI_FAN = {
|
||||
FAN_OFF: FanSpeed.QUIET,
|
||||
FAN_LOW: FanSpeed.LOW,
|
||||
FAN_MEDIUM: FanSpeed.MEDIUM,
|
||||
FAN_HIGH: FanSpeed.HIGH,
|
||||
|
@@ -13,6 +13,7 @@ from gardena_bluetooth.parse import (
|
||||
)
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
@@ -54,6 +55,7 @@ DESCRIPTIONS = (
|
||||
native_step=60,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
char=Valve.manual_watering_time,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
),
|
||||
GardenaBluetoothNumberEntityDescription(
|
||||
key=Valve.remaining_open_time.uuid,
|
||||
@@ -64,6 +66,7 @@ DESCRIPTIONS = (
|
||||
native_step=60.0,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
char=Valve.remaining_open_time,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
),
|
||||
GardenaBluetoothNumberEntityDescription(
|
||||
key=DeviceConfiguration.rain_pause.uuid,
|
||||
@@ -75,6 +78,7 @@ DESCRIPTIONS = (
|
||||
native_step=6 * 60.0,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
char=DeviceConfiguration.rain_pause,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
),
|
||||
GardenaBluetoothNumberEntityDescription(
|
||||
key=DeviceConfiguration.seasonal_adjust.uuid,
|
||||
@@ -86,6 +90,7 @@ DESCRIPTIONS = (
|
||||
native_step=1.0,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
char=DeviceConfiguration.seasonal_adjust,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
),
|
||||
GardenaBluetoothNumberEntityDescription(
|
||||
key=Sensor.threshold.uuid,
|
||||
@@ -153,6 +158,7 @@ class GardenaBluetoothRemainingOpenSetNumber(GardenaBluetoothEntity, NumberEntit
|
||||
_attr_native_min_value = 0.0
|
||||
_attr_native_max_value = 24 * 60
|
||||
_attr_native_step = 1.0
|
||||
_attr_device_class = NumberDeviceClass.DURATION
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user