Compare commits

..

3 Commits

Author SHA1 Message Date
Paul Bottein 6860e0f3b9 Remove comment 2026-06-25 13:02:39 +02:00
Paul Bottein d3be0cc852 Simplify live attribute handling and use event enum 2026-06-25 12:49:01 +02:00
Paul Bottein c830c05cbd Expose selected state attributes in the logbook 2026-06-25 12:15:44 +02:00
517 changed files with 7974 additions and 13011 deletions
-98
View File
@@ -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()
-1
View File
@@ -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.
@@ -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
View File
@@ -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: |
+68 -15
View File
@@ -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
+1 -1
View File
@@ -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:
-1
View File
@@ -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
+2
View File
@@ -181,6 +181,7 @@ CLAUDE.md @home-assistant/core
/tests/components/asuswrt/ @kennedyshead @ollo69 @Vaskivskyi
/homeassistant/components/atag/ @MatsNL
/tests/components/atag/ @MatsNL
/homeassistant/components/aten_pe/ @mtdcr
/homeassistant/components/atome/ @baqs
/homeassistant/components/august/ @bdraco
/tests/components/august/ @bdraco
@@ -1986,6 +1987,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
-8
View File
@@ -1,9 +1 @@
"""Init file for Home Assistant."""
from probatio.compat import install_as_voluptuous
# Probatio replaces voluptuous as the validation engine. Custom integrations and a
# few dependencies still import voluptuous directly, so alias it to probatio in
# sys.modules before anything imports it. This must run before the first
# `import voluptuous`, hence the package __init__.
install_as_voluptuous()
+3 -36
View File
@@ -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
+3
View File
@@ -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",
@@ -27,7 +27,7 @@ from .const import CONDITIONS_MAP, DOMAIN, FORECAST_MAP
_LOGGER = logging.getLogger(__name__)
API_TIMEOUT: Final[int] = 120
WEATHER_UPDATE_INTERVAL = timedelta(minutes=20)
WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
type AemetConfigEntry = ConfigEntry[AemetData]
@@ -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),
)
+12 -11
View File
@@ -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,
),
)
+10 -10
View File
@@ -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: {
+8 -4
View File
@@ -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,
+4 -3
View File
@@ -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,
+2 -2
View File
@@ -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),
)
+51 -47
View File
@@ -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 -9
View File
@@ -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"
}
}
},
+9 -8
View File
@@ -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)
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==14.1.8"]
"requirements": ["aioamazondevices==14.1.3"]
}
@@ -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
@@ -28,7 +28,7 @@ async def async_update_unique_id(
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{old_key}"
if entity_id := entity_registry.async_get_entity_id(
platform, DOMAIN, unique_id
DOMAIN, platform, unique_id
):
_LOGGER.debug("Updating unique_id for %s", entity_id)
new_unique_id = unique_id.replace(old_key, new_key)
@@ -48,7 +48,7 @@ async def async_remove_entity_from_virtual_group(
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{key}"
entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id)
entity_id = entity_registry.async_get_entity_id(DOMAIN, platform, unique_id)
is_group = coordinator.data[serial_num].device_family == SPEAKER_GROUP_FAMILY
if entity_id and is_group:
entity_registry.async_remove(entity_id)
@@ -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
DOMAIN, SENSOR_DOMAIN, unique_id=unique_id
)
is_unsupported = not coordinator.data[serial_num].notifications_supported
+12 -11
View File
@@ -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,
),
@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["pyanglianwater"],
"quality_scale": "bronze",
"requirements": ["pyanglianwater==3.2.3"]
"requirements": ["pyanglianwater==3.2.2"]
}
@@ -6,8 +6,8 @@ import logging
from typing import TYPE_CHECKING, Any, cast, override
import anthropic
from probatio import to_openapi as convert
import voluptuous as vol
from voluptuous_openapi import convert
from homeassistant.components.zone import ENTITY_ID_HOME
from homeassistant.config_entries import (
@@ -298,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",
@@ -308,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] = {}
@@ -360,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
),
+1 -1
View File
@@ -103,8 +103,8 @@ from anthropic.types.web_fetch_tool_result_block import (
from anthropic.types.web_fetch_tool_result_block_param import (
Content as WebFetchToolResultBlockParamContentParam,
)
from probatio import to_openapi as convert
import voluptuous as vol
from voluptuous_openapi import convert
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigSubentry
@@ -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": {
+5 -4
View File
@@ -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,
),
+10 -5
View File
@@ -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,
@@ -0,0 +1 @@
"""The ATEN PE component."""
@@ -0,0 +1,9 @@
{
"domain": "aten_pe",
"name": "ATEN Rack PDU",
"codeowners": ["@mtdcr"],
"documentation": "https://www.home-assistant.io/integrations/aten_pe",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["atenpdu==0.3.6"]
}
+119
View File
@@ -0,0 +1,119 @@
"""The ATEN PE switch component."""
import logging
from typing import Any, override
from atenpdu import AtenPE, AtenPEError
import voluptuous as vol
from homeassistant.components.switch import (
PLATFORM_SCHEMA as SWITCH_PLATFORM_SCHEMA,
SwitchDeviceClass,
SwitchEntity,
)
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
CONF_AUTH_KEY = "auth_key"
CONF_COMMUNITY = "community"
CONF_PRIV_KEY = "priv_key"
DEFAULT_COMMUNITY = "private"
DEFAULT_PORT = "161"
DEFAULT_USERNAME = "administrator"
PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_AUTH_KEY): cv.string,
vol.Optional(CONF_PRIV_KEY): cv.string,
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the ATEN PE switch."""
node = config[CONF_HOST]
serv = config[CONF_PORT]
dev = AtenPE(
node=node,
serv=serv,
community=config[CONF_COMMUNITY],
username=config[CONF_USERNAME],
authkey=config.get(CONF_AUTH_KEY),
privkey=config.get(CONF_PRIV_KEY),
)
try:
await hass.async_add_executor_job(dev.initialize)
mac = await dev.deviceMAC()
outlets = dev.outlets()
name = await dev.deviceName()
model = await dev.modelName()
sw_version = await dev.deviceFWversion()
except AtenPEError as exc:
_LOGGER.error("Failed to initialize %s:%s: %s", node, serv, str(exc))
raise PlatformNotReady from exc
info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, mac)},
manufacturer="ATEN",
model=model,
name=name,
sw_version=sw_version,
)
async_add_entities(
(AtenSwitch(dev, info, mac, outlet.id, outlet.name) for outlet in outlets), True
)
class AtenSwitch(SwitchEntity):
"""Represents an ATEN PE switch."""
_attr_device_class = SwitchDeviceClass.OUTLET
def __init__(
self, device: AtenPE, info: DeviceInfo, mac: str, outlet: str, name: str
) -> None:
"""Initialize an ATEN PE switch."""
self._device = device
self._outlet = outlet
self._attr_device_info = info
self._attr_unique_id = f"{mac}-{outlet}"
self._attr_name = name or f"Outlet {outlet}"
@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self._device.setOutletStatus(self._outlet, "on")
self._attr_is_on = True
@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self._device.setOutletStatus(self._outlet, "off")
self._attr_is_on = False
async def async_update(self) -> None:
"""Process update from entity."""
status = await self._device.displayOutletStatus(self._outlet)
if status == "on":
self._attr_is_on = True
elif status == "off":
self._attr_is_on = False
+2 -2
View File
@@ -74,8 +74,8 @@ from ipaddress import ip_address
from typing import TYPE_CHECKING, Any, cast
from aiohttp import web
from probatio import serialize
import voluptuous as vol
import voluptuous_serialize
from homeassistant import data_entry_flow
from homeassistant.auth import AuthManagerFlowManager, InvalidAuthError
@@ -263,7 +263,7 @@ def _prepare_result_json(result: AuthFlowResult) -> dict[str, Any]:
if (schema := result["data_schema"]) is None:
data["data_schema"] = []
else:
data["data_schema"] = serialize(schema)
data["data_schema"] = voluptuous_serialize.convert(schema)
return data
@@ -3,8 +3,8 @@
import logging
from typing import Any, override
from probatio import serialize
import voluptuous as vol
import voluptuous_serialize
from homeassistant import data_entry_flow
from homeassistant.components import websocket_api
@@ -153,6 +153,6 @@ def _prepare_result_json(result: data_entry_flow.FlowResult) -> dict[str, Any]:
if (schema := result["data_schema"]) is None:
data["data_schema"] = []
else:
data["data_schema"] = serialize(schema)
data["data_schema"] = voluptuous_serialize.convert(schema)
return data
+12 -29
View File
@@ -731,32 +731,17 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
trace_element = TraceElement(variables, trigger_path)
trace_append_element(trace_element)
if not skip_condition and self._condition is not None:
try:
conditions_pass = self._condition.async_check(variables=variables)
except (vol.Invalid, HomeAssistantError) as err:
self._logger.error(
"Error while checking conditions of automation %s: %s",
self.entity_id,
err,
)
automation_trace.set_error(err)
return None
except Exception as err:
self._logger.exception(
"Unexpected error while checking conditions of automation %s",
self.entity_id,
)
automation_trace.set_error(err)
return None
if not conditions_pass:
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
trace_get(clear=False),
)
script_execution_set("failed_conditions")
return None
if (
not skip_condition
and self._condition is not None
and not self._condition.async_check(variables=variables)
):
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
trace_get(clear=False),
)
script_execution_set("failed_conditions")
return None
self.async_set_context(trigger_context)
event_data = {
@@ -809,9 +794,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
)
automation_trace.set_error(err)
except Exception as err:
self._logger.exception(
"Unexpected error while executing automation %s", self.entity_id
)
self._logger.exception("While executing automation %s", self.entity_id)
automation_trace.set_error(err)
return None
@@ -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)
+1 -1
View File
@@ -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"
},
+12 -9
View File
@@ -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,
),
@@ -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"
]
+5 -4
View File
@@ -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,
+6 -6
View File
@@ -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,
),
@@ -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*",
+10 -9
View File
@@ -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)
@@ -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()
+1 -1
View File
@@ -42,8 +42,8 @@ from openai.types.responses.response_input_param import (
ImageGenerationCall as ImageGenerationCallParam,
)
from openai.types.responses.response_output_item import ImageGenerationCall
from probatio import to_openapi as convert
import voluptuous as vol
from voluptuous_openapi import convert
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry
+12 -11
View File
@@ -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,
@@ -805,10 +805,6 @@ class DefaultAgent(ConversationEntity):
else:
num_unmatched_entities += 1
# Literal text matched is the dominant signal
same_text_matched = (maybe_result is not None) and (
result.text_chunks_matched == maybe_result.text_chunks_matched
)
if (
(maybe_result is None) # first result
or (
@@ -817,25 +813,22 @@ class DefaultAgent(ConversationEntity):
)
or (
# More entities matched
same_text_matched
and (num_matched_entities > best_num_matched_entities)
num_matched_entities > best_num_matched_entities
)
or (
# Fewer unmatched entities
same_text_matched
and (num_matched_entities == best_num_matched_entities)
(num_matched_entities == best_num_matched_entities)
and (num_unmatched_entities < best_num_unmatched_entities)
)
or (
# Prefer unmatched ranges
same_text_matched
and (num_matched_entities == best_num_matched_entities)
(num_matched_entities == best_num_matched_entities)
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges > best_num_unmatched_ranges)
)
or (
# Prefer match failures with entities
same_text_matched
(result.text_chunks_matched == maybe_result.text_chunks_matched)
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges == best_num_unmatched_ranges)
and (
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.24"]
"requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.1"]
}
+14 -12
View File
@@ -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](
+7 -6
View File
@@ -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",
),
@@ -9,8 +9,8 @@ import logging
from types import ModuleType
from typing import TYPE_CHECKING, Any, Literal, overload
from probatio import serialize
import voluptuous as vol
import voluptuous_serialize
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api import ActiveConnection
@@ -318,7 +318,7 @@ async def _async_get_device_automation_capabilities(
if (extra_fields := capabilities.get("extra_fields")) is None:
capabilities["extra_fields"] = []
else:
capabilities["extra_fields"] = serialize(
capabilities["extra_fields"] = voluptuous_serialize.convert(
extra_fields, custom_serializer=cv.custom_serializer
)
@@ -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
+1 -1
View File
@@ -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
+9 -9
View File
@@ -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."
}
@@ -0,0 +1,87 @@
"""Support for Dovado router."""
# mypy: ignore-errors
from datetime import timedelta
import logging
# import dovado
import voluptuous as vol
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
DEVICE_DEFAULT_NAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DOMAIN = "dovado"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_PORT): cv.port,
}
)
},
extra=vol.ALLOW_EXTRA,
)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Dovado component."""
hass.data[DOMAIN] = DovadoData(
dovado.Dovado(
config[DOMAIN][CONF_USERNAME],
config[DOMAIN][CONF_PASSWORD],
config[DOMAIN].get(CONF_HOST),
config[DOMAIN].get(CONF_PORT),
)
)
return True
class DovadoData:
"""Maintain a connection to the router."""
def __init__(self, client):
"""Set up a new Dovado connection."""
self._client = client
self.state = {}
@property
def name(self):
"""Name of the router."""
return self.state.get("product name", DEVICE_DEFAULT_NAME)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update device state."""
try:
self.state = self._client.state or {}
if not self.state:
return False
self.state.update(connected=self.state.get("modem status") == "CONNECTED")
except OSError as error:
_LOGGER.warning("Could not contact the router: %s", error)
return None
_LOGGER.debug("Received: %s", self.state)
return True
@property
def client(self):
"""Dovado client instance."""
return self._client
@@ -0,0 +1,10 @@
{
"domain": "dovado",
"name": "Dovado",
"codeowners": [],
"disabled": "This integration is disabled because it uses non-open source code to operate.",
"documentation": "https://www.home-assistant.io/integrations/dovado",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["dovado==0.4.1"]
}
+38
View File
@@ -0,0 +1,38 @@
"""Support for SMS notifications from the Dovado router."""
import logging
from typing import Any, override
from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
def get_service(
hass: HomeAssistant,
config: ConfigType,
discovery_info: DiscoveryInfoType | None = None,
) -> DovadoSMSNotificationService:
"""Get the Dovado Router SMS notification service."""
return DovadoSMSNotificationService(hass.data[DOMAIN].client)
class DovadoSMSNotificationService(BaseNotificationService):
"""Implement the notification service for the Dovado SMS component."""
def __init__(self, client):
"""Initialize the service."""
self._client = client
@override
def send_message(self, message: str, **kwargs: Any) -> None:
"""Send SMS to the specified target phone number."""
if not (target := kwargs.get(ATTR_TARGET)):
_LOGGER.error("One target is required")
return
self._client.send_sms(target, message)
@@ -0,0 +1,5 @@
extend = "../../../pyproject.toml"
lint.extend-ignore = [
"F821"
]
+143
View File
@@ -0,0 +1,143 @@
"""Support for sensors from the Dovado router."""
from dataclasses import dataclass
from datetime import timedelta
import re
from typing import Any, override
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import CONF_SENSORS, PERCENTAGE, UnitOfInformation
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 . import DOMAIN
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
SENSOR_UPLOAD = "upload"
SENSOR_DOWNLOAD = "download"
SENSOR_SIGNAL = "signal"
SENSOR_NETWORK = "network"
SENSOR_SMS_UNREAD = "sms"
@dataclass(frozen=True, kw_only=True)
class DovadoSensorEntityDescription(SensorEntityDescription):
"""Describes Dovado sensor entity."""
identifier: str
SENSOR_TYPES: tuple[DovadoSensorEntityDescription, ...] = (
DovadoSensorEntityDescription(
identifier=SENSOR_NETWORK,
key="signal strength",
name="Network",
icon="mdi:access-point-network",
),
DovadoSensorEntityDescription(
identifier=SENSOR_SIGNAL,
key="signal strength",
name="Signal Strength",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:signal",
),
DovadoSensorEntityDescription(
identifier=SENSOR_SMS_UNREAD,
key="sms unread",
name="SMS unread",
icon="mdi:message-text-outline",
),
DovadoSensorEntityDescription(
identifier=SENSOR_UPLOAD,
key="traffic modem tx",
name="Sent",
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:cloud-upload",
),
DovadoSensorEntityDescription(
identifier=SENSOR_DOWNLOAD,
key="traffic modem rx",
name="Received",
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:cloud-download",
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, [vol.In(SENSOR_KEYS)])}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Dovado sensor platform."""
dovado = hass.data[DOMAIN]
sensors = config[CONF_SENSORS]
entities = [
DovadoSensor(dovado, description)
for description in SENSOR_TYPES
if description.key in sensors
]
add_entities(entities)
class DovadoSensor(SensorEntity):
"""Representation of a Dovado sensor."""
entity_description: DovadoSensorEntityDescription
def __init__(self, data, description: DovadoSensorEntityDescription) -> None:
"""Initialize the sensor."""
self.entity_description = description
self._data = data
self._attr_name = f"{data.name} {description.name}"
self._attr_native_value = self._compute_state()
def _compute_state(self):
"""Compute the state of the sensor."""
state = self._data.state.get(self.entity_description.key)
sensor_identifier = self.entity_description.identifier
if sensor_identifier == SENSOR_NETWORK:
match = re.search(r"\((.+)\)", state)
return match.group(1) if match else None
if sensor_identifier == SENSOR_SIGNAL:
try:
return int(state.split()[0])
except ValueError:
return None
if sensor_identifier == SENSOR_SMS_UNREAD:
return int(state)
if sensor_identifier in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]:
return round(float(state) / 1e6, 1)
return state
def update(self) -> None:
"""Update sensor values."""
self._data.update()
self._attr_native_value = self._compute_state()
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return {k: v for k, v in self._data.state.items() if k not in ["date", "time"]}
@@ -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,
-14
View File
@@ -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,
)
+3 -4
View File
@@ -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
@@ -70,9 +71,7 @@ async def async_setup_entry(
if node.node_id in known_nodes:
continue
# 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 is not NodeType.BOX:
continue
options = options_by_node.get(node.node_id)
+4 -4
View File
@@ -25,7 +25,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,7 +65,7 @@ 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",
@@ -76,7 +76,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
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",
@@ -89,7 +89,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
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",
+9 -4
View File
@@ -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",
),
+12 -11
View File
@@ -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,
),
}
+6 -5
View File
@@ -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,
),
}
@@ -65,14 +65,6 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
"/ivp/meters/readings",
"/ivp/pdm/device_data",
"/home",
"/inventory.json?deleted=1",
"/admin/lib/acb_config",
"/ivp/sc/sched",
"/admin/lib/network_display",
"/admin/lib/wireless_display",
"/ivp/ensemble/relay",
"/ivp/livedata/status",
"/ivp/pdm/energy",
]
for end_point in end_points:
@@ -142,15 +134,16 @@ async def async_get_config_entry_diagnostics(
"encharge_power": envoy_data.encharge_power,
"encharge_aggregate": envoy_data.encharge_aggregate,
"enpower": envoy_data.enpower,
"acb_power": envoy_data.acb_power,
"acb_inventory": envoy_data.acb_inventory,
"battery_aggregate": envoy_data.battery_aggregate,
"collar": envoy_data.collar,
"c6cc": envoy_data.c6cc,
"system_consumption": envoy_data.system_consumption,
"system_production": envoy_data.system_production,
"system_consumption_phases": envoy_data.system_consumption_phases,
"system_production_phases": envoy_data.system_production_phases,
"ctmeter_production": envoy_data.ctmeter_production,
"ctmeter_consumption": envoy_data.ctmeter_consumption,
"ctmeter_storage": envoy_data.ctmeter_storage,
"ctmeter_production_phases": envoy_data.ctmeter_production_phases,
"ctmeter_consumption_phases": envoy_data.ctmeter_consumption_phases,
"ctmeter_storage_phases": envoy_data.ctmeter_storage_phases,
"ctmeters": envoy_data.ctmeters,
"ctmeters_phases": envoy_data.ctmeters_phases,
"dry_contact_status": envoy_data.dry_contact_status,
+8 -3
View File
@@ -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"
],
@@ -52,7 +52,9 @@ __all__ = [
"DoorbellEventType",
"EventDeviceClass",
"EventEntity",
"EventEntityCapabilityAttribute",
"EventEntityDescription",
"EventEntityStateAttribute",
]
# mypy: disallow-any-generics
+5 -4
View File
@@ -13,11 +13,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
Platform,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -38,20 +39,20 @@ MAIN_SENSOR_TYPES: dict[str, SensorEntityDescription] = {
"com.fibaro.smokeSensor": SensorEntityDescription(
key="com.fibaro.smokeSensor",
name="Smoke",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:fire",
),
"CO2": SensorEntityDescription(
key="CO2",
name="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,
),
"com.fibaro.humiditySensor": SensorEntityDescription(
key="com.fibaro.humiditySensor",
name="Humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
+9 -7
View File
@@ -16,10 +16,12 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
ATTR_TEMPERATURE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
CONF_TOKEN,
CONF_USERNAME,
UnitOfDensity,
UnitOfRatio,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -42,7 +44,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="pm",
name=ATTR_PM2_5,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
icon="mdi:cloud",
),
SensorEntityDescription(
@@ -54,25 +56,25 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="hum",
name=ATTR_HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:water-percent",
),
SensorEntityDescription(
key="co2",
name=ATTR_CARBON_DIOXIDE,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:molecule-co2",
),
SensorEntityDescription(
key="voc",
name=ATTR_VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
icon="mdi:cloud",
),
SensorEntityDescription(
key="allpollu",
name=ATTR_FOOBOT_INDEX,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:percent",
),
)
+1 -13
View File
@@ -126,8 +126,6 @@ class FreeboxRouter:
self.raids: dict[int, dict[str, Any]] = {}
self.sensors_temperature: dict[str, int] = {}
self.sensors_temperature_names: dict[str, str] = {}
self.sensors_fan: dict[str, int] = {}
self.sensors_fan_names: dict[str, str] = {}
self.sensors_connection: dict[str, float] = {}
self.call_list: list[dict[str, Any]] = []
self.home_granted = True
@@ -192,12 +190,6 @@ class FreeboxRouter:
self.sensors_temperature[sensor_id] = sensor.get("value")
self.sensors_temperature_names[sensor_id] = sensor["name"]
# Fan speed sensors (rpm). Name and id may vary under Freebox devices.
for fan in syst_datas.get("fans", []):
fan_id = fan["id"]
self.sensors_fan[fan_id] = fan.get("value")
self.sensors_fan_names[fan_id] = fan["name"]
# Connection sensors
connection_datas: dict[str, Any] = await self._api.connection.get_status()
for sensor_key in CONNECTION_SENSORS_KEYS:
@@ -329,11 +321,7 @@ class FreeboxRouter:
@property
def sensors(self) -> dict[str, Any]:
"""Return sensors."""
return {
**self.sensors_temperature,
**self.sensors_fan,
**self.sensors_connection,
}
return {**self.sensors_temperature, **self.sensors_connection}
@property
def call(self) -> Call:
@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
EntityCategory,
UnitOfDataRate,
UnitOfTemperature,
@@ -94,27 +93,6 @@ async def async_setup_entry(
for sensor_id, sensor_name in router.sensors_temperature_names.items()
]
_LOGGER.debug(
"%s - %s - %s fan sensors",
router.name,
router.mac,
len(router.sensors_fan_names),
)
entities.extend(
FreeboxSensor(
router,
SensorEntityDescription(
key=fan_id,
name=fan_name,
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:fan",
),
)
for fan_id, fan_name in router.sensors_fan_names.items()
)
entities.extend(
[FreeboxSensor(router, description) for description in CONNECTION_SENSORS]
)
+8 -3
View File
@@ -13,7 +13,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
StateType,
)
from homeassistant.const import UnitOfRatio, UnitOfTemperature, UnitOfVolumeFlowRate
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfTemperature,
UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -49,14 +54,14 @@ _T2 = FreshrSensorEntityDescription(
_CO2 = FreshrSensorEntityDescription(
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 r: r.co2,
)
_HUM = FreshrSensorEntityDescription(
key="hum",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda r: r.hum,
)
+3 -3
View File
@@ -10,7 +10,7 @@ from homeassistant.components.button import (
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.const import EntityCategory, Platform
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
@@ -75,7 +75,7 @@ def repair_issue_cleanup(hass: HomeAssistant, avm_wrapper: AvmWrapper) -> None:
if (
(
entity_button := entity_registry.async_get_entity_id(
Platform.BUTTON, DOMAIN, f"{avm_wrapper.unique_id}-cleanup"
"button", DOMAIN, f"{avm_wrapper.unique_id}-cleanup"
)
)
and (entity_entry := entity_registry.async_get(entity_button))
@@ -102,7 +102,7 @@ def repair_issue_firmware_update(hass: HomeAssistant, avm_wrapper: AvmWrapper) -
if (
(
entity_button := entity_registry.async_get_entity_id(
Platform.BUTTON, DOMAIN, f"{avm_wrapper.unique_id}-firmware_update"
"button", DOMAIN, f"{avm_wrapper.unique_id}-firmware_update"
)
)
and (entity_entry := entity_registry.async_get(entity_button))
+2 -2
View File
@@ -7,13 +7,13 @@ from typing import override
from requests.exceptions import RequestException
from homeassistant.components.image import ImageEntity
from homeassistant.const import EntityCategory, Platform
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util, slugify
from .const import DOMAIN
from .const import DOMAIN, Platform
from .coordinator import AvmWrapper, FritzConfigEntry
from .entity import FritzBoxBaseEntity
@@ -21,5 +21,5 @@
"integration_type": "system",
"preview_features": { "winter_mode": {} },
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20260624.1"]
"requirements": ["home-assistant-frontend==20260624.0"]
}
+10 -10
View File
@@ -14,7 +14,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfDensity
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -63,7 +63,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
key=ATTR_C6H6,
value=lambda sensors: sensors.c6h6.value if sensors.c6h6 else None,
suggested_display_precision=0,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
translation_key="c6h6",
),
@@ -72,7 +72,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.co.value if sensors.co else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -80,7 +80,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.no.value if sensors.no else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -88,7 +88,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.no2.value if sensors.no2 else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -104,7 +104,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
translation_key=ATTR_NOX,
value=lambda sensors: sensors.nox.value if sensors.nox else None,
suggested_display_precision=0,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -112,7 +112,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.o3.value if sensors.o3 else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -128,7 +128,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.pm10.value if sensors.pm10 else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -144,7 +144,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.pm25.value if sensors.pm25 else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -160,7 +160,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.so2.value if sensors.so2 else None,
suggested_display_precision=0,
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,
),
GiosSensorEntityDescription(
@@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==13.3.0"]
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==13.2.5"]
}
@@ -14,7 +14,11 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.config_entries import ConfigSubentry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, UnitOfRatio
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
CONF_LATITUDE,
CONF_LONGITUDE,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -121,7 +125,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
native_unit_of_measurement_fn=lambda x: x.pollutants.co.concentration.units,
exists_fn=lambda x: "co" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.co.concentration.value,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
AirQualitySensorEntityDescription(
key="nh3",
@@ -31,8 +31,8 @@ from google.genai.types import (
Tool,
ToolListUnion,
)
from probatio import to_openapi as convert
import voluptuous as vol
from voluptuous_openapi import convert
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigSubentry
+7 -6
View File
@@ -19,9 +19,10 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
UnitOfDensity,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -43,13 +44,13 @@ SENSOR_DESCRIPTIONS = {
(DeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription(
key=f"{DeviceClass.HUMIDITY}_{Units.PERCENTAGE}",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
(DeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription(
key=f"{DeviceClass.BATTERY}_{Units.PERCENTAGE}",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
(
@@ -68,13 +69,13 @@ SENSOR_DESCRIPTIONS = {
): SensorEntityDescription(
key=f"{DeviceClass.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,
),
(DeviceClass.CO2, Units.CONCENTRATION_PARTS_PER_MILLION): SensorEntityDescription(
key=f"{DeviceClass.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,
),
}
@@ -27,6 +27,25 @@
"state": {
"2d_fix": "2D Fix",
"3d_fix": "3D Fix"
},
"state_attributes": {
"climb": {
"name": "[%key:component::gpsd::entity::sensor::climb::name%]"
},
"elevation": {
"name": "[%key:common::config_flow::data::elevation%]"
},
"gps_time": {
"name": "[%key:component::time_date::selector::display_options::options::time%]"
},
"latitude": { "name": "[%key:common::config_flow::data::latitude%]" },
"longitude": {
"name": "[%key:common::config_flow::data::longitude%]"
},
"mode": { "name": "[%key:common::config_flow::data::mode%]" },
"speed": {
"name": "[%key:component::sensor::entity_component::speed::name%]"
}
}
},
"time": {

Some files were not shown because too many files have changed in this diff Show More