Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston a6bbf2f78f Bump cryptography to 49.0.0 and pyOpenSSL to 26.3.0 2026-06-25 14:53:39 +02:00
335 changed files with 6912 additions and 9947 deletions
@@ -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
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
+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
@@ -70,7 +70,7 @@ async def async_remove_unsupported_notification_sensors(
):
unique_id = f"{serial_num}-{notification_key}"
entity_id = entity_registry.async_get_entity_id(
Platform.SENSOR, DOMAIN, unique_id=unique_id
SENSOR_DOMAIN, DOMAIN, unique_id=unique_id
)
is_unsupported = not coordinator.data[serial_num].notifications_supported
+12 -11
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"]
}
+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
+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*",
@@ -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()
+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,
+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",
),
@@ -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,
+1 -16
View File
@@ -27,19 +27,6 @@ _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
SUPPORTED_SELECT_NODE_TYPES = {
NodeType.BOX,
NodeType.VLV,
NodeType.VLVRH,
NodeType.VLVVOC,
NodeType.VLVCO2,
NodeType.VLVCO2RH,
NodeType.EAV,
NodeType.EAVRH,
NodeType.EAVVOC,
NodeType.EAVCO2,
}
def _get_ventilation_options(action: ActionItem) -> tuple[str, ...] | None:
"""Return ventilation options advertised by a node action."""
@@ -84,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 SUPPORTED_SELECT_NODE_TYPES:
if node.general.node_type is not NodeType.BOX:
continue
options = options_by_node.get(node.node_id)
+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"
],
+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",
),
)
+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(
@@ -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",
@@ -0,0 +1 @@
"""The greenwave component."""
+123
View File
@@ -0,0 +1,123 @@
"""Support for Greenwave Reality (TCP Connected) lights."""
from datetime import timedelta
import logging
import os
from typing import Any, override
import greenwavereality as greenwave
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
ColorMode,
LightEntity,
)
from homeassistant.const import CONF_HOST
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 Throttle
_LOGGER = logging.getLogger(__name__)
CONF_VERSION = "version"
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_HOST): cv.string, vol.Required(CONF_VERSION): cv.positive_int}
)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Greenwave Reality Platform."""
host = config.get(CONF_HOST)
tokenfilename = hass.config.path(".greenwave")
if config.get(CONF_VERSION) == 3:
if os.path.exists(tokenfilename):
with open(tokenfilename, encoding="utf8") as tokenfile:
token = tokenfile.read()
else:
try:
token = greenwave.grab_token(host, "hass", "homeassistant")
except PermissionError:
_LOGGER.error("The Gateway Is Not In Sync Mode")
raise
with open(tokenfilename, "w+", encoding="utf8") as tokenfile:
tokenfile.write(token)
else:
token = None
bulbs = greenwave.grab_bulbs(host, token)
add_entities(
GreenwaveLight(device, host, token, GatewayData(host, token))
for device in bulbs.values()
)
class GreenwaveLight(LightEntity):
"""Representation of an Greenwave Reality Light."""
_attr_color_mode = ColorMode.BRIGHTNESS
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
def __init__(self, light, host, token, gatewaydata):
"""Initialize a Greenwave Reality Light."""
self._did = int(light["did"])
self._attr_name = light["name"]
self._attr_is_on = bool(int(light["state"]))
self._attr_brightness = greenwave.hass_brightness(light)
self._host = host
self._attr_available = greenwave.check_online(light)
self._token = token
self._gatewaydata = gatewaydata
@override
def turn_on(self, **kwargs: Any) -> None:
"""Instruct the light to turn on."""
temp_brightness = int((kwargs.get(ATTR_BRIGHTNESS, 255) / 255) * 100)
greenwave.set_brightness(self._host, self._did, temp_brightness, self._token)
greenwave.turn_on(self._host, self._did, self._token)
@override
def turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
greenwave.turn_off(self._host, self._did, self._token)
def update(self) -> None:
"""Fetch new state data for this light."""
self._gatewaydata.update()
bulbs = self._gatewaydata.greenwave
self._attr_is_on = bool(int(bulbs[self._did]["state"]))
self._attr_brightness = greenwave.hass_brightness(bulbs[self._did])
self._attr_available = greenwave.check_online(bulbs[self._did])
self._attr_name = bulbs[self._did]["name"]
class GatewayData:
"""Handle Gateway data and limit updates."""
def __init__(self, host, token):
"""Initialize the data object."""
self._host = host
self._token = token
self._greenwave = greenwave.grab_bulbs(host, token)
@property
def greenwave(self):
"""Return Gateway API object."""
return self._greenwave
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from the gateway."""
self._greenwave = greenwave.grab_bulbs(self._host, self._token)
return self._greenwave
@@ -0,0 +1,10 @@
{
"domain": "greenwave",
"name": "Greenwave Reality",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/greenwave",
"iot_class": "local_polling",
"loggers": ["greenwavereality"],
"quality_scale": "legacy",
"requirements": ["greenwavereality==0.5.1"]
}
+1 -1
View File
@@ -92,7 +92,7 @@ class SupervisorJobs:
# We catch all errors to prevent an error in one from stopping the others
for match in [job for job in self._jobs.values() if subscription.matches(job)]:
try:
subscription.event_callback(match)
return subscription.event_callback(match)
except Exception as err: # noqa: BLE001
_LOGGER.error(
"Error encountered processing Supervisor Job (%s %s %s) - %s",
+14 -13
View File
@@ -12,9 +12,11 @@ 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,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
@@ -22,7 +24,6 @@ from homeassistant.const import (
UnitOfPower,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
@@ -56,7 +57,7 @@ HM_STATE_HA_CAST = {
SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
"HUMIDITY": SensorEntityDescription(
key="HUMIDITY",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -128,7 +129,7 @@ SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
),
"CONCENTRATION": SensorEntityDescription(
key="CONCENTRATION",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -203,15 +204,15 @@ SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
),
"VALVE_STATE": SensorEntityDescription(
key="VALVE_STATE",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
"CARRIER_SENSE_LEVEL": SensorEntityDescription(
key="CARRIER_SENSE_LEVEL",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
"DUTY_CYCLE_LEVEL": SensorEntityDescription(
key="DUTY_CYCLE_LEVEL",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
"BRIGHTNESS": SensorEntityDescription(
key="BRIGHTNESS",
@@ -220,37 +221,37 @@ SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
),
"MASS_CONCENTRATION_PM_1": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_1",
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,
),
"MASS_CONCENTRATION_PM_2_5": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_2_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,
),
"MASS_CONCENTRATION_PM_10": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_10",
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,
),
"MASS_CONCENTRATION_PM_1_24H_AVERAGE": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_1_24H_AVERAGE",
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,
),
"MASS_CONCENTRATION_PM_2_5_24H_AVERAGE": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_2_5_24H_AVERAGE",
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,
),
"MASS_CONCENTRATION_PM_10_24H_AVERAGE": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_10_24H_AVERAGE",
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,
),
@@ -9,5 +9,5 @@
"iot_class": "local_polling",
"loggers": ["aioimmich"],
"quality_scale": "platinum",
"requirements": ["aioimmich==0.15.1"]
"requirements": ["aioimmich==0.15.0"]
}
@@ -161,7 +161,5 @@ SENSOR_KEYS: Final[dict[int, list[str]]] = {
IndevoltSolar.CUMULATIVE_PRODUCTION,
IndevoltBattery.GEN_2_CYCLE_COUNT,
IndevoltBattery.GEN_2_TRANSFORMER_TEMPERATURE,
IndevoltBattery.REMAINING_CHARGING_TIME,
IndevoltBattery.REMAINING_DISCHARGING_TIME,
],
}
@@ -27,7 +27,6 @@ from homeassistant.const import (
UnitOfFrequency,
UnitOfPower,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -46,7 +45,6 @@ class IndevoltSensorEntityDescription(SensorEntityDescription):
state_mapping: dict[str | int, str] = field(default_factory=dict)
generation: tuple[int, ...] = (1, 2)
energy_mode: IndevoltEnergyMode | None = None
charge_discharge_state: int | None = None
SENSORS: Final = (
@@ -244,26 +242,6 @@ SENSORS: Final = (
state_mapping={1000: "static", 1001: "charging", 1002: "discharging"},
device_class=SensorDeviceClass.ENUM,
),
IndevoltSensorEntityDescription(
key=IndevoltBattery.REMAINING_CHARGING_TIME,
generation=(2,),
translation_key="remaining_charging_time",
native_unit_of_measurement=UnitOfTime.MINUTES,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
charge_discharge_state=1001,
entity_registry_enabled_default=False,
),
IndevoltSensorEntityDescription(
key=IndevoltBattery.REMAINING_DISCHARGING_TIME,
generation=(2,),
translation_key="remaining_discharging_time",
native_unit_of_measurement=UnitOfTime.MINUTES,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
charge_discharge_state=1002,
entity_registry_enabled_default=False,
),
IndevoltSensorEntityDescription(
key=IndevoltBattery.SOC,
translation_key="battery_soc",
@@ -970,14 +948,6 @@ class IndevoltSensorEntity(IndevoltEntity, SensorEntity):
if energy_mode != self.entity_description.energy_mode:
return False
# Check whether the battery is not in the required charge/discharge state
if (
self.entity_description.charge_discharge_state is not None
and self.coordinator.data.get(IndevoltBattery.CHARGE_DISCHARGE_STATE)
!= self.entity_description.charge_discharge_state
):
return False
# Check whether inverter is reporting 0 degrees with heater not active (thus reporting to indicate "idle")
# Pending fix by Indevolt: https://discord.com/channels/1417471269942591571/1510277757689659522
if self.entity_description.key == IndevoltBattery.GEN_1_INVERTER_TEMPERATURE:
@@ -365,12 +365,6 @@
"realtime_target_soc": {
"name": "Real-time target SOC"
},
"remaining_charging_time": {
"name": "Remaining charging time"
},
"remaining_discharging_time": {
"name": "Remaining discharging time"
},
"serial_number": {
"name": "Serial number"
},
@@ -12,7 +12,11 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -42,14 +46,14 @@ INTELLICLIMA_SENSORS: tuple[IntelliClimaSensorEntityDescription, ...] = (
key="humidity",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda device_data: float(device_data.rh),
),
IntelliClimaSensorEntityDescription(
key="voc",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
value_fn=lambda device_data: float(device_data.voc_state),
),
)
+4 -3
View File
@@ -14,14 +14,15 @@ from homeassistant.components.sensor import (
SensorEntity,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
CONF_DOMAIN,
CONF_ENTITIES,
CONF_SOURCE,
CONF_UNIT_OF_MEASUREMENT,
LIGHT_LUX,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
)
@@ -66,8 +67,8 @@ UNIT_OF_MEASUREMENT_MAPPING = {
pypck.lcn_defs.VarUnit.METERPERSECOND: UnitOfSpeed.METERS_PER_SECOND,
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
pypck.lcn_defs.VarUnit.AMPERE: UnitOfElectricCurrent.AMPERE,
pypck.lcn_defs.VarUnit.PPM: UnitOfRatio.PARTS_PER_MILLION,
pypck.lcn_defs.VarUnit.PERCENT: UnitOfRatio.PERCENTAGE,
pypck.lcn_defs.VarUnit.PPM: CONCENTRATION_PARTS_PER_MILLION,
pypck.lcn_defs.VarUnit.PERCENT: PERCENTAGE,
}
+3 -3
View File
@@ -15,7 +15,7 @@ from homeassistant.components.number import (
NumberMode,
)
from homeassistant.components.script import scripts_with_entity
from homeassistant.const import UnitOfRatio, UnitOfTemperature, UnitOfTime
from homeassistant.const import PERCENTAGE, UnitOfTemperature, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -41,13 +41,13 @@ NUMBER_DESC: dict[ThinQProperty, NumberEntityDescription] = {
),
ThinQProperty.LIGHT_STATUS: NumberEntityDescription(
key=ThinQProperty.LIGHT_STATUS,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.LIGHT_STATUS,
),
ThinQProperty.TARGET_HUMIDITY: NumberEntityDescription(
key=ThinQProperty.TARGET_HUMIDITY,
device_class=NumberDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.TARGET_HUMIDITY,
),
ThinQProperty.TARGET_TEMPERATURE: NumberEntityDescription(
+14 -14
View File
@@ -18,9 +18,9 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
UnitOfDensity,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
UnitOfEnergy,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -37,25 +37,25 @@ AIR_QUALITY_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
ThinQProperty.PM1: SensorEntityDescription(
key=ThinQProperty.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,
),
ThinQProperty.PM2: SensorEntityDescription(
key=ThinQProperty.PM2,
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,
),
ThinQProperty.PM10: SensorEntityDescription(
key=ThinQProperty.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,
),
ThinQProperty.HUMIDITY: SensorEntityDescription(
key=ThinQProperty.HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
ThinQProperty.MONITORING_ENABLED: SensorEntityDescription(
@@ -106,12 +106,12 @@ FILTER_INFO_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.FILTER_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.FILTER_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.FILTER_LIFETIME,
),
ThinQProperty.TOP_FILTER_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.TOP_FILTER_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.TOP_FILTER_REMAIN_PERCENT,
),
}
@@ -119,7 +119,7 @@ HUMIDITY_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
ThinQProperty.CURRENT_HUMIDITY: SensorEntityDescription(
key=ThinQProperty.CURRENT_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
)
}
@@ -215,7 +215,7 @@ RECIPE_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.BEER_REMAIN: SensorEntityDescription(
key=ThinQProperty.BEER_REMAIN,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.BEER_REMAIN,
),
}
@@ -227,7 +227,7 @@ REFRIGERATION_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.FRESH_AIR_FILTER_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.FRESH_AIR_FILTER_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.FRESH_AIR_FILTER,
),
}
@@ -318,17 +318,17 @@ WATER_FILTER_INFO_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.WATER_FILTER_1_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.WATER_FILTER_1_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.WATER_FILTER_1_REMAIN_PERCENT,
),
ThinQProperty.WATER_FILTER_2_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.WATER_FILTER_2_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.WATER_FILTER_2_REMAIN_PERCENT,
),
ThinQProperty.WATER_FILTER_3_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.WATER_FILTER_3_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.WATER_FILTER_3_REMAIN_PERCENT,
),
}
@@ -0,0 +1,56 @@
"""Support for sending data to Logentries webhook endpoint."""
import json
import logging
import requests
import voluptuous as vol
from homeassistant.const import CONF_TOKEN, EVENT_STATE_CHANGED
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, state as state_helper
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "logentries"
DEFAULT_HOST = "https://webhook.logentries.com/noformat/logs/"
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_TOKEN): cv.string})}, extra=vol.ALLOW_EXTRA
)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Logentries component."""
conf = config[DOMAIN]
token = conf.get(CONF_TOKEN)
le_wh = f"{DEFAULT_HOST}{token}"
def logentries_event_listener(event):
"""Listen for new messages on the bus and sends them to Logentries."""
if (state := event.data.get("new_state")) is None:
return
try:
_state = state_helper.state_as_number(state)
except ValueError:
_state = state.state
json_body = [
{
"domain": state.domain,
"entity_id": state.object_id,
"attributes": dict(state.attributes),
"time": str(event.time_fired),
"value": _state,
}
]
try:
payload = {"host": le_wh, "event": json_body}
requests.post(le_wh, data=json.dumps(payload), timeout=10)
except requests.exceptions.RequestException:
_LOGGER.exception("Error sending to Logentries")
hass.bus.listen(EVENT_STATE_CHANGED, logentries_event_listener)
return True
@@ -0,0 +1,8 @@
{
"domain": "logentries",
"name": "Logentries",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/logentries",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
+5 -5
View File
@@ -11,10 +11,10 @@ from homeassistant.components.sensor import (
from homeassistant.const import (
ATTR_LATITUDE,
ATTR_LONGITUDE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONF_SHOW_ON_MAP,
UnitOfDensity,
PERCENTAGE,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -36,7 +36,7 @@ SENSORS: tuple[SensorEntityDescription, ...] = (
),
SensorEntityDescription(
key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -55,13 +55,13 @@ SENSORS: tuple[SensorEntityDescription, ...] = (
),
SensorEntityDescription(
key="P1",
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="P2",
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,
),
+6 -4
View File
@@ -12,9 +12,11 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -30,7 +32,7 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
SensorType.AIR_HUMIDITY: SensorEntityDescription(
key="air_humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorType.AIR_PRESSURE: SensorEntityDescription(
@@ -47,7 +49,7 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
SensorType.ECO2: SensorEntityDescription(
key="eco2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
SensorType.LIGHT: SensorEntityDescription(
@@ -65,7 +67,7 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
SensorType.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,
),
}
+3 -3
View File
@@ -16,10 +16,10 @@ from homeassistant.components.number import (
NumberMode,
)
from homeassistant.const import (
PERCENTAGE,
EntityCategory,
Platform,
UnitOfLength,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -354,7 +354,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(
key="pump_setpoint",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key="pump_setpoint",
native_max_value=100,
native_min_value=0.5,
@@ -516,7 +516,7 @@ DISCOVERY_SCHEMAS = [
entity_description=MatterRangeNumberEntityDescription(
key="speaker_setpoint",
translation_key="speaker_setpoint",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
command=lambda value: clusters.LevelControl.Commands.MoveToLevel(
level=int(value)
),
+21 -20
View File
@@ -22,19 +22,20 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
Platform,
UnitOfApparentPower,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfReactivePower,
UnitOfTemperature,
UnitOfTime,
@@ -443,7 +444,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="HumiditySensor",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
device_to_ha=lambda x: x / HUMIDITY_SCALING_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
@@ -458,7 +459,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="SoilMoistureSensor",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.MOISTURE,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -483,7 +484,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PowerSource",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
# value has double precision
@@ -624,7 +625,7 @@ DISCOVERY_SCHEMAS = [
entity_description=MatterSensorEntityDescription(
key="EveThermoValvePosition",
translation_key="valve_position",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
entity_class=MatterSensor,
required_attributes=(EveCluster.Attributes.ValvePosition,),
@@ -657,7 +658,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="CarbonDioxideSensor",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -670,7 +671,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="TotalVolatileOrganicCompoundsSensor",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -698,7 +699,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PM1Sensor",
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,
),
@@ -711,7 +712,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PM25Sensor",
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,
),
@@ -724,7 +725,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PM10Sensor",
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,
),
@@ -749,7 +750,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="CarbonMonoxideSensor",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -762,7 +763,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="NitrogenDioxideSensor",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -775,7 +776,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="OzoneConcentrationSensor",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.OZONE,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -801,7 +802,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="HepaFilterCondition",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="hepa_filter_condition",
),
@@ -812,7 +813,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="ActivatedCarbonFilterCondition",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="activated_carbon_filter_condition",
),
@@ -1296,7 +1297,7 @@ DISCOVERY_SCHEMAS = [
entity_description=MatterSensorEntityDescription(
key="ThermostatPIHeatingDemand",
translation_key="pi_heating_demand",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterSensor,
@@ -1378,7 +1379,7 @@ DISCOVERY_SCHEMAS = [
entity_registry_enabled_default=False,
translation_key="window_covering_target_position",
device_to_ha=lambda x: round((10000 - x) / 100),
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
entity_class=MatterSensor,
required_attributes=(
@@ -1463,7 +1464,7 @@ DISCOVERY_SCHEMAS = [
entity_description=MatterSensorEntityDescription(
key="EnergyEvseStateOfCharge",
translation_key="evse_soc",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -1487,7 +1488,7 @@ DISCOVERY_SCHEMAS = [
entity_description=MatterSensorEntityDescription(
key="WaterHeaterManagementTankPercentage",
translation_key="tank_percentage",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
@@ -31,9 +31,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
}
)
+9 -3
View File
@@ -10,7 +10,13 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import LIGHT_LUX, UnitOfPower, UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfPower,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -36,14 +42,14 @@ SENSOR_TYPES = {
14: 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="carbon_dioxide",
suggested_display_precision=1,
),
16: 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,
),
@@ -0,0 +1,344 @@
"""Support for Microsoft face recognition."""
import asyncio
from collections.abc import Coroutine
import json
import logging
from typing import Any, override
import aiohttp
from aiohttp.hdrs import CONTENT_TYPE
import voluptuous as vol
from homeassistant.components import camera
from homeassistant.const import ATTR_NAME, CONF_API_KEY, CONF_TIMEOUT, CONTENT_TYPE_JSON
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from homeassistant.util.hass_dict import HassKey
_LOGGER = logging.getLogger(__name__)
ATTR_CAMERA_ENTITY = "camera_entity"
ATTR_GROUP = "group"
ATTR_PERSON = "person"
CONF_AZURE_REGION = "azure_region"
DEFAULT_TIMEOUT = 10
DOMAIN = "microsoft_face"
DATA_MICROSOFT_FACE: HassKey[MicrosoftFace] = HassKey(DOMAIN)
FACE_API_URL = "api.cognitive.microsoft.com/face/v1.0/{0}"
SERVICE_CREATE_GROUP = "create_group"
SERVICE_CREATE_PERSON = "create_person"
SERVICE_DELETE_GROUP = "delete_group"
SERVICE_DELETE_PERSON = "delete_person"
SERVICE_FACE_PERSON = "face_person"
SERVICE_TRAIN_GROUP = "train_group"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_AZURE_REGION, default="westus"): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}
)
},
extra=vol.ALLOW_EXTRA,
)
SCHEMA_GROUP_SERVICE = vol.Schema({vol.Required(ATTR_NAME): cv.string})
SCHEMA_PERSON_SERVICE = SCHEMA_GROUP_SERVICE.extend(
{vol.Required(ATTR_GROUP): cv.slugify}
)
SCHEMA_FACE_SERVICE = vol.Schema(
{
vol.Required(ATTR_PERSON): cv.string,
vol.Required(ATTR_GROUP): cv.slugify,
vol.Required(ATTR_CAMERA_ENTITY): cv.entity_id,
}
)
SCHEMA_TRAIN_SERVICE = vol.Schema({vol.Required(ATTR_GROUP): cv.slugify})
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Microsoft Face."""
component = EntityComponent[MicrosoftFaceGroupEntity](
logging.getLogger(__name__), DOMAIN, hass
)
entities: dict[str, MicrosoftFaceGroupEntity] = {}
domain_config: dict[str, Any] = config[DOMAIN]
azure_region: str = domain_config[CONF_AZURE_REGION]
api_key: str = domain_config[CONF_API_KEY]
timeout: int = domain_config[CONF_TIMEOUT]
face = MicrosoftFace(
hass,
azure_region,
api_key,
timeout,
component,
entities,
)
try:
# read exists group/person from cloud and create entities
await face.update_store()
except HomeAssistantError as err:
_LOGGER.error("Can't load data from face api: %s", err)
return False
hass.data[DATA_MICROSOFT_FACE] = face
async def async_create_group(service: ServiceCall) -> None:
"""Create a new person group."""
name = service.data[ATTR_NAME]
g_id = slugify(name)
try:
await face.call_api("put", f"persongroups/{g_id}", {"name": name})
face.store[g_id] = {}
old_entity = entities.pop(g_id, None)
if old_entity:
await component.async_remove_entity(old_entity.entity_id)
entities[g_id] = MicrosoftFaceGroupEntity(face, g_id, name)
await component.async_add_entities([entities[g_id]])
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't create group '%s' with error: %s", g_id, err)
hass.services.async_register(
DOMAIN, SERVICE_CREATE_GROUP, async_create_group, schema=SCHEMA_GROUP_SERVICE
)
async def async_delete_group(service: ServiceCall) -> None:
"""Delete a person group."""
g_id = slugify(service.data[ATTR_NAME])
try:
await face.call_api("delete", f"persongroups/{g_id}")
face.store.pop(g_id)
entity = entities.pop(g_id)
await component.async_remove_entity(entity.entity_id)
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't delete group '%s' with error: %s", g_id, err)
hass.services.async_register(
DOMAIN, SERVICE_DELETE_GROUP, async_delete_group, schema=SCHEMA_GROUP_SERVICE
)
async def async_train_group(service: ServiceCall) -> None:
"""Train a person group."""
g_id = service.data[ATTR_GROUP]
try:
await face.call_api("post", f"persongroups/{g_id}/train")
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't train group '%s' with error: %s", g_id, err)
hass.services.async_register(
DOMAIN, SERVICE_TRAIN_GROUP, async_train_group, schema=SCHEMA_TRAIN_SERVICE
)
async def async_create_person(service: ServiceCall) -> None:
"""Create a person in a group."""
name = service.data[ATTR_NAME]
g_id = service.data[ATTR_GROUP]
try:
user_data = await face.call_api(
"post", f"persongroups/{g_id}/persons", {"name": name}
)
face.store[g_id][name] = user_data["personId"]
entities[g_id].async_write_ha_state()
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't create person '%s' with error: %s", name, err)
hass.services.async_register(
DOMAIN, SERVICE_CREATE_PERSON, async_create_person, schema=SCHEMA_PERSON_SERVICE
)
async def async_delete_person(service: ServiceCall) -> None:
"""Delete a person in a group."""
name = service.data[ATTR_NAME]
g_id = service.data[ATTR_GROUP]
p_id = face.store[g_id].get(name)
try:
await face.call_api("delete", f"persongroups/{g_id}/persons/{p_id}")
face.store[g_id].pop(name)
entities[g_id].async_write_ha_state()
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't delete person '%s' with error: %s", p_id, err)
hass.services.async_register(
DOMAIN, SERVICE_DELETE_PERSON, async_delete_person, schema=SCHEMA_PERSON_SERVICE
)
async def async_face_person(service: ServiceCall) -> None:
"""Add a new face picture to a person."""
g_id = service.data[ATTR_GROUP]
p_id = face.store[g_id].get(service.data[ATTR_PERSON])
camera_entity = service.data[ATTR_CAMERA_ENTITY]
try:
image = await camera.async_get_image(hass, camera_entity)
await face.call_api(
"post",
f"persongroups/{g_id}/persons/{p_id}/persistedFaces",
image.content,
binary=True,
)
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error(
"Can't add an image of a person '%s' with error: %s", p_id, err
)
hass.services.async_register(
DOMAIN, SERVICE_FACE_PERSON, async_face_person, schema=SCHEMA_FACE_SERVICE
)
return True
class MicrosoftFaceGroupEntity(Entity):
"""Person-Group state/data Entity."""
_attr_should_poll = False
def __init__(self, api: MicrosoftFace, g_id: str, name: str) -> None:
"""Initialize person/group entity."""
self.entity_id = f"{DOMAIN}.{g_id}"
self._api = api
self._id = g_id
self._attr_name = name
@property
@override
def state(self) -> int:
"""Return the state of the entity."""
return len(self._api.store[self._id])
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return device specific state attributes."""
return dict(self._api.store[self._id])
class MicrosoftFace:
"""Microsoft Face api for Home Assistant."""
def __init__(
self,
hass: HomeAssistant,
server_loc: str,
api_key: str,
timeout: int,
component: EntityComponent[MicrosoftFaceGroupEntity],
entities: dict[str, MicrosoftFaceGroupEntity],
) -> None:
"""Initialize Microsoft Face api."""
self.hass = hass
self.websession = async_get_clientsession(hass)
self.timeout = timeout
self._api_key = api_key
self._server_url = f"https://{server_loc}.{FACE_API_URL}"
self._store: dict[str, dict[str, Any]] = {}
self._component = component
self._entities = entities
@property
def store(self) -> dict[str, dict[str, Any]]:
"""Store group/person data and IDs."""
return self._store
async def update_store(self) -> None:
"""Load all group/person data into local store."""
groups = await self.call_api("get", "persongroups")
remove_tasks: list[Coroutine[Any, Any, None]] = []
new_entities = []
for group in groups:
g_id = group["personGroupId"]
self._store[g_id] = {}
old_entity = self._entities.pop(g_id, None)
if old_entity:
remove_tasks.append(
self._component.async_remove_entity(old_entity.entity_id)
)
self._entities[g_id] = MicrosoftFaceGroupEntity(self, g_id, group["name"])
new_entities.append(self._entities[g_id])
persons = await self.call_api("get", f"persongroups/{g_id}/persons")
for person in persons:
self._store[g_id][person["name"]] = person["personId"]
if remove_tasks:
await asyncio.gather(*remove_tasks)
await self._component.async_add_entities(new_entities)
async def call_api(self, method, function, data=None, binary=False, params=None):
"""Make an api call."""
headers = {"Ocp-Apim-Subscription-Key": self._api_key}
url = self._server_url.format(function)
payload = None
if binary:
headers[CONTENT_TYPE] = "application/octet-stream"
payload = data
else:
headers[CONTENT_TYPE] = CONTENT_TYPE_JSON
if data is not None:
payload = json.dumps(data).encode()
else:
payload = None
try:
async with asyncio.timeout(self.timeout):
response = await self.websession.request(
method, url, data=payload, headers=headers, params=params
)
answer = await response.json()
_LOGGER.debug("Read from microsoft face api: %s", answer)
if response.status < 300:
return answer
_LOGGER.warning(
"Error %d microsoft face api %s", response.status, response.url
)
raise HomeAssistantError(answer["error"]["message"])
except aiohttp.ClientError:
_LOGGER.warning("Can't connect to microsoft face api")
except TimeoutError:
_LOGGER.warning("Timeout from microsoft face api %s", response.url)
raise HomeAssistantError("Network error on microsoft face api.")
@@ -0,0 +1,22 @@
{
"services": {
"create_group": {
"service": "mdi:account-multiple-plus"
},
"create_person": {
"service": "mdi:account-plus"
},
"delete_group": {
"service": "mdi:account-multiple-remove"
},
"delete_person": {
"service": "mdi:account-remove"
},
"face_person": {
"service": "mdi:face-man"
},
"train_group": {
"service": "mdi:account-multiple-check"
}
}
}
@@ -0,0 +1,9 @@
{
"domain": "microsoft_face",
"name": "Microsoft Face",
"codeowners": [],
"dependencies": ["camera"],
"documentation": "https://www.home-assistant.io/integrations/microsoft_face",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
@@ -0,0 +1,62 @@
create_group:
fields:
name:
required: true
example: family
selector:
text:
create_person:
fields:
group:
required: true
example: family
selector:
text:
name:
required: true
example: Hans
selector:
text:
delete_group:
fields:
name:
required: true
example: family
selector:
text:
delete_person:
fields:
group:
required: true
example: family
selector:
text:
name:
required: true
example: Hans
selector:
text:
face_person:
fields:
camera_entity:
required: true
example: camera.door
selector:
text:
group:
required: true
example: family
selector:
text:
person:
required: true
example: Hans
selector:
text:
train_group:
fields:
group:
required: true
example: family
selector:
text:
@@ -0,0 +1,80 @@
{
"services": {
"create_group": {
"description": "Creates a new person group.",
"fields": {
"name": {
"description": "Name of the group.",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Create group"
},
"create_person": {
"description": "Creates a new person in the group.",
"fields": {
"group": {
"description": "Name of the group.",
"name": "Group"
},
"name": {
"description": "Name of the person.",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Create person"
},
"delete_group": {
"description": "Deletes a new person group.",
"fields": {
"name": {
"description": "Name of the group.",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Delete group"
},
"delete_person": {
"description": "Deletes a person in the group.",
"fields": {
"group": {
"description": "Name of the group.",
"name": "Group"
},
"name": {
"description": "[%key:component::microsoft_face::services::create_person::fields::name::description%]",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Delete person"
},
"face_person": {
"description": "Adds a new picture to a person.",
"fields": {
"camera_entity": {
"description": "Camera to take a picture.",
"name": "Camera entity"
},
"group": {
"description": "Name of the group.",
"name": "Group"
},
"person": {
"description": "[%key:component::microsoft_face::services::create_person::fields::name::description%]",
"name": "Person"
}
},
"name": "Face person"
},
"train_group": {
"description": "Trains a person group.",
"fields": {
"group": {
"description": "Name of the group.",
"name": "Group"
}
},
"name": "Train group"
}
}
}
@@ -0,0 +1 @@
"""The microsoft_face_detect component."""
@@ -0,0 +1,125 @@
"""Component that will help set the Microsoft face detect processing."""
import logging
from typing import TYPE_CHECKING, override
import voluptuous as vol
from homeassistant.components.image_processing import (
ATTR_AGE,
ATTR_GENDER,
ATTR_GLASSES,
PLATFORM_SCHEMA as IMAGE_PROCESSING_PLATFORM_SCHEMA,
FaceInformation,
ImageProcessingFaceEntity,
)
from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE, MicrosoftFace
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE
from homeassistant.core import HomeAssistant, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
SUPPORTED_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER, ATTR_GLASSES]
CONF_ATTRIBUTES = "attributes"
DEFAULT_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER]
def validate_attributes(list_attributes):
"""Validate face attributes."""
for attr in list_attributes:
if attr not in SUPPORTED_ATTRIBUTES:
raise vol.Invalid(f"Invalid attribute {attr}")
return list_attributes
PLATFORM_SCHEMA = IMAGE_PROCESSING_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_ATTRIBUTES, default=DEFAULT_ATTRIBUTES): vol.All(
cv.ensure_list, validate_attributes
)
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Microsoft Face detection platform."""
api = hass.data[DATA_MICROSOFT_FACE]
attributes: list[str] = config[CONF_ATTRIBUTES]
source: list[dict[str, str]] = config[CONF_SOURCE]
async_add_entities(
MicrosoftFaceDetectEntity(
camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME)
)
for camera in source
)
class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
"""Microsoft Face API entity for identify."""
def __init__(
self,
camera_entity: str,
api: MicrosoftFace,
attributes: list[str],
name: str | None,
) -> None:
"""Initialize Microsoft Face."""
super().__init__()
self._api = api
self._attr_camera_entity = camera_entity
self._attributes = attributes
if name:
self._attr_name = name
else:
self._attr_name = f"MicrosoftFace {split_entity_id(camera_entity)[1]}"
@override
async def async_process_image(self, image: bytes) -> None:
"""Process image.
This method is a coroutine.
"""
face_data = None
try:
face_data = await self._api.call_api(
"post",
"detect",
image,
binary=True,
params={"returnFaceAttributes": ",".join(self._attributes)},
)
except HomeAssistantError as err:
_LOGGER.error("Can't process image on microsoft face: %s", err)
return
if not face_data:
face_data = []
faces: list[FaceInformation] = []
for face in face_data:
face_attr = FaceInformation()
for attr in self._attributes:
if TYPE_CHECKING:
assert attr in SUPPORTED_ATTRIBUTES
if attr in face["faceAttributes"]:
face_attr[attr] = face["faceAttributes"][attr] # type: ignore[literal-required]
if face_attr:
faces.append(face_attr)
self.async_process_faces(faces, len(face_data))
@@ -0,0 +1,9 @@
{
"domain": "microsoft_face_detect",
"name": "Microsoft Face Detect",
"codeowners": [],
"dependencies": ["microsoft_face"],
"documentation": "https://www.home-assistant.io/integrations/microsoft_face_detect",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
@@ -0,0 +1 @@
"""The microsoft_face_identify component."""
@@ -0,0 +1,121 @@
"""Component that will help set the Microsoft face for verify processing."""
import logging
from typing import override
import voluptuous as vol
from homeassistant.components.image_processing import (
ATTR_CONFIDENCE,
CONF_CONFIDENCE,
PLATFORM_SCHEMA as IMAGE_PROCESSING_PLATFORM_SCHEMA,
FaceInformation,
ImageProcessingFaceEntity,
)
from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE, MicrosoftFace
from homeassistant.const import ATTR_NAME, CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE
from homeassistant.core import HomeAssistant, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
CONF_GROUP = "group"
PLATFORM_SCHEMA = IMAGE_PROCESSING_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_GROUP): cv.slugify}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Microsoft Face identify platform."""
api = hass.data[DATA_MICROSOFT_FACE]
face_group: str = config[CONF_GROUP]
confidence: float = config[CONF_CONFIDENCE]
source: list[dict[str, str]] = config[CONF_SOURCE]
async_add_entities(
MicrosoftFaceIdentifyEntity(
camera[CONF_ENTITY_ID],
api,
face_group,
confidence,
camera.get(CONF_NAME),
)
for camera in source
)
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
"""Representation of the Microsoft Face API entity for identify."""
def __init__(
self,
camera_entity: str,
api: MicrosoftFace,
face_group: str,
confidence: float,
name: str | None,
) -> None:
"""Initialize the Microsoft Face API."""
super().__init__()
self._api = api
self._attr_camera_entity = camera_entity
self._attr_confidence = confidence
self._face_group = face_group
if name:
self._attr_name = name
else:
self._attr_name = f"MicrosoftFace {split_entity_id(camera_entity)[1]}"
@override
async def async_process_image(self, image: bytes) -> None:
"""Process image.
This method is a coroutine.
"""
detect = []
try:
face_data = await self._api.call_api("post", "detect", image, binary=True)
if face_data:
face_ids = [data["faceId"] for data in face_data]
detect = await self._api.call_api(
"post",
"identify",
{"faceIds": face_ids, "personGroupId": self._face_group},
)
except HomeAssistantError as err:
_LOGGER.error("Can't process image on Microsoft face: %s", err)
return
# Parse data
known_faces: list[FaceInformation] = []
total = 0
for face in detect:
total += 1
if not face["candidates"]:
continue
data = face["candidates"][0]
name = ""
for s_name, s_id in self._api.store[self._face_group].items():
if data["personId"] == s_id:
name = s_name
break
known_faces.append(
{ATTR_NAME: name, ATTR_CONFIDENCE: data["confidence"] * 100}
)
self.async_process_faces(known_faces, total)
@@ -0,0 +1,9 @@
{
"domain": "microsoft_face_identify",
"name": "Microsoft Face Identify",
"codeowners": [],
"dependencies": ["microsoft_face"],
"documentation": "https://www.home-assistant.io/integrations/microsoft_face_identify",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}

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