mirror of
https://github.com/home-assistant/core.git
synced 2026-06-30 18:45:58 +02:00
Compare commits
52 Commits
dev
..
2026.7.0b2
| Author | SHA1 | Date | |
|---|---|---|---|
| 03fb4b099c | |||
| 46a6048f60 | |||
| c598d2c10e | |||
| 67bcd7550c | |||
| e44e822cec | |||
| daff150276 | |||
| 1f33859297 | |||
| 512fe8c022 | |||
| 6f038bb5b2 | |||
| d0b5162507 | |||
| 9e9978b6cb | |||
| 76feb821f4 | |||
| cd41529a89 | |||
| 8a1434332d | |||
| 36b714b513 | |||
| 46f1e4c957 | |||
| 431dcda092 | |||
| c575ef51b9 | |||
| 87690d2000 | |||
| 7afb26b1c0 | |||
| b6d5af0480 | |||
| f56098df5f | |||
| 427dd028f5 | |||
| cef9461610 | |||
| ace5398012 | |||
| 3791c83b95 | |||
| 4bdfa5c25b | |||
| 5a60771a14 | |||
| 70aba68326 | |||
| f20f86a067 | |||
| ee0c98e450 | |||
| 907a5c3c6c | |||
| a1e1b400f3 | |||
| 1544ae83dd | |||
| 145c490816 | |||
| bb7a756f84 | |||
| 183e6af8c2 | |||
| 2b66d045ff | |||
| 4841329814 | |||
| e710fc8782 | |||
| 99e18dcdd8 | |||
| eeedf28b6f | |||
| 5cca9328d6 | |||
| ebf3de3073 | |||
| 41e79927d0 | |||
| 4022eb93de | |||
| 28171dfe90 | |||
| e9af932fbe | |||
| 3212e0f051 | |||
| 87f0720450 | |||
| 534ff3f3dc | |||
| 1096c8af13 |
@@ -1,98 +0,0 @@
|
||||
---
|
||||
name: bump-dependency
|
||||
description: Bumps a Python package dependency across Home Assistant Core integrations, regenerates core requirement files, runs verification tests and prek lint, and prepares a pull request with proper release/compare links.
|
||||
---
|
||||
|
||||
# Bump Python Package Dependency in Home Assistant Core
|
||||
|
||||
Follow these systematic steps to successfully bump a python package requirement in the repository, regenerate necessary derivative files, verify the integration, and raise a pull request.
|
||||
|
||||
## Gotchas & Non-Obvious Constraints
|
||||
|
||||
- **PR Template Integrity**: Follow Home Assistant's Pull Request template (`.github/PULL_REQUEST_TEMPLATE.md`) exactly as written, including any instructions inside the template itself. Preserve all sections, comments, and unchecked checkboxes unless the template explicitly says otherwise; the only allowed removal is the **Breaking change** section when the template instructs you to remove it if not applicable.
|
||||
- **GitHub Tag Volatility**: Release tags on GitHub are highly inconsistent (e.g., `v1.2.3` vs `1.2.3` vs `release-1.2.3`). Always use the automated resolver `resolve_dependency.py` to check HEAD status for correct tags before hardcoding comparison URLs.
|
||||
|
||||
## Step-by-Step Workflow Checklist
|
||||
|
||||
### Phase A: Research and Plan
|
||||
- [ ] **1. Identify Targets**: Note the requested target package and target version to bump.
|
||||
- [ ] **2. Discover Codebase References**: Search the codebase to find all `manifest.json` and requirements files referencing the package.
|
||||
- [ ] **3. Resolve Version/Tag Details**: Run the integrated validation helper script to resolve version details, GitHub repo, release tag format, and formatted PR links:
|
||||
```bash
|
||||
uv run python3 ./.claude/skills/bump-dependency/scripts/resolve_dependency.py <package> <old_version> [--new-version <new_version>]
|
||||
```
|
||||
- [ ] **4. Plan-Validate-Execute (Draft Plan)**: Before modifying any files, write a brief, structured plan outlining the integrations to change, old version, new version, and the resolved comparison link. Show this draft plan to the user.
|
||||
|
||||
### Phase B: Execute and Validate (Local Changes)
|
||||
- [ ] **5. Check Uncommitted Changes**: Check for any uncommitted changes in the repository. If they exist, ask the user whether to stash, commit, or discard them before proceeding.
|
||||
- [ ] **6. Git Branch Setup**: Create a clean branch starting from the latest `upstream/dev`:
|
||||
```bash
|
||||
git fetch upstream dev
|
||||
git checkout -b bump-<package>-to-<version> upstream/dev
|
||||
```
|
||||
- [ ] **7. Apply Bump to manifests**: Update the version constraint string in all identified `manifest.json` files (e.g., change `"package==1.0.0"` to `"package==1.1.0"`).
|
||||
- [ ] **8. Regenerate Core Requirements**: Run the requirements generator to update all derivative requirements and constraint files:
|
||||
```bash
|
||||
uv run python3 -m script.gen_requirements_all
|
||||
```
|
||||
- [ ] **9. Validate Requirements**: Check `git diff` to ensure that only the targeted `manifest.json` files and `requirements_all.txt` (and potentially standard constraints) were modified. No unrelated files must be affected.
|
||||
- [ ] **10. Local Venv Verification**: Install the exact targeted package version directly inside the virtual environment:
|
||||
```bash
|
||||
uv pip install "<package>==<version>"
|
||||
```
|
||||
|
||||
### Phase C: Validation Loop (Tests & Lint)
|
||||
- [ ] **11. Run Integration Tests**: Execute the pytest suite for all integrations that consume the bumped package:
|
||||
```bash
|
||||
uv run pytest tests/components/<integration_name>
|
||||
```
|
||||
- *Validation Loop*: If tests fail, analyze the error, apply appropriate fixes, and re-run pytest until all tests pass cleanly.
|
||||
- [ ] **12. Run prek Lint Checks**: Run the local prek hooks on modified files:
|
||||
```bash
|
||||
uv run prek run
|
||||
```
|
||||
- *Validation Loop*: If prek checks report any formatting or linting violations, fix them and repeat `uv run prek run` until it passes completely without errors.
|
||||
|
||||
### Phase D: User Confirmation & PR Creation
|
||||
- [ ] **13. Commit Changes**: Commit the clean changes:
|
||||
```bash
|
||||
git add <modified_files>
|
||||
git commit -m "Bump <package> to <version>"
|
||||
```
|
||||
- [ ] **14. Push Branch**: Push the local branch to your origin remote:
|
||||
```bash
|
||||
git push origin bump-<package>-to-<version>
|
||||
```
|
||||
- [ ] **15. PR Description Preparation**: Generate the pull request body from `.github/PULL_REQUEST_TEMPLATE.md`:
|
||||
- **Proposed change**: Describe the package, old version, new version, target/source branches, and insert the resolved PyPI, changelog, and comparison diff links.
|
||||
- **Type of change**: Check only 1 box in this section, and mark the `Dependency upgrade` checkbox as checked: `[x] Dependency upgrade`.
|
||||
- **Breaking change**: You may remove the "Breaking change" section entirely from the template.
|
||||
- **Validation checklists**: Mark `The code change is tested` checkbox as checked: `[x] The code change is tested`.
|
||||
- **Keep remaining template intact**: Do NOT remove any other commented-out blocks, headers, or unchecked checkboxes in the template.
|
||||
- [ ] **16. Mandatory Review Presentation**: Format the PR proposal using the **PR Presentation Template** below and display it to the user. **Stop and wait for the user to review and explicitly confirm/approve the PR template and draft details before creating the PR.**
|
||||
- [ ] **17. Raise Pull Request**: Once the user approves, create the Pull Request using the GitHub CLI:
|
||||
```bash
|
||||
gh pr create --repo home-assistant/core --base dev --head <username>:bump-<package>-to-<version> --title "Bump <package> to <version>" --body-file <pr_body_file>
|
||||
```
|
||||
|
||||
## PR Presentation Template
|
||||
|
||||
```markdown
|
||||
### 🚀 Dependency Bump Pull Request Draft Review
|
||||
|
||||
- **Package**: `<package_name>` (`<old_version>` → `<new_version>`)
|
||||
- **PR Title**: `Bump <package_name> to <new_version>`
|
||||
- **Target Branch**: `dev`
|
||||
- **Head Branch**: `<fork_username>:bump-<package_name>-to-<new_version>`
|
||||
|
||||
#### 🔗 PyPI & GitHub Links
|
||||
- **PyPI Release**: https://pypi.org/project/<package_name>/<new_version>/
|
||||
- **Changelog Link**: `<changelog_url>`
|
||||
- **Comparison Diff**: `<compare_url>`
|
||||
|
||||
#### 📁 Modified Files
|
||||
- `<list_of_modified_files>`
|
||||
|
||||
#### 📝 Proposed PR Body
|
||||
<render the complete filled PR template body here, showing all checks and modifications for user approval>
|
||||
```
|
||||
@@ -1,205 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# ruff: noqa: T201, D103, BLE001
|
||||
"""Helper script to resolve package details, GitHub repo, release tags, and diff links from PyPI."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
_VERSION_MATCH = r"^[a-zA-Z0-9_\-\.\+\!\*]+$"
|
||||
_REPO_MATCH = r"https?://(?:www\.)?github\.com/([^/]+)/([^/]+)"
|
||||
# Project owner or repo name
|
||||
_NAME_MATCH = r"^[a-zA-Z0-9_\-\.]+$"
|
||||
|
||||
|
||||
def get_pypi_data(package_name):
|
||||
# Sanitize and URL-quote the package name to prevent URL path injection
|
||||
safe_package_name = urllib.parse.quote(package_name)
|
||||
url = f"https://pypi.org/pypi/{safe_package_name}/json"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"User-Agent": "HomeAssistant-Skill-Resolver/1.0",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
return json.loads(response.read().decode())
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"Error fetching PyPI data (HTTP {e.code}): {e.reason}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError as e:
|
||||
print(f"Network error fetching PyPI data: {e.reason}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error fetching PyPI data: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def find_github_repo(info):
|
||||
urls = []
|
||||
if info.get("home_page"):
|
||||
urls.append(info["home_page"])
|
||||
if info.get("project_urls"):
|
||||
urls.extend(info["project_urls"].values())
|
||||
|
||||
for u in urls:
|
||||
if not u:
|
||||
continue
|
||||
cleaned_url = u.replace("git+", "")
|
||||
m = re.search(_REPO_MATCH, cleaned_url, re.IGNORECASE)
|
||||
if m:
|
||||
owner, repo = m.groups()
|
||||
# Strip query parameters, hashes, trailing slashes, and .git extension
|
||||
repo = repo.split("?")[0].split("#")[0].split("/")[0]
|
||||
repo = repo.removesuffix(".git")
|
||||
# Validate that the owner and repo name conform to valid formats,
|
||||
# preventing prompt injection payloads embedded in repository URLs.
|
||||
if re.match(_NAME_MATCH, owner) and re.match(_NAME_MATCH, repo):
|
||||
return f"https://github.com/{owner}/{repo}"
|
||||
return None
|
||||
|
||||
|
||||
def check_github_tag(repo_url, version):
|
||||
# Try with 'v' prefix, without prefix, and with 'release-' prefix
|
||||
tag_options = [f"v{version}", version, f"release-{version}"]
|
||||
for tag in tag_options:
|
||||
# Check both tree and releases paths on GitHub
|
||||
for path_template in [f"tree/{tag}", f"releases/tag/{tag}"]:
|
||||
url = f"{repo_url}/{path_template}"
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
method="HEAD",
|
||||
headers={"User-Agent": "HomeAssistant-Skill-Resolver/1.0"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
if resp.status == 200:
|
||||
return tag
|
||||
except urllib.error.HTTPError as e:
|
||||
# If it's a 404, we continue checking other tag/path options
|
||||
if e.code == 404:
|
||||
continue
|
||||
# For non-404 HTTP errors (like 403 Forbidden/429 rate limit), exit with error to avoid false reports
|
||||
print(
|
||||
f"\n[ERROR] HTTP error contacting GitHub ({e.code} {e.reason}) for tag '{tag}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError as e:
|
||||
# Connection or timeout error
|
||||
print(
|
||||
f"\n[ERROR] Network error contacting GitHub: {e.reason}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
# Unexpected exceptions
|
||||
print(
|
||||
f"\n[ERROR] Unexpected error verifying tag '{tag}': {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Resolve PyPI package info and GitHub release diffs."
|
||||
)
|
||||
parser.add_argument("package", help="Name of the PyPI package")
|
||||
parser.add_argument(
|
||||
"old_version", help="Current version installed in Home Assistant"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--new-version", help="Target version to bump to (defaults to latest on PyPI)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not re.match(_NAME_MATCH, args.package):
|
||||
print(f"[ERROR] Invalid package name format: '{args.package}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not re.match(_VERSION_MATCH, args.old_version):
|
||||
print(
|
||||
f"[ERROR] Invalid old version format: '{args.old_version}'", file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if args.new_version and not re.match(_VERSION_MATCH, args.new_version):
|
||||
print(
|
||||
f"[ERROR] Invalid target version format: '{args.new_version}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
data = get_pypi_data(args.package)
|
||||
info = data.get("info", {})
|
||||
|
||||
# Filter out pre-releases from the releases list to identify the latest stable version
|
||||
stable_versions = []
|
||||
for v in data.get("releases", {}):
|
||||
try:
|
||||
ver = Version(v)
|
||||
if not ver.is_prerelease:
|
||||
stable_versions.append(ver)
|
||||
except Exception:
|
||||
continue
|
||||
latest = str(max(stable_versions)) if stable_versions else info.get("version")
|
||||
|
||||
if latest and not re.match(_VERSION_MATCH, latest):
|
||||
print("[ERROR] Invalid latest version resolved from PyPI.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
target_version = args.new_version or latest
|
||||
if not target_version:
|
||||
print("[ERROR] Could not resolve a target version from PyPI.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
github_repo = find_github_repo(info)
|
||||
|
||||
print("--- RESOLVED DEPENDENCY DETAILS ---")
|
||||
print(f"Package: {args.package}")
|
||||
print(f"Current Version: {args.old_version}")
|
||||
print(f"Latest (PyPI): {latest}")
|
||||
print(f"Target Version: {target_version}")
|
||||
|
||||
if github_repo:
|
||||
print(f"GitHub Repository: {github_repo}")
|
||||
|
||||
# Verify tag formats on GitHub
|
||||
old_tag = check_github_tag(github_repo, args.old_version)
|
||||
new_tag = check_github_tag(github_repo, target_version)
|
||||
|
||||
if not old_tag or not new_tag:
|
||||
missing_tags = []
|
||||
if not old_tag:
|
||||
missing_tags.append(args.old_version)
|
||||
if not new_tag:
|
||||
missing_tags.append(target_version)
|
||||
print(
|
||||
f"\n[ERROR] Could not resolve GitHub release tag for version(s): {', '.join(missing_tags)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
changelog_url = f"{github_repo}/releases/tag/{new_tag}"
|
||||
compare_url = f"{github_repo}/compare/{old_tag}...{new_tag}"
|
||||
|
||||
print("\n--- PR DOCUMENTATION LINKS ---")
|
||||
print(f"Changelog Link: {changelog_url}")
|
||||
print(f"Comparison Diff Link: {compare_url}")
|
||||
else:
|
||||
print("\n[WARNING] GitHub repository could not be resolved from PyPI metadata.")
|
||||
print("Please resolve the release notes and diff links manually.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -54,4 +54,3 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
|
||||
@@ -14,7 +14,7 @@ env:
|
||||
UV_HTTP_TIMEOUT: 60
|
||||
UV_SYSTEM_PYTHON: "true"
|
||||
# Base image version from https://github.com/home-assistant/docker
|
||||
BASE_IMAGE_VERSION: "2026.07.0"
|
||||
BASE_IMAGE_VERSION: "2026.05.0"
|
||||
ARCHITECTURES: '["amd64", "aarch64"]'
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "requirements*.txt"
|
||||
- "**/requirements*.txt"
|
||||
- "homeassistant/package_constraints.txt"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -59,7 +58,6 @@ jobs:
|
||||
echo "head_sha=${HEAD_SHA}" >> "${GITHUB_OUTPUT}"
|
||||
- name: Run deterministic checks
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ inputs.pull_request_number || github.event.pull_request.number }}
|
||||
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
|
||||
run: |
|
||||
|
||||
+125
-54
@@ -1,5 +1,5 @@
|
||||
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"36a7fc263a2ce868d74a266f23eb7772d82fd397806464384fe087479ddd4a70","body_hash":"bba8c011f2b82bb4d9847a359f43f0e7d91245b280678c20e5112b3c9e77d5cd","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5c2fe865bb4dc46e1450f6ee0d0541d759aea73a","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]}
|
||||
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7b142e96e0f8b454cdcc9c0c25070cf9a52c44d83a6b1fbc3ad6725b6567337c","body_hash":"3894ded07d5934ac5f29d160ffb1f9115cf72b6da8a7e453a4d4f69e8641a48e","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"v0.79.6","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]}
|
||||
# ___ _ _
|
||||
# / _ \ | | (_)
|
||||
# | |_| | __ _ ___ _ __ | |_ _ ___
|
||||
@@ -36,7 +36,7 @@
|
||||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
# - github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
# - github/gh-aw-actions/setup@v0.79.6
|
||||
#
|
||||
# Container images used:
|
||||
# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -344,8 +344,9 @@ jobs:
|
||||
agent:
|
||||
needs:
|
||||
- activation
|
||||
- prepare
|
||||
if: (needs.prepare.outputs.skip != 'true') && (needs.activation.outputs.daily_effective_workflow_exceeded != 'true')
|
||||
- extract_pr_number
|
||||
- gate
|
||||
if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -382,7 +383,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -488,15 +489,15 @@ jobs:
|
||||
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
|
||||
mkdir -p /tmp/gh-aw/safeoutputs
|
||||
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_823c5547a5e52957_EOF'
|
||||
{"add_comment":{"max":1,"target":"${{ needs.prepare.outputs.pr_number }}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_823c5547a5e52957_EOF
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f496a449c5dccca1_EOF'
|
||||
{"add_comment":{"max":1,"target":"${{ needs.extract_pr_number.outputs.pr_number }}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_f496a449c5dccca1_EOF
|
||||
- name: Generate Safe Outputs Tools
|
||||
env:
|
||||
GH_AW_TOOLS_META_JSON: |
|
||||
{
|
||||
"description_suffixes": {
|
||||
"add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: ${{ needs.prepare.outputs.pr_number }}. Supports reply_to_id for discussion threading."
|
||||
"add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: ${{ needs.extract_pr_number.outputs.pr_number }}. Supports reply_to_id for discussion threading."
|
||||
},
|
||||
"repo_params": {},
|
||||
"dynamic_tools": []
|
||||
@@ -993,7 +994,8 @@ jobs:
|
||||
- activation
|
||||
- agent
|
||||
- detection
|
||||
- prepare
|
||||
- extract_pr_number
|
||||
- gate
|
||||
- safe_outputs
|
||||
if: >
|
||||
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
|
||||
@@ -1016,7 +1018,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1206,7 +1208,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1427,6 +1429,111 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
extract_pr_number:
|
||||
needs: gate
|
||||
if: needs.gate.outputs.skip != 'true' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
|
||||
outputs:
|
||||
pr_number: ${{ steps.extract.outputs.pr_number }}
|
||||
steps:
|
||||
- name: Configure GH_HOST for enterprise compatibility
|
||||
id: ghes-host-config
|
||||
shell: bash
|
||||
# zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||||
run: |
|
||||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||||
GH_HOST="${GITHUB_SERVER_URL#https://}"
|
||||
GH_HOST="${GH_HOST#http://}"
|
||||
echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
|
||||
- name: Download deterministic-results artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: check-requirements-deterministic
|
||||
path: /tmp/deterministic
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
- name: Extract PR number from artifact
|
||||
id: extract
|
||||
run: |
|
||||
PR=$(jq -r '.pr_number' /tmp/deterministic/results.json)
|
||||
echo "pr_number=${PR}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
gate:
|
||||
needs: activation
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
outputs:
|
||||
skip: ${{ steps.gate.outputs.skip }}
|
||||
steps:
|
||||
- name: Configure GH_HOST for enterprise compatibility
|
||||
id: ghes-host-config
|
||||
shell: bash
|
||||
# zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||||
run: |
|
||||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||||
GH_HOST="${GITHUB_SERVER_URL#https://}"
|
||||
GH_HOST="${GH_HOST#http://}"
|
||||
echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
|
||||
- name: Download deterministic-results artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: check-requirements-deterministic
|
||||
path: /tmp/gate
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
- name: Decide whether requirements changed since the last comment
|
||||
id: gate
|
||||
run: |
|
||||
PR=$(jq -r '.pr_number' /tmp/gate/results.json)
|
||||
HEAD=$(jq -r '.head_sha // empty' /tmp/gate/results.json)
|
||||
if [ -z "${HEAD}" ]; then
|
||||
echo "Artifact has no head_sha; running the agent."
|
||||
exit 0
|
||||
fi
|
||||
# Recover the commit recorded in the most recent requirements-check
|
||||
# comment from the "Checked at commit" link
|
||||
PRIOR=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" \
|
||||
--jq '.[] | select(.body | contains("<!-- requirements-check -->")) | .body' \
|
||||
| grep -oiE '/commit/[0-9a-f]{40}' \
|
||||
| grep -oiE '[0-9a-f]{40}' | tail -1 || true)
|
||||
if [ -z "${PRIOR}" ]; then
|
||||
echo "No previous comment with a recorded commit; running the agent."
|
||||
exit 0
|
||||
fi
|
||||
if [ "${PRIOR}" = "${HEAD}" ]; then
|
||||
echo "Head ${HEAD} unchanged since the last comment; skipping the agent."
|
||||
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
# List files changed between the recorded commit and the current head.
|
||||
# Tracked patterns mirror script/check_requirements/diff.py TRACKED_PATTERNS.
|
||||
CHANGED=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${PRIOR}...${HEAD}" \
|
||||
--jq '.files[].filename' 2>/dev/null) || {
|
||||
echo "Could not compare ${PRIOR}...${HEAD}; running the agent."
|
||||
exit 0
|
||||
}
|
||||
TRACKED=$(printf '%s\n' "${CHANGED}" \
|
||||
| grep -Ex 'requirements.*\.txt|homeassistant/package_constraints\.txt' || true)
|
||||
if [ -z "${TRACKED}" ]; then
|
||||
echo "No tracked requirement files changed since ${PRIOR}; skipping the agent."
|
||||
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "Tracked requirement files changed since ${PRIOR}; running the agent:"
|
||||
printf '%s\n' "${TRACKED}"
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
pre_activation:
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
@@ -1438,7 +1545,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1461,48 +1568,12 @@ jobs:
|
||||
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs');
|
||||
await main();
|
||||
|
||||
prepare:
|
||||
needs: activation
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
outputs:
|
||||
pr_number: ${{ steps.prepare.outputs.pr_number }}
|
||||
skip: ${{ steps.prepare.outputs.skip }}
|
||||
steps:
|
||||
- name: Configure GH_HOST for enterprise compatibility
|
||||
id: ghes-host-config
|
||||
shell: bash
|
||||
# zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||||
run: |
|
||||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||||
GH_HOST="${GITHUB_SERVER_URL#https://}"
|
||||
GH_HOST="${GH_HOST#http://}"
|
||||
echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
|
||||
- name: Download deterministic-results artifact
|
||||
id: download
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: check-requirements-deterministic
|
||||
path: /tmp/deterministic
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
- name: Resolve skip and PR number from the artifact
|
||||
id: prepare
|
||||
run: |
|
||||
echo "skip=$(jq -r '.skip_aw' /tmp/deterministic/results.json)" >> "${GITHUB_OUTPUT}"
|
||||
echo "pr_number=$(jq -r '.pr_number' /tmp/deterministic/results.json)" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
safe_outputs:
|
||||
needs:
|
||||
- activation
|
||||
- agent
|
||||
- detection
|
||||
- prepare
|
||||
- extract_pr_number
|
||||
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
@@ -1538,7 +1609,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1583,7 +1654,7 @@ jobs:
|
||||
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,files.pythonhosted.org,github.com,host.docker.internal,pip.pypa.io,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,telemetry.enterprise.githubcopilot.com"
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"${{ needs.prepare.outputs.pr_number }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"${{ needs.extract_pr_number.outputs.pr_number }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -15,41 +15,94 @@ tools:
|
||||
github:
|
||||
toolsets: [repos, pull_requests]
|
||||
min-integrity: unapproved
|
||||
if: needs.prepare.outputs.skip != 'true'
|
||||
safe-outputs:
|
||||
add-comment:
|
||||
max: 1
|
||||
target: "${{ needs.prepare.outputs.pr_number }}"
|
||||
target: "${{ needs.extract_pr_number.outputs.pr_number }}"
|
||||
needs:
|
||||
- prepare
|
||||
- extract_pr_number
|
||||
jobs:
|
||||
prepare:
|
||||
# The deterministic stage always uploads an artifact; its `skip_aw` flag is
|
||||
# true when no tracked requirement file changed since the last comment,
|
||||
# which is our cue to skip the (token-spending) agent. Recover the PR number
|
||||
# to comment on either way.
|
||||
gate:
|
||||
# Skip the (token-spending) agent when no tracked requirement file changed
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
skip: ${{ steps.prepare.outputs.skip }}
|
||||
pr_number: ${{ steps.prepare.outputs.pr_number }}
|
||||
skip: ${{ steps.gate.outputs.skip }}
|
||||
steps:
|
||||
- name: Download deterministic-results artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: check-requirements-deterministic
|
||||
path: /tmp/gate
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Decide whether requirements changed since the last comment
|
||||
id: gate
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR=$(jq -r '.pr_number' /tmp/gate/results.json)
|
||||
HEAD=$(jq -r '.head_sha // empty' /tmp/gate/results.json)
|
||||
if [ -z "${HEAD}" ]; then
|
||||
echo "Artifact has no head_sha; running the agent."
|
||||
exit 0
|
||||
fi
|
||||
# Recover the commit recorded in the most recent requirements-check
|
||||
# comment from the "Checked at commit" link
|
||||
PRIOR=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" \
|
||||
--jq '.[] | select(.body | contains("<!-- requirements-check -->")) | .body' \
|
||||
| grep -oiE '/commit/[0-9a-f]{40}' \
|
||||
| grep -oiE '[0-9a-f]{40}' | tail -1 || true)
|
||||
if [ -z "${PRIOR}" ]; then
|
||||
echo "No previous comment with a recorded commit; running the agent."
|
||||
exit 0
|
||||
fi
|
||||
if [ "${PRIOR}" = "${HEAD}" ]; then
|
||||
echo "Head ${HEAD} unchanged since the last comment; skipping the agent."
|
||||
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
# List files changed between the recorded commit and the current head.
|
||||
# Tracked patterns mirror script/check_requirements/diff.py TRACKED_PATTERNS.
|
||||
CHANGED=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${PRIOR}...${HEAD}" \
|
||||
--jq '.files[].filename' 2>/dev/null) || {
|
||||
echo "Could not compare ${PRIOR}...${HEAD}; running the agent."
|
||||
exit 0
|
||||
}
|
||||
TRACKED=$(printf '%s\n' "${CHANGED}" \
|
||||
| grep -Ex 'requirements.*\.txt|homeassistant/package_constraints\.txt' || true)
|
||||
if [ -z "${TRACKED}" ]; then
|
||||
echo "No tracked requirement files changed since ${PRIOR}; skipping the agent."
|
||||
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "Tracked requirement files changed since ${PRIOR}; running the agent:"
|
||||
printf '%s\n' "${TRACKED}"
|
||||
fi
|
||||
extract_pr_number:
|
||||
needs: gate
|
||||
if: needs.gate.outputs.skip != 'true' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
outputs:
|
||||
pr_number: ${{ steps.extract.outputs.pr_number }}
|
||||
steps:
|
||||
- name: Download deterministic-results artifact
|
||||
id: download
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: check-requirements-deterministic
|
||||
path: /tmp/deterministic
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Resolve skip and PR number from the artifact
|
||||
id: prepare
|
||||
- name: Extract PR number from artifact
|
||||
id: extract
|
||||
run: |
|
||||
echo "skip=$(jq -r '.skip_aw' /tmp/deterministic/results.json)" >> "${GITHUB_OUTPUT}"
|
||||
echo "pr_number=$(jq -r '.pr_number' /tmp/deterministic/results.json)" >> "${GITHUB_OUTPUT}"
|
||||
PR=$(jq -r '.pr_number' /tmp/deterministic/results.json)
|
||||
echo "pr_number=${PR}" >> "${GITHUB_OUTPUT}"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.workflow_run.id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
+17
-17
@@ -39,7 +39,7 @@ on:
|
||||
env:
|
||||
CACHE_VERSION: 3
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.8"
|
||||
HA_SHORT_VERSION: "2026.7"
|
||||
ADDITIONAL_PYTHON_VERSIONS: "[]"
|
||||
# 10.3 is the oldest supported version
|
||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||
@@ -352,7 +352,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -372,7 +372,7 @@ jobs:
|
||||
echo "full_key=${partial_key}${HASH_FILES}" >> $GITHUB_OUTPUT
|
||||
- name: Restore uv wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: ${{ steps.generate-uv-key.outputs.full_key }}
|
||||
@@ -442,13 +442,13 @@ jobs:
|
||||
uv cache prune --ci
|
||||
- name: Save uv wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: ${{ steps.generate-uv-key.outputs.full_key }}
|
||||
- name: Save base Python virtual environment
|
||||
if: always() && steps.create-venv.outcome == 'success'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -486,7 +486,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -523,7 +523,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -614,7 +614,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -665,7 +665,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -718,7 +718,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -775,7 +775,7 @@ jobs:
|
||||
echo "key=mypy-${MYPY_CACHE_VERSION}-${mypy_version}-${HA_SHORT_VERSION}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -783,7 +783,7 @@ jobs:
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore mypy cache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: >-
|
||||
@@ -846,7 +846,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -911,7 +911,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1053,7 +1053,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1209,7 +1209,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1377,7 +1377,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@9e17ab1ed5c4c79d8b61e29fa63de25ca2710716 # 2026.07.0
|
||||
uses: home-assistant/wheels@34957438948e0b3dcde73c77750643dadae594f5 # 2026.06.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@9e17ab1ed5c4c79d8b61e29fa63de25ca2710716 # 2026.07.0
|
||||
uses: home-assistant/wheels@34957438948e0b3dcde73c77750643dadae594f5 # 2026.06.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.18
|
||||
rev: v0.15.17
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args:
|
||||
|
||||
@@ -43,4 +43,3 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
|
||||
Generated
+3
-2
@@ -789,8 +789,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/html5/ @alexyao2015 @tr4nt0r
|
||||
/homeassistant/components/http/ @home-assistant/core
|
||||
/tests/components/http/ @home-assistant/core
|
||||
/homeassistant/components/huawei_lte/ @fphammerle
|
||||
/tests/components/huawei_lte/ @fphammerle
|
||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||
/tests/components/huawei_lte/ @scop @fphammerle
|
||||
/homeassistant/components/hue/ @marcelveldt
|
||||
/tests/components/hue/ @marcelveldt
|
||||
/homeassistant/components/hue_ble/ @flip-dots
|
||||
@@ -1986,6 +1986,7 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/waterfurnace/ @sdague @masterkoppa
|
||||
/homeassistant/components/watergate/ @adam-the-hero
|
||||
/tests/components/watergate/ @adam-the-hero
|
||||
/homeassistant/components/watson_tts/ @rutkai
|
||||
/homeassistant/components/watts/ @theobld-ww @devender-verma-ww @ssi-spyro
|
||||
/tests/components/watts/ @theobld-ww @devender-verma-ww @ssi-spyro
|
||||
/homeassistant/components/watttime/ @bachya
|
||||
|
||||
@@ -66,7 +66,6 @@ from .const import (
|
||||
BASE_PLATFORMS,
|
||||
FORMAT_DATETIME,
|
||||
KEY_DATA_LOGGING as DATA_LOGGING,
|
||||
KEY_DATA_LOGGING_DISABLED_REASON as DATA_LOGGING_DISABLED_REASON,
|
||||
SIGNAL_BOOTSTRAP_INTEGRATIONS,
|
||||
)
|
||||
from .core_config import async_process_ha_core_config
|
||||
@@ -130,11 +129,6 @@ SETUP_ORDER_SORT_KEY = partial(contains, BASE_PLATFORMS)
|
||||
|
||||
|
||||
ERROR_LOG_FILENAME = "home-assistant.log"
|
||||
ENV_DISABLE_LOG_FILE = "HA_DISABLE_LOG_FILE"
|
||||
ENV_DUPLICATE_LOG_FILE = "HA_DUPLICATE_LOG_FILE"
|
||||
ENV_SUPERVISOR = "SUPERVISOR"
|
||||
LOG_FILE_DISABLED_REASON_ENVIRONMENT = "environment"
|
||||
LOG_FILE_DISABLED_REASON_SUPERVISOR = "supervisor"
|
||||
|
||||
# hass.data key for logging information.
|
||||
DATA_REGISTRIES_LOADED: HassKey[None] = HassKey("bootstrap_registries_loaded")
|
||||
@@ -648,12 +642,10 @@ async def async_enable_logging(
|
||||
logger.setLevel(logging.INFO if verbose else logging.WARNING)
|
||||
|
||||
if log_file is None:
|
||||
disabled_log_file_reason = _log_file_disabled_reason()
|
||||
default_log_path = hass.config.path(ERROR_LOG_FILENAME)
|
||||
if disabled_log_file_reason:
|
||||
if "SUPERVISOR" in os.environ and "HA_DUPLICATE_LOG_FILE" not in os.environ:
|
||||
# Rename the default log file if it exists, since previous versions created
|
||||
# it before Supervisor disabled duplicate file logging or
|
||||
# HA_DISABLE_LOG_FILE disabled the log file.
|
||||
# it even on Supervisor
|
||||
def rename_old_file() -> None:
|
||||
"""Rename old log file in executor."""
|
||||
if os.path.isfile(default_log_path):
|
||||
@@ -665,7 +657,6 @@ async def async_enable_logging(
|
||||
else:
|
||||
err_log_path = default_log_path
|
||||
else:
|
||||
disabled_log_file_reason = None
|
||||
err_log_path = os.path.abspath(log_file)
|
||||
|
||||
if err_log_path:
|
||||
@@ -678,34 +669,10 @@ async def async_enable_logging(
|
||||
|
||||
# Save the log file location for access by other components.
|
||||
hass.data[DATA_LOGGING] = err_log_path
|
||||
elif disabled_log_file_reason == LOG_FILE_DISABLED_REASON_ENVIRONMENT:
|
||||
hass.data[DATA_LOGGING_DISABLED_REASON] = disabled_log_file_reason
|
||||
|
||||
async_activate_log_queue_handler(hass)
|
||||
|
||||
|
||||
def _log_file_disabled_reason() -> str | None:
|
||||
"""Return why the log file is disabled."""
|
||||
if ENV_SUPERVISOR in os.environ and ENV_DUPLICATE_LOG_FILE not in os.environ:
|
||||
return LOG_FILE_DISABLED_REASON_SUPERVISOR
|
||||
|
||||
disable_log_file = os.environ.get(ENV_DISABLE_LOG_FILE)
|
||||
if disable_log_file is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
if cv.boolean(disable_log_file):
|
||||
return LOG_FILE_DISABLED_REASON_ENVIRONMENT
|
||||
except vol.Invalid:
|
||||
_LOGGER.warning(
|
||||
"Ignoring invalid %s value: %s. Expected a boolean value: "
|
||||
"1/0, true/false, yes/no, on/off, or enable/disable",
|
||||
ENV_DISABLE_LOG_FILE,
|
||||
disable_log_file,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _create_log_file(
|
||||
err_log_path: str, log_rotate_days: int | None
|
||||
) -> RotatingFileHandler | TimedRotatingFileHandler:
|
||||
@@ -767,7 +734,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
||||
domains.update(DEFAULT_INTEGRATIONS_RECOVERY_MODE)
|
||||
|
||||
# Add domains depending on if the Supervisor is used or not
|
||||
if ENV_SUPERVISOR in os.environ:
|
||||
if "SUPERVISOR" in os.environ:
|
||||
domains.update(DEFAULT_INTEGRATIONS_SUPERVISOR)
|
||||
|
||||
return domains
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"azure_event_hub",
|
||||
"azure_service_bus",
|
||||
"azure_storage",
|
||||
"microsoft_face_detect",
|
||||
"microsoft_face_identify",
|
||||
"microsoft_face",
|
||||
"microsoft",
|
||||
"onedrive",
|
||||
"onedrive_for_business",
|
||||
|
||||
@@ -108,7 +108,7 @@ class AdGuardHomeSensor(AdGuardHomeEntity, SensorEntity):
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(data, entry)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = "_".join( # pylint: disable=home-assistant-entity-unique-id-redundant-domain,home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = "_".join( # pylint: disable=home-assistant-entity-unique-id-redundant-domain
|
||||
[
|
||||
DOMAIN,
|
||||
self.adguard.host,
|
||||
|
||||
@@ -103,7 +103,7 @@ class AdGuardHomeSwitch(AdGuardHomeEntity, SwitchEntity):
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(data, entry)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = "_".join( # pylint: disable=home-assistant-entity-unique-id-redundant-domain,home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = "_".join( # pylint: disable=home-assistant-entity-unique-id-redundant-domain
|
||||
[
|
||||
DOMAIN,
|
||||
self.adguard.host,
|
||||
|
||||
@@ -46,7 +46,7 @@ class AdGuardHomeUpdate(AdGuardHomeEntity, UpdateEntity):
|
||||
"""Initialize AdGuard Home update."""
|
||||
super().__init__(data, entry)
|
||||
|
||||
self._attr_unique_id = "_".join( # pylint: disable=home-assistant-entity-unique-id-redundant-domain,home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = "_".join( # pylint: disable=home-assistant-entity-unique-id-redundant-domain
|
||||
[DOMAIN, self.adguard.host, str(self.adguard.port), "update"]
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from typing import Final, final, override
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfDensity
|
||||
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -152,4 +152,4 @@ class AirQualityEntity(Entity):
|
||||
@override
|
||||
def unit_of_measurement(self) -> str:
|
||||
"""Return the unit of measurement of this entity."""
|
||||
return UnitOfDensity.MICROGRAMS_PER_CUBIC_METER
|
||||
return CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
|
||||
@@ -5,7 +5,13 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, UnitOfDensity, UnitOfRatio
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.condition import (
|
||||
@@ -54,12 +60,12 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
# Numerical sensor conditions with unit conversion
|
||||
"is_co_value": make_entity_numerical_condition_with_unit(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.CO)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CarbonMonoxideConcentrationConverter,
|
||||
),
|
||||
"is_ozone_value": make_entity_numerical_condition_with_unit(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.OZONE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
OzoneConcentrationConverter,
|
||||
),
|
||||
"is_voc_value": make_entity_numerical_condition_with_unit(
|
||||
@@ -68,7 +74,7 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS
|
||||
)
|
||||
},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
MassVolumeConcentrationConverter,
|
||||
),
|
||||
"is_voc_ratio_value": make_entity_numerical_condition_with_unit(
|
||||
@@ -77,48 +83,48 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS
|
||||
)
|
||||
},
|
||||
UnitOfRatio.PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
UnitlessRatioConverter,
|
||||
),
|
||||
"is_no_value": make_entity_numerical_condition_with_unit(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROGEN_MONOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
NitrogenMonoxideConcentrationConverter,
|
||||
),
|
||||
"is_no2_value": make_entity_numerical_condition_with_unit(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROGEN_DIOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
NitrogenDioxideConcentrationConverter,
|
||||
),
|
||||
"is_so2_value": make_entity_numerical_condition_with_unit(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.SULPHUR_DIOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
),
|
||||
# Numerical sensor conditions without unit conversion (single-unit device classes)
|
||||
"is_co2_value": make_entity_numerical_condition(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.CO2)},
|
||||
valid_unit=UnitOfRatio.PARTS_PER_MILLION,
|
||||
valid_unit=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
"is_pm1_value": make_entity_numerical_condition(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM1)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"is_pm25_value": make_entity_numerical_condition(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM25)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"is_pm4_value": make_entity_numerical_condition(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM4)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"is_pm10_value": make_entity_numerical_condition(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM10)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"is_n2o_value": make_entity_numerical_condition(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROUS_OXIDE)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, UnitOfDensity, UnitOfRatio
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import (
|
||||
@@ -59,25 +65,25 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
# Numerical sensor triggers with unit conversion
|
||||
"co_changed": make_entity_numerical_state_changed_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.CO)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CarbonMonoxideConcentrationConverter,
|
||||
),
|
||||
"co_crossed_threshold": (
|
||||
make_entity_numerical_state_crossed_threshold_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.CO)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CarbonMonoxideConcentrationConverter,
|
||||
)
|
||||
),
|
||||
"ozone_changed": make_entity_numerical_state_changed_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.OZONE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
OzoneConcentrationConverter,
|
||||
),
|
||||
"ozone_crossed_threshold": (
|
||||
make_entity_numerical_state_crossed_threshold_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.OZONE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
OzoneConcentrationConverter,
|
||||
)
|
||||
),
|
||||
@@ -87,7 +93,7 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS
|
||||
)
|
||||
},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
MassVolumeConcentrationConverter,
|
||||
),
|
||||
"voc_crossed_threshold": (
|
||||
@@ -97,7 +103,7 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS
|
||||
)
|
||||
},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
MassVolumeConcentrationConverter,
|
||||
)
|
||||
),
|
||||
@@ -107,7 +113,7 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS
|
||||
)
|
||||
},
|
||||
UnitOfRatio.PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
UnitlessRatioConverter,
|
||||
),
|
||||
"voc_ratio_crossed_threshold": (
|
||||
@@ -117,13 +123,13 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS
|
||||
)
|
||||
},
|
||||
UnitOfRatio.PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
UnitlessRatioConverter,
|
||||
)
|
||||
),
|
||||
"no_changed": make_entity_numerical_state_changed_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROGEN_MONOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
NitrogenMonoxideConcentrationConverter,
|
||||
),
|
||||
"no_crossed_threshold": (
|
||||
@@ -133,13 +139,13 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
device_class=SensorDeviceClass.NITROGEN_MONOXIDE
|
||||
)
|
||||
},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
NitrogenMonoxideConcentrationConverter,
|
||||
)
|
||||
),
|
||||
"no2_changed": make_entity_numerical_state_changed_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROGEN_DIOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
NitrogenDioxideConcentrationConverter,
|
||||
),
|
||||
"no2_crossed_threshold": (
|
||||
@@ -149,70 +155,70 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE
|
||||
)
|
||||
},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
NitrogenDioxideConcentrationConverter,
|
||||
)
|
||||
),
|
||||
"so2_changed": make_entity_numerical_state_changed_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.SULPHUR_DIOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
),
|
||||
"so2_crossed_threshold": (
|
||||
make_entity_numerical_state_crossed_threshold_with_unit_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.SULPHUR_DIOXIDE)},
|
||||
UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
)
|
||||
),
|
||||
# Numerical sensor triggers without unit conversion (single-unit device classes)
|
||||
"co2_changed": make_entity_numerical_state_changed_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.CO2)},
|
||||
valid_unit=UnitOfRatio.PARTS_PER_MILLION,
|
||||
valid_unit=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
"co2_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.CO2)},
|
||||
valid_unit=UnitOfRatio.PARTS_PER_MILLION,
|
||||
valid_unit=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
"pm1_changed": make_entity_numerical_state_changed_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM1)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm1_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM1)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm25_changed": make_entity_numerical_state_changed_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM25)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm25_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM25)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm4_changed": make_entity_numerical_state_changed_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM4)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm4_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM4)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm10_changed": make_entity_numerical_state_changed_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM10)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"pm10_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.PM10)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"n2o_changed": make_entity_numerical_state_changed_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROUS_OXIDE)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
"n2o_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
{SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.NITROUS_OXIDE)},
|
||||
valid_unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
valid_unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory, UnitOfRatio
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -40,7 +40,7 @@ DISPLAY_BRIGHTNESS = AirGradientNumberEntityDescription(
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
native_step=1,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda config: config.display_brightness,
|
||||
set_value_fn=lambda client, value: client.set_display_brightness(value),
|
||||
)
|
||||
@@ -52,7 +52,7 @@ LED_BAR_BRIGHTNESS = AirGradientNumberEntityDescription(
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
native_step=1,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda config: config.led_bar_brightness,
|
||||
set_value_fn=lambda client, value: client.set_led_bar_brightness(value),
|
||||
)
|
||||
|
||||
@@ -19,10 +19,11 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
@@ -56,21 +57,21 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
|
||||
AirGradientMeasurementSensorEntityDescription(
|
||||
key="pm01",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.pm01,
|
||||
),
|
||||
AirGradientMeasurementSensorEntityDescription(
|
||||
key="pm02",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.pm02,
|
||||
),
|
||||
AirGradientMeasurementSensorEntityDescription(
|
||||
key="pm10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.pm10,
|
||||
),
|
||||
@@ -84,7 +85,7 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
|
||||
AirGradientMeasurementSensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.relative_humidity,
|
||||
),
|
||||
@@ -112,7 +113,7 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
|
||||
AirGradientMeasurementSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.rco2,
|
||||
),
|
||||
@@ -143,7 +144,7 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
|
||||
key="pm02_raw",
|
||||
translation_key="raw_pm02",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda status: status.raw_pm02,
|
||||
@@ -189,7 +190,7 @@ CONFIG_LED_BAR_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...
|
||||
AirGradientConfigSensorEntityDescription(
|
||||
key="led_bar_brightness",
|
||||
translation_key="led_bar_brightness",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda config: config.led_bar_brightness,
|
||||
),
|
||||
@@ -215,9 +216,9 @@ CONFIG_DISPLAY_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...
|
||||
AirGradientConfigSensorEntityDescription(
|
||||
key="display_brightness",
|
||||
translation_key="display_brightness",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda config: config.led_bar_brightness,
|
||||
value_fn=lambda config: config.display_brightness,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class AirGradientUpdate(AirGradientEntity, UpdateEntity):
|
||||
def __init__(self, coordinator: AirGradientCoordinator) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = f"{coordinator.serial_number}-update" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{coordinator.serial_number}-update"
|
||||
|
||||
@cached_property
|
||||
@override
|
||||
|
||||
@@ -11,10 +11,10 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONF_NAME,
|
||||
UnitOfDensity,
|
||||
PERCENTAGE,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -76,14 +76,14 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM1,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM25,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
attrs=lambda data: {
|
||||
@@ -94,7 +94,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
attrs=lambda data: {
|
||||
@@ -105,7 +105,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_HUMIDITY,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
@@ -126,7 +126,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_CO,
|
||||
device_class=SensorDeviceClass.CO,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
attrs=lambda data: {
|
||||
@@ -137,7 +137,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_NO2,
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
attrs=lambda data: {
|
||||
@@ -148,7 +148,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_SO2,
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
attrs=lambda data: {
|
||||
@@ -159,7 +159,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_O3,
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
attrs=lambda data: {
|
||||
|
||||
@@ -12,7 +12,11 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import ATTR_TIME, UnitOfDensity, UnitOfRatio
|
||||
from homeassistant.const import (
|
||||
ATTR_TIME,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -91,7 +95,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
translation_key="pm10",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
value_fn=lambda data: data.get(ATTR_API_PM10),
|
||||
@@ -100,7 +104,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_PM25,
|
||||
translation_key="pm25",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
value_fn=lambda data: data.get(ATTR_API_PM25),
|
||||
@@ -109,7 +113,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_O3,
|
||||
translation_key="o3",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data.get(ATTR_API_O3),
|
||||
extra_state_attributes_fn=None,
|
||||
|
||||
@@ -14,8 +14,9 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
@@ -58,7 +59,7 @@ SENSOR_TYPES: tuple[AirobotSensorEntityDescription, ...] = (
|
||||
AirobotSensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.hum_air,
|
||||
),
|
||||
@@ -74,7 +75,7 @@ SENSOR_TYPES: tuple[AirobotSensorEntityDescription, ...] = (
|
||||
AirobotSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.co2,
|
||||
supported_fn=lambda status: status.has_co2_sensor,
|
||||
|
||||
@@ -56,7 +56,7 @@ class AirOSUpdateEntity(AirOSEntity, UpdateEntity):
|
||||
self.status = status
|
||||
self.firmware = firmware
|
||||
|
||||
self._attr_unique_id = f"{status.data.derived.mac}_firmware_update" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{status.data.derived.mac}_firmware_update"
|
||||
|
||||
@property
|
||||
@override
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import override
|
||||
from aioairq.core import AirQ
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.const import UnitOfRatio
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -32,7 +32,7 @@ AIRQ_LED_BRIGHTNESS = AirQBrightnessDescription(
|
||||
native_min_value=0.0,
|
||||
native_max_value=100.0,
|
||||
native_step=1.0,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value=lambda data: data["brightness"],
|
||||
set_value=lambda device, value: device.set_current_brightness(value),
|
||||
)
|
||||
|
||||
@@ -12,9 +12,13 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
UnitOfDensity,
|
||||
CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -40,70 +44,70 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="c2h4o",
|
||||
translation_key="acetaldehyde",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c2h4o"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="nh3_MR100",
|
||||
translation_key="ammonia",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("nh3_MR100"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ash3",
|
||||
translation_key="arsine",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ash3"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="br2",
|
||||
translation_key="bromine",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("br2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch4s",
|
||||
translation_key="methanethiol",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch4s"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="cl2_M20",
|
||||
translation_key="chlorine",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("cl2_M20"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="clo2",
|
||||
translation_key="chlorine_dioxide",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("clo2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="co",
|
||||
translation_key="carbon_monoxide",
|
||||
native_unit_of_measurement=UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("co"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("co2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="cs2",
|
||||
translation_key="carbon_disulfide",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("cs2"),
|
||||
),
|
||||
@@ -118,182 +122,182 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="ethanol",
|
||||
translation_key="ethanol",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ethanol"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="c2h4",
|
||||
translation_key="ethylene",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c2h4"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch2o_M10",
|
||||
translation_key="formaldehyde",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch2o_M10"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="f2",
|
||||
translation_key="fluorine",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("f2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="h2s",
|
||||
translation_key="hydrogen_sulfide",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("h2s"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="hcl",
|
||||
translation_key="hydrochloric_acid",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("hcl"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="hcn",
|
||||
translation_key="hydrogen_cyanide",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("hcn"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="hf",
|
||||
translation_key="hydrogen_fluoride",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("hf"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="health",
|
||||
translation_key="health_index",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("health", 0.0) / 10.0,
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("humidity"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="humidity_abs",
|
||||
device_class=SensorDeviceClass.ABSOLUTE_HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfDensity.GRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("humidity_abs"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="h2_M1000",
|
||||
translation_key="hydrogen",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("h2_M1000"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="h2o2",
|
||||
translation_key="hydrogen_peroxide",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("h2o2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch4_MIPEX",
|
||||
translation_key="methane",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch4_MIPEX"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="mold",
|
||||
translation_key="mold",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("mold"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="n2o",
|
||||
device_class=SensorDeviceClass.NITROUS_OXIDE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("n2o"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="no_M250",
|
||||
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("no_M250"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="no2",
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("no2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="acid_M100",
|
||||
translation_key="organic_acid",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("acid_M100"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="oxygen",
|
||||
translation_key="oxygen",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("oxygen"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="o3",
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("o3"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="performance",
|
||||
translation_key="performance_index",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("performance", 0.0) / 10.0,
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ph3",
|
||||
translation_key="hydrogen_phosphide",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ph3"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pm1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("pm1"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pm2_5",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("pm2_5"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pm10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("pm10"),
|
||||
),
|
||||
@@ -315,42 +319,42 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="c3h8_MIPEX",
|
||||
translation_key="propane",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c3h8_MIPEX"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="r32",
|
||||
translation_key="r32",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("r32"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="r454b",
|
||||
translation_key="r454b",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("r454b"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="r454c",
|
||||
translation_key="r454c",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("r454c"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="sih4",
|
||||
translation_key="silicon_hydride",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("sih4"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="so2",
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("so2"),
|
||||
),
|
||||
@@ -387,7 +391,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="tvoc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("tvoc"),
|
||||
),
|
||||
@@ -395,14 +399,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
key="tvoc_ionsc",
|
||||
translation_key="industrial_volatile_organic_compounds",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("tvoc_ionsc"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="virus",
|
||||
translation_key="virus_index",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("virus", 0.0),
|
||||
),
|
||||
|
||||
@@ -11,12 +11,14 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -47,7 +49,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
"humidity": SensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
@@ -68,7 +70,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
"battery": SensorEntityDescription(
|
||||
key="battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -76,20 +78,20 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
"co2": SensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"voc": SensorEntityDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"light": SensorEntityDescription(
|
||||
key="light",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
translation_key="light",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -124,14 +126,14 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
),
|
||||
"pm1": SensorEntityDescription(
|
||||
key="pm1",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"pm25": SensorEntityDescription(
|
||||
key="pm25",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Support for airthings ble sensors."""
|
||||
|
||||
from collections.abc import Callable
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
@@ -15,11 +13,13 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
Platform,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -46,108 +46,87 @@ CONNECTIVITY_MODE_MAP = {
|
||||
AirthingsConnectivityMode.NOT_CONFIGURED.value: "not_configured",
|
||||
}
|
||||
|
||||
|
||||
def get_connectivity_mode(value: str | float | None) -> str | None:
|
||||
"""Get connectivity mode."""
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
return CONNECTIVITY_MODE_MAP.get(value)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AirthingsBLESensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Airthings BLE sensor entity."""
|
||||
|
||||
value_fn: Callable[[str | float | None], str | float | None] = lambda x: x
|
||||
|
||||
|
||||
SENSORS_MAPPING_TEMPLATE: dict[str, AirthingsBLESensorEntityDescription] = {
|
||||
"radon_1day_avg": AirthingsBLESensorEntityDescription(
|
||||
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
"radon_1day_avg": SensorEntityDescription(
|
||||
key="radon_1day_avg",
|
||||
translation_key="radon_1day_avg",
|
||||
native_unit_of_measurement=VOLUME_BECQUEREL,
|
||||
suggested_display_precision=0,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"radon_longterm_avg": AirthingsBLESensorEntityDescription(
|
||||
"radon_longterm_avg": SensorEntityDescription(
|
||||
key="radon_longterm_avg",
|
||||
translation_key="radon_longterm_avg",
|
||||
native_unit_of_measurement=VOLUME_BECQUEREL,
|
||||
suggested_display_precision=0,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"radon_1day_level": AirthingsBLESensorEntityDescription(
|
||||
"radon_1day_level": SensorEntityDescription(
|
||||
key="radon_1day_level",
|
||||
translation_key="radon_1day_level",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["good", "fair", "poor"],
|
||||
value_fn=lambda value: value if value != "unknown" else None,
|
||||
),
|
||||
"radon_longterm_level": AirthingsBLESensorEntityDescription(
|
||||
"radon_longterm_level": SensorEntityDescription(
|
||||
key="radon_longterm_level",
|
||||
translation_key="radon_longterm_level",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["good", "fair", "poor"],
|
||||
value_fn=lambda value: value if value != "unknown" else None,
|
||||
),
|
||||
"temperature": AirthingsBLESensorEntityDescription(
|
||||
"temperature": SensorEntityDescription(
|
||||
key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
"humidity": AirthingsBLESensorEntityDescription(
|
||||
"humidity": SensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
"pressure": AirthingsBLESensorEntityDescription(
|
||||
"pressure": SensorEntityDescription(
|
||||
key="pressure",
|
||||
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
"battery": AirthingsBLESensorEntityDescription(
|
||||
"battery": SensorEntityDescription(
|
||||
key="battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"co2": AirthingsBLESensorEntityDescription(
|
||||
"co2": SensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"voc": AirthingsBLESensorEntityDescription(
|
||||
"voc": SensorEntityDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"illuminance": AirthingsBLESensorEntityDescription(
|
||||
"illuminance": SensorEntityDescription(
|
||||
key="illuminance",
|
||||
translation_key="illuminance",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"lux": AirthingsBLESensorEntityDescription(
|
||||
"lux": SensorEntityDescription(
|
||||
key="lux",
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"noise": AirthingsBLESensorEntityDescription(
|
||||
"noise": SensorEntityDescription(
|
||||
key="noise",
|
||||
translation_key="ambient_noise",
|
||||
device_class=SensorDeviceClass.SOUND_PRESSURE,
|
||||
@@ -155,14 +134,13 @@ SENSORS_MAPPING_TEMPLATE: dict[str, AirthingsBLESensorEntityDescription] = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"connectivity_mode": AirthingsBLESensorEntityDescription(
|
||||
"connectivity_mode": SensorEntityDescription(
|
||||
key="connectivity_mode",
|
||||
translation_key="connectivity_mode",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=list(CONNECTIVITY_MODE_MAP.values()),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=get_connectivity_mode,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -250,13 +228,12 @@ class AirthingsSensor(
|
||||
"""Airthings BLE sensors for the device."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: AirthingsBLESensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirthingsBLEDataUpdateCoordinator,
|
||||
airthings_device: AirthingsDevice,
|
||||
entity_description: AirthingsBLESensorEntityDescription,
|
||||
entity_description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Populate the airthings entity with relevant data."""
|
||||
super().__init__(coordinator)
|
||||
@@ -295,4 +272,11 @@ class AirthingsSensor(
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the value reported by the sensor."""
|
||||
value = self.coordinator.data.sensors[self.entity_description.key]
|
||||
return self.entity_description.value_fn(value)
|
||||
|
||||
# Map connectivity mode to enum values
|
||||
if self.entity_description.key == "connectivity_mode":
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
return CONNECTIVITY_MODE_MAP.get(value)
|
||||
|
||||
return value
|
||||
|
||||
@@ -45,23 +45,13 @@
|
||||
"name": "Radon 1-day average"
|
||||
},
|
||||
"radon_1day_level": {
|
||||
"name": "Radon 1-day level",
|
||||
"state": {
|
||||
"fair": "Fair",
|
||||
"good": "Good",
|
||||
"poor": "Poor"
|
||||
}
|
||||
"name": "Radon 1-day level"
|
||||
},
|
||||
"radon_longterm_avg": {
|
||||
"name": "Radon longterm average"
|
||||
},
|
||||
"radon_longterm_level": {
|
||||
"name": "Radon longterm level",
|
||||
"state": {
|
||||
"fair": "[%key:component::airthings_ble::entity::sensor::radon_1day_level::state::fair%]",
|
||||
"good": "[%key:component::airthings_ble::entity::sensor::radon_1day_level::state::good%]",
|
||||
"poor": "[%key:component::airthings_ble::entity::sensor::radon_1day_level::state::poor%]"
|
||||
}
|
||||
"name": "Radon longterm level"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,13 +12,14 @@ from homeassistant.const import (
|
||||
ATTR_LATITUDE,
|
||||
ATTR_LONGITUDE,
|
||||
ATTR_STATE,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_COUNTRY,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_SHOW_ON_MAP,
|
||||
CONF_STATE,
|
||||
UnitOfDensity,
|
||||
UnitOfRatio,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -93,12 +94,12 @@ POLLUTANT_LEVELS = {
|
||||
}
|
||||
|
||||
POLLUTANT_UNITS = {
|
||||
"co": UnitOfRatio.PARTS_PER_MILLION,
|
||||
"n2": UnitOfRatio.PARTS_PER_BILLION,
|
||||
"o3": UnitOfRatio.PARTS_PER_BILLION,
|
||||
"p1": UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
"p2": UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
"s2": UnitOfRatio.PARTS_PER_BILLION,
|
||||
"co": CONCENTRATION_PARTS_PER_MILLION,
|
||||
"n2": CONCENTRATION_PARTS_PER_BILLION,
|
||||
"o3": CONCENTRATION_PARTS_PER_BILLION,
|
||||
"p1": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
"p2": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
"s2": CONCENTRATION_PARTS_PER_BILLION,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -56,21 +57,21 @@ SENSOR_DESCRIPTIONS = (
|
||||
key="battery_level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: status["battery"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["co2"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements[
|
||||
"humidity"
|
||||
@@ -79,21 +80,21 @@ SENSOR_DESCRIPTIONS = (
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_0_1",
|
||||
translation_key="pm01",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["pm0_1"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_1_0",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["pm1_0"],
|
||||
),
|
||||
AirVisualProMeasurementDescription(
|
||||
key="particulate_matter_2_5",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["pm2_5"],
|
||||
),
|
||||
@@ -109,7 +110,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
AirVisualProMeasurementDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda settings, status, measurements, history: measurements["voc"],
|
||||
),
|
||||
|
||||
@@ -35,13 +35,13 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfInformation,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -141,7 +141,7 @@ WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
key=AZD_CPU_USAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="cpu_usage",
|
||||
),
|
||||
@@ -172,19 +172,19 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
key=AZD_AQ_PM_1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
key=AZD_AQ_PM_2P5,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
key=AZD_AQ_PM_10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@@ -196,20 +196,20 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
key=AZD_HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
key=AZD_THERMOSTAT_BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
key=AZD_THERMOSTAT_COVERAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="thermostat_coverage",
|
||||
),
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
@@ -118,7 +118,7 @@ async def async_setup_entry(
|
||||
for serial_num in coordinator.data:
|
||||
unique_id = f"{serial_num}-{sensor_desc.key}"
|
||||
if entity_id := entity_registry.async_get_entity_id(
|
||||
Platform.BINARY_SENSOR, DOMAIN, unique_id
|
||||
BINARY_SENSOR_DOMAIN, DOMAIN, unique_id
|
||||
):
|
||||
_LOGGER.debug("Removing deprecated entity %s", entity_id)
|
||||
entity_registry.async_remove(entity_id)
|
||||
|
||||
@@ -18,7 +18,13 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import LIGHT_LUX, UnitOfDensity, UnitOfRatio, UnitOfTemperature
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
@@ -91,25 +97,25 @@ SENSORS: Final = (
|
||||
AmazonSensorEntityDescription(
|
||||
key="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AmazonSensorEntityDescription(
|
||||
key="PM10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AmazonSensorEntityDescription(
|
||||
key="PM25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AmazonSensorEntityDescription(
|
||||
key="CO",
|
||||
device_class=SensorDeviceClass.CO,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AmazonSensorEntityDescription(
|
||||
|
||||
@@ -7,7 +7,7 @@ from aioamazondevices.const.schedules import (
|
||||
NOTIFICATION_TIMER,
|
||||
)
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
|
||||
@@ -70,7 +70,7 @@ async def async_remove_unsupported_notification_sensors(
|
||||
):
|
||||
unique_id = f"{serial_num}-{notification_key}"
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
Platform.SENSOR, DOMAIN, unique_id=unique_id
|
||||
SENSOR_DOMAIN, DOMAIN, unique_id=unique_id
|
||||
)
|
||||
is_unsupported = not coordinator.data[serial_num].notifications_supported
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -44,7 +45,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
key="BME280_humidity",
|
||||
translation_key="humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=2,
|
||||
translation_placeholders={"sensor_name": "BME280"},
|
||||
),
|
||||
@@ -69,7 +70,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
key="BME680_humidity",
|
||||
translation_key="humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=2,
|
||||
translation_placeholders={"sensor_name": "BME680"},
|
||||
),
|
||||
@@ -128,7 +129,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
key="HTU21D_humidity",
|
||||
translation_key="humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=2,
|
||||
translation_placeholders={"sensor_name": "HTU21D"},
|
||||
),
|
||||
@@ -144,21 +145,21 @@ SENSOR_DESCRIPTIONS = [
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
translation_key="pm_10",
|
||||
key="SDS_P1",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
AltruistSensorEntityDescription(
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
translation_key="pm_25",
|
||||
key="SDS_P2",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
AltruistSensorEntityDescription(
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
key="SHT3X_humidity",
|
||||
translation_key="humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=2,
|
||||
translation_placeholders={"sensor_name": "SHT3X"},
|
||||
),
|
||||
@@ -193,7 +194,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
),
|
||||
AltruistSensorEntityDescription(
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
translation_key="co2",
|
||||
key="CCS_CO2",
|
||||
suggested_display_precision=2,
|
||||
@@ -202,7 +203,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
AltruistSensorEntityDescription(
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
key="CCS_TVOC",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
AltruistSensorEntityDescription(
|
||||
@@ -214,7 +215,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
AltruistSensorEntityDescription(
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
translation_key="co2",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
key="SCD4x_co2",
|
||||
suggested_display_precision=2,
|
||||
translation_placeholders={"sensor_name": "SCD4x"},
|
||||
|
||||
@@ -10,14 +10,15 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_MAC,
|
||||
DEGREE,
|
||||
UnitOfDensity,
|
||||
PERCENTAGE,
|
||||
UnitOfIrradiance,
|
||||
UnitOfLength,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolumetricFlux,
|
||||
@@ -93,7 +94,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
@@ -133,7 +134,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
@@ -187,14 +188,14 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_24H,
|
||||
translation_key="pm25_24h_average",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
suggested_display_precision=1,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
|
||||
@@ -11,14 +11,15 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_NAME,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
UnitOfDensity,
|
||||
PERCENTAGE,
|
||||
UnitOfIrradiance,
|
||||
UnitOfLength,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolumetricFlux,
|
||||
@@ -156,13 +157,13 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN_AQIN,
|
||||
translation_key="pm25_indoor_aqin",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN_24H_AQIN,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
translation_key="pm25_indoor_24h_aqin",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -170,28 +171,28 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM10_IN_AQIN,
|
||||
translation_key="pm10_indoor_aqin",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM10_IN_24H_AQIN,
|
||||
translation_key="pm10_indoor_24h_aqin",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_CO2_IN_AQIN,
|
||||
translation_key="co2_indoor_aqin",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_CO2_IN_24H_AQIN,
|
||||
translation_key="co2_indoor_24h_aqin",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -205,7 +206,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM_IN_HUMIDITY_AQIN,
|
||||
translation_key="pm_indoor_humidity_aqin",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -249,7 +250,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -290,83 +291,83 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY10,
|
||||
translation_key="humidity_10",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY1,
|
||||
translation_key="humidity_1",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY2,
|
||||
translation_key="humidity_2",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY3,
|
||||
translation_key="humidity_3",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY4,
|
||||
translation_key="humidity_4",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY5,
|
||||
translation_key="humidity_5",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY6,
|
||||
translation_key="humidity_6",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY7,
|
||||
translation_key="humidity_7",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY8,
|
||||
translation_key="humidity_8",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY9,
|
||||
translation_key="humidity_9",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITYIN,
|
||||
translation_key="humidity_indoor",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -416,95 +417,95 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_24H,
|
||||
translation_key="pm25_24h_average",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN,
|
||||
translation_key="pm25_indoor",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN_24H,
|
||||
translation_key="pm25_indoor_24h_average",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM10,
|
||||
translation_key="soil_humidity_10",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM1,
|
||||
translation_key="soil_humidity_1",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM2,
|
||||
translation_key="soil_humidity_2",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM3,
|
||||
translation_key="soil_humidity_3",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM4,
|
||||
translation_key="soil_humidity_4",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM5,
|
||||
translation_key="soil_humidity_5",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM6,
|
||||
translation_key="soil_humidity_6",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM7,
|
||||
translation_key="soil_humidity_7",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM8,
|
||||
translation_key="soil_humidity_8",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM9,
|
||||
translation_key="soil_humidity_9",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
|
||||
@@ -43,7 +43,7 @@ class IPWebcamCamera(MjpegCamera):
|
||||
username=coordinator.config_entry.data.get(CONF_USERNAME),
|
||||
password=coordinator.config_entry.data.get(CONF_PASSWORD, ""),
|
||||
)
|
||||
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-camera" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-camera"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
|
||||
name=coordinator.config_entry.data[CONF_HOST],
|
||||
|
||||
@@ -28,6 +28,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, llm
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.selector import (
|
||||
NumberSelector,
|
||||
NumberSelectorConfig,
|
||||
@@ -65,7 +66,7 @@ from .const import (
|
||||
TOOL_SEARCH_UNSUPPORTED_MODELS,
|
||||
PromptCaching,
|
||||
)
|
||||
from .coordinator import async_create_client, model_alias
|
||||
from .coordinator import model_alias
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import AnthropicConfigEntry
|
||||
@@ -94,7 +95,9 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
||||
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
client = await async_create_client(hass, data[CONF_API_KEY])
|
||||
client = anthropic.AsyncAnthropic(
|
||||
api_key=data[CONF_API_KEY], http_client=get_async_client(hass)
|
||||
)
|
||||
await client.models.list(timeout=10.0)
|
||||
|
||||
|
||||
@@ -295,7 +298,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
):
|
||||
self.options.pop(CONF_LLM_HASS_API)
|
||||
if not errors:
|
||||
return await self.async_step_additional()
|
||||
return await self.async_step_advanced()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
@@ -305,10 +308,10 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
errors=errors or None,
|
||||
)
|
||||
|
||||
async def async_step_additional(
|
||||
async def async_step_advanced(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage additional options."""
|
||||
"""Manage advanced options."""
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
|
||||
@@ -357,7 +360,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
return await self.async_step_model()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="additional",
|
||||
step_id="advanced",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(step_schema), self.options
|
||||
),
|
||||
@@ -546,8 +549,9 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
location_data: dict[str, str] = {}
|
||||
zone_home = self.hass.states.get(ENTITY_ID_HOME)
|
||||
if zone_home is not None:
|
||||
client = await async_create_client(
|
||||
self.hass, self._get_entry().data[CONF_API_KEY]
|
||||
client = anthropic.AsyncAnthropic(
|
||||
api_key=self._get_entry().data[CONF_API_KEY],
|
||||
http_client=get_async_client(self.hass),
|
||||
)
|
||||
location_schema = vol.Schema(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Coordinator for the Anthropic integration."""
|
||||
|
||||
import datetime
|
||||
from functools import partial
|
||||
from typing import override
|
||||
|
||||
import anthropic
|
||||
@@ -21,19 +20,6 @@ UPDATE_INTERVAL_DISCONNECTED = datetime.timedelta(minutes=1)
|
||||
type AnthropicConfigEntry = ConfigEntry[AnthropicCoordinator]
|
||||
|
||||
|
||||
async def async_create_client(
|
||||
hass: HomeAssistant, api_key: str
|
||||
) -> anthropic.AsyncAnthropic:
|
||||
"""Create an Anthropic client."""
|
||||
return await hass.async_add_executor_job(
|
||||
partial(
|
||||
anthropic.AsyncAnthropic,
|
||||
api_key=api_key,
|
||||
http_client=get_async_client(hass),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def model_alias(model_id: str) -> str:
|
||||
"""Resolve alias from versioned model name."""
|
||||
@@ -47,8 +33,7 @@ def model_alias(model_id: str) -> str:
|
||||
class AnthropicCoordinator(DataUpdateCoordinator[list[anthropic.types.ModelInfo]]):
|
||||
"""Coordinator using different intervals after success and failure."""
|
||||
|
||||
config_entry: AnthropicConfigEntry
|
||||
_client: anthropic.AsyncAnthropic
|
||||
client: anthropic.AsyncAnthropic
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: AnthropicConfigEntry) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
@@ -61,17 +46,8 @@ class AnthropicCoordinator(DataUpdateCoordinator[list[anthropic.types.ModelInfo]
|
||||
update_method=self.async_update_data,
|
||||
always_update=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def client(self) -> anthropic.AsyncAnthropic:
|
||||
"""Return the Anthropic client."""
|
||||
return self._client
|
||||
|
||||
@override
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
self._client = await async_create_client(
|
||||
self.hass, self.config_entry.data[CONF_API_KEY]
|
||||
self.client = anthropic.AsyncAnthropic(
|
||||
api_key=config_entry.data[CONF_API_KEY], http_client=get_async_client(hass)
|
||||
)
|
||||
|
||||
@callback
|
||||
|
||||
@@ -48,16 +48,16 @@
|
||||
"user": "Add AI task"
|
||||
},
|
||||
"step": {
|
||||
"additional": {
|
||||
"advanced": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::additional::data::prompt_caching%]"
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]"
|
||||
},
|
||||
"data_description": {
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::additional::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::additional::data_description::prompt_caching%]"
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]"
|
||||
},
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::additional::title%]"
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::advanced::title%]"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
@@ -115,7 +115,7 @@
|
||||
"user": "Add conversation agent"
|
||||
},
|
||||
"step": {
|
||||
"additional": {
|
||||
"advanced": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "Caching strategy"
|
||||
@@ -124,7 +124,7 @@
|
||||
"chat_model": "The model to serve the responses.",
|
||||
"prompt_caching": "Optimize your API cost and response times based on your usage."
|
||||
},
|
||||
"title": "Additional settings"
|
||||
"title": "Advanced settings"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
|
||||
@@ -23,9 +23,10 @@ from homeassistant.const import (
|
||||
ATTR_MODEL,
|
||||
ATTR_NAME,
|
||||
ATTR_SW_VERSION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
@@ -61,7 +62,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
key="humidity",
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"pressure": AranetSensorEntityDescription(
|
||||
@@ -82,7 +83,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
key="co2",
|
||||
name="Carbon Dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"radiation_rate": AranetSensorEntityDescription(
|
||||
@@ -114,7 +115,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
key="battery",
|
||||
name="Battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
|
||||
@@ -12,7 +12,12 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import UnitOfDensity, UnitOfRatio, UnitOfTemperature
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -30,7 +35,7 @@ class ArveDeviceEntityDescription(SensorEntityDescription):
|
||||
SENSORS: tuple[ArveDeviceEntityDescription, ...] = (
|
||||
ArveDeviceEntityDescription(
|
||||
key="CO2",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
value_fn=lambda arve_data: arve_data.co2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -43,21 +48,21 @@ SENSORS: tuple[ArveDeviceEntityDescription, ...] = (
|
||||
),
|
||||
ArveDeviceEntityDescription(
|
||||
key="Humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
value_fn=lambda arve_data: arve_data.humidity,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
ArveDeviceEntityDescription(
|
||||
key="PM10",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
value_fn=lambda arve_data: arve_data.pm10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
ArveDeviceEntityDescription(
|
||||
key="PM25",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
value_fn=lambda arve_data: arve_data.pm25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
|
||||
@@ -27,6 +27,7 @@ from homeassistant.const import ( # noqa: F401
|
||||
CONF_PATH,
|
||||
CONF_TRIGGERS,
|
||||
CONF_VARIABLES,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
SERVICE_RELOAD,
|
||||
SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF,
|
||||
@@ -37,7 +38,7 @@ from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
Context,
|
||||
CoreState,
|
||||
HassJob,
|
||||
Event,
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
callback,
|
||||
@@ -829,13 +830,13 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
if self._condition is not None:
|
||||
self._condition.async_unload()
|
||||
|
||||
async def _async_enable_automation(self) -> None:
|
||||
"""Arm the automation's triggers on startup."""
|
||||
async def _async_enable_automation(self, event: Event) -> None:
|
||||
"""Start automation on startup."""
|
||||
# Don't do anything if no longer enabled or already attached
|
||||
if not self._is_enabled or self._async_detach_triggers is not None:
|
||||
return
|
||||
|
||||
self._async_detach_triggers = await self._async_attach_triggers()
|
||||
self._async_detach_triggers = await self._async_attach_triggers(True)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def _async_enable(self) -> None:
|
||||
@@ -850,14 +851,13 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
self._is_enabled = True
|
||||
# HomeAssistant is starting up
|
||||
if self.hass.state is not CoreState.not_running:
|
||||
self._async_detach_triggers = await self._async_attach_triggers()
|
||||
self._async_detach_triggers = await self._async_attach_triggers(False)
|
||||
return
|
||||
|
||||
# Arm the triggers in a startup job, which runs after all listeners to
|
||||
# EVENT_HOMEASSISTANT_START have run but before EVENT_HOMEASSISTANT_STARTED
|
||||
# has fired. This ensures automations do not fire during startup, but
|
||||
# triggers listening for the started event are armed in time to catch it.
|
||||
self.hass.async_add_startup_job(HassJob(self._async_enable_automation))
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
self._async_enable_automation,
|
||||
)
|
||||
|
||||
async def _async_disable(self, stop_actions: bool = DEFAULT_STOP_ACTIONS) -> None:
|
||||
"""Disable the automation entity.
|
||||
@@ -942,7 +942,9 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
|
||||
script_execution_set("not_triggered")
|
||||
|
||||
async def _async_attach_triggers(self) -> Callable[[], None] | None:
|
||||
async def _async_attach_triggers(
|
||||
self, home_assistant_start: bool
|
||||
) -> Callable[[], None] | None:
|
||||
"""Set up the triggers."""
|
||||
this = None
|
||||
if state := self.hass.states.get(self.entity_id):
|
||||
@@ -966,7 +968,8 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
DOMAIN,
|
||||
str(self.name),
|
||||
self._log_callback,
|
||||
variables=variables,
|
||||
home_assistant_start,
|
||||
variables,
|
||||
did_not_trigger=self._handle_not_triggered,
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from homeassistant.helpers.selector import (
|
||||
TextSelectorType,
|
||||
)
|
||||
|
||||
from .const import DEFAULT_HOST, DOMAIN, SECTION_ADDITIONAL_SETTINGS
|
||||
from .const import DEFAULT_HOST, DOMAIN, SECTION_ADVANCED_SETTINGS
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -25,7 +25,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
vol.Required(CONF_PASSWORD): TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
vol.Required(SECTION_ADDITIONAL_SETTINGS): section(
|
||||
vol.Required(SECTION_ADVANCED_SETTINGS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST, default=DEFAULT_HOST): TextSelector(
|
||||
@@ -79,7 +79,7 @@ class AutoskopeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
username = user_input[CONF_USERNAME].lower()
|
||||
host = user_input[SECTION_ADDITIONAL_SETTINGS][CONF_HOST].lower()
|
||||
host = user_input[SECTION_ADVANCED_SETTINGS][CONF_HOST].lower()
|
||||
|
||||
try:
|
||||
cv.url(host)
|
||||
|
||||
@@ -5,5 +5,5 @@ from datetime import timedelta
|
||||
DOMAIN = "autoskope"
|
||||
|
||||
DEFAULT_HOST = "https://portal.autoskope.de"
|
||||
SECTION_ADDITIONAL_SETTINGS = "additional_settings"
|
||||
SECTION_ADVANCED_SETTINGS = "advanced_settings"
|
||||
UPDATE_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"description": "Enter your Autoskope credentials.",
|
||||
"sections": {
|
||||
"additional_settings": {
|
||||
"advanced_settings": {
|
||||
"data": {
|
||||
"host": "API endpoint"
|
||||
},
|
||||
|
||||
@@ -16,9 +16,12 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_CONNECTIONS,
|
||||
ATTR_SW_VERSION,
|
||||
CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
UnitOfDensity,
|
||||
UnitOfRatio,
|
||||
PERCENTAGE,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -58,7 +61,7 @@ class AwairSensorEntityDescription(SensorEntityDescription):
|
||||
|
||||
SENSOR_TYPE_SCORE = AwairSensorEntityDescription(
|
||||
key=API_SCORE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
translation_key="score",
|
||||
unique_id_tag="score", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -68,7 +71,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_HUMID,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
unique_id_tag="HUMID", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -90,7 +93,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_VOC,
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
unique_id_tag="VOC", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -104,7 +107,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_CO2,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
unique_id_tag="CO2", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -120,7 +123,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_ABS_HUMID,
|
||||
device_class=SensorDeviceClass.ABSOLUTE_HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfDensity.GRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
unique_id_tag="absolute_humidity",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -131,14 +134,14 @@ SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_PM25,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
unique_id_tag="PM25", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_PM10,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
unique_id_tag="PM10", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
|
||||
@@ -34,7 +34,7 @@ class AutomaticBackupEvent(BackupManagerBaseEntity, EventEntity):
|
||||
def __init__(self, coordinator: BackupDataUpdateCoordinator) -> None:
|
||||
"""Initialize the automatic backup event."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = "automatic_backup_event" # pylint: disable=home-assistant-entity-unique-id-redundant-domain,home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = "automatic_backup_event" # pylint: disable=home-assistant-entity-unique-id-redundant-domain
|
||||
self._attr_translation_key = "automatic_backup_event"
|
||||
|
||||
@callback
|
||||
|
||||
@@ -15,15 +15,16 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfApparentPower,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfReactiveEnergy,
|
||||
UnitOfReactivePower,
|
||||
UnitOfSpeed,
|
||||
@@ -52,19 +53,19 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm2_5",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
@@ -83,7 +84,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
@@ -178,7 +179,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
|
||||
@@ -56,7 +56,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
|
||||
super().__init__(coordinator)
|
||||
Camera.__init__(self)
|
||||
self._camera = camera
|
||||
self._attr_unique_id = f"{camera.serial}-camera" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{camera.serial}-camera"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, camera.serial)},
|
||||
serial_number=camera.serial,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""The BlinkStick integration."""
|
||||
@@ -0,0 +1,87 @@
|
||||
"""Support for BlinkStick lights."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from typing import Any
|
||||
|
||||
# from blinkstick import blinkstick
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_HS_COLOR,
|
||||
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
CONF_SERIAL = "serial"
|
||||
|
||||
DEFAULT_NAME = "Blinkstick"
|
||||
|
||||
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_SERIAL): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up BlinkStick device specified by serial number."""
|
||||
|
||||
name = config[CONF_NAME]
|
||||
serial = config[CONF_SERIAL]
|
||||
|
||||
stick = blinkstick.find_by_serial(serial)
|
||||
|
||||
add_entities([BlinkStickLight(stick, name)], True)
|
||||
|
||||
|
||||
class BlinkStickLight(LightEntity):
|
||||
"""Representation of a BlinkStick light."""
|
||||
|
||||
_attr_color_mode = ColorMode.HS
|
||||
_attr_supported_color_modes = {ColorMode.HS}
|
||||
|
||||
def __init__(self, stick, name):
|
||||
"""Initialize the light."""
|
||||
self._stick = stick
|
||||
self._attr_name = name
|
||||
|
||||
def update(self) -> None:
|
||||
"""Read back the device state."""
|
||||
rgb_color = self._stick.get_color()
|
||||
hsv = color_util.color_RGB_to_hsv(*rgb_color)
|
||||
self._attr_hs_color = hsv[:2]
|
||||
self._attr_brightness = int(hsv[2])
|
||||
self._attr_is_on = self.brightness is not None and self.brightness > 0
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
self._attr_hs_color = kwargs[ATTR_HS_COLOR]
|
||||
|
||||
brightness: int = kwargs.get(ATTR_BRIGHTNESS, 255)
|
||||
self._attr_brightness = brightness
|
||||
self._attr_is_on = bool(brightness)
|
||||
|
||||
assert self.hs_color
|
||||
rgb_color = color_util.color_hsv_to_RGB(
|
||||
self.hs_color[0], self.hs_color[1], brightness / 255 * 100
|
||||
)
|
||||
self._stick.set_color(red=rgb_color[0], green=rgb_color[1], blue=rgb_color[2])
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
self._stick.turn_off()
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"domain": "blinksticklight",
|
||||
"name": "BlinkStick",
|
||||
"codeowners": [],
|
||||
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
||||
"documentation": "https://www.home-assistant.io/integrations/blinksticklight",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["blinkstick"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["BlinkStick==1.2.0"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
extend = "../../../pyproject.toml"
|
||||
|
||||
lint.extend-ignore = [
|
||||
"F821"
|
||||
]
|
||||
@@ -187,7 +187,7 @@ async def async_process_advertisements(
|
||||
)
|
||||
stack.callback(unload)
|
||||
|
||||
if mode is BluetoothScanningMode.ACTIVE:
|
||||
if mode == BluetoothScanningMode.ACTIVE:
|
||||
task = hass.async_create_task(manager.async_request_active_scan(timeout))
|
||||
stack.callback(task.cancel)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "bluetooth_adapters",
|
||||
"name": "Bluetooth Adapters",
|
||||
"after_dependencies": ["esphome", "shelly", "ruuvi_gateway", "smlight"],
|
||||
"after_dependencies": ["esphome", "shelly", "ruuvi_gateway"],
|
||||
"codeowners": ["@bdraco"],
|
||||
"dependencies": ["bluetooth"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bluetooth_adapters",
|
||||
|
||||
@@ -13,9 +13,10 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -57,13 +58,13 @@ SENSOR_DESCRIPTIONS: dict[str, SHCSensorEntityDescription] = {
|
||||
HUMIDITY_SENSOR: SHCSensorEntityDescription(
|
||||
key=HUMIDITY_SENSOR,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda device: device.humidity,
|
||||
),
|
||||
PURITY_SENSOR: SHCSensorEntityDescription(
|
||||
key=PURITY_SENSOR,
|
||||
translation_key=PURITY_SENSOR,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
value_fn=lambda device: device.purity,
|
||||
),
|
||||
AIR_QUALITY_SENSOR: SHCSensorEntityDescription(
|
||||
@@ -111,7 +112,7 @@ SENSOR_DESCRIPTIONS: dict[str, SHCSensorEntityDescription] = {
|
||||
key=VALVE_TAPPET_SENSOR,
|
||||
translation_key=VALVE_TAPPET_SENSOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda device: device.position,
|
||||
attributes_fn=lambda device: {
|
||||
"valve_tappet_state": device.valvestate.name,
|
||||
|
||||
@@ -10,12 +10,12 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
UnitOfDensity,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
PERCENTAGE,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -37,25 +37,25 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="pm10",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="pm2_5",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="pm1",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
|
||||
@@ -36,7 +36,7 @@ class BroadlinkTime(BroadlinkEntity, TimeEntity):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(device)
|
||||
|
||||
self._attr_unique_id = f"{device.unique_id}-device_time" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{device.unique_id}-device_time"
|
||||
|
||||
@override
|
||||
def _update_state(self, data: dict[str, Any]) -> None:
|
||||
|
||||
@@ -93,9 +93,9 @@ class BSBLANClimate(BSBLanCircuitEntity, ClimateEntity):
|
||||
|
||||
# Backward compatible unique ID: circuit 1 keeps old format
|
||||
if circuit == 1:
|
||||
self._attr_unique_id = f"{mac}-climate" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{mac}-climate"
|
||||
else:
|
||||
self._attr_unique_id = f"{mac}-climate-{circuit}" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"{mac}-climate-{circuit}"
|
||||
|
||||
# Set temperature range from per-circuit static data
|
||||
if (static := data.static.get(circuit)) is not None:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["bsblan"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["python-bsblan==6.1.4"],
|
||||
"requirements": ["python-bsblan==6.1.3"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "bsb-lan*",
|
||||
|
||||
@@ -18,13 +18,15 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
REVOLUTIONS_PER_MINUTE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfConductivity,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
@@ -32,7 +34,6 @@ from homeassistant.const import (
|
||||
UnitOfMass,
|
||||
UnitOfPower,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
@@ -62,7 +63,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
(BTHomeSensorDeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.BATTERY}_{Units.PERCENTAGE}",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
@@ -95,7 +96,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.CO2}_{Units.CONCENTRATION_PARTS_PER_MILLION}",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# Current (Ampere)
|
||||
@@ -181,7 +182,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
(BTHomeSensorDeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.HUMIDITY}_{Units.PERCENTAGE}",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# Illuminance (lux)
|
||||
@@ -215,7 +216,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
(BTHomeSensorDeviceClass.MOISTURE, Units.PERCENTAGE): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.MOISTURE}_{Units.PERCENTAGE}",
|
||||
device_class=SensorDeviceClass.MOISTURE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# Packet Id (-)
|
||||
@@ -233,7 +234,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.PM10}_{Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER}",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# PM2.5 (μg/m3)
|
||||
@@ -243,7 +244,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.PM25}_{Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER}",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# Power (Watt)
|
||||
@@ -356,7 +357,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
): SensorEntityDescription(
|
||||
key=f"{BTHomeSensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS}_{Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER}",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# Voltage (volt)
|
||||
|
||||
@@ -6,6 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ccm15",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["py_ccm15==0.6.0"]
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: This integration does not register any service actions.
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not register any service actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities poll through the coordinator and do not subscribe to events.
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
status: exempt
|
||||
comment: This integration does not register any service actions.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: This integration has no options flow.
|
||||
docs-installation-parameters: todo
|
||||
entity-unavailable: todo
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: todo
|
||||
reauthentication-flow:
|
||||
status: exempt
|
||||
comment: The device connection is unauthenticated; revisit when optional password (pwd=) support lands.
|
||||
test-coverage: todo
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info: todo
|
||||
discovery: todo
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
dynamic-devices: todo
|
||||
entity-category:
|
||||
status: exempt
|
||||
comment: The single climate entity does not need a non-default category.
|
||||
entity-device-class:
|
||||
status: exempt
|
||||
comment: The climate entity has no applicable device class.
|
||||
entity-disabled-by-default:
|
||||
status: exempt
|
||||
comment: Only primary climate entities are provided.
|
||||
entity-translations:
|
||||
status: exempt
|
||||
comment: The climate entity uses the device name; no entity names to translate.
|
||||
exception-translations: todo
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: The integration uses default entity icons.
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: This integration does not raise repairable issues.
|
||||
stale-devices: todo
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: todo
|
||||
@@ -12,10 +12,6 @@
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your CCM15 controller.",
|
||||
"port": "The TCP port of the CCM15 controller's HTTP interface."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
"""Diagnostics platform for CentriConnect/MyPropane API integration."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import CentriConnectConfigEntry
|
||||
|
||||
TO_REDACT = {"Latitude", "Longitude", "Altitude"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: CentriConnectConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for the provided config entry."""
|
||||
coord = entry.runtime_data
|
||||
return {
|
||||
"device_info": {
|
||||
"device_id": coord.device_info.device_id,
|
||||
"device_name": coord.device_info.device_name,
|
||||
"hardware_version": coord.device_info.hardware_version,
|
||||
"lte_version": coord.device_info.lte_version,
|
||||
},
|
||||
"tank_data": async_redact_data(coord.data.raw_data, TO_REDACT),
|
||||
}
|
||||
@@ -47,7 +47,7 @@ rules:
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
diagnostics: todo
|
||||
discovery:
|
||||
status: exempt
|
||||
comment: This is a cloud polling integration with no local discovery mechanism.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""The clementine component."""
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"domain": "clementine",
|
||||
"name": "Clementine Music Player",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/clementine",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["clementineremote"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["python-clementine-remote==1.0.1"]
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Support for Clementine Music Player as media player."""
|
||||
|
||||
from datetime import timedelta
|
||||
import time
|
||||
from typing import override
|
||||
|
||||
from clementineremote import ClementineRemote
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
|
||||
MediaPlayerEntity,
|
||||
MediaPlayerEntityFeature,
|
||||
MediaPlayerState,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
DEFAULT_NAME = "Clementine Remote"
|
||||
DEFAULT_PORT = 5500
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_ACCESS_TOKEN): cv.positive_int,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Clementine platform."""
|
||||
|
||||
host = config[CONF_HOST]
|
||||
port = config[CONF_PORT]
|
||||
token = config.get(CONF_ACCESS_TOKEN)
|
||||
|
||||
client = ClementineRemote(host, port, token, reconnect=True)
|
||||
|
||||
add_entities([ClementineDevice(client, config[CONF_NAME])])
|
||||
|
||||
|
||||
class ClementineDevice(MediaPlayerEntity):
|
||||
"""Representation of Clementine Player."""
|
||||
|
||||
_attr_media_content_type = MediaType.MUSIC
|
||||
_attr_supported_features = (
|
||||
MediaPlayerEntityFeature.PAUSE
|
||||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.NEXT_TRACK
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.PLAY
|
||||
)
|
||||
_attr_volume_step = 0.04
|
||||
|
||||
def __init__(self, client, name):
|
||||
"""Initialize the Clementine device."""
|
||||
self._client = client
|
||||
self._attr_name = name
|
||||
|
||||
def update(self) -> None:
|
||||
"""Retrieve the latest data from the Clementine Player."""
|
||||
try:
|
||||
client = self._client
|
||||
|
||||
if client.state == "Playing":
|
||||
self._attr_state = MediaPlayerState.PLAYING
|
||||
elif client.state == "Paused":
|
||||
self._attr_state = MediaPlayerState.PAUSED
|
||||
elif client.state == "Disconnected":
|
||||
self._attr_state = MediaPlayerState.OFF
|
||||
else:
|
||||
self._attr_state = MediaPlayerState.PAUSED
|
||||
|
||||
if client.last_update and (time.time() - client.last_update > 40):
|
||||
self._attr_state = MediaPlayerState.OFF
|
||||
|
||||
volume = float(client.volume) if client.volume else 0.0
|
||||
self._attr_volume_level = volume / 100.0
|
||||
if client.active_playlist_id in client.playlists:
|
||||
self._attr_source = client.playlists[client.active_playlist_id]["name"]
|
||||
else:
|
||||
self._attr_source = "Unknown"
|
||||
self._attr_source_list = [s["name"] for s in client.playlists.values()]
|
||||
|
||||
if client.current_track:
|
||||
self._attr_media_title = client.current_track["title"]
|
||||
self._attr_media_artist = client.current_track["track_artist"]
|
||||
self._attr_media_album_name = client.current_track["track_album"]
|
||||
self._attr_media_image_hash = client.current_track["track_id"]
|
||||
else:
|
||||
self._attr_media_image_hash = None
|
||||
|
||||
except Exception:
|
||||
self._attr_state = MediaPlayerState.OFF
|
||||
raise
|
||||
|
||||
@override
|
||||
def select_source(self, source: str) -> None:
|
||||
"""Select input source."""
|
||||
client = self._client
|
||||
sources = [s for s in client.playlists.values() if s["name"] == source]
|
||||
if len(sources) == 1:
|
||||
client.change_song(sources[0]["id"], 0)
|
||||
|
||||
@override
|
||||
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
|
||||
"""Fetch media image of current playing image."""
|
||||
if self._client.current_track:
|
||||
image = bytes(self._client.current_track["art"])
|
||||
return (image, "image/png")
|
||||
|
||||
return None, None
|
||||
|
||||
@override
|
||||
def mute_volume(self, mute: bool) -> None:
|
||||
"""Send mute command."""
|
||||
self._client.set_volume(0)
|
||||
|
||||
@override
|
||||
def set_volume_level(self, volume: float) -> None:
|
||||
"""Set volume level."""
|
||||
self._client.set_volume(int(100 * volume))
|
||||
|
||||
def media_play_pause(self) -> None:
|
||||
"""Simulate play pause media player."""
|
||||
if self.state == MediaPlayerState.PLAYING:
|
||||
self.media_pause()
|
||||
else:
|
||||
self.media_play()
|
||||
|
||||
@override
|
||||
def media_play(self) -> None:
|
||||
"""Send play command."""
|
||||
self._attr_state = MediaPlayerState.PLAYING
|
||||
self._client.play()
|
||||
|
||||
@override
|
||||
def media_pause(self) -> None:
|
||||
"""Send media pause command to media player."""
|
||||
self._attr_state = MediaPlayerState.PAUSED
|
||||
self._client.pause()
|
||||
|
||||
@override
|
||||
def media_next_track(self) -> None:
|
||||
"""Send next track command."""
|
||||
self._client.next()
|
||||
|
||||
@override
|
||||
def media_previous_track(self) -> None:
|
||||
"""Send the previous track command."""
|
||||
self._client.previous()
|
||||
@@ -13,11 +13,12 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
UnitOfDensity,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -116,7 +117,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
CompitParameter.BOILER_TEMPERATURE: SensorEntityDescription(
|
||||
key=CompitParameter.BOILER_TEMPERATURE.value,
|
||||
@@ -203,14 +204,14 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
CompitParameter.CO2_PERCENT: SensorEntityDescription(
|
||||
key=CompitParameter.CO2_PERCENT.value,
|
||||
translation_key="co2_percent",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
CompitParameter.COLLECTOR_POWER: SensorEntityDescription(
|
||||
key=CompitParameter.COLLECTOR_POWER.value,
|
||||
@@ -289,7 +290,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
translation_key="fuel_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
CompitParameter.HEATING1_TARGET_TEMPERATURE: SensorEntityDescription(
|
||||
key=CompitParameter.HEATING1_TARGET_TEMPERATURE.value,
|
||||
@@ -332,7 +333,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
CompitParameter.LOWER_SOURCE_TEMPERATURE: SensorEntityDescription(
|
||||
key=CompitParameter.LOWER_SOURCE_TEMPERATURE.value,
|
||||
@@ -400,14 +401,14 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
CompitParameter.PM4_LEVEL_MEASURED: SensorEntityDescription(
|
||||
key=CompitParameter.PM4_LEVEL_MEASURED.value,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
device_class=SensorDeviceClass.PM4,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
CompitParameter.PM10_LEVEL: SensorEntityDescription(
|
||||
key=CompitParameter.PM10_LEVEL.value,
|
||||
@@ -421,7 +422,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
key=CompitParameter.PM10_MEASURED.value,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
CompitParameter.PM25_LEVEL: SensorEntityDescription(
|
||||
key=CompitParameter.PM25_LEVEL.value,
|
||||
@@ -435,7 +436,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
|
||||
key=CompitParameter.PM25_MEASURED.value,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
CompitParameter.PROTECTION_TEMPERATURE: SensorEntityDescription(
|
||||
key=CompitParameter.PROTECTION_TEMPERATURE.value,
|
||||
|
||||
@@ -8,7 +8,6 @@ from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import (
|
||||
ENTITY_STATE_TRIGGER_SCHEMA,
|
||||
EntityTriggerBase,
|
||||
NotTriggeredReasonReporter,
|
||||
Trigger,
|
||||
)
|
||||
|
||||
@@ -31,11 +30,7 @@ class CounterBaseIntegerTrigger(EntityTriggerBase):
|
||||
_schema = ENTITY_STATE_TRIGGER_SCHEMA
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the new state is valid."""
|
||||
return _is_integer_state(state)
|
||||
|
||||
@@ -68,11 +63,7 @@ class CounterMaxReachedTrigger(CounterValueBaseTrigger):
|
||||
"""Trigger for when a counter reaches its maximum value."""
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the new state matches the expected state(s)."""
|
||||
if (max_value := state.attributes.get(CONF_MAXIMUM)) is None:
|
||||
return False
|
||||
@@ -83,11 +74,7 @@ class CounterMinReachedTrigger(CounterValueBaseTrigger):
|
||||
"""Trigger for when a counter reaches its minimum value."""
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the new state matches the expected state(s)."""
|
||||
if (min_value := state.attributes.get(CONF_MINIMUM)) is None:
|
||||
return False
|
||||
@@ -98,11 +85,7 @@ class CounterResetTrigger(CounterValueBaseTrigger):
|
||||
"""Trigger for reset of counter entities."""
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the new state matches the expected state(s)."""
|
||||
if (init_state := state.attributes.get(CONF_INITIAL)) is None:
|
||||
return False
|
||||
|
||||
@@ -5,11 +5,7 @@ from typing import override
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.trigger import (
|
||||
EntityTriggerBase,
|
||||
NotTriggeredReasonReporter,
|
||||
Trigger,
|
||||
)
|
||||
from homeassistant.helpers.trigger import EntityTriggerBase, Trigger
|
||||
|
||||
from .const import ATTR_IS_CLOSED, DOMAIN, CoverDeviceClass
|
||||
from .models import CoverDomainSpec
|
||||
@@ -28,11 +24,7 @@ class CoverTriggerBase(EntityTriggerBase):
|
||||
return state.state
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the state matches the target cover state."""
|
||||
domain_spec = self._domain_specs[state.domain]
|
||||
return self._get_value(state) == domain_spec.target_value
|
||||
|
||||
@@ -35,13 +35,15 @@ from homeassistant.components.sensor import (
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
ATTR_VOLTAGE,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
@@ -136,7 +138,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
name_suffix="PPB",
|
||||
old_unique_id_suffix="ppb",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
),
|
||||
DeconzSensorDescription[AirQuality](
|
||||
key="air_quality_formaldehyde",
|
||||
@@ -147,7 +149,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
name_suffix="CH2O",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
DeconzSensorDescription[AirQuality](
|
||||
key="air_quality_co2",
|
||||
@@ -158,7 +160,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
name_suffix="CO2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
DeconzSensorDescription[AirQuality](
|
||||
key="air_quality_pm2_5",
|
||||
@@ -169,7 +171,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
name_suffix="PM25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
DeconzSensorDescription[CarbonDioxide](
|
||||
key="carbon_dioxide",
|
||||
@@ -179,7 +181,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
instance_check=CarbonDioxide,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
),
|
||||
DeconzSensorDescription[Consumption](
|
||||
key="consumption",
|
||||
@@ -208,7 +210,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
instance_check=Formaldehyde,
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
),
|
||||
DeconzSensorDescription[GenericStatus](
|
||||
key="status",
|
||||
@@ -225,7 +227,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
instance_check=Humidity,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
DeconzSensorDescription[LightLevel](
|
||||
@@ -246,7 +248,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
instance_check=Moisture,
|
||||
device_class=SensorDeviceClass.MOISTURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
DeconzSensorDescription[ParticulateMatter](
|
||||
@@ -258,7 +260,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
name_suffix="PM25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
DeconzSensorDescription[Power](
|
||||
key="power",
|
||||
@@ -308,7 +310,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
|
||||
old_unique_id_suffix="battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
DeconzSensorDescription[SensorResources](
|
||||
|
||||
@@ -12,10 +12,11 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolume,
|
||||
)
|
||||
@@ -51,7 +52,7 @@ async def async_setup_entry(
|
||||
12,
|
||||
SensorDeviceClass.BATTERY,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
UnitOfRatio.PERCENTAGE,
|
||||
PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_name="Battery",
|
||||
),
|
||||
@@ -62,7 +63,7 @@ async def async_setup_entry(
|
||||
54,
|
||||
SensorDeviceClass.HUMIDITY,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
UnitOfRatio.PERCENTAGE,
|
||||
PERCENTAGE,
|
||||
),
|
||||
DemoSensor(
|
||||
"sensor_3",
|
||||
@@ -71,7 +72,7 @@ async def async_setup_entry(
|
||||
54,
|
||||
SensorDeviceClass.CO,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
UnitOfRatio.PARTS_PER_MILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
DemoSensor(
|
||||
"sensor_4",
|
||||
@@ -80,7 +81,7 @@ async def async_setup_entry(
|
||||
54,
|
||||
SensorDeviceClass.CO2,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
UnitOfRatio.PARTS_PER_MILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
DemoSensor(
|
||||
"battery_4",
|
||||
@@ -89,7 +90,7 @@ async def async_setup_entry(
|
||||
99,
|
||||
SensorDeviceClass.BATTERY,
|
||||
SensorStateClass.MEASUREMENT,
|
||||
UnitOfRatio.PERCENTAGE,
|
||||
PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_name="Battery",
|
||||
),
|
||||
|
||||
@@ -149,7 +149,7 @@ class DemoWeather(WeatherEntity):
|
||||
) -> None:
|
||||
"""Initialize the Demo weather."""
|
||||
self._attr_name = f"Demo Weather {name}"
|
||||
self._attr_unique_id = f"demo-weather-{name.lower()}" # pylint: disable=home-assistant-entity-unique-id-redundant-domain,home-assistant-entity-unique-id-redundant-platform
|
||||
self._attr_unique_id = f"demo-weather-{name.lower()}" # pylint: disable=home-assistant-entity-unique-id-redundant-domain
|
||||
self._condition = condition
|
||||
self._native_temperature = temperature
|
||||
self._native_temperature_unit = temperature_unit
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.data_entry_flow import SectionConfig, section
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_HOSTNAME,
|
||||
CONF_IPV4,
|
||||
CONF_IPV6,
|
||||
@@ -39,7 +39,7 @@ from .const import (
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
|
||||
vol.Required(CONF_ADDITIONAL_OPTIONS): section(
|
||||
vol.Required(CONF_ADVANCED_OPTIONS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_RESOLVER): cv.string,
|
||||
@@ -117,13 +117,13 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if user_input:
|
||||
hostname = user_input[CONF_HOSTNAME]
|
||||
name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname
|
||||
additional_options = user_input[CONF_ADDITIONAL_OPTIONS]
|
||||
resolver = additional_options.get(CONF_RESOLVER, DEFAULT_RESOLVER)
|
||||
resolver_ipv6 = additional_options.get(
|
||||
advanced_options = user_input[CONF_ADVANCED_OPTIONS]
|
||||
resolver = advanced_options.get(CONF_RESOLVER, DEFAULT_RESOLVER)
|
||||
resolver_ipv6 = advanced_options.get(
|
||||
CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6
|
||||
)
|
||||
port = additional_options.get(CONF_PORT, DEFAULT_PORT)
|
||||
port_ipv6 = additional_options.get(CONF_PORT_IPV6, DEFAULT_PORT)
|
||||
port = advanced_options.get(CONF_PORT, DEFAULT_PORT)
|
||||
port_ipv6 = advanced_options.get(CONF_PORT_IPV6, DEFAULT_PORT)
|
||||
|
||||
validate = await async_validate_hostname(
|
||||
hostname, resolver, resolver_ipv6, port, port_ipv6
|
||||
|
||||
@@ -12,7 +12,7 @@ CONF_PORT_IPV6 = "port_ipv6"
|
||||
CONF_IPV4 = "ipv4"
|
||||
CONF_IPV6 = "ipv6"
|
||||
CONF_IPV6_V4 = "ipv6_v4"
|
||||
CONF_ADDITIONAL_OPTIONS = "additional_options"
|
||||
CONF_ADVANCED_OPTIONS = "advanced_options"
|
||||
|
||||
DEFAULT_HOSTNAME = "myip.opendns.com"
|
||||
DEFAULT_IPV6 = False
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"hostname": "The hostname for which to perform the DNS query."
|
||||
},
|
||||
"sections": {
|
||||
"additional_options": {
|
||||
"advanced_options": {
|
||||
"data": {
|
||||
"port": "IPv4 port",
|
||||
"port_ipv6": "IPv6 port",
|
||||
@@ -63,16 +63,16 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::additional_options::data::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::additional_options::data::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data::resolver_ipv6%]"
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::resolver_ipv6%]"
|
||||
},
|
||||
"data_description": {
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::resolver_ipv6%]"
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::resolver_ipv6%]"
|
||||
},
|
||||
"description": "Optionally change resolvers and ports."
|
||||
}
|
||||
|
||||
@@ -10,11 +10,7 @@ from homeassistant.components.event import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import (
|
||||
NotTriggeredReasonReporter,
|
||||
StatelessEntityTriggerBase,
|
||||
Trigger,
|
||||
)
|
||||
from homeassistant.helpers.trigger import StatelessEntityTriggerBase, Trigger
|
||||
|
||||
|
||||
class DoorbellRangTrigger(StatelessEntityTriggerBase):
|
||||
@@ -23,11 +19,7 @@ class DoorbellRangTrigger(StatelessEntityTriggerBase):
|
||||
_domain_specs = {EVENT_DOMAIN: DomainSpec(device_class=EventDeviceClass.DOORBELL)}
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the event type is ring."""
|
||||
return state.attributes.get(ATTR_EVENT_TYPE) == DoorbellEventType.RING
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
TEMPERATURE,
|
||||
EntityCategory,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolume,
|
||||
UnitOfVolumeFlowRate,
|
||||
@@ -140,7 +141,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
|
||||
DROPSensorEntityDescription(
|
||||
key=BATTERY,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=0,
|
||||
value_fn=lambda device: device.drop_api.battery(),
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -157,7 +158,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
|
||||
DROPSensorEntityDescription(
|
||||
key=INLET_TDS,
|
||||
translation_key=INLET_TDS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
value_fn=lambda device: device.drop_api.inlet_tds(),
|
||||
@@ -165,7 +166,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
|
||||
DROPSensorEntityDescription(
|
||||
key=OUTLET_TDS,
|
||||
translation_key=OUTLET_TDS,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
value_fn=lambda device: device.drop_api.outlet_tds(),
|
||||
@@ -173,7 +174,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
|
||||
DROPSensorEntityDescription(
|
||||
key=CARTRIDGE_1_LIFE,
|
||||
translation_key=CARTRIDGE_1_LIFE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suggested_display_precision=0,
|
||||
@@ -182,7 +183,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
|
||||
DROPSensorEntityDescription(
|
||||
key=CARTRIDGE_2_LIFE,
|
||||
translation_key=CARTRIDGE_2_LIFE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suggested_display_precision=0,
|
||||
@@ -191,7 +192,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
|
||||
DROPSensorEntityDescription(
|
||||
key=CARTRIDGE_3_LIFE,
|
||||
translation_key=CARTRIDGE_3_LIFE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suggested_display_precision=0,
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/dropbox",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["python-dropbox-api==0.1.4"]
|
||||
}
|
||||
|
||||
@@ -52,9 +52,7 @@ rules:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
integration-owner: done
|
||||
log-when-unavailable:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
log-when-unavailable: todo
|
||||
parallel-updates:
|
||||
status: exempt
|
||||
comment: Integration does not make any entity updates.
|
||||
|
||||
@@ -2,23 +2,9 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from duco_connectivity.models import NodeType
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "duco"
|
||||
PLATFORMS = [Platform.FAN, Platform.SELECT, Platform.SENSOR]
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
BOX_NODE_ID = 1
|
||||
VENTILATION_CAPABLE_NODE_TYPES: tuple[NodeType, ...] = (
|
||||
NodeType.BOX,
|
||||
NodeType.VLV,
|
||||
NodeType.VLVRH,
|
||||
NodeType.VLVVOC,
|
||||
NodeType.VLVCO2,
|
||||
NodeType.VLVCO2RH,
|
||||
NodeType.EAV,
|
||||
NodeType.EAVRH,
|
||||
NodeType.EAVVOC,
|
||||
NodeType.EAVCO2,
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ from duco_connectivity import (
|
||||
KnownActionName,
|
||||
Node,
|
||||
NodeListActionItemList,
|
||||
NodeType,
|
||||
VentilationState,
|
||||
)
|
||||
|
||||
@@ -18,7 +19,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, VENTILATION_CAPABLE_NODE_TYPES
|
||||
from .const import DOMAIN
|
||||
from .coordinator import DucoConfigEntry, DucoCoordinator
|
||||
from .entity import DucoEntity
|
||||
|
||||
@@ -26,6 +27,19 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
SUPPORTED_SELECT_NODE_TYPES = {
|
||||
NodeType.BOX,
|
||||
NodeType.VLV,
|
||||
NodeType.VLVRH,
|
||||
NodeType.VLVVOC,
|
||||
NodeType.VLVCO2,
|
||||
NodeType.VLVCO2RH,
|
||||
NodeType.EAV,
|
||||
NodeType.EAVRH,
|
||||
NodeType.EAVVOC,
|
||||
NodeType.EAVCO2,
|
||||
}
|
||||
|
||||
|
||||
def _get_ventilation_options(action: ActionItem) -> tuple[str, ...] | None:
|
||||
"""Return ventilation options advertised by a node action."""
|
||||
@@ -72,7 +86,7 @@ async def async_setup_entry(
|
||||
|
||||
# Duco advertises SetVentilationState broadly, so keep the select
|
||||
# limited to the box and known valve node families.
|
||||
if node.general.node_type not in VENTILATION_CAPABLE_NODE_TYPES:
|
||||
if node.general.node_type not in SUPPORTED_SELECT_NODE_TYPES:
|
||||
continue
|
||||
|
||||
options = options_by_node.get(node.node_id)
|
||||
|
||||
@@ -15,9 +15,10 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfRatio,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -25,7 +26,7 @@ from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import BOX_NODE_ID, DOMAIN, VENTILATION_CAPABLE_NODE_TYPES
|
||||
from .const import BOX_NODE_ID, DOMAIN
|
||||
from .coordinator import DucoConfigEntry, DucoCoordinator
|
||||
from .entity import DucoEntity
|
||||
|
||||
@@ -65,35 +66,37 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
if node.ventilation and node.ventilation.state != VentilationState.UNKNOWN
|
||||
else None
|
||||
),
|
||||
node_types=VENTILATION_CAPABLE_NODE_TYPES,
|
||||
node_types=(NodeType.BOX,),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="target_flow_level",
|
||||
translation_key="target_flow_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
suggested_display_precision=0,
|
||||
value_fn=lambda node: (
|
||||
node.ventilation.flow_lvl_tgt if node.ventilation else None
|
||||
),
|
||||
node_types=VENTILATION_CAPABLE_NODE_TYPES,
|
||||
node_types=(NodeType.BOX,),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="time_state_end",
|
||||
translation_key="time_state_end",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda node: (
|
||||
dt_util.utc_from_timestamp(node.ventilation.time_state_end)
|
||||
dt_util.utc_from_timestamp(node.ventilation.time_state_end).replace(
|
||||
second=0, microsecond=0
|
||||
)
|
||||
if node.ventilation and node.ventilation.time_state_end != 0
|
||||
else None
|
||||
),
|
||||
node_types=VENTILATION_CAPABLE_NODE_TYPES,
|
||||
node_types=(NodeType.BOX,),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
value_fn=lambda node: node.sensor.co2 if node.sensor else None,
|
||||
node_types=(
|
||||
NodeType.BSCO2,
|
||||
@@ -105,7 +108,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
DucoSensorEntityDescription(
|
||||
key="iaq_co2",
|
||||
translation_key="iaq_co2",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda node: node.sensor.iaq_co2 if node.sensor else None,
|
||||
@@ -120,14 +123,14 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda node: node.sensor.rh if node.sensor else None,
|
||||
node_types=(NodeType.BSRH, NodeType.UCRH, NodeType.VLVRH, NodeType.VLVCO2RH),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="iaq_rh",
|
||||
translation_key="iaq_rh",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda node: node.sensor.iaq_rh if node.sensor else None,
|
||||
|
||||
@@ -31,7 +31,7 @@ class EcobeeNotifyEntity(EcobeeBaseEntity, NotifyEntity):
|
||||
"""Initialize the thermostat."""
|
||||
super().__init__(data, thermostat_index)
|
||||
self._attr_unique_id = (
|
||||
f"{self.thermostat['identifier']}_notify_{thermostat_index}" # pylint: disable=home-assistant-entity-unique-id-redundant-platform
|
||||
f"{self.thermostat['identifier']}_notify_{thermostat_index}"
|
||||
)
|
||||
|
||||
@override
|
||||
|
||||
@@ -11,7 +11,12 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import UnitOfDensity, UnitOfRatio, UnitOfTemperature
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -37,14 +42,14 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
|
||||
),
|
||||
EcobeeSensorEntityDescription(
|
||||
key="humidity",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
runtime_key=None,
|
||||
),
|
||||
EcobeeSensorEntityDescription(
|
||||
key="co2PPM",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
runtime_key="actualCO2",
|
||||
@@ -52,7 +57,7 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
|
||||
EcobeeSensorEntityDescription(
|
||||
key="vocPPM",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
runtime_key="actualVOC",
|
||||
),
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecowitt",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["aioecowitt==2026.6.0"]
|
||||
"requirements": ["aioecowitt==2025.9.2"]
|
||||
}
|
||||
|
||||
@@ -14,17 +14,18 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UV_INDEX,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfIrradiance,
|
||||
UnitOfLength,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolumetricFlux,
|
||||
@@ -98,7 +99,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.HUMIDITY: SensorEntityDescription(
|
||||
key="HUMIDITY",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.DEGREE: SensorEntityDescription(
|
||||
@@ -121,19 +122,19 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.PM25: SensorEntityDescription(
|
||||
key="PM25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.PM10: SensorEntityDescription(
|
||||
key="PM10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.BATTERY_PERCENTAGE: SensorEntityDescription(
|
||||
key="BATTERY_PERCENTAGE",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
@@ -148,7 +149,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.CO2_PPM: SensorEntityDescription(
|
||||
key="CO2_PPM",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.LUX: SensorEntityDescription(
|
||||
@@ -262,13 +263,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
),
|
||||
EcoWittSensorTypes.PERCENTAGE: SensorEntityDescription(
|
||||
key="PERCENTAGE",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.SOIL_MOISTURE: SensorEntityDescription(
|
||||
key="SOIL_MOISTURE",
|
||||
device_class=SensorDeviceClass.MOISTURE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.DISTANCE_MM: SensorEntityDescription(
|
||||
@@ -285,13 +286,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.PM1: SensorEntityDescription(
|
||||
key="PM1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.PM4: SensorEntityDescription(
|
||||
key="PM4",
|
||||
device_class=SensorDeviceClass.PM4,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -9,16 +9,17 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_URL,
|
||||
PERCENTAGE,
|
||||
UnitOfApparentPower,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
@@ -157,19 +158,19 @@ SENSORS: dict[str | None, SensorEntityDescription] = {
|
||||
"μg/m³": SensorEntityDescription(
|
||||
key="concentration|microgram_per_cubic_meter",
|
||||
translation_key="concentration",
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"ppm": SensorEntityDescription(
|
||||
key="concentration|microgram_parts_per_million",
|
||||
translation_key="concentration",
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"%": SensorEntityDescription(
|
||||
key="percent",
|
||||
translation_key="percent",
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyenphase==3.0.1"],
|
||||
"requirements": ["pyenphase==3.0.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
||||
@@ -8,7 +8,12 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import UnitOfPressure, UnitOfRatio, UnitOfTemperature
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -21,7 +26,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
key="co2",
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
@@ -35,7 +40,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
key="humidity",
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==45.5.2",
|
||||
"aioesphomeapi==45.3.1",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.9.4"
|
||||
],
|
||||
|
||||
@@ -10,7 +10,6 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import (
|
||||
ENTITY_STATE_TRIGGER_SCHEMA,
|
||||
NotTriggeredReasonReporter,
|
||||
StatelessEntityTriggerBase,
|
||||
Trigger,
|
||||
TriggerConfig,
|
||||
@@ -43,11 +42,7 @@ class EventReceivedTrigger(StatelessEntityTriggerBase):
|
||||
self._event_types = set(self._options[CONF_EVENT_TYPE])
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: NotTriggeredReasonReporter,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the event type matches one of the configured types."""
|
||||
return state.attributes.get(ATTR_EVENT_TYPE) in self._event_types
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user