mirror of
https://github.com/home-assistant/core.git
synced 2026-06-25 16:15:28 +02:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6860e0f3b9 | |||
| d3be0cc852 | |||
| c830c05cbd | |||
| 2be55a06cc | |||
| d786fb16a0 | |||
| f78dd797b1 | |||
| 0d957a971d | |||
| cff3a711f3 | |||
| 177c4a4fb5 | |||
| 7d8204f5e7 | |||
| 9aed167f71 | |||
| a8630f5570 | |||
| 2a75b0e2fb | |||
| 9c4ad761c4 | |||
| 8e3e1044a1 | |||
| bec6c94e32 | |||
| c9729df69a | |||
| 70ff0fd682 | |||
| 258ae6d506 | |||
| 4f93afd6ae | |||
| 7968fc4809 | |||
| 975f2a831e | |||
| cc2944d626 | |||
| 548ec5cacf | |||
| dc6eef2844 | |||
| 0808e30e37 | |||
| f0ed257f47 | |||
| b4b710b474 | |||
| 0004a82fe4 | |||
| 0c4bc95bdd | |||
| 5fdab795e8 | |||
| 2193665909 | |||
| c9d91d5812 | |||
| de9d9c66c1 | |||
| dfcc4d1ae4 | |||
| d71812f09b | |||
| a323ebe634 | |||
| 024bba55cf | |||
| a5546566e7 | |||
| 3d9994ee4f | |||
| c542f38387 | |||
| 49d6166b7e | |||
| 7249190c64 | |||
| cebdde6ab4 | |||
| 031f4cd965 | |||
| a734f7110c | |||
| 395e949591 | |||
| 484e60a1c4 | |||
| b7a234fbd9 | |||
| a1982fbd54 | |||
| c384cd9894 | |||
| 1aefd2a5ac | |||
| e3605be5cd | |||
| e87a41a01d | |||
| 190ff034aa | |||
| b301925687 | |||
| 7a0f5b066e | |||
| 308fad166d | |||
| 1305c2978c | |||
| 955ad6db1b | |||
| 87dc013803 | |||
| 1bb41cb2dd | |||
| 277af6c60b | |||
| 69e18aa580 | |||
| 75852fc191 | |||
| a661b678a2 | |||
| bd0951110d | |||
| 899f904cf3 | |||
| d2e7426aa5 | |||
| c0e02457bc | |||
| e7562b50cf | |||
| c36e4a03e0 | |||
| 71430af6ff | |||
| 815cce5a0c | |||
| 32929755eb | |||
| 88d4d1c879 | |||
| 51bd71d096 | |||
| 1fcf9eb5b7 | |||
| 1917a007f8 | |||
| b095baa65a | |||
| 2bd81c7351 | |||
| a576aef9a4 | |||
| c2e780dfd2 | |||
| 687064d5cc | |||
| 0b801d74cd | |||
| dbdcb1a91e | |||
| f9bf7ab122 | |||
| d484f75c7b | |||
| 5ba4d588b6 | |||
| 42da67e7a9 | |||
| 7d08a7b898 | |||
| 05016e46c8 | |||
| 58603326e3 | |||
| 10fe3dc13f | |||
| adf2f2854c | |||
| 7fd101005d | |||
| 40aa8dd617 | |||
| fb283dfb93 | |||
| 2084d52504 | |||
| cb914495e7 | |||
| 97f2eecc57 | |||
| 2d1b3f799d | |||
| f57418ed60 | |||
| 8d1bf68045 | |||
| c5bfad9bfe | |||
| 679b0ac2aa | |||
| 22a583c83f | |||
| d966f71832 | |||
| 08569420f6 | |||
| c1bcbca520 | |||
| c73c647162 | |||
| b2f1c38b6f | |||
| e8824bedf5 | |||
| 27b107f4a5 | |||
| 7536e8647f | |||
| 75c6058396 | |||
| 77533e5af5 | |||
| 31a1e7c5e1 | |||
| 20f4e7306b | |||
| 5c1ac08c92 | |||
| 5631c68069 | |||
| 8648611278 | |||
| 971d15be1e | |||
| b6f2429ff3 | |||
| c676e2a806 | |||
| a27ea536db | |||
| 22e25d9ce2 |
@@ -53,3 +53,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- When validation guarantees a dict key exists, prefer direct key access (`data["key"]`) instead of `.get("key")` so contract violations are surfaced instead of silently masked.
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
base_image_version: ${{ env.BASE_IMAGE_VERSION }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -245,7 +245,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -292,7 +292,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -469,7 +469,7 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -516,7 +516,7 @@ jobs:
|
||||
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -8,15 +8,11 @@ name: Check requirements (deterministic)
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
# Auto-trigger on PRs that touch tracked requirement files is disabled
|
||||
# for now while we iterate — testing the workflow_run handoff to the
|
||||
# agentic stage is hard with an auto-trigger. Re-enable once the chain
|
||||
# has been validated end-to-end.
|
||||
# pull_request:
|
||||
# types: [opened, synchronize, reopened]
|
||||
# paths:
|
||||
# - "**/requirements*.txt"
|
||||
# - "homeassistant/package_constraints.txt"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "requirements*.txt"
|
||||
- "homeassistant/package_constraints.txt"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pull_request_number:
|
||||
@@ -40,7 +36,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
|
||||
+4
-4
@@ -31,7 +31,7 @@
|
||||
# - GITHUB_TOKEN
|
||||
#
|
||||
# Custom actions used:
|
||||
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
- name: Checkout .github and .agents folders
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
@@ -404,7 +404,7 @@ jobs:
|
||||
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create gh-aw temp directory
|
||||
@@ -1236,7 +1236,7 @@ jobs:
|
||||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository for patch context
|
||||
if: needs.agent.outputs.has_patch == 'true'
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# --- Threat Detection ---
|
||||
|
||||
+21
-21
@@ -39,7 +39,7 @@ on:
|
||||
env:
|
||||
CACHE_VERSION: 3
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.7"
|
||||
HA_SHORT_VERSION: "2026.8"
|
||||
ADDITIONAL_PYTHON_VERSIONS: "[]"
|
||||
# 10.3 is the oldest supported version
|
||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
skip_coverage: ${{ steps.info.outputs.skip_coverage }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Generate partial Python venv restore key
|
||||
@@ -264,7 +264,7 @@ jobs:
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Register problem matchers
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run zizmor
|
||||
@@ -318,7 +318,7 @@ jobs:
|
||||
- script/hassfest/docker/Dockerfile
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Register hadolint problem matcher
|
||||
@@ -341,7 +341,7 @@ jobs:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
@@ -469,7 +469,7 @@ jobs:
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
@@ -512,7 +512,7 @@ jobs:
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
@@ -548,7 +548,7 @@ jobs:
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
@@ -576,7 +576,7 @@ jobs:
|
||||
&& github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Dependency review
|
||||
@@ -603,7 +603,7 @@ jobs:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
@@ -654,7 +654,7 @@ jobs:
|
||||
|| github.event.inputs.pylint-only == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
@@ -707,7 +707,7 @@ jobs:
|
||||
&& (needs.info.outputs.tests_glob || needs.info.outputs.test_full_suite == 'true')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
@@ -758,7 +758,7 @@ jobs:
|
||||
|| github.event.inputs.mypy-only == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
@@ -825,7 +825,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
@@ -889,7 +889,7 @@ jobs:
|
||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
@@ -1030,7 +1030,7 @@ jobs:
|
||||
mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
@@ -1179,7 +1179,7 @@ jobs:
|
||||
postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
@@ -1317,7 +1317,7 @@ jobs:
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download all coverage artifacts
|
||||
@@ -1355,7 +1355,7 @@ jobs:
|
||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
@@ -1476,7 +1476,7 @@ jobs:
|
||||
- pytest-partial
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download all coverage artifacts
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ homeassistant.components
|
||||
homeassistant.components.abode.*
|
||||
homeassistant.components.acaia.*
|
||||
homeassistant.components.accuweather.*
|
||||
homeassistant.components.acer_projector.*
|
||||
homeassistant.components.acmeda.*
|
||||
homeassistant.components.actiontec.*
|
||||
homeassistant.components.actron_air.*
|
||||
@@ -77,7 +76,6 @@ homeassistant.components.amberelectric.*
|
||||
homeassistant.components.ambient_network.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
homeassistant.components.ampio.*
|
||||
homeassistant.components.analytics.*
|
||||
homeassistant.components.analytics_insights.*
|
||||
homeassistant.components.android_ip_webcam.*
|
||||
|
||||
@@ -42,3 +42,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- When validation guarantees a dict key exists, prefer direct key access (`data["key"]`) instead of `.get("key")` so contract violations are surfaced instead of silently masked.
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
|
||||
Generated
+6
-8
@@ -230,7 +230,6 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/battery/ @home-assistant/core
|
||||
/homeassistant/components/bayesian/ @HarvsG
|
||||
/tests/components/bayesian/ @HarvsG
|
||||
/homeassistant/components/beewi_smartclim/ @alemuro
|
||||
/homeassistant/components/binary_sensor/ @home-assistant/core
|
||||
/tests/components/binary_sensor/ @home-assistant/core
|
||||
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
|
||||
@@ -254,8 +253,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
|
||||
/homeassistant/components/bosch_alarm/ @mag1024 @sanjay900
|
||||
/tests/components/bosch_alarm/ @mag1024 @sanjay900
|
||||
/homeassistant/components/bosch_shc/ @tschamm
|
||||
/tests/components/bosch_shc/ @tschamm
|
||||
/homeassistant/components/bosch_shc/ @tschamm @mosandlt
|
||||
/tests/components/bosch_shc/ @tschamm @mosandlt
|
||||
/homeassistant/components/brands/ @home-assistant/core
|
||||
/tests/components/brands/ @home-assistant/core
|
||||
/homeassistant/components/braviatv/ @bieniu @Drafteed
|
||||
@@ -791,8 +790,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/html5/ @alexyao2015 @tr4nt0r
|
||||
/homeassistant/components/http/ @home-assistant/core
|
||||
/tests/components/http/ @home-assistant/core
|
||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||
/tests/components/huawei_lte/ @scop @fphammerle
|
||||
/homeassistant/components/huawei_lte/ @fphammerle
|
||||
/tests/components/huawei_lte/ @fphammerle
|
||||
/homeassistant/components/hue/ @marcelveldt
|
||||
/tests/components/hue/ @marcelveldt
|
||||
/homeassistant/components/hue_ble/ @flip-dots
|
||||
@@ -1603,8 +1602,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/sensorpush/ @bdraco
|
||||
/homeassistant/components/sensorpush_cloud/ @sstallion
|
||||
/tests/components/sensorpush_cloud/ @sstallion
|
||||
/homeassistant/components/sensoterra/ @markruys
|
||||
/tests/components/sensoterra/ @markruys
|
||||
/homeassistant/components/sensoterra/ @SanderBakkumCuriousInc @curious-florian @markruys
|
||||
/tests/components/sensoterra/ @SanderBakkumCuriousInc @curious-florian @markruys
|
||||
/homeassistant/components/sentry/ @dcramer @frenck
|
||||
/tests/components/sentry/ @dcramer @frenck
|
||||
/homeassistant/components/senz/ @milanmeu
|
||||
@@ -1900,7 +1899,6 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/unifi_direct/ @tofuSCHNITZEL
|
||||
/homeassistant/components/unifi_discovery/ @RaHehl
|
||||
/tests/components/unifi_discovery/ @RaHehl
|
||||
/homeassistant/components/unifiled/ @florisvdk
|
||||
/homeassistant/components/unifiprotect/ @RaHehl
|
||||
/tests/components/unifiprotect/ @RaHehl
|
||||
/homeassistant/components/upb/ @gwww
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"unifi_access",
|
||||
"unifi_direct",
|
||||
"unifi_discovery",
|
||||
"unifiled",
|
||||
"unifiprotect"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
No custom actions are defined.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""The acer_projector component."""
|
||||
@@ -1,34 +0,0 @@
|
||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
CONF_READ_TIMEOUT: Final = "timeout"
|
||||
CONF_WRITE_TIMEOUT: Final = "write_timeout"
|
||||
|
||||
DEFAULT_NAME: Final = "Acer Projector"
|
||||
DEFAULT_READ_TIMEOUT: Final = 1
|
||||
DEFAULT_WRITE_TIMEOUT: Final = 1
|
||||
|
||||
ECO_MODE: Final = "ECO Mode"
|
||||
|
||||
ICON: Final = "mdi:projector"
|
||||
|
||||
INPUT_SOURCE: Final = "Input Source"
|
||||
|
||||
LAMP: Final = "Lamp"
|
||||
LAMP_HOURS: Final = "Lamp Hours"
|
||||
|
||||
MODEL: Final = "Model"
|
||||
|
||||
# Commands known to the projector
|
||||
CMD_DICT: Final[dict[str, str]] = {
|
||||
LAMP: "* 0 Lamp ?\r",
|
||||
LAMP_HOURS: "* 0 Lamp\r",
|
||||
INPUT_SOURCE: "* 0 Src ?\r",
|
||||
ECO_MODE: "* 0 IR 052\r",
|
||||
MODEL: "* 0 IR 035\r",
|
||||
STATE_ON: "* 0 IR 001\r",
|
||||
STATE_OFF: "* 0 IR 002\r",
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"domain": "acer_projector",
|
||||
"name": "Acer Projector",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["serialx==1.8.2"]
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, override
|
||||
|
||||
from serialx import Serial, SerialException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
PLATFORM_SCHEMA as SWITCH_PLATFORM_SCHEMA,
|
||||
SwitchEntity,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_FILENAME,
|
||||
CONF_NAME,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import (
|
||||
CMD_DICT,
|
||||
CONF_READ_TIMEOUT,
|
||||
CONF_WRITE_TIMEOUT,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
DEFAULT_WRITE_TIMEOUT,
|
||||
ECO_MODE,
|
||||
ICON,
|
||||
INPUT_SOURCE,
|
||||
LAMP,
|
||||
LAMP_HOURS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_FILENAME): cv.isdevice,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_READ_TIMEOUT, default=DEFAULT_READ_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_WRITE_TIMEOUT, default=DEFAULT_WRITE_TIMEOUT
|
||||
): cv.positive_int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Connect with serial port and return Acer Projector."""
|
||||
serial_port = config[CONF_FILENAME]
|
||||
name = config[CONF_NAME]
|
||||
read_timeout = config[CONF_READ_TIMEOUT]
|
||||
write_timeout = config[CONF_WRITE_TIMEOUT]
|
||||
|
||||
add_entities([AcerSwitch(serial_port, name, read_timeout, write_timeout)], True)
|
||||
|
||||
|
||||
class AcerSwitch(SwitchEntity):
|
||||
"""Represents an Acer Projector as a switch."""
|
||||
|
||||
_attr_icon = ICON
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
serial_port: str,
|
||||
name: str,
|
||||
read_timeout: int,
|
||||
write_timeout: int,
|
||||
) -> None:
|
||||
"""Init of the Acer projector."""
|
||||
self._serial_port = serial_port
|
||||
self._read_timeout = read_timeout
|
||||
self._write_timeout = write_timeout
|
||||
|
||||
self._attr_name = name
|
||||
self._attributes = {
|
||||
LAMP_HOURS: STATE_UNKNOWN,
|
||||
INPUT_SOURCE: STATE_UNKNOWN,
|
||||
ECO_MODE: STATE_UNKNOWN,
|
||||
}
|
||||
|
||||
def _write_read(self, msg: str) -> str:
|
||||
"""Write to the projector and read the return."""
|
||||
|
||||
# Sometimes the projector won't answer for no reason or the projector
|
||||
# was disconnected during runtime.
|
||||
# This way the projector can be reconnected and will still work
|
||||
try:
|
||||
with Serial.from_url(
|
||||
self._serial_port,
|
||||
read_timeout=self._read_timeout,
|
||||
write_timeout=self._write_timeout,
|
||||
) as serial:
|
||||
serial.write(msg.encode("utf-8"))
|
||||
|
||||
# Size is an experience value there is no real limit.
|
||||
# AFAIK there is no limit and no end character so we will usually
|
||||
# need to wait for timeout
|
||||
return serial.read_until(size=20).decode("utf-8")
|
||||
except (OSError, SerialException, TimeoutError) as exc:
|
||||
raise HomeAssistantError(
|
||||
f"Problem communicating with {self._serial_port}"
|
||||
) from exc
|
||||
|
||||
def _write_read_format(self, msg: str) -> str:
|
||||
"""Write msg, obtain answer and format output."""
|
||||
# answers are formatted as ***\answer\r***
|
||||
awns = self._write_read(msg)
|
||||
if match := re.search(r"\r(.+)\r", awns):
|
||||
return match.group(1)
|
||||
return STATE_UNKNOWN
|
||||
|
||||
def update(self) -> None:
|
||||
"""Get the latest state from the projector."""
|
||||
awns = self._write_read_format(CMD_DICT[LAMP])
|
||||
if awns == "Lamp 1":
|
||||
self._attr_is_on = True
|
||||
self._attr_available = True
|
||||
elif awns == "Lamp 0":
|
||||
self._attr_is_on = False
|
||||
self._attr_available = True
|
||||
else:
|
||||
self._attr_available = False
|
||||
|
||||
for key in self._attributes:
|
||||
if msg := CMD_DICT.get(key):
|
||||
awns = self._write_read_format(msg)
|
||||
self._attributes[key] = awns
|
||||
self._attr_extra_state_attributes = self._attributes
|
||||
|
||||
@override
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the projector on."""
|
||||
msg = CMD_DICT[STATE_ON]
|
||||
self._write_read(msg)
|
||||
self._attr_is_on = True
|
||||
|
||||
@override
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the projector off."""
|
||||
msg = CMD_DICT[STATE_OFF]
|
||||
self._write_read(msg)
|
||||
self._attr_is_on = False
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not have custom service actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: This integration does not subscribe to external events.
|
||||
|
||||
@@ -18,9 +18,15 @@ rules:
|
||||
comment: Data descriptions missing
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: todo
|
||||
docs-removal-instructions: todo
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities do not explicitly subscribe to events.
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: This integration does not register any events.
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not register custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Integration does not subscribe to events.
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not register custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Integration does not use event subscriptions.
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["airos==0.6.8"]
|
||||
"requirements": ["airos==0.6.9"]
|
||||
}
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: airOS does not have actions
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: local_polling without events
|
||||
|
||||
@@ -10,9 +10,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not provide custom actions
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not register any service actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Integration does not subscribe to external events.
|
||||
|
||||
@@ -8,7 +8,7 @@ from propcache.api import cached_property
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
from homeassistant.const import ( # noqa: F401
|
||||
ATTR_CODE,
|
||||
ATTR_CODE_FORMAT,
|
||||
SERVICE_ALARM_ARM_AWAY,
|
||||
@@ -28,11 +28,12 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import (
|
||||
from .const import ( # noqa: F401
|
||||
ATTR_CHANGED_BY,
|
||||
ATTR_CODE_ARM_REQUIRED,
|
||||
DOMAIN,
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelEntityStateAttribute,
|
||||
AlarmControlPanelState,
|
||||
CodeFormat,
|
||||
)
|
||||
@@ -303,9 +304,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_CODE_FORMAT: self.code_format,
|
||||
ATTR_CHANGED_BY: self.changed_by,
|
||||
ATTR_CODE_ARM_REQUIRED: self.code_arm_required,
|
||||
AlarmControlPanelEntityStateAttribute.CODE_FORMAT: self.code_format,
|
||||
AlarmControlPanelEntityStateAttribute.CHANGED_BY: self.changed_by,
|
||||
AlarmControlPanelEntityStateAttribute.CODE_ARM_REQUIRED: (
|
||||
self.code_arm_required
|
||||
),
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -9,6 +9,14 @@ ATTR_CHANGED_BY: Final = "changed_by"
|
||||
ATTR_CODE_ARM_REQUIRED: Final = "code_arm_required"
|
||||
|
||||
|
||||
class AlarmControlPanelEntityStateAttribute(StrEnum):
|
||||
"""State attributes for alarm control panel entities."""
|
||||
|
||||
CODE_FORMAT = "code_format"
|
||||
CHANGED_BY = "changed_by"
|
||||
CODE_ARM_REQUIRED = "code_arm_required"
|
||||
|
||||
|
||||
class AlarmControlPanelState(StrEnum):
|
||||
"""Alarm control panel entity states."""
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ PLATFORMS = [
|
||||
Platform.EVENT,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.NOTIFY,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.TODO,
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
"default": "mdi:chat-processing"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"dropin": {
|
||||
"default": "mdi:account-multiple-plus",
|
||||
"state": {
|
||||
"all": "mdi:account-multiple-plus",
|
||||
"home": "mdi:home-plus",
|
||||
"off": "mdi:phone-off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"voc_index": {
|
||||
"default": "mdi:molecule"
|
||||
|
||||
@@ -84,6 +84,7 @@ class AmazonNotifyEntity(AmazonEntity, NotifyEntity):
|
||||
entity_description: AmazonNotifyEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return (
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: no actions
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: entities do not explicitly subscribe to events
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Support for select entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Final, override
|
||||
|
||||
from aioamazondevices.structures import AmazonDropInStatus
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import AmazonConfigEntry, AmazonDevice, alexa_api_call
|
||||
from .entity import AmazonEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AmazonSelectEntityDescription(SelectEntityDescription):
|
||||
"""Alexa Devices select entity description."""
|
||||
|
||||
method: str
|
||||
|
||||
|
||||
SELECTS: Final = (
|
||||
AmazonSelectEntityDescription(
|
||||
key="dropin",
|
||||
translation_key="dropin",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
method="set_dropin_status",
|
||||
# API values: "All", "Home", "Off"
|
||||
options=[status.value.lower() for status in AmazonDropInStatus],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AmazonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up select entities based on a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
known_devices: set[str] = set()
|
||||
|
||||
def _check_device() -> None:
|
||||
current_devices = set(coordinator.data)
|
||||
new_devices = current_devices - known_devices
|
||||
if new_devices:
|
||||
known_devices.update(new_devices)
|
||||
select_entities = [
|
||||
AmazonSelectEntity(coordinator, serial_num, select_desc)
|
||||
for select_desc in SELECTS
|
||||
for serial_num in new_devices
|
||||
if select_desc.key
|
||||
in coordinator.data[serial_num].communication_settings
|
||||
]
|
||||
async_add_entities(select_entities)
|
||||
|
||||
_check_device()
|
||||
entry.async_on_unload(coordinator.async_add_listener(_check_device))
|
||||
|
||||
|
||||
class AmazonSelectEntity(AmazonEntity, SelectEntity):
|
||||
"""Representation of a select entity for the default Alexa device."""
|
||||
|
||||
entity_description: AmazonSelectEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def options(self) -> list[str]:
|
||||
"""Return a list of available options."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.entity_description.options is not None
|
||||
|
||||
return self.entity_description.options
|
||||
|
||||
@property
|
||||
def _device(self) -> AmazonDevice:
|
||||
"""Return the device."""
|
||||
return self.coordinator.data[self._serial_num]
|
||||
|
||||
@override
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last known option."""
|
||||
await super().async_added_to_hass()
|
||||
self._attr_current_option = self._device.communication_settings[
|
||||
self.entity_description.key
|
||||
].lower()
|
||||
|
||||
@override
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
method = getattr(self.coordinator.api, self.entity_description.method)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert method is not None
|
||||
|
||||
async with alexa_api_call(self.coordinator):
|
||||
await method(self.device, AmazonDropInStatus(option.capitalize()))
|
||||
|
||||
self._attr_current_option = option
|
||||
self.async_write_ha_state()
|
||||
@@ -78,6 +78,16 @@
|
||||
"name": "Speak"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"dropin": {
|
||||
"name": "Drop In",
|
||||
"state": {
|
||||
"all": "Allow drop in from anyone",
|
||||
"home": "Allow drop in from household members",
|
||||
"off": "Don't allow drop in"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"alarm": {
|
||||
"name": "Next alarm"
|
||||
@@ -140,6 +150,9 @@
|
||||
"invalid_sound_value": {
|
||||
"message": "Invalid sound {sound} specified"
|
||||
},
|
||||
"select_option_not_found": {
|
||||
"message": "Selected option not found: {option}"
|
||||
},
|
||||
"unknown_exception": {
|
||||
"message": "Unknown error occurred: {error}"
|
||||
}
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""The Ampio component."""
|
||||
@@ -1,105 +0,0 @@
|
||||
"""Support for Ampio Air Quality data."""
|
||||
|
||||
import logging
|
||||
from typing import Final, override
|
||||
|
||||
from asmog import AmpioSmog
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.air_quality import (
|
||||
PLATFORM_SCHEMA as AIR_QUALITY_PLATFORM_SCHEMA,
|
||||
AirQualityEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import CONF_STATION_ID, SCAN_INTERVAL
|
||||
|
||||
_LOGGER: Final = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA: Final = AIR_QUALITY_PLATFORM_SCHEMA.extend(
|
||||
{vol.Required(CONF_STATION_ID): cv.string, vol.Optional(CONF_NAME): cv.string}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Ampio Smog air quality platform."""
|
||||
|
||||
name = config.get(CONF_NAME)
|
||||
station_id = config[CONF_STATION_ID]
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
api = AmpioSmogMapData(AmpioSmog(station_id, hass.loop, session))
|
||||
|
||||
await api.async_update()
|
||||
|
||||
if not api.api.data:
|
||||
_LOGGER.error("Station %s is not available", station_id)
|
||||
return
|
||||
|
||||
async_add_entities([AmpioSmogQuality(api, station_id, name)], True)
|
||||
|
||||
|
||||
class AmpioSmogQuality(AirQualityEntity):
|
||||
"""Implementation of an Ampio Smog air quality entity."""
|
||||
|
||||
_attr_attribution = "Data provided by Ampio"
|
||||
|
||||
def __init__(
|
||||
self, api: AmpioSmogMapData, station_id: str, name: str | None
|
||||
) -> None:
|
||||
"""Initialize the air quality entity."""
|
||||
self._ampio = api
|
||||
self._station_id = station_id
|
||||
self._name = name or api.api.name
|
||||
|
||||
@property
|
||||
@override
|
||||
def name(self) -> str:
|
||||
"""Return the name of the air quality entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
@override
|
||||
def unique_id(self) -> str:
|
||||
"""Return unique_name."""
|
||||
return f"ampio_smog_{self._station_id}" # pylint: disable=home-assistant-entity-unique-id-redundant-domain
|
||||
|
||||
@property
|
||||
@override
|
||||
def particulate_matter_2_5(self) -> str | None:
|
||||
"""Return the particulate matter 2.5 level."""
|
||||
return self._ampio.api.pm2_5 # type: ignore[no-any-return]
|
||||
|
||||
@property
|
||||
@override
|
||||
def particulate_matter_10(self) -> str | None:
|
||||
"""Return the particulate matter 10 level."""
|
||||
return self._ampio.api.pm10 # type: ignore[no-any-return]
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data from the AmpioMap API."""
|
||||
await self._ampio.async_update()
|
||||
|
||||
|
||||
class AmpioSmogMapData:
|
||||
"""Get the latest data and update the states."""
|
||||
|
||||
def __init__(self, api: AmpioSmog) -> None:
|
||||
"""Initialize the data object."""
|
||||
self.api = api
|
||||
|
||||
@Throttle(SCAN_INTERVAL)
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data from AmpioMap."""
|
||||
await self.api.get_data()
|
||||
@@ -1,7 +0,0 @@
|
||||
"""Constants for Ampio Air Quality platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Final
|
||||
|
||||
CONF_STATION_ID: Final = "station_id"
|
||||
SCAN_INTERVAL: Final = timedelta(minutes=10)
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "ampio",
|
||||
"name": "Ampio Smart Smog System",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/ampio",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["asmog"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["asmog==0.0.6"]
|
||||
}
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: todo
|
||||
docs-installation-instructions: todo
|
||||
docs-removal-instructions: todo
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
No custom actions are defined.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
Integration has no actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -11,9 +11,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
The integration does not provide any actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"available_volume": {
|
||||
"default": "mdi:car-coolant-level"
|
||||
},
|
||||
"ground_water_level": {
|
||||
"default": "mdi:arrow-collapse-down"
|
||||
},
|
||||
"in_flow": {
|
||||
"default": "mdi:water-plus-outline"
|
||||
},
|
||||
"meter_value": {
|
||||
"level_from_sensor": {
|
||||
"default": "mdi:waves-arrow-up"
|
||||
},
|
||||
"level_from_top": {
|
||||
"default": "mdi:waves"
|
||||
},
|
||||
"out_volume": {
|
||||
"default": "mdi:water-pump"
|
||||
},
|
||||
"stored_volume": {
|
||||
"default": "mdi:car-coolant-level"
|
||||
},
|
||||
"water_level": {
|
||||
"default": "mdi:waves"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyaqvify"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyaqvify==0.0.11"]
|
||||
"requirements": ["pyaqvify==0.0.12"]
|
||||
}
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
The integration does not provide any actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -46,8 +46,8 @@ class AqvifySensorAggrEntityDescription(SensorEntityDescription):
|
||||
|
||||
ENTITIES: tuple[AqvifySensorEntityDescription, ...] = (
|
||||
AqvifySensorEntityDescription(
|
||||
key="meter_value",
|
||||
translation_key="meter_value",
|
||||
key="level_from_sensor",
|
||||
translation_key="level_from_sensor",
|
||||
native_unit_of_measurement=UnitOfLength.METERS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
@@ -55,8 +55,8 @@ ENTITIES: tuple[AqvifySensorEntityDescription, ...] = (
|
||||
value_fn=lambda value: value.meter_value,
|
||||
),
|
||||
AqvifySensorEntityDescription(
|
||||
key="water_level",
|
||||
translation_key="water_level",
|
||||
key="level_from_top",
|
||||
translation_key="level_from_top",
|
||||
native_unit_of_measurement=UnitOfLength.METERS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
@@ -64,8 +64,8 @@ ENTITIES: tuple[AqvifySensorEntityDescription, ...] = (
|
||||
value_fn=lambda value: value.water_level,
|
||||
),
|
||||
AqvifySensorEntityDescription(
|
||||
key="stored_volume",
|
||||
translation_key="stored_volume",
|
||||
key="available_volume",
|
||||
translation_key="available_volume",
|
||||
native_unit_of_measurement=UnitOfVolume.LITERS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.VOLUME_STORAGE,
|
||||
|
||||
@@ -34,23 +34,23 @@
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"available_volume": {
|
||||
"name": "Available volume"
|
||||
},
|
||||
"ground_water_level": {
|
||||
"name": "Ground water level"
|
||||
},
|
||||
"in_flow": {
|
||||
"name": "Inflow"
|
||||
},
|
||||
"meter_value": {
|
||||
"name": "Meter value"
|
||||
"level_from_sensor": {
|
||||
"name": "Level from sensor"
|
||||
},
|
||||
"level_from_top": {
|
||||
"name": "Level from top"
|
||||
},
|
||||
"out_volume": {
|
||||
"name": "Outflow"
|
||||
},
|
||||
"stored_volume": {
|
||||
"name": "Stored volume"
|
||||
},
|
||||
"water_level": {
|
||||
"name": "Water level"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -12,7 +12,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
|
||||
from homeassistant.const import (
|
||||
from homeassistant.const import ( # noqa: F401
|
||||
ATTR_AREA_ID,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FLOOR_ID,
|
||||
@@ -58,7 +58,7 @@ from homeassistant.helpers.issue_registry import (
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.script import (
|
||||
from homeassistant.helpers.script import ( # noqa: F401
|
||||
ATTR_CUR,
|
||||
ATTR_MAX,
|
||||
CONF_MAX,
|
||||
@@ -91,6 +91,8 @@ from .const import (
|
||||
DEFAULT_INITIAL_STATE,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
AutomationEntityCapabilityAttribute,
|
||||
AutomationEntityStateAttribute,
|
||||
)
|
||||
from .helpers import async_get_blueprints
|
||||
from .trace import trace_automation
|
||||
@@ -318,7 +320,13 @@ class BaseAutomationEntity(ToggleEntity, ABC):
|
||||
"""Base class for automation entities."""
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset(
|
||||
(ATTR_LAST_TRIGGERED, ATTR_MODE, ATTR_CUR, ATTR_MAX, CONF_ID)
|
||||
(
|
||||
AutomationEntityStateAttribute.LAST_TRIGGERED,
|
||||
AutomationEntityStateAttribute.MODE,
|
||||
AutomationEntityStateAttribute.CUR,
|
||||
AutomationEntityStateAttribute.MAX,
|
||||
AutomationEntityCapabilityAttribute.ID,
|
||||
)
|
||||
)
|
||||
raw_config: ConfigType | None
|
||||
|
||||
@@ -327,7 +335,7 @@ class BaseAutomationEntity(ToggleEntity, ABC):
|
||||
def capability_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return capability attributes."""
|
||||
if self.unique_id is not None:
|
||||
return {CONF_ID: self.unique_id}
|
||||
return {AutomationEntityCapabilityAttribute.ID: self.unique_id}
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
@@ -507,13 +515,15 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
@override
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the entity state attributes."""
|
||||
attrs = {
|
||||
ATTR_LAST_TRIGGERED: self.action_script.last_triggered,
|
||||
ATTR_MODE: self.action_script.script_mode,
|
||||
ATTR_CUR: self.action_script.runs,
|
||||
attrs: dict[str, Any] = {
|
||||
AutomationEntityStateAttribute.LAST_TRIGGERED: (
|
||||
self.action_script.last_triggered
|
||||
),
|
||||
AutomationEntityStateAttribute.MODE: self.action_script.script_mode,
|
||||
AutomationEntityStateAttribute.CUR: self.action_script.runs,
|
||||
}
|
||||
if self.action_script.supports_max:
|
||||
attrs[ATTR_MAX] = self.action_script.max_runs
|
||||
attrs[AutomationEntityStateAttribute.MAX] = self.action_script.max_runs
|
||||
return attrs
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
"""Constants for the automation integration."""
|
||||
|
||||
from enum import StrEnum
|
||||
import logging
|
||||
|
||||
CONF_TRIGGER_VARIABLES = "trigger_variables"
|
||||
DOMAIN = "automation"
|
||||
|
||||
|
||||
class AutomationEntityCapabilityAttribute(StrEnum):
|
||||
"""Capability attributes for automation entities."""
|
||||
|
||||
ID = "id"
|
||||
|
||||
|
||||
class AutomationEntityStateAttribute(StrEnum):
|
||||
"""State attributes for automation entities."""
|
||||
|
||||
LAST_TRIGGERED = "last_triggered"
|
||||
MODE = "mode"
|
||||
CUR = "current"
|
||||
MAX = "max"
|
||||
|
||||
|
||||
CONF_HIDE_ENTITY = "hide_entity"
|
||||
|
||||
CONF_CONDITION_TYPE = "condition_type"
|
||||
|
||||
@@ -16,9 +16,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
Integration does not provide custom services.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"data_description": {
|
||||
"host": "The URL of your Autoskope API endpoint. Only change this if you use a white-label portal."
|
||||
},
|
||||
"name": "Advanced settings"
|
||||
"name": "Additional settings"
|
||||
}
|
||||
},
|
||||
"title": "Connect to Autoskope"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""The avion component."""
|
||||
@@ -1,123 +0,0 @@
|
||||
"""Support for Avion dimmers."""
|
||||
|
||||
import importlib
|
||||
import time
|
||||
from typing import Any, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_DEVICES,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
vol.Optional(CONF_ID): cv.positive_int,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA},
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up an Avion switch."""
|
||||
avion = importlib.import_module("avion")
|
||||
|
||||
lights = [
|
||||
AvionLight(
|
||||
avion.Avion(
|
||||
mac=address,
|
||||
passphrase=device_config[CONF_API_KEY],
|
||||
name=device_config.get(CONF_NAME),
|
||||
object_id=device_config.get(CONF_ID),
|
||||
connect=False,
|
||||
)
|
||||
)
|
||||
for address, device_config in config[CONF_DEVICES].items()
|
||||
]
|
||||
if CONF_USERNAME in config and CONF_PASSWORD in config:
|
||||
lights.extend(
|
||||
AvionLight(device)
|
||||
for device in avion.get_devices(
|
||||
config[CONF_USERNAME], config[CONF_PASSWORD]
|
||||
)
|
||||
)
|
||||
|
||||
add_entities(lights)
|
||||
|
||||
|
||||
class AvionLight(LightEntity):
|
||||
"""Representation of an Avion light."""
|
||||
|
||||
_attr_support_color_mode = ColorMode.BRIGHTNESS
|
||||
_attr_support_color_modes = {ColorMode.BRIGHTNESS}
|
||||
_attr_should_poll = False
|
||||
_attr_assumed_state = True
|
||||
_attr_is_on = True
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize the light."""
|
||||
self._attr_name = device.name
|
||||
self._attr_unique_id = device.mac
|
||||
self._attr_brightness = 255
|
||||
self._switch = device
|
||||
|
||||
def set_state(self, brightness):
|
||||
"""Set the state of this lamp to the provided brightness."""
|
||||
avion = importlib.import_module("avion")
|
||||
|
||||
# Bluetooth LE is unreliable, and the connection may drop at any
|
||||
# time. Make an effort to re-establish the link.
|
||||
initial = time.monotonic()
|
||||
while True:
|
||||
if time.monotonic() - initial >= 10:
|
||||
return False
|
||||
try:
|
||||
self._switch.set_brightness(brightness)
|
||||
break
|
||||
except avion.AvionException:
|
||||
self._switch.connect()
|
||||
return True
|
||||
|
||||
@override
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the specified or all lights on."""
|
||||
if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None:
|
||||
self._attr_brightness = brightness
|
||||
|
||||
self.set_state(self.brightness)
|
||||
self._attr_is_on = True
|
||||
|
||||
@override
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the specified or all lights off."""
|
||||
self.set_state(0)
|
||||
self._attr_is_on = False
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"domain": "avion",
|
||||
"name": "Avi-on",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/avion",
|
||||
"iot_class": "assumed_state",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["avion==0.10"]
|
||||
}
|
||||
@@ -28,9 +28,15 @@ rules:
|
||||
comment: |
|
||||
Dependency is not built in the CI
|
||||
docs-actions: todo
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: No explicit event subscription
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not have any custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities of this integration does not explicitly subscribe to events.
|
||||
|
||||
@@ -27,6 +27,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.selector import TextSelector, TextSelectorConfig
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
from homeassistant.helpers.service_info.ssdp import (
|
||||
ATTR_UPNP_FRIENDLY_NAME,
|
||||
@@ -98,8 +99,11 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
else:
|
||||
if (serial := self._get_serial_number(api)) is None:
|
||||
return self.async_abort(reason="no_serial_number")
|
||||
if not self.unique_id:
|
||||
if (serial := self._get_formatted_serial(api)) is None:
|
||||
return self.async_abort(reason="no_serial_number")
|
||||
await self.async_set_unique_id(serial)
|
||||
|
||||
config = {
|
||||
CONF_PROTOCOL: user_input[CONF_PROTOCOL],
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
@@ -108,8 +112,6 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
}
|
||||
|
||||
await self.async_set_unique_id(format_mac(serial))
|
||||
|
||||
if self.source == SOURCE_REAUTH:
|
||||
self._abort_if_unique_id_mismatch()
|
||||
return self.async_update_and_abort(
|
||||
@@ -124,7 +126,7 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self.config = config | {CONF_MODEL: api.vapix.product_number}
|
||||
|
||||
return await self._create_entry(serial)
|
||||
return await self._create_entry()
|
||||
|
||||
data = self.discovery_schema or {
|
||||
vol.Required(CONF_PROTOCOL): vol.In(PROTOCOL_CHOICES),
|
||||
@@ -141,7 +143,7 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def _create_entry(self, serial: str) -> ConfigFlowResult:
|
||||
async def _create_entry(self) -> ConfigFlowResult:
|
||||
"""Create entry for device.
|
||||
|
||||
Use the discovered device name when available.
|
||||
@@ -149,7 +151,7 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
if (title_placeholders := self.context.get("title_placeholders")) is not None:
|
||||
name = title_placeholders[CONF_NAME]
|
||||
else:
|
||||
name = f"{self.config[CONF_MODEL]} - {serial}"
|
||||
name = f"{self.config[CONF_MODEL]} - {self.unique_id}"
|
||||
self.config[CONF_NAME] = name
|
||||
|
||||
return self.async_create_entry(title=name, data=self.config)
|
||||
@@ -196,7 +198,7 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return await self._process_discovered_device(
|
||||
{
|
||||
CONF_HOST: discovery_info.ip,
|
||||
CONF_MAC: format_mac(discovery_info.macaddress),
|
||||
CONF_MAC: discovery_info.macaddress,
|
||||
CONF_NAME: discovery_info.hostname,
|
||||
CONF_PORT: 80,
|
||||
}
|
||||
@@ -211,7 +213,7 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return await self._process_discovered_device(
|
||||
{
|
||||
CONF_HOST: url.hostname,
|
||||
CONF_MAC: format_mac(discovery_info.upnp[ATTR_UPNP_SERIAL]),
|
||||
CONF_MAC: discovery_info.upnp[ATTR_UPNP_SERIAL],
|
||||
CONF_NAME: f"{discovery_info.upnp[ATTR_UPNP_FRIENDLY_NAME]}",
|
||||
CONF_PORT: url.port,
|
||||
}
|
||||
@@ -225,7 +227,7 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return await self._process_discovered_device(
|
||||
{
|
||||
CONF_HOST: discovery_info.host,
|
||||
CONF_MAC: format_mac(discovery_info.properties["macaddress"]),
|
||||
CONF_MAC: discovery_info.properties["macaddress"],
|
||||
CONF_NAME: discovery_info.name.split(".", 1)[0],
|
||||
CONF_PORT: discovery_info.port,
|
||||
}
|
||||
@@ -235,17 +237,17 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self, discovery_info: dict[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Prepare configuration for a discovered Axis device."""
|
||||
if discovery_info[CONF_MAC][:8] not in AXIS_OUI:
|
||||
serial = format_mac(discovery_info[CONF_MAC])
|
||||
if serial[:8] not in AXIS_OUI:
|
||||
return self.async_abort(reason="not_axis_device")
|
||||
|
||||
if is_link_local(ip_address(discovery_info[CONF_HOST])):
|
||||
return self.async_abort(reason="link_local_address")
|
||||
|
||||
await self.async_set_unique_id(discovery_info[CONF_MAC])
|
||||
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: discovery_info[CONF_HOST]}, reload_on_update=False
|
||||
)
|
||||
if await self.async_set_unique_id(serial):
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: discovery_info[CONF_HOST]}, reload_on_update=False
|
||||
)
|
||||
|
||||
self.context.update(
|
||||
{
|
||||
@@ -259,7 +261,9 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self.discovery_schema = {
|
||||
vol.Required(CONF_PROTOCOL): vol.In(PROTOCOL_CHOICES),
|
||||
vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str,
|
||||
vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): TextSelector(
|
||||
TextSelectorConfig(read_only=True)
|
||||
),
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
@@ -268,16 +272,16 @@ class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return await self.async_step_user()
|
||||
|
||||
@staticmethod
|
||||
def _get_serial_number(api: axis.AxisDevice) -> str | None:
|
||||
def _get_formatted_serial(api: axis.AxisDevice) -> str | None:
|
||||
"""Retrieve the device serial number from the Axis API.
|
||||
|
||||
Tries basic_device_info first, then property_handler. Returns None if not found.
|
||||
"""
|
||||
vapix = api.vapix
|
||||
if vapix.basic_device_info.initialized:
|
||||
return vapix.basic_device_info["0"].serial_number
|
||||
return format_mac(vapix.basic_device_info["0"].serial_number)
|
||||
if vapix.params.property_handler.initialized:
|
||||
return vapix.params.property_handler["0"].system_serial_number
|
||||
return format_mac(vapix.params.property_handler["0"].system_serial_number)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -16,9 +16,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not have any custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not have any custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities of this integration do not explicitly subscribe to events.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""The beewi_smartclim component."""
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "beewi_smartclim",
|
||||
"name": "BeeWi SmartClim BLE sensor",
|
||||
"codeowners": ["@alemuro"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/beewi_smartclim",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["beewi_smartclim"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["beewi-smartclim==0.0.10"]
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
"""Platform for beewi_smartclim integration."""
|
||||
|
||||
from beewi_smartclim import BeewiSmartClimPoller
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_MAC, CONF_NAME, PERCENTAGE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
# Default values
|
||||
DEFAULT_NAME = "BeeWi SmartClim"
|
||||
|
||||
# Sensor config
|
||||
SENSOR_TYPES = [
|
||||
[SensorDeviceClass.TEMPERATURE, "Temperature", UnitOfTemperature.CELSIUS],
|
||||
[SensorDeviceClass.HUMIDITY, "Humidity", PERCENTAGE],
|
||||
[SensorDeviceClass.BATTERY, "Battery", PERCENTAGE],
|
||||
]
|
||||
|
||||
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_MAC): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the beewi_smartclim platform."""
|
||||
|
||||
mac = config[CONF_MAC]
|
||||
prefix = config[CONF_NAME]
|
||||
poller = BeewiSmartClimPoller(mac)
|
||||
|
||||
sensors = []
|
||||
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
device = sensor_type[0]
|
||||
name = sensor_type[1]
|
||||
unit = sensor_type[2]
|
||||
# `prefix` is the name configured by the user for the sensor, we're appending
|
||||
# the device type at the end of the name (garden -> garden temperature)
|
||||
if prefix:
|
||||
name = f"{prefix} {name}"
|
||||
|
||||
sensors.append(BeewiSmartclimSensor(poller, name, mac, device, unit))
|
||||
|
||||
add_entities(sensors)
|
||||
|
||||
|
||||
class BeewiSmartclimSensor(SensorEntity):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
def __init__(self, poller, name, mac, device, unit):
|
||||
"""Initialize the sensor."""
|
||||
self._poller = poller
|
||||
self._attr_name = name
|
||||
self._device = device
|
||||
self._attr_native_unit_of_measurement = unit
|
||||
self._attr_device_class = self._device
|
||||
self._attr_unique_id = f"{mac}_{device}"
|
||||
|
||||
def update(self) -> None:
|
||||
"""Fetch new state data from the poller."""
|
||||
self._poller.update_sensor()
|
||||
self._attr_native_value = None
|
||||
if self._device == SensorDeviceClass.TEMPERATURE:
|
||||
self._attr_native_value = self._poller.get_temperature()
|
||||
if self._device == SensorDeviceClass.HUMIDITY:
|
||||
self._attr_native_value = self._poller.get_humidity()
|
||||
if self._device == SensorDeviceClass.BATTERY:
|
||||
self._attr_native_value = self._poller.get_battery()
|
||||
@@ -15,16 +15,15 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfApparentPower,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfReactiveEnergy,
|
||||
UnitOfReactivePower,
|
||||
UnitOfSpeed,
|
||||
@@ -53,19 +52,19 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="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,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm2_5",
|
||||
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,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
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,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
@@ -84,7 +83,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
@@ -179,7 +178,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
"requirements": [
|
||||
"bleak==3.0.2",
|
||||
"bleak-retry-connector==4.6.1",
|
||||
"bluetooth-adapters==2.3.0",
|
||||
"bluetooth-adapters==2.4.0",
|
||||
"bluetooth-auto-recovery==1.6.4",
|
||||
"bluetooth-data-tools==1.29.18",
|
||||
"dbus-fast==5.0.16",
|
||||
"habluetooth==6.8.3"
|
||||
"dbus-fast==5.0.22",
|
||||
"habluetooth==6.25.1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"domain": "bosch_shc",
|
||||
"name": "Bosch SHC",
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": ["@tschamm"],
|
||||
"codeowners": ["@tschamm", "@mosandlt"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/bosch_shc",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["boschshcpy"],
|
||||
"requirements": ["boschshcpy==0.2.111"],
|
||||
"requirements": ["boschshcpy==0.3.5"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "bosch shc*",
|
||||
|
||||
@@ -10,9 +10,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: The integration registers no events
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -8,9 +8,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -68,6 +68,7 @@ from .const import (
|
||||
EVENT_UID,
|
||||
LIST_EVENT_FIELDS,
|
||||
CalendarEntityFeature,
|
||||
CalendarEntityStateAttribute,
|
||||
)
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
@@ -519,7 +520,9 @@ class CalendarEntity(Entity):
|
||||
|
||||
entity_description: CalendarEntityDescription
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset({"description"})
|
||||
_entity_component_unrecorded_attributes = frozenset(
|
||||
{CalendarEntityStateAttribute.DESCRIPTION}
|
||||
)
|
||||
|
||||
_alarm_unsubs: list[CALLBACK_TYPE] | None = None
|
||||
_event_listeners: (
|
||||
@@ -573,12 +576,16 @@ class CalendarEntity(Entity):
|
||||
return None
|
||||
|
||||
return {
|
||||
"message": event.summary,
|
||||
"all_day": event.all_day,
|
||||
"start_time": event.start_datetime_local.strftime(DATE_STR_FORMAT),
|
||||
"end_time": event.end_datetime_local.strftime(DATE_STR_FORMAT),
|
||||
"location": event.location or "",
|
||||
"description": event.description or "",
|
||||
CalendarEntityStateAttribute.MESSAGE: event.summary,
|
||||
CalendarEntityStateAttribute.ALL_DAY: event.all_day,
|
||||
CalendarEntityStateAttribute.START_TIME: event.start_datetime_local.strftime(
|
||||
DATE_STR_FORMAT
|
||||
),
|
||||
CalendarEntityStateAttribute.END_TIME: event.end_datetime_local.strftime(
|
||||
DATE_STR_FORMAT
|
||||
),
|
||||
CalendarEntityStateAttribute.LOCATION: event.location or "",
|
||||
CalendarEntityStateAttribute.DESCRIPTION: event.description or "",
|
||||
}
|
||||
|
||||
@final
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants for calendar components."""
|
||||
|
||||
from enum import IntFlag
|
||||
from enum import IntFlag, StrEnum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
@@ -14,6 +14,17 @@ DOMAIN = "calendar"
|
||||
DATA_COMPONENT: HassKey[EntityComponent[CalendarEntity]] = HassKey(DOMAIN)
|
||||
|
||||
|
||||
class CalendarEntityStateAttribute(StrEnum):
|
||||
"""State attributes for calendar entities."""
|
||||
|
||||
MESSAGE = "message"
|
||||
ALL_DAY = "all_day"
|
||||
START_TIME = "start_time"
|
||||
END_TIME = "end_time"
|
||||
LOCATION = "location"
|
||||
DESCRIPTION = "description"
|
||||
|
||||
|
||||
class CalendarEntityFeature(IntFlag):
|
||||
"""Supported features of the calendar entity."""
|
||||
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -68,6 +68,7 @@ from .const import (
|
||||
PREF_ORIENTATION,
|
||||
PREF_PRELOAD_STREAM,
|
||||
SERVICE_RECORD,
|
||||
CameraEntityStateAttribute,
|
||||
CameraState,
|
||||
StreamType,
|
||||
)
|
||||
@@ -421,7 +422,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""The base class for camera entities."""
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset(
|
||||
{"access_token", "entity_picture"}
|
||||
{CameraEntityStateAttribute.ACCESS_TOKEN, "entity_picture"}
|
||||
)
|
||||
|
||||
# Entity Properties
|
||||
@@ -649,18 +650,22 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@final
|
||||
@property
|
||||
@override
|
||||
def state_attributes(self) -> dict[str, str | None]:
|
||||
def state_attributes(self) -> dict[str, str | bool | None]:
|
||||
"""Return the camera state attributes."""
|
||||
attrs = {"access_token": self.access_tokens[-1]}
|
||||
attrs: dict[str, str | bool | None] = {
|
||||
CameraEntityStateAttribute.ACCESS_TOKEN: self.access_tokens[-1]
|
||||
}
|
||||
|
||||
if model := self.model:
|
||||
attrs["model_name"] = model
|
||||
attrs[CameraEntityStateAttribute.MODEL_NAME] = model
|
||||
|
||||
if brand := self.brand:
|
||||
attrs["brand"] = brand
|
||||
attrs[CameraEntityStateAttribute.BRAND] = brand
|
||||
|
||||
if motion_detection_enabled := self.motion_detection_enabled:
|
||||
attrs["motion_detection"] = motion_detection_enabled
|
||||
attrs[CameraEntityStateAttribute.MOTION_DETECTION] = (
|
||||
motion_detection_enabled
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
@@ -28,6 +28,15 @@ CAMERA_STREAM_SOURCE_TIMEOUT: Final = 10
|
||||
CAMERA_IMAGE_TIMEOUT: Final = 10
|
||||
|
||||
|
||||
class CameraEntityStateAttribute(StrEnum):
|
||||
"""State attributes for camera entities."""
|
||||
|
||||
ACCESS_TOKEN = "access_token"
|
||||
MODEL_NAME = "model_name"
|
||||
BRAND = "brand"
|
||||
MOTION_DETECTION = "motion_detection"
|
||||
|
||||
|
||||
class CameraState(StrEnum):
|
||||
"""Camera entity states."""
|
||||
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: No custom actions/services.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -174,6 +174,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
|
||||
"""Set the fan mode."""
|
||||
await self.coordinator.async_set_fan_mode(self._ac_index, self.data, fan_mode)
|
||||
|
||||
@override
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set the swing mode."""
|
||||
await self.coordinator.async_set_swing_mode(
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not provide actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -18,9 +18,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not register custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Integration does not subscribe to events.
|
||||
|
||||
@@ -15,9 +15,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: No custom actions are defined.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: done
|
||||
comment: |
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: There are no custom actions
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities do not explicitly subscribe to events
|
||||
|
||||
@@ -8,9 +8,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -84,7 +84,9 @@ from .const import ( # noqa: F401
|
||||
SWING_OFF,
|
||||
SWING_ON,
|
||||
SWING_VERTICAL,
|
||||
ClimateEntityCapabilityAttribute,
|
||||
ClimateEntityFeature,
|
||||
ClimateEntityStateAttribute,
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
@@ -242,16 +244,16 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset(
|
||||
{
|
||||
ATTR_HVAC_MODES,
|
||||
ATTR_FAN_MODES,
|
||||
ATTR_SWING_MODES,
|
||||
ATTR_MIN_TEMP,
|
||||
ATTR_MAX_TEMP,
|
||||
ATTR_MIN_HUMIDITY,
|
||||
ATTR_MAX_HUMIDITY,
|
||||
ATTR_TARGET_HUMIDITY_STEP,
|
||||
ATTR_TARGET_TEMP_STEP,
|
||||
ATTR_PRESET_MODES,
|
||||
ClimateEntityCapabilityAttribute.HVAC_MODES,
|
||||
ClimateEntityCapabilityAttribute.FAN_MODES,
|
||||
ClimateEntityCapabilityAttribute.SWING_MODES,
|
||||
ClimateEntityCapabilityAttribute.MIN_TEMP,
|
||||
ClimateEntityCapabilityAttribute.MAX_TEMP,
|
||||
ClimateEntityCapabilityAttribute.MIN_HUMIDITY,
|
||||
ClimateEntityCapabilityAttribute.MAX_HUMIDITY,
|
||||
ClimateEntityCapabilityAttribute.TARGET_HUMIDITY_STEP,
|
||||
ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP,
|
||||
ClimateEntityCapabilityAttribute.PRESET_MODES,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -315,32 +317,42 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
hass = self.hass
|
||||
|
||||
data: dict[str, Any] = {
|
||||
ATTR_HVAC_MODES: self.hvac_modes,
|
||||
ATTR_MIN_TEMP: show_temp(hass, self.min_temp, temperature_unit, precision),
|
||||
ATTR_MAX_TEMP: show_temp(hass, self.max_temp, temperature_unit, precision),
|
||||
ClimateEntityCapabilityAttribute.HVAC_MODES: self.hvac_modes,
|
||||
ClimateEntityCapabilityAttribute.MIN_TEMP: show_temp(
|
||||
hass, self.min_temp, temperature_unit, precision
|
||||
),
|
||||
ClimateEntityCapabilityAttribute.MAX_TEMP: show_temp(
|
||||
hass, self.max_temp, temperature_unit, precision
|
||||
),
|
||||
}
|
||||
|
||||
if target_temperature_step := self.target_temperature_step:
|
||||
data[ATTR_TARGET_TEMP_STEP] = target_temperature_step
|
||||
data[ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP] = (
|
||||
target_temperature_step
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.TARGET_HUMIDITY in supported_features:
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
data[ClimateEntityCapabilityAttribute.MIN_HUMIDITY] = self.min_humidity
|
||||
data[ClimateEntityCapabilityAttribute.MAX_HUMIDITY] = self.max_humidity
|
||||
|
||||
if self.target_humidity_step is not None:
|
||||
data[ATTR_TARGET_HUMIDITY_STEP] = self.target_humidity_step
|
||||
data[ClimateEntityCapabilityAttribute.TARGET_HUMIDITY_STEP] = (
|
||||
self.target_humidity_step
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.FAN_MODE in supported_features:
|
||||
data[ATTR_FAN_MODES] = self.fan_modes
|
||||
data[ClimateEntityCapabilityAttribute.FAN_MODES] = self.fan_modes
|
||||
|
||||
if ClimateEntityFeature.PRESET_MODE in supported_features:
|
||||
data[ATTR_PRESET_MODES] = self.preset_modes
|
||||
data[ClimateEntityCapabilityAttribute.PRESET_MODES] = self.preset_modes
|
||||
|
||||
if ClimateEntityFeature.SWING_MODE in supported_features:
|
||||
data[ATTR_SWING_MODES] = self.swing_modes
|
||||
data[ClimateEntityCapabilityAttribute.SWING_MODES] = self.swing_modes
|
||||
|
||||
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
|
||||
data[ATTR_SWING_HORIZONTAL_MODES] = self.swing_horizontal_modes
|
||||
data[ClimateEntityCapabilityAttribute.SWING_HORIZONTAL_MODES] = (
|
||||
self.swing_horizontal_modes
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@@ -355,13 +367,13 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
hass = self.hass
|
||||
|
||||
data: dict[str, str | float | None] = {
|
||||
ATTR_CURRENT_TEMPERATURE: show_temp(
|
||||
ClimateEntityStateAttribute.CURRENT_TEMPERATURE: show_temp(
|
||||
hass, self.current_temperature, temperature_unit, precision
|
||||
),
|
||||
}
|
||||
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE in supported_features:
|
||||
data[ATTR_TEMPERATURE] = show_temp(
|
||||
data[ClimateEntityStateAttribute.TEMPERATURE] = show_temp(
|
||||
hass,
|
||||
self.target_temperature,
|
||||
temperature_unit,
|
||||
@@ -369,33 +381,35 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE_RANGE in supported_features:
|
||||
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||
data[ClimateEntityStateAttribute.TARGET_TEMP_HIGH] = show_temp(
|
||||
hass, self.target_temperature_high, temperature_unit, precision
|
||||
)
|
||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||
data[ClimateEntityStateAttribute.TARGET_TEMP_LOW] = show_temp(
|
||||
hass, self.target_temperature_low, temperature_unit, precision
|
||||
)
|
||||
|
||||
if (current_humidity := self.current_humidity) is not None:
|
||||
data[ATTR_CURRENT_HUMIDITY] = current_humidity
|
||||
data[ClimateEntityStateAttribute.CURRENT_HUMIDITY] = current_humidity
|
||||
|
||||
if ClimateEntityFeature.TARGET_HUMIDITY in supported_features:
|
||||
data[ATTR_HUMIDITY] = self.target_humidity
|
||||
data[ClimateEntityStateAttribute.HUMIDITY] = self.target_humidity
|
||||
|
||||
if ClimateEntityFeature.FAN_MODE in supported_features:
|
||||
data[ATTR_FAN_MODE] = self.fan_mode
|
||||
data[ClimateEntityStateAttribute.FAN_MODE] = self.fan_mode
|
||||
|
||||
if hvac_action := self.hvac_action:
|
||||
data[ATTR_HVAC_ACTION] = hvac_action
|
||||
data[ClimateEntityStateAttribute.HVAC_ACTION] = hvac_action
|
||||
|
||||
if ClimateEntityFeature.PRESET_MODE in supported_features:
|
||||
data[ATTR_PRESET_MODE] = self.preset_mode
|
||||
data[ClimateEntityStateAttribute.PRESET_MODE] = self.preset_mode
|
||||
|
||||
if ClimateEntityFeature.SWING_MODE in supported_features:
|
||||
data[ATTR_SWING_MODE] = self.swing_mode
|
||||
data[ClimateEntityStateAttribute.SWING_MODE] = self.swing_mode
|
||||
|
||||
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
|
||||
data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode
|
||||
data[ClimateEntityStateAttribute.SWING_HORIZONTAL_MODE] = (
|
||||
self.swing_horizontal_mode
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -137,6 +137,38 @@ SERVICE_SET_SWING_HORIZONTAL_MODE = "set_swing_horizontal_mode"
|
||||
SERVICE_SET_TEMPERATURE = "set_temperature"
|
||||
|
||||
|
||||
class ClimateEntityCapabilityAttribute(StrEnum):
|
||||
"""Capability attributes for climate entities."""
|
||||
|
||||
HVAC_MODES = "hvac_modes"
|
||||
MIN_TEMP = "min_temp"
|
||||
MAX_TEMP = "max_temp"
|
||||
TARGET_TEMP_STEP = "target_temp_step"
|
||||
MIN_HUMIDITY = "min_humidity"
|
||||
MAX_HUMIDITY = "max_humidity"
|
||||
TARGET_HUMIDITY_STEP = "target_humidity_step"
|
||||
FAN_MODES = "fan_modes"
|
||||
PRESET_MODES = "preset_modes"
|
||||
SWING_MODES = "swing_modes"
|
||||
SWING_HORIZONTAL_MODES = "swing_horizontal_modes"
|
||||
|
||||
|
||||
class ClimateEntityStateAttribute(StrEnum):
|
||||
"""State attributes for climate entities."""
|
||||
|
||||
CURRENT_TEMPERATURE = "current_temperature"
|
||||
TEMPERATURE = "temperature"
|
||||
TARGET_TEMP_HIGH = "target_temp_high"
|
||||
TARGET_TEMP_LOW = "target_temp_low"
|
||||
CURRENT_HUMIDITY = "current_humidity"
|
||||
HUMIDITY = "humidity"
|
||||
FAN_MODE = "fan_mode"
|
||||
HVAC_ACTION = "hvac_action"
|
||||
PRESET_MODE = "preset_mode"
|
||||
SWING_MODE = "swing_mode"
|
||||
SWING_HORIZONTAL_MODE = "swing_horizontal_mode"
|
||||
|
||||
|
||||
class ClimateEntityFeature(IntFlag):
|
||||
"""Supported features of the climate entity."""
|
||||
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not have any custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities of this integration do not explicitly subscribe to events.
|
||||
|
||||
@@ -25,9 +25,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
The integration does not provide any actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: no actions
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: no events
|
||||
|
||||
@@ -17,9 +17,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -12,11 +12,17 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: No service actions implemented
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions:
|
||||
status: exempt
|
||||
comment: No special external action required
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: No callbacks are implemented
|
||||
|
||||
@@ -41,6 +41,7 @@ from .const import (
|
||||
INTENT_OPEN_COVER,
|
||||
CoverDeviceClass,
|
||||
CoverEntityFeature,
|
||||
CoverEntityStateAttribute,
|
||||
CoverState,
|
||||
)
|
||||
from .trigger import make_cover_closed_trigger, make_cover_opened_trigger
|
||||
@@ -260,13 +261,13 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Return the state attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
|
||||
data[ATTR_IS_CLOSED] = self.is_closed
|
||||
data[CoverEntityStateAttribute.IS_CLOSED] = self.is_closed
|
||||
|
||||
if (current := self.current_cover_position) is not None:
|
||||
data[ATTR_CURRENT_POSITION] = current
|
||||
data[CoverEntityStateAttribute.CURRENT_POSITION] = current
|
||||
|
||||
if (current_tilt := self.current_cover_tilt_position) is not None:
|
||||
data[ATTR_CURRENT_TILT_POSITION] = current_tilt
|
||||
data[CoverEntityStateAttribute.CURRENT_TILT_POSITION] = current_tilt
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -10,6 +10,15 @@ ATTR_IS_CLOSED = "is_closed"
|
||||
ATTR_POSITION = "position"
|
||||
ATTR_TILT_POSITION = "tilt_position"
|
||||
|
||||
|
||||
class CoverEntityStateAttribute(StrEnum):
|
||||
"""State attributes for cover entities."""
|
||||
|
||||
IS_CLOSED = "is_closed"
|
||||
CURRENT_POSITION = "current_position"
|
||||
CURRENT_TILT_POSITION = "current_tilt_position"
|
||||
|
||||
|
||||
INTENT_OPEN_COVER = "HassOpenCover"
|
||||
INTENT_CLOSE_COVER = "HassCloseCover"
|
||||
|
||||
|
||||
@@ -14,9 +14,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -12,9 +12,15 @@ rules:
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: This integration does not register custom actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Entities use the coordinator pattern and do not subscribe to events.
|
||||
|
||||
@@ -8,9 +8,15 @@ rules:
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
@@ -37,6 +37,35 @@ class TrackingType(StrEnum):
|
||||
POSITION = "position"
|
||||
|
||||
|
||||
class DeviceTrackerEntityCapabilityAttribute(StrEnum):
|
||||
"""Capability attributes for device tracker entities."""
|
||||
|
||||
TRACKING_TYPE = "tracking_type"
|
||||
|
||||
|
||||
class DeviceTrackerEntityStateAttribute(StrEnum):
|
||||
"""State attributes common to device tracker entities."""
|
||||
|
||||
SOURCE_TYPE = "source_type"
|
||||
IN_ZONES = "in_zones"
|
||||
|
||||
|
||||
class TrackerEntityStateAttribute(StrEnum):
|
||||
"""State attributes set by TrackerEntity."""
|
||||
|
||||
LATITUDE = "latitude"
|
||||
LONGITUDE = "longitude"
|
||||
GPS_ACCURACY = "gps_accuracy"
|
||||
|
||||
|
||||
class ScannerEntityStateAttribute(StrEnum):
|
||||
"""State attributes set by ScannerEntity."""
|
||||
|
||||
IP = "ip"
|
||||
MAC = "mac"
|
||||
HOST_NAME = "host_name"
|
||||
|
||||
|
||||
CONF_SCAN_INTERVAL: Final = "interval_seconds"
|
||||
SCAN_INTERVAL: Final = timedelta(seconds=12)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from propcache.api import cached_property
|
||||
|
||||
from homeassistant.components import zone
|
||||
from homeassistant.components.zone import ATTR_PASSIVE, ATTR_RADIUS
|
||||
from homeassistant.const import (
|
||||
from homeassistant.const import ( # noqa: F401
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_GPS_ACCURACY,
|
||||
ATTR_LATITUDE,
|
||||
@@ -42,7 +42,7 @@ from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.loader import async_suggest_report_issue
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import (
|
||||
from .const import ( # noqa: F401
|
||||
ATTR_HOST_NAME,
|
||||
ATTR_IN_ZONES,
|
||||
ATTR_IP,
|
||||
@@ -53,7 +53,11 @@ from .const import (
|
||||
CONNECTED_DEVICE_REGISTERED,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
DeviceTrackerEntityCapabilityAttribute,
|
||||
DeviceTrackerEntityStateAttribute,
|
||||
ScannerEntityStateAttribute,
|
||||
SourceType,
|
||||
TrackerEntityStateAttribute,
|
||||
TrackingType,
|
||||
)
|
||||
|
||||
@@ -215,7 +219,9 @@ class BaseTrackerEntity(Entity):
|
||||
@override
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the device state attributes."""
|
||||
attr: dict[str, Any] = {ATTR_SOURCE_TYPE: self.source_type}
|
||||
attr: dict[str, Any] = {
|
||||
DeviceTrackerEntityStateAttribute.SOURCE_TYPE: self.source_type
|
||||
}
|
||||
|
||||
if self.battery_level is not None:
|
||||
attr[ATTR_BATTERY_LEVEL] = self.battery_level
|
||||
@@ -243,7 +249,7 @@ class TrackerEntity(
|
||||
|
||||
entity_description: TrackerEntityDescription
|
||||
_attr_capability_attributes: dict[str, Any] = {
|
||||
ATTR_TRACKING_TYPE: TrackingType.POSITION
|
||||
DeviceTrackerEntityCapabilityAttribute.TRACKING_TYPE: TrackingType.POSITION
|
||||
}
|
||||
_attr_in_zones: list[str] | None = None
|
||||
_attr_latitude: float | None = None
|
||||
@@ -406,13 +412,15 @@ class TrackerEntity(
|
||||
@override
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the device state attributes."""
|
||||
attr: dict[str, Any] = {ATTR_IN_ZONES: self.__in_zones or []}
|
||||
attr: dict[str, Any] = {
|
||||
DeviceTrackerEntityStateAttribute.IN_ZONES: self.__in_zones or []
|
||||
}
|
||||
attr.update(super().state_attributes)
|
||||
|
||||
if self.latitude is not None and self.longitude is not None:
|
||||
attr[ATTR_LATITUDE] = self.latitude
|
||||
attr[ATTR_LONGITUDE] = self.longitude
|
||||
attr[ATTR_GPS_ACCURACY] = self.location_accuracy
|
||||
attr[TrackerEntityStateAttribute.LATITUDE] = self.latitude
|
||||
attr[TrackerEntityStateAttribute.LONGITUDE] = self.longitude
|
||||
attr[TrackerEntityStateAttribute.GPS_ACCURACY] = self.location_accuracy
|
||||
|
||||
return attr
|
||||
|
||||
@@ -425,7 +433,7 @@ class BaseScannerEntity(BaseTrackerEntity):
|
||||
"""
|
||||
|
||||
_attr_capability_attributes: dict[str, Any] = {
|
||||
ATTR_TRACKING_TYPE: TrackingType.CONNECTION
|
||||
DeviceTrackerEntityCapabilityAttribute.TRACKING_TYPE: TrackingType.CONNECTION
|
||||
}
|
||||
_scanner_option_associated_zone: str = zone.ENTITY_ID_HOME
|
||||
_scanner_option_associated_zone_unsub: CALLBACK_TYPE | None = None
|
||||
@@ -556,7 +564,7 @@ class BaseScannerEntity(BaseTrackerEntity):
|
||||
@override
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the device state attributes."""
|
||||
attr: dict[str, Any] = {ATTR_IN_ZONES: []}
|
||||
attr: dict[str, Any] = {DeviceTrackerEntityStateAttribute.IN_ZONES: []}
|
||||
attr.update(super().state_attributes)
|
||||
|
||||
if not self.is_connected:
|
||||
@@ -571,7 +579,7 @@ class BaseScannerEntity(BaseTrackerEntity):
|
||||
):
|
||||
return attr
|
||||
|
||||
attr[ATTR_IN_ZONES] = [
|
||||
attr[DeviceTrackerEntityStateAttribute.IN_ZONES] = [
|
||||
associated_zone,
|
||||
*zone.async_get_enclosing_zones(self.hass, associated_zone),
|
||||
]
|
||||
@@ -721,10 +729,10 @@ class ScannerEntity(
|
||||
attr = super().state_attributes
|
||||
|
||||
if ip_address := self.ip_address:
|
||||
attr[ATTR_IP] = ip_address
|
||||
attr[ScannerEntityStateAttribute.IP] = ip_address
|
||||
if (mac_address := self.mac_address) is not None:
|
||||
attr[ATTR_MAC] = mac_address
|
||||
attr[ScannerEntityStateAttribute.MAC] = mac_address
|
||||
if (hostname := self.hostname) is not None:
|
||||
attr[ATTR_HOST_NAME] = hostname
|
||||
attr[ScannerEntityStateAttribute.HOST_NAME] = hostname
|
||||
|
||||
return attr
|
||||
|
||||
@@ -17,9 +17,15 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions.
|
||||
docs-conditions:
|
||||
status: exempt
|
||||
comment: This integration does not have any conditions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
docs-triggers:
|
||||
status: exempt
|
||||
comment: This integration does not have any triggers.
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user