Compare commits

..

3 Commits

Author SHA1 Message Date
Paul Bottein 6860e0f3b9 Remove comment 2026-06-25 13:02:39 +02:00
Paul Bottein d3be0cc852 Simplify live attribute handling and use event enum 2026-06-25 12:49:01 +02:00
Paul Bottein c830c05cbd Expose selected state attributes in the logbook 2026-06-25 12:15:44 +02:00
96 changed files with 1209 additions and 2016 deletions
-1
View File
@@ -95,7 +95,6 @@ components: &components
- homeassistant/components/input_select/**
- homeassistant/components/input_text/**
- homeassistant/components/labs/**
- homeassistant/components/llm/**
- homeassistant/components/logbook/**
- homeassistant/components/logger/**
- homeassistant/components/lovelace/**
@@ -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: |
+129 -58
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"}]}
# ___ _ _
# / _ \ | | (_)
# | |_| | __ _ ___ _ __ | |_ _ ___
@@ -31,12 +31,12 @@
# - GITHUB_TOKEN
#
# Custom actions used:
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - 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 }}
@@ -155,7 +155,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Checkout .github and .agents folders
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
@@ -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 }}
@@ -403,7 +404,7 @@ jobs:
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -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 }}
@@ -1234,7 +1236,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# --- Threat Detection ---
@@ -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
@@ -1027,8 +1027,6 @@ CLAUDE.md @home-assistant/core
/tests/components/litterrobot/ @natekspencer @tkdrob
/homeassistant/components/livisi/ @StefanIacobLivisi @planbnet
/tests/components/livisi/ @StefanIacobLivisi @planbnet
/homeassistant/components/llm/ @home-assistant/core
/tests/components/llm/ @home-assistant/core
/homeassistant/components/local_calendar/ @allenporter
/tests/components/local_calendar/ @allenporter
/homeassistant/components/local_ip/ @issacg
+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,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==14.1.6"]
"requirements": ["aioamazondevices==14.1.3"]
}
@@ -28,7 +28,7 @@ async def async_update_unique_id(
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{old_key}"
if entity_id := entity_registry.async_get_entity_id(
platform, DOMAIN, unique_id
DOMAIN, platform, unique_id
):
_LOGGER.debug("Updating unique_id for %s", entity_id)
new_unique_id = unique_id.replace(old_key, new_key)
@@ -48,7 +48,7 @@ async def async_remove_entity_from_virtual_group(
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{key}"
entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id)
entity_id = entity_registry.async_get_entity_id(DOMAIN, platform, unique_id)
is_group = coordinator.data[serial_num].device_family == SPEAKER_GROUP_FAMILY
if entity_id and is_group:
entity_registry.async_remove(entity_id)
@@ -70,7 +70,7 @@ async def async_remove_unsupported_notification_sensors(
):
unique_id = f"{serial_num}-{notification_key}"
entity_id = entity_registry.async_get_entity_id(
SENSOR_DOMAIN, DOMAIN, unique_id=unique_id
DOMAIN, SENSOR_DOMAIN, unique_id=unique_id
)
is_unsupported = not coordinator.data[serial_num].notifications_supported
+12 -29
View File
@@ -731,32 +731,17 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
trace_element = TraceElement(variables, trigger_path)
trace_append_element(trace_element)
if not skip_condition and self._condition is not None:
try:
conditions_pass = self._condition.async_check(variables=variables)
except (vol.Invalid, HomeAssistantError) as err:
self._logger.error(
"Error while checking conditions of automation %s: %s",
self.entity_id,
err,
)
automation_trace.set_error(err)
return None
except Exception as err:
self._logger.exception(
"Unexpected error while checking conditions of automation %s",
self.entity_id,
)
automation_trace.set_error(err)
return None
if not conditions_pass:
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
trace_get(clear=False),
)
script_execution_set("failed_conditions")
return None
if (
not skip_condition
and self._condition is not None
and not self._condition.async_check(variables=variables)
):
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
trace_get(clear=False),
)
script_execution_set("failed_conditions")
return None
self.async_set_context(trigger_context)
event_data = {
@@ -809,9 +794,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
)
automation_trace.set_error(err)
except Exception as err:
self._logger.exception(
"Unexpected error while executing automation %s", self.entity_id
)
self._logger.exception("While executing automation %s", self.entity_id)
automation_trace.set_error(err)
return None
@@ -805,10 +805,6 @@ class DefaultAgent(ConversationEntity):
else:
num_unmatched_entities += 1
# Literal text matched is the dominant signal
same_text_matched = (maybe_result is not None) and (
result.text_chunks_matched == maybe_result.text_chunks_matched
)
if (
(maybe_result is None) # first result
or (
@@ -817,25 +813,22 @@ class DefaultAgent(ConversationEntity):
)
or (
# More entities matched
same_text_matched
and (num_matched_entities > best_num_matched_entities)
num_matched_entities > best_num_matched_entities
)
or (
# Fewer unmatched entities
same_text_matched
and (num_matched_entities == best_num_matched_entities)
(num_matched_entities == best_num_matched_entities)
and (num_unmatched_entities < best_num_unmatched_entities)
)
or (
# Prefer unmatched ranges
same_text_matched
and (num_matched_entities == best_num_matched_entities)
(num_matched_entities == best_num_matched_entities)
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges > best_num_unmatched_ranges)
)
or (
# Prefer match failures with entities
same_text_matched
(result.text_chunks_matched == maybe_result.text_chunks_matched)
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges == best_num_unmatched_ranges)
and (
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.24"]
"requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.1"]
}
@@ -17,7 +17,7 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==45.5.2",
"aioesphomeapi==45.3.1",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==3.9.4"
],
@@ -52,7 +52,9 @@ __all__ = [
"DoorbellEventType",
"EventDeviceClass",
"EventEntity",
"EventEntityCapabilityAttribute",
"EventEntityDescription",
"EventEntityStateAttribute",
]
# mypy: disallow-any-generics
@@ -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"]
}
@@ -13,7 +13,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import EntityCategory, UnitOfRatio, UnitOfVolume
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfVolume
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util, slugify
@@ -67,7 +67,7 @@ BSH_PROGRAM_SENSORS = (
),
HomeConnectSensorEntityDescription(
key=EventKey.BSH_COMMON_OPTION_PROGRAM_PROGRESS,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key="program_progress",
appliance_types=APPLIANCES_WITH_PROGRAMS,
),
@@ -158,7 +158,6 @@ SENSORS = (
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_BATTERY_LEVEL,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
),
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_VIDEO_CAMERA_STATE,
@@ -26,17 +26,18 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
Platform,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfTemperature,
)
@@ -253,7 +254,7 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
name="Current Humidity",
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
# This sensor is only for humidity characteristics that are not part
# of a humidity sensor service.
probe=(lambda char: char.service.type != ServicesTypes.HUMIDITY_SENSOR),
@@ -269,42 +270,42 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
name="PM2.5 Density",
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,
),
CharacteristicsTypes.DENSITY_PM10: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.DENSITY_PM10,
name="PM10 Density",
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CharacteristicsTypes.DENSITY_OZONE: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.DENSITY_OZONE,
name="Ozone Density",
device_class=SensorDeviceClass.OZONE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CharacteristicsTypes.DENSITY_NO2: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.DENSITY_NO2,
name="Nitrogen Dioxide Density",
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CharacteristicsTypes.DENSITY_SO2: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.DENSITY_SO2,
name="Sulphur Dioxide Density",
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CharacteristicsTypes.DENSITY_VOC: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.DENSITY_VOC,
name="Volatile Organic Compound Density",
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,
),
CharacteristicsTypes.THREAD_NODE_CAPABILITIES: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.THREAD_NODE_CAPABILITIES,
@@ -362,13 +363,13 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
key=CharacteristicsTypes.FILTER_LIFE_LEVEL,
name="Filter lifetime",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
CharacteristicsTypes.WATER_LEVEL: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.WATER_LEVEL,
name="Water level",
translation_key="water_level",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
CharacteristicsTypes.VENDOR_EVE_THERMO_VALVE_POSITION: (
@@ -378,7 +379,7 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
translation_key="valve_position",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
)
),
}
@@ -408,7 +409,7 @@ class HomeKitHumiditySensor(HomeKitSensor):
"""Representation of a Homekit humidity sensor."""
_attr_device_class = SensorDeviceClass.HUMIDITY
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
@override
def get_characteristic_types(self) -> list[str]:
@@ -480,7 +481,7 @@ class HomeKitCarbonDioxideSensor(HomeKitSensor):
"""Representation of a Homekit Carbon Dioxide sensor."""
_attr_device_class = SensorDeviceClass.CO2
_attr_native_unit_of_measurement = UnitOfRatio.PARTS_PER_MILLION
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
@override
def get_characteristic_types(self) -> list[str]:
@@ -504,7 +505,7 @@ class HomeKitBatterySensor(HomeKitSensor):
"""Representation of a Homekit battery sensor."""
_attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_entity_category = EntityCategory.DIAGNOSTIC
@override
@@ -50,13 +50,14 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_GRAMS_PER_CUBIC_METER,
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
DEGREE,
LIGHT_LUX,
UnitOfDensity,
PERCENTAGE,
UnitOfEnergy,
UnitOfPower,
UnitOfPrecipitationDepth,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
@@ -83,7 +84,7 @@ SMOKE_DETECTOR_SENSORS: tuple[HmipSmokeDetectorSensorDescription, ...] = (
HmipSmokeDetectorSensorDescription(
key="dirt_level",
translation_key="smoke_detector_dirt_level",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
channel_field="dirtLevel",
@@ -531,7 +532,7 @@ class HomematicipFloorTerminalBlockMechanicChannelValve(
):
"""Representation of the HomematicIP floor terminal block."""
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(
@@ -580,7 +581,7 @@ class HomematicipAccesspointDutyCycle(HomematicipGenericEntity, SensorEntity):
"""Representation of then HomeMaticIP access point."""
_attr_icon = "mdi:access-point-network"
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
@@ -599,7 +600,7 @@ class HomematicipAccesspointDutyCycle(HomematicipGenericEntity, SensorEntity):
class HomematicipHeatingThermostat(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP heating thermostat."""
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize heating thermostat device."""
@@ -628,7 +629,7 @@ class HomematicipHumiditySensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP humidity sensor."""
_attr_device_class = SensorDeviceClass.HUMIDITY
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
@@ -679,9 +680,9 @@ class HomematicipAbsoluteHumiditySensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP absolute humidity sensor."""
_attr_device_class = SensorDeviceClass.ABSOLUTE_HUMIDITY
_attr_native_unit_of_measurement = UnitOfDensity.GRAMS_PER_CUBIC_METER
_attr_native_unit_of_measurement = CONCENTRATION_GRAMS_PER_CUBIC_METER
_attr_suggested_display_precision = 1
_attr_suggested_unit_of_measurement = UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER
_attr_suggested_unit_of_measurement = CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
@@ -1142,7 +1143,7 @@ class HomematicipSoilMoistureSensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP soil moisture sensor."""
_attr_device_class = SensorDeviceClass.MOISTURE
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
-79
View File
@@ -1,79 +0,0 @@
"""The LLM integration.
Owns the LLM tools platform: integrations contribute tools to the LLM APIs
through an ``<integration>/llm.py`` platform with an ``async_get_tools`` hook.
The platforms are loaded lazily and queried per request. The framework
(``Tool``, the APIs) lives in ``homeassistant.helpers.llm``.
"""
from dataclasses import dataclass
import logging
from typing import Protocol
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.integration_platform import LazyIntegrationPlatforms
from homeassistant.helpers.llm import LLMContext, Tool
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
DATA_PLATFORMS: HassKey[LazyIntegrationPlatforms[LLMToolsPlatformProtocol]] = HassKey(
"llm_platforms"
)
@dataclass(slots=True)
class LLMTools:
"""Tools and an optional prompt fragment contributed by a platform."""
tools: list[Tool]
prompt: str | None = None
class LLMToolsPlatformProtocol(Protocol):
"""Define the format that LLM tools platforms can have."""
@callback
def async_get_tools(self, hass: HomeAssistant, llm_context: LLMContext) -> LLMTools:
"""Return the integration's LLM tools for the given context."""
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the LLM integration."""
hass.data[DATA_PLATFORMS] = LazyIntegrationPlatforms(
hass, DOMAIN, _process_llm_tools_platform
)
return True
@callback
def _process_llm_tools_platform(
hass: HomeAssistant, domain: str, platform: LLMToolsPlatformProtocol
) -> LLMToolsPlatformProtocol:
"""Process an integration's LLM tools platform."""
return platform
async def async_get_tools(hass: HomeAssistant, llm_context: LLMContext) -> LLMTools:
"""Return the tools and merged prompt from all integration platforms."""
platforms = await hass.data[DATA_PLATFORMS].async_get_platforms()
tools: list[Tool] = []
prompts: list[str] = []
# Sort by domain so the tool and prompt order is independent of load order.
for domain, platform in sorted(platforms.items()):
try:
result = platform.async_get_tools(hass, llm_context)
except Exception:
_LOGGER.exception("Error getting tools from LLM platform %s", domain)
continue
tools.extend(result.tools)
if result.prompt:
prompts.append(result.prompt)
return LLMTools(tools=tools, prompt="\n".join(prompts) if prompts else None)
-3
View File
@@ -1,3 +0,0 @@
"""Constants for the LLM integration."""
DOMAIN = "llm"
@@ -1,9 +0,0 @@
{
"domain": "llm",
"name": "LLM",
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/llm",
"integration_type": "system",
"iot_class": "calculated",
"quality_scale": "internal"
}
@@ -1,6 +1,7 @@
"""Event parser and human readable log generator."""
from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED
from homeassistant.components.event import EventEntityStateAttribute
from homeassistant.components.script import EVENT_SCRIPT_STARTED
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import EVENT_CALL_SERVICE, EVENT_LOGBOOK_ENTRY
@@ -39,8 +40,12 @@ LOGBOOK_ENTRY_SOURCE = "source"
LOGBOOK_ENTRY_MESSAGE = "message"
LOGBOOK_ENTRY_NAME = "name"
LOGBOOK_ENTRY_STATE = "state"
LOGBOOK_ENTRY_ATTRIBUTES = "attributes"
LOGBOOK_ENTRY_WHEN = "when"
# State attributes surfaced in logbook entries; extend as needed.
EXPOSED_STATE_ATTRIBUTES = {EventEntityStateAttribute.EVENT_TYPE}
# Automation events that can affect an entity_id or device_id
AUTOMATION_EVENTS = {EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED}
+7 -3
View File
@@ -106,10 +106,11 @@ CONTEXT_PARENT_ID_BIN_POS: Final = 6
STATE_POS: Final = 7
ENTITY_ID_POS: Final = 8
ICON_POS: Final = 9
CONTEXT_ONLY_POS: Final = 10
ATTRIBUTES_POS: Final = 10
CONTEXT_ONLY_POS: Final = 11
# - For EventAsRow, additional fields are:
DATA_POS: Final = 11
CONTEXT_POS: Final = 12
DATA_POS: Final = 12
CONTEXT_POS: Final = 13
@final # Final to allow direct checking of the type instead of using isinstance
@@ -129,6 +130,7 @@ class EventAsRow(NamedTuple):
state: str | None
entity_id: str | None
icon: str | None
attributes: Mapping[str, Any] | None
context_only: bool | None
# Additional fields for EventAsRow
@@ -152,6 +154,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
state=None,
entity_id=None,
icon=None,
attributes=None,
context_only=None,
data=event.data,
context=context,
@@ -175,6 +178,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
state=new_state.state,
entity_id=new_state.entity_id,
icon=new_state.attributes.get(ATTR_ICON),
attributes=new_state.attributes,
context_only=None,
data=event.data,
context=context,
+27 -1
View File
@@ -1,6 +1,6 @@
"""Event parser and human readable log generator."""
from collections.abc import Callable, Collection, Generator, Sequence
from collections.abc import Callable, Collection, Generator, Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime as dt
import logging
@@ -16,6 +16,7 @@ from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.filters import Filters
from homeassistant.components.recorder.models import (
bytes_to_uuid_hex_or_none,
decode_attributes_from_source,
extract_event_type_ids,
extract_metadata_ids,
process_timestamp_to_utc_isoformat,
@@ -53,6 +54,8 @@ from .const import (
CONTEXT_STATE,
CONTEXT_USER_ID,
DOMAIN,
EXPOSED_STATE_ATTRIBUTES,
LOGBOOK_ENTRY_ATTRIBUTES,
LOGBOOK_ENTRY_DOMAIN,
LOGBOOK_ENTRY_ENTITY_ID,
LOGBOOK_ENTRY_ICON,
@@ -64,6 +67,7 @@ from .const import (
)
from .helpers import is_sensor_continuous
from .models import (
ATTRIBUTES_POS,
CONTEXT_ID_BIN_POS,
CONTEXT_ONLY_POS,
CONTEXT_PARENT_ID_BIN_POS,
@@ -273,6 +277,24 @@ class EventProcessor:
)
def _exposed_state_attributes(
row: Row | EventAsRow, attr_cache: dict[str, dict[str, Any]]
) -> dict[str, Any]:
"""Return the allowlisted state attributes for a state change row."""
attributes: Mapping[str, Any] | None
if type(row) is EventAsRow:
attributes = row[ATTRIBUTES_POS]
else:
attributes = decode_attributes_from_source(row[ATTRIBUTES_POS], attr_cache)
if not attributes:
return {}
return {
name: attributes[name]
for name in EXPOSED_STATE_ATTRIBUTES
if name in attributes
}
def _humanify(
hass: HomeAssistant,
rows: Generator[EventAsRow] | Sequence[Row] | Result,
@@ -294,6 +316,8 @@ def _humanify(
get_context = context_augmenter.get_context
context_id_bin: bytes
data: dict[str, Any]
# Decode each shared attribute set only once per run.
attr_cache: dict[str, dict[str, Any]] = {}
context_user_ids = logbook_run.context_user_ids
# Skip the LRU write on one-shot runs — the LogbookRun is discarded.
@@ -337,6 +361,8 @@ def _humanify(
data[LOGBOOK_ENTRY_NAME] = entity_name_cache_get(entity_id)
if icon := row[ICON_POS]:
data[LOGBOOK_ENTRY_ICON] = icon
if exposed := _exposed_state_attributes(row, attr_cache):
data[LOGBOOK_ENTRY_ATTRIBUTES] = exposed
elif event_type in external_events:
domain, describe_event = external_events[event_type]
@@ -14,6 +14,7 @@ from homeassistant.components.recorder.db_schema import (
EVENTS_CONTEXT_ID_BIN_INDEX,
OLD_FORMAT_ATTRS_JSON,
OLD_STATE,
SHARED_ATTR_OR_LEGACY_ATTRIBUTES,
SHARED_ATTRS_JSON,
SHARED_DATA_OR_LEGACY_EVENT_DATA,
STATES_CONTEXT_ID_BIN_INDEX,
@@ -65,12 +66,14 @@ STATE_COLUMNS = (
States.state.label("state"),
StatesMeta.entity_id.label("entity_id"),
ICON_OR_OLD_FORMAT_ICON_JSON,
SHARED_ATTR_OR_LEGACY_ATTRIBUTES,
)
STATE_CONTEXT_ONLY_COLUMNS = (
States.state.label("state"),
StatesMeta.entity_id.label("entity_id"),
literal(value=None, type_=sqlalchemy.String).label("icon"),
literal(value=None, type_=sqlalchemy.String).label("attributes"),
)
EVENT_COLUMNS_FOR_STATE_SELECT = (
@@ -93,6 +96,7 @@ EMPTY_STATE_COLUMNS = (
literal(value=None, type_=sqlalchemy.String).label("state"),
literal(value=None, type_=sqlalchemy.String).label("entity_id"),
literal(value=None, type_=sqlalchemy.String).label("icon"),
literal(value=None, type_=sqlalchemy.String).label("attributes"),
)
+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,
),
}
+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,
@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
@@ -74,7 +73,6 @@ async def async_setup_entry(
class MealieStatisticSensors(MealieEntity, SensorEntity):
"""Defines a Mealie sensor."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
entity_description: MealieStatisticsSensorEntityDescription
coordinator: MealieStatisticsCoordinator
@@ -0,0 +1,21 @@
"""Support for Mycroft AI."""
import voluptuous as vol
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.typing import ConfigType
DOMAIN = "mycroft"
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA
)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Mycroft component."""
hass.data[DOMAIN] = config[DOMAIN][CONF_HOST]
discovery.load_platform(hass, Platform.NOTIFY, DOMAIN, {}, config)
return True
@@ -0,0 +1,11 @@
{
"domain": "mycroft",
"name": "Mycroft",
"codeowners": [],
"disabled": "Dependencies not compatible with the new pip resolver",
"documentation": "https://www.home-assistant.io/integrations/mycroft",
"iot_class": "local_push",
"loggers": ["mycroftapi"],
"quality_scale": "legacy",
"requirements": ["mycroftapi==2.0"]
}
@@ -0,0 +1,42 @@
"""Mycroft AI notification platform."""
import logging
from typing import Any, override
from mycroftapi import MycroftAPI
from homeassistant.components.notify import 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,
) -> MycroftNotificationService:
"""Get the Mycroft notification service."""
return MycroftNotificationService(hass.data[DOMAIN])
class MycroftNotificationService(BaseNotificationService):
"""The Mycroft Notification Service."""
def __init__(self, mycroft_ip: str) -> None:
"""Initialize the service."""
self.mycroft_ip = mycroft_ip
@override
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message mycroft to speak on instance."""
text = message
mycroft = MycroftAPI(self.mycroft_ip)
if mycroft is not None:
mycroft.speak_text(text)
else:
_LOGGER.warning("Could not reach this instance of mycroft")
+1 -3
View File
@@ -365,7 +365,5 @@ async def create_rexel_client(
gateway_id=entry.data[CONF_GATEWAY_ID],
),
session=async_create_clientsession(hass),
settings=OverkizClientSettings(
action_queue=ActionQueueSettings(), default_rts_command_duration=0
),
settings=OverkizClientSettings(action_queue=ActionQueueSettings()),
)
+7 -6
View File
@@ -15,12 +15,13 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS,
EntityCategory,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfTime,
@@ -54,7 +55,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
OverkizSensorDescription(
key=OverkizState.CORE_BATTERY_LEVEL,
name="Battery level",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -328,7 +329,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
native_value=lambda value: round(cast(float, value), 2),
device_class=SensorDeviceClass.HUMIDITY,
# core:MeasuredValueType = core:RelativeValueInPercentage
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
# TemperatureSensor/TemperatureSensor
@@ -368,7 +369,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
key=OverkizState.CORE_CO_CONCENTRATION,
name="CO concentration",
device_class=SensorDeviceClass.CO,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
# AirSensor/CO2Sensor
@@ -376,7 +377,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
key=OverkizState.CORE_CO2_CONCENTRATION,
name="CO2 concentration",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
# SunSensor/SunEnergySensor
@@ -488,7 +489,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
OverkizSensorDescription(
key=OverkizState.CORE_TARGET_CLOSURE,
name="Target closure",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
),
# ThreeWayWindowHandle/WindowHandle
+3 -3
View File
@@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
SensorStateClass,
StateType,
)
from homeassistant.const import UnitOfInformation, UnitOfRatio
from homeassistant.const import PERCENTAGE, UnitOfInformation
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -122,7 +122,7 @@ CONTAINER_SENSORS: tuple[PortainerContainerSensorEntityDescription, ...] = (
and data.stats.memory_stats.usage > 0
else 0.0
),
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
state_class=SensorStateClass.MEASUREMENT,
@@ -151,7 +151,7 @@ CONTAINER_SENSORS: tuple[PortainerContainerSensorEntityDescription, ...] = (
and data.stats.cpu_stats.online_cpus > 0
else 0.0
),
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
state_class=SensorStateClass.MEASUREMENT,
+6 -5
View File
@@ -22,14 +22,15 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfLength,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfSpeed,
UnitOfTemperature,
@@ -125,7 +126,7 @@ _GAUGE_VARIANT_DESCRIPTIONS = {
"AIRQUALITY": SensorEntityDescription(
key="airquality",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"CURRENT": SensorEntityDescription(
@@ -155,7 +156,7 @@ _GAUGE_VARIANT_DESCRIPTIONS = {
"HUMIDITY": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
"LIGHT": SensorEntityDescription(
@@ -352,7 +353,7 @@ class QbusHumiditySensor(QbusEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.HUMIDITY
_attr_name = None
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
@override
@@ -381,7 +382,7 @@ class QbusVentilationSensor(QbusEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.CO2
_attr_name = None
_attr_native_unit_of_measurement = UnitOfRatio.PARTS_PER_MILLION
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_suggested_display_precision = 0
@@ -30,7 +30,6 @@ from homeassistant.util.event_type import EventType
# startup
from . import (
backup, # noqa: F401
entity_options,
entity_registry,
websocket_api,
)
@@ -43,7 +42,6 @@ from .const import ( # noqa: F401
SupportedDialect,
)
from .core import Recorder
from .entity_options import is_entity_recorded # noqa: F401
from .services import async_setup_services
from .tasks import AddRecorderPlatformTask
from .util import get_instance
@@ -127,6 +125,15 @@ CONFIG_SCHEMA = vol.Schema(
)
def is_entity_recorded(hass: HomeAssistant, entity_id: str) -> bool:
"""Check if an entity is being recorded.
Async friendly.
"""
instance = get_instance(hass)
return instance.entity_filter is None or instance.entity_filter(entity_id)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the recorder."""
conf = config[DOMAIN]
@@ -160,7 +167,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
get_instance.cache_clear()
entity_registry.async_setup(hass)
entity_options.async_setup(hass)
instance.async_initialize()
instance.async_register()
instance.start()
@@ -1,68 +0,0 @@
"""Control recorder entity options."""
import dataclasses
from enum import StrEnum
from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from .util import get_instance
def is_entity_recorded(hass: HomeAssistant, entity_id: str) -> bool:
"""Check if an entity is being recorded.
Async friendly.
"""
instance = get_instance(hass)
return instance.entity_filter is None or instance.entity_filter(entity_id)
@callback
def async_setup(hass: HomeAssistant) -> None:
"""Set up the recorder entity options."""
websocket_api.async_register_command(hass, ws_get_entity_options)
class EntityRecordingDisabler(StrEnum):
"""What disabled recording of an entity."""
USER = "user"
@dataclasses.dataclass(frozen=True)
class RecorderEntityOptions:
"""Recorder options for an entity."""
recording_disabled_by: EntityRecordingDisabler | None = None
def to_json(self) -> dict[str, Any]:
"""Return a JSON serializable representation for storage."""
return {
"recording_disabled_by": self.recording_disabled_by,
}
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "recorder/entity_options/get",
vol.Required("entity_id"): cv.strict_entity_id,
}
)
@callback
def ws_get_entity_options(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Get recorder settings for a single entity."""
entity_id: str = msg["entity_id"]
recording_disabled = (
None if is_entity_recorded(hass, entity_id) else EntityRecordingDisabler.USER
)
options = RecorderEntityOptions(recording_disabled_by=recording_disabled)
connection.send_result(msg["id"], options.to_json())
@@ -9,6 +9,7 @@ from .context import (
from .database import DatabaseEngine, DatabaseOptimizer, UnsupportedDialect
from .event import extract_event_type_ids
from .state import LazyState, extract_metadata_ids, row_to_compressed_state
from .state_attributes import decode_attributes_from_source
from .statistics import (
CalendarStatisticPeriod,
FixedStatisticPeriod,
@@ -44,6 +45,7 @@ __all__ = [
"bytes_to_ulid_or_none",
"bytes_to_uuid_hex_or_none",
"datetime_to_timestamp_or_none",
"decode_attributes_from_source",
"extract_event_type_ids",
"extract_metadata_ids",
"process_timestamp",
@@ -1,8 +1,7 @@
"""The Steam integration."""
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.core import HomeAssistant
from .coordinator import SteamConfigEntry, SteamDataUpdateCoordinator
@@ -22,22 +21,3 @@ async def async_setup_entry(hass: HomeAssistant, entry: SteamConfigEntry) -> boo
async def async_unload_entry(hass: HomeAssistant, entry: SteamConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: SteamConfigEntry) -> bool:
"""Migrate old entry."""
if entry.version < 2:
# Migrate entity unique id
@callback
def migrate_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
if entity_entry.unique_id.startswith("sensor.steam_"):
new = entity_entry.unique_id.removeprefix("sensor.steam_") + "_account"
return {"new_unique_id": new}
return None
await er.async_migrate_entries(hass, entry.entry_id, migrate_unique_id)
hass.config_entries.async_update_entry(entry, version=2)
return True
@@ -41,8 +41,6 @@ def validate_input(user_input: dict[str, str]) -> dict[str, str | int]:
class SteamFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Steam."""
VERSION = 2
@staticmethod
@callback
@override
@@ -152,7 +150,7 @@ class SteamOptionsFlowHandler(OptionsFlowWithReload):
for _id in self.options[CONF_ACCOUNTS]:
if _id not in user_input[CONF_ACCOUNTS] and (
entity_id := er.async_get(self.hass).async_get_entity_id(
Platform.SENSOR, DOMAIN, f"{_id}_account"
Platform.SENSOR, DOMAIN, f"sensor.steam_{_id}"
)
):
er.async_get(self.hass).async_remove(entity_id)
@@ -1,6 +1,5 @@
"""Entity classes for the Steam integration."""
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -13,17 +12,9 @@ class SteamEntity(CoordinatorEntity[SteamDataUpdateCoordinator]):
_attr_has_entity_name = True
def __init__(
self,
coordinator: SteamDataUpdateCoordinator,
steamid: str,
description: SensorEntityDescription,
) -> None:
def __init__(self, coordinator: SteamDataUpdateCoordinator) -> None:
"""Initialize a Steam entity."""
super().__init__(coordinator)
self._steamid = steamid
self.entity_description = description
self._attr_unique_id = f"{steamid}_{description.key}"
self._attr_device_info = DeviceInfo(
configuration_url="https://store.steampowered.com",
entry_type=DeviceEntryType.SERVICE,
@@ -80,7 +80,10 @@ class SteamSensorEntity(SteamEntity, SensorEntity):
description: SteamSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator, steamid, description)
super().__init__(coordinator)
self._steamid = steamid
self.entity_description = description
self._attr_unique_id = f"sensor.steam_{steamid}"
self._attr_name = self.entity_description.name_fn(coordinator.data[steamid])
@property
+20 -14
View File
@@ -85,18 +85,34 @@ def sun(
has_sunrise_condition = SUN_EVENT_SUNRISE in (before, after)
has_sunset_condition = SUN_EVENT_SUNSET in (before, after)
after_sunrise = sunrise is not None and today > dt_util.as_local(sunrise).date()
after_sunrise = today > dt_util.as_local(cast(datetime, sunrise)).date()
if after_sunrise and has_sunrise_condition:
tomorrow = today + timedelta(days=1)
sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow)
after_sunset = sunset is not None and today > dt_util.as_local(sunset).date()
after_sunset = today > dt_util.as_local(cast(datetime, sunset)).date()
if after_sunset and has_sunset_condition:
tomorrow = today + timedelta(days=1)
sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow)
# A missing sunrise/sunset means the sun doesn't rise/set on this day, which
# happens in polar regions.
# Special case: before sunrise OR after sunset
# This will handle the very rare case in the polar region when the sun rises/sets
# but does not set/rise.
# However this entire condition does not handle those full days of darkness
# or light, the following should be used instead:
#
# condition:
# condition: state
# entity_id: sun.sun
# state: 'above_horizon' (or 'below_horizon')
#
if before == SUN_EVENT_SUNRISE and after == SUN_EVENT_SUNSET:
wanted_time_before = cast(datetime, sunrise) + before_offset
condition_trace_update_result(wanted_time_before=wanted_time_before)
wanted_time_after = cast(datetime, sunset) + after_offset
condition_trace_update_result(wanted_time_after=wanted_time_after)
return utcnow < wanted_time_before or utcnow > wanted_time_after
if sunrise is None and has_sunrise_condition:
# There is no sunrise today
condition_trace_set_result(False, message="no sunrise today")
@@ -107,16 +123,6 @@ def sun(
condition_trace_set_result(False, message="no sunset today")
return False
# "before: sunrise" combined with "after: sunset" describes the dark period
# around midnight, so it is evaluated as an OR (true before sunrise or after
# sunset) rather than the usual AND of the two bounds.
if before == SUN_EVENT_SUNRISE and after == SUN_EVENT_SUNSET:
wanted_time_before = cast(datetime, sunrise) + before_offset
condition_trace_update_result(wanted_time_before=wanted_time_before)
wanted_time_after = cast(datetime, sunset) + after_offset
condition_trace_update_result(wanted_time_after=wanted_time_after)
return utcnow < wanted_time_before or utcnow > wanted_time_after
if before == SUN_EVENT_SUNRISE:
wanted_time_before = cast(datetime, sunrise) + before_offset
condition_trace_update_result(wanted_time_before=wanted_time_before)
+8 -3
View File
@@ -13,7 +13,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import Platform, UnitOfDensity, UnitOfRatio, UnitOfTime
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
Platform,
UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -57,7 +62,7 @@ SENSOR_DESCRIPTIONS_BATTERY: tuple[TradfriSensorEntityDescription, ...] = (
key="battery_level",
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value=lambda device: cast(int, device.device_info.battery_level),
),
)
@@ -68,7 +73,7 @@ SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = (
key="aqi",
translation_key="aqi",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
value=_get_air_quality,
),
TradfriSensorEntityDescription(
+10 -7
View File
@@ -6,18 +6,21 @@ import logging
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
Platform,
UnitOfConductivity,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumetricFlux,
@@ -1020,7 +1023,7 @@ UNITS = (
},
),
UnitOfMeasurement(
unit=UnitOfRatio.PERCENTAGE,
unit=PERCENTAGE,
aliases={"pct", "percent", "% RH"},
device_classes={
SensorDeviceClass.BATTERY,
@@ -1029,14 +1032,14 @@ UNITS = (
},
),
UnitOfMeasurement(
unit=UnitOfRatio.PARTS_PER_MILLION,
unit=CONCENTRATION_PARTS_PER_MILLION,
device_classes={
SensorDeviceClass.CO,
SensorDeviceClass.CO2,
},
),
UnitOfMeasurement(
unit=UnitOfRatio.PARTS_PER_BILLION,
unit=CONCENTRATION_PARTS_PER_BILLION,
device_classes={
SensorDeviceClass.CO,
SensorDeviceClass.CO2,
@@ -1083,7 +1086,7 @@ UNITS = (
device_classes={SensorDeviceClass.ILLUMINANCE},
),
UnitOfMeasurement(
unit=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
# The μ-char has 2 unicode variants \u00b5 and \u03bc
# The \u03bc variant (Greek Mu char) is recommended
aliases={"ug/m3", "\u03bcg/m3", "\u00b5g/m3", "ug/m³"},
@@ -1100,7 +1103,7 @@ UNITS = (
},
),
UnitOfMeasurement(
unit=UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER,
unit=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
aliases={"mg/m3"},
device_classes={
SensorDeviceClass.NITROGEN_DIOXIDE,
+2 -2
View File
@@ -44,7 +44,7 @@
"iot_class": "cloud_push",
"loggers": ["tuya_sharing"],
"requirements": [
"tuya-device-handlers==0.0.24",
"tuya-device-sharing-sdk==0.2.10"
"tuya-device-handlers==0.0.22",
"tuya-device-sharing-sdk==0.2.8"
]
}
+25 -24
View File
@@ -31,13 +31,14 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
@@ -74,7 +75,7 @@ BATTERY_SENSORS: tuple[TuyaSensorEntityDescription, ...] = (
TuyaSensorEntityDescription(
key=DPCode.BATTERY_PERCENTAGE,
translation_key="battery",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -82,7 +83,7 @@ BATTERY_SENSORS: tuple[TuyaSensorEntityDescription, ...] = (
TuyaSensorEntityDescription(
key=DPCode.BATTERY, # Used by non-standard contact sensor implementations
translation_key="battery",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -180,7 +181,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.CH2O_VALUE,
@@ -198,7 +199,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.PM10,
@@ -214,7 +215,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_monoxide",
device_class=SensorDeviceClass.CO,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
*BATTERY_SENSORS,
),
@@ -301,21 +302,21 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.CO_VALUE,
translation_key="carbon_monoxide",
device_class=SensorDeviceClass.CO,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.CO2_VALUE,
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.EC_CURRENT,
@@ -534,7 +535,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.CH2O_VALUE,
@@ -552,14 +553,14 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.PM10,
translation_key="pm10",
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
*BATTERY_SENSORS,
),
@@ -569,7 +570,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.VOC_VALUE,
@@ -582,7 +583,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.VA_HUMIDITY,
@@ -683,7 +684,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.TEMP,
@@ -708,7 +709,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="concentration_carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.TOTAL_TIME,
@@ -755,7 +756,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
*BATTERY_SENSORS,
),
@@ -807,7 +808,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.CH2O_VALUE,
@@ -831,7 +832,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.HUMIDITY_VALUE,
@@ -844,14 +845,14 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="pm1",
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.PM10,
translation_key="pm10",
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
*BATTERY_SENSORS,
),
@@ -1229,14 +1230,14 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
TuyaSensorEntityDescription(
key=DPCode.PM25_VALUE,
translation_key="pm25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
TuyaSensorEntityDescription(
key=DPCode.CH2O_VALUE,
@@ -9,5 +9,5 @@
"iot_class": "local_push",
"loggers": ["uiprotect"],
"quality_scale": "platinum",
"requirements": ["uiprotect==15.1.0"]
"requirements": ["uiprotect==15.0.0"]
}
+17 -17
View File
@@ -20,16 +20,16 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfEnergy,
UnitOfMass,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
UnitOfVolume,
@@ -80,7 +80,7 @@ VICARE_UNIT_TO_HA_UNIT = {
VICARE_CUBIC_METER: UnitOfVolume.CUBIC_METERS,
VICARE_KW: UnitOfPower.KILO_WATT,
VICARE_KWH: UnitOfEnergy.KILO_WATT_HOUR,
VICARE_PERCENT: UnitOfRatio.PERCENTAGE,
VICARE_PERCENT: PERCENTAGE,
VICARE_W: UnitOfPower.WATT,
VICARE_WH: UnitOfEnergy.WATT_HOUR,
}
@@ -117,7 +117,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
key="outside_humidity",
translation_key="outside_humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_getter=lambda api: api.getOutsideHumidity(),
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
@@ -165,7 +165,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
key="primary_circuit_pump_rotation",
translation_key="primary_circuit_pump_rotation",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_getter=lambda api: api.getPrimaryCircuitPumpRotation(),
unit_getter=lambda api: api.getPrimaryCircuitPumpRotationUnit(),
state_class=SensorStateClass.MEASUREMENT,
@@ -799,7 +799,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
key="ess_state_of_charge",
translation_key="ess_state_of_charge",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
value_getter=lambda api: api.getElectricalEnergySystemSOC(),
@@ -996,7 +996,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
key="room_humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_getter=lambda api: api.getHumidity(),
),
@@ -1122,7 +1122,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
),
ViCareSensorEntityDescription(
key="battery_level",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -1142,7 +1142,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
translation_key="zigbee_signal_strength",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_getter=lambda api: api.getZigbeeSignalStrength(),
entity_registry_enabled_default=False,
),
@@ -1150,7 +1150,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
key="valve_position",
translation_key="valve_position",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_getter=lambda api: api.getValvePosition(),
entity_registry_enabled_default=False,
),
@@ -1177,7 +1177,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
key="supply_humidity",
translation_key="supply_humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_getter=lambda api: api.getSupplyHumidity(),
),
@@ -1229,28 +1229,28 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
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_getter=lambda api: api.getAirborneDustPM1(),
),
ViCareSensorEntityDescription(
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_getter=lambda api: api.getAirborneDustPM2d5(),
),
ViCareSensorEntityDescription(
key="pm04",
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,
value_getter=lambda api: api.getAirborneDustPM4(),
),
ViCareSensorEntityDescription(
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_getter=lambda api: api.getAirborneDustPM10(),
),
@@ -1293,7 +1293,7 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
key="burner_modulation",
translation_key="burner_modulation",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_getter=lambda api: api.getModulation(),
state_class=SensorStateClass.MEASUREMENT,
),
@@ -1312,7 +1312,7 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
ViCareSensorEntityDescription(
key="compressor_modulation",
translation_key="compressor_modulation",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_getter=lambda api: api.getModulation(),
unit_getter=lambda api: api.getModulationUnit(),
state_class=SensorStateClass.MEASUREMENT,
@@ -149,6 +149,7 @@ SELECTOR_TYPES = (
XiaomiMiioSelectDescription(
key=ATTR_DISPLAY_ORIENTATION,
attr_name=ATTR_DISPLAY_ORIENTATION,
name="Display Orientation",
options_map={
"Portrait": "Forward",
"LandscapeLeft": "Left",
@@ -164,6 +165,7 @@ SELECTOR_TYPES = (
XiaomiMiioSelectDescription(
key=ATTR_MODE,
attr_name=ATTR_MODE,
name="Mode",
set_method="set_mode",
set_method_error_message="Setting the mode of the fan failed.",
icon="mdi:fan",
@@ -174,6 +176,7 @@ SELECTOR_TYPES = (
XiaomiMiioSelectDescription(
key=ATTR_LED_BRIGHTNESS,
attr_name=ATTR_LED_BRIGHTNESS,
name="Led Brightness",
set_method="set_led_brightness",
set_method_error_message="Setting the led brightness failed.",
icon="mdi:brightness-6",
@@ -184,6 +187,7 @@ SELECTOR_TYPES = (
XiaomiMiioSelectDescription(
key=ATTR_PTC_LEVEL,
attr_name=ATTR_PTC_LEVEL,
name="Auxiliary Heat Level",
set_method="set_ptc_level",
set_method_error_message="Setting the ptc level failed.",
icon="mdi:fire-circle",
@@ -138,7 +138,6 @@
},
"select": {
"airpurifier_mode": {
"name": "Mode",
"state": {
"auto": "[%key:common::state::auto%]",
"favorite": "Favorite",
@@ -146,7 +145,6 @@
}
},
"display_orientation": {
"name": "Display orientation",
"state": {
"forward": "Forward",
"left": "Left",
@@ -154,7 +152,6 @@
}
},
"led_brightness": {
"name": "LED brightness",
"state": {
"bright": "Bright",
"dim": "Dim",
@@ -162,7 +159,6 @@
}
},
"ptc_level": {
"name": "Auxiliary heat level",
"state": {
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
@@ -4587,6 +4587,12 @@
"config_flow": false,
"iot_class": "cloud_polling"
},
"mycroft": {
"name": "Mycroft",
"integration_type": "hub",
"config_flow": false,
"iot_class": "local_push"
},
"myneomitis": {
"name": "MyNeomitis",
"integration_type": "hub",
+1 -1
View File
@@ -740,7 +740,7 @@ def _get_exposed_entities(
if include_state and (
attributes := {
str(attr_name): (
attr_name: (
str(attr_value)
if isinstance(attr_value, (Enum, Decimal, int))
else attr_value
+2 -2
View File
@@ -39,8 +39,8 @@ habluetooth==6.25.1
hass-nabucasa==2.2.0
hassil==3.8.0
home-assistant-bluetooth==2.0.0
home-assistant-frontend==20260624.1
home-assistant-intents==2026.6.24
home-assistant-frontend==20260624.0
home-assistant-intents==2026.6.1
httpx==0.28.1
ifaddr==0.2.0
Jinja2==3.1.6
+1 -1
View File
@@ -5,7 +5,7 @@ To update, run python3 -m script.hassfest
from typing import Final
FRONTEND_VERSION: Final[str] = "20260624.1"
FRONTEND_VERSION: Final[str] = "20260624.0"
MDI_ICONS: Final[set[str]] = {
"ab-testing",
+1 -1
View File
@@ -27,7 +27,7 @@ ha-ffmpeg==3.2.2
hass-nabucasa==2.2.0
hassil==3.8.0
home-assistant-bluetooth==2.0.0
home-assistant-intents==2026.6.24
home-assistant-intents==2026.6.1
httpx==0.28.1
ifaddr==0.2.0
infrared-protocols==6.3.0
+7 -7
View File
@@ -193,7 +193,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.5
# homeassistant.components.alexa_devices
aioamazondevices==14.1.6
aioamazondevices==14.1.3
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@@ -260,7 +260,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==45.5.2
aioesphomeapi==45.3.1
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -1272,10 +1272,10 @@ hole==0.9.2
holidays==0.99
# homeassistant.components.frontend
home-assistant-frontend==20260624.1
home-assistant-frontend==20260624.0
# homeassistant.components.conversation
home-assistant-intents==2026.6.24
home-assistant-intents==2026.6.1
# homeassistant.components.homekit
homekit-audio-proxy==1.2.1
@@ -3221,10 +3221,10 @@ ttls==1.8.3
ttn_client==1.3.0
# homeassistant.components.tuya
tuya-device-handlers==0.0.24
tuya-device-handlers==0.0.22
# homeassistant.components.tuya
tuya-device-sharing-sdk==0.2.10
tuya-device-sharing-sdk==0.2.8
# homeassistant.components.twentemilieu
twentemilieu==3.0.0
@@ -3245,7 +3245,7 @@ uasiren==0.0.1
uhooapi==1.2.8
# homeassistant.components.unifiprotect
uiprotect==15.1.0
uiprotect==15.0.0
# homeassistant.components.landisgyr_heat_meter
ultraheat-api==0.6.1
-1
View File
@@ -19,7 +19,6 @@ mock-open==1.4.0
mypy==2.1.0
prek==0.2.28
pydantic==2.13.4
PyGithub==2.9.1
pylint==4.0.6
pylint-per-file-ignores==3.2.1
pipdeptree==2.26.1
+14 -38
View File
@@ -2,28 +2,12 @@
import argparse
import json
import os
from pathlib import Path
import sys
from .gate import GateDecision, decide_skip
from .models import CheckRunResult
from .runner import run_checks
def _resolve_skip(pr_number: int, head_sha: str | None) -> GateDecision:
"""Decide whether this run can skip re-checking the PR.
Needs the repo and a token (from the Actions environment) to read prior
comments; without them it falls open and runs the checks.
"""
repo = os.environ.get("GITHUB_REPOSITORY")
token = os.environ.get("GITHUB_TOKEN")
if not head_sha or not repo or not token:
return GateDecision(False, "Gate inputs unavailable; running checks.")
return decide_skip(pr_number, head_sha, repo, token)
def main(argv: list[str] | None = None) -> int:
"""Run the deterministic check_requirements stage and write its artifact."""
parser = argparse.ArgumentParser(prog="python -m script.check_requirements")
@@ -47,32 +31,24 @@ def main(argv: list[str] | None = None) -> int:
)
args = parser.parse_args(argv)
decision = _resolve_skip(args.pr_number, args.head_sha)
print(decision.reason, file=sys.stderr)
if decision.skip:
result = CheckRunResult(
pr_number=args.pr_number, head_sha=args.head_sha, skip_aw=True
)
else:
try:
diff_text = args.diff.read_text(encoding="utf-8")
except FileNotFoundError:
parser.error(f"input file {args.diff} not found")
result = run_checks(
pr_number=args.pr_number,
diff_text=diff_text,
head_sha=args.head_sha,
)
print(
f"check_requirements: {len(result.packages)} package change(s); "
f"needs_agent={result.needs_agent}",
file=sys.stderr,
)
try:
diff_text = args.diff.read_text(encoding="utf-8")
except FileNotFoundError:
parser.error(f"input file {args.diff} not found")
result = run_checks(
pr_number=args.pr_number,
diff_text=diff_text,
head_sha=args.head_sha,
)
args.output.write_text(
json.dumps(result.to_dict(), indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
print(
f"check_requirements: {len(result.packages)} package change(s); "
f"needs_agent={result.needs_agent}",
file=sys.stderr,
)
return 0
+2 -4
View File
@@ -17,13 +17,11 @@ from .models import PackageChange
# of truth for pinned package changes.
TRACKED_PATTERNS = (
"requirements*.txt",
"**/requirements*.txt",
"homeassistant/package_constraints.txt",
)
def is_tracked(path: str) -> bool:
"""Return True if `path` is a requirement file the checks care about."""
def _is_tracked(path: str) -> bool:
return any(fnmatchcase(path, pattern) for pattern in TRACKED_PATTERNS)
@@ -63,7 +61,7 @@ def parse_diff(diff_text: str) -> list[PackageChange]:
added: dict[str, _Pin] = {}
removed: dict[str, _Pin] = {}
for patched_file in PatchSet(diff_text):
if not is_tracked(patched_file.path):
if not _is_tracked(patched_file.path):
continue
for hunk in patched_file:
for line in hunk:
-107
View File
@@ -1,107 +0,0 @@
"""Decide whether the deterministic stage can skip re-checking a PR.
The deterministic stage re-runs on every `synchronize` where the PR touches a
tracked requirement file, even when the latest push changed only unrelated
files. This module answers "did a tracked requirement file actually change
since we last commented?" so the stage can skip the PyPI work and flag the
uploaded artifact as skipped, telling the agentic stage to no-op.
"""
from dataclasses import dataclass
import logging
import os
import re
from github import Auth, Github, GithubException
from github.IssueComment import IssueComment
from .diff import is_tracked
from .render import COMMIT_PATH
_LOGGER = logging.getLogger(__name__)
# The "Checked at commit [`abc1234`](...COMMIT_PATH<40-hex>)." link rendered by
# render._intro is the only place the head SHA is recorded in the comment.
_COMMIT_SHA_RE = re.compile(re.escape(COMMIT_PATH) + r"([0-9a-f]{40})", re.IGNORECASE)
_TRUSTED_AUTHOR = "github-actions[bot]"
def _is_trusted_author(comment: IssueComment) -> bool:
"""True only for the github-actions bot that posts the check comment."""
return comment.user is not None and comment.user.login == _TRUSTED_AUTHOR
@dataclass(slots=True, frozen=True)
class GateDecision:
"""Whether to skip the deterministic checks, with a human-readable reason."""
skip: bool
reason: str
def _client(token: str) -> Github:
"""A lazy GitHub client on the configured (possibly GHES) API base."""
base_url = os.environ.get("GITHUB_API_URL", "https://api.github.com").rstrip("/")
return Github(auth=Auth.Token(token), base_url=base_url, lazy=True)
def fetch_marker_comment_bodies(pr_number: int, repo: str, token: str) -> list[str]:
"""Return the trusted requirements-check comment bodies, oldest-first."""
try:
comments = _client(token).get_repo(repo).get_issue(pr_number).get_comments()
return [comment.body for comment in comments if _is_trusted_author(comment)]
except GithubException as err:
_LOGGER.warning("Could not read comments for PR #%s: %s", pr_number, err)
return []
def extract_prior_sha(bodies: list[str]) -> str | None:
"""Return the head SHA recorded in the most recent marker comment."""
shas = [
match.group(1).lower()
for body in bodies
for match in _COMMIT_SHA_RE.finditer(body)
]
return shas[-1] if shas else None
def compare_changed_files(
base: str, head: str, repo: str, token: str
) -> list[str] | None:
"""Return filenames changed between two commits, or None if unavailable."""
try:
comparison = _client(token).get_repo(repo).compare(base, head)
return [changed.filename for changed in comparison.files]
except GithubException as err:
_LOGGER.warning("Could not compare %s...%s: %s", base, head, err)
return None
def decide_skip(pr_number: int, head_sha: str, repo: str, token: str) -> GateDecision:
"""Decide whether requirements changed since the last comment."""
if not head_sha:
return GateDecision(False, "No head SHA available; running checks.")
prior = extract_prior_sha(fetch_marker_comment_bodies(pr_number, repo, token))
if prior is None:
return GateDecision(
False, "No previous requirements-check comment; running checks."
)
if prior == head_sha.lower():
return GateDecision(
True, f"Head {head_sha} unchanged since the last comment; skipping."
)
changed = compare_changed_files(prior, head_sha, repo, token)
if changed is None:
return GateDecision(
False, f"Could not compare {prior}...{head_sha}; running checks."
)
tracked = [path for path in changed if is_tracked(path)]
if tracked:
return GateDecision(
False,
f"Tracked requirement files changed since {prior}; running checks: "
+ ", ".join(tracked),
)
return GateDecision(
True, f"No tracked requirement files changed since {prior}; skipping."
)
-2
View File
@@ -90,7 +90,6 @@ class CheckRunResult:
head_sha: str | None = None
packages: list[PackageChange] = field(default_factory=list)
rendered_comment: str = ""
skip_aw: bool = False
@property
def needs_agent(self) -> bool:
@@ -102,7 +101,6 @@ class CheckRunResult:
return {
"version": 1,
"pr_number": self.pr_number,
"skip_aw": self.skip_aw,
"head_sha": self.head_sha,
"needs_agent": self.needs_agent,
"packages": [p.to_dict() for p in self.packages],
+1 -2
View File
@@ -14,7 +14,6 @@ from .models import CheckKind, CheckRunResult, CheckStatus, PackageChange
MARKER = "<!-- requirements-check -->"
HEADER = "## Check requirements"
REPO_URL = "https://github.com/home-assistant/core"
COMMIT_PATH = "/commit/"
# Column / bullet labels per check kind, in display order.
_CHECK_DISPLAY: tuple[tuple[CheckKind, str], ...] = (
@@ -128,7 +127,7 @@ def _intro(result: CheckRunResult) -> str:
"""Marker, header, and the optional commit line the gate reads back."""
parts: list[str] = []
if result.head_sha:
commit = f"[`{result.head_sha[:7]}`]({REPO_URL}{COMMIT_PATH}{result.head_sha})"
commit = f"[`{result.head_sha[:7]}`]({REPO_URL}/commit/{result.head_sha})"
parts.append(f"Checked at commit {commit}.")
return "\n\n".join([f"{MARKER}\n{HEADER}", *parts])
-1
View File
@@ -1,3 +1,2 @@
PyGithub==2.9.1
requests==2.34.2
unidiff==0.7.5
+2 -1
View File
@@ -622,6 +622,7 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"mullvad",
"mutesync",
"mvglive",
"mycroft",
"myq",
"mysensors",
"mystrom",
@@ -1588,6 +1589,7 @@ INTEGRATIONS_WITHOUT_SCALE = [
"mullvad",
"mutesync",
"mvglive",
"mycroft",
"myq",
"mysensors",
"mystrom",
@@ -2082,7 +2084,6 @@ NO_QUALITY_SCALE = [
"intent_script",
"intent",
"labs",
"llm",
"logbook",
"logger",
"lovelace",
@@ -38,7 +38,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-co',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_carbon_monoxide-state]
@@ -50,7 +50,7 @@
'limit': 4000,
'percent': 4,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_carbon_monoxide',
@@ -157,7 +157,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor[sensor.home_humidity-state]
@@ -167,7 +167,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.home_humidity',
@@ -216,7 +216,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-no2',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_nitrogen_dioxide-state]
@@ -228,7 +228,7 @@
'limit': 25,
'percent': 64,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_nitrogen_dioxide',
@@ -277,7 +277,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-o3',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_ozone-state]
@@ -289,7 +289,7 @@
'limit': 100,
'percent': 42,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_ozone',
@@ -338,7 +338,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-pm1',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_pm1-state]
@@ -348,7 +348,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm1',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home PM1',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_pm1',
@@ -397,7 +397,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-pm10',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_pm10-state]
@@ -409,7 +409,7 @@
'limit': 45,
'percent': 14,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_pm10',
@@ -458,7 +458,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-pm25',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_pm2_5-state]
@@ -470,7 +470,7 @@
'limit': 15,
'percent': 29,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_pm2_5',
@@ -578,7 +578,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '123-456-so2',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor[sensor.home_sulphur_dioxide-state]
@@ -590,7 +590,7 @@
'limit': 40,
'percent': 35,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_sulphur_dioxide',
+3 -3
View File
@@ -107,8 +107,8 @@ async def test_alexa_unique_id_migration(
)
entity = entity_registry.async_get_or_create(
SWITCH_DOMAIN,
DOMAIN,
SWITCH_DOMAIN,
unique_id=f"{TEST_DEVICE_1_SN}-do_not_disturb",
device_id=device.id,
config_entry=mock_config_entry,
@@ -145,8 +145,8 @@ async def test_alexa_dnd_group_removal(
)
entity = entity_registry.async_get_or_create(
SWITCH_DOMAIN,
DOMAIN,
SWITCH_DOMAIN,
unique_id=f"{TEST_DEVICE_1_SN}-do_not_disturb",
device_id=device.id,
config_entry=mock_config_entry,
@@ -184,8 +184,8 @@ async def test_alexa_unsupported_notification_sensor_removal(
)
entity = entity_registry.async_get_or_create(
SENSOR_DOMAIN,
DOMAIN,
SENSOR_DOMAIN,
unique_id=f"{TEST_DEVICE_1_SN}-Timer",
device_id=device.id,
config_entry=mock_config_entry,
@@ -853,7 +853,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called Are the',
'speech': 'Sorry, I am not aware of any area called Are the',
}),
}),
}),
@@ -902,7 +902,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called Are the',
'speech': 'Sorry, I am not aware of any area called Are the',
}),
}),
}),
-182
View File
@@ -7,7 +7,6 @@ from typing import Any
from unittest.mock import ANY, Mock, patch
import pytest
import voluptuous as vol
from homeassistant.components import automation, input_boolean, script
from homeassistant.components.automation import (
@@ -1931,187 +1930,6 @@ async def test_automation_with_error_in_script_2(
assert "string value is None" in caplog.text
@pytest.mark.parametrize(
("side_effect", "expected_error", "expect_traceback"),
[
(
HomeAssistantError("boom"),
"Error while executing automation automation.hello: boom",
False,
),
(
vol.Invalid("not valid"),
"Error while executing automation automation.hello: not valid",
False,
),
(
ValueError("unexpected"),
"Unexpected error while executing automation automation.hello",
True,
),
],
ids=["home_assistant_error", "voluptuous_invalid", "unexpected_exception"],
)
async def test_automation_with_error_in_action_script(
hass: HomeAssistant,
calls: list[ServiceCall],
caplog: pytest.LogCaptureFixture,
hass_ws_client: WebSocketGenerator,
side_effect: Exception,
expected_error: str,
expect_traceback: bool,
) -> None:
"""Test errors raised while running the action script are handled and traced."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"id": "hello",
"alias": "hello",
"trigger": {"platform": "event", "event_type": "test_event"},
"action": {"action": "test.automation"},
}
},
)
with patch(
"homeassistant.helpers.script.Script.async_run",
side_effect=side_effect,
):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(calls) == 0
assert expected_error in caplog.text
# A HomeAssistantError/voluptuous error is logged without a traceback, an
# unexpected error is logged with a traceback.
assert ("Traceback" in caplog.text) is expect_traceback
# The error is recorded on the automation trace.
client = await hass_ws_client()
await client.send_json_auto_id(
{"type": "trace/list", "domain": "automation", "item_id": "hello"}
)
response = await client.receive_json()
assert response["success"]
traces = response["result"]
assert len(traces) == 1
assert traces[0]["error"] == str(side_effect)
@pytest.mark.parametrize(
("side_effect", "expected_error", "expect_traceback"),
[
(
HomeAssistantError("boom"),
"Error while checking conditions of automation automation.hello: boom",
False,
),
(
vol.Invalid("not valid"),
"Error while checking conditions of automation automation.hello: not valid",
False,
),
(
ValueError("unexpected"),
"Unexpected error while checking conditions of automation automation.hello",
True,
),
],
ids=["home_assistant_error", "voluptuous_invalid", "unexpected_exception"],
)
async def test_automation_with_error_in_condition(
hass: HomeAssistant,
calls: list[ServiceCall],
caplog: pytest.LogCaptureFixture,
hass_ws_client: WebSocketGenerator,
side_effect: Exception,
expected_error: str,
expect_traceback: bool,
) -> None:
"""Test errors raised while checking conditions are handled and traced."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"id": "hello",
"alias": "hello",
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
"condition": "state",
"entity_id": "test.entity",
"state": "on",
},
"action": {"action": "test.automation"},
}
},
)
with patch(
"homeassistant.helpers.condition.ConditionsChecker.async_check",
side_effect=side_effect,
):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
# The action must not run when the condition check raises.
assert len(calls) == 0
assert expected_error in caplog.text
# A HomeAssistantError/voluptuous error is logged without a traceback, an
# unexpected error is logged with a traceback.
assert ("Traceback" in caplog.text) is expect_traceback
# The error is recorded on the automation trace.
client = await hass_ws_client()
await client.send_json_auto_id(
{"type": "trace/list", "domain": "automation", "item_id": "hello"}
)
response = await client.receive_json()
assert response["success"]
traces = response["result"]
assert len(traces) == 1
assert traces[0]["error"] == str(side_effect)
async def test_automation_with_error_in_condition_continues_after_recovery(
hass: HomeAssistant,
calls: list[ServiceCall],
) -> None:
"""Test the automation still runs once the condition stops raising."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"alias": "hello",
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
"condition": "state",
"entity_id": "test.entity",
"state": "on",
},
"action": {"action": "test.automation"},
}
},
)
hass.states.async_set("test.entity", "on")
with patch(
"homeassistant.helpers.condition.ConditionsChecker.async_check",
side_effect=HomeAssistantError("boom"),
):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(calls) == 0
# Without the error, the condition passes and the action runs.
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(calls) == 1
async def test_automation_restore_last_triggered_with_initial_state(
hass: HomeAssistant,
) -> None:
@@ -302,7 +302,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called late added',
'speech': 'Sorry, I am not aware of any area called late added',
}),
}),
}),
@@ -373,7 +373,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called kitchen',
'speech': 'Sorry, I am not aware of any area called kitchen',
}),
}),
}),
@@ -423,7 +423,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called renamed',
'speech': 'Sorry, I am not aware of any area called renamed',
}),
}),
}),
@@ -467,11 +467,10 @@
'name': 'HassTurnOn',
}),
'match': True,
'sentence_template': '<turn> on [<the>] {name}',
'sentence_template': '<turn> on (<area> <name>|<name> [in <area>])',
'slots': dict({
'name': 'my cool light',
}),
'source': 'builtin',
'targets': dict({
'light.kitchen': dict({
'matched': True,
@@ -492,11 +491,10 @@
'name': 'HassTurnOff',
}),
'match': True,
'sentence_template': '[<turn>] [<the>] {name} [to] off',
'sentence_template': '[<turn>] (<area> <name>|<name> [in <area>]) [to] off',
'slots': dict({
'name': 'my cool light',
}),
'source': 'builtin',
'targets': dict({
'light.kitchen': dict({
'matched': True,
@@ -522,12 +520,11 @@
'name': 'HassTurnOn',
}),
'match': True,
'sentence_template': '<turn> on [(<all>|<the>)] <light> <in> [<the>] {area}',
'sentence_template': '<turn> on [(<all>|<the>)] <light> <in> <area>',
'slots': dict({
'area': 'kitchen',
'domain': 'light',
}),
'source': 'builtin',
'targets': dict({
'light.kitchen': dict({
'matched': True,
@@ -545,7 +542,7 @@
}),
'domain': dict({
'name': 'domain',
'text': '',
'text': 'lights',
'value': 'light',
}),
'state': dict({
@@ -558,13 +555,12 @@
'name': 'HassGetState',
}),
'match': True,
'sentence_template': '<how_many> <light> <is> {on_off_states:state} [<in>] [<the>] {area}',
'sentence_template': '[tell me] how many {on_off_domains:domain} (is|are) {on_off_states:state} [<in_area_floor>]',
'slots': dict({
'area': 'kitchen',
'domain': 'light',
'domain': 'lights',
'state': 'on',
}),
'source': 'builtin',
'targets': dict({
'light.kitchen': dict({
'matched': False,
@@ -633,12 +629,11 @@
'name': 'HassLightSet',
}),
'match': True,
'sentence_template': '[<numeric_value_set>] [<the>] {name} brightness [to] {brightness}[([ ]%)| percent]',
'sentence_template': '[<numeric_value_set>] <name> brightness [to] <brightness>',
'slots': dict({
'brightness': '100',
'name': 'test light',
}),
'source': 'builtin',
'targets': dict({
'light.demo_1234': dict({
'matched': True,
@@ -665,11 +660,10 @@
'name': 'HassLightSet',
}),
'match': False,
'sentence_template': '[<numeric_value_set>] [<the>] {name} brightness [to] {brightness}[([ ]%)| percent]',
'sentence_template': '[<numeric_value_set>] <name> brightness [to] <brightness>',
'slots': dict({
'name': 'test light',
}),
'source': 'builtin',
'targets': dict({
}),
'unmatched_slots': dict({
@@ -729,6 +729,19 @@ async def test_satellite_area_context(
}
turn_off_calls.clear()
# Turn on/off all lights also works
for command in ("on", "off"):
result = await conversation.async_converse(
hass, f"turn {command} all lights", None, Context(), None
)
await hass.async_block_till_done()
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
# All lights should have been targeted
assert {s.entity_id for s in result.response.matched_states} == {
e.entity_id for e in all_lights
}
@pytest.mark.usefixtures("init_components")
async def test_error_no_device(hass: HomeAssistant) -> None:
@@ -828,7 +841,7 @@ async def test_error_no_device_on_floor(
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
== "Sorry, I am not aware of any device called missing entity in the ground floor"
== "Sorry, I am not aware of any device called missing entity on ground floor"
)
@@ -1115,7 +1128,7 @@ async def test_error_no_domain_on_floor_exposed(
await hass.async_block_till_done()
result = await conversation.async_converse(
hass, "turn on all lights in the ground floor", None, Context(), None
hass, "turn on all lights on the ground floor", None, Context(), None
)
assert result.response.response_type is intent.IntentResponseType.ERROR
@@ -1480,6 +1493,21 @@ async def test_error_duplicate_names_same_area(
f" {name} in the {area_kitchen.name} area"
)
# question
result = await conversation.async_converse(
hass, f"is {name} on in the {area_kitchen.name}?", None, Context(), None
)
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
)
assert (
result.response.speech["plain"]["speech"]
== f"Sorry, there are multiple devices called"
f" {name} in the {area_kitchen.name} area"
)
@pytest.mark.usefixtures("init_components")
async def test_duplicate_names_same_area_but_one_is_exposed(
@@ -2827,9 +2855,9 @@ async def test_config_sentences_priority(
{
"conversation": {
"intents": {
"CustomIntent": ["turn on [the] {name}"],
"CustomIntent": ["turn on <name>"],
"WorseCustomIntent": ["turn on the lamp"],
"FakeCustomIntent": ["turn on [the] {name}"],
"FakeCustomIntent": ["turn on <name>"],
}
}
},
@@ -127,39 +127,29 @@ async def test_cover_set_position(
async def test_cover_device_class(
hass: HomeAssistant,
init_components,
area_registry: ar.AreaRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test the open position for covers by device class."""
await cover_intent.async_setup_intents(hass)
area_kitchen = area_registry.async_get_or_create("kitchen_id")
area_kitchen = area_registry.async_update(area_kitchen.id, name="kitchen")
kitchen_window = entity_registry.async_get_or_create(
"cover", "demo", "kitchen_window"
)
kitchen_window = entity_registry.async_update_entity(
kitchen_window.entity_id, area_id=area_kitchen.id
)
entity_id = f"{cover.DOMAIN}.front"
hass.states.async_set(
kitchen_window.entity_id, STATE_CLOSED, attributes={"device_class": "window"}
entity_id, STATE_CLOSED, attributes={"device_class": "garage"}
)
async_expose_entity(hass, conversation.DOMAIN, kitchen_window.entity_id, True)
async_expose_entity(hass, conversation.DOMAIN, entity_id, True)
# Open service
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
result = await conversation.async_converse(
hass, "open the window in the kitchen", None, Context(), None, device_id=None
hass, "open the garage door", None, Context(), None
)
await hass.async_block_till_done()
response = result.response
assert response.response_type is intent.IntentResponseType.ACTION_DONE
assert response.speech["plain"]["speech"] == "Opening the window"
assert response.speech["plain"]["speech"] == "Opening the garage"
assert len(calls) == 1
call = calls[0]
assert call.data == {"entity_id": kitchen_window.entity_id}
assert call.data == {"entity_id": entity_id}
async def test_valve_intents(
@@ -325,13 +325,13 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_32896_32900',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Airversa AP2 1808 Filter lifetime',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.airversa_ap2_1808_filter_lifetime',
'state': '100.0',
@@ -373,14 +373,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_2576_2580',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Airversa AP2 1808 PM2.5 Density',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'entity_id': 'sensor.airversa_ap2_1808_pm2_5_density',
'state': '3.0',
@@ -930,7 +930,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4_101',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -938,7 +938,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'eufyCam2-0000 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-20',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.eufycam2_0000_battery',
'state': '17',
@@ -1192,7 +1192,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_2_101',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -1200,7 +1200,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'eufyCam2-000A Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-40',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.eufycam2_000a_battery',
'state': '38',
@@ -1454,7 +1454,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_3_101',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -1462,7 +1462,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'eufyCam2-000A Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-alert',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.eufycam2_000a_battery_2',
'state': '100',
@@ -1898,7 +1898,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_33_5',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -1906,7 +1906,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Contact Sensor Battery Sensor',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.contact_sensor_battery_sensor',
'state': '100',
@@ -2321,7 +2321,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_5',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -2329,7 +2329,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Programmable Switch Battery Sensor',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.programmable_switch_battery_sensor',
'state': '100',
@@ -2655,7 +2655,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_700',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -2663,7 +2663,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ArloBabyA0 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-80',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.arlobabya0_battery',
'state': '82',
@@ -2705,14 +2705,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_900',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ArloBabyA0 Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.arlobabya0_humidity',
'state': '60.099998',
@@ -3950,14 +3950,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_16_24',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'HomeW Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.homew_current_humidity',
'state': '34',
@@ -4573,7 +4573,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4295608960_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -4581,7 +4581,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Basement Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.basement_battery',
'state': '100',
@@ -4888,7 +4888,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298360914_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -4896,7 +4896,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Basement Window 1 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.basement_window_1_battery',
'state': '100',
@@ -5151,7 +5151,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298360921_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -5159,7 +5159,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Deck Door Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.deck_door_battery',
'state': '100',
@@ -5414,7 +5414,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298527970_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -5422,7 +5422,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Front Door Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.front_door_battery',
'state': '100',
@@ -5677,7 +5677,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298527962_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -5685,7 +5685,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Garage Door Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.garage_door_battery',
'state': '100',
@@ -5895,7 +5895,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4295016858_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -5903,7 +5903,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Living Room Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.living_room_battery',
'state': '100',
@@ -6210,7 +6210,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298360712_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -6218,7 +6218,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Living Room Window 1 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.living_room_window_1_battery',
'state': '100',
@@ -6473,7 +6473,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298649931_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -6481,7 +6481,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Loft window Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.loft_window_battery',
'state': '100',
@@ -6691,7 +6691,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4295608971_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -6699,7 +6699,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Master BR Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.master_br_battery',
'state': '100',
@@ -7006,7 +7006,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298584118_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -7014,7 +7014,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Master BR Window Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.master_br_window_battery',
'state': '100',
@@ -7363,14 +7363,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_16_24',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Thermostat Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.thermostat_current_humidity',
'state': '45.0',
@@ -7632,7 +7632,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4295016969_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -7640,7 +7640,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Upstairs BR Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.upstairs_br_battery',
'state': '100',
@@ -7947,7 +7947,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4298568508_192',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -7955,7 +7955,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Upstairs BR Window Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.upstairs_br_window_battery',
'state': '100',
@@ -8394,14 +8394,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_16_24',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'HomeW Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.homew_current_humidity',
'state': '34',
@@ -8826,14 +8826,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_16_24',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'HomeW Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.homew_current_humidity',
'state': '34',
@@ -9683,14 +9683,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_16_24',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My ecobee Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.my_ecobee_current_humidity',
'state': '55.0',
@@ -10341,7 +10341,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_17',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -10349,7 +10349,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Degree AA11 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-60',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.eve_degree_aa11_battery',
'state': '65',
@@ -10391,14 +10391,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_27',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Degree AA11 Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.eve_degree_aa11_humidity',
'state': '59.4818115234375',
@@ -11350,7 +11350,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_123016423_162',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -11358,7 +11358,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Family Room North Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.family_room_north_battery',
'state': '100',
@@ -11603,7 +11603,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_878448248_9',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -11611,7 +11611,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kitchen Window Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.kitchen_window_battery',
'state': '100',
@@ -12255,14 +12255,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1233851541_169_180',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '89 Living Room Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.89_living_room_current_humidity',
'state': '60',
@@ -12648,7 +12648,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_3982136094_604',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -12656,7 +12656,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Laundry Smoke ED78 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.laundry_smoke_ed78_battery',
'state': '100',
@@ -12827,7 +12827,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_123016423_162',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -12835,7 +12835,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Family Room North Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.family_room_north_battery',
'state': '100',
@@ -13080,7 +13080,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_878448248_9',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -13088,7 +13088,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kitchen Window Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.kitchen_window_battery',
'state': '100',
@@ -13957,14 +13957,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1233851541_169_180',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '89 Living Room Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.89_living_room_current_humidity',
'state': '60',
@@ -14358,14 +14358,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_293334836_8_9',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Humidifier 182A Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.humidifier_182a_current_humidity',
'state': '0',
@@ -14629,14 +14629,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_293334836_8_9',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Humidifier 182A Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.humidifier_182a_current_humidity',
'state': '0',
@@ -14902,7 +14902,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_3982136094_604',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -14910,7 +14910,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Laundry Smoke ED78 Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.laundry_smoke_ed78_battery',
'state': '100',
@@ -16320,7 +16320,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_6623462389072572_644245094400',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -16328,7 +16328,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hue dimmer switch Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.hue_dimmer_switch_battery',
'state': '100',
@@ -18122,14 +18122,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_100_107',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Lennox Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.lennox_current_humidity',
'state': '34',
@@ -19321,14 +19321,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_20_27',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mysa-85dda9 Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.mysa_85dda9_current_humidity',
'state': '40',
@@ -20323,14 +20323,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_10',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Healthy Home Coach Carbon Dioxide sensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'entity_id': 'sensor.healthy_home_coach_carbon_dioxide_sensor',
'state': '804',
@@ -20372,14 +20372,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_14',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Healthy Home Coach Humidity sensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.healthy_home_coach_humidity_sensor',
'state': '59',
@@ -21112,7 +21112,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_2_64',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -21120,7 +21120,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Master Bath South RYSE Shade Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.master_bath_south_ryse_shade_battery',
'state': '100',
@@ -21365,7 +21365,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_3_64',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -21373,7 +21373,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'RYSE SmartShade RYSE Shade Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.ryse_smartshade_ryse_shade_battery',
'state': '100',
@@ -21544,7 +21544,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_4_64',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -21552,7 +21552,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'BR Left RYSE Shade Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.br_left_ryse_shade_battery',
'state': '100',
@@ -21719,7 +21719,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_2_64',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -21727,7 +21727,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'LR Left RYSE Shade Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-90',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.lr_left_ryse_shade_battery',
'state': '89',
@@ -21894,7 +21894,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_3_64',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -21902,7 +21902,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'LR Right RYSE Shade Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.lr_right_ryse_shade_battery',
'state': '100',
@@ -22147,7 +22147,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_5_64',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
@@ -22155,7 +22155,7 @@
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'RZSS RYSE Shade Battery',
<EntityStateAttribute.ICON: 'icon'>: 'mdi:battery-alert',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.rzss_ryse_shade_battery',
'state': '0',
@@ -23151,14 +23151,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_14',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'VELUX Sensor Carbon Dioxide sensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'entity_id': 'sensor.velux_sensor_carbon_dioxide_sensor',
'state': '1124.0',
@@ -23200,14 +23200,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_11',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'VELUX Sensor Humidity sensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.velux_sensor_humidity_sensor',
'state': '69.0',
@@ -23461,14 +23461,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_2_14',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'VELUX Sensor Carbon Dioxide sensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'entity_id': 'sensor.velux_sensor_carbon_dioxide_sensor',
'state': '400',
@@ -23510,14 +23510,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_2_11',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'VELUX Sensor Humidity sensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.velux_sensor_humidity_sensor',
'state': '58',
@@ -24267,14 +24267,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_30_33',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
}),
'state': dict({
'attributes': dict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'VOCOlinc-Flowerbud-0d324b Current Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'entity_id': 'sensor.vocolinc_flowerbud_0d324b_current_humidity',
'state': '45.0',
-1
View File
@@ -1 +0,0 @@
"""Tests for the LLM integration."""
-121
View File
@@ -1,121 +0,0 @@
"""Tests for the LLM integration."""
from unittest.mock import Mock
import pytest
from homeassistant.components.llm import DATA_PLATFORMS, LLMTools, async_get_tools
from homeassistant.core import HomeAssistant
from homeassistant.helpers import llm
from homeassistant.setup import async_setup_component
from homeassistant.util.json import JsonObjectType
from tests.common import mock_platform
class _StubTool(llm.Tool):
"""Minimal tool for registry tests."""
def __init__(self, name: str) -> None:
"""Initialize the stub tool."""
self.name = name
self.description = f"{name} description"
async def async_call(
self,
hass: HomeAssistant,
tool_input: llm.ToolInput,
llm_context: llm.LLMContext,
) -> JsonObjectType:
"""Return an empty result."""
return {}
@pytest.fixture
def llm_context() -> llm.LLMContext:
"""Return an LLM context."""
return llm.LLMContext(
platform="test",
context=None,
language="*",
assistant="conversation",
device_id=None,
)
def _mock_tools_platform(
hass: HomeAssistant, domain: str, tools: LLMTools | Exception
) -> None:
"""Register a mock <integration>/llm.py platform returning the given tools."""
if isinstance(tools, Exception):
async_get_tools = Mock(side_effect=tools)
else:
async_get_tools = Mock(return_value=tools)
hass.config.components.add(domain)
mock_platform(hass, f"{domain}.llm", Mock(async_get_tools=async_get_tools))
async def test_setup(hass: HomeAssistant) -> None:
"""Test the integration sets up."""
assert await async_setup_component(hass, "llm", {})
assert DATA_PLATFORMS in hass.data
async def test_get_tools(hass: HomeAssistant, llm_context: llm.LLMContext) -> None:
"""Test that tools from an integration platform are returned."""
tool = _StubTool("my_tool")
_mock_tools_platform(
hass, "test", LLMTools(tools=[tool], prompt="use my_tool wisely")
)
assert await async_setup_component(hass, "llm", {})
result = await async_get_tools(hass, llm_context)
assert result.tools == [tool]
assert result.prompt == "use my_tool wisely"
async def test_get_tools_empty(
hass: HomeAssistant, llm_context: llm.LLMContext
) -> None:
"""Test that no platforms yields no tools."""
assert await async_setup_component(hass, "llm", {})
result = await async_get_tools(hass, llm_context)
assert result.tools == []
assert result.prompt is None
async def test_get_tools_merges_sorted(
hass: HomeAssistant, llm_context: llm.LLMContext
) -> None:
"""Test that tools and prompts are merged in a load-order-independent order."""
tool_a = _StubTool("tool_a")
tool_b = _StubTool("tool_b")
# Register "test_b" before "test_a" to prove the result is sorted by domain.
_mock_tools_platform(hass, "test_b", LLMTools(tools=[tool_b], prompt="prompt b"))
_mock_tools_platform(hass, "test_a", LLMTools(tools=[tool_a], prompt="prompt a"))
assert await async_setup_component(hass, "llm", {})
result = await async_get_tools(hass, llm_context)
assert result.tools == [tool_a, tool_b]
assert result.prompt == "prompt a\nprompt b"
async def test_get_tools_isolates_failing_platform(
hass: HomeAssistant,
llm_context: llm.LLMContext,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that one failing platform does not drop the others' tools."""
tool = _StubTool("good_tool")
_mock_tools_platform(hass, "test_bad", ValueError("boom"))
_mock_tools_platform(hass, "test_good", LLMTools(tools=[tool], prompt="prompt"))
assert await async_setup_component(hass, "llm", {})
result = await async_get_tools(hass, llm_context)
assert result.tools == [tool]
assert result.prompt == "prompt"
assert "Error getting tools from LLM platform test_bad" in caplog.text
+43
View File
@@ -42,6 +42,7 @@ from homeassistant.const import (
EVENT_LOGBOOK_ENTRY,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
)
from homeassistant.core import Context, Event, HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -345,6 +346,7 @@ def create_state_changed_event_from_old_new(
state=new_state and new_state.get("state"),
entity_id=entity_id,
icon=None,
attributes=None,
context_only=False,
data=None,
context=None,
@@ -1857,6 +1859,46 @@ async def test_icon_and_state(
assert response_json[2]["state"] == STATE_OFF
@pytest.mark.usefixtures("recorder_mock")
async def test_state_attributes_in_logbook(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test state attributes are exposed in the logbook like the history.
The recorded subset is surfaced, so attributes the recorder excludes
(such as supported_features) must not appear.
"""
await asyncio.gather(
*[
async_setup_component(hass, domain, {})
for domain in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set("event.doorbell", STATE_UNKNOWN, {"event_type": None})
hass.states.async_set(
"event.doorbell",
"2024-01-01T00:00:00.000+00:00",
{"event_type": "ring", "supported_features": 1},
)
hass.states.async_set(
"event.doorbell", "2024-01-01T00:01:00.000+00:00", {"event_type": "motion"}
)
await async_wait_recording_done(hass)
client = await hass_client()
response_json = await _async_fetch_logbook(client)
entries = [e for e in response_json if e.get("entity_id") == "event.doorbell"]
assert len(entries) == 2
assert entries[0]["attributes"] == {"event_type": "ring"}
assert entries[1]["attributes"] == {"event_type": "motion"}
@pytest.mark.usefixtures("recorder_mock")
async def test_fire_logbook_entries(
hass: HomeAssistant, hass_client: ClientSessionGenerator
@@ -3335,6 +3377,7 @@ async def test_parent_user_attribution_does_not_use_origin_event_fallback(
state=STATE_ON,
entity_id="switch.heater",
icon=None,
attributes=None,
context_only=False,
data={},
context=child_context,
+1
View File
@@ -19,6 +19,7 @@ def test_lazy_event_partial_state_context() -> None:
state="state",
entity_id="entity_id",
icon="icon",
attributes=None,
context_only=False,
data={},
context=Mock(),
@@ -35,6 +35,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
)
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -1479,6 +1480,64 @@ async def test_subscribe_unsubscribe_logbook_stream(
) == listeners_without_writes(init_listeners)
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
async def test_subscribe_logbook_stream_state_attributes(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the live logbook stream exposes allowlisted state attributes."""
now = dt_util.utcnow()
await asyncio.gather(
*[
async_setup_component(hass, domain, {})
for domain in ("homeassistant", "logbook")
]
)
await hass.async_block_till_done()
hass.states.async_set("binary_sensor.is_light", STATE_ON)
hass.states.async_set("binary_sensor.is_light", STATE_OFF)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
websocket_client = await hass_ws_client()
await websocket_client.send_json(
{"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()}
)
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == TYPE_RESULT
assert msg["success"]
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["event"]["partial"] is True
await hass.async_block_till_done()
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert "partial" not in msg["event"]
assert msg["event"]["events"] == []
hass.states.async_set("event.doorbell", STATE_UNKNOWN, {"event_type": None})
hass.states.async_set(
"event.doorbell",
"2024-01-01T00:00:00.000+00:00",
{"event_type": "ring", "supported_features": 1},
)
await hass.async_block_till_done()
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == "event"
assert msg["event"]["events"] == [
{
"entity_id": "event.doorbell",
"state": "2024-01-01T00:00:00.000+00:00",
"attributes": {"event_type": "ring"},
"when": ANY,
}
]
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
async def test_subscribe_unsubscribe_logbook_stream_entities(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator
@@ -151,7 +151,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'be37ca9c47c24498a38bc62c7c711840-sensor2-air_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_setup[sensor.test_sensor_2-state]
@@ -160,7 +160,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Test Sensor 2',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.test_sensor_2',
+124 -124
View File
@@ -103,7 +103,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-CarbonDioxideSensor-1037-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_carbon_dioxide-state]
@@ -112,7 +112,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor Carbon dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_carbon_dioxide',
@@ -158,7 +158,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_humidity-state]
@@ -167,7 +167,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_humidity',
@@ -213,7 +213,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-NitrogenDioxideSensor-1043-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_nitrogen_dioxide-state]
@@ -222,7 +222,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'nitrogen_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor Nitrogen dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_nitrogen_dioxide',
@@ -268,7 +268,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-PM1Sensor-1068-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_pm1-state]
@@ -277,7 +277,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm1',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor PM1',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_pm1',
@@ -323,7 +323,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-PM10Sensor-1069-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_pm10-state]
@@ -332,7 +332,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor PM10',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_pm10',
@@ -378,7 +378,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-PM25Sensor-1066-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_pm2_5-state]
@@ -387,7 +387,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_pm2_5',
@@ -598,7 +598,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-TotalVolatileOrganicCompoundsSensor-1070-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[air_quality_sensor][sensor.lightfi_aq1_air_quality_sensor_volatile_organic_compounds_parts-state]
@@ -607,7 +607,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'volatile_organic_compounds_parts',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'lightfi-aq1-air-quality-sensor Volatile organic compounds parts',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.lightfi_aq1_air_quality_sensor_volatile_organic_compounds_parts',
@@ -653,7 +653,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000005B-MatterNodeDevice-2-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_door_window_p2][sensor.aqara_door_and_window_sensor_p2_battery-state]
@@ -662,7 +662,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Aqara Door and Window Sensor P2 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.aqara_door_and_window_sensor_p2_battery',
@@ -993,7 +993,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000053-MatterNodeDevice-3-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_motion_p2][sensor.aqara_motion_and_light_sensor_p2_battery-state]
@@ -1002,7 +1002,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Aqara Motion and Light Sensor P2 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.aqara_motion_and_light_sensor_p2_battery',
@@ -1388,7 +1388,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000016C-MatterNodeDevice-2-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_multi_state_p100][sensor.multi_state_sensor_p100_battery-state]
@@ -1397,7 +1397,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Multi-State Sensor P100 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.multi_state_sensor_p100_battery',
@@ -1900,7 +1900,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-00000000000000CD-MatterNodeDevice-5-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_presence_fp300][sensor.presence_multi_sensor_fp300_1_battery-state]
@@ -1909,7 +1909,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Presence Multi-Sensor FP300 1 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.presence_multi_sensor_fp300_1_battery',
@@ -2136,7 +2136,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-00000000000000CD-MatterNodeDevice-4-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_presence_fp300][sensor.presence_multi_sensor_fp300_1_humidity-state]
@@ -2145,7 +2145,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Presence Multi-Sensor FP300 1 Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.presence_multi_sensor_fp300_1_humidity',
@@ -2580,7 +2580,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-6-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery-state]
@@ -2589,7 +2589,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Climate Sensor W100 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_battery',
@@ -2975,7 +2975,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-2-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_humidity-state]
@@ -2984,7 +2984,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Climate Sensor W100 Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_humidity',
@@ -3556,7 +3556,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000064-MatterNodeDevice-2-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_thermostat_w500][sensor.floor_heating_thermostat_humidity-state]
@@ -3565,7 +3565,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Floor Heating Thermostat Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.floor_heating_thermostat_humidity',
@@ -3945,7 +3945,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000014-MatterNodeDevice-2-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_u200][sensor.aqara_smart_lock_u200_battery-state]
@@ -3954,7 +3954,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Aqara Smart Lock U200 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.aqara_smart_lock_u200_battery',
@@ -4633,14 +4633,14 @@
'supported_features': 0,
'translation_key': 'pi_heating_demand',
'unique_id': '00000000000004D2-0000000000000007-MatterNodeDevice-1-ThermostatPIHeatingDemand-513-8',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eberle_ute3000][sensor.connected_thermostat_ute_3000_heating_demand-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Connected Thermostat UTE 3000 Heating demand',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.connected_thermostat_ute_3000_heating_demand',
@@ -4852,7 +4852,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000002F-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[ecovacs_deebot][sensor.ecodeebot_battery-state]
@@ -4861,7 +4861,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ecodeebot Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.ecodeebot_battery',
@@ -5539,7 +5539,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_battery-state]
@@ -5548,7 +5548,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '2BAVS-AB6031X-44PE Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.2bavs_ab6031x_44pe_battery',
@@ -5994,7 +5994,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000009-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_contact_sensor][sensor.eve_door_battery-state]
@@ -6003,7 +6003,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Door Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_door_battery',
@@ -7973,14 +7973,14 @@
'supported_features': 0,
'translation_key': 'window_covering_target_position',
'unique_id': '00000000000004D2-0000000000000094-MatterNodeDevice-1-TargetPositionLiftPercent100ths-258-11',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_shutter][sensor.eve_shutter_switch_20eci1701_target_opening_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Shutter Switch 20ECI1701 Target opening position',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_shutter_switch_20eci1701_target_opening_position',
@@ -8249,7 +8249,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_battery-state]
@@ -8258,7 +8258,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Thermo 20EBP1701 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ebp1701_battery',
@@ -8697,14 +8697,14 @@
'supported_features': 0,
'translation_key': 'valve_position',
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-EveThermoValvePosition-319486977-319422488',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_valve_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Thermo 20EBP1701 Valve position',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ebp1701_valve_position',
@@ -8750,7 +8750,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_battery-state]
@@ -8759,7 +8759,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Thermo 20ECD1701 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_battery',
@@ -9137,14 +9137,14 @@
'supported_features': 0,
'translation_key': 'valve_position',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-EveThermoValvePosition-319486977-319422488',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_valve_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Thermo 20ECD1701 Valve position',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_valve_position',
@@ -9190,7 +9190,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000001D-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_weather_sensor][sensor.eve_weather_battery-state]
@@ -9199,7 +9199,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Weather Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_weather_battery',
@@ -9306,7 +9306,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000001D-MatterNodeDevice-2-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_weather_sensor][sensor.eve_weather_humidity-state]
@@ -9315,7 +9315,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Eve Weather Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_weather_humidity',
@@ -9991,7 +9991,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000003-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[haojai_switch][sensor.hjmt_6b_battery-state]
@@ -10000,7 +10000,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'HJMT-6B Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.hjmt_6b_battery',
@@ -10771,7 +10771,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000006-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[heiman_co_sensor][sensor.smart_co_sensor_battery-state]
@@ -10780,7 +10780,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart CO sensor Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.smart_co_sensor_battery',
@@ -10937,7 +10937,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000006-MatterNodeDevice-1-CarbonMonoxideSensor-1036-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[heiman_co_sensor][sensor.smart_co_sensor_carbon_monoxide-state]
@@ -10946,7 +10946,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_monoxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart CO sensor Carbon monoxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.smart_co_sensor_carbon_monoxide',
@@ -11329,7 +11329,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000058-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[heiman_motion_sensor_m1][sensor.smart_motion_sensor_battery-state]
@@ -11338,7 +11338,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart motion sensor Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.smart_motion_sensor_battery',
@@ -11887,7 +11887,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000000B-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[heiman_smoke_detector][sensor.smoke_sensor_battery-state]
@@ -11896,7 +11896,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smoke sensor Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.smoke_sensor_battery',
@@ -12365,7 +12365,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000025-MatterNodeDevice-1-CarbonDioxideSensor-1037-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[ikea_air_quality_monitor][sensor.alpstuga_air_quality_monitor_carbon_dioxide-state]
@@ -12374,7 +12374,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ALPSTUGA air quality monitor Carbon dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.alpstuga_air_quality_monitor_carbon_dioxide',
@@ -12420,7 +12420,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000025-MatterNodeDevice-1-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[ikea_air_quality_monitor][sensor.alpstuga_air_quality_monitor_humidity-state]
@@ -12429,7 +12429,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ALPSTUGA air quality monitor Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.alpstuga_air_quality_monitor_humidity',
@@ -12475,7 +12475,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000025-MatterNodeDevice-1-PM25Sensor-1066-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[ikea_air_quality_monitor][sensor.alpstuga_air_quality_monitor_pm2_5-state]
@@ -12484,7 +12484,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ALPSTUGA air quality monitor PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.alpstuga_air_quality_monitor_pm2_5',
@@ -12864,7 +12864,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000089-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[ikea_bilresa_dual_button][sensor.bilresa_dual_button_battery-state]
@@ -12873,7 +12873,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'BILRESA dual button Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.bilresa_dual_button_battery',
@@ -13412,7 +13412,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000002-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[ikea_scroll_wheel][sensor.bilresa_scroll_wheel_battery-state]
@@ -13421,7 +13421,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'BILRESA scroll wheel Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.bilresa_scroll_wheel_battery',
@@ -14682,7 +14682,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000100-MatterNodeDevice-8-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[inovelli_vtm30][sensor.white_series_onoff_switch_humidity-state]
@@ -14691,7 +14691,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'White Series OnOff Switch Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.white_series_onoff_switch_humidity',
@@ -16043,7 +16043,7 @@
'supported_features': 0,
'translation_key': 'activated_carbon_filter_condition',
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-ActivatedCarbonFilterCondition-114-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_activated_carbon_filter_condition-state]
@@ -16051,7 +16051,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Activated carbon filter condition',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_activated_carbon_filter_condition',
@@ -16235,7 +16235,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-CarbonDioxideSensor-1037-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_carbon_dioxide-state]
@@ -16244,7 +16244,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Carbon dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_carbon_dioxide',
@@ -16290,7 +16290,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-CarbonMonoxideSensor-1036-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_carbon_monoxide-state]
@@ -16299,7 +16299,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_monoxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Carbon monoxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_carbon_monoxide',
@@ -16345,7 +16345,7 @@
'supported_features': 0,
'translation_key': 'hepa_filter_condition',
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-HepaFilterCondition-113-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_hepa_filter_condition-state]
@@ -16353,7 +16353,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier HEPA filter condition',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_hepa_filter_condition',
@@ -16399,7 +16399,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-4-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_humidity-state]
@@ -16408,7 +16408,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_humidity',
@@ -16454,7 +16454,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-NitrogenDioxideSensor-1043-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_nitrogen_dioxide-state]
@@ -16463,7 +16463,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'nitrogen_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Nitrogen dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_nitrogen_dioxide',
@@ -16509,7 +16509,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-OzoneConcentrationSensor-1045-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_ozone-state]
@@ -16518,7 +16518,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'ozone',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Ozone',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_ozone',
@@ -16564,7 +16564,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-PM1Sensor-1068-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_pm1-state]
@@ -16573,7 +16573,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm1',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier PM1',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_pm1',
@@ -16619,7 +16619,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-PM10Sensor-1069-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_pm10-state]
@@ -16628,7 +16628,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier PM10',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_pm10',
@@ -16674,7 +16674,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-PM25Sensor-1066-0',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_pm2_5-state]
@@ -16683,7 +16683,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_pm2_5',
@@ -17067,7 +17067,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-2-TotalVolatileOrganicCompoundsSensor-1070-0',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensors[mock_air_purifier][sensor.mock_air_purifier_volatile_organic_compounds_parts-state]
@@ -17076,7 +17076,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'volatile_organic_compounds_parts',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Air Purifier Volatile organic compounds parts',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_air_purifier_volatile_organic_compounds_parts',
@@ -17249,7 +17249,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000019-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_battery_storage][sensor.mock_battery_storage_battery-state]
@@ -17258,7 +17258,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Battery Storage Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_battery_storage_battery',
@@ -18815,7 +18815,7 @@
'supported_features': 0,
'translation_key': 'activated_carbon_filter_condition',
'unique_id': '00000000000004D2-0000000000000049-MatterNodeDevice-1-ActivatedCarbonFilterCondition-114-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_extractor_hood][sensor.mock_extractor_hood_activated_carbon_filter_condition-state]
@@ -18823,7 +18823,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Extractor hood Activated carbon filter condition',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_extractor_hood_activated_carbon_filter_condition',
@@ -18939,7 +18939,7 @@
'supported_features': 0,
'translation_key': 'hepa_filter_condition',
'unique_id': '00000000000004D2-0000000000000049-MatterNodeDevice-1-HepaFilterCondition-113-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_extractor_hood][sensor.mock_extractor_hood_hepa_filter_condition-state]
@@ -18947,7 +18947,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Extractor hood HEPA filter condition',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_extractor_hood_hepa_filter_condition',
@@ -19656,7 +19656,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000015-MatterNodeDevice-1-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_humidity_sensor][sensor.mock_humidity_sensor_humidity-state]
@@ -19665,7 +19665,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Humidity Sensor Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_humidity_sensor_humidity',
@@ -22707,7 +22707,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000065-MatterNodeDevice-1-SoilMoistureSensor-1072-1',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_soil_sensor][sensor.mock_soil_sensor_moisture-state]
@@ -22716,7 +22716,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'moisture',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Soil Sensor Moisture',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_soil_sensor_moisture',
@@ -23410,14 +23410,14 @@
'supported_features': 0,
'translation_key': 'pi_heating_demand',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatPIHeatingDemand-513-8',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Thermostat Heating demand',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_heating_demand',
@@ -24348,14 +24348,14 @@
'supported_features': 0,
'translation_key': 'window_covering_target_position',
'unique_id': '00000000000004D2-0000000000000032-MatterNodeDevice-1-TargetPositionLiftPercent100ths-258-11',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_window_covering_full][sensor.mock_full_window_covering_target_opening_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Full Window Covering Target opening position',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_full_window_covering_target_opening_position',
@@ -24685,14 +24685,14 @@
'supported_features': 0,
'translation_key': 'window_covering_target_position',
'unique_id': '00000000000004D2-0000000000000027-MatterNodeDevice-1-TargetPositionLiftPercent100ths-258-11',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_window_covering_pa_lift][sensor.longan_link_wncv_da01_target_opening_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Longan link WNCV DA01 Target opening position',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.longan_link_wncv_da01_target_opening_position',
@@ -25451,7 +25451,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_battery-state]
@@ -25460,7 +25460,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Robotic Vacuum Cleaner Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.robotic_vacuum_cleaner_battery',
@@ -27905,7 +27905,7 @@
'supported_features': 0,
'translation_key': 'evse_soc',
'unique_id': '00000000000004D2-0000000000000017-MatterNodeDevice-1-EnergyEvseStateOfCharge-153-48',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[silabs_evse_charging][sensor.evse_state_of_charge-state]
@@ -27914,7 +27914,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'evse State of charge',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.evse_state_of_charge',
@@ -30846,7 +30846,7 @@
'supported_features': 0,
'translation_key': 'tank_percentage',
'unique_id': '00000000000004D2-0000000000000039-MatterNodeDevice-2-WaterHeaterManagementTankPercentage-148-4',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[silabs_water_heater][sensor.water_heater_hot_water_level-state]
@@ -30854,7 +30854,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Water Heater Hot water level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.water_heater_hot_water_level',
@@ -31775,7 +31775,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000031-MatterNodeDevice-1-HumiditySensor-1029-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[tado_smart_radiator_thermostat_x][sensor.smart_radiator_thermostat_x_humidity-state]
@@ -31784,7 +31784,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart Radiator Thermostat X Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.smart_radiator_thermostat_x_humidity',
@@ -32395,7 +32395,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000007A-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[zemismart_mt25b][sensor.zemismart_mt25b_roller_motor_battery-state]
@@ -32404,7 +32404,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Zemismart MT25B Roller Motor Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.zemismart_mt25b_roller_motor_battery',
@@ -14,7 +14,7 @@
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_category': None,
'entity_id': 'sensor.mealie_categories',
'has_entity_name': True,
'hidden_by': None,
@@ -68,7 +68,7 @@
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_category': None,
'entity_id': 'sensor.mealie_recipes',
'has_entity_name': True,
'hidden_by': None,
@@ -122,7 +122,7 @@
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_category': None,
'entity_id': 'sensor.mealie_tags',
'has_entity_name': True,
'hidden_by': None,
@@ -176,7 +176,7 @@
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_category': None,
'entity_id': 'sensor.mealie_tools',
'has_entity_name': True,
'hidden_by': None,
@@ -230,7 +230,7 @@
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_category': None,
'entity_id': 'sensor.mealie_users',
'has_entity_name': True,
'hidden_by': None,
@@ -1987,7 +1987,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678-1698/15702199#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.maple_residence_garden_radiator_battery_level-state]
@@ -1996,7 +1996,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Garden Radiator Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.maple_residence_garden_radiator_battery_level',
@@ -2447,14 +2447,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678-1698/4080031-core:TargetClosureState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.maple_residence_hallway_shutter_target_closure-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hallway Shutter Target closure',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.maple_residence_hallway_shutter_target_closure',
@@ -3016,7 +3016,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678-1698/9253412#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.maple_residence_living_room_radiator_battery_level-state]
@@ -3025,7 +3025,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Living Room Radiator Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.maple_residence_living_room_radiator_battery_level',
@@ -4087,14 +4087,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678-1698/141613-core:TargetClosureState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.maple_residence_nursery_shutter_target_closure-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nursery Shutter Target closure',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.maple_residence_nursery_shutter_target_closure',
@@ -4361,14 +4361,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678-1698/9740264-core:TargetClosureState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.maple_residence_office_shutter_target_closure-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Office Shutter Target closure',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.maple_residence_office_shutter_target_closure',
@@ -4637,7 +4637,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678-1698/9187218#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.maple_residence_study_radiator_battery_level-state]
@@ -4646,7 +4646,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study Radiator Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.maple_residence_study_radiator_battery_level',
@@ -5518,7 +5518,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/6360455#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.attic_heater_battery_level-state]
@@ -5527,7 +5527,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Attic Heater Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.attic_heater_battery_level',
@@ -6117,14 +6117,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/3868695-core:TargetClosureState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.conservatory_screen_target_closure-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Conservatory Screen Target closure',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.conservatory_screen_target_closure',
@@ -6170,7 +6170,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/10832611#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.dining_room_thermostat_battery_level-state]
@@ -6179,7 +6179,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Dining Room Thermostat Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.dining_room_thermostat_battery_level',
@@ -6771,7 +6771,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/3000744#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.garage_radiator_battery_level-state]
@@ -6780,7 +6780,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Garage Radiator Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.garage_radiator_battery_level',
@@ -7252,7 +7252,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/10074960#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.garage_thermostat_battery_level-state]
@@ -7261,7 +7261,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Garage Thermostat Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.garage_thermostat_battery_level',
@@ -7491,7 +7491,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/13876191#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.garden_radiator_battery_level-state]
@@ -7500,7 +7500,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Garden Radiator Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.garden_radiator_battery_level',
@@ -8576,7 +8576,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/13659989#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.hallway_radiator_battery_level-state]
@@ -8585,7 +8585,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hallway Radiator Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.hallway_radiator_battery_level',
@@ -9537,14 +9537,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/3904805-core:TargetClosureState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.library_screen_target_closure-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Library Screen Target closure',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.library_screen_target_closure',
@@ -9710,7 +9710,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/9749990#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.living_room_thermostat_battery_level-state]
@@ -9719,7 +9719,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Living Room Thermostat Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.living_room_thermostat_battery_level',
@@ -9949,7 +9949,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/1292684#1-core:BatteryLevelState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.patio_thermostat_battery_level-state]
@@ -9958,7 +9958,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Patio Thermostat Battery level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.patio_thermostat_battery_level',
@@ -10546,14 +10546,14 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'io://1234-5678--9373/5632438-core:TargetClosureState',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_snapshot[local_somfy_tahoma_switch_europe_3.json][sensor.terrace_awning_target_closure-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Terrace Awning Target closure',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.terrace_awning_target_closure',
@@ -203,7 +203,7 @@
'supported_features': 0,
'translation_key': 'cpu_usage_total',
'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_cpu_usage_total',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_cpu_usage_total-state]
@@ -211,7 +211,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 CPU usage total',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_cpu_usage_total',
@@ -432,7 +432,7 @@
'supported_features': 0,
'translation_key': 'memory_usage_percentage',
'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage-state]
@@ -440,7 +440,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Memory usage percentage',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage',
@@ -730,7 +730,7 @@
'supported_features': 0,
'translation_key': 'cpu_usage_total',
'unique_id': 'portainer_test_entry_123_focused_einstein_cpu_usage_total',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.focused_einstein_cpu_usage_total-state]
@@ -738,7 +738,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'focused_einstein CPU usage total',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.focused_einstein_cpu_usage_total',
@@ -959,7 +959,7 @@
'supported_features': 0,
'translation_key': 'memory_usage_percentage',
'unique_id': 'portainer_test_entry_123_focused_einstein_memory_usage_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.focused_einstein_memory_usage_percentage-state]
@@ -967,7 +967,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'focused_einstein Memory usage percentage',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.focused_einstein_memory_usage_percentage',
@@ -1084,7 +1084,7 @@
'supported_features': 0,
'translation_key': 'cpu_usage_total',
'unique_id': 'portainer_test_entry_123_funny_chatelet_cpu_usage_total',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.funny_chatelet_cpu_usage_total-state]
@@ -1092,7 +1092,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'funny_chatelet CPU usage total',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.funny_chatelet_cpu_usage_total',
@@ -1313,7 +1313,7 @@
'supported_features': 0,
'translation_key': 'memory_usage_percentage',
'unique_id': 'portainer_test_entry_123_funny_chatelet_memory_usage_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.funny_chatelet_memory_usage_percentage-state]
@@ -1321,7 +1321,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'funny_chatelet Memory usage percentage',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.funny_chatelet_memory_usage_percentage',
@@ -2533,7 +2533,7 @@
'supported_features': 0,
'translation_key': 'cpu_usage_total',
'unique_id': 'portainer_test_entry_123_practical_morse_cpu_usage_total',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.practical_morse_cpu_usage_total-state]
@@ -2541,7 +2541,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'practical_morse CPU usage total',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.practical_morse_cpu_usage_total',
@@ -2762,7 +2762,7 @@
'supported_features': 0,
'translation_key': 'memory_usage_percentage',
'unique_id': 'portainer_test_entry_123_practical_morse_memory_usage_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.practical_morse_memory_usage_percentage-state]
@@ -2770,7 +2770,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'practical_morse Memory usage percentage',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.practical_morse_memory_usage_percentage',
@@ -2887,7 +2887,7 @@
'supported_features': 0,
'translation_key': 'cpu_usage_total',
'unique_id': 'portainer_test_entry_123_serene_banach_cpu_usage_total',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.serene_banach_cpu_usage_total-state]
@@ -2895,7 +2895,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'serene_banach CPU usage total',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.serene_banach_cpu_usage_total',
@@ -3116,7 +3116,7 @@
'supported_features': 0,
'translation_key': 'memory_usage_percentage',
'unique_id': 'portainer_test_entry_123_serene_banach_memory_usage_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.serene_banach_memory_usage_percentage-state]
@@ -3124,7 +3124,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'serene_banach Memory usage percentage',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.serene_banach_memory_usage_percentage',
@@ -3241,7 +3241,7 @@
'supported_features': 0,
'translation_key': 'cpu_usage_total',
'unique_id': 'portainer_test_entry_123_stoic_turing_cpu_usage_total',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.stoic_turing_cpu_usage_total-state]
@@ -3249,7 +3249,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'stoic_turing CPU usage total',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.stoic_turing_cpu_usage_total',
@@ -3470,7 +3470,7 @@
'supported_features': 0,
'translation_key': 'memory_usage_percentage',
'unique_id': 'portainer_test_entry_123_stoic_turing_memory_usage_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.stoic_turing_memory_usage_percentage-state]
@@ -3478,7 +3478,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'stoic_turing Memory usage percentage',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.stoic_turing_memory_usage_percentage',
@@ -267,7 +267,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'ctd_000001_94-1',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor[sensor.kitchen_vochtigheid_keuken-state]
@@ -276,7 +276,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Vochtigheid Keuken',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.kitchen_vochtigheid_keuken',
@@ -441,7 +441,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'ctd_000001_224',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensor[sensor.luchtsensor-state]
@@ -450,7 +450,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Luchtsensor',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.luchtsensor',
@@ -615,7 +615,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'ctd_000001_82',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensor[sensor.tuin_luchtkwaliteit-state]
@@ -624,7 +624,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Luchtkwaliteit',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.tuin_luchtkwaliteit',
@@ -4765,46 +4765,3 @@ async def test_import_statistics_with_last_reset(
},
]
}
async def test_entity_options_ws(
hass: HomeAssistant,
async_setup_recorder_instance: RecorderInstanceGenerator,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test recorder entity options WS commands."""
client = await hass_ws_client()
await async_setup_recorder_instance(hass, {"exclude": {"domains": "test2"}})
# Test getting a single entity's settings
await client.send_json_auto_id(
{
"type": "recorder/entity_options/get",
"entity_id": "test.recorder",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {"recording_disabled_by": None}
await client.send_json_auto_id(
{
"type": "recorder/entity_options/get",
"entity_id": "test2.recorder",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {"recording_disabled_by": "user"}
# Test getting settings for an unknown entity
await client.send_json_auto_id(
{
"type": "recorder/entity_options/get",
"entity_id": "unknown.entity",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {"recording_disabled_by": None}
@@ -20,7 +20,6 @@ def mock_config_entry() -> MockConfigEntry:
data=CONF_DATA,
options=CONF_OPTIONS,
unique_id=ACCOUNT_1,
version=2,
)
@@ -32,7 +32,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <SteamSensor.ACCOUNT: 'account'>,
'unique_id': '12345678901234567_account',
'unique_id': 'sensor.steam_12345678901234567',
'unit_of_measurement': None,
})
# ---
+1 -41
View File
@@ -7,11 +7,8 @@ import steam.api
from homeassistant.components.steam_online.const import DEFAULT_NAME, DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import ACCOUNT_1, ACCOUNT_NAME_1, CONF_DATA, CONF_OPTIONS
from homeassistant.helpers import device_registry as dr
from tests.common import MockConfigEntry
@@ -98,40 +95,3 @@ async def test_device_info(
assert device.identifiers == {(DOMAIN, config_entry.entry_id)}
assert device.manufacturer == DEFAULT_NAME
assert device.name == DEFAULT_NAME
@pytest.mark.usefixtures("steam_api")
async def test_migrate_entry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test entry migration."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONF_DATA,
options=CONF_OPTIONS,
unique_id=ACCOUNT_1,
version=1,
)
config_entry.add_to_hass(hass)
assert config_entry.version == 1
sensor = entity_registry.async_get_or_create(
domain=Platform.SENSOR,
platform=DOMAIN,
unique_id=f"sensor.steam_{ACCOUNT_1}",
config_entry=config_entry,
original_name=ACCOUNT_NAME_1,
)
assert sensor.unique_id == f"sensor.steam_{ACCOUNT_1}"
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.version == 2
assert (sensor := entity_registry.async_get(sensor.entity_id))
assert sensor.unique_id == f"{ACCOUNT_1}_account"
+96 -162
View File
@@ -16,11 +16,8 @@ from homeassistant.util import dt as dt_util
from tests.typing import WebSocketGenerator
# San Diego (default test location), Kotzebue, Alaska (just inside the Arctic
# Circle - brief midnight sun in June) and Longyearbyen, Svalbard (deep polar -
# long polar night in December).
# San Diego (default test location) and Longyearbyen, Svalbard (deep polar).
_SAN_DIEGO = (32.87336, -117.22743, "US/Pacific")
_KOTZEBUE = (66.8983, -162.5966, "America/Anchorage")
_SVALBARD = (78.22, 15.65, "Europe/Oslo")
_TWILIGHT_TYPES = ("any", "civil", "nautical", "astronomical")
@@ -41,8 +38,8 @@ def _find_run_id(traces, trace_type, item_id):
return None
async def _get_automation_condition_trace(hass_ws_client, automation_id):
"""Return the condition trace for a given automation."""
async def assert_automation_condition_trace(hass_ws_client, automation_id, expected):
"""Test the result of automation condition."""
msg_id = 1
def next_id():
@@ -74,15 +71,8 @@ async def _get_automation_condition_trace(hass_ws_client, automation_id):
assert response["success"]
trace = response["result"]
assert len(trace["trace"]["condition/0"]) == 1
return trace["trace"]["condition/0"][0]
async def assert_automation_condition_trace(hass_ws_client, automation_id, expected):
"""Test the result of automation condition."""
condition_trace = await _get_automation_condition_trace(
hass_ws_client, automation_id
)
assert condition_trace["result"] == expected
condition_trace = trace["trace"]["condition/0"][0]["result"]
assert condition_trace == expected
async def test_if_action_before_sunrise_no_offset(
@@ -947,14 +937,14 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
) -> None:
"""Test if action was before sunrise.
Local timezone: Alaska time (America/Anchorage)
Location: Kotzebue, Alaska, whose far-west longitude skews local time by
~3 hours, so in late July sunrise is ~04:48 local. Before sunrise is true
from local midnight until sunrise.
Local timezone: Alaska time
Location: Kotzebue, which has a very skewed local timezone with sunrise
at 7 AM and sunset at 3AM during summer
After sunrise is true from sunrise until midnight, local time.
"""
await hass.config.async_set_time_zone("America/Anchorage")
hass.config.latitude = 66.8983
hass.config.longitude = -162.5966
hass.config.latitude = 66.5
hass.config.longitude = 162.4
await async_setup_component(
hass,
automation.DOMAIN,
@@ -971,9 +961,10 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
},
)
# sunrise: 2015-07-24 04:48:24 local = 2015-07-24 12:48:24 UTC
# sunrise: 2015-07-24 07:21:12 local, sunset: 2015-07-25 03:13:33 local
# sunrise: 2015-07-24 15:21:12 UTC, sunset: 2015-07-25 11:13:33 UTC
# now = sunrise + 1s -> 'before sunrise' not true
now = datetime(2015, 7, 24, 12, 48, 25, tzinfo=dt_util.UTC)
now = datetime(2015, 7, 24, 15, 21, 13, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -981,11 +972,11 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_before": "2015-07-24T12:48:24.249497+00:00"},
{"result": False, "wanted_time_before": "2015-07-24T15:16:46.975735+00:00"},
)
# now = sunrise - 1h -> 'before sunrise' true
now = datetime(2015, 7, 24, 11, 48, 24, tzinfo=dt_util.UTC)
now = datetime(2015, 7, 24, 14, 21, 12, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -993,7 +984,7 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_before": "2015-07-24T12:48:24.249497+00:00"},
{"result": True, "wanted_time_before": "2015-07-24T15:16:46.975735+00:00"},
)
# now = local midnight -> 'before sunrise' true
@@ -1005,7 +996,7 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_before": "2015-07-24T12:48:24.249497+00:00"},
{"result": True, "wanted_time_before": "2015-07-24T15:16:46.975735+00:00"},
)
# now = local midnight - 1s -> 'before sunrise' not true
@@ -1017,7 +1008,7 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_before": "2015-07-23T12:43:32.413351+00:00"},
{"result": False, "wanted_time_before": "2015-07-23T15:12:19.155123+00:00"},
)
@@ -1028,14 +1019,14 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
) -> None:
"""Test if action was after sunrise.
Local timezone: Alaska time (America/Anchorage)
Location: Kotzebue, Alaska, whose far-west longitude skews local time by
~3 hours, so in late July sunrise is ~04:48 local. After sunrise is true
from sunrise until local midnight.
Local timezone: Alaska time
Location: Kotzebue, which has a very skewed local timezone with sunrise
at 7 AM and sunset at 3AM during summer
Before sunrise is true from midnight until sunrise, local time.
"""
await hass.config.async_set_time_zone("America/Anchorage")
hass.config.latitude = 66.8983
hass.config.longitude = -162.5966
hass.config.latitude = 66.5
hass.config.longitude = 162.4
await async_setup_component(
hass,
automation.DOMAIN,
@@ -1052,9 +1043,10 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
},
)
# sunrise: 2015-07-24 04:48:24 local = 2015-07-24 12:48:24 UTC
# now = sunrise + 1s -> 'after sunrise' true
now = datetime(2015, 7, 24, 12, 48, 25, tzinfo=dt_util.UTC)
# sunrise: 2015-07-24 07:21:12 local, sunset: 2015-07-25 03:13:33 local
# sunrise: 2015-07-24 15:21:12 UTC, sunset: 2015-07-25 11:13:33 UTC
# now = sunrise -> 'after sunrise' true
now = datetime(2015, 7, 24, 15, 21, 12, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -1062,11 +1054,11 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_after": "2015-07-24T12:48:24.249497+00:00"},
{"result": True, "wanted_time_after": "2015-07-24T15:16:46.975735+00:00"},
)
# now = sunrise - 1h -> 'after sunrise' not true
now = datetime(2015, 7, 24, 11, 48, 24, tzinfo=dt_util.UTC)
now = datetime(2015, 7, 24, 14, 21, 12, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -1074,7 +1066,7 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_after": "2015-07-24T12:48:24.249497+00:00"},
{"result": False, "wanted_time_after": "2015-07-24T15:16:46.975735+00:00"},
)
# now = local midnight -> 'after sunrise' not true
@@ -1086,7 +1078,7 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_after": "2015-07-24T12:48:24.249497+00:00"},
{"result": False, "wanted_time_after": "2015-07-24T15:16:46.975735+00:00"},
)
# now = local midnight - 1s -> 'after sunrise' true
@@ -1098,7 +1090,7 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_after": "2015-07-23T12:43:32.413351+00:00"},
{"result": True, "wanted_time_after": "2015-07-23T15:12:19.155123+00:00"},
)
@@ -1107,17 +1099,16 @@ async def test_if_action_before_sunset_no_offset_kotzebue(
hass_ws_client: WebSocketGenerator,
service_calls: list[ServiceCall],
) -> None:
"""Test if action was before sunset on a day with two sunsets.
"""Test if action was before sunrise.
Local timezone: Alaska time (America/Anchorage)
Location: Kotzebue, Alaska. On 2015-08-07 (local) the sun sets twice - at
00:03 and again at 23:59 - because solar midnight falls near local midnight.
The condition tracks the day's (late) sunset, so 'before sunset' stays true
across the early sunset and only turns false after the late one.
Local timezone: Alaska time
Location: Kotzebue, which has a very skewed local timezone with sunrise
at 7 AM and sunset at 3AM during summer
Before sunset is true from midnight until sunset, local time.
"""
await hass.config.async_set_time_zone("America/Anchorage")
hass.config.latitude = 66.8983
hass.config.longitude = -162.5966
hass.config.latitude = 66.5
hass.config.longitude = 162.4
await async_setup_component(
hass,
automation.DOMAIN,
@@ -1134,9 +1125,22 @@ async def test_if_action_before_sunset_no_offset_kotzebue(
},
)
# 2015-08-07 local has two sunsets: 00:03 (08:03 UTC) and 23:59 (08-08 07:59 UTC)
# now = local midnight -> 'before sunset' true
now = datetime(2015, 8, 7, 8, 0, 0, tzinfo=dt_util.UTC)
# sunrise: 2015-07-24 07:21:12 local, sunset: 2015-07-25 03:13:33 local
# sunrise: 2015-07-24 15:21:12 UTC, sunset: 2015-07-25 11:13:33 UTC
# now = sunset + 1s -> 'before sunset' not true
now = datetime(2015, 7, 25, 11, 13, 34, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 0
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_before": "2015-07-25T11:13:32.501837+00:00"},
)
# now = sunset - 1h-> 'before sunset' true
now = datetime(2015, 7, 25, 10, 13, 33, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -1144,11 +1148,11 @@ async def test_if_action_before_sunset_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_before": "2015-08-08T07:59:25.982224+00:00"},
{"result": True, "wanted_time_before": "2015-07-25T11:13:32.501837+00:00"},
)
# now = first (early) sunset + 1s -> still 'before sunset' (tracks the late one)
now = datetime(2015, 8, 7, 8, 3, 43, tzinfo=dt_util.UTC)
# now = local midnight -> 'before sunrise' true
now = datetime(2015, 7, 24, 8, 0, 0, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -1156,31 +1160,19 @@ async def test_if_action_before_sunset_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_before": "2015-08-08T07:59:25.982224+00:00"},
{"result": True, "wanted_time_before": "2015-07-24T11:17:54.446913+00:00"},
)
# now = late sunset - 1h -> 'before sunset' true
now = datetime(2015, 8, 8, 6, 59, 25, tzinfo=dt_util.UTC)
# now = local midnight - 1s -> 'before sunrise' not true
now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 3
assert len(service_calls) == 2
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_before": "2015-08-08T07:59:25.982224+00:00"},
)
# now = late sunset + 1s -> 'before sunset' not true
now = datetime(2015, 8, 8, 7, 59, 26, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 3
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_before": "2015-08-08T07:59:25.982224+00:00"},
{"result": False, "wanted_time_before": "2015-07-23T11:22:18.467277+00:00"},
)
@@ -1189,17 +1181,16 @@ async def test_if_action_after_sunset_no_offset_kotzebue(
hass_ws_client: WebSocketGenerator,
service_calls: list[ServiceCall],
) -> None:
"""Test if action was after sunset on a day with two sunsets.
"""Test if action was after sunrise.
Local timezone: Alaska time (America/Anchorage)
Location: Kotzebue, Alaska. On 2015-08-07 (local) the sun sets twice - at
00:03 and again at 23:59. The condition tracks the day's (late) sunset, so
'after sunset' is false right after the early sunset and only true in the
short window after the late sunset before local midnight.
Local timezone: Alaska time
Location: Kotzebue, which has a very skewed local timezone with sunrise
at 7 AM and sunset at 3AM during summer
After sunset is true from sunset until midnight, local time.
"""
await hass.config.async_set_time_zone("America/Anchorage")
hass.config.latitude = 66.8983
hass.config.longitude = -162.5966
hass.config.latitude = 66.5
hass.config.longitude = 162.4
await async_setup_component(
hass,
automation.DOMAIN,
@@ -1216,33 +1207,10 @@ async def test_if_action_after_sunset_no_offset_kotzebue(
},
)
# 2015-08-07 local has two sunsets: 00:03 (08:03 UTC) and 23:59 (08-08 07:59 UTC)
# now = first (early) sunset + 1s -> 'after sunset' not true (tracks the late one)
now = datetime(2015, 8, 7, 8, 4, 0, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 0
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_after": "2015-08-08T07:59:25.982224+00:00"},
)
# now = late sunset - 1s -> 'after sunset' not true
now = datetime(2015, 8, 8, 7, 59, 25, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 0
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_after": "2015-08-08T07:59:25.982224+00:00"},
)
# now = late sunset + 1s -> 'after sunset' true
now = datetime(2015, 8, 8, 7, 59, 27, tzinfo=dt_util.UTC)
# sunrise: 2015-07-24 07:21:12 local, sunset: 2015-07-25 03:13:33 local
# sunrise: 2015-07-24 15:21:12 UTC, sunset: 2015-07-25 11:13:33 UTC
# now = sunset -> 'after sunset' true
now = datetime(2015, 7, 25, 11, 13, 33, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -1250,11 +1218,11 @@ async def test_if_action_after_sunset_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_after": "2015-08-08T07:59:25.982224+00:00"},
{"result": True, "wanted_time_after": "2015-07-25T11:13:32.501837+00:00"},
)
# now = local midnight (next day) -> 'after sunset' not true
now = datetime(2015, 8, 8, 8, 0, 1, tzinfo=dt_util.UTC)
# now = sunset - 1s -> 'after sunset' not true
now = datetime(2015, 7, 25, 11, 13, 32, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
@@ -1262,65 +1230,31 @@ async def test_if_action_after_sunset_no_offset_kotzebue(
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "wanted_time_after": "2015-08-09T07:55:10.646523+00:00"},
)
@pytest.mark.parametrize(
("location", "now", "event"),
[
# Midnight sun at Kotzebue (early June to early July): the sun neither
# rises nor sets, so neither a sunrise nor a sunset condition can be met.
(_KOTZEBUE, datetime(2015, 6, 15, 12, tzinfo=dt_util.UTC), SUN_EVENT_SUNSET),
(_KOTZEBUE, datetime(2015, 6, 15, 12, tzinfo=dt_util.UTC), SUN_EVENT_SUNRISE),
# Polar night at Svalbard: the sun neither rises nor sets here either.
(_SVALBARD, datetime(2015, 12, 15, 12, tzinfo=dt_util.UTC), SUN_EVENT_SUNSET),
(_SVALBARD, datetime(2015, 12, 15, 12, tzinfo=dt_util.UTC), SUN_EVENT_SUNRISE),
],
)
async def test_if_action_no_sun_event_in_polar_regions(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
service_calls: list[ServiceCall],
location: tuple[float, float, str],
now: datetime,
event: str,
) -> None:
"""Test a sun condition where the requested event never occurs.
During midnight sun and polar night the sun neither rises nor sets, so
``get_astral_event_date`` returns None for the requested event. The
condition cannot be satisfied and reports "no sunrise today" / "no sunset
today" instead of raising.
"""
latitude, longitude, time_zone = location
await hass.config.async_set_time_zone(time_zone)
hass.config.latitude = latitude
hass.config.longitude = longitude
await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"id": "sun",
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
"condition": "sun",
"options": {"after": event},
},
"action": {"service": "test.automation"},
}
},
{"result": False, "wanted_time_after": "2015-07-25T11:13:32.501837+00:00"},
)
# now = local midnight -> 'after sunset' not true
now = datetime(2015, 7, 24, 8, 0, 1, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 0
assert len(service_calls) == 1
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": False, "message": f"no {event} today"},
{"result": False, "wanted_time_after": "2015-07-24T11:17:54.446913+00:00"},
)
# now = local midnight - 1s -> 'after sunset' true
now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC)
with freeze_time(now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 2
await assert_automation_condition_trace(
hass_ws_client,
"sun",
{"result": True, "wanted_time_after": "2015-07-23T11:22:18.467277+00:00"},
)
-41
View File
@@ -346,12 +346,6 @@ async def test_dawn_defaults_to_civil(
_SVALBARD = (78.22, 15.65, "Europe/Oslo")
_KOTZEBUE = (66.8983, -162.5966, "America/Anchorage")
# A two-sunrise day is the mirror of Kotzebue's two-sunset day: it needs solar
# noon (not midnight) near local midnight, which no real location has because
# time zones keep solar noon near local noon. This synthetic location forces it
# with a polar latitude on a deliberately ~12 h-offset time zone.
_TWO_SUNRISE_LOCATION = (66.5, -32.5, "America/Anchorage")
@pytest.mark.parametrize(
("location", "now", "trigger_key", "astral_event", "options", "depression"),
@@ -458,41 +452,6 @@ async def test_two_sunsets_on_one_day_at_kotzebue(
assert len(service_calls) == 2
async def test_two_sunrises_on_one_day(
hass: HomeAssistant, service_calls: list[ServiceCall]
) -> None:
"""Test both sunrises fire on a calendar day that has two.
The mirror of the two-sunset case: a synthetic polar location on a
deliberately offset time zone puts solar noon near local midnight, so
2015-03-07 (local) has two sunrises ~24 h apart - one just after midnight
and one just before - and the scheduler must fire for both.
"""
latitude, longitude, time_zone = _TWO_SUNRISE_LOCATION
await hass.config.async_set_time_zone(time_zone)
await hass.config.async_update(latitude=latitude, longitude=longitude, elevation=0)
# 2015-03-07 09:02:36 UTC (00:02 local) and 2015-03-08 08:58:43 UTC (23:58 local)
now = datetime(2015, 3, 7, 9, tzinfo=dt_util.UTC)
with freeze_time(now) as freezer:
await _arm_automation(hass, {"platform": "sun.sunrise"}, {})
first = get_astral_event_next(hass, "sunrise", now)
second = get_astral_event_next(hass, "sunrise", first)
# Two sunrises ~24 h apart that share one local calendar day.
assert dt_util.as_local(first).date() == dt_util.as_local(second).date()
assert timedelta(hours=23) < second - first < timedelta(hours=25)
freezer.move_to(first + timedelta(seconds=1))
async_fire_time_changed(hass, first + timedelta(seconds=1))
await hass.async_block_till_done()
assert len(service_calls) == 1
freezer.move_to(second + timedelta(seconds=1))
async_fire_time_changed(hass, second + timedelta(seconds=1))
await hass.async_block_till_done()
assert len(service_calls) == 2
@pytest.mark.parametrize(
("trigger_key", "astral_event", "now", "above_horizon"),
[
@@ -1044,7 +1044,7 @@
'object_id_base': 'Carbon dioxide',
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'suggested_unit_of_measurement': 'ppm',
}),
}),
'original_device_class': <SensorDeviceClass.CO2: 'carbon_dioxide'>,
@@ -1056,7 +1056,7 @@
'supported_features': 0,
'translation_key': 'carbon_dioxide',
'unique_id': 'tuya.iks13mcaiyie3rryjb2occo2_value',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_platform_setup_and_discovery[sensor.aqi_carbon_dioxide-state]
@@ -1220,7 +1220,7 @@
'supported_features': 0,
'translation_key': 'pm10',
'unique_id': 'tuya.iks13mcaiyie3rryjb2ocpm10',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_platform_setup_and_discovery[sensor.aqi_pm10-state]
@@ -1229,7 +1229,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'AQI PM10',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.aqi_pm10',
@@ -1266,7 +1266,7 @@
'object_id_base': 'PM2.5',
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'suggested_unit_of_measurement': 'μg/m³',
}),
}),
'original_device_class': <SensorDeviceClass.PM25: 'pm25'>,
@@ -1278,7 +1278,7 @@
'supported_features': 0,
'translation_key': 'pm25',
'unique_id': 'tuya.iks13mcaiyie3rryjb2ocpm25_value',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_platform_setup_and_discovery[sensor.aqi_pm2_5-state]
@@ -1287,7 +1287,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'AQI PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.aqi_pm2_5',
@@ -1391,7 +1391,7 @@
'supported_features': 0,
'translation_key': 'voc',
'unique_id': 'tuya.iks13mcaiyie3rryjb2ocvoc_value',
'unit_of_measurement': <UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER: 'mg/m³'>,
'unit_of_measurement': 'mg/m³',
})
# ---
# name: test_platform_setup_and_discovery[sensor.aqi_volatile_organic_compounds-state]
@@ -1400,7 +1400,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'volatile_organic_compounds',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'AQI Volatile organic compounds',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER: 'mg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'mg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.aqi_volatile_organic_compounds',
@@ -5985,7 +5985,7 @@
'supported_features': 0,
'translation_key': 'battery',
'unique_id': 'tuya.bFFsO8HimyAJGIj7scmbattery',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_platform_setup_and_discovery[sensor.door_garage_battery-state]
@@ -5994,7 +5994,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Door Garage Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.door_garage_battery',
@@ -12304,7 +12304,7 @@
'object_id_base': 'PM2.5',
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'suggested_unit_of_measurement': 'μg/m³',
}),
}),
'original_device_class': <SensorDeviceClass.PM25: 'pm25'>,
@@ -12316,7 +12316,7 @@
'supported_features': 0,
'translation_key': 'pm25',
'unique_id': 'tuya.yo2karkjuhzztxsfjkpm25',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_platform_setup_and_discovery[sensor.kalado_air_purifier_pm2_5-state]
@@ -12325,7 +12325,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kalado Air Purifier PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.kalado_air_purifier_pm2_5',
@@ -15899,7 +15899,7 @@
'supported_features': 0,
'translation_key': 'battery',
'unique_id': 'tuya.s3zzjdcfripbattery_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_platform_setup_and_discovery[sensor.motion_sensor_lidl_zigbee_battery-state]
@@ -15908,7 +15908,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Motion sensor lidl zigbee Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.motion_sensor_lidl_zigbee_battery',
@@ -16062,7 +16062,7 @@
'object_id_base': 'Carbon dioxide',
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'suggested_unit_of_measurement': 'ppm',
}),
}),
'original_device_class': <SensorDeviceClass.CO2: 'carbon_dioxide'>,
@@ -16074,7 +16074,7 @@
'supported_features': 0,
'translation_key': 'carbon_dioxide',
'unique_id': 'tuya.rzt2knqamsxjp8f9ycjjhco2_value',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_platform_setup_and_discovery[sensor.mt15_mt29_carbon_dioxide-state]
@@ -18843,7 +18843,7 @@
'object_id_base': 'Carbon dioxide',
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'suggested_unit_of_measurement': 'ppm',
}),
}),
'original_device_class': <SensorDeviceClass.CO2: 'carbon_dioxide'>,
@@ -18855,7 +18855,7 @@
'supported_features': 0,
'translation_key': 'carbon_dioxide',
'unique_id': 'tuya.cvowstbid97lokayjb2occo2_value',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_platform_setup_and_discovery[sensor.pth_9cw_32_carbon_dioxide-state]
@@ -19624,7 +19624,7 @@
'supported_features': 0,
'translation_key': 'battery',
'unique_id': 'tuya.gm0whbftkwbattery_percentage',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_platform_setup_and_discovery[sensor.salon_battery-state]
@@ -19633,7 +19633,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Salon Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.salon_battery',
@@ -20694,7 +20694,7 @@
'object_id_base': 'Carbon monoxide',
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'suggested_unit_of_measurement': 'ppm',
}),
}),
'original_device_class': <SensorDeviceClass.CO: 'carbon_monoxide'>,
@@ -20706,7 +20706,7 @@
'supported_features': 0,
'translation_key': 'carbon_monoxide',
'unique_id': 'tuya.swhtzki3qrz5ydchjbocco_value',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_platform_setup_and_discovery[sensor.smogo_carbon_monoxide-state]
@@ -20715,7 +20715,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_monoxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smogo Carbon monoxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.smogo_carbon_monoxide',
@@ -147,7 +147,7 @@
'supported_features': 0,
'translation_key': 'burner_modulation',
'unique_id': 'gateway0_deviceSerialVitodens300W-burner_modulation-0',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model0_burner_modulation-state]
@@ -155,7 +155,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model0 Burner modulation',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model0_burner_modulation',
@@ -1200,7 +1200,7 @@
'supported_features': 0,
'translation_key': 'zigbee_signal_strength',
'unique_id': 'gateway10_zigbee_################-zigbee_signal_strength',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model10_signal_strength-state]
@@ -1208,7 +1208,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model10 Signal strength',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model10_signal_strength',
@@ -1254,7 +1254,7 @@
'supported_features': 0,
'translation_key': 'zigbee_signal_strength',
'unique_id': 'gateway11_zigbee_################-zigbee_signal_strength',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model11_signal_strength-state]
@@ -1262,7 +1262,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model11 Signal strength',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model11_signal_strength',
@@ -5389,7 +5389,7 @@
'supported_features': 0,
'translation_key': 'primary_circuit_pump_rotation',
'unique_id': 'gateway2_################-primary_circuit_pump_rotation',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model2_primary_circuit_pump_rotation-state]
@@ -5397,7 +5397,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model2 Primary circuit pump rotation',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model2_primary_circuit_pump_rotation',
@@ -6387,7 +6387,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway4_deviceId4-pm01',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_all_entities[sensor.model4_pm1-state]
@@ -6396,7 +6396,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm1',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model4 PM1',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.model4_pm1',
@@ -6442,7 +6442,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway4_deviceId4-pm10',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_all_entities[sensor.model4_pm10-state]
@@ -6451,7 +6451,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model4 PM10',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.model4_pm10',
@@ -6497,7 +6497,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway4_deviceId4-pm02',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_all_entities[sensor.model4_pm2_5-state]
@@ -6506,7 +6506,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model4 PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.model4_pm2_5',
@@ -6552,7 +6552,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway4_deviceId4-pm04',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_all_entities[sensor.model4_pm4-state]
@@ -6561,7 +6561,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm4',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model4 PM4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.model4_pm4',
@@ -6770,7 +6770,7 @@
'supported_features': 0,
'translation_key': 'supply_humidity',
'unique_id': 'gateway4_deviceId4-supply_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model4_supply_humidity-state]
@@ -6779,7 +6779,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model4 Supply humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model4_supply_humidity',
@@ -7075,7 +7075,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway7_zigbee_d87a3bfffe5d844a-battery_level',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model7_battery-state]
@@ -7084,7 +7084,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model7 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model7_battery',
@@ -7130,7 +7130,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway7_zigbee_d87a3bfffe5d844a-room_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model7_humidity-state]
@@ -7139,7 +7139,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model7 Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model7_humidity',
@@ -7243,7 +7243,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway8_zigbee_5cc7c1fffea33a3b-room_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model8_humidity-state]
@@ -7252,7 +7252,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model8 Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model8_humidity',
@@ -7356,7 +7356,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway9_zigbee_################-battery_level',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model9_battery-state]
@@ -7365,7 +7365,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model9 Battery',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model9_battery',
@@ -7411,7 +7411,7 @@
'supported_features': 0,
'translation_key': 'zigbee_signal_strength',
'unique_id': 'gateway9_zigbee_################-zigbee_signal_strength',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model9_signal_strength-state]
@@ -7419,7 +7419,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model9 Signal strength',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model9_signal_strength',
@@ -7581,7 +7581,7 @@
'supported_features': 0,
'translation_key': 'valve_position',
'unique_id': 'gateway9_zigbee_################-valve_position',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_all_entities[sensor.model9_valve_position-state]
@@ -7589,7 +7589,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'model9 Valve position',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model9_valve_position',
-48
View File
@@ -934,54 +934,6 @@ async def test_entity_limit_per_config_entry_frees_slot_when_disabled(
assert hass.states.get("test_domain.ent3") is not None
@pytest.mark.parametrize(
"filler_ids",
[
pytest.param([], id="below_limit"),
pytest.param(["2"], id="at_limit"),
],
)
async def test_entity_limit_per_config_entry_allows_enabling_disabled_entity(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
filler_ids: list[str],
) -> None:
"""Test a user can enable a disabled entity regardless of the limit."""
config_entry = MockConfigEntry()
config_entry.add_to_hass(hass)
platform = MockEntityPlatform(hass)
platform.config_entry = config_entry
with patch.object(entity_platform, "MAX_ENABLED_ENTITIES_PER_CONFIG_ENTRY", 1):
# Register a disabled-by-default entity; fillers optionally reach the limit
await platform.async_add_entities(
[
MockEntity(
unique_id="1",
name="ent1",
entity_registry_enabled_default=False,
),
*(MockEntity(unique_id=uid, name=f"ent{uid}") for uid in filler_ids),
]
)
entity_id = entity_registry.async_get_entity_id("test_domain", "test_platform", "1")
assert entity_id is not None
assert hass.states.get("test_domain.ent1") is None
# The user enables the disabled entity
entity_registry.async_update_entity(entity_id, disabled_by=None)
# Re-adding the now-enabled entity, e.g. after a reload, adds it because it is
# already registered, even when this pushes the count over the limit
with patch.object(entity_platform, "MAX_ENABLED_ENTITIES_PER_CONFIG_ENTRY", 1):
await platform.async_add_entities([MockEntity(unique_id="1", name="ent1")])
assert hass.states.get("test_domain.ent1") is not None
assert "Reached the maximum" not in caplog.text
async def test_entity_limit_per_config_entry_allows_existing_entities(
hass: HomeAssistant,
) -> None:
+1 -8
View File
@@ -11,7 +11,6 @@ from homeassistant.components import calendar, todo
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
from homeassistant.components.intent import async_register_timer_handler
from homeassistant.components.script import ScriptConfig
from homeassistant.const import EntityStateAttribute
from homeassistant.core import Context, HomeAssistant, State, SupportsResponse
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
@@ -480,12 +479,7 @@ async def test_assist_api_prompt(
hass.states.async_set(
entry1.entity_id,
"on",
{
"friendly_name": "Kitchen",
"temperature": Decimal("0.9"),
"humidity": 65,
EntityStateAttribute.UNIT_OF_MEASUREMENT: "°C",
},
{"friendly_name": "Kitchen", "temperature": Decimal("0.9"), "humidity": 65},
)
hass.states.async_set(entry2.entity_id, "on", {"friendly_name": "Living Room"})
@@ -642,7 +636,6 @@ Live Context: An overview of the areas and the devices in this smart home:
attributes:
temperature: '0.9'
humidity: '65'
unit_of_measurement: °C
- names: Living Room
domain: light
state: 'on'
@@ -1,223 +0,0 @@
"""Tests for script.check_requirements.gate."""
from collections.abc import Callable
from types import SimpleNamespace
from unittest.mock import MagicMock
from github import GithubException
import pytest
from script.check_requirements import gate
from script.check_requirements.gate import (
decide_skip,
extract_prior_sha,
fetch_marker_comment_bodies,
)
from script.check_requirements.models import CheckRunResult
from script.check_requirements.render import render_comment
_REPO = "home-assistant/core"
_TOKEN = "test-token"
_PRIOR = "1234567890abcdef1234567890abcdef12345678"
_HEAD = "fedcba0987654321fedcba0987654321fedcba09"
InstallGithub = Callable[..., MagicMock]
def _body(sha: str | None) -> str:
body = "<!-- requirements-check -->\n## Check requirements\n"
if sha is not None:
body += (
f"\nChecked at commit "
f"[`{sha[:7]}`](https://github.com/home-assistant/core/commit/{sha})."
)
return body
def _comment(
sha: str | None, *, author: str = "github-actions[bot]"
) -> SimpleNamespace:
"""A PyGithub-like IssueComment with a body and an author login."""
return SimpleNamespace(body=_body(sha), user=SimpleNamespace(login=author))
def _file(filename: str) -> SimpleNamespace:
"""A PyGithub-like File entry from a commit comparison."""
return SimpleNamespace(filename=filename)
@pytest.fixture
def install_github(monkeypatch: pytest.MonkeyPatch) -> InstallGithub:
"""Install a fake PyGithub client and return the installer for assertions."""
def _install(
*,
comments: list[SimpleNamespace] | None = None,
files: list[SimpleNamespace] | None = None,
comments_exc: Exception | None = None,
compare_exc: Exception | None = None,
) -> MagicMock:
issue = MagicMock()
if comments_exc is not None:
issue.get_comments.side_effect = comments_exc
else:
issue.get_comments.return_value = comments or []
repo = MagicMock()
repo.get_issue.return_value = issue
if compare_exc is not None:
repo.compare.side_effect = compare_exc
else:
repo.compare.return_value = SimpleNamespace(files=files or [])
client = MagicMock()
client.get_repo.return_value = repo
monkeypatch.setattr(gate, "Github", lambda **_: client)
return client
return _install
@pytest.mark.parametrize(
("bodies", "expected"),
[
pytest.param([], None, id="no-comments"),
pytest.param(
["<!-- requirements-check -->\nNo commit link here."],
None,
id="marker-without-link",
),
pytest.param([_body(_PRIOR)], _PRIOR, id="single-comment"),
pytest.param([_body(_PRIOR), _body(_HEAD)], _HEAD, id="most-recent-wins"),
],
)
def test_extract_prior_sha(bodies: list[str], expected: str | None) -> None:
"""The last recorded commit SHA across marker comments is returned."""
assert extract_prior_sha(bodies) == expected
def test_extract_prior_sha_normalizes_case() -> None:
"""An upper-case SHA in a link is returned lower-cased."""
assert extract_prior_sha([_body(_PRIOR.upper())]) == _PRIOR
def test_extract_prior_sha_round_trips_rendered_comment() -> None:
"""The gate reads back exactly the SHA the renderer wrote, keeping them in sync."""
body = render_comment(CheckRunResult(pr_number=1, head_sha=_HEAD))
assert extract_prior_sha([body]) == _HEAD
def test_fetch_marker_comment_bodies_returns_all_bot_comments(
install_github: InstallGithub,
) -> None:
"""Every bot comment body is returned in API order; the marker is not filtered on."""
install_github(
comments=[
_comment(None), # no SHA recorded yet
_comment(_PRIOR),
SimpleNamespace(
body="chatter", user=SimpleNamespace(login="github-actions[bot]")
),
]
)
bodies = fetch_marker_comment_bodies(7, _REPO, _TOKEN)
assert bodies == [_body(None), _body(_PRIOR), "chatter"]
@pytest.mark.parametrize(
"author",
[
pytest.param("attacker", id="drive-by-commenter"),
pytest.param("dependabot[bot]", id="other-bot"),
pytest.param("maintainer", id="maintainer-account"),
],
)
def test_fetch_marker_comment_bodies_ignores_non_actions_author(
install_github: InstallGithub,
author: str,
) -> None:
"""A forged marker comment from anyone but github-actions is ignored."""
install_github(comments=[_comment(_HEAD, author=author)])
assert fetch_marker_comment_bodies(7, _REPO, _TOKEN) == []
def test_fetch_marker_comment_bodies_handles_api_error(
install_github: InstallGithub,
) -> None:
"""A GitHub API error yields no bodies (fails open) instead of raising."""
install_github(comments_exc=GithubException(500, {}, {}))
assert fetch_marker_comment_bodies(7, _REPO, _TOKEN) == []
def test_decide_skip_no_head_sha(install_github: InstallGithub) -> None:
"""An empty head SHA never skips and makes no API calls."""
client = install_github()
assert decide_skip(7, "", _REPO, _TOKEN).skip is False
client.get_repo.assert_not_called()
def test_decide_skip_no_prior_comment(install_github: InstallGithub) -> None:
"""The first run (no prior comment) runs the checks."""
install_github(
comments=[SimpleNamespace(body="hi", user=SimpleNamespace(login="x"))]
)
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is False
def test_decide_skip_head_unchanged(install_github: InstallGithub) -> None:
"""When head matches the last comment's SHA, skip without comparing."""
client = install_github(comments=[_comment(_HEAD)])
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is True
client.get_repo.return_value.compare.assert_not_called()
def test_decide_skip_tracked_files_changed(install_github: InstallGithub) -> None:
"""A requirement file changed since the comment runs the checks."""
install_github(
comments=[_comment(_PRIOR)],
files=[_file("homeassistant/foo.py"), _file("requirements_all.txt")],
)
decision = decide_skip(7, _HEAD, _REPO, _TOKEN)
assert decision.skip is False
# The untracked file must not be reported as the reason to run.
assert "requirements_all.txt" in decision.reason
assert "homeassistant/foo.py" not in decision.reason
def test_decide_skip_no_tracked_files_changed(install_github: InstallGithub) -> None:
"""Only non-requirement files changed since the comment, so skip."""
install_github(
comments=[_comment(_PRIOR)],
files=[_file("homeassistant/components/demo/light.py")],
)
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is True
def test_decide_skip_compare_unavailable_runs(install_github: InstallGithub) -> None:
"""A failed compare falls back to running the checks."""
install_github(
comments=[_comment(_PRIOR)], compare_exc=GithubException(404, {}, {})
)
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is False
def test_decide_skip_comments_error_runs(install_github: InstallGithub) -> None:
"""A failed comments fetch fails open (runs the checks), never skips."""
install_github(comments_exc=GithubException(500, {}, {}))
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is False
def test_client_uses_github_api_url(monkeypatch: pytest.MonkeyPatch) -> None:
"""GHES is supported by passing GITHUB_API_URL (trailing slash stripped)."""
captured: dict[str, object] = {}
monkeypatch.setenv("GITHUB_API_URL", "https://ghe.example.com/api/v3/")
def _fake_github(**kwargs: object) -> MagicMock:
captured.update(kwargs)
client = MagicMock()
client.get_repo.return_value.get_issue.return_value.get_comments.return_value = [
_comment(_HEAD)
]
return client
monkeypatch.setattr(gate, "Github", _fake_github)
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is True
assert captured["base_url"] == "https://ghe.example.com/api/v3"
+15 -81
View File
@@ -6,14 +6,17 @@ from pathlib import Path
import pytest
from script.check_requirements import __main__ as main_mod
from script.check_requirements.gate import GateDecision
from script.check_requirements.pypi import ProvenanceResult, PypiPackageInfo
_SHA = "abc1234def5678abc1234def5678abc1234def56"
def _write_bump_diff(path: Path) -> None:
path.write_text(
def test_main_writes_artifact(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""The CLI parses args, runs checks, and writes a JSON artifact."""
diff_file = tmp_path / "diff.patch"
diff_file.write_text(
"diff --git a/requirements_all.txt b/requirements_all.txt\n"
"--- a/requirements_all.txt\n"
"+++ b/requirements_all.txt\n"
@@ -22,9 +25,8 @@ def _write_bump_diff(path: Path) -> None:
"+pkg==1.1.0\n",
encoding="utf-8",
)
output_file = tmp_path / "results.json"
def _mock_pypi(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"script.check_requirements.runner.fetch_package_info",
lambda name, version: PypiPackageInfo(
@@ -44,27 +46,13 @@ def _mock_pypi(monkeypatch: pytest.MonkeyPatch) -> None:
),
)
def test_main_writes_artifact(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""When the gate runs the checks, the CLI writes a non-skipped artifact."""
diff_file = tmp_path / "diff.patch"
_write_bump_diff(diff_file)
output_file = tmp_path / "results.json"
monkeypatch.setattr(
main_mod, "_resolve_skip", lambda pr, sha: GateDecision(False, "running checks")
)
_mock_pypi(monkeypatch)
sha = "abc1234def5678abc1234def5678abc1234def56"
exit_code = main_mod.main(
[
"--pr-number",
"42",
"--head-sha",
_SHA,
sha,
"--diff",
str(diff_file),
"--output",
@@ -74,70 +62,16 @@ def test_main_writes_artifact(
assert exit_code == 0
payload = json.loads(output_file.read_text(encoding="utf-8"))
assert payload["skip_aw"] is False
assert payload["pr_number"] == 42
assert payload["head_sha"] == _SHA
assert payload["head_sha"] == sha
assert payload["packages"][0]["name"] == "pkg"
assert (
f"https://github.com/home-assistant/core/commit/{_SHA}"
f"https://github.com/home-assistant/core/commit/{sha}"
in payload["rendered_comment"]
)
assert "check_requirements: 1 package change(s)" in capsys.readouterr().err
def test_main_skips_but_still_writes_artifact(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""When the gate skips, no checks run but a skip-flagged artifact is written."""
diff_file = tmp_path / "diff.patch"
_write_bump_diff(diff_file)
output_file = tmp_path / "results.json"
monkeypatch.setattr(
main_mod, "_resolve_skip", lambda pr, sha: GateDecision(True, "nothing changed")
)
# The checks must not run when skipping; make them explode if they do.
def _boom(name: str, version: str) -> None:
raise AssertionError("checks must not run when the gate skips")
monkeypatch.setattr("script.check_requirements.runner.fetch_package_info", _boom)
exit_code = main_mod.main(
[
"--pr-number",
"42",
"--head-sha",
_SHA,
"--diff",
str(diff_file),
"--output",
str(output_file),
]
)
assert exit_code == 0
payload = json.loads(output_file.read_text(encoding="utf-8"))
assert payload == {
"version": 1,
"pr_number": 42,
"skip_aw": True,
"head_sha": _SHA,
"needs_agent": False,
"packages": [],
"rendered_comment": "",
}
def test_resolve_skip_without_credentials_runs(monkeypatch: pytest.MonkeyPatch) -> None:
"""Missing repo/token falls open (runs) without ever calling the gate."""
monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
def _boom(*args: object, **kwargs: object) -> None:
raise AssertionError("decide_skip must not be called without credentials")
monkeypatch.setattr(main_mod, "decide_skip", _boom)
assert main_mod._resolve_skip(42, _SHA).skip is False
captured = capsys.readouterr()
assert "check_requirements: 1 package change(s)" in captured.err
def test_main_missing_diff_file_exits(