This commit is contained in:
Franck Nijhof 2023-11-01 15:59:51 +01:00 committed by GitHub
commit 0b8d4235c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1520 changed files with 53680 additions and 28980 deletions

View File

@ -45,6 +45,7 @@ base_platforms: &base_platforms
- homeassistant/components/switch/** - homeassistant/components/switch/**
- homeassistant/components/text/** - homeassistant/components/text/**
- homeassistant/components/time/** - homeassistant/components/time/**
- homeassistant/components/todo/**
- homeassistant/components/tts/** - homeassistant/components/tts/**
- homeassistant/components/update/** - homeassistant/components/update/**
- homeassistant/components/vacuum/** - homeassistant/components/vacuum/**
@ -96,8 +97,8 @@ components: &components
- homeassistant/components/persistent_notification/** - homeassistant/components/persistent_notification/**
- homeassistant/components/person/** - homeassistant/components/person/**
- homeassistant/components/recorder/** - homeassistant/components/recorder/**
- homeassistant/components/recovery_mode/**
- homeassistant/components/repairs/** - homeassistant/components/repairs/**
- homeassistant/components/safe_mode/**
- homeassistant/components/script/** - homeassistant/components/script/**
- homeassistant/components/shopping_list/** - homeassistant/components/shopping_list/**
- homeassistant/components/ssdp/** - homeassistant/components/ssdp/**

View File

@ -178,6 +178,8 @@ omit =
homeassistant/components/comelit/cover.py homeassistant/components/comelit/cover.py
homeassistant/components/comelit/coordinator.py homeassistant/components/comelit/coordinator.py
homeassistant/components/comelit/light.py homeassistant/components/comelit/light.py
homeassistant/components/comelit/sensor.py
homeassistant/components/comelit/switch.py
homeassistant/components/comfoconnect/fan.py homeassistant/components/comfoconnect/fan.py
homeassistant/components/concord232/alarm_control_panel.py homeassistant/components/concord232/alarm_control_panel.py
homeassistant/components/concord232/binary_sensor.py homeassistant/components/concord232/binary_sensor.py
@ -284,9 +286,6 @@ omit =
homeassistant/components/edl21/__init__.py homeassistant/components/edl21/__init__.py
homeassistant/components/edl21/sensor.py homeassistant/components/edl21/sensor.py
homeassistant/components/egardia/* homeassistant/components/egardia/*
homeassistant/components/eight_sleep/__init__.py
homeassistant/components/eight_sleep/binary_sensor.py
homeassistant/components/eight_sleep/sensor.py
homeassistant/components/electric_kiwi/__init__.py homeassistant/components/electric_kiwi/__init__.py
homeassistant/components/electric_kiwi/api.py homeassistant/components/electric_kiwi/api.py
homeassistant/components/electric_kiwi/oauth2.py homeassistant/components/electric_kiwi/oauth2.py
@ -376,6 +375,7 @@ omit =
homeassistant/components/fibaro/binary_sensor.py homeassistant/components/fibaro/binary_sensor.py
homeassistant/components/fibaro/climate.py homeassistant/components/fibaro/climate.py
homeassistant/components/fibaro/cover.py homeassistant/components/fibaro/cover.py
homeassistant/components/fibaro/event.py
homeassistant/components/fibaro/light.py homeassistant/components/fibaro/light.py
homeassistant/components/fibaro/lock.py homeassistant/components/fibaro/lock.py
homeassistant/components/fibaro/sensor.py homeassistant/components/fibaro/sensor.py
@ -541,12 +541,6 @@ omit =
homeassistant/components/hvv_departures/__init__.py homeassistant/components/hvv_departures/__init__.py
homeassistant/components/hvv_departures/binary_sensor.py homeassistant/components/hvv_departures/binary_sensor.py
homeassistant/components/hvv_departures/sensor.py homeassistant/components/hvv_departures/sensor.py
homeassistant/components/hydrawise/__init__.py
homeassistant/components/hydrawise/binary_sensor.py
homeassistant/components/hydrawise/const.py
homeassistant/components/hydrawise/coordinator.py
homeassistant/components/hydrawise/sensor.py
homeassistant/components/hydrawise/switch.py
homeassistant/components/ialarm/alarm_control_panel.py homeassistant/components/ialarm/alarm_control_panel.py
homeassistant/components/iammeter/sensor.py homeassistant/components/iammeter/sensor.py
homeassistant/components/iaqualink/binary_sensor.py homeassistant/components/iaqualink/binary_sensor.py
@ -563,7 +557,6 @@ omit =
homeassistant/components/ifttt/alarm_control_panel.py homeassistant/components/ifttt/alarm_control_panel.py
homeassistant/components/iglo/light.py homeassistant/components/iglo/light.py
homeassistant/components/ihc/* homeassistant/components/ihc/*
homeassistant/components/imap_email_content/sensor.py
homeassistant/components/incomfort/* homeassistant/components/incomfort/*
homeassistant/components/insteon/binary_sensor.py homeassistant/components/insteon/binary_sensor.py
homeassistant/components/insteon/climate.py homeassistant/components/insteon/climate.py
@ -750,11 +743,6 @@ omit =
homeassistant/components/mikrotik/hub.py homeassistant/components/mikrotik/hub.py
homeassistant/components/mill/climate.py homeassistant/components/mill/climate.py
homeassistant/components/mill/sensor.py homeassistant/components/mill/sensor.py
homeassistant/components/minecraft_server/__init__.py
homeassistant/components/minecraft_server/binary_sensor.py
homeassistant/components/minecraft_server/coordinator.py
homeassistant/components/minecraft_server/entity.py
homeassistant/components/minecraft_server/sensor.py
homeassistant/components/minio/minio_helper.py homeassistant/components/minio/minio_helper.py
homeassistant/components/mjpeg/camera.py homeassistant/components/mjpeg/camera.py
homeassistant/components/mjpeg/util.py homeassistant/components/mjpeg/util.py
@ -795,6 +783,7 @@ omit =
homeassistant/components/mystrom/binary_sensor.py homeassistant/components/mystrom/binary_sensor.py
homeassistant/components/mystrom/light.py homeassistant/components/mystrom/light.py
homeassistant/components/mystrom/switch.py homeassistant/components/mystrom/switch.py
homeassistant/components/mystrom/sensor.py
homeassistant/components/nad/media_player.py homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/button.py homeassistant/components/nanoleaf/button.py
@ -835,7 +824,6 @@ omit =
homeassistant/components/nibe_heatpump/__init__.py homeassistant/components/nibe_heatpump/__init__.py
homeassistant/components/nibe_heatpump/climate.py homeassistant/components/nibe_heatpump/climate.py
homeassistant/components/nibe_heatpump/binary_sensor.py homeassistant/components/nibe_heatpump/binary_sensor.py
homeassistant/components/nibe_heatpump/number.py
homeassistant/components/nibe_heatpump/select.py homeassistant/components/nibe_heatpump/select.py
homeassistant/components/nibe_heatpump/sensor.py homeassistant/components/nibe_heatpump/sensor.py
homeassistant/components/nibe_heatpump/switch.py homeassistant/components/nibe_heatpump/switch.py
@ -964,6 +952,7 @@ omit =
homeassistant/components/ping/__init__.py homeassistant/components/ping/__init__.py
homeassistant/components/ping/binary_sensor.py homeassistant/components/ping/binary_sensor.py
homeassistant/components/ping/device_tracker.py homeassistant/components/ping/device_tracker.py
homeassistant/components/ping/helpers.py
homeassistant/components/pioneer/media_player.py homeassistant/components/pioneer/media_player.py
homeassistant/components/plaato/__init__.py homeassistant/components/plaato/__init__.py
homeassistant/components/plaato/binary_sensor.py homeassistant/components/plaato/binary_sensor.py
@ -1000,6 +989,7 @@ omit =
homeassistant/components/pushsafer/notify.py homeassistant/components/pushsafer/notify.py
homeassistant/components/pyload/sensor.py homeassistant/components/pyload/sensor.py
homeassistant/components/qbittorrent/__init__.py homeassistant/components/qbittorrent/__init__.py
homeassistant/components/qbittorrent/coordinator.py
homeassistant/components/qbittorrent/sensor.py homeassistant/components/qbittorrent/sensor.py
homeassistant/components/qnap/__init__.py homeassistant/components/qnap/__init__.py
homeassistant/components/qnap/coordinator.py homeassistant/components/qnap/coordinator.py
@ -1122,7 +1112,6 @@ omit =
homeassistant/components/sesame/lock.py homeassistant/components/sesame/lock.py
homeassistant/components/seven_segments/image_processing.py homeassistant/components/seven_segments/image_processing.py
homeassistant/components/seventeentrack/sensor.py homeassistant/components/seventeentrack/sensor.py
homeassistant/components/shiftr/*
homeassistant/components/shodan/sensor.py homeassistant/components/shodan/sensor.py
homeassistant/components/sia/__init__.py homeassistant/components/sia/__init__.py
homeassistant/components/sia/alarm_control_panel.py homeassistant/components/sia/alarm_control_panel.py
@ -1276,6 +1265,7 @@ omit =
homeassistant/components/switchbot/sensor.py homeassistant/components/switchbot/sensor.py
homeassistant/components/switchbot/switch.py homeassistant/components/switchbot/switch.py
homeassistant/components/switchbot/lock.py homeassistant/components/switchbot/lock.py
homeassistant/components/switchbot_cloud/climate.py
homeassistant/components/switchbot_cloud/coordinator.py homeassistant/components/switchbot_cloud/coordinator.py
homeassistant/components/switchbot_cloud/entity.py homeassistant/components/switchbot_cloud/entity.py
homeassistant/components/switchbot_cloud/switch.py homeassistant/components/switchbot_cloud/switch.py
@ -1301,6 +1291,7 @@ omit =
homeassistant/components/system_bridge/__init__.py homeassistant/components/system_bridge/__init__.py
homeassistant/components/system_bridge/binary_sensor.py homeassistant/components/system_bridge/binary_sensor.py
homeassistant/components/system_bridge/coordinator.py homeassistant/components/system_bridge/coordinator.py
homeassistant/components/system_bridge/media_player.py
homeassistant/components/system_bridge/notify.py homeassistant/components/system_bridge/notify.py
homeassistant/components/system_bridge/sensor.py homeassistant/components/system_bridge/sensor.py
homeassistant/components/systemmonitor/sensor.py homeassistant/components/systemmonitor/sensor.py
@ -1400,6 +1391,7 @@ omit =
homeassistant/components/trafikverket_weatherstation/coordinator.py homeassistant/components/trafikverket_weatherstation/coordinator.py
homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/__init__.py homeassistant/components/transmission/__init__.py
homeassistant/components/transmission/coordinator.py
homeassistant/components/transmission/sensor.py homeassistant/components/transmission/sensor.py
homeassistant/components/transmission/switch.py homeassistant/components/transmission/switch.py
homeassistant/components/travisci/sensor.py homeassistant/components/travisci/sensor.py
@ -1474,7 +1466,9 @@ omit =
homeassistant/components/vicare/binary_sensor.py homeassistant/components/vicare/binary_sensor.py
homeassistant/components/vicare/button.py homeassistant/components/vicare/button.py
homeassistant/components/vicare/climate.py homeassistant/components/vicare/climate.py
homeassistant/components/vicare/entity.py
homeassistant/components/vicare/sensor.py homeassistant/components/vicare/sensor.py
homeassistant/components/vicare/utils.py
homeassistant/components/vicare/water_heater.py homeassistant/components/vicare/water_heater.py
homeassistant/components/vilfo/__init__.py homeassistant/components/vilfo/__init__.py
homeassistant/components/vilfo/sensor.py homeassistant/components/vilfo/sensor.py
@ -1516,7 +1510,6 @@ omit =
homeassistant/components/wiffi/sensor.py homeassistant/components/wiffi/sensor.py
homeassistant/components/wiffi/wiffi_strings.py homeassistant/components/wiffi/wiffi_strings.py
homeassistant/components/wirelesstag/* homeassistant/components/wirelesstag/*
homeassistant/components/withings/api.py
homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/__init__.py
homeassistant/components/wolflink/sensor.py homeassistant/components/wolflink/sensor.py
homeassistant/components/worldtidesinfo/sensor.py homeassistant/components/worldtidesinfo/sensor.py

View File

@ -24,12 +24,12 @@ jobs:
publish: ${{ steps.version.outputs.publish }} publish: ${{ steps.version.outputs.publish }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -56,10 +56,10 @@ jobs:
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -98,7 +98,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Download nightly wheels of frontend - name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
@ -124,7 +124,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -252,7 +252,7 @@ jobs:
- green - green
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set build additional args - name: Set build additional args
run: | run: |
@ -289,7 +289,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Initialize git - name: Initialize git
uses: home-assistant/actions/helpers/git-init@master uses: home-assistant/actions/helpers/git-init@master
@ -327,7 +327,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Install Cosign - name: Install Cosign
uses: sigstore/cosign-installer@v3.1.2 uses: sigstore/cosign-installer@v3.1.2

View File

@ -37,18 +37,20 @@ env:
PIP_CACHE_VERSION: 4 PIP_CACHE_VERSION: 4
MYPY_CACHE_VERSION: 5 MYPY_CACHE_VERSION: 5
BLACK_CACHE_VERSION: 1 BLACK_CACHE_VERSION: 1
HA_SHORT_VERSION: "2023.10" HA_SHORT_VERSION: "2023.11"
DEFAULT_PYTHON: "3.11" DEFAULT_PYTHON: "3.11"
ALL_PYTHON_VERSIONS: "['3.11']" ALL_PYTHON_VERSIONS: "['3.11', '3.12']"
# 10.3 is the oldest supported version # 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support # 10.6 is the current long-term-support
# - 10.6.10 is the version currently shipped with the Add-on (as of 31 Jan 2023) # - 10.6.10 is the version currently shipped with the Add-on (as of 31 Jan 2023)
# 10.10 is the latest short-term-support # 10.10 is the latest short-term-support
# - 10.10.3 is the latest (as of 6 Feb 2023) # - 10.10.3 is the latest (as of 6 Feb 2023)
# 10.11 is the latest long-term-support
# - 10.11.2 is the version currently shipped with Synology (as of 11 Oct 2023)
# mysql 8.0.32 does not always behave the same as MariaDB # mysql 8.0.32 does not always behave the same as MariaDB
# and some queries that work on MariaDB do not work on MySQL # and some queries that work on MariaDB do not work on MySQL
MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3','mysql:8.0.32']" MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3','mariadb:10.11.2','mysql:8.0.32']"
# 12 is the oldest supported version # 12 is the oldest supported version
# - 12.14 is the latest (as of 9 Feb 2023) # - 12.14 is the latest (as of 9 Feb 2023)
# 15 is the latest version # 15 is the latest version
@ -89,7 +91,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Generate partial Python venv restore key - name: Generate partial Python venv restore key
id: generate_python_cache_key id: generate_python_cache_key
run: >- run: >-
@ -222,10 +224,10 @@ jobs:
- info - info
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true check-latest: true
@ -267,9 +269,9 @@ jobs:
- pre-commit - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -335,9 +337,9 @@ jobs:
- pre-commit - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -384,9 +386,9 @@ jobs:
- pre-commit - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -478,10 +480,10 @@ jobs:
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }} python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
check-latest: true check-latest: true
@ -546,10 +548,10 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true check-latest: true
@ -578,10 +580,10 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true check-latest: true
@ -611,10 +613,10 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true check-latest: true
@ -655,10 +657,10 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true check-latest: true
@ -737,10 +739,10 @@ jobs:
bluez \ bluez \
ffmpeg ffmpeg
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
check-latest: true check-latest: true
@ -889,10 +891,10 @@ jobs:
ffmpeg \ ffmpeg \
libmariadb-dev-compat libmariadb-dev-compat
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
check-latest: true check-latest: true
@ -1013,10 +1015,10 @@ jobs:
ffmpeg \ ffmpeg \
postgresql-server-dev-14 postgresql-server-dev-14
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
check-latest: true check-latest: true
@ -1108,7 +1110,7 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Download all coverage artifacts - name: Download all coverage artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- name: Upload coverage to Codecov (full coverage) - name: Upload coverage to Codecov (full coverage)

39
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: "CodeQL"
# yamllint disable-line rule:truthy
on:
push:
branches:
- dev
- rc
- master
schedule:
- cron: "30 18 * * 4"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
- name: Initialize CodeQL
uses: github/codeql-action/init@v2.22.4
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2.22.4
with:
category: "/language:python"

View File

@ -19,10 +19,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}

View File

@ -10,8 +10,10 @@ on:
- dev - dev
- rc - rc
paths: paths:
- "requirements.txt" - ".github/workflows/wheels.yml"
- "homeassistant/package_constraints.txt"
- "requirements_all.txt" - "requirements_all.txt"
- "requirements.txt"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref_name}} group: ${{ github.workflow }}-${{ github.ref_name}}
@ -26,7 +28,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }} architectures: ${{ steps.info.outputs.architectures }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Get information - name: Get information
id: info id: info
@ -80,11 +82,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
abi: ["cp311"] abi: ["cp311", "cp312"]
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@ -97,7 +99,7 @@ jobs:
name: requirements_diff name: requirements_diff
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2023.09.1 uses: home-assistant/wheels@2023.10.5
with: with:
abi: ${{ matrix.abi }} abi: ${{ matrix.abi }}
tag: musllinux_1_2 tag: musllinux_1_2
@ -110,7 +112,7 @@ jobs:
requirements-diff: "requirements_diff.txt" requirements-diff: "requirements_diff.txt"
requirements: "requirements.txt" requirements: "requirements.txt"
integrations_cp311: integrations:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }} name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant' if: github.repository_owner == 'home-assistant'
needs: init needs: init
@ -118,11 +120,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
abi: ["cp311"] abi: ["cp311", "cp312"]
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.0 uses: actions/checkout@v4.1.1
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@ -168,6 +170,18 @@ jobs:
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all.txt requirements_all.txt split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all.txt requirements_all.txt
- name: Create requirements for cython<3
run: |
# Some dependencies still require 'cython<3'
# and don't yet use isolated build environments.
# Build these first.
# grpcio: https://github.com/grpc/grpc/issues/33918
# pydantic: https://github.com/pydantic/pydantic/issues/7689
touch requirements_old-cython.txt
cat homeassistant/package_constraints.txt | grep 'grpcio==' >> requirements_old-cython.txt
cat homeassistant/package_constraints.txt | grep 'pydantic==' >> requirements_old-cython.txt
- name: Adjust build env - name: Adjust build env
run: | run: |
if [ "${{ matrix.arch }}" = "i386" ]; then if [ "${{ matrix.arch }}" = "i386" ]; then
@ -177,8 +191,23 @@ jobs:
# Do not pin numpy in wheels building # Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels (old cython)
uses: home-assistant/wheels@2023.10.5
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_old-cython.txt"
pip: "'cython<3'"
- name: Build wheels (part 1) - name: Build wheels (part 1)
uses: home-assistant/wheels@2023.09.1 uses: home-assistant/wheels@2023.10.5
with: with:
abi: ${{ matrix.abi }} abi: ${{ matrix.abi }}
tag: musllinux_1_2 tag: musllinux_1_2
@ -192,7 +221,7 @@ jobs:
requirements: "requirements_all.txtaa" requirements: "requirements_all.txtaa"
- name: Build wheels (part 2) - name: Build wheels (part 2)
uses: home-assistant/wheels@2023.09.1 uses: home-assistant/wheels@2023.10.5
with: with:
abi: ${{ matrix.abi }} abi: ${{ matrix.abi }}
tag: musllinux_1_2 tag: musllinux_1_2
@ -206,7 +235,7 @@ jobs:
requirements: "requirements_all.txtab" requirements: "requirements_all.txtab"
- name: Build wheels (part 3) - name: Build wheels (part 3)
uses: home-assistant/wheels@2023.09.1 uses: home-assistant/wheels@2023.10.5
with: with:
abi: ${{ matrix.abi }} abi: ${{ matrix.abi }}
tag: musllinux_1_2 tag: musllinux_1_2

3
.gitignore vendored
View File

@ -111,9 +111,6 @@ virtualization/vagrant/config
!.vscode/tasks.json !.vscode/tasks.json
.env .env
# Built docs
docs/build
# Windows Explorer # Windows Explorer
desktop.ini desktop.ini
/home-assistant.pyproj /home-assistant.pyproj

View File

@ -3,3 +3,4 @@ ignored:
- DL3008 - DL3008
- DL3013 - DL3013
- DL3018 - DL3018
- DL3042

View File

@ -1,12 +1,12 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.289 rev: v0.1.1
hooks: hooks:
- id: ruff - id: ruff
args: args:
- --fix - --fix
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1 rev: 23.10.0
hooks: hooks:
- id: black - id: black
args: args:

View File

@ -1,7 +1,6 @@
*.md *.md
.strict-typing .strict-typing
azure-*.yml azure-*.yml
docs/source/_templates/*
homeassistant/components/*/translations/*.json homeassistant/components/*/translations/*.json
homeassistant/generated/* homeassistant/generated/*
tests/components/lidarr/fixtures/initialize.js tests/components/lidarr/fixtures/initialize.js

View File

@ -1,14 +0,0 @@
# .readthedocs.yml
version: 2
build:
os: ubuntu-20.04
tools:
python: "3.9"
python:
install:
- method: setuptools
path: .
- requirements: requirements_docs.txt

View File

@ -103,6 +103,7 @@ homeassistant.components.devolo_home_control.*
homeassistant.components.devolo_home_network.* homeassistant.components.devolo_home_network.*
homeassistant.components.dhcp.* homeassistant.components.dhcp.*
homeassistant.components.diagnostics.* homeassistant.components.diagnostics.*
homeassistant.components.discovergy.*
homeassistant.components.dlna_dmr.* homeassistant.components.dlna_dmr.*
homeassistant.components.dnsip.* homeassistant.components.dnsip.*
homeassistant.components.doorbird.* homeassistant.components.doorbird.*
@ -156,15 +157,7 @@ homeassistant.components.homeassistant_green.*
homeassistant.components.homeassistant_hardware.* homeassistant.components.homeassistant_hardware.*
homeassistant.components.homeassistant_sky_connect.* homeassistant.components.homeassistant_sky_connect.*
homeassistant.components.homeassistant_yellow.* homeassistant.components.homeassistant_yellow.*
homeassistant.components.homekit homeassistant.components.homekit.*
homeassistant.components.homekit.accessories
homeassistant.components.homekit.aidmanager
homeassistant.components.homekit.config_flow
homeassistant.components.homekit.diagnostics
homeassistant.components.homekit.logbook
homeassistant.components.homekit.type_locks
homeassistant.components.homekit.type_triggers
homeassistant.components.homekit.util
homeassistant.components.homekit_controller homeassistant.components.homekit_controller
homeassistant.components.homekit_controller.alarm_control_panel homeassistant.components.homekit_controller.alarm_control_panel
homeassistant.components.homekit_controller.button homeassistant.components.homekit_controller.button
@ -211,6 +204,7 @@ homeassistant.components.light.*
homeassistant.components.litejet.* homeassistant.components.litejet.*
homeassistant.components.litterrobot.* homeassistant.components.litterrobot.*
homeassistant.components.local_ip.* homeassistant.components.local_ip.*
homeassistant.components.local_todo.*
homeassistant.components.lock.* homeassistant.components.lock.*
homeassistant.components.logbook.* homeassistant.components.logbook.*
homeassistant.components.logger.* homeassistant.components.logger.*
@ -327,6 +321,7 @@ homeassistant.components.synology_dsm.*
homeassistant.components.systemmonitor.* homeassistant.components.systemmonitor.*
homeassistant.components.tag.* homeassistant.components.tag.*
homeassistant.components.tailscale.* homeassistant.components.tailscale.*
homeassistant.components.tami4.*
homeassistant.components.tautulli.* homeassistant.components.tautulli.*
homeassistant.components.tcp.* homeassistant.components.tcp.*
homeassistant.components.text.* homeassistant.components.text.*
@ -343,6 +338,7 @@ homeassistant.components.trafikverket_camera.*
homeassistant.components.trafikverket_ferry.* homeassistant.components.trafikverket_ferry.*
homeassistant.components.trafikverket_train.* homeassistant.components.trafikverket_train.*
homeassistant.components.trafikverket_weatherstation.* homeassistant.components.trafikverket_weatherstation.*
homeassistant.components.transmission.*
homeassistant.components.trend.* homeassistant.components.trend.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.twentemilieu.* homeassistant.components.twentemilieu.*
@ -366,6 +362,7 @@ homeassistant.components.webostv.*
homeassistant.components.websocket_api.* homeassistant.components.websocket_api.*
homeassistant.components.wemo.* homeassistant.components.wemo.*
homeassistant.components.whois.* homeassistant.components.whois.*
homeassistant.components.withings.*
homeassistant.components.wiz.* homeassistant.components.wiz.*
homeassistant.components.wled.* homeassistant.components.wled.*
homeassistant.components.worldclock.* homeassistant.components.worldclock.*

6
.vscode/tasks.json vendored
View File

@ -16,7 +16,7 @@
{ {
"label": "Pytest", "label": "Pytest",
"type": "shell", "type": "shell",
"command": "pytest --timeout=10 tests", "command": "python3 -m pytest --timeout=10 tests",
"dependsOn": ["Install all Test Requirements"], "dependsOn": ["Install all Test Requirements"],
"group": { "group": {
"kind": "test", "kind": "test",
@ -31,7 +31,7 @@
{ {
"label": "Pytest (changed tests only)", "label": "Pytest (changed tests only)",
"type": "shell", "type": "shell",
"command": "pytest --timeout=10 --picked", "command": "python3 -m pytest --timeout=10 --picked",
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true "isDefault": true
@ -75,7 +75,7 @@
"label": "Code Coverage", "label": "Code Coverage",
"detail": "Generate code coverage report for a given integration.", "detail": "Generate code coverage report for a given integration.",
"type": "shell", "type": "shell",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto", "command": "python3 -m pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto",
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true "isDefault": true

View File

@ -100,8 +100,8 @@ build.json @home-assistant/supervisor
/tests/components/apprise/ @caronc /tests/components/apprise/ @caronc
/homeassistant/components/aprs/ @PhilRW /homeassistant/components/aprs/ @PhilRW
/tests/components/aprs/ @PhilRW /tests/components/aprs/ @PhilRW
/homeassistant/components/aranet/ @aschmitz /homeassistant/components/aranet/ @aschmitz @thecode
/tests/components/aranet/ @aschmitz /tests/components/aranet/ @aschmitz @thecode
/homeassistant/components/arcam_fmj/ @elupus /homeassistant/components/arcam_fmj/ @elupus
/tests/components/arcam_fmj/ @elupus /tests/components/arcam_fmj/ @elupus
/homeassistant/components/arris_tg2492lg/ @vanbalken /homeassistant/components/arris_tg2492lg/ @vanbalken
@ -233,8 +233,8 @@ build.json @home-assistant/supervisor
/tests/components/counter/ @fabaff /tests/components/counter/ @fabaff
/homeassistant/components/cover/ @home-assistant/core /homeassistant/components/cover/ @home-assistant/core
/tests/components/cover/ @home-assistant/core /tests/components/cover/ @home-assistant/core
/homeassistant/components/cpuspeed/ @fabaff @frenck /homeassistant/components/cpuspeed/ @fabaff
/tests/components/cpuspeed/ @fabaff @frenck /tests/components/cpuspeed/ @fabaff
/homeassistant/components/crownstone/ @Crownstone @RicArch97 /homeassistant/components/crownstone/ @Crownstone @RicArch97
/tests/components/crownstone/ @Crownstone @RicArch97 /tests/components/crownstone/ @Crownstone @RicArch97
/homeassistant/components/cups/ @fabaff /homeassistant/components/cups/ @fabaff
@ -319,8 +319,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/efergy/ @tkdrob /homeassistant/components/efergy/ @tkdrob
/tests/components/efergy/ @tkdrob /tests/components/efergy/ @tkdrob
/homeassistant/components/egardia/ @jeroenterheerdt /homeassistant/components/egardia/ @jeroenterheerdt
/homeassistant/components/eight_sleep/ @mezz64 @raman325
/tests/components/eight_sleep/ @mezz64 @raman325
/homeassistant/components/electrasmart/ @jafar-atili /homeassistant/components/electrasmart/ @jafar-atili
/tests/components/electrasmart/ @jafar-atili /tests/components/electrasmart/ @jafar-atili
/homeassistant/components/electric_kiwi/ @mikey0000 /homeassistant/components/electric_kiwi/ @mikey0000
@ -423,8 +421,8 @@ build.json @home-assistant/supervisor
/tests/components/fritzbox/ @mib1185 @flabbamann /tests/components/fritzbox/ @mib1185 @flabbamann
/homeassistant/components/fritzbox_callmonitor/ @cdce8p /homeassistant/components/fritzbox_callmonitor/ @cdce8p
/tests/components/fritzbox_callmonitor/ @cdce8p /tests/components/fritzbox_callmonitor/ @cdce8p
/homeassistant/components/fronius/ @nielstron @farmio /homeassistant/components/fronius/ @farmio
/tests/components/fronius/ @nielstron @farmio /tests/components/fronius/ @farmio
/homeassistant/components/frontend/ @home-assistant/frontend /homeassistant/components/frontend/ @home-assistant/frontend
/tests/components/frontend/ @home-assistant/frontend /tests/components/frontend/ @home-assistant/frontend
/homeassistant/components/frontier_silicon/ @wlcrs /homeassistant/components/frontier_silicon/ @wlcrs
@ -479,6 +477,8 @@ build.json @home-assistant/supervisor
/tests/components/google_mail/ @tkdrob /tests/components/google_mail/ @tkdrob
/homeassistant/components/google_sheets/ @tkdrob /homeassistant/components/google_sheets/ @tkdrob
/tests/components/google_sheets/ @tkdrob /tests/components/google_sheets/ @tkdrob
/homeassistant/components/google_tasks/ @allenporter
/tests/components/google_tasks/ @allenporter
/homeassistant/components/google_travel_time/ @eifinger /homeassistant/components/google_travel_time/ @eifinger
/tests/components/google_travel_time/ @eifinger /tests/components/google_travel_time/ @eifinger
/homeassistant/components/govee_ble/ @bdraco @PierreAronnax /homeassistant/components/govee_ble/ @bdraco @PierreAronnax
@ -586,6 +586,8 @@ build.json @home-assistant/supervisor
/tests/components/image_upload/ @home-assistant/core /tests/components/image_upload/ @home-assistant/core
/homeassistant/components/imap/ @jbouwh /homeassistant/components/imap/ @jbouwh
/tests/components/imap/ @jbouwh /tests/components/imap/ @jbouwh
/homeassistant/components/improv_ble/ @emontnemery
/tests/components/improv_ble/ @emontnemery
/homeassistant/components/incomfort/ @zxdavb /homeassistant/components/incomfort/ @zxdavb
/homeassistant/components/influxdb/ @mdegat01 /homeassistant/components/influxdb/ @mdegat01
/tests/components/influxdb/ @mdegat01 /tests/components/influxdb/ @mdegat01
@ -660,8 +662,8 @@ build.json @home-assistant/supervisor
/tests/components/kmtronic/ @dgomes /tests/components/kmtronic/ @dgomes
/homeassistant/components/knx/ @Julius2342 @farmio @marvin-w /homeassistant/components/knx/ @Julius2342 @farmio @marvin-w
/tests/components/knx/ @Julius2342 @farmio @marvin-w /tests/components/knx/ @Julius2342 @farmio @marvin-w
/homeassistant/components/kodi/ @OnFreund @cgtobi /homeassistant/components/kodi/ @OnFreund
/tests/components/kodi/ @OnFreund @cgtobi /tests/components/kodi/ @OnFreund
/homeassistant/components/konnected/ @heythisisnate /homeassistant/components/konnected/ @heythisisnate
/tests/components/konnected/ @heythisisnate /tests/components/konnected/ @heythisisnate
/homeassistant/components/kostal_plenticore/ @stegm /homeassistant/components/kostal_plenticore/ @stegm
@ -708,6 +710,8 @@ build.json @home-assistant/supervisor
/tests/components/local_calendar/ @allenporter /tests/components/local_calendar/ @allenporter
/homeassistant/components/local_ip/ @issacg /homeassistant/components/local_ip/ @issacg
/tests/components/local_ip/ @issacg /tests/components/local_ip/ @issacg
/homeassistant/components/local_todo/ @allenporter
/tests/components/local_todo/ @allenporter
/homeassistant/components/lock/ @home-assistant/core /homeassistant/components/lock/ @home-assistant/core
/tests/components/lock/ @home-assistant/core /tests/components/lock/ @home-assistant/core
/homeassistant/components/logbook/ @home-assistant/core /homeassistant/components/logbook/ @home-assistant/core
@ -755,8 +759,8 @@ build.json @home-assistant/supervisor
/tests/components/melissa/ @kennedyshead /tests/components/melissa/ @kennedyshead
/homeassistant/components/melnor/ @vanstinator /homeassistant/components/melnor/ @vanstinator
/tests/components/melnor/ @vanstinator /tests/components/melnor/ @vanstinator
/homeassistant/components/met/ @danielhiversen @thimic /homeassistant/components/met/ @danielhiversen
/tests/components/met/ @danielhiversen @thimic /tests/components/met/ @danielhiversen
/homeassistant/components/met_eireann/ @DylanGore /homeassistant/components/met_eireann/ @DylanGore
/tests/components/met_eireann/ @DylanGore /tests/components/met_eireann/ @DylanGore
/homeassistant/components/meteo_france/ @hacf-fr @oncleben31 @Quentame /homeassistant/components/meteo_france/ @hacf-fr @oncleben31 @Quentame
@ -949,6 +953,8 @@ build.json @home-assistant/supervisor
/tests/components/picnic/ @corneyl /tests/components/picnic/ @corneyl
/homeassistant/components/pilight/ @trekky12 /homeassistant/components/pilight/ @trekky12
/tests/components/pilight/ @trekky12 /tests/components/pilight/ @trekky12
/homeassistant/components/ping/ @jpbede
/tests/components/ping/ @jpbede
/homeassistant/components/plaato/ @JohNan /homeassistant/components/plaato/ @JohNan
/tests/components/plaato/ @JohNan /tests/components/plaato/ @JohNan
/homeassistant/components/plex/ @jjlawren /homeassistant/components/plex/ @jjlawren
@ -1033,6 +1039,8 @@ build.json @home-assistant/supervisor
/tests/components/recollect_waste/ @bachya /tests/components/recollect_waste/ @bachya
/homeassistant/components/recorder/ @home-assistant/core /homeassistant/components/recorder/ @home-assistant/core
/tests/components/recorder/ @home-assistant/core /tests/components/recorder/ @home-assistant/core
/homeassistant/components/recovery_mode/ @home-assistant/core
/tests/components/recovery_mode/ @home-assistant/core
/homeassistant/components/rejseplanen/ @DarkFox /homeassistant/components/rejseplanen/ @DarkFox
/homeassistant/components/remote/ @home-assistant/core /homeassistant/components/remote/ @home-assistant/core
/tests/components/remote/ @home-assistant/core /tests/components/remote/ @home-assistant/core
@ -1063,8 +1071,8 @@ build.json @home-assistant/supervisor
/tests/components/roborock/ @humbertogontijo @Lash-L /tests/components/roborock/ @humbertogontijo @Lash-L
/homeassistant/components/roku/ @ctalkington /homeassistant/components/roku/ @ctalkington
/tests/components/roku/ @ctalkington /tests/components/roku/ @ctalkington
/homeassistant/components/roomba/ @pschmitt @cyr-ius @shenxn /homeassistant/components/roomba/ @pschmitt @cyr-ius @shenxn @Xitee1
/tests/components/roomba/ @pschmitt @cyr-ius @shenxn /tests/components/roomba/ @pschmitt @cyr-ius @shenxn @Xitee1
/homeassistant/components/roon/ @pavoni /homeassistant/components/roon/ @pavoni
/tests/components/roon/ @pavoni /tests/components/roon/ @pavoni
/homeassistant/components/rpi_power/ @shenxn @swetoast /homeassistant/components/rpi_power/ @shenxn @swetoast
@ -1083,8 +1091,6 @@ build.json @home-assistant/supervisor
/tests/components/rympro/ @OnFreund @elad-bar @maorcc /tests/components/rympro/ @OnFreund @elad-bar @maorcc
/homeassistant/components/sabnzbd/ @shaiu /homeassistant/components/sabnzbd/ @shaiu
/tests/components/sabnzbd/ @shaiu /tests/components/sabnzbd/ @shaiu
/homeassistant/components/safe_mode/ @home-assistant/core
/tests/components/safe_mode/ @home-assistant/core
/homeassistant/components/saj/ @fredericvl /homeassistant/components/saj/ @fredericvl
/homeassistant/components/samsungtv/ @chemelli74 @epenet /homeassistant/components/samsungtv/ @chemelli74 @epenet
/tests/components/samsungtv/ @chemelli74 @epenet /tests/components/samsungtv/ @chemelli74 @epenet
@ -1189,8 +1195,8 @@ build.json @home-assistant/supervisor
/tests/components/sonarr/ @ctalkington /tests/components/sonarr/ @ctalkington
/homeassistant/components/songpal/ @rytilahti @shenxn /homeassistant/components/songpal/ @rytilahti @shenxn
/tests/components/songpal/ @rytilahti @shenxn /tests/components/songpal/ @rytilahti @shenxn
/homeassistant/components/sonos/ @cgtobi @jjlawren /homeassistant/components/sonos/ @jjlawren
/tests/components/sonos/ @cgtobi @jjlawren /tests/components/sonos/ @jjlawren
/homeassistant/components/soundtouch/ @kroimon /homeassistant/components/soundtouch/ @kroimon
/tests/components/soundtouch/ @kroimon /tests/components/soundtouch/ @kroimon
/homeassistant/components/spaceapi/ @fabaff /homeassistant/components/spaceapi/ @fabaff
@ -1265,6 +1271,8 @@ build.json @home-assistant/supervisor
/tests/components/tag/ @balloob @dmulcahey /tests/components/tag/ @balloob @dmulcahey
/homeassistant/components/tailscale/ @frenck /homeassistant/components/tailscale/ @frenck
/tests/components/tailscale/ @frenck /tests/components/tailscale/ @frenck
/homeassistant/components/tami4/ @Guy293
/tests/components/tami4/ @Guy293
/homeassistant/components/tankerkoenig/ @guillempages @mib1185 /homeassistant/components/tankerkoenig/ @guillempages @mib1185
/tests/components/tankerkoenig/ @guillempages @mib1185 /tests/components/tankerkoenig/ @guillempages @mib1185
/homeassistant/components/tapsaff/ @bazwilliams /homeassistant/components/tapsaff/ @bazwilliams
@ -1299,6 +1307,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/time_date/ @fabaff /homeassistant/components/time_date/ @fabaff
/tests/components/time_date/ @fabaff /tests/components/time_date/ @fabaff
/homeassistant/components/tmb/ @alemuro /homeassistant/components/tmb/ @alemuro
/homeassistant/components/todo/ @home-assistant/core
/tests/components/todo/ @home-assistant/core
/homeassistant/components/todoist/ @boralyl /homeassistant/components/todoist/ @boralyl
/tests/components/todoist/ @boralyl /tests/components/todoist/ @boralyl
/homeassistant/components/tolo/ @MatthiasLohr /homeassistant/components/tolo/ @MatthiasLohr
@ -1321,10 +1331,10 @@ build.json @home-assistant/supervisor
/tests/components/trafikverket_camera/ @gjohansson-ST /tests/components/trafikverket_camera/ @gjohansson-ST
/homeassistant/components/trafikverket_ferry/ @gjohansson-ST /homeassistant/components/trafikverket_ferry/ @gjohansson-ST
/tests/components/trafikverket_ferry/ @gjohansson-ST /tests/components/trafikverket_ferry/ @gjohansson-ST
/homeassistant/components/trafikverket_train/ @endor-force @gjohansson-ST /homeassistant/components/trafikverket_train/ @gjohansson-ST
/tests/components/trafikverket_train/ @endor-force @gjohansson-ST /tests/components/trafikverket_train/ @gjohansson-ST
/homeassistant/components/trafikverket_weatherstation/ @endor-force @gjohansson-ST /homeassistant/components/trafikverket_weatherstation/ @gjohansson-ST
/tests/components/trafikverket_weatherstation/ @endor-force @gjohansson-ST /tests/components/trafikverket_weatherstation/ @gjohansson-ST
/homeassistant/components/transmission/ @engrbm87 @JPHutchins /homeassistant/components/transmission/ @engrbm87 @JPHutchins
/tests/components/transmission/ @engrbm87 @JPHutchins /tests/components/transmission/ @engrbm87 @JPHutchins
/homeassistant/components/trend/ @jpbede /homeassistant/components/trend/ @jpbede
@ -1436,8 +1446,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/wilight/ @leofig-rj /homeassistant/components/wilight/ @leofig-rj
/tests/components/wilight/ @leofig-rj /tests/components/wilight/ @leofig-rj
/homeassistant/components/wirelesstag/ @sergeymaysak /homeassistant/components/wirelesstag/ @sergeymaysak
/homeassistant/components/withings/ @vangorra @joostlek /homeassistant/components/withings/ @joostlek
/tests/components/withings/ @vangorra @joostlek /tests/components/withings/ @joostlek
/homeassistant/components/wiz/ @sbidy /homeassistant/components/wiz/ @sbidy
/tests/components/wiz/ @sbidy /tests/components/wiz/ @sbidy
/homeassistant/components/wled/ @frenck /homeassistant/components/wled/ @frenck

View File

@ -14,41 +14,29 @@ COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \ RUN \
pip3 install \ pip3 install \
--no-cache-dir \
--only-binary=:all: \ --only-binary=:all: \
--index-url "https://wheels.home-assistant.io/musllinux-index/" \
-r homeassistant/requirements.txt -r homeassistant/requirements.txt
COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/ COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
RUN \ RUN \
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \ if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
pip3 install \ pip3 install homeassistant/home_assistant_frontend-*.whl; \
--no-cache-dir \
--no-index \
homeassistant/home_assistant_frontend-*.whl; \
fi \ fi \
&& if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \ && if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \
pip3 install \ pip3 install homeassistant/home_assistant_intents-*.whl; \
--no-cache-dir \
--no-index \
homeassistant/home_assistant_intents-*.whl; \
fi \ fi \
&& \ && \
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
pip3 install \ pip3 install \
--no-cache-dir \
--only-binary=:all: \ --only-binary=:all: \
--index-url "https://wheels.home-assistant.io/musllinux-index/" \
-r homeassistant/requirements_all.txt -r homeassistant/requirements_all.txt
## Setup Home Assistant Core ## Setup Home Assistant Core
COPY . homeassistant/ COPY . homeassistant/
RUN \ RUN \
pip3 install \ pip3 install \
--no-cache-dir \
--only-binary=:all: \ --only-binary=:all: \
--index-url "https://wheels.home-assistant.io/musllinux-index/" \
-e ./homeassistant \ -e ./homeassistant \
&& python3 -m compileall \ && python3 -m compileall \
homeassistant/homeassistant homeassistant/homeassistant

View File

@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-homeassistant image: ghcr.io/home-assistant/{arch}-homeassistant
build_from: build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.09.0 aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.10.1
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.09.0 armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.10.1
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.09.0 armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.10.1
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.09.0 amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.10.1
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.09.0 i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.10.1
codenotary: codenotary:
signer: notary@home-assistant.io signer: notary@home-assistant.io
base_image: notary@home-assistant.io base_image: notary@home-assistant.io

View File

@ -1,230 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " livehtml to make standalone HTML files via sphinx-autobuild"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: livehtml
livehtml:
sphinx-autobuild -z ../homeassistant/ --port 0 -B -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Home-Assistant.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Home-Assistant.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Home-Assistant"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Home-Assistant"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."

0
docs/build/.empty vendored
View File

View File

@ -1,281 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
set I18NSPHINXOPTS=%SPHINXOPTS% source
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. epub3 to make an epub3
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
echo. dummy to check syntax errors of document sources
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 1>NUL 2>NUL
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Home-Assistant.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Home-Assistant.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "epub3" (
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
if "%1" == "dummy" (
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
if errorlevel 1 exit /b 1
echo.
echo.Build finished. Dummy builder generates no files.
goto end
)
:end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

View File

@ -1,45 +0,0 @@
"""Sphinx extension for ReadTheDocs-style "Edit on GitHub" links on the sidebar.
Loosely based on https://github.com/astropy/astropy/pull/347
"""
import os
import warnings
__licence__ = "BSD (3 clause)"
def get_github_url(app, view, path):
"""Build the GitHub URL."""
return (
f"https://github.com/{app.config.edit_on_github_project}/"
f"{view}/{app.config.edit_on_github_branch}/"
f"{app.config.edit_on_github_src_path}{path}"
)
def html_page_context(app, pagename, templatename, context, doctree):
"""Build the HTML page."""
if templatename != "page.html":
return
if not app.config.edit_on_github_project:
warnings.warn("edit_on_github_project not specified")
return
if not doctree:
warnings.warn("doctree is None")
return
path = os.path.relpath(doctree.get("source"), app.builder.srcdir)
show_url = get_github_url(app, "blob", path)
edit_url = get_github_url(app, "edit", path)
context["show_on_github_url"] = show_url
context["edit_on_github_url"] = edit_url
def setup(app):
"""Set up the app."""
app.add_config_value("edit_on_github_project", "", True)
app.add_config_value("edit_on_github_branch", "master", True)
app.add_config_value("edit_on_github_src_path", "", True) # 'eg' "docs/"
app.connect("html-page-context", html_page_context)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,6 +0,0 @@
<ul>
<li><a href="https://home-assistant.io/">Homepage</a></li>
<li><a href="https://community.home-assistant.io">Community Forums</a></li>
<li><a href="https://github.com/home-assistant/core">GitHub</a></li>
<li><a href="https://discord.gg/c5DvZ4e">Discord</a></li>
</ul>

View File

@ -1,13 +0,0 @@
{%- if show_source and has_source and sourcename %}
<h3>{{ _('This Page') }}</h3>
<ul class="this-page-menu">
{%- if show_on_github_url %}
<li><a href="{{ show_on_github_url }}"
rel="nofollow">{{ _('Show on GitHub') }}</a></li>
{%- endif %}
{%- if edit_on_github_url %}
<li><a href="{{ edit_on_github_url }}"
rel="nofollow">{{ _('Edit on GitHub') }}</a></li>
{%- endif %}
</ul>
{%- endif %}

View File

@ -1,29 +0,0 @@
:mod:`homeassistant.auth`
=========================
.. automodule:: homeassistant.auth
:members:
homeassistant.auth.auth\_store
------------------------------
.. automodule:: homeassistant.auth.auth_store
:members:
:undoc-members:
:show-inheritance:
homeassistant.auth.const
------------------------
.. automodule:: homeassistant.auth.const
:members:
:undoc-members:
:show-inheritance:
homeassistant.auth.models
-------------------------
.. automodule:: homeassistant.auth.models
:members:
:undoc-members:
:show-inheritance:

View File

@ -1,7 +0,0 @@
.. _bootstrap_module:
:mod:`homeassistant.bootstrap`
------------------------------
.. automodule:: homeassistant.bootstrap
:members:

View File

@ -1,170 +0,0 @@
:mod:`homeassistant.components`
===============================
air\_quality
--------------------------------------------
.. automodule:: homeassistant.components.air_quality
:members:
:undoc-members:
:show-inheritance:
alarm\_control\_panel
--------------------------------------------
.. automodule:: homeassistant.components.alarm_control_panel
:members:
:undoc-members:
:show-inheritance:
binary\_sensor
--------------------------------------------
.. automodule:: homeassistant.components.binary_sensor
:members:
:undoc-members:
:show-inheritance:
camera
---------------------------
.. automodule:: homeassistant.components.camera
:members:
:undoc-members:
:show-inheritance:
calendar
---------------------------
.. automodule:: homeassistant.components.calendar
:members:
:undoc-members:
:show-inheritance:
climate
---------------------------
.. automodule:: homeassistant.components.climate
:members:
:undoc-members:
:show-inheritance:
conversation
---------------------------
.. automodule:: homeassistant.components.conversation
:members:
:undoc-members:
:show-inheritance:
cover
---------------------------
.. automodule:: homeassistant.components.cover
:members:
:undoc-members:
:show-inheritance:
device\_tracker
---------------------------
.. automodule:: homeassistant.components.device_tracker
:members:
:undoc-members:
:show-inheritance:
fan
---------------------------
.. automodule:: homeassistant.components.fan
:members:
:undoc-members:
:show-inheritance:
light
---------------------------
.. automodule:: homeassistant.components.light
:members:
:undoc-members:
:show-inheritance:
lock
---------------------------
.. automodule:: homeassistant.components.lock
:members:
:undoc-members:
:show-inheritance:
media\_player
---------------------------
.. automodule:: homeassistant.components.media_player
:members:
:undoc-members:
:show-inheritance:
notify
---------------------------
.. automodule:: homeassistant.components.notify
:members:
:undoc-members:
:show-inheritance:
remote
---------------------------
.. automodule:: homeassistant.components.remote
:members:
:undoc-members:
:show-inheritance:
switch
---------------------------
.. automodule:: homeassistant.components.switch
:members:
:undoc-members:
:show-inheritance:
sensor
-------------------------------------
.. automodule:: homeassistant.components.sensor
:members:
:undoc-members:
:show-inheritance:
vacuum
-------------------------------------
.. automodule:: homeassistant.components.vacuum
:members:
:undoc-members:
:show-inheritance:
water\_heater
-------------------------------------
.. automodule:: homeassistant.components.water_heater
:members:
:undoc-members:
:show-inheritance:
weather
---------------------------
.. automodule:: homeassistant.components.weather
:members:
:undoc-members:
:show-inheritance:
webhook
---------------------------
.. automodule:: homeassistant.components.webhook
:members:
:undoc-members:
:show-inheritance:

View File

@ -1,7 +0,0 @@
.. _config_entries_module:
:mod:`homeassistant.config_entries`
-----------------------------------
.. automodule:: homeassistant.config_entries
:members:

View File

@ -1,7 +0,0 @@
.. _core_module:
:mod:`homeassistant.core`
-------------------------
.. automodule:: homeassistant.core
:members:

View File

@ -1,7 +0,0 @@
.. _data_entry_flow_module:
:mod:`homeassistant.data_entry_flow`
-----------------------------
.. automodule:: homeassistant.data_entry_flow
:members:

View File

@ -1,7 +0,0 @@
.. _exceptions_module:
:mod:`homeassistant.exceptions`
-------------------------------
.. automodule:: homeassistant.exceptions
:members:

View File

@ -1,335 +0,0 @@
:mod:`homeassistant.helpers`
============================
.. automodule:: homeassistant.helpers
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.aiohttp\_client
-------------------------------------
.. automodule:: homeassistant.helpers.aiohttp_client
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.area\_registry
------------------------------------
.. automodule:: homeassistant.helpers.area_registry
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.check\_config
-----------------------------------
.. automodule:: homeassistant.helpers.check_config
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.collection
--------------------------------
.. automodule:: homeassistant.helpers.collection
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.condition
-------------------------------
.. automodule:: homeassistant.helpers.condition
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.config\_entry\_flow
-----------------------------------------
.. automodule:: homeassistant.helpers.config_entry_flow
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.config\_entry\_oauth2\_flow
-------------------------------------------------
.. automodule:: homeassistant.helpers.config_entry_oauth2_flow
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.config\_validation
----------------------------------------
.. automodule:: homeassistant.helpers.config_validation
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.data\_entry\_flow
---------------------------------------
.. automodule:: homeassistant.helpers.data_entry_flow
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.debounce
------------------------------
.. automodule:: homeassistant.helpers.debounce
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.deprecation
---------------------------------
.. automodule:: homeassistant.helpers.deprecation
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.device\_registry
--------------------------------------
.. automodule:: homeassistant.helpers.device_registry
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.discovery
-------------------------------
.. automodule:: homeassistant.helpers.discovery
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.dispatcher
--------------------------------
.. automodule:: homeassistant.helpers.dispatcher
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity
----------------------------
.. automodule:: homeassistant.helpers.entity
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity\_component
---------------------------------------
.. automodule:: homeassistant.helpers.entity_component
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity\_platform
--------------------------------------
.. automodule:: homeassistant.helpers.entity_platform
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity\_registry
--------------------------------------
.. automodule:: homeassistant.helpers.entity_registry
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity\_values
------------------------------------
.. automodule:: homeassistant.helpers.entity_values
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entityfilter
----------------------------------
.. automodule:: homeassistant.helpers.entityfilter
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.event
---------------------------
.. automodule:: homeassistant.helpers.event
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.icon
--------------------------
.. automodule:: homeassistant.helpers.icon
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.integration\_platform
-------------------------------------------
.. automodule:: homeassistant.helpers.integration_platform
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.intent
----------------------------
.. automodule:: homeassistant.helpers.intent
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.json
--------------------------
.. automodule:: homeassistant.helpers.json
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.location
------------------------------
.. automodule:: homeassistant.helpers.location
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.logging
-----------------------------
.. automodule:: homeassistant.helpers.logging
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.network
-----------------------------
.. automodule:: homeassistant.helpers.network
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.restore\_state
------------------------------------
.. automodule:: homeassistant.helpers.restore_state
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.script
----------------------------
.. automodule:: homeassistant.helpers.script
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.service
-----------------------------
.. automodule:: homeassistant.helpers.service
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.signal
-----------------------------
.. automodule:: homeassistant.helpers.signal
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.state
---------------------------
.. automodule:: homeassistant.helpers.state
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.storage
-----------------------------
.. automodule:: homeassistant.helpers.storage
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.sun
-------------------------
.. automodule:: homeassistant.helpers.sun
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.system\_info
----------------------------------
.. automodule:: homeassistant.helpers.system_info
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.temperature
---------------------------------
.. automodule:: homeassistant.helpers.temperature
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.template
------------------------------
.. automodule:: homeassistant.helpers.template
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.translation
---------------------------------
.. automodule:: homeassistant.helpers.translation
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.typing
----------------------------
.. automodule:: homeassistant.helpers.typing
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.update\_coordinator
-----------------------------------------
.. automodule:: homeassistant.helpers.update_coordinator
:members:
:undoc-members:
:show-inheritance:

View File

@ -1,7 +0,0 @@
.. _loader_module:
:mod:`homeassistant.loader`
---------------------------
.. automodule:: homeassistant.loader
:members:

View File

@ -1,151 +0,0 @@
:mod:`homeassistant.util`
=========================
.. automodule:: homeassistant.util
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.yaml
-----------------------
.. automodule:: homeassistant.util.yaml
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.aiohttp
--------------------------
.. automodule:: homeassistant.util.aiohttp
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.async\_
--------------------------
.. automodule:: homeassistant.util.async_
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.color
------------------------
.. automodule:: homeassistant.util.color
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.decorator
----------------------------
.. automodule:: homeassistant.util.decorator
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.distance
---------------------------
.. automodule:: homeassistant.util.distance
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.dt
---------------------
.. automodule:: homeassistant.util.dt
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.json
-----------------------
.. automodule:: homeassistant.util.json
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.location
---------------------------
.. automodule:: homeassistant.util.location
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.logging
--------------------------
.. automodule:: homeassistant.util.logging
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.network
--------------------------
.. automodule:: homeassistant.util.network
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.package
--------------------------
.. automodule:: homeassistant.util.package
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.pil
----------------------
.. automodule:: homeassistant.util.pil
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.pressure
---------------------------
.. automodule:: homeassistant.util.pressure
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.ssl
----------------------
.. automodule:: homeassistant.util.ssl
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.temperature
------------------------------
.. automodule:: homeassistant.util.temperature
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.unit\_system
-------------------------------
.. automodule:: homeassistant.util.unit_system
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.volume
-------------------------
.. automodule:: homeassistant.util.volume
:members:
:undoc-members:
:show-inheritance:

View File

@ -1,438 +0,0 @@
#!/usr/bin/env python3
"""Home Assistant documentation build configuration file.
This file is execfile()d with the current directory set to its
containing dir.
Note that not all possible configuration values are present in this
autogenerated file.
All configuration values have a default; values that are commented out
serve to show the default.
If extensions (or modules to document with autodoc) are in another directory,
add these directories to sys.path here. If the directory is relative to the
documentation root, use os.path.abspath to make it absolute, like shown here.
"""
import inspect
import os
import sys
from homeassistant.const import __short_version__, __version__
PROJECT_NAME = "Home Assistant"
PROJECT_PACKAGE_NAME = "homeassistant"
PROJECT_AUTHOR = "The Home Assistant Authors"
PROJECT_COPYRIGHT = PROJECT_AUTHOR
PROJECT_LONG_DESCRIPTION = (
"Home Assistant is an open-source "
"home automation platform running on Python 3. "
"Track and control all devices at home and "
"automate control. "
"Installation in less than a minute."
)
PROJECT_GITHUB_USERNAME = "home-assistant"
PROJECT_GITHUB_REPOSITORY = "home-assistant"
GITHUB_PATH = f"{PROJECT_GITHUB_USERNAME}/{PROJECT_GITHUB_REPOSITORY}"
GITHUB_URL = f"https://github.com/{GITHUB_PATH}"
sys.path.insert(0, os.path.abspath("_ext"))
sys.path.insert(0, os.path.abspath("../homeassistant"))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.linkcode",
"sphinx_autodoc_annotation",
"edit_on_github",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = PROJECT_NAME
copyright = PROJECT_COPYRIGHT
author = PROJECT_AUTHOR
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __short_version__
# The full version, including alpha/beta/rc tags.
release = __version__
code_branch = "dev" if "dev" in __version__ else "master"
# Edit on Github config
edit_on_github_project = GITHUB_PATH
edit_on_github_branch = code_branch
edit_on_github_src_path = "docs/source/"
def linkcode_resolve(domain, info):
"""Determine the URL corresponding to Python object."""
if domain != "py":
return None
modname = info["module"]
fullname = info["fullname"]
submod = sys.modules.get(modname)
if submod is None:
return None
obj = submod
for part in fullname.split("."):
try:
obj = getattr(obj, part)
except Exception: # pylint: disable=broad-except
return None
try:
fn = inspect.getsourcefile(obj)
except Exception: # pylint: disable=broad-except
fn = None
if not fn:
return None
try:
source, lineno = inspect.findsource(obj)
except Exception: # pylint: disable=broad-except
lineno = None
if lineno:
linespec = "#L%d" % (lineno + 1)
else:
linespec = ""
index = fn.find("/homeassistant/")
if index == -1:
index = 0
fn = fn[index:]
return f"{GITHUB_URL}/blob/{code_branch}/{fn}{linespec}"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
"logo": "logo.png",
"logo_name": PROJECT_NAME,
"description": PROJECT_LONG_DESCRIPTION,
"github_user": PROJECT_GITHUB_USERNAME,
"github_repo": PROJECT_GITHUB_REPOSITORY,
"github_type": "star",
"github_banner": True,
"touch_icon": "logo-apple.png",
# 'fixed_sidebar': True, # Re-enable when we have more content
}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = 'Home-Assistant v0.27.0'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = '_static/logo.png'
# The name of an image file (relative to this directory) to use as a favicon of
# the docs.
# This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
html_favicon = "_static/favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
html_last_updated_fmt = "%b %d, %Y"
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
html_sidebars = {
"**": [
"about.html",
"links.html",
"searchbox.html",
"sourcelink.html",
"navigation.html",
"relations.html",
]
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
# html_domain_indices = True
# If false, no index is generated.
#
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = "Home-Assistantdoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
"home-assistant.tex",
"Home Assistant Documentation",
"Home Assistant Team",
"manual",
)
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# It false, will not define \strong, \code, itleref, \crossref ... but only
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
# packages.
#
# latex_keep_old_macro_names = True
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, "home-assistant", "Home Assistant Documentation", [author], 1)
]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"Home-Assistant",
"Home Assistant Documentation",
author,
"Home Assistant",
"Open-source home automation platform.",
"Miscellaneous",
)
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False

View File

@ -1,22 +0,0 @@
================================
Home Assistant API Documentation
================================
Public API documentation for `Home Assistant developers`_.
Contents:
.. toctree::
:maxdepth: 2
:glob:
api/*
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _Home Assistant developers: https://developers.home-assistant.io/

View File

@ -93,7 +93,9 @@ def get_arguments() -> argparse.Namespace:
help="Directory that contains the Home Assistant configuration", help="Directory that contains the Home Assistant configuration",
) )
parser.add_argument( parser.add_argument(
"--safe-mode", action="store_true", help="Start Home Assistant in safe mode" "--recovery-mode",
action="store_true",
help="Start Home Assistant in recovery mode",
) )
parser.add_argument( parser.add_argument(
"--debug", action="store_true", help="Start Home Assistant in debug mode" "--debug", action="store_true", help="Start Home Assistant in debug mode"
@ -183,7 +185,9 @@ def main() -> int:
ensure_config_path(config_dir) ensure_config_path(config_dir)
# pylint: disable-next=import-outside-toplevel # pylint: disable-next=import-outside-toplevel
from . import runner from . import config, runner
safe_mode = config.safe_mode_enabled(config_dir)
runtime_conf = runner.RuntimeConfig( runtime_conf = runner.RuntimeConfig(
config_dir=config_dir, config_dir=config_dir,
@ -193,9 +197,10 @@ def main() -> int:
log_no_color=args.log_no_color, log_no_color=args.log_no_color,
skip_pip=args.skip_pip, skip_pip=args.skip_pip,
skip_pip_packages=args.skip_pip_packages, skip_pip_packages=args.skip_pip_packages,
safe_mode=args.safe_mode, recovery_mode=args.recovery_mode,
debug=args.debug, debug=args.debug,
open_ui=args.open_ui, open_ui=args.open_ui,
safe_mode=safe_mode,
) )
fault_file_name = os.path.join(config_dir, FAULT_LOG_FILENAME) fault_file_name = os.path.join(config_dir, FAULT_LOG_FILENAME)

View File

@ -5,6 +5,7 @@ import asyncio
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Mapping from collections.abc import Mapping
from datetime import timedelta from datetime import timedelta
import time
from typing import Any, cast from typing import Any, cast
import jwt import jwt
@ -12,7 +13,6 @@ import jwt
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.util import dt as dt_util
from . import auth_store, jwt_wrapper, models from . import auth_store, jwt_wrapper, models
from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN
@ -505,12 +505,13 @@ class AuthManager:
self._store.async_log_refresh_token_usage(refresh_token, remote_ip) self._store.async_log_refresh_token_usage(refresh_token, remote_ip)
now = dt_util.utcnow() now = int(time.time())
expire_seconds = int(refresh_token.access_token_expiration.total_seconds())
return jwt.encode( return jwt.encode(
{ {
"iss": refresh_token.id, "iss": refresh_token.id,
"iat": now, "iat": now,
"exp": now + refresh_token.access_token_expiration, "exp": now + expire_seconds,
}, },
refresh_token.jwt_key, refresh_token.jwt_key,
algorithm="HS256", algorithm="HS256",

View File

@ -50,7 +50,7 @@ class MultiFactorAuthModule:
Default is same as type Default is same as type
""" """
return self.config.get(CONF_ID, self.type) return self.config.get(CONF_ID, self.type) # type: ignore[no-any-return]
@property @property
def type(self) -> str: def type(self) -> str:
@ -60,7 +60,7 @@ class MultiFactorAuthModule:
@property @property
def name(self) -> str: def name(self) -> str:
"""Return the name of the auth module.""" """Return the name of the auth module."""
return self.config.get(CONF_NAME, self.DEFAULT_TITLE) return self.config.get(CONF_NAME, self.DEFAULT_TITLE) # type: ignore[no-any-return]
# Implement by extending class # Implement by extending class

View File

@ -1,32 +1,25 @@
"""Permission constants for the websocket API. """Permission for events."""
Separate file to avoid circular imports.
"""
from __future__ import annotations from __future__ import annotations
from typing import Final from typing import Final
from homeassistant.components.frontend import EVENT_PANELS_UPDATED
from homeassistant.components.lovelace import EVENT_LOVELACE_UPDATED
from homeassistant.components.persistent_notification import (
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED,
)
from homeassistant.components.recorder import (
EVENT_RECORDER_5MIN_STATISTICS_GENERATED,
EVENT_RECORDER_HOURLY_STATISTICS_GENERATED,
)
from homeassistant.components.shopping_list import EVENT_SHOPPING_LIST_UPDATED
from homeassistant.const import ( from homeassistant.const import (
EVENT_COMPONENT_LOADED, EVENT_COMPONENT_LOADED,
EVENT_CORE_CONFIG_UPDATE, EVENT_CORE_CONFIG_UPDATE,
EVENT_LOVELACE_UPDATED,
EVENT_PANELS_UPDATED,
EVENT_RECORDER_5MIN_STATISTICS_GENERATED,
EVENT_RECORDER_HOURLY_STATISTICS_GENERATED,
EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REGISTERED,
EVENT_SERVICE_REMOVED, EVENT_SERVICE_REMOVED,
EVENT_SHOPPING_LIST_UPDATED,
EVENT_STATE_CHANGED, EVENT_STATE_CHANGED,
EVENT_THEMES_UPDATED, EVENT_THEMES_UPDATED,
) )
from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
from homeassistant.helpers.issue_registry import EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED
# These are events that do not contain any sensitive data # These are events that do not contain any sensitive data
# Except for state_changed, which is handled accordingly. # Except for state_changed, which is handled accordingly.
@ -36,9 +29,9 @@ SUBSCRIBE_ALLOWLIST: Final[set[str]] = {
EVENT_CORE_CONFIG_UPDATE, EVENT_CORE_CONFIG_UPDATE,
EVENT_DEVICE_REGISTRY_UPDATED, EVENT_DEVICE_REGISTRY_UPDATED,
EVENT_ENTITY_REGISTRY_UPDATED, EVENT_ENTITY_REGISTRY_UPDATED,
EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED,
EVENT_LOVELACE_UPDATED, EVENT_LOVELACE_UPDATED,
EVENT_PANELS_UPDATED, EVENT_PANELS_UPDATED,
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED,
EVENT_RECORDER_5MIN_STATISTICS_GENERATED, EVENT_RECORDER_5MIN_STATISTICS_GENERATED,
EVENT_RECORDER_HOURLY_STATISTICS_GENERATED, EVENT_RECORDER_HOURLY_STATISTICS_GENERATED,
EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REGISTERED,

View File

@ -109,4 +109,4 @@ def test_all(policy: CategoryType, key: str) -> bool:
if not isinstance(all_policy, dict): if not isinstance(all_policy, dict):
return bool(all_policy) return bool(all_policy)
return all_policy.get(key, False) return all_policy.get(key, False) # type: ignore[no-any-return]

View File

@ -67,7 +67,7 @@ class AuthProvider:
@property @property
def name(self) -> str: def name(self) -> str:
"""Return the name of the auth provider.""" """Return the name of the auth provider."""
return self.config.get(CONF_NAME, self.DEFAULT_TITLE) return self.config.get(CONF_NAME, self.DEFAULT_TITLE) # type: ignore[no-any-return]
@property @property
def support_mfa(self) -> bool: def support_mfa(self) -> bool:

View File

@ -0,0 +1,279 @@
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see https://opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
Python software and documentation are licensed under the
Python Software Foundation License Version 2.
Starting with Python 3.8.6, examples, recipes, and other code in
the documentation are dual licensed under the PSF License Version 2
and the Zero-Clause BSD license.
Some software incorporated into Python is under different licenses.
The licenses are listed with code falling under that license.
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
----------------------------------------------------------------------
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@ -0,0 +1,5 @@
This package contains backports of Python functionality from future Python
versions.
Some of the backports have been copied directly from the CPython project,
and are subject to license agreement as detailed in LICENSE.Python.

View File

@ -1,4 +1,14 @@
"""Functools backports from standard lib.""" """Functools backports from standard lib."""
# This file contains parts of Python's module wrapper
# for the _functools C module
# to allow utilities written in Python to be added
# to the functools module.
# Written by Nick Coghlan <ncoghlan at gmail.com>,
# Raymond Hettinger <python at rcn.com>,
# and Łukasz Langa <lukasz at langa.pl>.
# Copyright © 2001-2023 Python Software Foundation; All Rights Reserved
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
@ -68,4 +78,4 @@ class cached_property(Generic[_T]):
raise TypeError(msg) from None raise TypeError(msg) from None
return val return val
__class_getitem__ = classmethod(GenericAlias) __class_getitem__ = classmethod(GenericAlias) # type: ignore[var-annotated]

View File

@ -120,6 +120,7 @@ async def async_setup_hass(
runtime_config.log_no_color, runtime_config.log_no_color,
) )
hass.config.safe_mode = runtime_config.safe_mode
hass.config.skip_pip = runtime_config.skip_pip hass.config.skip_pip = runtime_config.skip_pip
hass.config.skip_pip_packages = runtime_config.skip_pip_packages hass.config.skip_pip_packages = runtime_config.skip_pip_packages
if runtime_config.skip_pip or runtime_config.skip_pip_packages: if runtime_config.skip_pip or runtime_config.skip_pip_packages:
@ -137,14 +138,14 @@ async def async_setup_hass(
config_dict = None config_dict = None
basic_setup_success = False basic_setup_success = False
if not (safe_mode := runtime_config.safe_mode): if not (recovery_mode := runtime_config.recovery_mode):
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass) await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
try: try:
config_dict = await conf_util.async_hass_config_yaml(hass) config_dict = await conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err: except HomeAssistantError as err:
_LOGGER.error( _LOGGER.error(
"Failed to parse configuration.yaml: %s. Activating safe mode", "Failed to parse configuration.yaml: %s. Activating recovery mode",
err, err,
) )
else: else:
@ -156,24 +157,24 @@ async def async_setup_hass(
) )
if config_dict is None: if config_dict is None:
safe_mode = True recovery_mode = True
elif not basic_setup_success: elif not basic_setup_success:
_LOGGER.warning("Unable to set up core integrations. Activating safe mode") _LOGGER.warning("Unable to set up core integrations. Activating recovery mode")
safe_mode = True recovery_mode = True
elif ( elif (
"frontend" in hass.data.get(DATA_SETUP, {}) "frontend" in hass.data.get(DATA_SETUP, {})
and "frontend" not in hass.config.components and "frontend" not in hass.config.components
): ):
_LOGGER.warning("Detected that frontend did not load. Activating safe mode") _LOGGER.warning("Detected that frontend did not load. Activating recovery mode")
# Ask integrations to shut down. It's messy but we can't # Ask integrations to shut down. It's messy but we can't
# do a clean stop without knowing what is broken # do a clean stop without knowing what is broken
with contextlib.suppress(asyncio.TimeoutError): with contextlib.suppress(asyncio.TimeoutError):
async with hass.timeout.async_timeout(10): async with hass.timeout.async_timeout(10):
await hass.async_stop() await hass.async_stop()
safe_mode = True recovery_mode = True
old_config = hass.config old_config = hass.config
old_logging = hass.data.get(DATA_LOGGING) old_logging = hass.data.get(DATA_LOGGING)
@ -187,16 +188,18 @@ async def async_setup_hass(
# Setup loader cache after the config dir has been set # Setup loader cache after the config dir has been set
loader.async_setup(hass) loader.async_setup(hass)
if safe_mode: if recovery_mode:
_LOGGER.info("Starting in safe mode") _LOGGER.info("Starting in recovery mode")
hass.config.safe_mode = True hass.config.recovery_mode = True
http_conf = (await http.async_get_last_config(hass)) or {} http_conf = (await http.async_get_last_config(hass)) or {}
await async_from_config_dict( await async_from_config_dict(
{"safe_mode": {}, "http": http_conf}, {"recovery_mode": {}, "http": http_conf},
hass, hass,
) )
elif hass.config.safe_mode:
_LOGGER.info("Starting in safe mode")
if runtime_config.open_ui: if runtime_config.open_ui:
hass.add_job(open_hass_ui, hass) hass.add_job(open_hass_ui, hass)
@ -471,7 +474,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
domains = {key.partition(" ")[0] for key in config if key != core.DOMAIN} domains = {key.partition(" ")[0] for key in config if key != core.DOMAIN}
# Add config entry domains # Add config entry domains
if not hass.config.safe_mode: if not hass.config.recovery_mode:
domains.update(hass.config_entries.async_domains()) domains.update(hass.config_entries.async_domains())
# Make sure the Hass.io component is loaded # Make sure the Hass.io component is loaded

View File

@ -11,6 +11,7 @@
"google_maps", "google_maps",
"google_pubsub", "google_pubsub",
"google_sheets", "google_sheets",
"google_tasks",
"google_translate", "google_translate",
"google_travel_time", "google_travel_time",
"google_wifi", "google_wifi",

View File

@ -1,6 +1,7 @@
"""Support for the Abode Security System.""" """Support for the Abode Security System."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field
from functools import partial from functools import partial
from jaraco.abode.automation import Automation as AbodeAuto from jaraco.abode.automation import Automation as AbodeAuto
@ -25,7 +26,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
Platform, Platform,
) )
from homeassistant.core import Event, HomeAssistant, ServiceCall from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, entity from homeassistant.helpers import config_validation as cv, entity
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
@ -71,15 +72,14 @@ PLATFORMS = [
] ]
@dataclass
class AbodeSystem: class AbodeSystem:
"""Abode System class.""" """Abode System class."""
def __init__(self, abode: Abode, polling: bool) -> None: abode: Abode
"""Initialize the system.""" polling: bool
self.abode = abode entity_ids: set[str | None] = field(default_factory=set)
self.polling = polling logout_listener: CALLBACK_TYPE | None = None
self.entity_ids: set[str | None] = set()
self.logout_listener = None
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -8,5 +8,5 @@
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["accuweather"], "loggers": ["accuweather"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["accuweather==1.0.0"] "requirements": ["accuweather==2.0.0"]
} }

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/adax", "documentation": "https://www.home-assistant.io/integrations/adax",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["adax", "adax_local"], "loggers": ["adax", "adax_local"],
"requirements": ["adax==0.2.0", "Adax-local==0.1.5"] "requirements": ["adax==0.3.0", "Adax-local==0.1.5"]
} }

View File

@ -7,5 +7,5 @@
"integration_type": "service", "integration_type": "service",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["adguardhome"], "loggers": ["adguardhome"],
"requirements": ["adguardhome==0.6.1"] "requirements": ["adguardhome==0.6.2"]
} }

View File

@ -127,7 +127,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
"""Return the current target temperature.""" """Return the current target temperature."""
# If the system is in MyZone mode, and a zone is set, return that temperature instead. # If the system is in MyZone mode, and a zone is set, return that temperature instead.
if ( if (
self._ac["myZone"] > 0 self._myzone
and not self._ac.get(ADVANTAGE_AIR_MYAUTO_ENABLED) and not self._ac.get(ADVANTAGE_AIR_MYAUTO_ENABLED)
and not self._ac.get(ADVANTAGE_AIR_MYTEMP_ENABLED) and not self._ac.get(ADVANTAGE_AIR_MYTEMP_ENABLED)
): ):

View File

@ -63,7 +63,7 @@ class AdvantageAirAcEntity(AdvantageAirEntity):
return self.coordinator.data["aircons"][self.ac_key]["info"] return self.coordinator.data["aircons"][self.ac_key]["info"]
@property @property
def _myzone(self) -> dict[str, Any]: def _myzone(self) -> dict[str, Any] | None:
return self.coordinator.data["aircons"][self.ac_key]["zones"].get( return self.coordinator.data["aircons"][self.ac_key]["zones"].get(
f"z{self._ac['myZone']:02}" f"z{self._ac['myZone']:02}"
) )

View File

@ -9,8 +9,9 @@ ATTR_API_CAT_DESCRIPTION = "Name"
ATTR_API_O3 = "O3" ATTR_API_O3 = "O3"
ATTR_API_PM25 = "PM2.5" ATTR_API_PM25 = "PM2.5"
ATTR_API_POLLUTANT = "Pollutant" ATTR_API_POLLUTANT = "Pollutant"
ATTR_API_REPORT_DATE = "HourObserved" ATTR_API_REPORT_DATE = "DateObserved"
ATTR_API_REPORT_HOUR = "DateObserved" ATTR_API_REPORT_HOUR = "HourObserved"
ATTR_API_REPORT_TZ = "LocalTimeZone"
ATTR_API_STATE = "StateCode" ATTR_API_STATE = "StateCode"
ATTR_API_STATION = "ReportingArea" ATTR_API_STATION = "ReportingArea"
ATTR_API_STATION_LATITUDE = "Latitude" ATTR_API_STATION_LATITUDE = "Latitude"

View File

@ -20,6 +20,7 @@ from .const import (
ATTR_API_POLLUTANT, ATTR_API_POLLUTANT,
ATTR_API_REPORT_DATE, ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR, ATTR_API_REPORT_HOUR,
ATTR_API_REPORT_TZ,
ATTR_API_STATE, ATTR_API_STATE,
ATTR_API_STATION, ATTR_API_STATION,
ATTR_API_STATION_LATITUDE, ATTR_API_STATION_LATITUDE,
@ -83,6 +84,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator):
# Copy Report Details # Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE] data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR] data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
# Copy Station Details # Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE] data[ATTR_API_STATE] = obv[ATTR_API_STATE]

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime
from typing import Any from typing import Any
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
@ -13,6 +14,7 @@ from homeassistant.components.sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_TIME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
) )
@ -21,6 +23,7 @@ from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import get_time_zone
from . import AirNowDataUpdateCoordinator from . import AirNowDataUpdateCoordinator
from .const import ( from .const import (
@ -29,6 +32,9 @@ from .const import (
ATTR_API_AQI_LEVEL, ATTR_API_AQI_LEVEL,
ATTR_API_O3, ATTR_API_O3,
ATTR_API_PM25, ATTR_API_PM25,
ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR,
ATTR_API_REPORT_TZ,
ATTR_API_STATION, ATTR_API_STATION,
ATTR_API_STATION_LATITUDE, ATTR_API_STATION_LATITUDE,
ATTR_API_STATION_LONGITUDE, ATTR_API_STATION_LONGITUDE,
@ -78,6 +84,12 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
extra_state_attributes_fn=lambda data: { extra_state_attributes_fn=lambda data: {
ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION], ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION],
ATTR_LEVEL: data[ATTR_API_AQI_LEVEL], ATTR_LEVEL: data[ATTR_API_AQI_LEVEL],
ATTR_TIME: datetime.strptime(
f"{data[ATTR_API_REPORT_DATE]} {data[ATTR_API_REPORT_HOUR]}",
"%Y-%m-%d %H",
)
.replace(tzinfo=get_time_zone(data[ATTR_API_REPORT_TZ]))
.isoformat(),
}, },
), ),
AirNowEntityDescription( AirNowEntityDescription(

View File

@ -226,6 +226,14 @@ class AirthingsSensor(
model=airthings_device.model, model=airthings_device.model,
) )
@property
def available(self) -> bool:
"""Check if device and sensor is available in data."""
return (
super().available
and self.entity_description.key in self.coordinator.data.sensors
)
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the value reported by the sensor.""" """Return the value reported by the sensor."""

View File

@ -5,7 +5,7 @@
"user": { "user": {
"description": "[%key:component::bluetooth::config::step::user::description%]", "description": "[%key:component::bluetooth::config::step::user::description%]",
"data": { "data": {
"address": "[%key:component::bluetooth::config::step::user::data::address%]" "address": "[%key:common::config_flow::data::device%]"
} }
}, },
"bluetooth_confirm": { "bluetooth_confirm": {

View File

@ -421,7 +421,6 @@ class AirVisualEntity(CoordinatorEntity):
self._entry = entry self._entry = entry
self.entity_description = description self.entity_description = description
# pylint: disable-next=hass-missing-super-call
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Register callbacks.""" """Register callbacks."""
await super().async_added_to_hass() await super().async_added_to_hass()

View File

@ -7,15 +7,21 @@ from aioairzone_cloud.common import OperationAction, OperationMode, TemperatureU
from aioairzone_cloud.const import ( from aioairzone_cloud.const import (
API_MODE, API_MODE,
API_OPTS, API_OPTS,
API_PARAMS,
API_POWER, API_POWER,
API_SETPOINT, API_SETPOINT,
API_UNITS, API_UNITS,
API_VALUE, API_VALUE,
AZD_ACTION, AZD_ACTION,
AZD_AIDOOS,
AZD_GROUPS,
AZD_HUMIDITY, AZD_HUMIDITY,
AZD_INSTALLATIONS,
AZD_MASTER, AZD_MASTER,
AZD_MODE, AZD_MODE,
AZD_MODES, AZD_MODES,
AZD_NUM_DEVICES,
AZD_NUM_GROUPS,
AZD_POWER, AZD_POWER,
AZD_TEMP, AZD_TEMP,
AZD_TEMP_SET, AZD_TEMP_SET,
@ -39,7 +45,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator from .coordinator import AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity from .entity import (
AirzoneAidooEntity,
AirzoneEntity,
AirzoneGroupEntity,
AirzoneInstallationEntity,
AirzoneZoneEntity,
)
HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = { HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = {
OperationAction.COOLING: HVACAction.COOLING, OperationAction.COOLING: HVACAction.COOLING,
@ -82,6 +94,38 @@ async def async_setup_entry(
entities: list[AirzoneClimate] = [] entities: list[AirzoneClimate] = []
# Aidoos
for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items():
entities.append(
AirzoneAidooClimate(
coordinator,
aidoo_id,
aidoo_data,
)
)
# Groups
for group_id, group_data in coordinator.data.get(AZD_GROUPS, {}).items():
if group_data[AZD_NUM_DEVICES] > 1:
entities.append(
AirzoneGroupClimate(
coordinator,
group_id,
group_data,
)
)
# Installations
for inst_id, inst_data in coordinator.data.get(AZD_INSTALLATIONS, {}).items():
if inst_data[AZD_NUM_GROUPS] > 1:
entities.append(
AirzoneInstallationClimate(
coordinator,
inst_id,
inst_data,
)
)
# Zones # Zones
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items(): for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items():
entities.append( entities.append(
@ -98,9 +142,39 @@ async def async_setup_entry(
class AirzoneClimate(AirzoneEntity, ClimateEntity): class AirzoneClimate(AirzoneEntity, ClimateEntity):
"""Define an Airzone Cloud climate.""" """Define an Airzone Cloud climate."""
_attr_has_entity_name = True
_attr_name = None
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_temperature_unit = UnitOfTemperature.CELSIUS
@callback
def _handle_coordinator_update(self) -> None:
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
@callback
def _async_update_attrs(self) -> None:
"""Update climate attributes."""
self._attr_current_temperature = self.get_airzone_value(AZD_TEMP)
self._attr_current_humidity = self.get_airzone_value(AZD_HUMIDITY)
self._attr_hvac_action = HVAC_ACTION_LIB_TO_HASS[
self.get_airzone_value(AZD_ACTION)
]
if self.get_airzone_value(AZD_POWER):
self._attr_hvac_mode = HVAC_MODE_LIB_TO_HASS[
self.get_airzone_value(AZD_MODE)
]
else:
self._attr_hvac_mode = HVACMode.OFF
self._attr_max_temp = self.get_airzone_value(AZD_TEMP_SET_MAX)
self._attr_min_temp = self.get_airzone_value(AZD_TEMP_SET_MIN)
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)
class AirzoneDeviceClimate(AirzoneClimate):
"""Define an Airzone Cloud Device base class."""
async def async_turn_on(self) -> None: async def async_turn_on(self) -> None:
"""Turn the entity on.""" """Turn the entity on."""
params = { params = {
@ -131,36 +205,143 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
} }
await self._async_update_params(params) await self._async_update_params(params)
@callback
def _handle_coordinator_update(self) -> None:
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
@callback class AirzoneDeviceGroupClimate(AirzoneClimate):
def _async_update_attrs(self) -> None: """Define an Airzone Cloud DeviceGroup base class."""
"""Update climate attributes."""
self._attr_current_temperature = self.get_airzone_value(AZD_TEMP) async def async_turn_on(self) -> None:
self._attr_current_humidity = self.get_airzone_value(AZD_HUMIDITY) """Turn the entity on."""
self._attr_hvac_action = HVAC_ACTION_LIB_TO_HASS[ params = {
self.get_airzone_value(AZD_ACTION) API_PARAMS: {
] API_POWER: True,
if self.get_airzone_value(AZD_POWER): },
self._attr_hvac_mode = HVAC_MODE_LIB_TO_HASS[ }
self.get_airzone_value(AZD_MODE) await self._async_update_params(params)
]
async def async_turn_off(self) -> None:
"""Turn the entity off."""
params = {
API_PARAMS: {
API_POWER: False,
},
}
await self._async_update_params(params)
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
params: dict[str, Any] = {}
if ATTR_TEMPERATURE in kwargs:
params[API_PARAMS] = {
API_SETPOINT: kwargs[ATTR_TEMPERATURE],
}
params[API_OPTS] = {
API_UNITS: TemperatureUnit.CELSIUS.value,
}
await self._async_update_params(params)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode."""
params: dict[str, Any] = {
API_PARAMS: {},
}
if hvac_mode == HVACMode.OFF:
params[API_PARAMS][API_POWER] = False
else: else:
self._attr_hvac_mode = HVACMode.OFF mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
self._attr_max_temp = self.get_airzone_value(AZD_TEMP_SET_MAX) params[API_PARAMS][API_MODE] = mode.value
self._attr_min_temp = self.get_airzone_value(AZD_TEMP_SET_MIN) params[API_PARAMS][API_POWER] = True
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET) await self._async_update_params(params)
class AirzoneZoneClimate(AirzoneZoneEntity, AirzoneClimate): class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneDeviceClimate):
"""Define an Airzone Cloud Aidoo climate."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
aidoo_id: str,
aidoo_data: dict,
) -> None:
"""Initialize Airzone Cloud Aidoo climate."""
super().__init__(coordinator, aidoo_id, aidoo_data)
self._attr_unique_id = aidoo_id
self._attr_target_temperature_step = self.get_airzone_value(AZD_TEMP_STEP)
self._attr_hvac_modes = [
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
]
if HVACMode.OFF not in self._attr_hvac_modes:
self._attr_hvac_modes += [HVACMode.OFF]
self._async_update_attrs()
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode."""
params: dict[str, Any] = {}
if hvac_mode == HVACMode.OFF:
params[API_POWER] = {
API_VALUE: False,
}
else:
mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
params[API_MODE] = {
API_VALUE: mode.value,
}
params[API_POWER] = {
API_VALUE: True,
}
await self._async_update_params(params)
class AirzoneGroupClimate(AirzoneGroupEntity, AirzoneDeviceGroupClimate):
"""Define an Airzone Cloud Group climate."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
group_id: str,
group_data: dict,
) -> None:
"""Initialize Airzone Cloud Group climate."""
super().__init__(coordinator, group_id, group_data)
self._attr_unique_id = group_id
self._attr_target_temperature_step = self.get_airzone_value(AZD_TEMP_STEP)
self._attr_hvac_modes = [
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
]
if HVACMode.OFF not in self._attr_hvac_modes:
self._attr_hvac_modes += [HVACMode.OFF]
self._async_update_attrs()
class AirzoneInstallationClimate(AirzoneInstallationEntity, AirzoneDeviceGroupClimate):
"""Define an Airzone Cloud Installation climate."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
inst_id: str,
inst_data: dict,
) -> None:
"""Initialize Airzone Cloud Installation climate."""
super().__init__(coordinator, inst_id, inst_data)
self._attr_unique_id = inst_id
self._attr_target_temperature_step = self.get_airzone_value(AZD_TEMP_STEP)
self._attr_hvac_modes = [
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
]
if HVACMode.OFF not in self._attr_hvac_modes:
self._attr_hvac_modes += [HVACMode.OFF]
self._async_update_attrs()
class AirzoneZoneClimate(AirzoneZoneEntity, AirzoneDeviceClimate):
"""Define an Airzone Cloud Zone climate.""" """Define an Airzone Cloud Zone climate."""
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
@ -191,7 +372,8 @@ class AirzoneZoneClimate(AirzoneZoneEntity, AirzoneClimate):
} }
else: else:
mode = HVAC_MODE_HASS_TO_LIB[hvac_mode] mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
if mode != self.get_airzone_value(AZD_MODE): cur_mode = self.get_airzone_value(AZD_MODE)
if hvac_mode != HVAC_MODE_LIB_TO_HASS[cur_mode]:
if self.get_airzone_value(AZD_MASTER): if self.get_airzone_value(AZD_MASTER):
params[API_MODE] = { params[API_MODE] = {
API_VALUE: mode.value, API_VALUE: mode.value,

View File

@ -9,6 +9,8 @@ from aioairzone_cloud.const import (
AZD_AIDOOS, AZD_AIDOOS,
AZD_AVAILABLE, AZD_AVAILABLE,
AZD_FIRMWARE, AZD_FIRMWARE,
AZD_GROUPS,
AZD_INSTALLATIONS,
AZD_NAME, AZD_NAME,
AZD_SYSTEM_ID, AZD_SYSTEM_ID,
AZD_SYSTEMS, AZD_SYSTEMS,
@ -74,6 +76,104 @@ class AirzoneAidooEntity(AirzoneEntity):
value = aidoo.get(key) value = aidoo.get(key)
return value return value
async def _async_update_params(self, params: dict[str, Any]) -> None:
"""Send Aidoo parameters to Cloud API."""
_LOGGER.debug("aidoo=%s: update_params=%s", self.name, params)
try:
await self.coordinator.airzone.api_set_aidoo_id_params(
self.aidoo_id, params
)
except AirzoneCloudError as error:
raise HomeAssistantError(
f"Failed to set {self.name} params: {error}"
) from error
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
class AirzoneGroupEntity(AirzoneEntity):
"""Define an Airzone Cloud Group entity."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
group_id: str,
group_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator)
self.group_id = group_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, group_id)},
manufacturer=MANUFACTURER,
name=group_data[AZD_NAME],
)
def get_airzone_value(self, key: str) -> Any:
"""Return Group value by key."""
value = None
if group := self.coordinator.data[AZD_GROUPS].get(self.group_id):
value = group.get(key)
return value
async def _async_update_params(self, params: dict[str, Any]) -> None:
"""Send Group parameters to Cloud API."""
_LOGGER.debug("group=%s: update_params=%s", self.name, params)
try:
await self.coordinator.airzone.api_set_group_id_params(
self.group_id, params
)
except AirzoneCloudError as error:
raise HomeAssistantError(
f"Failed to set {self.name} params: {error}"
) from error
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
class AirzoneInstallationEntity(AirzoneEntity):
"""Define an Airzone Cloud Installation entity."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
inst_id: str,
inst_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator)
self.inst_id = inst_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, inst_id)},
manufacturer=MANUFACTURER,
name=inst_data[AZD_NAME],
)
def get_airzone_value(self, key: str) -> Any:
"""Return Installation value by key."""
value = None
if inst := self.coordinator.data[AZD_INSTALLATIONS].get(self.inst_id):
value = inst.get(key)
return value
async def _async_update_params(self, params: dict[str, Any]) -> None:
"""Send Installation parameters to Cloud API."""
_LOGGER.debug("installation=%s: update_params=%s", self.name, params)
try:
await self.coordinator.airzone.api_set_installation_id_params(
self.inst_id, params
)
except AirzoneCloudError as error:
raise HomeAssistantError(
f"Failed to set {self.name} params: {error}"
) from error
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
class AirzoneSystemEntity(AirzoneEntity): class AirzoneSystemEntity(AirzoneEntity):
"""Define an Airzone Cloud System entity.""" """Define an Airzone Cloud System entity."""

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud", "documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aioairzone_cloud"], "loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.2.3"] "requirements": ["aioairzone-cloud==0.3.1"]
} }

View File

@ -580,8 +580,8 @@ class AlexaBrightnessController(AlexaCapability):
"""Read and return a property.""" """Read and return a property."""
if name != "brightness": if name != "brightness":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
if "brightness" in self.entity.attributes: if brightness := self.entity.attributes.get("brightness"):
return round(self.entity.attributes["brightness"] / 255.0 * 100) return round(brightness / 255.0 * 100)
return 0 return 0
@ -630,12 +630,16 @@ class AlexaColorController(AlexaCapability):
if name != "color": if name != "color":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
hue, saturation = self.entity.attributes.get(light.ATTR_HS_COLOR, (0, 0)) hue_saturation: tuple[float, float] | None
if (hue_saturation := self.entity.attributes.get(light.ATTR_HS_COLOR)) is None:
hue_saturation = (0, 0)
if (brightness := self.entity.attributes.get(light.ATTR_BRIGHTNESS)) is None:
brightness = 0
return { return {
"hue": hue, "hue": hue_saturation[0],
"saturation": saturation / 100.0, "saturation": hue_saturation[1] / 100.0,
"brightness": self.entity.attributes.get(light.ATTR_BRIGHTNESS, 0) / 255.0, "brightness": brightness / 255.0,
} }
@ -683,10 +687,8 @@ class AlexaColorTemperatureController(AlexaCapability):
"""Read and return a property.""" """Read and return a property."""
if name != "colorTemperatureInKelvin": if name != "colorTemperatureInKelvin":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
if "color_temp" in self.entity.attributes: if color_temp := self.entity.attributes.get("color_temp"):
return color_util.color_temperature_mired_to_kelvin( return color_util.color_temperature_mired_to_kelvin(color_temp)
self.entity.attributes["color_temp"]
)
return None return None

View File

@ -5,7 +5,6 @@ from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_NAME,
CONF_PASSWORD, CONF_PASSWORD,
CONF_USERNAME, CONF_USERNAME,
HTTP_BASIC_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
@ -39,14 +38,7 @@ class IPWebcamCamera(MjpegCamera):
def __init__(self, coordinator: AndroidIPCamDataUpdateCoordinator) -> None: def __init__(self, coordinator: AndroidIPCamDataUpdateCoordinator) -> None:
"""Initialize the camera.""" """Initialize the camera."""
name = None
# keep imported name until YAML is removed
if CONF_NAME in coordinator.config_entry.data:
name = coordinator.config_entry.data[CONF_NAME]
self._attr_has_entity_name = False
super().__init__( super().__init__(
name=name,
mjpeg_url=coordinator.cam.mjpeg_url, mjpeg_url=coordinator.cam.mjpeg_url,
still_image_url=coordinator.cam.image_url, still_image_url=coordinator.cam.image_url,
authentication=HTTP_BASIC_AUTHENTICATION, authentication=HTTP_BASIC_AUTHENTICATION,
@ -56,5 +48,5 @@ class IPWebcamCamera(MjpegCamera):
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-camera" self._attr_unique_id = f"{coordinator.config_entry.entry_id}-camera"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
name=name or coordinator.config_entry.data[CONF_HOST], name=coordinator.config_entry.data[CONF_HOST],
) )

View File

@ -19,11 +19,6 @@ class AndroidIPCamBaseEntity(CoordinatorEntity[AndroidIPCamDataUpdateCoordinator
) -> None: ) -> None:
"""Initialize the base entity.""" """Initialize the base entity."""
super().__init__(coordinator) super().__init__(coordinator)
if CONF_NAME in coordinator.config_entry.data:
# name is legacy imported from YAML config
# this block can be removed when removing import from YAML
self._attr_name = f"{coordinator.config_entry.data[CONF_NAME]} {self.entity_description.name}"
self._attr_has_entity_name = False
self.cam = coordinator.cam self.cam = coordinator.cam
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, identifiers={(DOMAIN, coordinator.config_entry.entry_id)},

View File

@ -9,7 +9,7 @@
"loggers": ["adb_shell", "androidtv", "pure_python_adb"], "loggers": ["adb_shell", "androidtv", "pure_python_adb"],
"requirements": [ "requirements": [
"adb-shell[async]==0.4.4", "adb-shell[async]==0.4.4",
"androidtv[async]==0.0.72", "androidtv[async]==0.0.73",
"pure-python-adb[async]==0.3.0.dev0" "pure-python-adb[async]==0.3.0.dev0"
] ]
} }

View File

@ -1,8 +1,8 @@
"""Support for Apache Kafka.""" """Support for Apache Kafka."""
from datetime import datetime from datetime import datetime
import json import json
import sys
from aiokafka import AIOKafkaProducer
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
@ -16,11 +16,16 @@ from homeassistant.const import (
STATE_UNKNOWN, STATE_UNKNOWN,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import ssl as ssl_util from homeassistant.util import ssl as ssl_util
if sys.version_info < (3, 12):
from aiokafka import AIOKafkaProducer
DOMAIN = "apache_kafka" DOMAIN = "apache_kafka"
CONF_FILTER = "filter" CONF_FILTER = "filter"
@ -49,6 +54,10 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Activate the Apache Kafka integration.""" """Activate the Apache Kafka integration."""
if sys.version_info >= (3, 12):
raise HomeAssistantError(
"Apache Kafka is not supported on Python 3.12. Please use Python 3.11."
)
conf = config[DOMAIN] conf = config[DOMAIN]
kafka = hass.data[DOMAIN] = KafkaManager( kafka = hass.data[DOMAIN] = KafkaManager(

View File

@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv", "documentation": "https://www.home-assistant.io/integrations/apple_tv",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["pyatv", "srptools"], "loggers": ["pyatv", "srptools"],
"requirements": ["pyatv==0.13.4"], "requirements": ["pyatv==0.14.3"],
"zeroconf": [ "zeroconf": [
"_mediaremotetv._tcp.local.", "_mediaremotetv._tcp.local.",
"_companion-link._tcp.local.", "_companion-link._tcp.local.",

View File

@ -371,11 +371,15 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
@property @property
def repeat(self) -> RepeatMode | None: def repeat(self) -> RepeatMode | None:
"""Return current repeat mode.""" """Return current repeat mode."""
if self._playing and self._is_feature_available(FeatureName.Repeat): if (
self._playing
and self._is_feature_available(FeatureName.Repeat)
and (repeat := self._playing.repeat)
):
return { return {
RepeatState.Track: RepeatMode.ONE, RepeatState.Track: RepeatMode.ONE,
RepeatState.All: RepeatMode.ALL, RepeatState.All: RepeatMode.ALL,
}.get(self._playing.repeat, RepeatMode.OFF) }.get(repeat, RepeatMode.OFF)
return None return None
@property @property

View File

@ -21,6 +21,15 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
COMMAND_TO_ATTRIBUTE = {
"wakeup": ("power", "turn_on"),
"suspend": ("power", "turn_off"),
"turn_on": ("power", "turn_on"),
"turn_off": ("power", "turn_off"),
"volume_up": ("audio", "volume_up"),
"volume_down": ("audio", "volume_down"),
"home_hold": ("remote_control", "home"),
}
async def async_setup_entry( async def async_setup_entry(
@ -61,7 +70,13 @@ class AppleTVRemote(AppleTVEntity, RemoteEntity):
for _ in range(num_repeats): for _ in range(num_repeats):
for single_command in command: for single_command in command:
attr_value = getattr(self.atv.remote_control, single_command, None) attr_value = None
if attributes := COMMAND_TO_ATTRIBUTE.get(single_command):
attr_value = self.atv
for attr_name in attributes:
attr_value = getattr(attr_value, attr_name, None)
if not attr_value:
attr_value = getattr(self.atv.remote_control, single_command, None)
if not attr_value: if not attr_value:
raise ValueError("Command not found. Exiting sequence") raise ValueError("Command not found. Exiting sequence")

View File

@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/apprise", "documentation": "https://www.home-assistant.io/integrations/apprise",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["apprise"], "loggers": ["apprise"],
"requirements": ["apprise==1.5.0"] "requirements": ["apprise==1.6.0"]
} }

View File

@ -13,11 +13,11 @@
"connectable": false "connectable": false
} }
], ],
"codeowners": ["@aschmitz"], "codeowners": ["@aschmitz", "@thecode"],
"config_flow": true, "config_flow": true,
"dependencies": ["bluetooth_adapters"], "dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/aranet", "documentation": "https://www.home-assistant.io/integrations/aranet",
"integration_type": "device", "integration_type": "device",
"iot_class": "local_push", "iot_class": "local_push",
"requirements": ["aranet4==2.1.3"] "requirements": ["aranet4==2.2.2"]
} }

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from aranet4.client import Aranet4Advertisement from aranet4.client import Aranet4Advertisement
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@ -32,6 +33,7 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
@ -121,22 +123,22 @@ def sensor_update_to_bluetooth_data_update(
adv: Aranet4Advertisement, adv: Aranet4Advertisement,
) -> PassiveBluetoothDataUpdate: ) -> PassiveBluetoothDataUpdate:
"""Convert a sensor update to a Bluetooth data update.""" """Convert a sensor update to a Bluetooth data update."""
data: dict[PassiveBluetoothEntityKey, Any] = {}
names: dict[PassiveBluetoothEntityKey, str | None] = {}
descs: dict[PassiveBluetoothEntityKey, EntityDescription] = {}
for key, desc in SENSOR_DESCRIPTIONS.items():
tag = _device_key_to_bluetooth_entity_key(adv.device, key)
val = getattr(adv.readings, key)
if val == -1:
continue
data[tag] = val
names[tag] = desc.name
descs[tag] = desc
return PassiveBluetoothDataUpdate( return PassiveBluetoothDataUpdate(
devices={adv.device.address: _sensor_device_info_to_hass(adv)}, devices={adv.device.address: _sensor_device_info_to_hass(adv)},
entity_descriptions={ entity_descriptions=descs,
_device_key_to_bluetooth_entity_key(adv.device, key): desc entity_data=data,
for key, desc in SENSOR_DESCRIPTIONS.items() entity_names=names,
},
entity_data={
_device_key_to_bluetooth_entity_key(adv.device, key): getattr(
adv.readings, key, None
)
for key in SENSOR_DESCRIPTIONS
},
entity_names={
_device_key_to_bluetooth_entity_key(adv.device, key): desc.name
for key, desc in SENSOR_DESCRIPTIONS.items()
},
) )

View File

@ -4,7 +4,7 @@
"user": { "user": {
"description": "[%key:component::bluetooth::config::step::user::description%]", "description": "[%key:component::bluetooth::config::step::user::description%]",
"data": { "data": {
"address": "[%key:component::bluetooth::config::step::user::data::address%]" "address": "[%key:common::config_flow::data::device%]"
} }
}, },
"bluetooth_confirm": { "bluetooth_confirm": {

View File

@ -60,7 +60,6 @@ class VariableSensorEntity(AsekoEntity, SensorEntity):
self._attr_icon = { self._attr_icon = {
"clf": "mdi:flask", "clf": "mdi:flask",
"ph": "mdi:ph",
"rx": "mdi:test-tube", "rx": "mdi:test-tube",
"waterLevel": "mdi:waves", "waterLevel": "mdi:waves",
"waterTemp": "mdi:coolant-temperature", "waterTemp": "mdi:coolant-temperature",
@ -69,6 +68,7 @@ class VariableSensorEntity(AsekoEntity, SensorEntity):
self._attr_device_class = { self._attr_device_class = {
"airTemp": SensorDeviceClass.TEMPERATURE, "airTemp": SensorDeviceClass.TEMPERATURE,
"waterTemp": SensorDeviceClass.TEMPERATURE, "waterTemp": SensorDeviceClass.TEMPERATURE,
"ph": SensorDeviceClass.PH,
}.get(self._variable.type) }.get(self._variable.type)
@property @property

View File

@ -9,7 +9,7 @@ from homeassistant.components import stt
from homeassistant.core import Context, HomeAssistant from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import CONF_DEBUG_RECORDING_DIR, DATA_CONFIG, DOMAIN from .const import CONF_DEBUG_RECORDING_DIR, DATA_CONFIG, DATA_LAST_WAKE_UP, DOMAIN
from .error import PipelineNotFound from .error import PipelineNotFound
from .pipeline import ( from .pipeline import (
AudioSettings, AudioSettings,
@ -58,6 +58,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Assist pipeline integration.""" """Set up the Assist pipeline integration."""
hass.data[DATA_CONFIG] = config.get(DOMAIN, {}) hass.data[DATA_CONFIG] = config.get(DOMAIN, {})
# wake_word_id -> timestamp of last detection (monotonic_ns)
hass.data[DATA_LAST_WAKE_UP] = {}
await async_setup_pipeline_store(hass) await async_setup_pipeline_store(hass)
async_register_websocket_api(hass) async_register_websocket_api(hass)

View File

@ -12,11 +12,13 @@ from pathlib import Path
from queue import Queue from queue import Queue
from threading import Thread from threading import Thread
import time import time
from typing import Any, Final, cast from typing import TYPE_CHECKING, Any, Final, cast
import wave import wave
import voluptuous as vol import voluptuous as vol
from webrtc_noise_gain import AudioProcessor
if TYPE_CHECKING:
from webrtc_noise_gain import AudioProcessor
from homeassistant.components import ( from homeassistant.components import (
conversation, conversation,
@ -522,6 +524,12 @@ class PipelineRun:
# Initialize with audio settings # Initialize with audio settings
self.audio_processor_buffer = AudioBuffer(AUDIO_PROCESSOR_BYTES) self.audio_processor_buffer = AudioBuffer(AUDIO_PROCESSOR_BYTES)
if self.audio_settings.needs_processor: if self.audio_settings.needs_processor:
# Delay import of webrtc so HA start up is not crashing
# on older architectures (armhf).
#
# pylint: disable=import-outside-toplevel
from webrtc_noise_gain import AudioProcessor
self.audio_processor = AudioProcessor( self.audio_processor = AudioProcessor(
self.audio_settings.auto_gain_dbfs, self.audio_settings.auto_gain_dbfs,
self.audio_settings.noise_suppression_level, self.audio_settings.noise_suppression_level,
@ -681,7 +689,8 @@ class PipelineRun:
wake_word_output: dict[str, Any] = {} wake_word_output: dict[str, Any] = {}
else: else:
# Avoid duplicate detections by checking cooldown # Avoid duplicate detections by checking cooldown
last_wake_up = self.hass.data.get(DATA_LAST_WAKE_UP) wake_up_key = f"{self.wake_word_entity_id}.{result.wake_word_id}"
last_wake_up = self.hass.data[DATA_LAST_WAKE_UP].get(wake_up_key)
if last_wake_up is not None: if last_wake_up is not None:
sec_since_last_wake_up = time.monotonic() - last_wake_up sec_since_last_wake_up = time.monotonic() - last_wake_up
if sec_since_last_wake_up < wake_word_settings.cooldown_seconds: if sec_since_last_wake_up < wake_word_settings.cooldown_seconds:
@ -689,7 +698,7 @@ class PipelineRun:
raise WakeWordDetectionAborted raise WakeWordDetectionAborted
# Record last wake up time to block duplicate detections # Record last wake up time to block duplicate detections
self.hass.data[DATA_LAST_WAKE_UP] = time.monotonic() self.hass.data[DATA_LAST_WAKE_UP][wake_up_key] = time.monotonic()
if result.queued_audio: if result.queued_audio:
# Add audio that was pending at detection. # Add audio that was pending at detection.

View File

@ -7,8 +7,6 @@ from dataclasses import dataclass
from enum import StrEnum from enum import StrEnum
from typing import Final, cast from typing import Final, cast
from webrtc_noise_gain import AudioProcessor
_SAMPLE_RATE: Final = 16000 # Hz _SAMPLE_RATE: Final = 16000 # Hz
_SAMPLE_WIDTH: Final = 2 # bytes _SAMPLE_WIDTH: Final = 2 # bytes
@ -51,6 +49,12 @@ class WebRtcVad(VoiceActivityDetector):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize webrtcvad.""" """Initialize webrtcvad."""
# Delay import of webrtc so HA start up is not crashing
# on older architectures (armhf).
#
# pylint: disable=import-outside-toplevel
from webrtc_noise_gain import AudioProcessor
# Just VAD: no noise suppression or auto gain # Just VAD: no noise suppression or auto gain
self._audio_processor = AudioProcessor(0, 0) self._audio_processor = AudioProcessor(0, 0)

View File

@ -10,6 +10,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_ASUSWRT, DOMAIN from .const import DATA_ASUSWRT, DOMAIN
from .router import AsusWrtDevInfo, AsusWrtRouter from .router import AsusWrtDevInfo, AsusWrtRouter
ATTR_LAST_TIME_REACHABLE = "last_time_reachable"
DEFAULT_DEVICE_NAME = "Unknown device" DEFAULT_DEVICE_NAME = "Unknown device"
@ -52,13 +54,14 @@ def add_entities(
class AsusWrtDevice(ScannerEntity): class AsusWrtDevice(ScannerEntity):
"""Representation of a AsusWrt device.""" """Representation of a AsusWrt device."""
_unrecorded_attributes = frozenset({ATTR_LAST_TIME_REACHABLE})
_attr_should_poll = False _attr_should_poll = False
def __init__(self, router: AsusWrtRouter, device: AsusWrtDevInfo) -> None: def __init__(self, router: AsusWrtRouter, device: AsusWrtDevInfo) -> None:
"""Initialize a AsusWrt device.""" """Initialize a AsusWrt device."""
self._router = router self._router = router
self._device = device self._device = device
self._attr_unique_id = device.mac
self._attr_name = device.name or DEFAULT_DEVICE_NAME self._attr_name = device.name or DEFAULT_DEVICE_NAME
@property @property
@ -98,7 +101,7 @@ class AsusWrtDevice(ScannerEntity):
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
if self._device.last_activity: if self._device.last_activity:
self._attr_extra_state_attributes[ self._attr_extra_state_attributes[
"last_time_reachable" ATTR_LAST_TIME_REACHABLE
] = self._device.last_activity.isoformat(timespec="seconds") ] = self._device.last_activity.isoformat(timespec="seconds")
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -25,13 +25,14 @@ from homeassistant.exceptions import (
ConfigEntryNotReady, ConfigEntryNotReady,
HomeAssistantError, HomeAssistantError,
) )
from homeassistant.helpers import aiohttp_client, device_registry as dr, discovery_flow from homeassistant.helpers import device_registry as dr, discovery_flow
from .activity import ActivityStream from .activity import ActivityStream
from .const import CONF_BRAND, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS from .const import CONF_BRAND, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS
from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .exceptions import CannotConnect, InvalidAuth, RequireValidation
from .gateway import AugustGateway from .gateway import AugustGateway
from .subscriber import AugustSubscriberMixin from .subscriber import AugustSubscriberMixin
from .util import async_create_august_clientsession
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -46,10 +47,7 @@ YALEXS_BLE_DOMAIN = "yalexs_ble"
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up August from a config entry.""" """Set up August from a config entry."""
# Create an aiohttp session instead of using the default one since the session = async_create_august_clientsession(hass)
# default one is likely to trigger august's WAF if another integration
# is also using Cloudflare
session = aiohttp_client.async_create_clientsession(hass)
august_gateway = AugustGateway(hass, session) august_gateway = AugustGateway(hass, session)
try: try:

View File

@ -13,7 +13,6 @@ from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client
from .const import ( from .const import (
CONF_ACCESS_TOKEN_CACHE_FILE, CONF_ACCESS_TOKEN_CACHE_FILE,
@ -26,6 +25,7 @@ from .const import (
) )
from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .exceptions import CannotConnect, InvalidAuth, RequireValidation
from .gateway import AugustGateway from .gateway import AugustGateway
from .util import async_create_august_clientsession
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -159,10 +159,7 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Set up the gateway.""" """Set up the gateway."""
if self._august_gateway is not None: if self._august_gateway is not None:
return self._august_gateway return self._august_gateway
# Create an aiohttp session instead of using the default one since the self._aiohttp_session = async_create_august_clientsession(self.hass)
# default one is likely to trigger august's WAF if another integration
# is also using Cloudflare
self._aiohttp_session = aiohttp_client.async_create_clientsession(self.hass)
self._august_gateway = AugustGateway(self.hass, self._aiohttp_session) self._august_gateway = AugustGateway(self.hass, self._aiohttp_session)
return self._august_gateway return self._august_gateway

View File

@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august", "documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"], "loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.3.0"] "requirements": ["yalexs==1.10.0", "yalexs-ble==2.3.1"]
} }

View File

@ -12,6 +12,7 @@ from yalexs.keypad import KeypadDetail
from yalexs.lock import Lock, LockDetail from yalexs.lock import Lock, LockDetail
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
@ -27,7 +28,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from . import AugustData from . import AugustData
from .const import ( from .const import (
@ -174,8 +174,7 @@ async def _async_migrate_old_unique_ids(hass, devices):
registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id) registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id)
# pylint: disable-next=hass-invalid-inheritance # needs fixing class AugustOperatorSensor(AugustEntityMixin, RestoreSensor):
class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, SensorEntity):
"""Representation of an August lock operation sensor.""" """Representation of an August lock operation sensor."""
_attr_translation_key = "operator" _attr_translation_key = "operator"
@ -247,10 +246,15 @@ class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, SensorEntity):
await super().async_added_to_hass() await super().async_added_to_hass()
last_state = await self.async_get_last_state() last_state = await self.async_get_last_state()
if not last_state or last_state.state == STATE_UNAVAILABLE: last_sensor_state = await self.async_get_last_sensor_data()
if (
not last_state
or not last_sensor_state
or last_state.state == STATE_UNAVAILABLE
):
return return
self._attr_native_value = last_state.state self._attr_native_value = last_sensor_state.native_value
if ATTR_ENTITY_PICTURE in last_state.attributes: if ATTR_ENTITY_PICTURE in last_state.attributes:
self._attr_entity_picture = last_state.attributes[ATTR_ENTITY_PICTURE] self._attr_entity_picture = last_state.attributes[ATTR_ENTITY_PICTURE]
if ATTR_OPERATION_REMOTE in last_state.attributes: if ATTR_OPERATION_REMOTE in last_state.attributes:

View File

@ -0,0 +1,24 @@
"""August util functions."""
import socket
import aiohttp
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import aiohttp_client
@callback
def async_create_august_clientsession(hass: HomeAssistant) -> aiohttp.ClientSession:
"""Create an aiohttp session for the august integration."""
# Create an aiohttp session instead of using the default one since the
# default one is likely to trigger august's WAF if another integration
# is also using Cloudflare
#
# The family is set to AF_INET because IPv6 keeps coming up as an issue
# see https://github.com/home-assistant/core/issues/97146
#
# When https://github.com/aio-libs/aiohttp/issues/4451 is implemented
# we can allow IPv6 again
#
return aiohttp_client.async_create_clientsession(hass, family=socket.AF_INET)

View File

@ -733,14 +733,14 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
self.async_write_ha_state() self.async_write_ha_state()
def _log_callback(self, level: int, msg: str, **kwargs: Any) -> None:
"""Log helper callback."""
self._logger.log(level, "%s %s", msg, self.name, **kwargs)
async def _async_attach_triggers( async def _async_attach_triggers(
self, home_assistant_start: bool self, home_assistant_start: bool
) -> Callable[[], None] | None: ) -> Callable[[], None] | None:
"""Set up the triggers.""" """Set up the triggers."""
def log_cb(level: int, msg: str, **kwargs: Any) -> None:
self._logger.log(level, "%s %s", msg, self.name, **kwargs)
this = None this = None
self.async_write_ha_state() self.async_write_ha_state()
if state := self.hass.states.get(self.entity_id): if state := self.hass.states.get(self.entity_id):
@ -763,7 +763,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
self.async_trigger, self.async_trigger,
DOMAIN, DOMAIN,
str(self.name), str(self.name),
log_cb, self._log_callback,
home_assistant_start, home_assistant_start,
variables, variables,
) )

View File

@ -42,7 +42,8 @@ class AxisEntity(Entity):
self.device = device self.device = device
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(AXIS_DOMAIN, device.unique_id)} identifiers={(AXIS_DOMAIN, device.unique_id)},
serial_number=device.unique_id,
) )
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:

View File

@ -247,8 +247,8 @@
"presence": { "presence": {
"name": "Presence", "name": "Presence",
"state": { "state": {
"off": "[%key:component::device_tracker::entity_component::_::state::not_home%]", "off": "[%key:common::state::not_home%]",
"on": "[%key:component::device_tracker::entity_component::_::state::home%]" "on": "[%key:common::state::home%]"
} }
}, },
"problem": { "problem": {

View File

@ -8,14 +8,20 @@ from blebox_uniapi.feature import Feature
from blebox_uniapi.session import ApiHost from blebox_uniapi.session import ApiHost
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT
from .helpers import get_maybe_authenticated_session
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -36,12 +42,16 @@ _FeatureT = TypeVar("_FeatureT", bound=Feature)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up BleBox devices from a config entry.""" """Set up BleBox devices from a config entry."""
websession = async_get_clientsession(hass)
host = entry.data[CONF_HOST] host = entry.data[CONF_HOST]
port = entry.data[CONF_PORT] port = entry.data[CONF_PORT]
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
timeout = DEFAULT_SETUP_TIMEOUT timeout = DEFAULT_SETUP_TIMEOUT
websession = get_maybe_authenticated_session(hass, password, username)
api_host = ApiHost(host, port, timeout, websession, hass.loop) api_host = ApiHost(host, port, timeout, websession, hass.loop)
try: try:

View File

@ -5,16 +5,22 @@ import logging
from typing import Any from typing import Any
from blebox_uniapi.box import Box from blebox_uniapi.box import Box
from blebox_uniapi.error import Error, UnsupportedBoxResponse, UnsupportedBoxVersion from blebox_uniapi.error import (
Error,
UnauthorizedRequest,
UnsupportedBoxResponse,
UnsupportedBoxVersion,
)
from blebox_uniapi.session import ApiHost from blebox_uniapi.session import ApiHost
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import get_maybe_authenticated_session
from .const import ( from .const import (
ADDRESS_ALREADY_CONFIGURED, ADDRESS_ALREADY_CONFIGURED,
CANNOT_CONNECT, CANNOT_CONNECT,
@ -46,6 +52,8 @@ def create_schema(previous_input=None):
{ {
vol.Required(CONF_HOST, default=host): str, vol.Required(CONF_HOST, default=host): str,
vol.Required(CONF_PORT, default=port): int, vol.Required(CONF_PORT, default=port): int,
vol.Inclusive(CONF_USERNAME, "auth"): str,
vol.Inclusive(CONF_PASSWORD, "auth"): str,
} }
) )
@ -153,6 +161,9 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
addr = host_port(user_input) addr = host_port(user_input)
username = user_input.get(CONF_USERNAME)
password = user_input.get(CONF_PASSWORD)
for entry in self._async_current_entries(): for entry in self._async_current_entries():
if addr == host_port(entry.data): if addr == host_port(entry.data):
host, port = addr host, port = addr
@ -160,7 +171,9 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
reason=ADDRESS_ALREADY_CONFIGURED, reason=ADDRESS_ALREADY_CONFIGURED,
description_placeholders={"address": f"{host}:{port}"}, description_placeholders={"address": f"{host}:{port}"},
) )
websession = async_get_clientsession(hass)
websession = get_maybe_authenticated_session(hass, password, username)
api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER) api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER)
try: try:
product = await Box.async_from_host(api_host) product = await Box.async_from_host(api_host)
@ -169,6 +182,10 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.handle_step_exception( return self.handle_step_exception(
"user", ex, schema, *addr, UNSUPPORTED_VERSION, _LOGGER.debug "user", ex, schema, *addr, UNSUPPORTED_VERSION, _LOGGER.debug
) )
except UnauthorizedRequest as ex:
return self.handle_step_exception(
"user", ex, schema, *addr, CANNOT_CONNECT, _LOGGER.error
)
except Error as ex: except Error as ex:
return self.handle_step_exception( return self.handle_step_exception(

View File

@ -0,0 +1,21 @@
"""Blebox helpers."""
from __future__ import annotations
import aiohttp
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import (
async_create_clientsession,
async_get_clientsession,
)
def get_maybe_authenticated_session(
hass: HomeAssistant, password: str | None, username: str | None
) -> aiohttp.ClientSession:
"""Return proper session object."""
if username and password:
auth = aiohttp.BasicAuth(login=username, password=password)
return async_create_clientsession(hass, auth=auth)
return async_get_clientsession(hass)

View File

@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/blebox", "documentation": "https://www.home-assistant.io/integrations/blebox",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["blebox_uniapi"], "loggers": ["blebox_uniapi"],
"requirements": ["blebox-uniapi==2.1.4"], "requirements": ["blebox-uniapi==2.2.0"],
"zeroconf": ["_bbxsrv._tcp.local."] "zeroconf": ["_bbxsrv._tcp.local."]
} }

View File

@ -1,7 +1,9 @@
"""Support for Blink Home Camera System.""" """Support for Blink Home Camera System."""
import asyncio
from copy import deepcopy from copy import deepcopy
import logging import logging
from aiohttp import ClientError
from blinkpy.auth import Auth from blinkpy.auth import Auth
from blinkpy.blinkpy import Blink from blinkpy.blinkpy import Blink
import voluptuous as vol import voluptuous as vol
@ -16,8 +18,9 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ( from .const import (
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
@ -28,6 +31,7 @@ from .const import (
SERVICE_SAVE_VIDEO, SERVICE_SAVE_VIDEO,
SERVICE_SEND_PIN, SERVICE_SEND_PIN,
) )
from .coordinator import BlinkUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -40,23 +44,7 @@ SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema(
) )
def _blink_startup_wrapper(hass: HomeAssistant, entry: ConfigEntry) -> Blink: async def _reauth_flow_wrapper(hass, data):
"""Startup wrapper for blink."""
blink = Blink()
auth_data = deepcopy(dict(entry.data))
blink.auth = Auth(auth_data, no_prompt=True)
blink.refresh_rate = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
if blink.start():
blink.setup_post_verify()
elif blink.auth.check_key_required():
_LOGGER.debug("Attempting a reauth flow")
_reauth_flow_wrapper(hass, auth_data)
return blink
def _reauth_flow_wrapper(hass, data):
"""Reauth flow wrapper.""" """Reauth flow wrapper."""
hass.add_job( hass.add_job(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
@ -79,10 +67,10 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data = {**entry.data} data = {**entry.data}
if entry.version == 1: if entry.version == 1:
data.pop("login_response", None) data.pop("login_response", None)
await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) await _reauth_flow_wrapper(hass, data)
return False return False
if entry.version == 2: if entry.version == 2:
await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) await _reauth_flow_wrapper(hass, data)
return False return False
return True return True
@ -92,19 +80,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
_async_import_options_from_data_if_missing(hass, entry) _async_import_options_from_data_if_missing(hass, entry)
hass.data[DOMAIN][entry.entry_id] = await hass.async_add_executor_job( session = async_get_clientsession(hass)
_blink_startup_wrapper, hass, entry blink = Blink(session=session)
) auth_data = deepcopy(dict(entry.data))
blink.auth = Auth(auth_data, no_prompt=True, session=session)
blink.refresh_rate = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
coordinator = BlinkUpdateCoordinator(hass, blink)
if not hass.data[DOMAIN][entry.entry_id].available: try:
await blink.start()
except (ClientError, asyncio.TimeoutError) as ex:
raise ConfigEntryNotReady("Can not connect to host") from ex
if blink.auth.check_key_required():
_LOGGER.debug("Attempting a reauth flow")
raise ConfigEntryAuthFailed("Need 2FA for Blink")
if not blink.available:
raise ConfigEntryNotReady raise ConfigEntryNotReady
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener)) entry.async_on_unload(entry.add_update_listener(update_listener))
def blink_refresh(event_time=None): async def blink_refresh(event_time=None):
"""Call blink to refresh info.""" """Call blink to refresh info."""
hass.data[DOMAIN][entry.entry_id].refresh(force_cache=True) await coordinator.api.refresh(force_cache=True)
async def async_save_video(call): async def async_save_video(call):
"""Call save video service handler.""" """Call save video service handler."""
@ -114,11 +117,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Call save recent clips service handler.""" """Call save recent clips service handler."""
await async_handle_save_recent_clips_service(hass, entry, call) await async_handle_save_recent_clips_service(hass, entry, call)
def send_pin(call): async def send_pin(call):
"""Call blink to send new pin.""" """Call blink to send new pin."""
pin = call.data[CONF_PIN] pin = call.data[CONF_PIN]
hass.data[DOMAIN][entry.entry_id].auth.send_auth_key( await coordinator.api.auth.send_auth_key(
hass.data[DOMAIN][entry.entry_id], hass.data[DOMAIN][entry.entry_id].api,
pin, pin,
) )
@ -153,64 +156,53 @@ def _async_import_options_from_data_if_missing(
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Blink entry.""" """Unload Blink entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
return True
if not unload_ok: hass.services.async_remove(DOMAIN, SERVICE_REFRESH)
return False hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO)
hass.services.async_remove(DOMAIN, SERVICE_SEND_PIN)
hass.data[DOMAIN].pop(entry.entry_id) return unload_ok
if len(hass.data[DOMAIN]) != 0:
return True
hass.services.async_remove(DOMAIN, SERVICE_REFRESH)
hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO)
hass.services.async_remove(DOMAIN, SERVICE_SEND_PIN)
return True
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update.""" """Handle options update."""
blink: Blink = hass.data[DOMAIN][entry.entry_id] blink: Blink = hass.data[DOMAIN][entry.entry_id].api
blink.refresh_rate = entry.options[CONF_SCAN_INTERVAL] blink.refresh_rate = entry.options[CONF_SCAN_INTERVAL]
async def async_handle_save_video_service(hass, entry, call): async def async_handle_save_video_service(
hass: HomeAssistant, entry: ConfigEntry, call
) -> None:
"""Handle save video service calls.""" """Handle save video service calls."""
camera_name = call.data[CONF_NAME] camera_name = call.data[CONF_NAME]
video_path = call.data[CONF_FILENAME] video_path = call.data[CONF_FILENAME]
if not hass.config.is_allowed_path(video_path): if not hass.config.is_allowed_path(video_path):
_LOGGER.error("Can't write %s, no access to path!", video_path) _LOGGER.error("Can't write %s, no access to path!", video_path)
return return
all_cameras = hass.data[DOMAIN][entry.entry_id].api.cameras
def _write_video(name, file_path): if camera_name in all_cameras:
"""Call video write.""" try:
all_cameras = hass.data[DOMAIN][entry.entry_id].cameras await all_cameras[camera_name].video_to_file(video_path)
if name in all_cameras: except OSError as err:
all_cameras[name].video_to_file(file_path) _LOGGER.error("Can't write image to file: %s", err)
try:
await hass.async_add_executor_job(_write_video, camera_name, video_path)
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)
async def async_handle_save_recent_clips_service(hass, entry, call): async def async_handle_save_recent_clips_service(
hass: HomeAssistant, entry: ConfigEntry, call
) -> None:
"""Save multiple recent clips to output directory.""" """Save multiple recent clips to output directory."""
camera_name = call.data[CONF_NAME] camera_name = call.data[CONF_NAME]
clips_dir = call.data[CONF_FILE_PATH] clips_dir = call.data[CONF_FILE_PATH]
if not hass.config.is_allowed_path(clips_dir): if not hass.config.is_allowed_path(clips_dir):
_LOGGER.error("Can't write to directory %s, no access to path!", clips_dir) _LOGGER.error("Can't write to directory %s, no access to path!", clips_dir)
return return
all_cameras = hass.data[DOMAIN][entry.entry_id].api.cameras
def _save_recent_clips(name, output_dir): if camera_name in all_cameras:
"""Call save recent clips.""" try:
all_cameras = hass.data[DOMAIN][entry.entry_id].cameras await all_cameras[camera_name].save_recent_clips(output_dir=clips_dir)
if name in all_cameras: except OSError as err:
all_cameras[name].save_recent_clips(output_dir=output_dir) _LOGGER.error("Can't write recent clips to directory: %s", err)
try:
await hass.async_add_executor_job(_save_recent_clips, camera_name, clips_dir)
except OSError as err:
_LOGGER.error("Can't write recent clips to directory: %s", err)

View File

@ -1,8 +1,11 @@
"""Support for Blink Alarm Control Panel.""" """Support for Blink Alarm Control Panel."""
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
from blinkpy.blinkpy import Blink, BlinkSyncModule
from homeassistant.components.alarm_control_panel import ( from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity, AlarmControlPanelEntity,
AlarmControlPanelEntityFeature, AlarmControlPanelEntityFeature,
@ -13,11 +16,14 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN from .const import DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN
from .coordinator import BlinkUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,59 +34,75 @@ async def async_setup_entry(
hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the Blink Alarm Control Panels.""" """Set up the Blink Alarm Control Panels."""
data = hass.data[DOMAIN][config.entry_id] coordinator: BlinkUpdateCoordinator = hass.data[DOMAIN][config.entry_id]
sync_modules = [] sync_modules = []
for sync_name, sync_module in data.sync.items(): for sync_name, sync_module in coordinator.api.sync.items():
sync_modules.append(BlinkSyncModule(data, sync_name, sync_module)) sync_modules.append(BlinkSyncModuleHA(coordinator, sync_name, sync_module))
async_add_entities(sync_modules) async_add_entities(sync_modules)
class BlinkSyncModule(AlarmControlPanelEntity): class BlinkSyncModuleHA(
CoordinatorEntity[BlinkUpdateCoordinator], AlarmControlPanelEntity
):
"""Representation of a Blink Alarm Control Panel.""" """Representation of a Blink Alarm Control Panel."""
_attr_icon = ICON _attr_icon = ICON
_attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY _attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
_attr_name = None
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None
def __init__(self, data, name, sync): def __init__(
self, coordinator: BlinkUpdateCoordinator, name: str, sync: BlinkSyncModule
) -> None:
"""Initialize the alarm control panel.""" """Initialize the alarm control panel."""
self.data = data super().__init__(coordinator)
self.api: Blink = coordinator.api
self._coordinator = coordinator
self.sync = sync self.sync = sync
self._name = name self._attr_unique_id: str = sync.serial
self._attr_unique_id = sync.serial
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, sync.serial)}, identifiers={(DOMAIN, sync.serial)},
name=f"{DOMAIN} {name}", name=f"{DOMAIN} {name}",
manufacturer=DEFAULT_BRAND, manufacturer=DEFAULT_BRAND,
serial_number=sync.serial,
) )
self._update_attr()
def update(self) -> None: @callback
"""Update the state of the device.""" def _handle_coordinator_update(self) -> None:
if self.data.check_if_ok_to_update(): """Handle coordinator update."""
_LOGGER.debug( self._update_attr()
"Initiating a blink.refresh() from BlinkSyncModule('%s') (%s)", super()._handle_coordinator_update()
self._name,
self.data,
)
self.data.refresh()
_LOGGER.info("Updating State of Blink Alarm Control Panel '%s'", self._name)
self._attr_state = ( @callback
STATE_ALARM_ARMED_AWAY if self.sync.arm else STATE_ALARM_DISARMED def _update_attr(self) -> None:
) """Update attributes for alarm control panel."""
self.sync.attributes["network_info"] = self.data.networks self.sync.attributes["network_info"] = self.api.networks
self.sync.attributes["associated_cameras"] = list(self.sync.cameras) self.sync.attributes["associated_cameras"] = list(self.sync.cameras)
self.sync.attributes[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION self.sync.attributes[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION
self._attr_extra_state_attributes = self.sync.attributes self._attr_extra_state_attributes = self.sync.attributes
self._attr_state = (
STATE_ALARM_ARMED_AWAY if self.sync.arm else STATE_ALARM_DISARMED
)
def alarm_disarm(self, code: str | None = None) -> None: async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
self.sync.arm = False try:
self.sync.refresh() await self.sync.async_arm(False)
def alarm_arm_away(self, code: str | None = None) -> None: except asyncio.TimeoutError as er:
raise HomeAssistantError("Blink failed to disarm camera") from er
await self._coordinator.async_refresh()
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm command.""" """Send arm command."""
self.sync.arm = True try:
self.sync.refresh() await self.sync.async_arm(True)
except asyncio.TimeoutError as er:
raise HomeAssistantError("Blink failed to arm camera away") from er
await self._coordinator.async_refresh()
self.async_write_ha_state()

View File

@ -10,9 +10,10 @@ from homeassistant.components.binary_sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ( from .const import (
DEFAULT_BRAND, DEFAULT_BRAND,
@ -21,6 +22,7 @@ from .const import (
TYPE_CAMERA_ARMED, TYPE_CAMERA_ARMED,
TYPE_MOTION_DETECTED, TYPE_MOTION_DETECTED,
) )
from .coordinator import BlinkUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,45 +47,58 @@ async def async_setup_entry(
hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the blink binary sensors.""" """Set up the blink binary sensors."""
data = hass.data[DOMAIN][config.entry_id] coordinator: BlinkUpdateCoordinator = hass.data[DOMAIN][config.entry_id]
entities = [ entities = [
BlinkBinarySensor(data, camera, description) BlinkBinarySensor(coordinator, camera, description)
for camera in data.cameras for camera in coordinator.api.cameras
for description in BINARY_SENSORS_TYPES for description in BINARY_SENSORS_TYPES
] ]
async_add_entities(entities) async_add_entities(entities)
class BlinkBinarySensor(BinarySensorEntity): class BlinkBinarySensor(CoordinatorEntity[BlinkUpdateCoordinator], BinarySensorEntity):
"""Representation of a Blink binary sensor.""" """Representation of a Blink binary sensor."""
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__( def __init__(
self, data, camera, description: BinarySensorEntityDescription self,
coordinator: BlinkUpdateCoordinator,
camera,
description: BinarySensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
self.data = data super().__init__(coordinator)
self.entity_description = description self.entity_description = description
self._camera = data.cameras[camera] self._camera = coordinator.api.cameras[camera]
self._attr_unique_id = f"{self._camera.serial}-{description.key}" serial = self._camera.serial
self._attr_unique_id = f"{serial}-{description.key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._camera.serial)}, identifiers={(DOMAIN, serial)},
serial_number=serial,
name=camera, name=camera,
manufacturer=DEFAULT_BRAND, manufacturer=DEFAULT_BRAND,
model=self._camera.camera_type, model=self._camera.camera_type,
) )
self._update_attrs()
def update(self) -> None: @callback
"""Update sensor state.""" def _handle_coordinator_update(self) -> None:
state = self._camera.attributes[self.entity_description.key] """Handle update from data coordinator."""
self._update_attrs()
super()._handle_coordinator_update()
@callback
def _update_attrs(self) -> None:
"""Update attributes for binary sensor."""
is_on = self._camera.attributes[self.entity_description.key]
_LOGGER.debug( _LOGGER.debug(
"'%s' %s = %s", "'%s' %s = %s",
self._camera.attributes["name"], self._camera.attributes["name"],
self.entity_description.key, self.entity_description.key,
state, is_on,
) )
if self.entity_description.key == TYPE_BATTERY: if self.entity_description.key == TYPE_BATTERY:
state = state != "ok" is_on = is_on != "ok"
self._attr_is_on = state self._attr_is_on = is_on

View File

@ -1,32 +1,41 @@
"""Support for Blink system camera.""" """Support for Blink system camera."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Mapping
import contextlib
import logging import logging
from typing import Any
from requests.exceptions import ChunkedEncodingError from requests.exceptions import ChunkedEncodingError
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_BRAND, DOMAIN, SERVICE_TRIGGER from .const import DEFAULT_BRAND, DOMAIN, SERVICE_TRIGGER
from .coordinator import BlinkUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_VIDEO_CLIP = "video" ATTR_VIDEO_CLIP = "video"
ATTR_IMAGE = "image" ATTR_IMAGE = "image"
PARALLEL_UPDATES = 1
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up a Blink Camera.""" """Set up a Blink Camera."""
data = hass.data[DOMAIN][config.entry_id] coordinator: BlinkUpdateCoordinator = hass.data[DOMAIN][config.entry_id]
entities = [ entities = [
BlinkCamera(data, name, camera) for name, camera in data.cameras.items() BlinkCamera(coordinator, name, camera)
for name, camera in coordinator.api.cameras.items()
] ]
async_add_entities(entities) async_add_entities(entities)
@ -35,20 +44,22 @@ async def async_setup_entry(
platform.async_register_entity_service(SERVICE_TRIGGER, {}, "trigger_camera") platform.async_register_entity_service(SERVICE_TRIGGER, {}, "trigger_camera")
class BlinkCamera(Camera): class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
"""An implementation of a Blink Camera.""" """An implementation of a Blink Camera."""
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None _attr_name = None
def __init__(self, data, name, camera): def __init__(self, coordinator: BlinkUpdateCoordinator, name, camera) -> None:
"""Initialize a camera.""" """Initialize a camera."""
super().__init__() super().__init__(coordinator)
self.data = data Camera.__init__(self)
self._coordinator = coordinator
self._camera = camera self._camera = camera
self._attr_unique_id = f"{camera.serial}-camera" self._attr_unique_id = f"{camera.serial}-camera"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, camera.serial)}, identifiers={(DOMAIN, camera.serial)},
serial_number=camera.serial,
name=name, name=name,
manufacturer=DEFAULT_BRAND, manufacturer=DEFAULT_BRAND,
model=camera.camera_type, model=camera.camera_type,
@ -56,19 +67,30 @@ class BlinkCamera(Camera):
_LOGGER.debug("Initialized blink camera %s", self.name) _LOGGER.debug("Initialized blink camera %s", self.name)
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the camera attributes.""" """Return the camera attributes."""
return self._camera.attributes return self._camera.attributes
def enable_motion_detection(self) -> None: async def async_enable_motion_detection(self) -> None:
"""Enable motion detection for the camera.""" """Enable motion detection for the camera."""
self._camera.arm = True try:
self.data.refresh() await self._camera.async_arm(True)
def disable_motion_detection(self) -> None: except asyncio.TimeoutError as er:
raise HomeAssistantError("Blink failed to arm camera") from er
self._camera.motion_enabled = True
await self._coordinator.async_refresh()
async def async_disable_motion_detection(self) -> None:
"""Disable motion detection for the camera.""" """Disable motion detection for the camera."""
self._camera.arm = False try:
self.data.refresh() await self._camera.async_arm(False)
except asyncio.TimeoutError as er:
raise HomeAssistantError("Blink failed to disarm camera") from er
self._camera.motion_enabled = False
await self._coordinator.async_refresh()
@property @property
def motion_detection_enabled(self) -> bool: def motion_detection_enabled(self) -> bool:
@ -76,21 +98,23 @@ class BlinkCamera(Camera):
return self._camera.arm return self._camera.arm
@property @property
def brand(self): def brand(self) -> str | None:
"""Return the camera brand.""" """Return the camera brand."""
return DEFAULT_BRAND return DEFAULT_BRAND
def trigger_camera(self): async def trigger_camera(self) -> None:
"""Trigger camera to take a snapshot.""" """Trigger camera to take a snapshot."""
self._camera.snap_picture() with contextlib.suppress(asyncio.TimeoutError):
self.data.refresh() await self._camera.snap_picture()
await self._coordinator.api.refresh()
self.async_write_ha_state()
def camera_image( def camera_image(
self, width: int | None = None, height: int | None = None self, width: int | None = None, height: int | None = None
) -> bytes | None: ) -> bytes | None:
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
try: try:
return self._camera.image_from_cache.content return self._camera.image_from_cache
except ChunkedEncodingError: except ChunkedEncodingError:
_LOGGER.debug("Could not retrieve image for %s", self._camera.name) _LOGGER.debug("Could not retrieve image for %s", self._camera.name)
return None return None

View File

@ -16,10 +16,11 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import selector from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.schema_config_entry_flow import ( from homeassistant.helpers.schema_config_entry_flow import (
SchemaFlowFormStep, SchemaFlowFormStep,
SchemaOptionsFlowHandler, SchemaOptionsFlowHandler,
@ -49,23 +50,23 @@ OPTIONS_FLOW = {
} }
def validate_input(auth: Auth) -> None: async def validate_input(auth: Auth) -> None:
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
try: try:
auth.startup() await auth.startup()
except (LoginError, TokenRefreshFailed) as err: except (LoginError, TokenRefreshFailed) as err:
raise InvalidAuth from err raise InvalidAuth from err
if auth.check_key_required(): if auth.check_key_required():
raise Require2FA raise Require2FA
def _send_blink_2fa_pin(auth: Auth, pin: str | None) -> bool: async def _send_blink_2fa_pin(hass: HomeAssistant, auth: Auth, pin: str | None) -> bool:
"""Send 2FA pin to blink servers.""" """Send 2FA pin to blink servers."""
blink = Blink() blink = Blink(session=async_get_clientsession(hass))
blink.auth = auth blink.auth = auth
blink.setup_login_ids() blink.setup_login_ids()
blink.setup_urls() blink.setup_urls()
return auth.send_auth_key(blink, pin) return await auth.send_auth_key(blink, pin)
class BlinkConfigFlow(ConfigFlow, domain=DOMAIN): class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
@ -91,11 +92,15 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initiated by the user.""" """Handle a flow initiated by the user."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
self.auth = Auth({**user_input, "device_id": DEVICE_ID}, no_prompt=True) self.auth = Auth(
{**user_input, "device_id": DEVICE_ID},
no_prompt=True,
session=async_get_clientsession(self.hass),
)
await self.async_set_unique_id(user_input[CONF_USERNAME]) await self.async_set_unique_id(user_input[CONF_USERNAME])
try: try:
await self.hass.async_add_executor_job(validate_input, self.auth) await validate_input(self.auth)
return self._async_finish_flow() return self._async_finish_flow()
except Require2FA: except Require2FA:
return await self.async_step_2fa() return await self.async_step_2fa()
@ -122,11 +127,9 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle 2FA step.""" """Handle 2FA step."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
pin: str | None = user_input.get(CONF_PIN)
try: try:
assert self.auth valid_token = await _send_blink_2fa_pin(
valid_token = await self.hass.async_add_executor_job( self.hass, self.auth, user_input.get(CONF_PIN)
_send_blink_2fa_pin, self.auth, pin
) )
except BlinkSetupError: except BlinkSetupError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"

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