mirror of
https://github.com/home-assistant/core.git
synced 2026-06-24 23:55:21 +02:00
Compare commits
164 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4022eb93de | |||
| 28171dfe90 | |||
| e9af932fbe | |||
| 3212e0f051 | |||
| 87f0720450 | |||
| 534ff3f3dc | |||
| 1096c8af13 | |||
| 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 | |||
| 1cb5e31901 | |||
| 4f414d0035 | |||
| 30b648ea6a | |||
| 18a259718f | |||
| 45de0f4b4a | |||
| 940bf5bf09 | |||
| 07e78b6dbf | |||
| abcb677b57 | |||
| 7e638f9d0c | |||
| 8aca6115f4 | |||
| dd688986f1 | |||
| c519b7ba07 | |||
| 158595464a | |||
| f81aff0a69 | |||
| 9f90e6ec22 | |||
| 31b311e17a | |||
| 7e07f2ab87 | |||
| 312f4c8c35 | |||
| ddeeba5f87 | |||
| 82a043bc47 | |||
| 629b9a5f9b | |||
| 9400c2e40e | |||
| 8a57bb0640 | |||
| ab8f1bf88e | |||
| b61ad539d9 | |||
| 283dcee830 | |||
| e61a3ac684 | |||
| 5595ba12fb | |||
| 1bfd4d1500 | |||
| 249c3bb5dd | |||
| 982fe7a370 | |||
| 035e7e0a38 | |||
| f6b0036ccc | |||
| b2a26afb14 | |||
| 231470f4dc | |||
| 8aeb5d63ab | |||
| d613591463 | |||
| dc6269f52d | |||
| 31690ac2d3 | |||
| ffe008c6b2 | |||
| dc48c3e30f | |||
| 448fd04137 | |||
| 41e7837bcf | |||
| 951062fa82 | |||
| f2ac5a4153 | |||
| 12832fe3cd | |||
| d35e58fdc7 | |||
| 3570cd0ee3 | |||
| 5a99cb4cca | |||
| d49a9c4780 | |||
| c330a983e4 | |||
| e47f58362a |
@@ -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 ---
|
||||
|
||||
+20
-20
@@ -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
|
||||
@@ -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
|
||||
@@ -1712,8 +1711,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/sql/ @gjohansson-ST @dougiteixeira
|
||||
/homeassistant/components/squeezebox/ @rajlaud @pssc @peteS-UK
|
||||
/tests/components/squeezebox/ @rajlaud @pssc @peteS-UK
|
||||
/homeassistant/components/srp_energy/ @briglx
|
||||
/tests/components/srp_energy/ @briglx
|
||||
/homeassistant/components/srp_energy/ @briglx @ammmze
|
||||
/tests/components/srp_energy/ @briglx @ammmze
|
||||
/homeassistant/components/starline/ @anonym-tsk
|
||||
/tests/components/starline/ @anonym-tsk
|
||||
/homeassistant/components/statistics/ @ThomDietrich @gjohansson-ST
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections.abc import Mapping
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
import time
|
||||
from typing import Any, cast
|
||||
from typing import Any, cast, override
|
||||
|
||||
import jwt
|
||||
|
||||
@@ -109,6 +109,7 @@ class AuthManagerFlowManager(
|
||||
super().__init__(hass)
|
||||
self.auth_manager = auth_manager
|
||||
|
||||
@override
|
||||
async def async_create_flow(
|
||||
self,
|
||||
handler_key: tuple[str, str],
|
||||
@@ -122,6 +123,7 @@ class AuthManagerFlowManager(
|
||||
raise KeyError(f"Unknown auth provider {handler_key}")
|
||||
return await auth_provider.async_login_flow(context)
|
||||
|
||||
@override
|
||||
async def async_finish_flow(
|
||||
self,
|
||||
flow: FlowHandler[AuthFlowContext, AuthFlowResult, tuple[str, str]],
|
||||
|
||||
@@ -39,6 +39,7 @@ class _PyJWSWithLoadCache(PyJWS):
|
||||
# We only ever have a global instance of this class
|
||||
# so we do not have to worry about the LRU growing
|
||||
# each time we create a new instance.
|
||||
@override
|
||||
def _load(self, jwt: str | bytes) -> tuple[bytes, bytes, dict, bytes]:
|
||||
"""Load a JWS."""
|
||||
return super()._load(jwt)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Example auth module."""
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -35,6 +35,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
|
||||
self._data = config["data"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def input_schema(self) -> vol.Schema:
|
||||
"""Validate login flow input data."""
|
||||
return vol.Schema({vol.Required("pin"): str})
|
||||
@@ -44,6 +45,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
|
||||
"""Validate async_setup_user input data."""
|
||||
return vol.Schema({vol.Required("pin"): str})
|
||||
|
||||
@override
|
||||
async def async_setup_flow(self, user_id: str) -> SetupFlow:
|
||||
"""Return a data entry flow handler for setup module.
|
||||
|
||||
@@ -51,6 +53,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
|
||||
"""
|
||||
return SetupFlow(self, self.setup_schema, user_id)
|
||||
|
||||
@override
|
||||
async def async_setup_user(self, user_id: str, setup_data: Any) -> Any:
|
||||
"""Set up user to use mfa module."""
|
||||
# data shall has been validate in caller
|
||||
@@ -64,6 +67,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
|
||||
|
||||
self._data.append({"user_id": user_id, "pin": pin})
|
||||
|
||||
@override
|
||||
async def async_depose_user(self, user_id: str) -> None:
|
||||
"""Remove user from mfa module."""
|
||||
found = None
|
||||
@@ -74,10 +78,12 @@ class InsecureExampleModule(MultiFactorAuthModule):
|
||||
if found:
|
||||
self._data.remove(found)
|
||||
|
||||
@override
|
||||
async def async_is_user_setup(self, user_id: str) -> bool:
|
||||
"""Return whether user is setup."""
|
||||
return any(data["user_id"] == user_id for data in self._data)
|
||||
|
||||
@override
|
||||
async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
return any(
|
||||
|
||||
@@ -5,7 +5,7 @@ Sending HOTP through notify service
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
from typing import Any, cast, override
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
@@ -107,6 +107,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
@property
|
||||
@override
|
||||
def input_schema(self) -> vol.Schema:
|
||||
"""Validate login flow input data."""
|
||||
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
|
||||
@@ -159,6 +160,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
|
||||
return sorted(unordered_services)
|
||||
|
||||
@override
|
||||
async def async_setup_flow(self, user_id: str) -> NotifySetupFlow:
|
||||
"""Return a data entry flow handler for setup module.
|
||||
|
||||
@@ -168,6 +170,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
self, self.input_schema, user_id, self.aync_get_available_notify_services()
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_setup_user(self, user_id: str, setup_data: Any) -> Any:
|
||||
"""Set up auth module for user."""
|
||||
if self._user_settings is None:
|
||||
@@ -181,6 +184,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
|
||||
await self._async_save()
|
||||
|
||||
@override
|
||||
async def async_depose_user(self, user_id: str) -> None:
|
||||
"""Depose auth module for user."""
|
||||
if self._user_settings is None:
|
||||
@@ -190,6 +194,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
if self._user_settings.pop(user_id, None):
|
||||
await self._async_save()
|
||||
|
||||
@override
|
||||
async def async_is_user_setup(self, user_id: str) -> bool:
|
||||
"""Return whether user is setup."""
|
||||
if self._user_settings is None:
|
||||
@@ -198,6 +203,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
|
||||
return user_id in self._user_settings
|
||||
|
||||
@override
|
||||
async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
if self._user_settings is None:
|
||||
@@ -283,6 +289,7 @@ class NotifySetupFlow(SetupFlow[NotifyAuthModule]):
|
||||
self._notify_service: str | None = None
|
||||
self._target: str | None = None
|
||||
|
||||
@override
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from io import BytesIO
|
||||
from typing import Any, cast
|
||||
from typing import Any, cast, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -87,6 +87,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
@property
|
||||
@override
|
||||
def input_schema(self) -> vol.Schema:
|
||||
"""Validate login flow input data."""
|
||||
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
|
||||
@@ -115,6 +116,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
self._users[user_id] = ota_secret # type: ignore[index]
|
||||
return ota_secret
|
||||
|
||||
@override
|
||||
async def async_setup_flow(self, user_id: str) -> TotpSetupFlow:
|
||||
"""Return a data entry flow handler for setup module.
|
||||
|
||||
@@ -124,6 +126,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
assert user is not None
|
||||
return TotpSetupFlow(self, self.input_schema, user)
|
||||
|
||||
@override
|
||||
async def async_setup_user(self, user_id: str, setup_data: Any) -> str:
|
||||
"""Set up auth module for user."""
|
||||
if self._users is None:
|
||||
@@ -136,6 +139,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
await self._async_save()
|
||||
return result
|
||||
|
||||
@override
|
||||
async def async_depose_user(self, user_id: str) -> None:
|
||||
"""Depose auth module for user."""
|
||||
if self._users is None:
|
||||
@@ -144,6 +148,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
if self._users.pop(user_id, None): # type: ignore[union-attr]
|
||||
await self._async_save()
|
||||
|
||||
@override
|
||||
async def async_is_user_setup(self, user_id: str) -> bool:
|
||||
"""Return whether user is setup."""
|
||||
if self._users is None:
|
||||
@@ -151,6 +156,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
|
||||
return user_id in self._users # type: ignore[operator]
|
||||
|
||||
@override
|
||||
async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
if self._users is None:
|
||||
@@ -189,6 +195,7 @@ class TotpSetupFlow(SetupFlow[TotpAuthModule]):
|
||||
super().__init__(auth_module, setup_schema, user.id)
|
||||
self._user = user
|
||||
|
||||
@override
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Permissions for Home Assistant."""
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -68,14 +68,17 @@ class PolicyPermissions(AbstractPermissions):
|
||||
self._policy = policy
|
||||
self._perm_lookup = perm_lookup
|
||||
|
||||
@override
|
||||
def access_all_entities(self, key: str) -> bool:
|
||||
"""Check if we have a certain access to all entities."""
|
||||
return test_all(self._policy.get(CAT_ENTITIES), key)
|
||||
|
||||
@override
|
||||
def _entity_func(self) -> Callable[[str, str], bool]:
|
||||
"""Return a function that can test entity access."""
|
||||
return compile_entities(self._policy.get(CAT_ENTITIES), self._perm_lookup)
|
||||
|
||||
@override
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""Equals check."""
|
||||
return isinstance(other, PolicyPermissions) and other._policy == self._policy
|
||||
@@ -84,10 +87,12 @@ class PolicyPermissions(AbstractPermissions):
|
||||
class _OwnerPermissions(AbstractPermissions):
|
||||
"""Owner permissions."""
|
||||
|
||||
@override
|
||||
def access_all_entities(self, key: str) -> bool:
|
||||
"""Check if we have a certain access to all entities."""
|
||||
return True
|
||||
|
||||
@override
|
||||
def _entity_func(self) -> Callable[[str, str], bool]:
|
||||
"""Return a function that can test entity access."""
|
||||
return lambda entity_id, key: True
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
from typing import Any, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -57,6 +57,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._user_meta: dict[str, dict[str, Any]] = {}
|
||||
|
||||
@override
|
||||
async def async_login_flow(
|
||||
self, context: AuthFlowContext | None
|
||||
) -> CommandLineLoginFlow:
|
||||
@@ -105,6 +106,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
meta[key] = value
|
||||
self._user_meta[username] = meta
|
||||
|
||||
@override
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Mapping[str, str]
|
||||
) -> Credentials:
|
||||
@@ -117,6 +119,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
# Create new credentials.
|
||||
return self.async_create_credentials({"username": username})
|
||||
|
||||
@override
|
||||
async def async_user_meta_for_credentials(
|
||||
self, credentials: Credentials
|
||||
) -> UserMeta:
|
||||
@@ -136,6 +139,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
class CommandLineLoginFlow(LoginFlow[CommandLineAuthProvider]):
|
||||
"""Handler for the login flow."""
|
||||
|
||||
@override
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> AuthFlowResult:
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
import base64
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
from typing import Any, cast, override
|
||||
|
||||
import bcrypt
|
||||
import voluptuous as vol
|
||||
@@ -302,6 +302,7 @@ class HassAuthProvider(AuthProvider):
|
||||
self.data: Data | None = None
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
@override
|
||||
async def async_initialize(self) -> None:
|
||||
"""Initialize the auth provider."""
|
||||
async with self._init_lock:
|
||||
@@ -312,6 +313,7 @@ class HassAuthProvider(AuthProvider):
|
||||
await data.async_load()
|
||||
self.data = data
|
||||
|
||||
@override
|
||||
async def async_login_flow(self, context: AuthFlowContext | None) -> HassLoginFlow:
|
||||
"""Return a flow to login."""
|
||||
return HassLoginFlow(self)
|
||||
@@ -369,6 +371,7 @@ class HassAuthProvider(AuthProvider):
|
||||
)
|
||||
await self.data.async_save()
|
||||
|
||||
@override
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Mapping[str, str]
|
||||
) -> Credentials:
|
||||
@@ -387,6 +390,7 @@ class HassAuthProvider(AuthProvider):
|
||||
# Create new credentials.
|
||||
return self.async_create_credentials({"username": username})
|
||||
|
||||
@override
|
||||
async def async_user_meta_for_credentials(
|
||||
self, credentials: Credentials
|
||||
) -> UserMeta:
|
||||
@@ -410,6 +414,7 @@ class HassAuthProvider(AuthProvider):
|
||||
class HassLoginFlow(LoginFlow[HassAuthProvider]):
|
||||
"""Handler for the login flow."""
|
||||
|
||||
@override
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> AuthFlowResult:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Mapping
|
||||
import hmac
|
||||
from typing import override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -33,6 +34,7 @@ class InvalidAuthError(HomeAssistantError):
|
||||
class ExampleAuthProvider(AuthProvider):
|
||||
"""Example auth provider based on hardcoded usernames and passwords."""
|
||||
|
||||
@override
|
||||
async def async_login_flow(
|
||||
self, context: AuthFlowContext | None
|
||||
) -> ExampleLoginFlow:
|
||||
@@ -61,6 +63,7 @@ class ExampleAuthProvider(AuthProvider):
|
||||
):
|
||||
raise InvalidAuthError
|
||||
|
||||
@override
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Mapping[str, str]
|
||||
) -> Credentials:
|
||||
@@ -74,6 +77,7 @@ class ExampleAuthProvider(AuthProvider):
|
||||
# Create new credentials.
|
||||
return self.async_create_credentials({"username": username})
|
||||
|
||||
@override
|
||||
async def async_user_meta_for_credentials(
|
||||
self, credentials: Credentials
|
||||
) -> UserMeta:
|
||||
@@ -95,6 +99,7 @@ class ExampleAuthProvider(AuthProvider):
|
||||
class ExampleLoginFlow(LoginFlow[ExampleAuthProvider]):
|
||||
"""Handler for the login flow."""
|
||||
|
||||
@override
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> AuthFlowResult:
|
||||
|
||||
@@ -13,7 +13,7 @@ from ipaddress import (
|
||||
ip_address,
|
||||
ip_network,
|
||||
)
|
||||
from typing import Any, cast
|
||||
from typing import Any, cast, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -98,10 +98,12 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def support_mfa(self) -> bool:
|
||||
"""Trusted Networks auth provider does not support MFA."""
|
||||
return False
|
||||
|
||||
@override
|
||||
async def async_login_flow(
|
||||
self, context: AuthFlowContext | None
|
||||
) -> TrustedNetworksLoginFlow:
|
||||
@@ -144,6 +146,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
self.config[CONF_ALLOW_BYPASS_LOGIN],
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Mapping[str, str]
|
||||
) -> Credentials:
|
||||
@@ -172,6 +175,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
# We only allow login as exist user
|
||||
raise InvalidUserError
|
||||
|
||||
@override
|
||||
async def async_user_meta_for_credentials(
|
||||
self, credentials: Credentials
|
||||
) -> UserMeta:
|
||||
@@ -203,6 +207,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
raise InvalidAuthError("Can't allow access from Home Assistant Cloud")
|
||||
|
||||
@callback
|
||||
@override
|
||||
def async_validate_refresh_token(
|
||||
self, refresh_token: RefreshToken, remote_ip: str | None = None
|
||||
) -> None:
|
||||
@@ -230,6 +235,7 @@ class TrustedNetworksLoginFlow(LoginFlow[TrustedNetworksAuthProvider]):
|
||||
self._ip_address = ip_addr
|
||||
self._allow_bypass_login = allow_bypass_login
|
||||
|
||||
@override
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> AuthFlowResult:
|
||||
|
||||
@@ -14,7 +14,7 @@ import platform
|
||||
import sys
|
||||
import threading
|
||||
from time import monotonic
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
# Import cryptography early since import openssl is not thread-safe
|
||||
# _frozen_importlib._DeadlockError: deadlock detected by
|
||||
@@ -697,6 +697,7 @@ def _create_log_file(
|
||||
class _RotatingFileHandlerWithoutShouldRollOver(RotatingFileHandler):
|
||||
"""RotatingFileHandler that does not check if it should roll over on every log."""
|
||||
|
||||
@override
|
||||
def shouldRollover(self, record: logging.LogRecord) -> bool:
|
||||
"""Never roll over.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -23,6 +23,7 @@ class AmazonNotifyEntityDescription(NotifyEntityDescription):
|
||||
"""Alexa Devices notify entity description."""
|
||||
|
||||
is_supported: Callable[[AmazonDevice], bool] = lambda _device: True
|
||||
is_available_fn: Callable[[AmazonDevice], bool] = lambda _device: True
|
||||
method: Callable[[AmazonEchoApi, AmazonDevice, str], Awaitable[None]]
|
||||
subkey: str
|
||||
|
||||
@@ -39,6 +40,9 @@ NOTIFY: Final = (
|
||||
key="announce",
|
||||
translation_key="announce",
|
||||
subkey="AUDIO_PLAYER",
|
||||
is_available_fn=lambda device: (
|
||||
device.communication_settings.get("communications") != "OFF"
|
||||
),
|
||||
method=lambda api, device, message: api.call_alexa_announcement(
|
||||
device, message
|
||||
),
|
||||
@@ -79,6 +83,14 @@ class AmazonNotifyEntity(AmazonEntity, NotifyEntity):
|
||||
|
||||
entity_description: AmazonNotifyEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return (
|
||||
self.entity_description.is_available_fn(self.device) and super().available
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_send_message(
|
||||
self, message: str, title: str | None = None, **kwargs: Any
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Platform for Alexa To-do integration."""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from aioamazondevices.structures import (
|
||||
AmazonListInfo,
|
||||
@@ -88,6 +88,7 @@ class AlexaToDoList(AmazonServiceEntity, TodoListEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def todo_items(self) -> list[TodoItem]:
|
||||
"""Return all to-do items in the list."""
|
||||
|
||||
@@ -104,6 +105,7 @@ class AlexaToDoList(AmazonServiceEntity, TodoListEntity):
|
||||
for item in todo_items
|
||||
]
|
||||
|
||||
@override
|
||||
async def async_create_todo_item(self, item: TodoItem) -> None:
|
||||
"""Add an item to the To-do list."""
|
||||
_LOGGER.debug(
|
||||
@@ -124,6 +126,7 @@ class AlexaToDoList(AmazonServiceEntity, TodoListEntity):
|
||||
self._list.name,
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_delete_todo_items(self, uids: list[str]) -> None:
|
||||
"""Delete items from the to-do list."""
|
||||
_LOGGER.debug("Called async_delete_todo_items for %s item(s)", len(uids))
|
||||
@@ -150,6 +153,7 @@ class AlexaToDoList(AmazonServiceEntity, TodoListEntity):
|
||||
existing_item.version,
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_update_todo_item(self, item: TodoItem) -> None:
|
||||
"""Update an item in the To-do list."""
|
||||
list_items_lookup = self.coordinator.todo_list_items[self._list.id]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/anthropic",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"quality_scale": "gold",
|
||||
"requirements": ["anthropic==0.108.0"]
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
Integration has no actions.
|
||||
appropriate-polling:
|
||||
status: exempt
|
||||
comment: |
|
||||
Integration does not poll.
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
@@ -17,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: |
|
||||
|
||||
@@ -210,6 +210,7 @@ class AqvifyAggrDataCoordinator(
|
||||
end_time = base_time.replace(minute=59).strftime(date_time_fmt)
|
||||
return beg_time, end_time
|
||||
|
||||
@override
|
||||
async def _async_update_data(self) -> dict[str, AqvifyHourAggregatedValues]:
|
||||
"""Fetch device state."""
|
||||
devices = self.config_entry.runtime_data.coordinator.data.devices
|
||||
|
||||
@@ -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,
|
||||
@@ -171,6 +171,7 @@ class AqvifyAggrSensor(AqvifyAggrEntity, SensorEntity):
|
||||
entity_description: AqvifySensorAggrEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> StateType | datetime | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data[self.device_key])
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyatome"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["pyAtome==0.1.1"]
|
||||
"requirements": ["pyAtome==0.1.2"]
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -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.23.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,13 @@ 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(
|
||||
self._ac_index, self.data, swing_mode
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_turn_off(self) -> None:
|
||||
"""Turn off."""
|
||||
|
||||
@@ -4,10 +4,10 @@ import datetime
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
from ccm15 import CCM15Device, CCM15DeviceState, CCM15SlaveDevice
|
||||
from ccm15 import CCM15Device, CCM15DeviceState, CCM15SlaveDevice, TriState
|
||||
import httpx
|
||||
|
||||
from homeassistant.components.climate import HVACMode
|
||||
from homeassistant.components.climate import SWING_ON, HVACMode
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
@@ -86,6 +86,14 @@ class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]):
|
||||
data.fan_mode = CONST_FAN_CMD_MAP[fan_mode]
|
||||
await self.async_set_state(ac_index, data)
|
||||
|
||||
async def async_set_swing_mode(
|
||||
self, ac_index: int, data: CCM15SlaveDevice, swing_mode: str
|
||||
) -> None:
|
||||
"""Set the swing mode."""
|
||||
_LOGGER.debug("Set Swing[%s]='%s'", ac_index, swing_mode)
|
||||
data.desired_swing = TriState.ON if swing_mode == SWING_ON else TriState.OFF
|
||||
await self.async_set_state(ac_index, data)
|
||||
|
||||
async def async_set_temperature(
|
||||
self,
|
||||
ac_index: int,
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user