mirror of
https://github.com/home-assistant/core.git
synced 2026-04-09 00:45:19 +00:00
Compare commits
113 Commits
edenhaus-t
...
adjust_dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4a117e6b1 | ||
|
|
1b4286381d | ||
|
|
524c2129eb | ||
|
|
8fe09e1837 | ||
|
|
99a186fad7 | ||
|
|
c1a9f293a7 | ||
|
|
783e2f0a00 | ||
|
|
36045c4bd3 | ||
|
|
18cd488622 | ||
|
|
ef66446a0d | ||
|
|
4870bb749c | ||
|
|
2e2ad0aaec | ||
|
|
fa7576dc5a | ||
|
|
c6ec90c871 | ||
|
|
c2065f1f14 | ||
|
|
5197722733 | ||
|
|
49d63892d1 | ||
|
|
713054f9f8 | ||
|
|
0b67644b97 | ||
|
|
f2001db68c | ||
|
|
df08d989f2 | ||
|
|
d5c7a04751 | ||
|
|
3369dfece1 | ||
|
|
7268571587 | ||
|
|
2341f8dd5a | ||
|
|
dd1722b5d6 | ||
|
|
c8667addd8 | ||
|
|
3b9a9ca6cb | ||
|
|
52050711a3 | ||
|
|
17abdd02d3 | ||
|
|
996f9fdca2 | ||
|
|
a434a0ab90 | ||
|
|
7bff0e2f3f | ||
|
|
9cf6911b7f | ||
|
|
b0201e893e | ||
|
|
df74d76ff2 | ||
|
|
6dc391e169 | ||
|
|
c7cf78952e | ||
|
|
2591cf2b3d | ||
|
|
2b1c93724f | ||
|
|
899b776e54 | ||
|
|
423b694a0d | ||
|
|
01324a84a8 | ||
|
|
b63ea35959 | ||
|
|
bb345dfd09 | ||
|
|
c05c2b7f70 | ||
|
|
3d07ec8696 | ||
|
|
3b396814ae | ||
|
|
b2047c1aca | ||
|
|
2b0cff2c93 | ||
|
|
fa7af34678 | ||
|
|
7563ea6217 | ||
|
|
08726af215 | ||
|
|
4fa1d6b0a1 | ||
|
|
3c86f1eee8 | ||
|
|
3a63f9fbb1 | ||
|
|
7b5408d20c | ||
|
|
058e8ba455 | ||
|
|
bba3c0e6bb | ||
|
|
a266976c33 | ||
|
|
f29c051c73 | ||
|
|
8842b4840e | ||
|
|
586d2ceff6 | ||
|
|
69a2284a00 | ||
|
|
19761a25da | ||
|
|
e4328fe34d | ||
|
|
e91b49e7cd | ||
|
|
7d145cd3b8 | ||
|
|
962d5386c7 | ||
|
|
3ba985f771 | ||
|
|
ef6718c242 | ||
|
|
02bcae00cf | ||
|
|
d6cd1dffa4 | ||
|
|
fc32f0dbd3 | ||
|
|
cda1974e40 | ||
|
|
5425e82fb4 | ||
|
|
84f36b0d4d | ||
|
|
0807525e1b | ||
|
|
73a86b8606 | ||
|
|
b8652e70e5 | ||
|
|
a3f3b0bed4 | ||
|
|
daaa68ce22 | ||
|
|
9ada10e0cf | ||
|
|
35287c381b | ||
|
|
2ff84b633c | ||
|
|
c09d91765f | ||
|
|
ac6ddf32c8 | ||
|
|
f15d9e5956 | ||
|
|
f95601a2e7 | ||
|
|
0aef0cc121 | ||
|
|
d1bfd94d33 | ||
|
|
8a9c0f4fde | ||
|
|
3596771af1 | ||
|
|
7b9b457f15 | ||
|
|
cb8597d62f | ||
|
|
c82cfaf633 | ||
|
|
80802c9997 | ||
|
|
971579f021 | ||
|
|
af6b8d4f66 | ||
|
|
e9a61963f2 | ||
|
|
b350712f9e | ||
|
|
51785f10c1 | ||
|
|
24e0627b41 | ||
|
|
6c453c8b49 | ||
|
|
904a2d1b4d | ||
|
|
f3b64dcbe0 | ||
|
|
0edc2cbbab | ||
|
|
751f06eb58 | ||
|
|
9bfac71bd7 | ||
|
|
9499476940 | ||
|
|
eda1eb2e35 | ||
|
|
075e179972 | ||
|
|
99e8066607 |
228
.claude/agents/raise-pull-request.md
Normal file
228
.claude/agents/raise-pull-request.md
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
name: raise-pull-request
|
||||
description: |
|
||||
Use this agent when creating a pull request for the Home Assistant core repository after completing implementation work. This agent automates the PR creation process including running tests, formatting checks, and proper checkbox handling.
|
||||
model: inherit
|
||||
color: green
|
||||
tools: Read, Bash, Grep, Glob
|
||||
---
|
||||
|
||||
You are an expert at creating pull requests for the Home Assistant core repository. You will automate the PR creation process with proper verification, formatting, testing, and checkbox handling.
|
||||
|
||||
**Execute each step in order. Do not skip steps.**
|
||||
|
||||
## Step 1: Gather Information
|
||||
|
||||
Run these commands in parallel to analyze the changes:
|
||||
|
||||
```bash
|
||||
# Get current branch and remote
|
||||
git branch --show-current
|
||||
git remote -v | grep push
|
||||
|
||||
# Determine the best available dev reference
|
||||
if git rev-parse --verify --quiet upstream/dev >/dev/null; then
|
||||
BASE_REF="upstream/dev"
|
||||
elif git rev-parse --verify --quiet origin/dev >/dev/null; then
|
||||
BASE_REF="origin/dev"
|
||||
elif git rev-parse --verify --quiet dev >/dev/null; then
|
||||
BASE_REF="dev"
|
||||
else
|
||||
echo "Could not find upstream/dev, origin/dev, or local dev"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_SHA="$(git merge-base "$BASE_REF" HEAD)"
|
||||
echo "BASE_REF=$BASE_REF"
|
||||
echo "BASE_SHA=$BASE_SHA"
|
||||
|
||||
# Get commit info for this branch vs dev
|
||||
git log "${BASE_SHA}..HEAD" --oneline
|
||||
|
||||
# Check what files changed
|
||||
git diff "${BASE_SHA}..HEAD" --name-only
|
||||
|
||||
# Check if test files were added/modified
|
||||
git diff "${BASE_SHA}..HEAD" --name-only | grep -E "^tests/.*\.py$" || echo "NO_TESTS_CHANGED"
|
||||
|
||||
# Check if manifest.json changed
|
||||
git diff "${BASE_SHA}..HEAD" --name-only | grep "manifest.json" || echo "NO_MANIFEST_CHANGED"
|
||||
```
|
||||
|
||||
From the file paths, extract the **integration domain** from `homeassistant/components/{integration}/` or `tests/components/{integration}/`.
|
||||
|
||||
**Track results:**
|
||||
- `BASE_REF`: the dev reference used for comparison
|
||||
- `BASE_SHA`: the merge-base commit used for diff-based checks
|
||||
- `TESTS_CHANGED`: true if test files were added or modified
|
||||
- `MANIFEST_CHANGED`: true if manifest.json was modified
|
||||
|
||||
**If no suitable dev reference is available, STOP and tell the user to fetch `upstream/dev`, `origin/dev`, or a local `dev` branch before continuing.**
|
||||
|
||||
## Step 2: Run Code Quality Checks
|
||||
|
||||
Run `prek` to perform code quality checks (formatting, linting, hassfest, etc.) on the files changed since `BASE_SHA`:
|
||||
|
||||
```bash
|
||||
prek run --from-ref "$BASE_SHA" --to-ref HEAD
|
||||
```
|
||||
|
||||
**Track results:**
|
||||
- `PREK_PASSED`: true if `prek run` exits with code 0
|
||||
|
||||
**If `prek` fails or is not available, STOP and report the failure to the user. Do not proceed with PR creation. If the failure appears to be an environment setup issue (e.g., missing tools, command not found, venv not activated), also point the user to https://developers.home-assistant.io/docs/development_environment.**
|
||||
|
||||
## Step 3: Stage Any Changes from Checks
|
||||
|
||||
If `prek` made any formatting or generated file changes, stage and commit them as a separate commit:
|
||||
|
||||
```bash
|
||||
git status --porcelain
|
||||
# If changes exist:
|
||||
git add -A
|
||||
git commit -m "Apply prek formatting and generated file updates"
|
||||
```
|
||||
|
||||
## Step 4: Run Tests
|
||||
|
||||
Run pytest for the specific integration:
|
||||
|
||||
```bash
|
||||
pytest tests/components/{integration} \
|
||||
--timeout=60 \
|
||||
--durations-min=1 \
|
||||
--durations=0 \
|
||||
-q
|
||||
```
|
||||
|
||||
**Track results:**
|
||||
- `TESTS_PASSED`: true if pytest exits with code 0
|
||||
|
||||
**If tests fail, STOP and report the failures to the user. Do not proceed with PR creation.**
|
||||
|
||||
## Step 5: Identify PR Metadata
|
||||
|
||||
Write a release-note-style PR title summarizing the change. The title becomes the release notes entry, so it should be a complete sentence fragment describing what changed in imperative mood.
|
||||
|
||||
**PR Title Examples by Type:**
|
||||
| Type | Example titles |
|
||||
|------|----------------|
|
||||
| Bugfix | `Fix Hikvision NVR binary sensors not being detected` |
|
||||
| | `Fix JSON serialization of time objects in anthropic tool results` |
|
||||
| | `Fix config flow bug in Tesla Fleet` |
|
||||
| Dependency | `Bump eheimdigital to 1.5.0` |
|
||||
| | `Bump python-otbr-api to 2.7.1` |
|
||||
| New feature | `Add asyncio-level timeout to Backblaze B2 uploads` |
|
||||
| | `Add Nettleie optimization option` |
|
||||
| Code quality | `Add exception translations to Teslemetry` |
|
||||
| | `Improve test coverage of Tesla Fleet` |
|
||||
| | `Refactor adguard tests to use proper fixtures for mocking` |
|
||||
| | `Simplify entity init in Proxmox` |
|
||||
|
||||
## Step 6: Verify Development Checklist
|
||||
|
||||
Check each item from the [development checklist](https://developers.home-assistant.io/docs/development_checklist/):
|
||||
|
||||
| Item | How to verify |
|
||||
|------|---------------|
|
||||
| External libraries on PyPI | Check manifest.json requirements - all should be PyPI packages |
|
||||
| Dependencies in requirements_all.txt | Only if dependency declarations changed (the `requirements` field in `manifest.json` or `requirements_all.txt`), run `python -m script.gen_requirements_all` |
|
||||
| Codeowners updated | If this is a new integration, ensure its `manifest.json` includes a `codeowners` field with one or more GitHub usernames |
|
||||
| No commented out code | Visually scan the diff for blocks of commented-out code |
|
||||
|
||||
**Track results:**
|
||||
- `NO_COMMENTED_CODE`: true if no blocks of commented-out code found in the diff
|
||||
- `DEPENDENCIES_CHANGED`: true if the diff changes the `requirements` field in `manifest.json` or changes `requirements_all.txt`
|
||||
- `REQUIREMENTS_UPDATED`: true if `DEPENDENCIES_CHANGED` is true and requirements_all.txt was regenerated successfully; not applicable if `DEPENDENCIES_CHANGED` is false
|
||||
- `CHECKLIST_PASSED`: true if all items above pass
|
||||
|
||||
## Step 7: Determine Type of Change
|
||||
|
||||
Select exactly ONE based on the changes. Mark the selected type with `[x]` and all others with `[ ]` (space):
|
||||
|
||||
| Type | Condition |
|
||||
|------|-----------|
|
||||
| Dependency upgrade | Only manifest.json/requirements changes |
|
||||
| Bugfix | Fixes broken behavior, no new features |
|
||||
| New integration | New folder in components/ |
|
||||
| New feature | Adds capability to existing integration |
|
||||
| Deprecation | Adds deprecation warnings for future breaking change |
|
||||
| Breaking change | Removes or changes existing functionality |
|
||||
| Code quality | Only refactoring or test additions, no functional change |
|
||||
|
||||
**Track results:**
|
||||
- `CHANGE_TYPE`: the selected type (e.g., "Bugfix", "New feature", "Code quality", etc.)
|
||||
|
||||
**Important:** All seven type options must remain in the PR body. Only the selected type gets `[x]`, all others get `[ ]`.
|
||||
|
||||
## Step 8: Determine Checkbox States
|
||||
|
||||
Based on the verification steps above, determine checkbox states:
|
||||
|
||||
| Checkbox | Condition to tick |
|
||||
|----------|-------------------|
|
||||
| The code change is tested and works locally | Leave unchecked for the contributor to verify manually (this refers to manual testing, not unit tests) |
|
||||
| Local tests pass | Tick only if `TESTS_PASSED` is true |
|
||||
| I understand the code I am submitting and can explain how it works | Leave unchecked for the contributor to review and set manually |
|
||||
| There is no commented out code | Tick only if `NO_COMMENTED_CODE` is true |
|
||||
| Development checklist | Tick only if `CHECKLIST_PASSED` is true |
|
||||
| Perfect PR recommendations | Tick only if the PR affects a single integration or closely related modules, represents one primary type of change, and has a clear, self-contained scope |
|
||||
| Formatted using Ruff | Tick only if `PREK_PASSED` is true |
|
||||
| Tests have been added | Tick only if `TESTS_CHANGED` is true AND the changes exercise new or changed functionality (not only cosmetic test changes) |
|
||||
| Documentation added/updated | Tick if documentation PR created (or not applicable) |
|
||||
| Manifest file fields filled out | Tick if `PREK_PASSED` is true (or not applicable) |
|
||||
| Dependencies in requirements_all.txt | Tick only if `DEPENDENCIES_CHANGED` is false, or if `DEPENDENCIES_CHANGED` is true and `REQUIREMENTS_UPDATED` is true |
|
||||
| Dependency changelog linked | Tick if dependency changelog linked in PR description (or not applicable) |
|
||||
| Any generated code has been carefully reviewed | Leave unchecked for the contributor to review and set manually |
|
||||
|
||||
## Step 9: Breaking Change Section
|
||||
|
||||
**If `CHANGE_TYPE` is NOT "Breaking change" or "Deprecation": REMOVE the entire "## Breaking change" section from the PR body (including the heading).**
|
||||
|
||||
If `CHANGE_TYPE` IS "Breaking change" or "Deprecation", keep the `## Breaking change` section and describe:
|
||||
- What breaks
|
||||
- How users can fix it
|
||||
- Why it was necessary
|
||||
|
||||
## Step 10: Push Branch and Create PR
|
||||
|
||||
```bash
|
||||
# Get branch name and GitHub username
|
||||
BRANCH=$(git branch --show-current)
|
||||
PUSH_REMOTE=$(git config "branch.$BRANCH.remote" 2>/dev/null || git remote | head -1)
|
||||
GITHUB_USER=$(gh api user --jq .login 2>/dev/null || git remote get-url "$PUSH_REMOTE" | sed -E 's#.*[:/]([^/]+)/([^/]+)(\.git)?$#\1#')
|
||||
|
||||
# Create PR (gh pr create pushes the branch automatically)
|
||||
gh pr create --repo home-assistant/core --base dev \
|
||||
--head "$GITHUB_USER:$BRANCH" \
|
||||
--title "TITLE_HERE" \
|
||||
--body "$(cat <<'EOF'
|
||||
BODY_HERE
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### PR Body Template
|
||||
|
||||
Read the PR template from `.github/PULL_REQUEST_TEMPLATE.md` and use it as the basis for the PR body. **Do not hardcode the template — always read it from the file to stay in sync with upstream changes.**
|
||||
|
||||
Use any HTML comments (`<!-- ... -->`) in the template as guidance to understand what to fill in. For the final PR body sent to GitHub, keep the template text intact — do not delete any text from the template unless it explicitly instructs removal (e.g., the breaking change section when not applicable). Then fill in the sections:
|
||||
|
||||
1. **Breaking change section**: If the type is NOT "Breaking change" or "Deprecation", remove the entire `## Breaking change` section (heading and body). Otherwise, describe what breaks, how users can fix it, and why.
|
||||
2. **Proposed change section**: Fill in a description of the change extracted from commit messages.
|
||||
3. **Type of change**: Check exactly ONE checkbox matching the determined type from Step 7. Leave all others unchecked.
|
||||
4. **Additional information**: Fill in any related issue numbers if known.
|
||||
5. **Checklist**: Check boxes based on the conditions in Step 8. Leave manual-verification boxes unchecked for the contributor.
|
||||
|
||||
**Important:** Preserve all template structure, options, and link references exactly as they appear in the file — only modify checkbox states and fill in content sections.
|
||||
|
||||
## Step 11: Report Result
|
||||
|
||||
Provide the user with:
|
||||
1. **PR URL** - The created pull request link
|
||||
2. **Verification Summary** - Which checks passed/failed
|
||||
3. **Unchecked Items** - List any checkboxes left unchecked and why
|
||||
4. **User Action Required** - Remind user to:
|
||||
- Review and set manual-verification checkboxes ("I understand the code..." and "Any generated code...") as applicable
|
||||
- Consider reviewing two other open PRs
|
||||
- Add any related issue numbers if applicable
|
||||
@@ -174,7 +174,6 @@ homeassistant.components.dnsip.*
|
||||
homeassistant.components.doorbird.*
|
||||
homeassistant.components.dormakaba_dkey.*
|
||||
homeassistant.components.downloader.*
|
||||
homeassistant.components.dropbox.*
|
||||
homeassistant.components.droplet.*
|
||||
homeassistant.components.dsmr.*
|
||||
homeassistant.components.duckdns.*
|
||||
|
||||
6
CODEOWNERS
generated
6
CODEOWNERS
generated
@@ -222,8 +222,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/binary_sensor/ @home-assistant/core
|
||||
/tests/components/binary_sensor/ @home-assistant/core
|
||||
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
|
||||
/homeassistant/components/blebox/ @bbx-a @swistakm
|
||||
/tests/components/blebox/ @bbx-a @swistakm
|
||||
/homeassistant/components/blebox/ @bbx-a @swistakm @bkobus-bbx
|
||||
/tests/components/blebox/ @bbx-a @swistakm @bkobus-bbx
|
||||
/homeassistant/components/blink/ @fronzbot
|
||||
/tests/components/blink/ @fronzbot
|
||||
/homeassistant/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
|
||||
@@ -401,8 +401,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/dremel_3d_printer/ @tkdrob
|
||||
/homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||
/tests/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||
/homeassistant/components/dropbox/ @bdr99
|
||||
/tests/components/dropbox/ @bdr99
|
||||
/homeassistant/components/droplet/ @sarahseidman
|
||||
/tests/components/droplet/ @sarahseidman
|
||||
/homeassistant/components/dsmr/ @Robbie1221
|
||||
|
||||
@@ -238,7 +238,9 @@ DEFAULT_INTEGRATIONS = {
|
||||
"timer",
|
||||
#
|
||||
# Base platforms:
|
||||
*BASE_PLATFORMS,
|
||||
# Note: Calendar and todo are not included to prevent them from registering
|
||||
# their frontend panels when there are no calendar or todo integrations.
|
||||
*(BASE_PLATFORMS - {"calendar", "todo"}),
|
||||
#
|
||||
# Integrations providing triggers and conditions for base platforms:
|
||||
"air_quality",
|
||||
|
||||
5
homeassistant/brands/bega.json
Normal file
5
homeassistant/brands/bega.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "bega",
|
||||
"name": "BEGA",
|
||||
"iot_standards": ["zigbee"]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
"""The actiontec component."""
|
||||
"""The Actiontec integration."""
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the value should match on the targeted entities.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
|
||||
"trigger_behavior_name": "Behavior",
|
||||
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
|
||||
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
|
||||
"trigger_threshold_name": "Threshold configuration"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_threshold_name": "Threshold type"
|
||||
},
|
||||
"conditions": {
|
||||
"is_co2_value": {
|
||||
"description": "Tests the carbon dioxide level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -29,7 +22,6 @@
|
||||
"description": "Tests if one or more carbon monoxide sensors are cleared.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -39,7 +31,6 @@
|
||||
"description": "Tests if one or more carbon monoxide sensors are detecting carbon monoxide.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -49,11 +40,9 @@
|
||||
"description": "Tests the carbon monoxide level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -63,7 +52,6 @@
|
||||
"description": "Tests if one or more gas sensors are cleared.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -73,7 +61,6 @@
|
||||
"description": "Tests if one or more gas sensors are detecting gas.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -83,11 +70,9 @@
|
||||
"description": "Tests the nitrous oxide level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -97,11 +82,9 @@
|
||||
"description": "Tests the nitrogen dioxide level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -111,11 +94,9 @@
|
||||
"description": "Tests the nitrogen monoxide level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -125,11 +106,9 @@
|
||||
"description": "Tests the ozone level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -139,11 +118,9 @@
|
||||
"description": "Tests the PM10 level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -153,11 +130,9 @@
|
||||
"description": "Tests the PM1 level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -167,11 +142,9 @@
|
||||
"description": "Tests the PM2.5 level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -181,11 +154,9 @@
|
||||
"description": "Tests the PM4 level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -195,7 +166,6 @@
|
||||
"description": "Tests if one or more smoke sensors are cleared.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -205,7 +175,6 @@
|
||||
"description": "Tests if one or more smoke sensors are detecting smoke.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -215,11 +184,9 @@
|
||||
"description": "Tests the sulphur dioxide level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -229,11 +196,9 @@
|
||||
"description": "Tests the volatile organic compounds ratio of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -243,11 +208,9 @@
|
||||
"description": "Tests the volatile organic compounds level of one or more entities.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -275,7 +238,6 @@
|
||||
"description": "Triggers after one or more carbon dioxide levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -285,11 +247,9 @@
|
||||
"description": "Triggers after one or more carbon dioxide levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -299,7 +259,6 @@
|
||||
"description": "Triggers after one or more carbon monoxide levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -309,7 +268,6 @@
|
||||
"description": "Triggers after one or more carbon monoxide sensors stop detecting carbon monoxide.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -319,11 +277,9 @@
|
||||
"description": "Triggers after one or more carbon monoxide levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -333,7 +289,6 @@
|
||||
"description": "Triggers after one or more carbon monoxide sensors start detecting carbon monoxide.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -343,7 +298,6 @@
|
||||
"description": "Triggers after one or more gas sensors stop detecting gas.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -353,7 +307,6 @@
|
||||
"description": "Triggers after one or more gas sensors start detecting gas.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -363,7 +316,6 @@
|
||||
"description": "Triggers after one or more nitrous oxide levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -373,11 +325,9 @@
|
||||
"description": "Triggers after one or more nitrous oxide levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -387,7 +337,6 @@
|
||||
"description": "Triggers after one or more nitrogen dioxide levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -397,11 +346,9 @@
|
||||
"description": "Triggers after one or more nitrogen dioxide levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -411,7 +358,6 @@
|
||||
"description": "Triggers after one or more nitrogen monoxide levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -421,11 +367,9 @@
|
||||
"description": "Triggers after one or more nitrogen monoxide levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -435,7 +379,6 @@
|
||||
"description": "Triggers after one or more ozone levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -445,11 +388,9 @@
|
||||
"description": "Triggers after one or more ozone levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -459,7 +400,6 @@
|
||||
"description": "Triggers after one or more PM10 levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -469,11 +409,9 @@
|
||||
"description": "Triggers after one or more PM10 levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -483,7 +421,6 @@
|
||||
"description": "Triggers after one or more PM1 levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -493,11 +430,9 @@
|
||||
"description": "Triggers after one or more PM1 levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -507,7 +442,6 @@
|
||||
"description": "Triggers after one or more PM2.5 levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -517,11 +451,9 @@
|
||||
"description": "Triggers after one or more PM2.5 levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -531,7 +463,6 @@
|
||||
"description": "Triggers after one or more PM4 levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -541,11 +472,9 @@
|
||||
"description": "Triggers after one or more PM4 levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -555,7 +484,6 @@
|
||||
"description": "Triggers after one or more smoke sensors stop detecting smoke.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -565,7 +493,6 @@
|
||||
"description": "Triggers after one or more smoke sensors start detecting smoke.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -575,7 +502,6 @@
|
||||
"description": "Triggers after one or more sulphur dioxide levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -585,11 +511,9 @@
|
||||
"description": "Triggers after one or more sulphur dioxide levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -599,7 +523,6 @@
|
||||
"description": "Triggers after one or more volatile organic compound levels change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -609,11 +532,9 @@
|
||||
"description": "Triggers after one or more volatile organic compounds levels cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -623,7 +544,6 @@
|
||||
"description": "Triggers after one or more volatile organic compound ratios change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -633,11 +553,9 @@
|
||||
"description": "Triggers after one or more volatile organic compounds ratios cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted alarms.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted alarms to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_armed": {
|
||||
"description": "Tests if one or more alarms are armed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more alarms are armed in away mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -30,7 +26,6 @@
|
||||
"description": "Tests if one or more alarms are armed in home mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -40,7 +35,6 @@
|
||||
"description": "Tests if one or more alarms are armed in night mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -50,7 +44,6 @@
|
||||
"description": "Tests if one or more alarms are armed in vacation mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -60,7 +53,6 @@
|
||||
"description": "Tests if one or more alarms are disarmed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -70,7 +62,6 @@
|
||||
"description": "Tests if one or more alarms are triggered.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -242,7 +233,6 @@
|
||||
"description": "Triggers after one or more alarms become armed, regardless of the mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -252,7 +242,6 @@
|
||||
"description": "Triggers after one or more alarms become armed in away mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -262,7 +251,6 @@
|
||||
"description": "Triggers after one or more alarms become armed in home mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -272,7 +260,6 @@
|
||||
"description": "Triggers after one or more alarms become armed in night mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -282,7 +269,6 @@
|
||||
"description": "Triggers after one or more alarms become armed in vacation mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -292,7 +278,6 @@
|
||||
"description": "Triggers after one or more alarms become disarmed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -302,7 +287,6 @@
|
||||
"description": "Triggers after one or more alarms become triggered.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from adext import AdExt
|
||||
from alarmdecoder.devices import SerialDevice, SocketDevice
|
||||
from alarmdecoder.devices import Device, SerialDevice, SocketDevice
|
||||
from alarmdecoder.util import NoDeviceError
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -102,16 +102,21 @@ class AlarmDecoderFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self._async_current_entries(), user_input, self.protocol
|
||||
):
|
||||
return self.async_abort(reason="already_configured")
|
||||
connection = {}
|
||||
connection: dict[str, Any] = {}
|
||||
baud = None
|
||||
device: Device
|
||||
if self.protocol == PROTOCOL_SOCKET:
|
||||
host = connection[CONF_HOST] = user_input[CONF_HOST]
|
||||
port = connection[CONF_PORT] = user_input[CONF_PORT]
|
||||
title = f"{host}:{port}"
|
||||
host = connection[CONF_HOST] = cast(str, user_input[CONF_HOST])
|
||||
port = connection[CONF_PORT] = cast(int, user_input[CONF_PORT])
|
||||
title: str = f"{host}:{port}"
|
||||
device = SocketDevice(interface=(host, port))
|
||||
if self.protocol == PROTOCOL_SERIAL:
|
||||
path = connection[CONF_DEVICE_PATH] = user_input[CONF_DEVICE_PATH]
|
||||
baud = connection[CONF_DEVICE_BAUD] = user_input[CONF_DEVICE_BAUD]
|
||||
path = connection[CONF_DEVICE_PATH] = cast(
|
||||
str, user_input[CONF_DEVICE_PATH]
|
||||
)
|
||||
baud = connection[CONF_DEVICE_BAUD] = cast(
|
||||
int, user_input[CONF_DEVICE_BAUD]
|
||||
)
|
||||
title = path
|
||||
device = SerialDevice(interface=path)
|
||||
|
||||
@@ -132,6 +137,7 @@ class AlarmDecoderFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.exception("Unexpected exception during AlarmDecoder setup")
|
||||
errors["base"] = "unknown"
|
||||
|
||||
schema: vol.Schema
|
||||
if self.protocol == PROTOCOL_SOCKET:
|
||||
schema = vol.Schema(
|
||||
{
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==13.3.1"]
|
||||
"requirements": ["aioamazondevices==13.3.2"]
|
||||
}
|
||||
|
||||
64
homeassistant/components/anthropic/diagnostics.py
Normal file
64
homeassistant/components/anthropic/diagnostics.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Diagnostics support for Anthropic."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from anthropic import __title__, __version__
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import (
|
||||
CONF_PROMPT,
|
||||
CONF_WEB_SEARCH_CITY,
|
||||
CONF_WEB_SEARCH_COUNTRY,
|
||||
CONF_WEB_SEARCH_REGION,
|
||||
CONF_WEB_SEARCH_TIMEZONE,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import AnthropicConfigEntry
|
||||
|
||||
|
||||
TO_REDACT = {
|
||||
CONF_API_KEY,
|
||||
CONF_PROMPT,
|
||||
CONF_WEB_SEARCH_CITY,
|
||||
CONF_WEB_SEARCH_REGION,
|
||||
CONF_WEB_SEARCH_COUNTRY,
|
||||
CONF_WEB_SEARCH_TIMEZONE,
|
||||
}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: AnthropicConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
|
||||
return {
|
||||
"client": f"{__title__}=={__version__}",
|
||||
"title": entry.title,
|
||||
"entry_id": entry.entry_id,
|
||||
"entry_version": f"{entry.version}.{entry.minor_version}",
|
||||
"state": entry.state.value,
|
||||
"data": async_redact_data(entry.data, TO_REDACT),
|
||||
"options": async_redact_data(entry.options, TO_REDACT),
|
||||
"subentries": {
|
||||
subentry.subentry_id: {
|
||||
"title": subentry.title,
|
||||
"subentry_type": subentry.subentry_type,
|
||||
"data": async_redact_data(subentry.data, TO_REDACT),
|
||||
}
|
||||
for subentry in entry.subentries.values()
|
||||
},
|
||||
"entities": {
|
||||
entity_entry.entity_id: entity_entry.extended_dict
|
||||
for entity_entry in er.async_entries_for_config_entry(
|
||||
er.async_get(hass), entry.entry_id
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -46,7 +46,7 @@ rules:
|
||||
test-coverage: done
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: |
|
||||
@@ -61,10 +61,7 @@ rules:
|
||||
No data updates.
|
||||
docs-examples: done
|
||||
docs-known-limitations: done
|
||||
docs-supported-devices:
|
||||
status: todo
|
||||
comment: |
|
||||
To write something about what models we support.
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: done
|
||||
|
||||
@@ -30,9 +30,10 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_CREDENTIALS,
|
||||
@@ -42,9 +43,12 @@ from .const import (
|
||||
SIGNAL_CONNECTED,
|
||||
SIGNAL_DISCONNECTED,
|
||||
)
|
||||
from .services import async_setup_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
DEFAULT_NAME_TV = "Apple TV"
|
||||
DEFAULT_NAME_HP = "HomePod"
|
||||
|
||||
@@ -77,6 +81,12 @@ DEVICE_EXCEPTIONS = (
|
||||
type AppleTvConfigEntry = ConfigEntry[AppleTVManager]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Apple TV component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AppleTvConfigEntry) -> bool:
|
||||
"""Set up a config entry for Apple TV."""
|
||||
manager = AppleTVManager(hass, entry)
|
||||
|
||||
@@ -9,3 +9,5 @@ CONF_START_OFF = "start_off"
|
||||
|
||||
SIGNAL_CONNECTED = "apple_tv_connected"
|
||||
SIGNAL_DISCONNECTED = "apple_tv_disconnected"
|
||||
|
||||
ATTR_TEXT = "text"
|
||||
|
||||
@@ -8,5 +8,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"append_keyboard_text": {
|
||||
"service": "mdi:keyboard"
|
||||
},
|
||||
"clear_keyboard_text": {
|
||||
"service": "mdi:keyboard-off"
|
||||
},
|
||||
"set_keyboard_text": {
|
||||
"service": "mdi:keyboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
130
homeassistant/components/apple_tv/services.py
Normal file
130
homeassistant/components/apple_tv/services.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""Define services for the Apple TV integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pyatv.const import KeyboardFocusState
|
||||
from pyatv.exceptions import NotSupportedError, ProtocolError
|
||||
from pyatv.interface import AppleTV as AppleTVInterface
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import ATTR_TEXT, DOMAIN
|
||||
|
||||
SERVICE_SET_KEYBOARD_TEXT = "set_keyboard_text"
|
||||
SERVICE_SET_KEYBOARD_TEXT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Required(ATTR_TEXT): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_APPEND_KEYBOARD_TEXT = "append_keyboard_text"
|
||||
SERVICE_APPEND_KEYBOARD_TEXT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Required(ATTR_TEXT): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_CLEAR_KEYBOARD_TEXT = "clear_keyboard_text"
|
||||
SERVICE_CLEAR_KEYBOARD_TEXT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _get_atv(call: ServiceCall) -> AppleTVInterface:
|
||||
"""Get the AppleTVInterface for a service call."""
|
||||
entry = service.async_get_config_entry(
|
||||
call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
|
||||
)
|
||||
atv: AppleTVInterface | None = entry.runtime_data.atv
|
||||
if atv is None:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="not_connected",
|
||||
)
|
||||
return atv
|
||||
|
||||
|
||||
def _check_keyboard_focus(atv: AppleTVInterface) -> None:
|
||||
"""Check that keyboard is focused on the device."""
|
||||
try:
|
||||
focus_state = atv.keyboard.text_focus_state
|
||||
except NotSupportedError as err:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_not_available",
|
||||
) from err
|
||||
if focus_state != KeyboardFocusState.Focused:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_not_focused",
|
||||
)
|
||||
|
||||
|
||||
async def _async_set_keyboard_text(call: ServiceCall) -> None:
|
||||
"""Set text in the keyboard input field on an Apple TV."""
|
||||
atv = _get_atv(call)
|
||||
_check_keyboard_focus(atv)
|
||||
try:
|
||||
await atv.keyboard.text_set(call.data[ATTR_TEXT])
|
||||
except ProtocolError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_error",
|
||||
) from err
|
||||
|
||||
|
||||
async def _async_append_keyboard_text(call: ServiceCall) -> None:
|
||||
"""Append text to the keyboard input field on an Apple TV."""
|
||||
atv = _get_atv(call)
|
||||
_check_keyboard_focus(atv)
|
||||
try:
|
||||
await atv.keyboard.text_append(call.data[ATTR_TEXT])
|
||||
except ProtocolError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_error",
|
||||
) from err
|
||||
|
||||
|
||||
async def _async_clear_keyboard_text(call: ServiceCall) -> None:
|
||||
"""Clear text in the keyboard input field on an Apple TV."""
|
||||
atv = _get_atv(call)
|
||||
_check_keyboard_focus(atv)
|
||||
try:
|
||||
await atv.keyboard.text_clear()
|
||||
except ProtocolError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_error",
|
||||
) from err
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up the services for the Apple TV integration."""
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_SET_KEYBOARD_TEXT,
|
||||
_async_set_keyboard_text,
|
||||
schema=SERVICE_SET_KEYBOARD_TEXT_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_APPEND_KEYBOARD_TEXT,
|
||||
_async_append_keyboard_text,
|
||||
schema=SERVICE_APPEND_KEYBOARD_TEXT_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_CLEAR_KEYBOARD_TEXT,
|
||||
_async_clear_keyboard_text,
|
||||
schema=SERVICE_CLEAR_KEYBOARD_TEXT_SCHEMA,
|
||||
)
|
||||
31
homeassistant/components/apple_tv/services.yaml
Normal file
31
homeassistant/components/apple_tv/services.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
set_keyboard_text:
|
||||
fields:
|
||||
config_entry_id:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: apple_tv
|
||||
text:
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
|
||||
append_keyboard_text:
|
||||
fields:
|
||||
config_entry_id:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: apple_tv
|
||||
text:
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
|
||||
clear_keyboard_text:
|
||||
fields:
|
||||
config_entry_id:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: apple_tv
|
||||
@@ -69,6 +69,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"keyboard_error": {
|
||||
"message": "An error occurred while sending text to the Apple TV"
|
||||
},
|
||||
"keyboard_not_available": {
|
||||
"message": "Keyboard input is not supported by this device"
|
||||
},
|
||||
"keyboard_not_focused": {
|
||||
"message": "No text input field is currently focused on the Apple TV"
|
||||
},
|
||||
"not_connected": {
|
||||
"message": "Apple TV is not connected"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
@@ -78,5 +92,45 @@
|
||||
"description": "Configure general device settings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"append_keyboard_text": {
|
||||
"description": "Appends text to the currently focused text input field on an Apple TV.",
|
||||
"fields": {
|
||||
"config_entry_id": {
|
||||
"description": "[%key:component::apple_tv::services::set_keyboard_text::fields::config_entry_id::description%]",
|
||||
"name": "[%key:component::apple_tv::services::set_keyboard_text::fields::config_entry_id::name%]"
|
||||
},
|
||||
"text": {
|
||||
"description": "The text to append.",
|
||||
"name": "[%key:component::apple_tv::services::set_keyboard_text::fields::text::name%]"
|
||||
}
|
||||
},
|
||||
"name": "Append keyboard text"
|
||||
},
|
||||
"clear_keyboard_text": {
|
||||
"description": "Clears the text in the currently focused text input field on an Apple TV.",
|
||||
"fields": {
|
||||
"config_entry_id": {
|
||||
"description": "[%key:component::apple_tv::services::set_keyboard_text::fields::config_entry_id::description%]",
|
||||
"name": "[%key:component::apple_tv::services::set_keyboard_text::fields::config_entry_id::name%]"
|
||||
}
|
||||
},
|
||||
"name": "Clear keyboard text"
|
||||
},
|
||||
"set_keyboard_text": {
|
||||
"description": "Sets the text in the currently focused text input field on an Apple TV.",
|
||||
"fields": {
|
||||
"config_entry_id": {
|
||||
"description": "The Apple TV to send text to.",
|
||||
"name": "Apple TV"
|
||||
},
|
||||
"text": {
|
||||
"description": "The text to set.",
|
||||
"name": "Text"
|
||||
}
|
||||
},
|
||||
"name": "Set keyboard text"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The aruba component."""
|
||||
"""The Aruba integration."""
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted Assist satellites.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted Assist satellites to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_idle": {
|
||||
"description": "Tests if one or more Assist satellites are idle.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more Assist satellites are listening.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -30,7 +26,6 @@
|
||||
"description": "Tests if one or more Assist satellites are processing.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -40,7 +35,6 @@
|
||||
"description": "Tests if one or more Assist satellites are responding.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -165,7 +159,6 @@
|
||||
"description": "Triggers after one or more voice assistant satellites become idle after having processed a command.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -175,7 +168,6 @@
|
||||
"description": "Triggers after one or more voice assistant satellites start listening for a command from someone.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -185,7 +177,6 @@
|
||||
"description": "Triggers after one or more voice assistant satellites start processing a command after having heard it.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -195,7 +186,6 @@
|
||||
"description": "Triggers after one or more voice assistant satellites start responding to a command after having processed it, or start announcing something.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ import hashlib
|
||||
import io
|
||||
from itertools import chain
|
||||
import json
|
||||
from pathlib import Path, PurePath
|
||||
from pathlib import Path, PurePath, PureWindowsPath
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
@@ -1957,7 +1957,10 @@ class CoreBackupReaderWriter(BackupReaderWriter):
|
||||
suggested_filename: str,
|
||||
) -> WrittenBackup:
|
||||
"""Receive a backup."""
|
||||
temp_file = Path(self.temp_backup_dir, suggested_filename)
|
||||
safe_filename = PureWindowsPath(suggested_filename).name
|
||||
if not safe_filename or safe_filename == "..":
|
||||
safe_filename = "backup.tar"
|
||||
temp_file = Path(self.temp_backup_dir, safe_filename)
|
||||
|
||||
async_add_executor_job = self._hass.async_add_executor_job
|
||||
await async_add_executor_job(make_backup_dir, self.temp_backup_dir)
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted batteries.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"trigger_behavior_description": "The behavior of the targeted batteries to trigger on.",
|
||||
"trigger_behavior_name": "Behavior",
|
||||
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
|
||||
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
|
||||
"trigger_threshold_name": "Threshold configuration"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_threshold_name": "Threshold type"
|
||||
},
|
||||
"conditions": {
|
||||
"is_charging": {
|
||||
"description": "Tests if one or more batteries are charging.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -25,11 +19,9 @@
|
||||
"description": "Tests the battery level of one or more batteries.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::battery::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::battery::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -39,7 +31,6 @@
|
||||
"description": "Tests if one or more batteries are low.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -49,7 +40,6 @@
|
||||
"description": "Tests if one or more batteries are not charging.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -59,7 +49,6 @@
|
||||
"description": "Tests if one or more batteries are not low.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -87,7 +76,6 @@
|
||||
"description": "Triggers after the battery level of one or more batteries changes.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::battery::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -97,11 +85,9 @@
|
||||
"description": "Triggers after the battery level of one or more batteries crosses a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::battery::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -111,7 +97,6 @@
|
||||
"description": "Triggers after one or more batteries become low.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -121,7 +106,6 @@
|
||||
"description": "Triggers after one or more batteries are no longer low.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -131,7 +115,6 @@
|
||||
"description": "Triggers after one or more batteries start charging.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -141,7 +124,6 @@
|
||||
"description": "Triggers after one or more batteries stop charging.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::battery::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::battery::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The bbox component."""
|
||||
"""The Bbox integration."""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "blebox",
|
||||
"name": "BleBox devices",
|
||||
"codeowners": ["@bbx-a", "@swistakm"],
|
||||
"codeowners": ["@bbx-a", "@swistakm", "@bkobus-bbx"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/blebox",
|
||||
"integration_type": "device",
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The blinksticklight component."""
|
||||
"""The BlinkStick integration."""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Support for Blinkstick lights."""
|
||||
"""Support for BlinkStick lights."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
@@ -40,7 +40,7 @@ def setup_platform(
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up Blinkstick device specified by serial number."""
|
||||
"""Set up BlinkStick device specified by serial number."""
|
||||
|
||||
name = config[CONF_NAME]
|
||||
serial = config[CONF_SERIAL]
|
||||
|
||||
41
homeassistant/components/bmw_connected_drive/__init__.py
Normal file
41
homeassistant/components/bmw_connected_drive/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""The BMW Connected Drive integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
DOMAIN = "bmw_connected_drive"
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up BMW Connected Drive from a config entry."""
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="integration_removed",
|
||||
translation_placeholders={
|
||||
"entries": "/config/integrations/integration/bmw_connected_drive",
|
||||
"custom_component_url": "https://github.com/kvanbiesen/bmw-cardata-ha",
|
||||
},
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return True
|
||||
|
||||
|
||||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Remove a config entry."""
|
||||
if not hass.config_entries.async_loaded_entries(DOMAIN):
|
||||
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
||||
# Remove any remaining disabled or ignored entries
|
||||
for _entry in hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id))
|
||||
@@ -0,0 +1,9 @@
|
||||
"""The BMW Connected Drive integration config flow."""
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
class BMWConnectedDriveConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for BMW Connected Drive."""
|
||||
10
homeassistant/components/bmw_connected_drive/manifest.json
Normal file
10
homeassistant/components/bmw_connected_drive/manifest.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"domain": "bmw_connected_drive",
|
||||
"name": "BMW Connected Drive",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": []
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"issues": {
|
||||
"integration_removed": {
|
||||
"description": "The BMW Connected Drive integration has been removed from Home Assistant.\n\nIn September 2025, BMW blocked third-party access to their servers by adding additional security measures. For EU-registered cars, a community-developed [custom component]({custom_component_url}) using BMW's CarData API is available as an alternative.\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing BMW Connected Drive integration entries]({entries}).",
|
||||
"title": "The BMW Connected Drive integration has been removed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Rotate the access token."""
|
||||
access_tokens.append(hex(_RND.getrandbits(256))[2:])
|
||||
|
||||
async_track_time_interval(hass, _rotate_token, TOKEN_CHANGE_INTERVAL)
|
||||
async_track_time_interval(
|
||||
hass, _rotate_token, TOKEN_CHANGE_INTERVAL, cancel_on_shutdown=True
|
||||
)
|
||||
|
||||
hass.http.register_view(BrandsIntegrationView(hass))
|
||||
hass.http.register_view(BrandsHardwareView(hass))
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted calendars.",
|
||||
"condition_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if"
|
||||
},
|
||||
"conditions": {
|
||||
"is_event_active": {
|
||||
"description": "Tests if one or more calendars have an active event.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::calendar::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::calendar::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30,6 +30,7 @@ class ChessConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
client = ChessComClient(session=session)
|
||||
try:
|
||||
user = await client.get_player(user_input[CONF_USERNAME])
|
||||
await client.get_player_stats(user_input[CONF_USERNAME])
|
||||
except NotFoundError:
|
||||
errors["base"] = "player_not_found"
|
||||
except Exception:
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted climate-control devices.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"trigger_behavior_description": "The behavior of the targeted climates to trigger on.",
|
||||
"trigger_behavior_name": "Behavior",
|
||||
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
|
||||
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
|
||||
"trigger_threshold_name": "Threshold configuration"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_threshold_name": "Threshold type"
|
||||
},
|
||||
"conditions": {
|
||||
"is_cooling": {
|
||||
"description": "Tests if one or more climate-control devices are cooling.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -25,7 +19,6 @@
|
||||
"description": "Tests if one or more climate-control devices are drying.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -35,7 +28,6 @@
|
||||
"description": "Tests if one or more climate-control devices are heating.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -45,7 +37,6 @@
|
||||
"description": "Tests if one or more climate-control devices are set to a specific HVAC mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"hvac_mode": {
|
||||
@@ -59,7 +50,6 @@
|
||||
"description": "Tests if one or more climate-control devices are off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -69,7 +59,6 @@
|
||||
"description": "Tests if one or more climate-control devices are on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -79,11 +68,9 @@
|
||||
"description": "Tests the humidity setpoint of one or more climate-control devices.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::climate::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::climate::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -93,11 +80,9 @@
|
||||
"description": "Tests the temperature setpoint of one or more climate-control devices.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::climate::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::climate::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -398,7 +383,6 @@
|
||||
"description": "Triggers after the mode of one or more climate-control devices changes.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
},
|
||||
"hvac_mode": {
|
||||
@@ -412,7 +396,6 @@
|
||||
"description": "Triggers after one or more climate-control devices start cooling.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -422,7 +405,6 @@
|
||||
"description": "Triggers after one or more climate-control devices start drying.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -432,7 +414,6 @@
|
||||
"description": "Triggers after one or more climate-control devices start heating.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -442,7 +423,6 @@
|
||||
"description": "Triggers after the humidity setpoint of one or more climate-control devices changes.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::climate::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -452,11 +432,9 @@
|
||||
"description": "Triggers after the humidity setpoint of one or more climate-control devices crosses a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::climate::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -466,7 +444,6 @@
|
||||
"description": "Triggers after the temperature setpoint of one or more climate-control devices changes.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::climate::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -476,11 +453,9 @@
|
||||
"description": "Triggers after the temperature setpoint of one or more climate-control devices crosses a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::climate::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -490,7 +465,6 @@
|
||||
"description": "Triggers after one or more climate-control devices turn off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -500,7 +474,6 @@
|
||||
"description": "Triggers after one or more climate-control devices turn on, regardless of the mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::climate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::climate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -210,7 +210,7 @@ def websocket_update_entity(
|
||||
)
|
||||
return
|
||||
|
||||
changes = {}
|
||||
changes: dict[str, Any] = {}
|
||||
|
||||
for key in (
|
||||
"area_id",
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
{
|
||||
"common": {
|
||||
"trigger_behavior_description": "The behavior of the targeted counters to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_value": {
|
||||
"description": "Tests the value of one or more counters.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "How the state should match on the targeted counters.",
|
||||
"name": "Behavior"
|
||||
"name": "Condition passes if"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "What to test for and threshold values.",
|
||||
"name": "Threshold"
|
||||
"name": "Threshold type"
|
||||
}
|
||||
},
|
||||
"name": "Counter value"
|
||||
@@ -98,7 +95,6 @@
|
||||
"description": "Triggers after one or more counters reach their maximum value.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::counter::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::counter::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -108,7 +104,6 @@
|
||||
"description": "Triggers after one or more counters reach their minimum value.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::counter::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::counter::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -118,7 +113,6 @@
|
||||
"description": "Triggers after one or more counters are reset.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::counter::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::counter::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted covers.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted covers to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"awning_is_closed": {
|
||||
"description": "Tests if one or more awnings are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more awnings are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -30,7 +26,6 @@
|
||||
"description": "Tests if one or more blinds are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -40,7 +35,6 @@
|
||||
"description": "Tests if one or more blinds are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -50,7 +44,6 @@
|
||||
"description": "Tests if one or more curtains are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -60,7 +53,6 @@
|
||||
"description": "Tests if one or more curtains are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -70,7 +62,6 @@
|
||||
"description": "Tests if one or more shades are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -80,7 +71,6 @@
|
||||
"description": "Tests if one or more shades are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -90,7 +80,6 @@
|
||||
"description": "Tests if one or more shutters are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -100,7 +89,6 @@
|
||||
"description": "Tests if one or more shutters are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -265,7 +253,6 @@
|
||||
"description": "Triggers after one or more awnings close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -275,7 +262,6 @@
|
||||
"description": "Triggers after one or more awnings open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -285,7 +271,6 @@
|
||||
"description": "Triggers after one or more blinds close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -295,7 +280,6 @@
|
||||
"description": "Triggers after one or more blinds open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -305,7 +289,6 @@
|
||||
"description": "Triggers after one or more curtains close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -315,7 +298,6 @@
|
||||
"description": "Triggers after one or more curtains open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -325,7 +307,6 @@
|
||||
"description": "Triggers after one or more shades close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -335,7 +316,6 @@
|
||||
"description": "Triggers after one or more shades open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -345,7 +325,6 @@
|
||||
"description": "Triggers after one or more shutters close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -355,7 +334,6 @@
|
||||
"description": "Triggers after one or more shutters open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::cover::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::cover::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"services": {
|
||||
"set_value": {
|
||||
"description": "Sets the date.",
|
||||
"description": "Sets the value of a date.",
|
||||
"fields": {
|
||||
"date": {
|
||||
"description": "The date to set.",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"services": {
|
||||
"set_value": {
|
||||
"description": "Sets the date/time for a datetime entity.",
|
||||
"description": "Sets the value of a date/time.",
|
||||
"fields": {
|
||||
"datetime": {
|
||||
"description": "The date/time to set. The time zone of the Home Assistant instance is assumed.",
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The denon component."""
|
||||
"""The Denon Network Receivers integration."""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The denonavr component."""
|
||||
"""The Denon AVR Network Receivers integration."""
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted device trackers.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted device trackers to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_home": {
|
||||
"description": "Tests if one or more device trackers are home.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::device_tracker::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::device_tracker::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more device trackers are not home.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::device_tracker::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::device_tracker::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -129,7 +125,6 @@
|
||||
"description": "Triggers when one or more device trackers enter home.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::device_tracker::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::device_tracker::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -139,7 +134,6 @@
|
||||
"description": "Triggers when one or more device trackers leave home.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::device_tracker::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::device_tracker::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from devolo_home_control_api.devices.zwave import Zwave
|
||||
from devolo_home_control_api.homecontrol import HomeControl
|
||||
|
||||
@@ -188,6 +190,8 @@ class DevoloConsumptionEntity(DevoloMultiLevelDeviceEntity):
|
||||
def sync_callback(self, message: tuple) -> None:
|
||||
"""Update the consumption sensor state."""
|
||||
if message[0] == self._attr_unique_id:
|
||||
if TYPE_CHECKING:
|
||||
assert self._attr_unique_id is not None
|
||||
self._value = getattr(
|
||||
self._device_instance.consumption_property[self._attr_unique_id],
|
||||
self._sensor_type,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The dnsip component."""
|
||||
"""The DNS IP integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -17,7 +17,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload dnsip config entry."""
|
||||
"""Unload DNS IP config entry."""
|
||||
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted doors.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted doors to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_closed": {
|
||||
"description": "Tests if one or more doors are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::door::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::door::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more doors are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::door::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::door::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -48,7 +44,6 @@
|
||||
"description": "Triggers after one or more doors close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::door::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::door::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -58,7 +53,6 @@
|
||||
"description": "Triggers after one or more doors open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::door::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::door::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
"""The Dropbox integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from python_dropbox_api import (
|
||||
DropboxAPIClient,
|
||||
DropboxAuthException,
|
||||
DropboxUnknownException,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
ImplementationUnavailableError,
|
||||
OAuth2Session,
|
||||
async_get_config_entry_implementation,
|
||||
)
|
||||
|
||||
from .auth import DropboxConfigEntryAuth
|
||||
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
|
||||
type DropboxConfigEntry = ConfigEntry[DropboxAPIClient]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: DropboxConfigEntry) -> bool:
|
||||
"""Set up Dropbox from a config entry."""
|
||||
try:
|
||||
oauth2_implementation = await async_get_config_entry_implementation(hass, entry)
|
||||
except ImplementationUnavailableError as err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="oauth2_implementation_unavailable",
|
||||
) from err
|
||||
oauth2_session = OAuth2Session(hass, entry, oauth2_implementation)
|
||||
|
||||
auth = DropboxConfigEntryAuth(
|
||||
aiohttp_client.async_get_clientsession(hass), oauth2_session
|
||||
)
|
||||
|
||||
client = DropboxAPIClient(auth)
|
||||
|
||||
try:
|
||||
await client.get_account_info()
|
||||
except DropboxAuthException as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except (DropboxUnknownException, TimeoutError) as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
entry.runtime_data = client
|
||||
|
||||
def async_notify_backup_listeners() -> None:
|
||||
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
|
||||
listener()
|
||||
|
||||
entry.async_on_unload(entry.async_on_state_change(async_notify_backup_listeners))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: DropboxConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return True
|
||||
@@ -1,38 +0,0 @@
|
||||
"""Application credentials platform for the Dropbox integration."""
|
||||
|
||||
from homeassistant.components.application_credentials import ClientCredential
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
AbstractOAuth2Implementation,
|
||||
LocalOAuth2ImplementationWithPkce,
|
||||
)
|
||||
|
||||
from .const import OAUTH2_AUTHORIZE, OAUTH2_SCOPES, OAUTH2_TOKEN
|
||||
|
||||
|
||||
async def async_get_auth_implementation(
|
||||
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
|
||||
) -> AbstractOAuth2Implementation:
|
||||
"""Return custom auth implementation."""
|
||||
return DropboxOAuth2Implementation(
|
||||
hass,
|
||||
auth_domain,
|
||||
credential.client_id,
|
||||
OAUTH2_AUTHORIZE,
|
||||
OAUTH2_TOKEN,
|
||||
credential.client_secret,
|
||||
)
|
||||
|
||||
|
||||
class DropboxOAuth2Implementation(LocalOAuth2ImplementationWithPkce):
|
||||
"""Custom Dropbox OAuth2 implementation to add the necessary authorize url parameters."""
|
||||
|
||||
@property
|
||||
def extra_authorize_data(self) -> dict:
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
data: dict = {
|
||||
"token_access_type": "offline",
|
||||
"scope": " ".join(OAUTH2_SCOPES),
|
||||
}
|
||||
data.update(super().extra_authorize_data)
|
||||
return data
|
||||
@@ -1,44 +0,0 @@
|
||||
"""Authentication for Dropbox."""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from python_dropbox_api import Auth
|
||||
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||
|
||||
|
||||
class DropboxConfigEntryAuth(Auth):
|
||||
"""Provide Dropbox authentication tied to an OAuth2 based config entry."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
websession: ClientSession,
|
||||
oauth_session: OAuth2Session,
|
||||
) -> None:
|
||||
"""Initialize DropboxConfigEntryAuth."""
|
||||
super().__init__(websession)
|
||||
self._oauth_session = oauth_session
|
||||
|
||||
async def async_get_access_token(self) -> str:
|
||||
"""Return a valid access token."""
|
||||
await self._oauth_session.async_ensure_token_valid()
|
||||
|
||||
return cast(str, self._oauth_session.token["access_token"])
|
||||
|
||||
|
||||
class DropboxConfigFlowAuth(Auth):
|
||||
"""Provide authentication tied to a fixed token for the config flow."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
websession: ClientSession,
|
||||
token: str,
|
||||
) -> None:
|
||||
"""Initialize DropboxConfigFlowAuth."""
|
||||
super().__init__(websession)
|
||||
self._token = token
|
||||
|
||||
async def async_get_access_token(self) -> str:
|
||||
"""Return the fixed access token."""
|
||||
return self._token
|
||||
@@ -1,230 +0,0 @@
|
||||
"""Backup platform for the Dropbox integration."""
|
||||
|
||||
from collections.abc import AsyncIterator, Callable, Coroutine
|
||||
from functools import wraps
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Concatenate
|
||||
|
||||
from python_dropbox_api import (
|
||||
DropboxAPIClient,
|
||||
DropboxAuthException,
|
||||
DropboxFileOrFolderNotFoundException,
|
||||
DropboxUnknownException,
|
||||
)
|
||||
|
||||
from homeassistant.components.backup import (
|
||||
AgentBackup,
|
||||
BackupAgent,
|
||||
BackupAgentError,
|
||||
BackupNotFound,
|
||||
suggested_filename,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from . import DropboxConfigEntry
|
||||
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _suggested_filenames(backup: AgentBackup) -> tuple[str, str]:
|
||||
"""Return the suggested filenames for the backup and metadata."""
|
||||
base_name = suggested_filename(backup).rsplit(".", 1)[0]
|
||||
return f"{base_name}.tar", f"{base_name}.metadata.json"
|
||||
|
||||
|
||||
async def _async_string_iterator(content: str) -> AsyncIterator[bytes]:
|
||||
"""Yield a string as a single bytes chunk."""
|
||||
yield content.encode()
|
||||
|
||||
|
||||
def handle_backup_errors[_R, **P](
|
||||
func: Callable[Concatenate[DropboxBackupAgent, P], Coroutine[Any, Any, _R]],
|
||||
) -> Callable[Concatenate[DropboxBackupAgent, P], Coroutine[Any, Any, _R]]:
|
||||
"""Handle backup errors."""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(
|
||||
self: DropboxBackupAgent, *args: P.args, **kwargs: P.kwargs
|
||||
) -> _R:
|
||||
try:
|
||||
return await func(self, *args, **kwargs)
|
||||
except DropboxFileOrFolderNotFoundException as err:
|
||||
raise BackupNotFound(
|
||||
f"Failed to {func.__name__.removeprefix('async_').replace('_', ' ')}"
|
||||
) from err
|
||||
except DropboxAuthException as err:
|
||||
self._entry.async_start_reauth(self._hass)
|
||||
raise BackupAgentError("Authentication error") from err
|
||||
except DropboxUnknownException as err:
|
||||
_LOGGER.error(
|
||||
"Error during %s: %s",
|
||||
func.__name__,
|
||||
err,
|
||||
)
|
||||
_LOGGER.debug("Full error: %s", err, exc_info=True)
|
||||
raise BackupAgentError(
|
||||
f"Failed to {func.__name__.removeprefix('async_').replace('_', ' ')}"
|
||||
) from err
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
async def async_get_backup_agents(
|
||||
hass: HomeAssistant,
|
||||
**kwargs: Any,
|
||||
) -> list[BackupAgent]:
|
||||
"""Return a list of backup agents."""
|
||||
entries = hass.config_entries.async_loaded_entries(DOMAIN)
|
||||
return [DropboxBackupAgent(hass, entry) for entry in entries]
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_backup_agents_listener(
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
listener: Callable[[], None],
|
||||
**kwargs: Any,
|
||||
) -> Callable[[], None]:
|
||||
"""Register a listener to be called when agents are added or removed.
|
||||
|
||||
:return: A function to unregister the listener.
|
||||
"""
|
||||
hass.data.setdefault(DATA_BACKUP_AGENT_LISTENERS, []).append(listener)
|
||||
|
||||
@callback
|
||||
def remove_listener() -> None:
|
||||
"""Remove the listener."""
|
||||
hass.data[DATA_BACKUP_AGENT_LISTENERS].remove(listener)
|
||||
if not hass.data[DATA_BACKUP_AGENT_LISTENERS]:
|
||||
del hass.data[DATA_BACKUP_AGENT_LISTENERS]
|
||||
|
||||
return remove_listener
|
||||
|
||||
|
||||
class DropboxBackupAgent(BackupAgent):
|
||||
"""Backup agent for the Dropbox integration."""
|
||||
|
||||
domain = DOMAIN
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: DropboxConfigEntry) -> None:
|
||||
"""Initialize the backup agent."""
|
||||
super().__init__()
|
||||
self._hass = hass
|
||||
self._entry = entry
|
||||
self.name = entry.title
|
||||
assert entry.unique_id
|
||||
self.unique_id = entry.unique_id
|
||||
self._api: DropboxAPIClient = entry.runtime_data
|
||||
|
||||
async def _async_get_backups(self) -> list[tuple[AgentBackup, str]]:
|
||||
"""Get backups and their corresponding file names."""
|
||||
files = await self._api.list_folder("")
|
||||
|
||||
tar_files = {f.name for f in files if f.name.endswith(".tar")}
|
||||
metadata_files = [f for f in files if f.name.endswith(".metadata.json")]
|
||||
|
||||
backups: list[tuple[AgentBackup, str]] = []
|
||||
for metadata_file in metadata_files:
|
||||
tar_name = metadata_file.name.removesuffix(".metadata.json") + ".tar"
|
||||
if tar_name not in tar_files:
|
||||
_LOGGER.warning(
|
||||
"Found metadata file '%s' without matching backup file",
|
||||
metadata_file.name,
|
||||
)
|
||||
continue
|
||||
|
||||
metadata_stream = self._api.download_file(f"/{metadata_file.name}")
|
||||
raw = b"".join([chunk async for chunk in metadata_stream])
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
backup = AgentBackup.from_dict(data)
|
||||
except (json.JSONDecodeError, ValueError, TypeError, KeyError) as err:
|
||||
_LOGGER.warning(
|
||||
"Skipping invalid metadata file '%s': %s",
|
||||
metadata_file.name,
|
||||
err,
|
||||
)
|
||||
continue
|
||||
backups.append((backup, tar_name))
|
||||
|
||||
return backups
|
||||
|
||||
@handle_backup_errors
|
||||
async def async_upload_backup(
|
||||
self,
|
||||
*,
|
||||
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
|
||||
backup: AgentBackup,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Upload a backup."""
|
||||
backup_filename, metadata_filename = _suggested_filenames(backup)
|
||||
backup_path = f"/{backup_filename}"
|
||||
metadata_path = f"/{metadata_filename}"
|
||||
|
||||
file_stream = await open_stream()
|
||||
await self._api.upload_file(backup_path, file_stream)
|
||||
|
||||
metadata_stream = _async_string_iterator(json.dumps(backup.as_dict()))
|
||||
|
||||
try:
|
||||
await self._api.upload_file(metadata_path, metadata_stream)
|
||||
except (
|
||||
DropboxAuthException,
|
||||
DropboxUnknownException,
|
||||
):
|
||||
await self._api.delete_file(backup_path)
|
||||
raise
|
||||
|
||||
@handle_backup_errors
|
||||
async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
|
||||
"""List backups."""
|
||||
return [backup for backup, _ in await self._async_get_backups()]
|
||||
|
||||
@handle_backup_errors
|
||||
async def async_download_backup(
|
||||
self,
|
||||
backup_id: str,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterator[bytes]:
|
||||
"""Download a backup file."""
|
||||
backups = await self._async_get_backups()
|
||||
for backup, filename in backups:
|
||||
if backup.backup_id == backup_id:
|
||||
return self._api.download_file(f"/{filename}")
|
||||
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
@handle_backup_errors
|
||||
async def async_get_backup(
|
||||
self,
|
||||
backup_id: str,
|
||||
**kwargs: Any,
|
||||
) -> AgentBackup:
|
||||
"""Return a backup."""
|
||||
backups = await self._async_get_backups()
|
||||
|
||||
for backup, _ in backups:
|
||||
if backup.backup_id == backup_id:
|
||||
return backup
|
||||
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
|
||||
@handle_backup_errors
|
||||
async def async_delete_backup(
|
||||
self,
|
||||
backup_id: str,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Delete a backup file."""
|
||||
backups = await self._async_get_backups()
|
||||
for backup, tar_filename in backups:
|
||||
if backup.backup_id == backup_id:
|
||||
metadata_filename = tar_filename.removesuffix(".tar") + ".metadata.json"
|
||||
await self._api.delete_file(f"/{tar_filename}")
|
||||
await self._api.delete_file(f"/{metadata_filename}")
|
||||
return
|
||||
|
||||
raise BackupNotFound(f"Backup {backup_id} not found")
|
||||
@@ -1,60 +0,0 @@
|
||||
"""Config flow for Dropbox."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from python_dropbox_api import DropboxAPIClient
|
||||
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
|
||||
|
||||
from .auth import DropboxConfigFlowAuth
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
"""Config flow to handle Dropbox OAuth2 authentication."""
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Create an entry for the flow, or update existing entry."""
|
||||
access_token = data[CONF_TOKEN][CONF_ACCESS_TOKEN]
|
||||
|
||||
auth = DropboxConfigFlowAuth(async_get_clientsession(self.hass), access_token)
|
||||
|
||||
client = DropboxAPIClient(auth)
|
||||
account_info = await client.get_account_info()
|
||||
|
||||
await self.async_set_unique_id(account_info.account_id)
|
||||
if self.source == SOURCE_REAUTH:
|
||||
self._abort_if_unique_id_mismatch(reason="wrong_account")
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(), data=data
|
||||
)
|
||||
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(title=account_info.email, data=data)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Perform reauth upon an API authentication error."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Dialog that informs the user that reauth is required."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return await self.async_step_user()
|
||||
@@ -1,19 +0,0 @@
|
||||
"""Constants for the Dropbox integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
DOMAIN = "dropbox"
|
||||
|
||||
OAUTH2_AUTHORIZE = "https://www.dropbox.com/oauth2/authorize"
|
||||
OAUTH2_TOKEN = "https://api.dropboxapi.com/oauth2/token"
|
||||
OAUTH2_SCOPES = [
|
||||
"account_info.read",
|
||||
"files.content.read",
|
||||
"files.content.write",
|
||||
]
|
||||
|
||||
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
|
||||
f"{DOMAIN}.backup_agent_listeners"
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"domain": "dropbox",
|
||||
"name": "Dropbox",
|
||||
"after_dependencies": ["backup"],
|
||||
"codeowners": ["@bdr99"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/dropbox",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["python-dropbox-api==0.1.3"]
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: Integration does not register any actions.
|
||||
appropriate-polling:
|
||||
status: exempt
|
||||
comment: Integration does not poll.
|
||||
brands: done
|
||||
common-modules:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities or coordinators.
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not register any actions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
entity-unique-id:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
has-entity-name:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
status: exempt
|
||||
comment: Integration does not register any actions.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: Integration does not have any configuration parameters.
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates:
|
||||
status: exempt
|
||||
comment: Integration does not make any entity updates.
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
diagnostics:
|
||||
status: exempt
|
||||
comment: Integration does not have any data to diagnose.
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: Integration is a service.
|
||||
discovery:
|
||||
status: exempt
|
||||
comment: Integration is a service.
|
||||
docs-data-update:
|
||||
status: exempt
|
||||
comment: Integration does not update any data.
|
||||
docs-examples:
|
||||
status: exempt
|
||||
comment: Integration only provides backup functionality.
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices:
|
||||
status: exempt
|
||||
comment: Integration does not support any devices.
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: done
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
comment: Integration does not use any devices.
|
||||
entity-category:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
entity-device-class:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
entity-disabled-by-default:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: Integration does not have any repairs.
|
||||
stale-devices:
|
||||
status: exempt
|
||||
comment: Integration does not have any devices.
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: done
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
|
||||
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
||||
"wrong_account": "Wrong account: Please authenticate with the correct account."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
},
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "The Dropbox integration needs to re-authenticate your account.",
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"oauth2_implementation_unavailable": {
|
||||
"message": "[%key:common::exceptions::oauth2_implementation_unavailable::message%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ def _fix_device_registry_identifiers(
|
||||
if old_identifier not in device_entry.identifiers: # type: ignore[comparison-overlap]
|
||||
continue
|
||||
new_identifiers = device_entry.identifiers.copy()
|
||||
new_identifiers.discard(old_identifier) # type: ignore[arg-type]
|
||||
new_identifiers.discard(old_identifier)
|
||||
new_identifiers.add((DOMAIN, entry.data["station"]))
|
||||
device_registry.async_update_device(
|
||||
device_entry.id, new_identifiers=new_identifiers
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The ebox component."""
|
||||
"""The EBox integration."""
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The edimax component."""
|
||||
"""The Edimax integration."""
|
||||
|
||||
@@ -273,7 +273,7 @@ class ElevenLabsTTSEntity(TextToSpeechEntity):
|
||||
continue
|
||||
|
||||
# Build kwargs common to both modes
|
||||
kwargs = base_stream_params | {
|
||||
kwargs: dict[str, Any] = base_stream_params | {
|
||||
"text": text,
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ElkM1ConfigEntry) -> boo
|
||||
|
||||
elk_temp_unit = elk.panel.temperature_units
|
||||
if elk_temp_unit == "C":
|
||||
temperature_unit = UnitOfTemperature.CELSIUS
|
||||
temperature_unit = UnitOfTemperature.CELSIUS # type: ignore[unreachable]
|
||||
else:
|
||||
temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||
config["temperature_unit"] = temperature_unit
|
||||
|
||||
@@ -361,7 +361,8 @@ class EvoController(EvoClimateEntity):
|
||||
async def async_tcs_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
||||
"""Process a service request (system mode) for a controller.
|
||||
|
||||
Data validation is not required, it will have been done upstream.
|
||||
Data validation is not required here; it is performed upstream by the service
|
||||
handler (service schema plus runtime checks).
|
||||
"""
|
||||
|
||||
if service == EvoService.RESET_SYSTEM:
|
||||
@@ -387,9 +388,16 @@ class EvoController(EvoClimateEntity):
|
||||
) -> None:
|
||||
"""Set a Controller to any of its native operating modes."""
|
||||
until = dt_util.as_utc(until) if until else None
|
||||
await self.coordinator.call_client_api(
|
||||
self._evo_device.set_mode(mode, until=until)
|
||||
)
|
||||
try:
|
||||
await self.coordinator.call_client_api(
|
||||
self._evo_device.set_mode(mode, until=until)
|
||||
)
|
||||
except evo.InvalidSystemModeError as err:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_system_mode",
|
||||
translation_placeholders={"error": str(err)},
|
||||
) from err
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
|
||||
@@ -139,6 +139,9 @@ class EvoDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
try:
|
||||
result = await client_api
|
||||
|
||||
except ec2.InvalidSystemModeError:
|
||||
raise
|
||||
|
||||
except ec2.ApiRequestFailedError as err:
|
||||
self.logger.error(err)
|
||||
return None
|
||||
|
||||
@@ -5,17 +5,18 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
from typing import Any, Final
|
||||
|
||||
from evohomeasync2 import ControlSystem
|
||||
from evohomeasync2.const import SZ_CAN_BE_TEMPORARY, SZ_SYSTEM_MODE, SZ_TIMING_MODE
|
||||
from evohomeasync2.schemas.const import (
|
||||
S2_DURATION as SZ_DURATION,
|
||||
S2_PERIOD as SZ_PERIOD,
|
||||
SystemMode as EvoSystemMode,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.const import ATTR_MODE
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.service import verify_domain_control
|
||||
@@ -23,8 +24,19 @@ from homeassistant.helpers.service import verify_domain_control
|
||||
from .const import ATTR_DURATION, ATTR_PERIOD, ATTR_SETPOINT, DOMAIN, EvoService
|
||||
from .coordinator import EvoDataUpdateCoordinator
|
||||
|
||||
# system mode schemas are built dynamically when the services are registered
|
||||
# because supported modes can vary for edge-case systems
|
||||
# System service schemas (registered as domain services)
|
||||
SET_SYSTEM_MODE_SCHEMA: Final[dict[str | vol.Marker, Any]] = {
|
||||
# unsupported modes are rejected at runtime with ServiceValidationError
|
||||
vol.Required(ATTR_MODE): cv.string, # avoid vol.In(SystemMode)
|
||||
vol.Exclusive(ATTR_DURATION, "temporary"): vol.All(
|
||||
cv.time_period,
|
||||
vol.Range(min=timedelta(hours=0), max=timedelta(hours=24)),
|
||||
),
|
||||
vol.Exclusive(ATTR_PERIOD, "temporary"): vol.All(
|
||||
cv.time_period,
|
||||
vol.Range(min=timedelta(days=1), max=timedelta(days=99)),
|
||||
),
|
||||
}
|
||||
|
||||
# Zone service schemas (registered as entity services)
|
||||
SET_ZONE_OVERRIDE_SCHEMA: Final[dict[str | vol.Marker, Any]] = {
|
||||
@@ -59,16 +71,56 @@ def _register_zone_entity_services(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _validate_set_system_mode_params(tcs: ControlSystem, data: dict[str, Any]) -> None:
|
||||
"""Validate that a set_system_mode service call is properly formed."""
|
||||
|
||||
mode = data[ATTR_MODE]
|
||||
tcs_modes = {m[SZ_SYSTEM_MODE]: m for m in tcs.allowed_system_modes}
|
||||
|
||||
# Validation occurs here, instead of in the library, because it uses a slightly
|
||||
# different schema (until instead of duration/period) for the method invoked
|
||||
# via this service call
|
||||
|
||||
if (mode_info := tcs_modes.get(mode)) is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="mode_not_supported",
|
||||
translation_placeholders={ATTR_MODE: mode},
|
||||
)
|
||||
|
||||
# voluptuous schema ensures that duration and period are not both present
|
||||
|
||||
if not mode_info[SZ_CAN_BE_TEMPORARY]:
|
||||
if ATTR_DURATION in data or ATTR_PERIOD in data:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="mode_cant_be_temporary",
|
||||
translation_placeholders={ATTR_MODE: mode},
|
||||
)
|
||||
return
|
||||
|
||||
timing_mode = mode_info.get(SZ_TIMING_MODE) # will not be None, as can_be_temporary
|
||||
|
||||
if timing_mode == SZ_DURATION and ATTR_PERIOD in data:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="mode_cant_have_period",
|
||||
translation_placeholders={ATTR_MODE: mode},
|
||||
)
|
||||
|
||||
if timing_mode == SZ_PERIOD and ATTR_DURATION in data:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="mode_cant_have_duration",
|
||||
translation_placeholders={ATTR_MODE: mode},
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def setup_service_functions(
|
||||
hass: HomeAssistant, coordinator: EvoDataUpdateCoordinator
|
||||
) -> None:
|
||||
"""Set up the service handlers for the system/zone operating modes.
|
||||
|
||||
Not all Honeywell TCC-compatible systems support all operating modes. In addition,
|
||||
each mode will require any of four distinct service schemas. This has to be
|
||||
enumerated before registering the appropriate handlers.
|
||||
"""
|
||||
"""Set up the service handlers for Evohome systems."""
|
||||
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def force_refresh(call: ServiceCall) -> None:
|
||||
@@ -77,7 +129,14 @@ def setup_service_functions(
|
||||
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def set_system_mode(call: ServiceCall) -> None:
|
||||
"""Set the system mode."""
|
||||
"""Set the Evohome system mode or reset the system."""
|
||||
|
||||
# No additional validation for RESET_SYSTEM here, as the library method invoked
|
||||
# via that service call may be able to emulate the reset even if the system
|
||||
# doesn't support AutoWithReset natively
|
||||
|
||||
if call.service == EvoService.SET_SYSTEM_MODE:
|
||||
_validate_set_system_mode_params(coordinator.tcs, call.data)
|
||||
|
||||
payload = {
|
||||
"unique_id": coordinator.tcs.id,
|
||||
@@ -86,59 +145,14 @@ def setup_service_functions(
|
||||
}
|
||||
async_dispatcher_send(hass, DOMAIN, payload)
|
||||
|
||||
assert coordinator.tcs is not None # mypy
|
||||
|
||||
hass.services.async_register(DOMAIN, EvoService.REFRESH_SYSTEM, force_refresh)
|
||||
hass.services.async_register(DOMAIN, EvoService.RESET_SYSTEM, set_system_mode)
|
||||
|
||||
# Enumerate which operating modes are supported by this system
|
||||
modes = list(coordinator.tcs.allowed_system_modes)
|
||||
|
||||
system_mode_schemas = []
|
||||
modes = [m for m in modes if m[SZ_SYSTEM_MODE] != EvoSystemMode.AUTO_WITH_RESET]
|
||||
|
||||
# Permanent-only modes will use this schema
|
||||
perm_modes = [m[SZ_SYSTEM_MODE] for m in modes if not m[SZ_CAN_BE_TEMPORARY]]
|
||||
if perm_modes: # any of: "Auto", "HeatingOff": permanent only
|
||||
schema = vol.Schema({vol.Required(ATTR_MODE): vol.In(perm_modes)})
|
||||
system_mode_schemas.append(schema)
|
||||
|
||||
modes = [m for m in modes if m[SZ_CAN_BE_TEMPORARY]]
|
||||
|
||||
# These modes are set for a number of hours (or indefinitely): use this schema
|
||||
temp_modes = [m[SZ_SYSTEM_MODE] for m in modes if m[SZ_TIMING_MODE] == SZ_DURATION]
|
||||
if temp_modes: # any of: "AutoWithEco", permanent or for 0-24 hours
|
||||
schema = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_MODE): vol.In(temp_modes),
|
||||
vol.Optional(ATTR_DURATION): vol.All(
|
||||
cv.time_period,
|
||||
vol.Range(min=timedelta(hours=0), max=timedelta(hours=24)),
|
||||
),
|
||||
}
|
||||
)
|
||||
system_mode_schemas.append(schema)
|
||||
|
||||
# These modes are set for a number of days (or indefinitely): use this schema
|
||||
temp_modes = [m[SZ_SYSTEM_MODE] for m in modes if m[SZ_TIMING_MODE] == SZ_PERIOD]
|
||||
if temp_modes: # any of: "Away", "Custom", "DayOff", permanent or for 1-99 days
|
||||
schema = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_MODE): vol.In(temp_modes),
|
||||
vol.Optional(ATTR_PERIOD): vol.All(
|
||||
cv.time_period,
|
||||
vol.Range(min=timedelta(days=1), max=timedelta(days=99)),
|
||||
),
|
||||
}
|
||||
)
|
||||
system_mode_schemas.append(schema)
|
||||
|
||||
if system_mode_schemas:
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
EvoService.SET_SYSTEM_MODE,
|
||||
set_system_mode,
|
||||
schema=vol.Schema(vol.Any(*system_mode_schemas)),
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
EvoService.SET_SYSTEM_MODE,
|
||||
set_system_mode,
|
||||
schema=vol.Schema(SET_SYSTEM_MODE_SCHEMA),
|
||||
)
|
||||
|
||||
_register_zone_entity_services(hass)
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
{
|
||||
"exceptions": {
|
||||
"invalid_system_mode": {
|
||||
"message": "The requested system mode is not supported: {error}"
|
||||
},
|
||||
"mode_cant_be_temporary": {
|
||||
"message": "The mode `{mode}` does not support `duration` or `period`"
|
||||
},
|
||||
"mode_cant_have_duration": {
|
||||
"message": "The mode `{mode}` does not support `duration`; use `period` instead"
|
||||
},
|
||||
"mode_cant_have_period": {
|
||||
"message": "The mode `{mode}` does not support `period`; use `duration` instead"
|
||||
},
|
||||
"mode_not_supported": {
|
||||
"message": "The mode `{mode}` is not supported by this controller"
|
||||
},
|
||||
"zone_only_service": {
|
||||
"message": "Only zones support the `{service}` action"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted fans.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted fans to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_off": {
|
||||
"description": "Tests if one or more fans are off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::fan::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::fan::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more fans are on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::fan::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::fan::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -199,7 +195,6 @@
|
||||
"description": "Triggers after one or more fans turn off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::fan::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::fan::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -209,7 +204,6 @@
|
||||
"description": "Triggers after one or more fans turn on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::fan::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::fan::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The fido component."""
|
||||
"""The Fido integration."""
|
||||
|
||||
@@ -30,22 +30,31 @@ class FreshrFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 1
|
||||
|
||||
async def _validate_input(self, username: str, password: str) -> str | None:
|
||||
"""Validate credentials, returning an error key or None on success."""
|
||||
client = FreshrClient(session=async_get_clientsession(self.hass))
|
||||
try:
|
||||
await client.login(username, password)
|
||||
except LoginError:
|
||||
return "invalid_auth"
|
||||
except ClientError:
|
||||
return "cannot_connect"
|
||||
except Exception: # noqa: BLE001
|
||||
LOGGER.exception("Unexpected exception")
|
||||
return "unknown"
|
||||
return None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
client = FreshrClient(session=async_get_clientsession(self.hass))
|
||||
try:
|
||||
await client.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
|
||||
except LoginError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except ClientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception: # noqa: BLE001
|
||||
LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
error = await self._validate_input(
|
||||
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||
)
|
||||
if error:
|
||||
errors["base"] = error
|
||||
else:
|
||||
await self.async_set_unique_id(user_input[CONF_USERNAME].lower())
|
||||
self._abort_if_unique_id_configured()
|
||||
@@ -58,6 +67,34 @@ class FreshrFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration."""
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
error = await self._validate_input(
|
||||
reconfigure_entry.data[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||
)
|
||||
if error:
|
||||
errors["base"] = error
|
||||
else:
|
||||
return self.async_update_reload_and_abort(
|
||||
reconfigure_entry,
|
||||
data_updates={CONF_PASSWORD: user_input[CONF_PASSWORD]},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
data_schema=vol.Schema({vol.Required(CONF_PASSWORD): str}),
|
||||
description_placeholders={
|
||||
CONF_USERNAME: reconfigure_entry.data[CONF_USERNAME]
|
||||
},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, _user_input: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
@@ -72,18 +109,11 @@ class FreshrFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
|
||||
if user_input is not None:
|
||||
client = FreshrClient(session=async_get_clientsession(self.hass))
|
||||
try:
|
||||
await client.login(
|
||||
reauth_entry.data[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||
)
|
||||
except LoginError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except ClientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception: # noqa: BLE001
|
||||
LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
error = await self._validate_input(
|
||||
reauth_entry.data[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||
)
|
||||
if error:
|
||||
errors["base"] = error
|
||||
else:
|
||||
return self.async_update_reload_and_abort(
|
||||
reauth_entry,
|
||||
|
||||
@@ -62,7 +62,7 @@ rules:
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
reconfiguration-flow: done
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: No actionable repair scenarios exist; authentication failures are handled via the reauthentication flow.
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
@@ -19,6 +20,15 @@
|
||||
},
|
||||
"description": "Re-enter the password for your Fresh-r account `{username}`."
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "[%key:component::freshr::config::step::user::data_description::password%]"
|
||||
},
|
||||
"description": "Update the password for your Fresh-r account `{username}`."
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "system",
|
||||
"preview_features": { "winter_mode": {} },
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20260325.2"]
|
||||
"requirements": ["home-assistant-frontend==20260325.5"]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted garage doors.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted garage doors to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_closed": {
|
||||
"description": "Tests if one or more garage doors are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::garage_door::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::garage_door::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more garage doors are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::garage_door::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::garage_door::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -48,7 +44,6 @@
|
||||
"description": "Triggers after one or more garage doors close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::garage_door::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::garage_door::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -58,7 +53,6 @@
|
||||
"description": "Triggers after one or more garage doors open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::garage_door::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::garage_door::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted gates.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted gates to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_closed": {
|
||||
"description": "Tests if one or more gates are closed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::gate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::gate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more gates are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::gate::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::gate::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -48,7 +44,6 @@
|
||||
"description": "Triggers after one or more gates close.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::gate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::gate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -58,7 +53,6 @@
|
||||
"description": "Triggers after one or more gates open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::gate::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::gate::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The Glances component."""
|
||||
"""The Glances integration."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Constants for Glances component."""
|
||||
"""Constants for Glances integration."""
|
||||
|
||||
from datetime import timedelta
|
||||
import sys
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Support gathering system information of hosts which are running glances."""
|
||||
"""Support gathering system information of hosts which are running Glances."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
from functools import partial
|
||||
|
||||
from aiohttp.client_exceptions import ClientError, ClientResponseError
|
||||
from google.auth.exceptions import RefreshError
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
from google.oauth2.credentials import Credentials
|
||||
from googleapiclient.discovery import Resource, build
|
||||
|
||||
@@ -14,6 +13,8 @@ from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
OAuth2TokenRequestError,
|
||||
OAuth2TokenRequestReauthError,
|
||||
)
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
|
||||
@@ -37,24 +38,26 @@ class AsyncConfigEntryAuth:
|
||||
|
||||
async def check_and_refresh_token(self) -> str:
|
||||
"""Check the token."""
|
||||
setup_in_progress = (
|
||||
self.oauth_session.config_entry.state is ConfigEntryState.SETUP_IN_PROGRESS
|
||||
)
|
||||
|
||||
try:
|
||||
await self.oauth_session.async_ensure_token_valid()
|
||||
except (RefreshError, ClientResponseError, ClientError) as ex:
|
||||
if (
|
||||
self.oauth_session.config_entry.state
|
||||
is ConfigEntryState.SETUP_IN_PROGRESS
|
||||
):
|
||||
if isinstance(ex, ClientResponseError) and 400 <= ex.status < 500:
|
||||
raise ConfigEntryAuthFailed(
|
||||
"OAuth session is not valid, reauth required"
|
||||
) from ex
|
||||
except OAuth2TokenRequestReauthError as ex:
|
||||
if setup_in_progress:
|
||||
raise ConfigEntryAuthFailed(
|
||||
"OAuth session is not valid, reauth required"
|
||||
) from ex
|
||||
self.oauth_session.config_entry.async_start_reauth(self.oauth_session.hass)
|
||||
raise
|
||||
except OAuth2TokenRequestError as ex:
|
||||
if setup_in_progress:
|
||||
raise ConfigEntryNotReady from ex
|
||||
raise
|
||||
except ClientError as ex:
|
||||
if setup_in_progress:
|
||||
raise ConfigEntryNotReady from ex
|
||||
if isinstance(ex, RefreshError) or (
|
||||
hasattr(ex, "status") and ex.status == 400
|
||||
):
|
||||
self.oauth_session.config_entry.async_start_reauth(
|
||||
self.oauth_session.hass
|
||||
)
|
||||
raise HomeAssistantError(ex) from ex
|
||||
return self.access_token
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""The hikvisioncam component."""
|
||||
"""The Hikvision integration."""
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/homeassistant_hardware",
|
||||
"integration_type": "system",
|
||||
"requirements": [
|
||||
"serialx==0.6.2",
|
||||
"serialx==1.1.1",
|
||||
"universal-silabs-flasher==1.0.3",
|
||||
"ha-silabs-firmware-client==0.3.0"
|
||||
]
|
||||
|
||||
@@ -979,7 +979,7 @@ class HomeKit:
|
||||
for entry in dev_reg.devices.get_devices_for_config_entry_id(self._entry_id)
|
||||
if (
|
||||
identifier not in entry.identifiers # type: ignore[comparison-overlap]
|
||||
or connection not in entry.connections
|
||||
or connection not in entry.connections # type: ignore[unreachable]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"message": "Honeywell could not stop hold mode"
|
||||
},
|
||||
"switch_failed_off": {
|
||||
"message": "Honeywell could turn off emergency heat mode."
|
||||
"message": "Honeywell could not turn off emergency heat mode."
|
||||
},
|
||||
"switch_failed_on": {
|
||||
"message": "Honeywell could not set system mode to emergency heat mode."
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
"dismiss": {
|
||||
"service": "mdi:bell-off"
|
||||
},
|
||||
"dismiss_message": {
|
||||
"service": "mdi:comment-remove"
|
||||
},
|
||||
"send_message": {
|
||||
"service": "mdi:message-arrow-right"
|
||||
"service": "mdi:comment-arrow-right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,5 +27,28 @@ def deprecated_notify_action_call(
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_notify_action",
|
||||
translation_placeholders={"action": action},
|
||||
translation_placeholders={
|
||||
"action": action,
|
||||
"new_action_1": "notify.send_message",
|
||||
"new_action_2": "html5.send_message",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def deprecated_dismiss_action_call(hass: HomeAssistant) -> None:
|
||||
"""Deprecated action call."""
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_dismiss_action",
|
||||
breaks_in_ha_version="2026.11.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_dismiss_action",
|
||||
translation_placeholders={
|
||||
"action": "html5.dismiss",
|
||||
"new_action": "html5.dismiss_message",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -60,7 +60,7 @@ from .const import (
|
||||
SERVICE_DISMISS,
|
||||
)
|
||||
from .entity import HTML5Entity, Registration
|
||||
from .issue import deprecated_notify_action_call
|
||||
from .issue import deprecated_dismiss_action_call, deprecated_notify_action_call
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -460,6 +460,9 @@ class HTML5NotificationService(BaseNotificationService):
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
|
||||
deprecated_dismiss_action_call(self.hass)
|
||||
|
||||
data: dict[str, Any] | None = kwargs.get(ATTR_DATA)
|
||||
tag: str = data.get(ATTR_TAG, "") if data else ""
|
||||
payload = {ATTR_TAG: tag, ATTR_DISMISS: True, ATTR_DATA: {}}
|
||||
@@ -624,6 +627,11 @@ class HTML5NotifyEntity(HTML5Entity, NotifyEntity):
|
||||
await self._webpush(**kwargs)
|
||||
self._async_record_notification()
|
||||
|
||||
async def dismiss_notification(self, tag: str = "") -> None:
|
||||
"""Dismiss a message via html5.dismiss_message action."""
|
||||
await self._webpush(dismiss=True, tag=tag)
|
||||
self._async_record_notification()
|
||||
|
||||
async def _webpush(
|
||||
self,
|
||||
message: str | None = None,
|
||||
|
||||
@@ -32,6 +32,7 @@ from .const import (
|
||||
)
|
||||
|
||||
SERVICE_SEND_MESSAGE = "send_message"
|
||||
SERVICE_DISMISS_MESSAGE = "dismiss_message"
|
||||
|
||||
SERVICE_SEND_MESSAGE_SCHEMA = cv.make_entity_service_schema(
|
||||
{
|
||||
@@ -67,6 +68,10 @@ SERVICE_SEND_MESSAGE_SCHEMA = cv.make_entity_service_schema(
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_DISMISS_MESSAGE_SCHEMA = cv.make_entity_service_schema(
|
||||
{vol.Optional(ATTR_TAG): cv.string}
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
@@ -80,3 +85,11 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
schema=SERVICE_SEND_MESSAGE_SCHEMA,
|
||||
func="send_push_notification",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_DISMISS_MESSAGE,
|
||||
entity_domain=NOTIFY_DOMAIN,
|
||||
schema=SERVICE_DISMISS_MESSAGE_SCHEMA,
|
||||
func="dismiss_notification",
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ send_message:
|
||||
text:
|
||||
type: url
|
||||
example: /static/images/image.jpg
|
||||
tag:
|
||||
tag: &tag
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
@@ -142,3 +142,10 @@ send_message:
|
||||
selector:
|
||||
object:
|
||||
example: "{'customKey': 'customValue'}"
|
||||
dismiss_message:
|
||||
target:
|
||||
entity:
|
||||
domain: notify
|
||||
integration: html5
|
||||
fields:
|
||||
tag: *tag
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
"received": "Received"
|
||||
}
|
||||
},
|
||||
"tag": { "name": "Tag" }
|
||||
"tag": {
|
||||
"name": "[%key:component::html5::services::send_message::fields::tag::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,8 +51,12 @@
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_dismiss_action": {
|
||||
"description": "The action `{action}` is deprecated and will be removed in a future release.\n\nPlease update your automations and scripts to use the notify entities with the `{new_action}` action instead.",
|
||||
"title": "[%key:component::html5::issues::deprecated_notify_action::title%]"
|
||||
},
|
||||
"deprecated_notify_action": {
|
||||
"description": "The action `{action}` is deprecated and will be removed in a future release.\n\nPlease update your automations and scripts to use the notify entities with the `notify.send_message` or `html5.send_message` actions instead.",
|
||||
"description": "The action `{action}` is deprecated and will be removed in a future release.\n\nPlease update your automations and scripts to use the notify entities with the `{new_action_1}` or `{new_action_2}` actions instead.",
|
||||
"title": "Detected use of deprecated action {action}"
|
||||
}
|
||||
},
|
||||
@@ -101,6 +107,16 @@
|
||||
},
|
||||
"name": "Dismiss"
|
||||
},
|
||||
"dismiss_message": {
|
||||
"description": "Dismisses one or more HTML5 notifications.",
|
||||
"fields": {
|
||||
"tag": {
|
||||
"description": "The tag of the notifications to dismiss. If not specified, all notifications will be dismissed.",
|
||||
"name": "[%key:component::html5::services::send_message::fields::tag::name%]"
|
||||
}
|
||||
},
|
||||
"name": "Dismiss message"
|
||||
},
|
||||
"send_message": {
|
||||
"description": "Sends a message via HTML5 Push Notifications",
|
||||
"fields": {
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted humidifiers.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"trigger_behavior_description": "The behavior of the targeted humidifiers to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_drying": {
|
||||
"description": "Tests if one or more humidifiers are drying.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -22,7 +18,6 @@
|
||||
"description": "Tests if one or more humidifiers are humidifying.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -32,7 +27,6 @@
|
||||
"description": "Tests if one or more humidifiers are set to a specific mode.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
},
|
||||
"mode": {
|
||||
@@ -46,7 +40,6 @@
|
||||
"description": "Tests if one or more humidifiers are off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -56,7 +49,6 @@
|
||||
"description": "Tests if one or more humidifiers are on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -66,11 +58,9 @@
|
||||
"description": "Tests the target humidity of one or more humidifiers.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::humidifier::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::humidifier::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -219,7 +209,6 @@
|
||||
"description": "Triggers after the operation mode of one or more humidifiers changes.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
|
||||
},
|
||||
"mode": {
|
||||
@@ -233,7 +222,6 @@
|
||||
"description": "Triggers after one or more humidifiers start drying.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -243,7 +231,6 @@
|
||||
"description": "Triggers after one or more humidifiers start humidifying.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -253,7 +240,6 @@
|
||||
"description": "Triggers after one or more humidifiers turn off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -263,7 +249,6 @@
|
||||
"description": "Triggers after one or more humidifiers turn on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted entities.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
|
||||
"trigger_behavior_name": "Behavior",
|
||||
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
|
||||
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
|
||||
"trigger_threshold_name": "Threshold configuration"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_threshold_name": "Threshold type"
|
||||
},
|
||||
"conditions": {
|
||||
"is_value": {
|
||||
"description": "Tests if a relative humidity value is above a threshold, below a threshold, or in a range of values.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidity::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::humidity::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::humidity::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::humidity::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -47,7 +40,6 @@
|
||||
"description": "Triggers after one or more relative humidity values change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::humidity::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::humidity::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -57,11 +49,9 @@
|
||||
"description": "Triggers after one or more relative humidity values cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidity::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidity::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::humidity::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::humidity::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The Hyperion component."""
|
||||
"""The Hyperion integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Switch platform for Hyperion."""
|
||||
"""Camera platform for Hyperion."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted entities.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
|
||||
"trigger_behavior_name": "Behavior",
|
||||
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
|
||||
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
|
||||
"trigger_threshold_name": "Threshold configuration"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_threshold_name": "Threshold type"
|
||||
},
|
||||
"conditions": {
|
||||
"is_detected": {
|
||||
"description": "Tests if light is currently detected.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::illuminance::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::illuminance::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -25,7 +19,6 @@
|
||||
"description": "Tests if light is currently not detected.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::illuminance::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::illuminance::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -35,11 +28,9 @@
|
||||
"description": "Tests the illuminance value.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::illuminance::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::illuminance::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::illuminance::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::illuminance::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -67,7 +58,6 @@
|
||||
"description": "Triggers after one or more illuminance values change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::illuminance::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::illuminance::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -77,7 +67,6 @@
|
||||
"description": "Triggers after one or more light sensors stop detecting light.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::illuminance::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -87,11 +76,9 @@
|
||||
"description": "Triggers after one or more illuminance values cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::illuminance::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::illuminance::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::illuminance::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -101,7 +88,6 @@
|
||||
"description": "Triggers after one or more light sensors start detecting light.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::illuminance::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -23,7 +23,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IstaConfigEntry) -> bool
|
||||
ista = PyEcotrendIsta(
|
||||
entry.data[CONF_EMAIL],
|
||||
entry.data[CONF_PASSWORD],
|
||||
_LOGGER,
|
||||
)
|
||||
|
||||
coordinator = IstaCoordinator(hass, entry, ista)
|
||||
|
||||
@@ -51,7 +51,6 @@ class IstaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
ista = PyEcotrendIsta(
|
||||
user_input[CONF_EMAIL],
|
||||
user_input[CONF_PASSWORD],
|
||||
_LOGGER,
|
||||
)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(ista.login)
|
||||
@@ -102,7 +101,6 @@ class IstaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
ista = PyEcotrendIsta(
|
||||
user_input[CONF_EMAIL],
|
||||
user_input[CONF_PASSWORD],
|
||||
_LOGGER,
|
||||
)
|
||||
|
||||
def get_consumption_units() -> set[str]:
|
||||
|
||||
@@ -94,10 +94,8 @@ class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
result = self.ista.get_consumption_unit_details()
|
||||
|
||||
return {
|
||||
consumption_unit: next(
|
||||
details
|
||||
for details in result["consumptionUnits"]
|
||||
if details["id"] == consumption_unit
|
||||
)
|
||||
consumption_unit: details
|
||||
for consumption_unit in self.ista.get_uuids()
|
||||
for details in result["consumptionUnits"]
|
||||
if details["id"] == consumption_unit
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The kodi component."""
|
||||
"""The Kodi integration."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Constants for the Kodi platform."""
|
||||
"""Constants for the Kodi integration."""
|
||||
|
||||
DOMAIN = "kodi"
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted lawn mowers.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted lawn mowers to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_docked": {
|
||||
"description": "Tests if one or more lawn mowers are docked.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more lawn mowers are encountering an error.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -30,7 +26,6 @@
|
||||
"description": "Tests if one or more lawn mowers are mowing.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -40,7 +35,6 @@
|
||||
"description": "Tests if one or more lawn mowers are paused.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -50,7 +44,6 @@
|
||||
"description": "Tests if one or more lawn mowers are returning to the dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -104,7 +97,6 @@
|
||||
"description": "Triggers after one or more lawn mowers have returned to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -114,7 +106,6 @@
|
||||
"description": "Triggers after one or more lawn mowers encounter an error.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -124,7 +115,6 @@
|
||||
"description": "Triggers after one or more lawn mowers pause mowing.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -134,7 +124,6 @@
|
||||
"description": "Triggers after one or more lawn mowers start mowing.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -144,7 +133,6 @@
|
||||
"description": "Triggers after one or more lawn mowers start returning to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["bluetooth-data-tools==1.28.4", "led-ble==1.1.7"]
|
||||
"requirements": ["bluetooth-data-tools==1.28.4", "led-ble==1.1.8"]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = {
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
entity_registry_enabled_default=False,
|
||||
attributes_fn=lambda data: {
|
||||
album.title: album.artist.artistName for album in data.records
|
||||
album.title: album.artist.artistName # type: ignore[misc]
|
||||
for album in data.records
|
||||
},
|
||||
),
|
||||
"albums": LidarrSensorEntityDescription[int](
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted lights.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"condition_threshold_description": "What to test for and threshold values.",
|
||||
"condition_threshold_name": "Threshold configuration",
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"field_brightness_description": "Number indicating brightness, where 0 turns the light off, 1 is the minimum brightness, and 255 is the maximum brightness.",
|
||||
"field_brightness_name": "Brightness value",
|
||||
"field_brightness_pct_description": "Number indicating the percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness, and 100 is the maximum brightness.",
|
||||
@@ -37,22 +35,17 @@
|
||||
"field_xy_color_description": "Color in XY-format. A list of two decimal numbers between 0 and 1.",
|
||||
"field_xy_color_name": "XY-color",
|
||||
"section_advanced_fields_name": "Advanced options",
|
||||
"trigger_behavior_description": "The behavior of the targeted lights to trigger on.",
|
||||
"trigger_behavior_name": "Behavior",
|
||||
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
|
||||
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
|
||||
"trigger_threshold_name": "Threshold configuration"
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_threshold_name": "Threshold type"
|
||||
},
|
||||
"conditions": {
|
||||
"is_brightness": {
|
||||
"description": "Tests the brightness of one or more lights.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::light::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::light::common::condition_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::light::common::condition_threshold_description%]",
|
||||
"name": "[%key:component::light::common::condition_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -62,7 +55,6 @@
|
||||
"description": "Tests if one or more lights are off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::light::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::light::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -72,7 +64,6 @@
|
||||
"description": "Tests if one or more lights are on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::light::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::light::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -513,7 +504,6 @@
|
||||
"description": "Triggers after the brightness of one or more lights changes.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"description": "[%key:component::light::common::trigger_threshold_changed_description%]",
|
||||
"name": "[%key:component::light::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -523,11 +513,9 @@
|
||||
"description": "Triggers after the brightness of one or more lights crosses a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::light::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::light::common::trigger_behavior_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "[%key:component::light::common::trigger_threshold_crossed_description%]",
|
||||
"name": "[%key:component::light::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
@@ -537,7 +525,6 @@
|
||||
"description": "Triggers after one or more lights turn off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::light::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::light::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -547,7 +534,6 @@
|
||||
"description": "Triggers after one or more lights turn on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::light::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::light::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted locks.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted locks to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_jammed": {
|
||||
"description": "Tests if one or more locks are jammed.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -20,7 +17,6 @@
|
||||
"description": "Tests if one or more locks are locked.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -30,7 +26,6 @@
|
||||
"description": "Tests if one or more locks are open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -40,7 +35,6 @@
|
||||
"description": "Tests if one or more locks are unlocked.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -151,7 +145,6 @@
|
||||
"description": "Triggers after one or more locks jam.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -161,7 +154,6 @@
|
||||
"description": "Triggers after one or more locks lock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -171,7 +163,6 @@
|
||||
"description": "Triggers after one or more locks open.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
@@ -181,7 +172,6 @@
|
||||
"description": "Triggers after one or more locks unlock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lock::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::lock::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user