Compare commits

...

106 Commits

Author SHA1 Message Date
Franck Nijhof 1096c8af13 Bump version to 2026.7.0b0 2026-06-24 16:36:39 +00:00
Leonardo Merza cc2944d626 Fix ecobee active sensor reporting for custom presets and shared device names (#174417) 2026-06-24 17:21:45 +01:00
Mick Vleeshouwer 548ec5cacf Enable action queue to batch concurrent commands in Overkiz (#174275) 2026-06-24 17:19:15 +02:00
karwosts dc6eef2844 Fix date-only input_datetime timestamp attribute to use the correct TZ (#174357) 2026-06-24 17:15:09 +02:00
Stefan Agner 0808e30e37 Add repair when IPv6 is disabled for Matter (#174653)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 17:11:54 +02:00
Manu f0ed257f47 Refactor Steam integration config flow and tests (#174504)
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-06-24 17:06:29 +02:00
Petro31 b4b710b474 Add restore state framework for template entities (#172847) 2026-06-24 16:54:56 +02:00
Bram Kragten 0004a82fe4 Update frontend to 20260624.0 (#174657) 2026-06-24 15:46:44 +01:00
epenet 0c4bc95bdd Migrate base entity attributes to StrEnum (#174633) 2026-06-24 15:38:49 +01:00
Manu 5fdab795e8 Remove Avi-on integration (#174649) 2026-06-24 16:35:22 +02:00
Manu 2193665909 Remove BeeWi SmartClim (#174651) 2026-06-24 16:32:05 +02:00
Martin Hjelmare c9d91d5812 Add enabled entity limit per config entry (#174194) 2026-06-24 16:07:54 +02:00
Erik Montnemery de9d9c66c1 Add additional sun conditions (#174537) 2026-06-24 16:00:08 +02:00
Paul Bottein dfcc4d1ae4 Fix friendly name for restored unavailable entities (#174614) 2026-06-24 14:47:39 +01:00
TimL d71812f09b Bump pysmlight to 0.4.0 (#174640) 2026-06-24 15:19:35 +02:00
Ariel Ebersberger a323ebe634 Reword "advanced operation" channel warning in Home Assistant Hardware (#174645) 2026-06-24 14:57:18 +02:00
Ariel Ebersberger 024bba55cf Reword "Configure advanced voice settings" in ElevenLabs (#174642) 2026-06-24 14:33:41 +02:00
Ariel Ebersberger a5546566e7 Rename "(advanced)" service names in Z-Wave (#174644) 2026-06-24 14:32:38 +02:00
Ariel Ebersberger 3d9994ee4f Rename "Local Risco Panel (advanced)" option in Risco (#174643) 2026-06-24 14:31:55 +02:00
Erik Montnemery c542f38387 Add checks for did_not_trigger calls to trigger tests (#174636)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:05:08 +01:00
wollew 49d6166b7e Bump pyvlx to 0.2.36 (#174638) 2026-06-24 13:21:45 +02:00
J. Nick Koston 7249190c64 Bump habluetooth to 6.23.1 (#174639) 2026-06-24 13:21:12 +02:00
epenet cebdde6ab4 Migrate device tracker entity attributes to StrEnum (#174621) 2026-06-24 12:53:43 +02:00
epenet 031f4cd965 Migrate remaining platform entity attributes to StrEnum (#174625) 2026-06-24 12:52:50 +02:00
Ariel Ebersberger a734f7110c Rename "Advanced options" to "Additional options" in DNS IP (#174628) 2026-06-24 12:38:16 +02:00
Ariel Ebersberger 395e949591 Rename "Advanced settings" to "Additional settings" in History Stats (#174629) 2026-06-24 12:38:00 +02:00
Ariel Ebersberger 484e60a1c4 Rename "Advanced options" to "Additional options" in SQL (#174631) 2026-06-24 12:19:38 +02:00
Ariel Ebersberger b7a234fbd9 Rename "Advanced settings" to "Additional settings" in Telegram bot (#174632) 2026-06-24 12:19:08 +02:00
Ariel Ebersberger a1982fbd54 Rename "Advanced settings" to "Additional settings" in Autoskope (#174630) 2026-06-24 12:18:53 +02:00
epenet c384cd9894 Migrate calendar entity attributes to StrEnum (#174615) 2026-06-24 12:10:37 +02:00
Tom 1aefd2a5ac Bump airOS dependency to support open wireless (#174559) 2026-06-24 12:00:38 +02:00
epenet e3605be5cd Migrate siren entity attributes to StrEnum (#174616) 2026-06-24 11:54:13 +02:00
epenet e87a41a01d Migrate text entity attributes to StrEnum (#174619) 2026-06-24 11:51:28 +02:00
epenet 190ff034aa Migrate vacuum entity attributes to StrEnum (#174617) 2026-06-24 11:46:29 +02:00
epenet b301925687 Migrate cover entity attributes to StrEnum (#174601) 2026-06-24 11:18:37 +02:00
epenet 7a0f5b066e Migrate humidifier entity attributes to StrEnum (#174609) 2026-06-24 11:17:45 +02:00
epenet 308fad166d Migrate media player entity attributes to StrEnum (#174605) 2026-06-24 11:16:33 +02:00
epenet 1305c2978c Migrate fan entity attributes to StrEnum (#174610) 2026-06-24 11:12:45 +02:00
epenet 955ad6db1b Migrate valve entity attributes to StrEnum (#174611) 2026-06-24 11:12:02 +02:00
Andreas Schneider 87dc013803 Use age-based filter for Matter BLE advertisement history replay (#173488) 2026-06-24 04:11:29 -05:00
Stefan Agner 1bb41cb2dd Use translated message when Matter Server add-on is not ready (#174529)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:02:08 +02:00
epenet 277af6c60b Migrate update entity attributes to StrEnum (#174608) 2026-06-24 10:57:58 +02:00
Raphael Hehl 69e18aa580 Source UniFi Protect light auto-shutoff duration from the public API (#174518) 2026-06-24 03:54:38 -05:00
Raphael Hehl 75852fc191 Add ufp_public_enabled_fn for public-API availability gating to UniFi Protect (#174544) 2026-06-24 03:53:49 -05:00
Mark Ruys a661b678a2 Add Sensoterra CODEOWNERs (#174431)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 10:53:36 +02:00
Raphael Hehl bd0951110d Drive the UniFi Protect doorbell ring event from the public events websocket (#174546) 2026-06-24 03:53:01 -05:00
epenet 899f904cf3 Migrate weather entity attributes to StrEnum (#174604) 2026-06-24 10:38:25 +02:00
epenet d2e7426aa5 Migrate image entity attributes to StrEnum (#174606) 2026-06-24 10:37:38 +02:00
epenet c0e02457bc Migrate lock entity attributes to StrEnum (#174607) 2026-06-24 10:37:11 +02:00
epenet e7562b50cf Migrate water heater entity attributes to StrEnum (#174603) 2026-06-24 10:32:38 +02:00
Thomas c36e4a03e0 Bump boschshcpy to 0.3.5 (#174550) 2026-06-24 10:16:45 +02:00
Nathan Spencer 71430af6ff Bump pylitterbot to 2025.5.0 (#174554) 2026-06-24 10:15:43 +02:00
Åke Strandberg 815cce5a0c Tweak aqvify entity names (#174597) 2026-06-24 10:08:15 +02:00
Robert Svensson 32929755eb Tighten the Axis unique ID in config flow (#172283) 2026-06-24 10:07:10 +02:00
Manu 88d4d1c879 Remove SCSGate integration (#174571) 2026-06-24 10:02:23 +02:00
Manu 51bd71d096 Remove Acer projector integration (#174579) 2026-06-24 09:58:09 +02:00
Thomas 1fcf9eb5b7 Add @mosandlt as codeowner for bosch_shc (#174563) 2026-06-24 09:57:19 +02:00
epenet 1917a007f8 Migrate event entity attributes to StrEnum (#174592) 2026-06-24 09:56:20 +02:00
Paul Bottein b095baa65a Bump Yoto quality scale to platinum (#174598) 2026-06-24 09:52:20 +02:00
Przemysław Szypowicz 2bd81c7351 Remove broken Ampio Smog integration (#173080) 2026-06-24 09:50:46 +02:00
Paul Bottein a576aef9a4 Add dynamic and stale device handling to Yoto (#173298)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 09:32:10 +02:00
Michael Hansen c2e780dfd2 Improve Wyoming satellite reconnect and tolerance of other satellites (#174460)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:22:03 +02:00
epenet 687064d5cc Migrate Ratio units to StrEnum (#172568) 2026-06-24 09:21:28 +02:00
epenet 0b801d74cd Migrate camera entity attributes to StrEnum (#174591) 2026-06-24 09:15:44 +02:00
epenet dbdcb1a91e Migrate alarm control panel entity attributes to StrEnum (#174590) 2026-06-24 09:06:16 +02:00
epenet f9bf7ab122 Migrate sensor entity attributes to StrEnum (#174595) 2026-06-24 09:05:32 +02:00
epenet d484f75c7b Migrate number entity attributes to StrEnum (#174593) 2026-06-24 09:04:22 +02:00
dependabot[bot] 5ba4d588b6 Bump actions/checkout from 6.0.3 to 7.0.0 (#174596)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 08:59:49 +02:00
epenet 42da67e7a9 Migrate light entity attributes to StrEnum (#174589) 2026-06-24 08:58:00 +02:00
Joost Lekkerkerker 7d08a7b898 Add new integration quality scale rules (#174555) 2026-06-24 08:53:46 +02:00
puddly 05016e46c8 Bump ZHA to 2.0.0 (#174586) 2026-06-24 08:52:44 +02:00
J. Nick Koston 58603326e3 Bump habluetooth to 6.19.1 (#174582) 2026-06-23 22:58:58 -05:00
Steve Rice 10fe3dc13f Add cloudhook support to switchbot_cloud webhook (#174566)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:50:08 -04:00
J. Nick Koston adf2f2854c Active scan govee_ble only when needed and widen the window to 30s (#174557) 2026-06-23 21:03:38 -04:00
A. Gideonse 7fd101005d Bump indevolt-api to 1.8.6 (#174573) 2026-06-24 03:03:09 +02:00
J. Nick Koston 40aa8dd617 Widen the INKBIRD IBS-TH2 active scan window to 30s (#174565) 2026-06-23 21:02:55 -04:00
Matthias Alphart fb283dfb93 Update knx-frontend to 2026.6.23.203726 (#174567) 2026-06-23 20:55:45 -04:00
J. Nick Koston 2084d52504 Bump habluetooth to 6.13.0 (#174568) 2026-06-23 20:55:34 -04:00
Fabian Munkes cb914495e7 Add username parameter to play media action in Music Assistant (#174486) 2026-06-24 01:04:44 +02:00
J. Nick Koston 97f2eecc57 Bump bluetooth-adapters to 2.4.0 (#174575) 2026-06-23 17:58:06 -05:00
J. Nick Koston 2d1b3f799d Bump dbus-fast to 5.0.22 (#174569) 2026-06-23 23:32:27 +02:00
J. Nick Koston f57418ed60 Bump govee-ble to 1.4.0 (#174553) 2026-06-23 20:27:33 +02:00
Abílio Costa 8d1bf68045 Enable mypy explicit-override check (#171853)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-06-23 18:20:08 +01:00
Raphael Hehl c5bfad9bfe Remove UniFi LED (unifiled) integration (#168232)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-06-23 17:45:31 +02:00
Abílio Costa 679b0ac2aa Add agent instruction to prevent divider comments (#174531) 2026-06-23 16:41:43 +01:00
epenet 22a583c83f Migrate climate entity attributes to StrEnum (#174528) 2026-06-23 16:48:38 +02:00
epenet d966f71832 Migrate select entity attributes to StrEnum (#174536) 2026-06-23 16:44:47 +02:00
Manu 08569420f6 Remove Eliqonline integration (#174538) 2026-06-23 17:10:21 +03:00
Ronald van der Meer c1bcbca520 Add filter remaining days sensor to Duco (#174316) 2026-06-23 15:16:42 +02:00
Simone Chemelli c73c647162 Improve docstring for async_get_entity_id() method (#174532) 2026-06-23 14:50:34 +02:00
Markus Tuominen b2f1c38b6f Bump ouman-eh-800-api to 1.0.0 (#174458) 2026-06-23 14:45:19 +02:00
Erik Montnemery e8824bedf5 Add additional sun triggers (#174485) 2026-06-23 14:23:29 +02:00
Ajinkya Gokhale 27b107f4a5 Update energieleser to silver quality scale (#174535) 2026-06-23 14:23:06 +02:00
Petro31 7536e8647f Fix entities listed in template blueprints (#171861)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-23 13:35:20 +02:00
Simone Chemelli 75c6058396 Add "Drop in" select to Alexa Devices (#174336) 2026-06-23 10:47:44 +02:00
Matthias Alphart 77533e5af5 Rename "Advanced options" in KNX strings (#174523) 2026-06-23 10:27:11 +02:00
LG-ThinQ-Integration 31a1e7c5e1 Bump thinqconnect 1.0.13 (#174510)
Co-authored-by: YunseonPark-LGE <yunseon.park@lge.com>
2026-06-23 09:55:33 +02:00
renovate[bot] 20f4e7306b Update pytest-unordered to 0.8.0 (#174515) 2026-06-23 09:15:35 +02:00
Mick Vleeshouwer 5c1ac08c92 Catch connection errors when executing Overkiz commands (#174453) 2026-06-23 08:51:36 +02:00
Franck Nijhof 5631c68069 Reword trigger descriptions for presence and detection entities (#174467) 2026-06-23 08:51:14 +02:00
Franck Nijhof 8648611278 Clarify the media player play media action name (#174480) 2026-06-23 08:49:04 +02:00
Franck Nijhof 971d15be1e Improve trigger wording for lawn mower and vacuum entities (#174477)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-06-23 08:48:45 +02:00
Åke Strandberg b6f2429ff3 Bump pyaqvify to 0.0.12 (#174516) 2026-06-23 08:35:13 +02:00
Jacob Hurwitz c676e2a806 Bump python-dropbox-api to 0.1.4 (#174512) 2026-06-23 07:22:21 +02:00
Robert Resch a27ea536db Enable aw check requirements on each deps PR (#174481) 2026-06-23 02:14:03 +02:00
Erwin Douna 22e25d9ce2 MELCloud Home exand sensor state behavior (#174495) 2026-06-23 00:44:14 +02:00
1790 changed files with 76224 additions and 69913 deletions
+1
View File
@@ -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.
+6 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
-2
View File
@@ -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.*
+1
View File
@@ -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
+4 -6
View File
@@ -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
@@ -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
-1
View File
@@ -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.
+1 -1
View File
@@ -8,5 +8,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "platinum",
"requirements": ["airos==0.6.8"]
"requirements": ["airos==0.6.9"]
}
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: airOS does not have actions
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: local_polling without events
@@ -10,9 +10,15 @@ rules:
docs-actions:
status: exempt
comment: Integration does not provide custom actions
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: Integration does not register any service actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: Integration does not subscribe to external events.
@@ -8,7 +8,7 @@ from propcache.api import cached_property
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
from homeassistant.const import ( # noqa: F401
ATTR_CODE,
ATTR_CODE_FORMAT,
SERVICE_ALARM_ARM_AWAY,
@@ -28,11 +28,12 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .const import (
from .const import ( # noqa: F401
ATTR_CHANGED_BY,
ATTR_CODE_ARM_REQUIRED,
DOMAIN,
AlarmControlPanelEntityFeature,
AlarmControlPanelEntityStateAttribute,
AlarmControlPanelState,
CodeFormat,
)
@@ -303,9 +304,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
def state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes."""
return {
ATTR_CODE_FORMAT: self.code_format,
ATTR_CHANGED_BY: self.changed_by,
ATTR_CODE_ARM_REQUIRED: self.code_arm_required,
AlarmControlPanelEntityStateAttribute.CODE_FORMAT: self.code_format,
AlarmControlPanelEntityStateAttribute.CHANGED_BY: self.changed_by,
AlarmControlPanelEntityStateAttribute.CODE_ARM_REQUIRED: (
self.code_arm_required
),
}
@override
@@ -9,6 +9,14 @@ ATTR_CHANGED_BY: Final = "changed_by"
ATTR_CODE_ARM_REQUIRED: Final = "code_arm_required"
class AlarmControlPanelEntityStateAttribute(StrEnum):
"""State attributes for alarm control panel entities."""
CODE_FORMAT = "code_format"
CHANGED_BY = "changed_by"
CODE_ARM_REQUIRED = "code_arm_required"
class AlarmControlPanelState(StrEnum):
"""Alarm control panel entity states."""
@@ -16,6 +16,7 @@ PLATFORMS = [
Platform.EVENT,
Platform.MEDIA_PLAYER,
Platform.NOTIFY,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.TODO,
@@ -5,6 +5,16 @@
"default": "mdi:chat-processing"
}
},
"select": {
"dropin": {
"default": "mdi:account-multiple-plus",
"state": {
"all": "mdi:account-multiple-plus",
"home": "mdi:home-plus",
"off": "mdi:phone-off"
}
}
},
"sensor": {
"voc_index": {
"default": "mdi:molecule"
@@ -84,6 +84,7 @@ class AmazonNotifyEntity(AmazonEntity, NotifyEntity):
entity_description: AmazonNotifyEntityDescription
@property
@override
def available(self) -> bool:
"""Return if entity is available."""
return (
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: no actions
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: entities do not explicitly subscribe to events
@@ -0,0 +1,105 @@
"""Support for select entities."""
from dataclasses import dataclass
from typing import TYPE_CHECKING, Final, override
from aioamazondevices.structures import AmazonDropInStatus
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AmazonConfigEntry, AmazonDevice, alexa_api_call
from .entity import AmazonEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class AmazonSelectEntityDescription(SelectEntityDescription):
"""Alexa Devices select entity description."""
method: str
SELECTS: Final = (
AmazonSelectEntityDescription(
key="dropin",
translation_key="dropin",
entity_category=EntityCategory.CONFIG,
method="set_dropin_status",
# API values: "All", "Home", "Off"
options=[status.value.lower() for status in AmazonDropInStatus],
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: AmazonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up select entities based on a config entry."""
coordinator = entry.runtime_data
known_devices: set[str] = set()
def _check_device() -> None:
current_devices = set(coordinator.data)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
select_entities = [
AmazonSelectEntity(coordinator, serial_num, select_desc)
for select_desc in SELECTS
for serial_num in new_devices
if select_desc.key
in coordinator.data[serial_num].communication_settings
]
async_add_entities(select_entities)
_check_device()
entry.async_on_unload(coordinator.async_add_listener(_check_device))
class AmazonSelectEntity(AmazonEntity, SelectEntity):
"""Representation of a select entity for the default Alexa device."""
entity_description: AmazonSelectEntityDescription
@property
@override
def options(self) -> list[str]:
"""Return a list of available options."""
if TYPE_CHECKING:
assert self.entity_description.options is not None
return self.entity_description.options
@property
def _device(self) -> AmazonDevice:
"""Return the device."""
return self.coordinator.data[self._serial_num]
@override
async def async_added_to_hass(self) -> None:
"""Restore last known option."""
await super().async_added_to_hass()
self._attr_current_option = self._device.communication_settings[
self.entity_description.key
].lower()
@override
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
method = getattr(self.coordinator.api, self.entity_description.method)
if TYPE_CHECKING:
assert method is not None
async with alexa_api_call(self.coordinator):
await method(self.device, AmazonDropInStatus(option.capitalize()))
self._attr_current_option = option
self.async_write_ha_state()
@@ -78,6 +78,16 @@
"name": "Speak"
}
},
"select": {
"dropin": {
"name": "Drop In",
"state": {
"all": "Allow drop in from anyone",
"home": "Allow drop in from household members",
"off": "Don't allow drop in"
}
}
},
"sensor": {
"alarm": {
"name": "Next alarm"
@@ -140,6 +150,9 @@
"invalid_sound_value": {
"message": "Invalid sound {sound} specified"
},
"select_option_not_found": {
"message": "Selected option not found: {option}"
},
"unknown_exception": {
"message": "Unknown error occurred: {error}"
}
@@ -14,9 +14,15 @@ rules:
status: exempt
comment: |
This integration does not provide additional actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -1 +0,0 @@
"""The Ampio component."""
@@ -1,105 +0,0 @@
"""Support for Ampio Air Quality data."""
import logging
from typing import Final, override
from asmog import AmpioSmog
import voluptuous as vol
from homeassistant.components.air_quality import (
PLATFORM_SCHEMA as AIR_QUALITY_PLATFORM_SCHEMA,
AirQualityEntity,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from .const import CONF_STATION_ID, SCAN_INTERVAL
_LOGGER: Final = logging.getLogger(__name__)
PLATFORM_SCHEMA: Final = AIR_QUALITY_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_STATION_ID): cv.string, vol.Optional(CONF_NAME): cv.string}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Ampio Smog air quality platform."""
name = config.get(CONF_NAME)
station_id = config[CONF_STATION_ID]
session = async_get_clientsession(hass)
api = AmpioSmogMapData(AmpioSmog(station_id, hass.loop, session))
await api.async_update()
if not api.api.data:
_LOGGER.error("Station %s is not available", station_id)
return
async_add_entities([AmpioSmogQuality(api, station_id, name)], True)
class AmpioSmogQuality(AirQualityEntity):
"""Implementation of an Ampio Smog air quality entity."""
_attr_attribution = "Data provided by Ampio"
def __init__(
self, api: AmpioSmogMapData, station_id: str, name: str | None
) -> None:
"""Initialize the air quality entity."""
self._ampio = api
self._station_id = station_id
self._name = name or api.api.name
@property
@override
def name(self) -> str:
"""Return the name of the air quality entity."""
return self._name
@property
@override
def unique_id(self) -> str:
"""Return unique_name."""
return f"ampio_smog_{self._station_id}" # pylint: disable=home-assistant-entity-unique-id-redundant-domain
@property
@override
def particulate_matter_2_5(self) -> str | None:
"""Return the particulate matter 2.5 level."""
return self._ampio.api.pm2_5 # type: ignore[no-any-return]
@property
@override
def particulate_matter_10(self) -> str | None:
"""Return the particulate matter 10 level."""
return self._ampio.api.pm10 # type: ignore[no-any-return]
async def async_update(self) -> None:
"""Get the latest data from the AmpioMap API."""
await self._ampio.async_update()
class AmpioSmogMapData:
"""Get the latest data and update the states."""
def __init__(self, api: AmpioSmog) -> None:
"""Initialize the data object."""
self.api = api
@Throttle(SCAN_INTERVAL)
async def async_update(self) -> None:
"""Get the latest data from AmpioMap."""
await self.api.get_data()
-7
View File
@@ -1,7 +0,0 @@
"""Constants for Ampio Air Quality platform."""
from datetime import timedelta
from typing import Final
CONF_STATION_ID: Final = "station_id"
SCAN_INTERVAL: Final = timedelta(minutes=10)
@@ -1,10 +0,0 @@
{
"domain": "ampio",
"name": "Ampio Smart Smog System",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/ampio",
"iot_class": "cloud_polling",
"loggers": ["asmog"],
"quality_scale": "legacy",
"requirements": ["asmog==0.0.6"]
}
@@ -14,9 +14,15 @@ rules:
status: exempt
comment: |
This integration does not provide additional actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: todo
docs-installation-instructions: todo
docs-removal-instructions: todo
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -12,9 +12,15 @@ rules:
config-flow: done
dependency-transparency: done
docs-actions: done
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -14,9 +14,15 @@ rules:
status: exempt
comment: |
No custom actions are defined.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -14,9 +14,15 @@ rules:
status: exempt
comment: |
Integration has no actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -11,9 +11,15 @@ rules:
status: exempt
comment: |
The integration does not provide any actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
+7 -7
View File
@@ -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: |
+6 -6
View File
@@ -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,
+8 -8
View File
@@ -34,23 +34,23 @@
},
"entity": {
"sensor": {
"available_volume": {
"name": "Available volume"
},
"ground_water_level": {
"name": "Ground water level"
},
"in_flow": {
"name": "Inflow"
},
"meter_value": {
"name": "Meter value"
"level_from_sensor": {
"name": "Level from sensor"
},
"level_from_top": {
"name": "Level from top"
},
"out_volume": {
"name": "Outflow"
},
"stored_volume": {
"name": "Stored volume"
},
"water_level": {
"name": "Water level"
}
}
},
@@ -14,9 +14,15 @@ rules:
status: exempt
comment: |
This integration does not provide additional actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
from homeassistant.const import (
from homeassistant.const import ( # noqa: F401
ATTR_AREA_ID,
ATTR_ENTITY_ID,
ATTR_FLOOR_ID,
@@ -58,7 +58,7 @@ from homeassistant.helpers.issue_registry import (
async_delete_issue,
)
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.script import (
from homeassistant.helpers.script import ( # noqa: F401
ATTR_CUR,
ATTR_MAX,
CONF_MAX,
@@ -91,6 +91,8 @@ from .const import (
DEFAULT_INITIAL_STATE,
DOMAIN,
LOGGER,
AutomationEntityCapabilityAttribute,
AutomationEntityStateAttribute,
)
from .helpers import async_get_blueprints
from .trace import trace_automation
@@ -318,7 +320,13 @@ class BaseAutomationEntity(ToggleEntity, ABC):
"""Base class for automation entities."""
_entity_component_unrecorded_attributes = frozenset(
(ATTR_LAST_TRIGGERED, ATTR_MODE, ATTR_CUR, ATTR_MAX, CONF_ID)
(
AutomationEntityStateAttribute.LAST_TRIGGERED,
AutomationEntityStateAttribute.MODE,
AutomationEntityStateAttribute.CUR,
AutomationEntityStateAttribute.MAX,
AutomationEntityCapabilityAttribute.ID,
)
)
raw_config: ConfigType | None
@@ -327,7 +335,7 @@ class BaseAutomationEntity(ToggleEntity, ABC):
def capability_attributes(self) -> dict[str, Any] | None:
"""Return capability attributes."""
if self.unique_id is not None:
return {CONF_ID: self.unique_id}
return {AutomationEntityCapabilityAttribute.ID: self.unique_id}
return None
@cached_property
@@ -507,13 +515,15 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the entity state attributes."""
attrs = {
ATTR_LAST_TRIGGERED: self.action_script.last_triggered,
ATTR_MODE: self.action_script.script_mode,
ATTR_CUR: self.action_script.runs,
attrs: dict[str, Any] = {
AutomationEntityStateAttribute.LAST_TRIGGERED: (
self.action_script.last_triggered
),
AutomationEntityStateAttribute.MODE: self.action_script.script_mode,
AutomationEntityStateAttribute.CUR: self.action_script.runs,
}
if self.action_script.supports_max:
attrs[ATTR_MAX] = self.action_script.max_runs
attrs[AutomationEntityStateAttribute.MAX] = self.action_script.max_runs
return attrs
@property
@@ -1,10 +1,27 @@
"""Constants for the automation integration."""
from enum import StrEnum
import logging
CONF_TRIGGER_VARIABLES = "trigger_variables"
DOMAIN = "automation"
class AutomationEntityCapabilityAttribute(StrEnum):
"""Capability attributes for automation entities."""
ID = "id"
class AutomationEntityStateAttribute(StrEnum):
"""State attributes for automation entities."""
LAST_TRIGGERED = "last_triggered"
MODE = "mode"
CUR = "current"
MAX = "max"
CONF_HIDE_ENTITY = "hide_entity"
CONF_CONDITION_TYPE = "condition_type"
@@ -16,9 +16,15 @@ rules:
status: exempt
comment: |
Integration does not provide custom services.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -38,7 +38,7 @@
"data_description": {
"host": "The URL of your Autoskope API endpoint. Only change this if you use a white-label portal."
},
"name": "Advanced settings"
"name": "Additional settings"
}
},
"title": "Connect to Autoskope"
@@ -1 +0,0 @@
"""The avion component."""
-123
View File
@@ -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.
+24 -20
View File
@@ -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: |
+14 -7
View File
@@ -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
+12 -1
View File
@@ -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
+11 -6
View File
@@ -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
+9
View File
@@ -28,6 +28,15 @@ CAMERA_STREAM_SOURCE_TIMEOUT: Final = 10
CAMERA_IMAGE_TIMEOUT: Final = 10
class CameraEntityStateAttribute(StrEnum):
"""State attributes for camera entities."""
ACCESS_TOKEN = "access_token"
MODEL_NAME = "model_name"
BRAND = "brand"
MOTION_DETECTION = "motion_detection"
class CameraState(StrEnum):
"""Camera entity states."""
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: No custom actions/services.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -174,6 +174,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
"""Set the fan mode."""
await self.coordinator.async_set_fan_mode(self._ac_index, self.data, fan_mode)
@override
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set the swing mode."""
await self.coordinator.async_set_swing_mode(
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: This integration does not provide actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -18,9 +18,15 @@ rules:
docs-actions:
status: exempt
comment: Integration does not register custom actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: Integration does not subscribe to events.
@@ -15,9 +15,15 @@ rules:
docs-actions:
status: exempt
comment: No custom actions are defined.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: done
comment: |
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: There are no custom actions
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: Entities do not explicitly subscribe to events
@@ -8,9 +8,15 @@ rules:
config-flow: done
dependency-transparency: done
docs-actions: done
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
+46 -32
View File
@@ -84,7 +84,9 @@ from .const import ( # noqa: F401
SWING_OFF,
SWING_ON,
SWING_VERTICAL,
ClimateEntityCapabilityAttribute,
ClimateEntityFeature,
ClimateEntityStateAttribute,
HVACAction,
HVACMode,
)
@@ -242,16 +244,16 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_entity_component_unrecorded_attributes = frozenset(
{
ATTR_HVAC_MODES,
ATTR_FAN_MODES,
ATTR_SWING_MODES,
ATTR_MIN_TEMP,
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_TARGET_HUMIDITY_STEP,
ATTR_TARGET_TEMP_STEP,
ATTR_PRESET_MODES,
ClimateEntityCapabilityAttribute.HVAC_MODES,
ClimateEntityCapabilityAttribute.FAN_MODES,
ClimateEntityCapabilityAttribute.SWING_MODES,
ClimateEntityCapabilityAttribute.MIN_TEMP,
ClimateEntityCapabilityAttribute.MAX_TEMP,
ClimateEntityCapabilityAttribute.MIN_HUMIDITY,
ClimateEntityCapabilityAttribute.MAX_HUMIDITY,
ClimateEntityCapabilityAttribute.TARGET_HUMIDITY_STEP,
ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP,
ClimateEntityCapabilityAttribute.PRESET_MODES,
}
)
@@ -315,32 +317,42 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
hass = self.hass
data: dict[str, Any] = {
ATTR_HVAC_MODES: self.hvac_modes,
ATTR_MIN_TEMP: show_temp(hass, self.min_temp, temperature_unit, precision),
ATTR_MAX_TEMP: show_temp(hass, self.max_temp, temperature_unit, precision),
ClimateEntityCapabilityAttribute.HVAC_MODES: self.hvac_modes,
ClimateEntityCapabilityAttribute.MIN_TEMP: show_temp(
hass, self.min_temp, temperature_unit, precision
),
ClimateEntityCapabilityAttribute.MAX_TEMP: show_temp(
hass, self.max_temp, temperature_unit, precision
),
}
if target_temperature_step := self.target_temperature_step:
data[ATTR_TARGET_TEMP_STEP] = target_temperature_step
data[ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP] = (
target_temperature_step
)
if ClimateEntityFeature.TARGET_HUMIDITY in supported_features:
data[ATTR_MIN_HUMIDITY] = self.min_humidity
data[ATTR_MAX_HUMIDITY] = self.max_humidity
data[ClimateEntityCapabilityAttribute.MIN_HUMIDITY] = self.min_humidity
data[ClimateEntityCapabilityAttribute.MAX_HUMIDITY] = self.max_humidity
if self.target_humidity_step is not None:
data[ATTR_TARGET_HUMIDITY_STEP] = self.target_humidity_step
data[ClimateEntityCapabilityAttribute.TARGET_HUMIDITY_STEP] = (
self.target_humidity_step
)
if ClimateEntityFeature.FAN_MODE in supported_features:
data[ATTR_FAN_MODES] = self.fan_modes
data[ClimateEntityCapabilityAttribute.FAN_MODES] = self.fan_modes
if ClimateEntityFeature.PRESET_MODE in supported_features:
data[ATTR_PRESET_MODES] = self.preset_modes
data[ClimateEntityCapabilityAttribute.PRESET_MODES] = self.preset_modes
if ClimateEntityFeature.SWING_MODE in supported_features:
data[ATTR_SWING_MODES] = self.swing_modes
data[ClimateEntityCapabilityAttribute.SWING_MODES] = self.swing_modes
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
data[ATTR_SWING_HORIZONTAL_MODES] = self.swing_horizontal_modes
data[ClimateEntityCapabilityAttribute.SWING_HORIZONTAL_MODES] = (
self.swing_horizontal_modes
)
return data
@@ -355,13 +367,13 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
hass = self.hass
data: dict[str, str | float | None] = {
ATTR_CURRENT_TEMPERATURE: show_temp(
ClimateEntityStateAttribute.CURRENT_TEMPERATURE: show_temp(
hass, self.current_temperature, temperature_unit, precision
),
}
if ClimateEntityFeature.TARGET_TEMPERATURE in supported_features:
data[ATTR_TEMPERATURE] = show_temp(
data[ClimateEntityStateAttribute.TEMPERATURE] = show_temp(
hass,
self.target_temperature,
temperature_unit,
@@ -369,33 +381,35 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
)
if ClimateEntityFeature.TARGET_TEMPERATURE_RANGE in supported_features:
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
data[ClimateEntityStateAttribute.TARGET_TEMP_HIGH] = show_temp(
hass, self.target_temperature_high, temperature_unit, precision
)
data[ATTR_TARGET_TEMP_LOW] = show_temp(
data[ClimateEntityStateAttribute.TARGET_TEMP_LOW] = show_temp(
hass, self.target_temperature_low, temperature_unit, precision
)
if (current_humidity := self.current_humidity) is not None:
data[ATTR_CURRENT_HUMIDITY] = current_humidity
data[ClimateEntityStateAttribute.CURRENT_HUMIDITY] = current_humidity
if ClimateEntityFeature.TARGET_HUMIDITY in supported_features:
data[ATTR_HUMIDITY] = self.target_humidity
data[ClimateEntityStateAttribute.HUMIDITY] = self.target_humidity
if ClimateEntityFeature.FAN_MODE in supported_features:
data[ATTR_FAN_MODE] = self.fan_mode
data[ClimateEntityStateAttribute.FAN_MODE] = self.fan_mode
if hvac_action := self.hvac_action:
data[ATTR_HVAC_ACTION] = hvac_action
data[ClimateEntityStateAttribute.HVAC_ACTION] = hvac_action
if ClimateEntityFeature.PRESET_MODE in supported_features:
data[ATTR_PRESET_MODE] = self.preset_mode
data[ClimateEntityStateAttribute.PRESET_MODE] = self.preset_mode
if ClimateEntityFeature.SWING_MODE in supported_features:
data[ATTR_SWING_MODE] = self.swing_mode
data[ClimateEntityStateAttribute.SWING_MODE] = self.swing_mode
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode
data[ClimateEntityStateAttribute.SWING_HORIZONTAL_MODE] = (
self.swing_horizontal_mode
)
return data
+32
View File
@@ -137,6 +137,38 @@ SERVICE_SET_SWING_HORIZONTAL_MODE = "set_swing_horizontal_mode"
SERVICE_SET_TEMPERATURE = "set_temperature"
class ClimateEntityCapabilityAttribute(StrEnum):
"""Capability attributes for climate entities."""
HVAC_MODES = "hvac_modes"
MIN_TEMP = "min_temp"
MAX_TEMP = "max_temp"
TARGET_TEMP_STEP = "target_temp_step"
MIN_HUMIDITY = "min_humidity"
MAX_HUMIDITY = "max_humidity"
TARGET_HUMIDITY_STEP = "target_humidity_step"
FAN_MODES = "fan_modes"
PRESET_MODES = "preset_modes"
SWING_MODES = "swing_modes"
SWING_HORIZONTAL_MODES = "swing_horizontal_modes"
class ClimateEntityStateAttribute(StrEnum):
"""State attributes for climate entities."""
CURRENT_TEMPERATURE = "current_temperature"
TEMPERATURE = "temperature"
TARGET_TEMP_HIGH = "target_temp_high"
TARGET_TEMP_LOW = "target_temp_low"
CURRENT_HUMIDITY = "current_humidity"
HUMIDITY = "humidity"
FAN_MODE = "fan_mode"
HVAC_ACTION = "hvac_action"
PRESET_MODE = "preset_mode"
SWING_MODE = "swing_mode"
SWING_HORIZONTAL_MODE = "swing_horizontal_mode"
class ClimateEntityFeature(IntFlag):
"""Supported features of the climate entity."""
@@ -14,9 +14,15 @@ rules:
docs-actions:
status: exempt
comment: This integration does not have any custom actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: Entities of this integration do not explicitly subscribe to events.
@@ -25,9 +25,15 @@ rules:
status: exempt
comment: |
The integration does not provide any actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: no actions
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: no events
@@ -17,9 +17,15 @@ rules:
status: exempt
comment: |
This integration does not provide additional actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: |
@@ -12,11 +12,17 @@ rules:
docs-actions:
status: exempt
comment: No service actions implemented
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions:
status: exempt
comment: No special external action required
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: No callbacks are implemented
+4 -3
View File
@@ -41,6 +41,7 @@ from .const import (
INTENT_OPEN_COVER,
CoverDeviceClass,
CoverEntityFeature,
CoverEntityStateAttribute,
CoverState,
)
from .trigger import make_cover_closed_trigger, make_cover_opened_trigger
@@ -260,13 +261,13 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Return the state attributes."""
data: dict[str, Any] = {}
data[ATTR_IS_CLOSED] = self.is_closed
data[CoverEntityStateAttribute.IS_CLOSED] = self.is_closed
if (current := self.current_cover_position) is not None:
data[ATTR_CURRENT_POSITION] = current
data[CoverEntityStateAttribute.CURRENT_POSITION] = current
if (current_tilt := self.current_cover_tilt_position) is not None:
data[ATTR_CURRENT_TILT_POSITION] = current_tilt
data[CoverEntityStateAttribute.CURRENT_TILT_POSITION] = current_tilt
return data
+9
View File
@@ -10,6 +10,15 @@ ATTR_IS_CLOSED = "is_closed"
ATTR_POSITION = "position"
ATTR_TILT_POSITION = "tilt_position"
class CoverEntityStateAttribute(StrEnum):
"""State attributes for cover entities."""
IS_CLOSED = "is_closed"
CURRENT_POSITION = "current_position"
CURRENT_TILT_POSITION = "current_tilt_position"
INTENT_OPEN_COVER = "HassOpenCover"
INTENT_CLOSE_COVER = "HassCloseCover"
@@ -14,9 +14,15 @@ rules:
status: exempt
comment: |
This integration does not provide additional actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -12,9 +12,15 @@ rules:
docs-actions:
status: exempt
comment: This integration does not register custom actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup:
status: exempt
comment: Entities use the coordinator pattern and do not subscribe to events.
@@ -8,9 +8,15 @@ rules:
config-flow: done
dependency-transparency: done
docs-actions: done
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -37,6 +37,35 @@ class TrackingType(StrEnum):
POSITION = "position"
class DeviceTrackerEntityCapabilityAttribute(StrEnum):
"""Capability attributes for device tracker entities."""
TRACKING_TYPE = "tracking_type"
class DeviceTrackerEntityStateAttribute(StrEnum):
"""State attributes common to device tracker entities."""
SOURCE_TYPE = "source_type"
IN_ZONES = "in_zones"
class TrackerEntityStateAttribute(StrEnum):
"""State attributes set by TrackerEntity."""
LATITUDE = "latitude"
LONGITUDE = "longitude"
GPS_ACCURACY = "gps_accuracy"
class ScannerEntityStateAttribute(StrEnum):
"""State attributes set by ScannerEntity."""
IP = "ip"
MAC = "mac"
HOST_NAME = "host_name"
CONF_SCAN_INTERVAL: Final = "interval_seconds"
SCAN_INTERVAL: Final = timedelta(seconds=12)
@@ -8,7 +8,7 @@ from propcache.api import cached_property
from homeassistant.components import zone
from homeassistant.components.zone import ATTR_PASSIVE, ATTR_RADIUS
from homeassistant.const import (
from homeassistant.const import ( # noqa: F401
ATTR_BATTERY_LEVEL,
ATTR_GPS_ACCURACY,
ATTR_LATITUDE,
@@ -42,7 +42,7 @@ from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.loader import async_suggest_report_issue
from homeassistant.util.hass_dict import HassKey
from .const import (
from .const import ( # noqa: F401
ATTR_HOST_NAME,
ATTR_IN_ZONES,
ATTR_IP,
@@ -53,7 +53,11 @@ from .const import (
CONNECTED_DEVICE_REGISTERED,
DOMAIN,
LOGGER,
DeviceTrackerEntityCapabilityAttribute,
DeviceTrackerEntityStateAttribute,
ScannerEntityStateAttribute,
SourceType,
TrackerEntityStateAttribute,
TrackingType,
)
@@ -215,7 +219,9 @@ class BaseTrackerEntity(Entity):
@override
def state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
attr: dict[str, Any] = {ATTR_SOURCE_TYPE: self.source_type}
attr: dict[str, Any] = {
DeviceTrackerEntityStateAttribute.SOURCE_TYPE: self.source_type
}
if self.battery_level is not None:
attr[ATTR_BATTERY_LEVEL] = self.battery_level
@@ -243,7 +249,7 @@ class TrackerEntity(
entity_description: TrackerEntityDescription
_attr_capability_attributes: dict[str, Any] = {
ATTR_TRACKING_TYPE: TrackingType.POSITION
DeviceTrackerEntityCapabilityAttribute.TRACKING_TYPE: TrackingType.POSITION
}
_attr_in_zones: list[str] | None = None
_attr_latitude: float | None = None
@@ -406,13 +412,15 @@ class TrackerEntity(
@override
def state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
attr: dict[str, Any] = {ATTR_IN_ZONES: self.__in_zones or []}
attr: dict[str, Any] = {
DeviceTrackerEntityStateAttribute.IN_ZONES: self.__in_zones or []
}
attr.update(super().state_attributes)
if self.latitude is not None and self.longitude is not None:
attr[ATTR_LATITUDE] = self.latitude
attr[ATTR_LONGITUDE] = self.longitude
attr[ATTR_GPS_ACCURACY] = self.location_accuracy
attr[TrackerEntityStateAttribute.LATITUDE] = self.latitude
attr[TrackerEntityStateAttribute.LONGITUDE] = self.longitude
attr[TrackerEntityStateAttribute.GPS_ACCURACY] = self.location_accuracy
return attr
@@ -425,7 +433,7 @@ class BaseScannerEntity(BaseTrackerEntity):
"""
_attr_capability_attributes: dict[str, Any] = {
ATTR_TRACKING_TYPE: TrackingType.CONNECTION
DeviceTrackerEntityCapabilityAttribute.TRACKING_TYPE: TrackingType.CONNECTION
}
_scanner_option_associated_zone: str = zone.ENTITY_ID_HOME
_scanner_option_associated_zone_unsub: CALLBACK_TYPE | None = None
@@ -556,7 +564,7 @@ class BaseScannerEntity(BaseTrackerEntity):
@override
def state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
attr: dict[str, Any] = {ATTR_IN_ZONES: []}
attr: dict[str, Any] = {DeviceTrackerEntityStateAttribute.IN_ZONES: []}
attr.update(super().state_attributes)
if not self.is_connected:
@@ -571,7 +579,7 @@ class BaseScannerEntity(BaseTrackerEntity):
):
return attr
attr[ATTR_IN_ZONES] = [
attr[DeviceTrackerEntityStateAttribute.IN_ZONES] = [
associated_zone,
*zone.async_get_enclosing_zones(self.hass, associated_zone),
]
@@ -721,10 +729,10 @@ class ScannerEntity(
attr = super().state_attributes
if ip_address := self.ip_address:
attr[ATTR_IP] = ip_address
attr[ScannerEntityStateAttribute.IP] = ip_address
if (mac_address := self.mac_address) is not None:
attr[ATTR_MAC] = mac_address
attr[ScannerEntityStateAttribute.MAC] = mac_address
if (hostname := self.hostname) is not None:
attr[ATTR_HOST_NAME] = hostname
attr[ScannerEntityStateAttribute.HOST_NAME] = hostname
return attr
@@ -17,9 +17,15 @@ rules:
status: exempt
comment: |
This integration does not provide additional actions.
docs-conditions:
status: exempt
comment: This integration does not have any conditions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
docs-triggers:
status: exempt
comment: This integration does not have any triggers.
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
@@ -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: |

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