mirror of
https://github.com/home-assistant/core.git
synced 2026-06-26 08:35:38 +02:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe13396a5e | |||
| 71342ef1f6 | |||
| 544dccd50b | |||
| 5f6508c424 | |||
| 70f3526be3 | |||
| f5f2ecadfd | |||
| a8de57a1c6 | |||
| 8b08f10f78 | |||
| 34679b033a | |||
| 014f785050 | |||
| 1c53ada438 | |||
| a88de3efab | |||
| 10ce428387 | |||
| dae8b60ec4 | |||
| e098cc384c | |||
| 6007bfa1cd | |||
| 16262362e1 | |||
| 01350e8f15 | |||
| 8977fc6f67 | |||
| 6411cc5c48 | |||
| 9ce56183ea | |||
| 5117c0b964 | |||
| 7dd5e188bb | |||
| ec80260c4c | |||
| 10ceac63f6 | |||
| 847f4dc287 | |||
| 8d4c8114d4 | |||
| b6b165fd00 | |||
| 7c99cf6385 | |||
| d6b743b93e | |||
| 0cae5e41b4 | |||
| 6fce245dfa | |||
| 44ba231bf6 | |||
| 18229b0998 | |||
| 39a1e6dfcc | |||
| bc8bf27e79 | |||
| 42dbf09f43 | |||
| 3c36f3809a | |||
| b6bf1c83ab |
@@ -95,6 +95,7 @@ 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,6 +12,7 @@ on:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "requirements*.txt"
|
||||
- "**/requirements*.txt"
|
||||
- "homeassistant/package_constraints.txt"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -58,6 +59,7 @@ 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: |
|
||||
|
||||
+58
-129
@@ -1,5 +1,5 @@
|
||||
# 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"}]}
|
||||
# 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"}]}
|
||||
# ___ _ _
|
||||
# / _ \ | | (_)
|
||||
# | |_| | __ _ ___ _ __ | |_ _ ___
|
||||
@@ -31,12 +31,12 @@
|
||||
# - GITHUB_TOKEN
|
||||
#
|
||||
# Custom actions used:
|
||||
# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
# - 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@v0.79.6
|
||||
# - github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # 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@v0.79.6
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
@@ -344,9 +344,8 @@ jobs:
|
||||
agent:
|
||||
needs:
|
||||
- activation
|
||||
- extract_pr_number
|
||||
- gate
|
||||
if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true'
|
||||
- prepare
|
||||
if: (needs.prepare.outputs.skip != 'true') && (needs.activation.outputs.daily_effective_workflow_exceeded != 'true')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -383,7 +382,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -404,7 +403,7 @@ jobs:
|
||||
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create gh-aw temp directory
|
||||
@@ -489,15 +488,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_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
|
||||
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
|
||||
- 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.extract_pr_number.outputs.pr_number }}. Supports reply_to_id for discussion threading."
|
||||
"add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: ${{ needs.prepare.outputs.pr_number }}. Supports reply_to_id for discussion threading."
|
||||
},
|
||||
"repo_params": {},
|
||||
"dynamic_tools": []
|
||||
@@ -994,8 +993,7 @@ jobs:
|
||||
- activation
|
||||
- agent
|
||||
- detection
|
||||
- extract_pr_number
|
||||
- gate
|
||||
- prepare
|
||||
- safe_outputs
|
||||
if: >
|
||||
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
|
||||
@@ -1018,7 +1016,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1208,7 +1206,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1236,7 +1234,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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
# --- Threat Detection ---
|
||||
@@ -1429,111 +1427,6 @@ 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:
|
||||
@@ -1545,7 +1438,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1568,12 +1461,48 @@ 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
|
||||
- extract_pr_number
|
||||
- prepare
|
||||
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
@@ -1609,7 +1538,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1654,7 +1583,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.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_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\":{}}"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -15,94 +15,41 @@ tools:
|
||||
github:
|
||||
toolsets: [repos, pull_requests]
|
||||
min-integrity: unapproved
|
||||
if: needs.prepare.outputs.skip != 'true'
|
||||
safe-outputs:
|
||||
add-comment:
|
||||
max: 1
|
||||
target: "${{ needs.extract_pr_number.outputs.pr_number }}"
|
||||
target: "${{ needs.prepare.outputs.pr_number }}"
|
||||
needs:
|
||||
- extract_pr_number
|
||||
- prepare
|
||||
jobs:
|
||||
gate:
|
||||
# Skip the (token-spending) agent when no tracked requirement file changed
|
||||
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.
|
||||
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: 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 }}
|
||||
skip: ${{ steps.prepare.outputs.skip }}
|
||||
pr_number: ${{ steps.prepare.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: Extract PR number from artifact
|
||||
id: extract
|
||||
- name: Resolve skip and PR number from the artifact
|
||||
id: prepare
|
||||
run: |
|
||||
PR=$(jq -r '.pr_number' /tmp/deterministic/results.json)
|
||||
echo "pr_number=${PR}" >> "${GITHUB_OUTPUT}"
|
||||
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}"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.workflow_run.id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
Generated
+2
@@ -1027,6 +1027,8 @@ 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
|
||||
|
||||
@@ -11,10 +11,10 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONF_NAME,
|
||||
PERCENTAGE,
|
||||
UnitOfDensity,
|
||||
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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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.3"]
|
||||
"requirements": ["aioamazondevices==14.1.6"]
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
DOMAIN, platform, unique_id
|
||||
platform, DOMAIN, 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(DOMAIN, platform, unique_id)
|
||||
entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, 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(
|
||||
DOMAIN, SENSOR_DOMAIN, unique_id=unique_id
|
||||
SENSOR_DOMAIN, DOMAIN, unique_id=unique_id
|
||||
)
|
||||
is_unsupported = not coordinator.data[serial_num].notifications_supported
|
||||
|
||||
|
||||
@@ -731,17 +731,32 @@ 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
|
||||
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
|
||||
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
|
||||
|
||||
self.async_set_context(trigger_context)
|
||||
event_data = {
|
||||
@@ -794,7 +809,9 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
)
|
||||
automation_trace.set_error(err)
|
||||
except Exception as err:
|
||||
self._logger.exception("While executing automation %s", self.entity_id)
|
||||
self._logger.exception(
|
||||
"Unexpected error while executing automation %s", self.entity_id
|
||||
)
|
||||
automation_trace.set_error(err)
|
||||
|
||||
return None
|
||||
|
||||
@@ -805,6 +805,10 @@ 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 (
|
||||
@@ -813,22 +817,25 @@ class DefaultAgent(ConversationEntity):
|
||||
)
|
||||
or (
|
||||
# More entities matched
|
||||
num_matched_entities > best_num_matched_entities
|
||||
same_text_matched
|
||||
and (num_matched_entities > best_num_matched_entities)
|
||||
)
|
||||
or (
|
||||
# Fewer unmatched entities
|
||||
(num_matched_entities == best_num_matched_entities)
|
||||
same_text_matched
|
||||
and (num_matched_entities == best_num_matched_entities)
|
||||
and (num_unmatched_entities < best_num_unmatched_entities)
|
||||
)
|
||||
or (
|
||||
# Prefer unmatched ranges
|
||||
(num_matched_entities == best_num_matched_entities)
|
||||
same_text_matched
|
||||
and (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
|
||||
(result.text_chunks_matched == maybe_result.text_chunks_matched)
|
||||
same_text_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.1"]
|
||||
"requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.24"]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==45.3.1",
|
||||
"aioesphomeapi==45.5.2",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.9.4"
|
||||
],
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "system",
|
||||
"preview_features": { "winter_mode": {} },
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20260624.0"]
|
||||
"requirements": ["home-assistant-frontend==20260624.1"]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfVolume
|
||||
from homeassistant.const import EntityCategory, UnitOfRatio, 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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
translation_key="program_progress",
|
||||
appliance_types=APPLIANCES_WITH_PROGRAMS,
|
||||
),
|
||||
@@ -158,6 +158,7 @@ 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,18 +26,17 @@ 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,
|
||||
)
|
||||
@@ -254,7 +253,7 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
|
||||
name="Current Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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),
|
||||
@@ -270,42 +269,42 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
|
||||
name="PM2.5 Density",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
CharacteristicsTypes.THREAD_NODE_CAPABILITIES: HomeKitSensorEntityDescription(
|
||||
key=CharacteristicsTypes.THREAD_NODE_CAPABILITIES,
|
||||
@@ -363,13 +362,13 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
|
||||
key=CharacteristicsTypes.FILTER_LIFE_LEVEL,
|
||||
name="Filter lifetime",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
),
|
||||
CharacteristicsTypes.WATER_LEVEL: HomeKitSensorEntityDescription(
|
||||
key=CharacteristicsTypes.WATER_LEVEL,
|
||||
name="Water level",
|
||||
translation_key="water_level",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
CharacteristicsTypes.VENDOR_EVE_THERMO_VALVE_POSITION: (
|
||||
@@ -379,7 +378,7 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
|
||||
translation_key="valve_position",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
)
|
||||
),
|
||||
}
|
||||
@@ -409,7 +408,7 @@ class HomeKitHumiditySensor(HomeKitSensor):
|
||||
"""Representation of a Homekit humidity sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.HUMIDITY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
|
||||
@override
|
||||
def get_characteristic_types(self) -> list[str]:
|
||||
@@ -481,7 +480,7 @@ class HomeKitCarbonDioxideSensor(HomeKitSensor):
|
||||
"""Representation of a Homekit Carbon Dioxide sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.CO2
|
||||
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PARTS_PER_MILLION
|
||||
|
||||
@override
|
||||
def get_characteristic_types(self) -> list[str]:
|
||||
@@ -505,7 +504,7 @@ class HomeKitBatterySensor(HomeKitSensor):
|
||||
"""Representation of a Homekit battery sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.BATTERY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
@override
|
||||
|
||||
@@ -50,14 +50,13 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfDensity,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolume,
|
||||
@@ -84,7 +83,7 @@ SMOKE_DETECTOR_SENSORS: tuple[HmipSmokeDetectorSensorDescription, ...] = (
|
||||
HmipSmokeDetectorSensorDescription(
|
||||
key="dirt_level",
|
||||
translation_key="smoke_detector_dirt_level",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
channel_field="dirtLevel",
|
||||
@@ -532,7 +531,7 @@ class HomematicipFloorTerminalBlockMechanicChannelValve(
|
||||
):
|
||||
"""Representation of the HomematicIP floor terminal block."""
|
||||
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(
|
||||
@@ -581,7 +580,7 @@ class HomematicipAccesspointDutyCycle(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of then HomeMaticIP access point."""
|
||||
|
||||
_attr_icon = "mdi:access-point-network"
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
@@ -600,7 +599,7 @@ class HomematicipAccesspointDutyCycle(HomematicipGenericEntity, SensorEntity):
|
||||
class HomematicipHeatingThermostat(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of the HomematicIP heating thermostat."""
|
||||
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
"""Initialize heating thermostat device."""
|
||||
@@ -629,7 +628,7 @@ class HomematicipHumiditySensor(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of the HomematicIP humidity sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.HUMIDITY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
@@ -680,9 +679,9 @@ class HomematicipAbsoluteHumiditySensor(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of the HomematicIP absolute humidity sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.ABSOLUTE_HUMIDITY
|
||||
_attr_native_unit_of_measurement = CONCENTRATION_GRAMS_PER_CUBIC_METER
|
||||
_attr_native_unit_of_measurement = UnitOfDensity.GRAMS_PER_CUBIC_METER
|
||||
_attr_suggested_display_precision = 1
|
||||
_attr_suggested_unit_of_measurement = CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER
|
||||
_attr_suggested_unit_of_measurement = UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
@@ -1143,7 +1142,7 @@ class HomematicipSoilMoistureSensor(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of the HomematicIP soil moisture sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.MOISTURE
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""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)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Constants for the LLM integration."""
|
||||
|
||||
DOMAIN = "llm"
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -12,11 +12,9 @@ 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
|
||||
@@ -32,7 +30,7 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
|
||||
SensorType.AIR_HUMIDITY: SensorEntityDescription(
|
||||
key="air_humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorType.AIR_PRESSURE: SensorEntityDescription(
|
||||
@@ -49,7 +47,7 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
|
||||
SensorType.ECO2: SensorEntityDescription(
|
||||
key="eco2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorType.LIGHT: SensorEntityDescription(
|
||||
@@ -67,7 +65,7 @@ SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
|
||||
SensorType.VOC: SensorEntityDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -22,20 +22,19 @@ 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,
|
||||
@@ -444,7 +443,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="HumiditySensor",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
device_to_ha=lambda x: x / HUMIDITY_SCALING_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -459,7 +458,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="SoilMoistureSensor",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.MOISTURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -484,7 +483,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="PowerSource",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
# value has double precision
|
||||
@@ -625,7 +624,7 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EveThermoValvePosition",
|
||||
translation_key="valve_position",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(EveCluster.Attributes.ValvePosition,),
|
||||
@@ -658,7 +657,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="CarbonDioxideSensor",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -671,7 +670,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="TotalVolatileOrganicCompoundsSensor",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -699,7 +698,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="PM1Sensor",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -712,7 +711,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="PM25Sensor",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -725,7 +724,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="PM10Sensor",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -750,7 +749,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="CarbonMonoxideSensor",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -763,7 +762,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="NitrogenDioxideSensor",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -776,7 +775,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="OzoneConcentrationSensor",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -802,7 +801,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="HepaFilterCondition",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="hepa_filter_condition",
|
||||
),
|
||||
@@ -813,7 +812,7 @@ DISCOVERY_SCHEMAS = [
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="ActivatedCarbonFilterCondition",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="activated_carbon_filter_condition",
|
||||
),
|
||||
@@ -1297,7 +1296,7 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="ThermostatPIHeatingDemand",
|
||||
translation_key="pi_heating_demand",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
@@ -1379,7 +1378,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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(
|
||||
@@ -1464,7 +1463,7 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EnergyEvseStateOfCharge",
|
||||
translation_key="evse_soc",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -1488,7 +1487,7 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="WaterHeaterManagementTankPercentage",
|
||||
translation_key="tank_percentage",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
|
||||
@@ -11,6 +11,7 @@ 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
|
||||
@@ -73,6 +74,7 @@ async def async_setup_entry(
|
||||
class MealieStatisticSensors(MealieEntity, SensorEntity):
|
||||
"""Defines a Mealie sensor."""
|
||||
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
entity_description: MealieStatisticsSensorEntityDescription
|
||||
coordinator: MealieStatisticsCoordinator
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
"""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
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
"""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")
|
||||
@@ -365,5 +365,7 @@ async def create_rexel_client(
|
||||
gateway_id=entry.data[CONF_GATEWAY_ID],
|
||||
),
|
||||
session=async_create_clientsession(hass),
|
||||
settings=OverkizClientSettings(action_queue=ActionQueueSettings()),
|
||||
settings=OverkizClientSettings(
|
||||
action_queue=ActionQueueSettings(), default_rts_command_duration=0
|
||||
),
|
||||
)
|
||||
|
||||
@@ -15,13 +15,12 @@ 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,
|
||||
@@ -55,7 +54,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
|
||||
OverkizSensorDescription(
|
||||
key=OverkizState.CORE_BATTERY_LEVEL,
|
||||
name="Battery level",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -329,7 +328,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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# TemperatureSensor/TemperatureSensor
|
||||
@@ -369,7 +368,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
|
||||
key=OverkizState.CORE_CO_CONCENTRATION,
|
||||
name="CO concentration",
|
||||
device_class=SensorDeviceClass.CO,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# AirSensor/CO2Sensor
|
||||
@@ -377,7 +376,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
|
||||
key=OverkizState.CORE_CO2_CONCENTRATION,
|
||||
name="CO2 concentration",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
# SunSensor/SunEnergySensor
|
||||
@@ -489,7 +488,7 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
|
||||
OverkizSensorDescription(
|
||||
key=OverkizState.CORE_TARGET_CLOSURE,
|
||||
name="Target closure",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
# ThreeWayWindowHandle/WindowHandle
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
StateType,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, UnitOfInformation
|
||||
from homeassistant.const import UnitOfInformation, UnitOfRatio
|
||||
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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suggested_display_precision=2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
|
||||
@@ -22,15 +22,14 @@ 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,
|
||||
@@ -126,7 +125,7 @@ _GAUGE_VARIANT_DESCRIPTIONS = {
|
||||
"AIRQUALITY": SensorEntityDescription(
|
||||
key="airquality",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"CURRENT": SensorEntityDescription(
|
||||
@@ -156,7 +155,7 @@ _GAUGE_VARIANT_DESCRIPTIONS = {
|
||||
"HUMIDITY": SensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"LIGHT": SensorEntityDescription(
|
||||
@@ -353,7 +352,7 @@ class QbusHumiditySensor(QbusEntity, SensorEntity):
|
||||
|
||||
_attr_device_class = SensorDeviceClass.HUMIDITY
|
||||
_attr_name = None
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PERCENTAGE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
@override
|
||||
@@ -382,7 +381,7 @@ class QbusVentilationSensor(QbusEntity, SensorEntity):
|
||||
|
||||
_attr_device_class = SensorDeviceClass.CO2
|
||||
_attr_name = None
|
||||
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
|
||||
_attr_native_unit_of_measurement = UnitOfRatio.PARTS_PER_MILLION
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_suggested_display_precision = 0
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ from homeassistant.util.event_type import EventType
|
||||
# startup
|
||||
from . import (
|
||||
backup, # noqa: F401
|
||||
entity_options,
|
||||
entity_registry,
|
||||
websocket_api,
|
||||
)
|
||||
@@ -42,6 +43,7 @@ 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
|
||||
@@ -125,15 +127,6 @@ 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]
|
||||
@@ -167,6 +160,7 @@ 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()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""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())
|
||||
@@ -1,7 +1,8 @@
|
||||
"""The Steam integration."""
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .coordinator import SteamConfigEntry, SteamDataUpdateCoordinator
|
||||
|
||||
@@ -21,3 +22,22 @@ 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,6 +41,8 @@ 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
|
||||
@@ -150,7 +152,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"sensor.steam_{_id}"
|
||||
Platform.SENSOR, DOMAIN, f"{_id}_account"
|
||||
)
|
||||
):
|
||||
er.async_get(self.hass).async_remove(entity_id)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""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
|
||||
|
||||
@@ -12,9 +13,17 @@ class SteamEntity(CoordinatorEntity[SteamDataUpdateCoordinator]):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator: SteamDataUpdateCoordinator) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: SteamDataUpdateCoordinator,
|
||||
steamid: str,
|
||||
description: SensorEntityDescription,
|
||||
) -> 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,10 +80,7 @@ class SteamSensorEntity(SteamEntity, SensorEntity):
|
||||
description: SteamSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._steamid = steamid
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"sensor.steam_{steamid}"
|
||||
super().__init__(coordinator, steamid, description)
|
||||
self._attr_name = self.entity_description.name_fn(coordinator.data[steamid])
|
||||
|
||||
@property
|
||||
|
||||
@@ -85,34 +85,18 @@ def sun(
|
||||
has_sunrise_condition = SUN_EVENT_SUNRISE in (before, after)
|
||||
has_sunset_condition = SUN_EVENT_SUNSET in (before, after)
|
||||
|
||||
after_sunrise = today > dt_util.as_local(cast(datetime, sunrise)).date()
|
||||
after_sunrise = sunrise is not None and today > dt_util.as_local(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 = today > dt_util.as_local(cast(datetime, sunset)).date()
|
||||
after_sunset = sunset is not None and today > dt_util.as_local(sunset).date()
|
||||
if after_sunset and has_sunset_condition:
|
||||
tomorrow = today + timedelta(days=1)
|
||||
sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow)
|
||||
|
||||
# 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
|
||||
|
||||
# A missing sunrise/sunset means the sun doesn't rise/set on this day, which
|
||||
# happens in polar regions.
|
||||
if sunrise is None and has_sunrise_condition:
|
||||
# There is no sunrise today
|
||||
condition_trace_set_result(False, message="no sunrise today")
|
||||
@@ -123,6 +107,16 @@ 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)
|
||||
|
||||
@@ -13,12 +13,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
PERCENTAGE,
|
||||
Platform,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.const import Platform, UnitOfDensity, UnitOfRatio, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -62,7 +57,7 @@ SENSOR_DESCRIPTIONS_BATTERY: tuple[TradfriSensorEntityDescription, ...] = (
|
||||
key="battery_level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
value=lambda device: cast(int, device.device_info.battery_level),
|
||||
),
|
||||
)
|
||||
@@ -73,7 +68,7 @@ SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = (
|
||||
key="aqi",
|
||||
translation_key="aqi",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
value=_get_air_quality,
|
||||
),
|
||||
TradfriSensorEntityDescription(
|
||||
|
||||
@@ -6,21 +6,18 @@ 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,
|
||||
@@ -1023,7 +1020,7 @@ UNITS = (
|
||||
},
|
||||
),
|
||||
UnitOfMeasurement(
|
||||
unit=PERCENTAGE,
|
||||
unit=UnitOfRatio.PERCENTAGE,
|
||||
aliases={"pct", "percent", "% RH"},
|
||||
device_classes={
|
||||
SensorDeviceClass.BATTERY,
|
||||
@@ -1032,14 +1029,14 @@ UNITS = (
|
||||
},
|
||||
),
|
||||
UnitOfMeasurement(
|
||||
unit=CONCENTRATION_PARTS_PER_MILLION,
|
||||
unit=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_classes={
|
||||
SensorDeviceClass.CO,
|
||||
SensorDeviceClass.CO2,
|
||||
},
|
||||
),
|
||||
UnitOfMeasurement(
|
||||
unit=CONCENTRATION_PARTS_PER_BILLION,
|
||||
unit=UnitOfRatio.PARTS_PER_BILLION,
|
||||
device_classes={
|
||||
SensorDeviceClass.CO,
|
||||
SensorDeviceClass.CO2,
|
||||
@@ -1086,7 +1083,7 @@ UNITS = (
|
||||
device_classes={SensorDeviceClass.ILLUMINANCE},
|
||||
),
|
||||
UnitOfMeasurement(
|
||||
unit=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
unit=UnitOfDensity.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³"},
|
||||
@@ -1103,7 +1100,7 @@ UNITS = (
|
||||
},
|
||||
),
|
||||
UnitOfMeasurement(
|
||||
unit=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
unit=UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER,
|
||||
aliases={"mg/m3"},
|
||||
device_classes={
|
||||
SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["tuya_sharing"],
|
||||
"requirements": [
|
||||
"tuya-device-handlers==0.0.22",
|
||||
"tuya-device-sharing-sdk==0.2.8"
|
||||
"tuya-device-handlers==0.0.24",
|
||||
"tuya-device-sharing-sdk==0.2.10"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,14 +31,13 @@ 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
|
||||
@@ -75,7 +74,7 @@ BATTERY_SENSORS: tuple[TuyaSensorEntityDescription, ...] = (
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.BATTERY_PERCENTAGE,
|
||||
translation_key="battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -83,7 +82,7 @@ BATTERY_SENSORS: tuple[TuyaSensorEntityDescription, ...] = (
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.BATTERY, # Used by non-standard contact sensor implementations
|
||||
translation_key="battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -181,7 +180,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.CH2O_VALUE,
|
||||
@@ -199,7 +198,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.PM10,
|
||||
@@ -215,7 +214,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_monoxide",
|
||||
device_class=SensorDeviceClass.CO,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
*BATTERY_SENSORS,
|
||||
),
|
||||
@@ -302,21 +301,21 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.CO2_VALUE,
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.EC_CURRENT,
|
||||
@@ -535,7 +534,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.CH2O_VALUE,
|
||||
@@ -553,14 +552,14 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.PM10,
|
||||
translation_key="pm10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
*BATTERY_SENSORS,
|
||||
),
|
||||
@@ -570,7 +569,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.VOC_VALUE,
|
||||
@@ -583,7 +582,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.VA_HUMIDITY,
|
||||
@@ -684,7 +683,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.TEMP,
|
||||
@@ -709,7 +708,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="concentration_carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.TOTAL_TIME,
|
||||
@@ -756,7 +755,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
*BATTERY_SENSORS,
|
||||
),
|
||||
@@ -808,7 +807,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.CH2O_VALUE,
|
||||
@@ -832,7 +831,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.HUMIDITY_VALUE,
|
||||
@@ -845,14 +844,14 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="pm1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.PM10,
|
||||
translation_key="pm10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
*BATTERY_SENSORS,
|
||||
),
|
||||
@@ -1230,14 +1229,14 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="carbon_dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.PM25_VALUE,
|
||||
translation_key="pm25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
suggested_unit_of_measurement=UnitOfDensity.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.0.0"]
|
||||
"requirements": ["uiprotect==15.1.0"]
|
||||
}
|
||||
|
||||
@@ -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: PERCENTAGE,
|
||||
VICARE_PERCENT: UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
value_getter=lambda api: api.getModulation(),
|
||||
unit_getter=lambda api: api.getModulationUnit(),
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
|
||||
@@ -149,7 +149,6 @@ SELECTOR_TYPES = (
|
||||
XiaomiMiioSelectDescription(
|
||||
key=ATTR_DISPLAY_ORIENTATION,
|
||||
attr_name=ATTR_DISPLAY_ORIENTATION,
|
||||
name="Display Orientation",
|
||||
options_map={
|
||||
"Portrait": "Forward",
|
||||
"LandscapeLeft": "Left",
|
||||
@@ -165,7 +164,6 @@ 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",
|
||||
@@ -176,7 +174,6 @@ 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",
|
||||
@@ -187,7 +184,6 @@ 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,6 +138,7 @@
|
||||
},
|
||||
"select": {
|
||||
"airpurifier_mode": {
|
||||
"name": "Mode",
|
||||
"state": {
|
||||
"auto": "[%key:common::state::auto%]",
|
||||
"favorite": "Favorite",
|
||||
@@ -145,6 +146,7 @@
|
||||
}
|
||||
},
|
||||
"display_orientation": {
|
||||
"name": "Display orientation",
|
||||
"state": {
|
||||
"forward": "Forward",
|
||||
"left": "Left",
|
||||
@@ -152,6 +154,7 @@
|
||||
}
|
||||
},
|
||||
"led_brightness": {
|
||||
"name": "LED brightness",
|
||||
"state": {
|
||||
"bright": "Bright",
|
||||
"dim": "Dim",
|
||||
@@ -159,6 +162,7 @@
|
||||
}
|
||||
},
|
||||
"ptc_level": {
|
||||
"name": "Auxiliary heat level",
|
||||
"state": {
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
|
||||
@@ -4587,12 +4587,6 @@
|
||||
"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",
|
||||
|
||||
@@ -740,7 +740,7 @@ def _get_exposed_entities(
|
||||
|
||||
if include_state and (
|
||||
attributes := {
|
||||
attr_name: (
|
||||
str(attr_name): (
|
||||
str(attr_value)
|
||||
if isinstance(attr_value, (Enum, Decimal, int))
|
||||
else attr_value
|
||||
|
||||
@@ -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.0
|
||||
home-assistant-intents==2026.6.1
|
||||
home-assistant-frontend==20260624.1
|
||||
home-assistant-intents==2026.6.24
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.6
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ To update, run python3 -m script.hassfest
|
||||
|
||||
from typing import Final
|
||||
|
||||
FRONTEND_VERSION: Final[str] = "20260624.0"
|
||||
FRONTEND_VERSION: Final[str] = "20260624.1"
|
||||
|
||||
MDI_ICONS: Final[set[str]] = {
|
||||
"ab-testing",
|
||||
|
||||
Generated
+1
-1
@@ -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.1
|
||||
home-assistant-intents==2026.6.24
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
infrared-protocols==6.3.0
|
||||
|
||||
Generated
+7
-7
@@ -193,7 +193,7 @@ aioairzone-cloud==0.7.2
|
||||
aioairzone==1.0.5
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==14.1.3
|
||||
aioamazondevices==14.1.6
|
||||
|
||||
# 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.3.1
|
||||
aioesphomeapi==45.5.2
|
||||
|
||||
# homeassistant.components.matrix
|
||||
# homeassistant.components.slack
|
||||
@@ -1272,10 +1272,10 @@ hole==0.9.2
|
||||
holidays==0.99
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260624.0
|
||||
home-assistant-frontend==20260624.1
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2026.6.1
|
||||
home-assistant-intents==2026.6.24
|
||||
|
||||
# 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.22
|
||||
tuya-device-handlers==0.0.24
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-sharing-sdk==0.2.8
|
||||
tuya-device-sharing-sdk==0.2.10
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==3.0.0
|
||||
@@ -3245,7 +3245,7 @@ uasiren==0.0.1
|
||||
uhooapi==1.2.8
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==15.0.0
|
||||
uiprotect==15.1.0
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.6.1
|
||||
|
||||
@@ -19,6 +19,7 @@ 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
|
||||
|
||||
@@ -2,12 +2,28 @@
|
||||
|
||||
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")
|
||||
@@ -31,24 +47,32 @@ def main(argv: list[str] | None = None) -> int:
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -17,11 +17,13 @@ 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:
|
||||
def is_tracked(path: str) -> bool:
|
||||
"""Return True if `path` is a requirement file the checks care about."""
|
||||
return any(fnmatchcase(path, pattern) for pattern in TRACKED_PATTERNS)
|
||||
|
||||
|
||||
@@ -61,7 +63,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:
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"""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."
|
||||
)
|
||||
@@ -90,6 +90,7 @@ 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:
|
||||
@@ -101,6 +102,7 @@ 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],
|
||||
|
||||
@@ -14,6 +14,7 @@ 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], ...] = (
|
||||
@@ -127,7 +128,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/{result.head_sha})"
|
||||
commit = f"[`{result.head_sha[:7]}`]({REPO_URL}{COMMIT_PATH}{result.head_sha})"
|
||||
parts.append(f"Checked at commit {commit}.")
|
||||
return "\n\n".join([f"{MARKER}\n{HEADER}", *parts])
|
||||
|
||||
|
||||
+1
@@ -1,2 +1,3 @@
|
||||
PyGithub==2.9.1
|
||||
requests==2.34.2
|
||||
unidiff==0.7.5
|
||||
|
||||
@@ -622,7 +622,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"mullvad",
|
||||
"mutesync",
|
||||
"mvglive",
|
||||
"mycroft",
|
||||
"myq",
|
||||
"mysensors",
|
||||
"mystrom",
|
||||
@@ -1589,7 +1588,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"mullvad",
|
||||
"mutesync",
|
||||
"mvglive",
|
||||
"mycroft",
|
||||
"myq",
|
||||
"mysensors",
|
||||
"mystrom",
|
||||
@@ -2084,6 +2082,7 @@ 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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_sulphur_dioxide',
|
||||
|
||||
@@ -107,8 +107,8 @@ async def test_alexa_unique_id_migration(
|
||||
)
|
||||
|
||||
entity = entity_registry.async_get_or_create(
|
||||
DOMAIN,
|
||||
SWITCH_DOMAIN,
|
||||
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(
|
||||
DOMAIN,
|
||||
SWITCH_DOMAIN,
|
||||
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(
|
||||
DOMAIN,
|
||||
SENSOR_DOMAIN,
|
||||
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 area called Are the',
|
||||
'speech': 'Sorry, I am not aware of any device called Are the',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
@@ -902,7 +902,7 @@
|
||||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any area called Are the',
|
||||
'speech': 'Sorry, I am not aware of any device called Are the',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 (
|
||||
@@ -1930,6 +1931,187 @@ 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 area called late added',
|
||||
'speech': 'Sorry, I am not aware of any device called late added',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
@@ -373,7 +373,7 @@
|
||||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any area called kitchen',
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
@@ -423,7 +423,7 @@
|
||||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any area called renamed',
|
||||
'speech': 'Sorry, I am not aware of any device called renamed',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -467,10 +467,11 @@
|
||||
'name': 'HassTurnOn',
|
||||
}),
|
||||
'match': True,
|
||||
'sentence_template': '<turn> on (<area> <name>|<name> [in <area>])',
|
||||
'sentence_template': '<turn> on [<the>] {name}',
|
||||
'slots': dict({
|
||||
'name': 'my cool light',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
'targets': dict({
|
||||
'light.kitchen': dict({
|
||||
'matched': True,
|
||||
@@ -491,10 +492,11 @@
|
||||
'name': 'HassTurnOff',
|
||||
}),
|
||||
'match': True,
|
||||
'sentence_template': '[<turn>] (<area> <name>|<name> [in <area>]) [to] off',
|
||||
'sentence_template': '[<turn>] [<the>] {name} [to] off',
|
||||
'slots': dict({
|
||||
'name': 'my cool light',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
'targets': dict({
|
||||
'light.kitchen': dict({
|
||||
'matched': True,
|
||||
@@ -520,11 +522,12 @@
|
||||
'name': 'HassTurnOn',
|
||||
}),
|
||||
'match': True,
|
||||
'sentence_template': '<turn> on [(<all>|<the>)] <light> <in> <area>',
|
||||
'sentence_template': '<turn> on [(<all>|<the>)] <light> <in> [<the>] {area}',
|
||||
'slots': dict({
|
||||
'area': 'kitchen',
|
||||
'domain': 'light',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
'targets': dict({
|
||||
'light.kitchen': dict({
|
||||
'matched': True,
|
||||
@@ -542,7 +545,7 @@
|
||||
}),
|
||||
'domain': dict({
|
||||
'name': 'domain',
|
||||
'text': 'lights',
|
||||
'text': '',
|
||||
'value': 'light',
|
||||
}),
|
||||
'state': dict({
|
||||
@@ -555,12 +558,13 @@
|
||||
'name': 'HassGetState',
|
||||
}),
|
||||
'match': True,
|
||||
'sentence_template': '[tell me] how many {on_off_domains:domain} (is|are) {on_off_states:state} [<in_area_floor>]',
|
||||
'sentence_template': '<how_many> <light> <is> {on_off_states:state} [<in>] [<the>] {area}',
|
||||
'slots': dict({
|
||||
'area': 'kitchen',
|
||||
'domain': 'lights',
|
||||
'domain': 'light',
|
||||
'state': 'on',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
'targets': dict({
|
||||
'light.kitchen': dict({
|
||||
'matched': False,
|
||||
@@ -629,11 +633,12 @@
|
||||
'name': 'HassLightSet',
|
||||
}),
|
||||
'match': True,
|
||||
'sentence_template': '[<numeric_value_set>] <name> brightness [to] <brightness>',
|
||||
'sentence_template': '[<numeric_value_set>] [<the>] {name} brightness [to] {brightness}[([ ]%)| percent]',
|
||||
'slots': dict({
|
||||
'brightness': '100',
|
||||
'name': 'test light',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
'targets': dict({
|
||||
'light.demo_1234': dict({
|
||||
'matched': True,
|
||||
@@ -660,10 +665,11 @@
|
||||
'name': 'HassLightSet',
|
||||
}),
|
||||
'match': False,
|
||||
'sentence_template': '[<numeric_value_set>] <name> brightness [to] <brightness>',
|
||||
'sentence_template': '[<numeric_value_set>] [<the>] {name} brightness [to] {brightness}[([ ]%)| percent]',
|
||||
'slots': dict({
|
||||
'name': 'test light',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
'targets': dict({
|
||||
}),
|
||||
'unmatched_slots': dict({
|
||||
|
||||
@@ -729,19 +729,6 @@ 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:
|
||||
@@ -841,7 +828,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 on ground floor"
|
||||
== "Sorry, I am not aware of any device called missing entity in the ground floor"
|
||||
)
|
||||
|
||||
|
||||
@@ -1128,7 +1115,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 on the ground floor", None, Context(), None
|
||||
hass, "turn on all lights in the ground floor", None, Context(), None
|
||||
)
|
||||
|
||||
assert result.response.response_type is intent.IntentResponseType.ERROR
|
||||
@@ -1493,21 +1480,6 @@ 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(
|
||||
@@ -2855,9 +2827,9 @@ async def test_config_sentences_priority(
|
||||
{
|
||||
"conversation": {
|
||||
"intents": {
|
||||
"CustomIntent": ["turn on <name>"],
|
||||
"CustomIntent": ["turn on [the] {name}"],
|
||||
"WorseCustomIntent": ["turn on the lamp"],
|
||||
"FakeCustomIntent": ["turn on <name>"],
|
||||
"FakeCustomIntent": ["turn on [the] {name}"],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -127,29 +127,39 @@ 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)
|
||||
|
||||
entity_id = f"{cover.DOMAIN}.front"
|
||||
hass.states.async_set(
|
||||
entity_id, STATE_CLOSED, attributes={"device_class": "garage"}
|
||||
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"
|
||||
)
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity_id, True)
|
||||
kitchen_window = entity_registry.async_update_entity(
|
||||
kitchen_window.entity_id, area_id=area_kitchen.id
|
||||
)
|
||||
hass.states.async_set(
|
||||
kitchen_window.entity_id, STATE_CLOSED, attributes={"device_class": "window"}
|
||||
)
|
||||
async_expose_entity(hass, conversation.DOMAIN, kitchen_window.entity_id, True)
|
||||
|
||||
# Open service
|
||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
|
||||
result = await conversation.async_converse(
|
||||
hass, "open the garage door", None, Context(), None
|
||||
hass, "open the window in the kitchen", None, Context(), None, device_id=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 garage"
|
||||
assert response.speech["plain"]["speech"] == "Opening the window"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
assert call.data == {"entity_id": kitchen_window.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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'entity_id': 'sensor.vocolinc_flowerbud_0d324b_current_humidity',
|
||||
'state': '45.0',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Tests for the LLM integration."""
|
||||
@@ -0,0 +1,121 @@
|
||||
"""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
|
||||
@@ -151,7 +151,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'be37ca9c47c24498a38bc62c7c711840-sensor2-air_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_sensor_2',
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-CarbonDioxideSensor-1037-0',
|
||||
'unit_of_measurement': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.zemismart_mt25b_roller_motor_battery',
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'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': None,
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'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': None,
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'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': None,
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'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': None,
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.tuin_luchtkwaliteit',
|
||||
|
||||
@@ -4765,3 +4765,46 @@ 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,6 +20,7 @@ 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': 'sensor.steam_12345678901234567',
|
||||
'unique_id': '12345678901234567_account',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -7,8 +7,11 @@ 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
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import ACCOUNT_1, ACCOUNT_NAME_1, CONF_DATA, CONF_OPTIONS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -95,3 +98,40 @@ 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"
|
||||
|
||||
@@ -16,8 +16,11 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
# San Diego (default test location) and Longyearbyen, Svalbard (deep polar).
|
||||
# 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 = (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")
|
||||
@@ -38,8 +41,8 @@ def _find_run_id(traces, trace_type, item_id):
|
||||
return None
|
||||
|
||||
|
||||
async def assert_automation_condition_trace(hass_ws_client, automation_id, expected):
|
||||
"""Test the result of automation condition."""
|
||||
async def _get_automation_condition_trace(hass_ws_client, automation_id):
|
||||
"""Return the condition trace for a given automation."""
|
||||
msg_id = 1
|
||||
|
||||
def next_id():
|
||||
@@ -71,8 +74,15 @@ async def assert_automation_condition_trace(hass_ws_client, automation_id, expec
|
||||
assert response["success"]
|
||||
trace = response["result"]
|
||||
assert len(trace["trace"]["condition/0"]) == 1
|
||||
condition_trace = trace["trace"]["condition/0"][0]["result"]
|
||||
assert condition_trace == expected
|
||||
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
|
||||
|
||||
|
||||
async def test_if_action_before_sunrise_no_offset(
|
||||
@@ -937,14 +947,14 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
|
||||
) -> None:
|
||||
"""Test if action was before 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.
|
||||
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.
|
||||
"""
|
||||
await hass.config.async_set_time_zone("America/Anchorage")
|
||||
hass.config.latitude = 66.5
|
||||
hass.config.longitude = 162.4
|
||||
hass.config.latitude = 66.8983
|
||||
hass.config.longitude = -162.5966
|
||||
await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
@@ -961,10 +971,9 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(
|
||||
},
|
||||
)
|
||||
|
||||
# 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
|
||||
# sunrise: 2015-07-24 04:48:24 local = 2015-07-24 12:48:24 UTC
|
||||
# now = sunrise + 1s -> 'before sunrise' not true
|
||||
now = datetime(2015, 7, 24, 15, 21, 13, tzinfo=dt_util.UTC)
|
||||
now = datetime(2015, 7, 24, 12, 48, 25, tzinfo=dt_util.UTC)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -972,11 +981,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-24T15:16:46.975735+00:00"},
|
||||
{"result": False, "wanted_time_before": "2015-07-24T12:48:24.249497+00:00"},
|
||||
)
|
||||
|
||||
# now = sunrise - 1h -> 'before sunrise' true
|
||||
now = datetime(2015, 7, 24, 14, 21, 12, tzinfo=dt_util.UTC)
|
||||
now = datetime(2015, 7, 24, 11, 48, 24, tzinfo=dt_util.UTC)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -984,7 +993,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-24T15:16:46.975735+00:00"},
|
||||
{"result": True, "wanted_time_before": "2015-07-24T12:48:24.249497+00:00"},
|
||||
)
|
||||
|
||||
# now = local midnight -> 'before sunrise' true
|
||||
@@ -996,7 +1005,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-24T15:16:46.975735+00:00"},
|
||||
{"result": True, "wanted_time_before": "2015-07-24T12:48:24.249497+00:00"},
|
||||
)
|
||||
|
||||
# now = local midnight - 1s -> 'before sunrise' not true
|
||||
@@ -1008,7 +1017,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-23T15:12:19.155123+00:00"},
|
||||
{"result": False, "wanted_time_before": "2015-07-23T12:43:32.413351+00:00"},
|
||||
)
|
||||
|
||||
|
||||
@@ -1019,14 +1028,14 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
|
||||
) -> None:
|
||||
"""Test if action was after 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
|
||||
Before sunrise is true from midnight until sunrise, local time.
|
||||
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.
|
||||
"""
|
||||
await hass.config.async_set_time_zone("America/Anchorage")
|
||||
hass.config.latitude = 66.5
|
||||
hass.config.longitude = 162.4
|
||||
hass.config.latitude = 66.8983
|
||||
hass.config.longitude = -162.5966
|
||||
await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
@@ -1043,10 +1052,9 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(
|
||||
},
|
||||
)
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -1054,11 +1062,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-24T15:16:46.975735+00:00"},
|
||||
{"result": True, "wanted_time_after": "2015-07-24T12:48:24.249497+00:00"},
|
||||
)
|
||||
|
||||
# now = sunrise - 1h -> 'after sunrise' not true
|
||||
now = datetime(2015, 7, 24, 14, 21, 12, tzinfo=dt_util.UTC)
|
||||
now = datetime(2015, 7, 24, 11, 48, 24, tzinfo=dt_util.UTC)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -1066,7 +1074,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-24T15:16:46.975735+00:00"},
|
||||
{"result": False, "wanted_time_after": "2015-07-24T12:48:24.249497+00:00"},
|
||||
)
|
||||
|
||||
# now = local midnight -> 'after sunrise' not true
|
||||
@@ -1078,7 +1086,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-24T15:16:46.975735+00:00"},
|
||||
{"result": False, "wanted_time_after": "2015-07-24T12:48:24.249497+00:00"},
|
||||
)
|
||||
|
||||
# now = local midnight - 1s -> 'after sunrise' true
|
||||
@@ -1090,7 +1098,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-23T15:12:19.155123+00:00"},
|
||||
{"result": True, "wanted_time_after": "2015-07-23T12:43:32.413351+00:00"},
|
||||
)
|
||||
|
||||
|
||||
@@ -1099,16 +1107,17 @@ async def test_if_action_before_sunset_no_offset_kotzebue(
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
service_calls: list[ServiceCall],
|
||||
) -> None:
|
||||
"""Test if action was before sunrise.
|
||||
"""Test if action was before sunset on a day with two sunsets.
|
||||
|
||||
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.
|
||||
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.
|
||||
"""
|
||||
await hass.config.async_set_time_zone("America/Anchorage")
|
||||
hass.config.latitude = 66.5
|
||||
hass.config.longitude = 162.4
|
||||
hass.config.latitude = 66.8983
|
||||
hass.config.longitude = -162.5966
|
||||
await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
@@ -1125,22 +1134,9 @@ async def test_if_action_before_sunset_no_offset_kotzebue(
|
||||
},
|
||||
)
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -1148,11 +1144,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-07-25T11:13:32.501837+00:00"},
|
||||
{"result": True, "wanted_time_before": "2015-08-08T07:59:25.982224+00:00"},
|
||||
)
|
||||
|
||||
# now = local midnight -> 'before sunrise' true
|
||||
now = datetime(2015, 7, 24, 8, 0, 0, tzinfo=dt_util.UTC)
|
||||
# now = first (early) sunset + 1s -> still 'before sunset' (tracks the late one)
|
||||
now = datetime(2015, 8, 7, 8, 3, 43, tzinfo=dt_util.UTC)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -1160,19 +1156,31 @@ 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-07-24T11:17:54.446913+00:00"},
|
||||
{"result": True, "wanted_time_before": "2015-08-08T07:59:25.982224+00:00"},
|
||||
)
|
||||
|
||||
# now = local midnight - 1s -> 'before sunrise' not true
|
||||
now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC)
|
||||
# now = late sunset - 1h -> 'before sunset' true
|
||||
now = datetime(2015, 8, 8, 6, 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) == 2
|
||||
assert len(service_calls) == 3
|
||||
await assert_automation_condition_trace(
|
||||
hass_ws_client,
|
||||
"sun",
|
||||
{"result": False, "wanted_time_before": "2015-07-23T11:22:18.467277+00:00"},
|
||||
{"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"},
|
||||
)
|
||||
|
||||
|
||||
@@ -1181,16 +1189,17 @@ async def test_if_action_after_sunset_no_offset_kotzebue(
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
service_calls: list[ServiceCall],
|
||||
) -> None:
|
||||
"""Test if action was after sunrise.
|
||||
"""Test if action was after sunset on a day with two sunsets.
|
||||
|
||||
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.
|
||||
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.
|
||||
"""
|
||||
await hass.config.async_set_time_zone("America/Anchorage")
|
||||
hass.config.latitude = 66.5
|
||||
hass.config.longitude = 162.4
|
||||
hass.config.latitude = 66.8983
|
||||
hass.config.longitude = -162.5966
|
||||
await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
@@ -1207,10 +1216,33 @@ async def test_if_action_after_sunset_no_offset_kotzebue(
|
||||
},
|
||||
)
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -1218,11 +1250,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-07-25T11:13:32.501837+00:00"},
|
||||
{"result": True, "wanted_time_after": "2015-08-08T07:59:25.982224+00:00"},
|
||||
)
|
||||
|
||||
# now = sunset - 1s -> 'after sunset' not true
|
||||
now = datetime(2015, 7, 25, 11, 13, 32, tzinfo=dt_util.UTC)
|
||||
# now = local midnight (next day) -> 'after sunset' not true
|
||||
now = datetime(2015, 8, 8, 8, 0, 1, tzinfo=dt_util.UTC)
|
||||
with freeze_time(now):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
@@ -1230,31 +1262,65 @@ 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-07-25T11:13:32.501837+00:00"},
|
||||
{"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"},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# 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) == 1
|
||||
assert len(service_calls) == 0
|
||||
await assert_automation_condition_trace(
|
||||
hass_ws_client,
|
||||
"sun",
|
||||
{"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"},
|
||||
{"result": False, "message": f"no {event} today"},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -346,6 +346,12 @@ 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"),
|
||||
@@ -452,6 +458,41 @@ 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': 'ppm',
|
||||
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'suggested_unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'mg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER: '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'>: 'mg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'μg/m³',
|
||||
'suggested_unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'ppm',
|
||||
'suggested_unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: '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'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: '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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μ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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'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': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# 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'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.model9_valve_position',
|
||||
|
||||
@@ -934,6 +934,54 @@ 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:
|
||||
|
||||
@@ -11,6 +11,7 @@ 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 (
|
||||
@@ -479,7 +480,12 @@ async def test_assist_api_prompt(
|
||||
hass.states.async_set(
|
||||
entry1.entity_id,
|
||||
"on",
|
||||
{"friendly_name": "Kitchen", "temperature": Decimal("0.9"), "humidity": 65},
|
||||
{
|
||||
"friendly_name": "Kitchen",
|
||||
"temperature": Decimal("0.9"),
|
||||
"humidity": 65,
|
||||
EntityStateAttribute.UNIT_OF_MEASUREMENT: "°C",
|
||||
},
|
||||
)
|
||||
hass.states.async_set(entry2.entity_id, "on", {"friendly_name": "Living Room"})
|
||||
|
||||
@@ -636,6 +642,7 @@ 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'
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
"""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"
|
||||
@@ -6,17 +6,14 @@ 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 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(
|
||||
|
||||
def _write_bump_diff(path: Path) -> None:
|
||||
path.write_text(
|
||||
"diff --git a/requirements_all.txt b/requirements_all.txt\n"
|
||||
"--- a/requirements_all.txt\n"
|
||||
"+++ b/requirements_all.txt\n"
|
||||
@@ -25,8 +22,9 @@ def test_main_writes_artifact(
|
||||
"+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(
|
||||
@@ -46,13 +44,27 @@ def test_main_writes_artifact(
|
||||
),
|
||||
)
|
||||
|
||||
sha = "abc1234def5678abc1234def5678abc1234def56"
|
||||
|
||||
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)
|
||||
|
||||
exit_code = main_mod.main(
|
||||
[
|
||||
"--pr-number",
|
||||
"42",
|
||||
"--head-sha",
|
||||
sha,
|
||||
_SHA,
|
||||
"--diff",
|
||||
str(diff_file),
|
||||
"--output",
|
||||
@@ -62,16 +74,70 @@ 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
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "check_requirements: 1 package change(s)" in captured.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
|
||||
|
||||
|
||||
def test_main_missing_diff_file_exits(
|
||||
|
||||
Reference in New Issue
Block a user