Merge pull request #52627 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2021-07-07 14:23:39 +02:00 committed by GitHub
commit 933e016150
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2063 changed files with 57453 additions and 19690 deletions

View File

@ -115,12 +115,14 @@ omit =
homeassistant/components/bmw_connected_drive/notify.py homeassistant/components/bmw_connected_drive/notify.py
homeassistant/components/bmw_connected_drive/sensor.py homeassistant/components/bmw_connected_drive/sensor.py
homeassistant/components/bosch_shc/__init__.py homeassistant/components/bosch_shc/__init__.py
homeassistant/components/bosch_shc/const.py
homeassistant/components/bosch_shc/binary_sensor.py homeassistant/components/bosch_shc/binary_sensor.py
homeassistant/components/bosch_shc/const.py
homeassistant/components/bosch_shc/entity.py homeassistant/components/bosch_shc/entity.py
homeassistant/components/bosch_shc/sensor.py
homeassistant/components/braviatv/__init__.py homeassistant/components/braviatv/__init__.py
homeassistant/components/braviatv/const.py homeassistant/components/braviatv/const.py
homeassistant/components/braviatv/media_player.py homeassistant/components/braviatv/media_player.py
homeassistant/components/braviatv/remote.py
homeassistant/components/broadlink/__init__.py homeassistant/components/broadlink/__init__.py
homeassistant/components/broadlink/const.py homeassistant/components/broadlink/const.py
homeassistant/components/broadlink/remote.py homeassistant/components/broadlink/remote.py
@ -152,7 +154,7 @@ omit =
homeassistant/components/clicksend_tts/notify.py homeassistant/components/clicksend_tts/notify.py
homeassistant/components/cmus/media_player.py homeassistant/components/cmus/media_player.py
homeassistant/components/co2signal/* homeassistant/components/co2signal/*
homeassistant/components/coinbase/* homeassistant/components/coinbase/sensor.py
homeassistant/components/comed_hourly_pricing/sensor.py homeassistant/components/comed_hourly_pricing/sensor.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
@ -182,11 +184,9 @@ omit =
homeassistant/components/denonavr/media_player.py homeassistant/components/denonavr/media_player.py
homeassistant/components/denonavr/receiver.py homeassistant/components/denonavr/receiver.py
homeassistant/components/deutsche_bahn/sensor.py homeassistant/components/deutsche_bahn/sensor.py
homeassistant/components/devolo_home_control/binary_sensor.py
homeassistant/components/devolo_home_control/climate.py homeassistant/components/devolo_home_control/climate.py
homeassistant/components/devolo_home_control/const.py homeassistant/components/devolo_home_control/const.py
homeassistant/components/devolo_home_control/cover.py homeassistant/components/devolo_home_control/cover.py
homeassistant/components/devolo_home_control/devolo_device.py
homeassistant/components/devolo_home_control/devolo_multi_level_switch.py homeassistant/components/devolo_home_control/devolo_multi_level_switch.py
homeassistant/components/devolo_home_control/light.py homeassistant/components/devolo_home_control/light.py
homeassistant/components/devolo_home_control/sensor.py homeassistant/components/devolo_home_control/sensor.py
@ -221,6 +221,7 @@ omit =
homeassistant/components/ecobee/__init__.py homeassistant/components/ecobee/__init__.py
homeassistant/components/ecobee/binary_sensor.py homeassistant/components/ecobee/binary_sensor.py
homeassistant/components/ecobee/climate.py homeassistant/components/ecobee/climate.py
homeassistant/components/ecobee/humidifier.py
homeassistant/components/ecobee/notify.py homeassistant/components/ecobee/notify.py
homeassistant/components/ecobee/sensor.py homeassistant/components/ecobee/sensor.py
homeassistant/components/ecobee/weather.py homeassistant/components/ecobee/weather.py
@ -273,6 +274,7 @@ omit =
homeassistant/components/esphome/entry_data.py homeassistant/components/esphome/entry_data.py
homeassistant/components/esphome/fan.py homeassistant/components/esphome/fan.py
homeassistant/components/esphome/light.py homeassistant/components/esphome/light.py
homeassistant/components/esphome/number.py
homeassistant/components/esphome/sensor.py homeassistant/components/esphome/sensor.py
homeassistant/components/esphome/switch.py homeassistant/components/esphome/switch.py
homeassistant/components/essent/sensor.py homeassistant/components/essent/sensor.py
@ -341,6 +343,7 @@ omit =
homeassistant/components/fritz/device_tracker.py homeassistant/components/fritz/device_tracker.py
homeassistant/components/fritz/sensor.py homeassistant/components/fritz/sensor.py
homeassistant/components/fritz/services.py homeassistant/components/fritz/services.py
homeassistant/components/fritz/switch.py
homeassistant/components/fritzbox_callmonitor/__init__.py homeassistant/components/fritzbox_callmonitor/__init__.py
homeassistant/components/fritzbox_callmonitor/const.py homeassistant/components/fritzbox_callmonitor/const.py
homeassistant/components/fritzbox_callmonitor/base.py homeassistant/components/fritzbox_callmonitor/base.py
@ -541,15 +544,12 @@ omit =
homeassistant/components/lastfm/sensor.py homeassistant/components/lastfm/sensor.py
homeassistant/components/launch_library/const.py homeassistant/components/launch_library/const.py
homeassistant/components/launch_library/sensor.py homeassistant/components/launch_library/sensor.py
homeassistant/components/lcn/__init__.py
homeassistant/components/lcn/binary_sensor.py homeassistant/components/lcn/binary_sensor.py
homeassistant/components/lcn/climate.py homeassistant/components/lcn/climate.py
homeassistant/components/lcn/const.py
homeassistant/components/lcn/cover.py homeassistant/components/lcn/cover.py
homeassistant/components/lcn/helpers.py homeassistant/components/lcn/helpers.py
homeassistant/components/lcn/light.py homeassistant/components/lcn/light.py
homeassistant/components/lcn/scene.py homeassistant/components/lcn/scene.py
homeassistant/components/lcn/schemas.py
homeassistant/components/lcn/sensor.py homeassistant/components/lcn/sensor.py
homeassistant/components/lcn/services.py homeassistant/components/lcn/services.py
homeassistant/components/lcn/switch.py homeassistant/components/lcn/switch.py
@ -613,6 +613,7 @@ omit =
homeassistant/components/meteoalarm/* homeassistant/components/meteoalarm/*
homeassistant/components/meteoclimatic/__init__.py homeassistant/components/meteoclimatic/__init__.py
homeassistant/components/meteoclimatic/const.py homeassistant/components/meteoclimatic/const.py
homeassistant/components/meteoclimatic/sensor.py
homeassistant/components/meteoclimatic/weather.py homeassistant/components/meteoclimatic/weather.py
homeassistant/components/metoffice/sensor.py homeassistant/components/metoffice/sensor.py
homeassistant/components/metoffice/weather.py homeassistant/components/metoffice/weather.py
@ -690,7 +691,7 @@ omit =
homeassistant/components/niko_home_control/light.py homeassistant/components/niko_home_control/light.py
homeassistant/components/nilu/air_quality.py homeassistant/components/nilu/air_quality.py
homeassistant/components/nissan_leaf/* homeassistant/components/nissan_leaf/*
homeassistant/components/nmap_tracker/device_tracker.py homeassistant/components/nmap_tracker/*
homeassistant/components/nmbs/sensor.py homeassistant/components/nmbs/sensor.py
homeassistant/components/notion/__init__.py homeassistant/components/notion/__init__.py
homeassistant/components/notion/binary_sensor.py homeassistant/components/notion/binary_sensor.py
@ -769,6 +770,7 @@ omit =
homeassistant/components/pcal9535a/* homeassistant/components/pcal9535a/*
homeassistant/components/pencom/switch.py homeassistant/components/pencom/switch.py
homeassistant/components/philips_js/__init__.py homeassistant/components/philips_js/__init__.py
homeassistant/components/philips_js/light.py
homeassistant/components/philips_js/media_player.py homeassistant/components/philips_js/media_player.py
homeassistant/components/philips_js/remote.py homeassistant/components/philips_js/remote.py
homeassistant/components/pi_hole/sensor.py homeassistant/components/pi_hole/sensor.py
@ -846,6 +848,8 @@ omit =
homeassistant/components/ripple/sensor.py homeassistant/components/ripple/sensor.py
homeassistant/components/rituals_perfume_genie/binary_sensor.py homeassistant/components/rituals_perfume_genie/binary_sensor.py
homeassistant/components/rituals_perfume_genie/entity.py homeassistant/components/rituals_perfume_genie/entity.py
homeassistant/components/rituals_perfume_genie/number.py
homeassistant/components/rituals_perfume_genie/select.py
homeassistant/components/rituals_perfume_genie/sensor.py homeassistant/components/rituals_perfume_genie/sensor.py
homeassistant/components/rituals_perfume_genie/switch.py homeassistant/components/rituals_perfume_genie/switch.py
homeassistant/components/rituals_perfume_genie/__init__.py homeassistant/components/rituals_perfume_genie/__init__.py
@ -921,9 +925,11 @@ omit =
homeassistant/components/slack/notify.py homeassistant/components/slack/notify.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
homeassistant/components/sia/binary_sensor.py
homeassistant/components/sia/const.py homeassistant/components/sia/const.py
homeassistant/components/sia/hub.py homeassistant/components/sia/hub.py
homeassistant/components/sia/utils.py homeassistant/components/sia/utils.py
homeassistant/components/sia/sia_entity_base.py
homeassistant/components/sinch/* homeassistant/components/sinch/*
homeassistant/components/slide/* homeassistant/components/slide/*
homeassistant/components/sma/__init__.py homeassistant/components/sma/__init__.py
@ -943,6 +949,7 @@ omit =
homeassistant/components/snmp/* homeassistant/components/snmp/*
homeassistant/components/sochain/sensor.py homeassistant/components/sochain/sensor.py
homeassistant/components/solaredge/__init__.py homeassistant/components/solaredge/__init__.py
homeassistant/components/solaredge/coordinator.py
homeassistant/components/solaredge/sensor.py homeassistant/components/solaredge/sensor.py
homeassistant/components/solaredge_local/sensor.py homeassistant/components/solaredge_local/sensor.py
homeassistant/components/solarlog/* homeassistant/components/solarlog/*
@ -970,6 +977,7 @@ omit =
homeassistant/components/squeezebox/__init__.py homeassistant/components/squeezebox/__init__.py
homeassistant/components/squeezebox/browse_media.py homeassistant/components/squeezebox/browse_media.py
homeassistant/components/squeezebox/media_player.py homeassistant/components/squeezebox/media_player.py
homeassistant/components/ssdp/util.py
homeassistant/components/starline/* homeassistant/components/starline/*
homeassistant/components/starlingbank/sensor.py homeassistant/components/starlingbank/sensor.py
homeassistant/components/steam_online/sensor.py homeassistant/components/steam_online/sensor.py
@ -985,6 +993,7 @@ omit =
homeassistant/components/swiss_public_transport/sensor.py homeassistant/components/swiss_public_transport/sensor.py
homeassistant/components/swisscom/device_tracker.py homeassistant/components/swisscom/device_tracker.py
homeassistant/components/switchbot/switch.py homeassistant/components/switchbot/switch.py
homeassistant/components/switcher_kis/sensor.py
homeassistant/components/switcher_kis/switch.py homeassistant/components/switcher_kis/switch.py
homeassistant/components/switchmate/switch.py homeassistant/components/switchmate/switch.py
homeassistant/components/syncthing/__init__.py homeassistant/components/syncthing/__init__.py
@ -1206,6 +1215,7 @@ omit =
homeassistant/components/xmpp/notify.py homeassistant/components/xmpp/notify.py
homeassistant/components/xs1/* homeassistant/components/xs1/*
homeassistant/components/yale_smart_alarm/alarm_control_panel.py homeassistant/components/yale_smart_alarm/alarm_control_panel.py
homeassistant/components/yamaha_musiccast/__init__.py
homeassistant/components/yamaha_musiccast/media_player.py homeassistant/components/yamaha_musiccast/media_player.py
homeassistant/components/yandex_transport/* homeassistant/components/yandex_transport/*
homeassistant/components/yeelightsunflower/light.py homeassistant/components/yeelightsunflower/light.py

View File

@ -102,13 +102,13 @@ jobs:
version="$(python setup.py -V)" version="$(python setup.py -V)"
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.9.0 uses: docker/login-action@v1.10.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1.9.0 uses: docker/login-action@v1.10.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -154,13 +154,13 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.9.0 uses: docker/login-action@v1.10.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1.9.0 uses: docker/login-action@v1.10.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -217,13 +217,13 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.9.0 uses: docker/login-action@v1.10.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1.9.0 uses: docker/login-action@v1.10.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -307,5 +307,9 @@ jobs:
create_manifest "${docker_reg}" "latest" "${{ needs.init.outputs.version }}" create_manifest "${docker_reg}" "latest" "${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "beta" "${{ needs.init.outputs.version }}" create_manifest "${docker_reg}" "beta" "${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "rc" "${{ needs.init.outputs.version }}" create_manifest "${docker_reg}" "rc" "${{ needs.init.outputs.version }}"
# Create series version tag (e.g. 2021.6)
v="${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "${v%.*}" "${{ needs.init.outputs.version }}"
fi fi
done done

View File

@ -10,7 +10,7 @@ on:
pull_request: ~ pull_request: ~
env: env:
CACHE_VERSION: 1 CACHE_VERSION: 2
DEFAULT_PYTHON: 3.8 DEFAULT_PYTHON: 3.8
PRE_COMMIT_CACHE: ~/.cache/pre-commit PRE_COMMIT_CACHE: ~/.cache/pre-commit
SQLALCHEMY_WARN_20: 1 SQLALCHEMY_WARN_20: 1
@ -41,7 +41,7 @@ jobs:
hashFiles('homeassistant/package_constraints.txt') }}" hashFiles('homeassistant/package_constraints.txt') }}"
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: >- key: >-
@ -65,7 +65,7 @@ jobs:
hashFiles('.pre-commit-config.yaml') }}" hashFiles('.pre-commit-config.yaml') }}"
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: >- key: >-
@ -92,7 +92,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -104,7 +104,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -132,7 +132,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -144,7 +144,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -172,7 +172,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -184,7 +184,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -234,7 +234,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -246,7 +246,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -277,7 +277,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -289,7 +289,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -320,7 +320,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -332,7 +332,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -360,7 +360,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -372,7 +372,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -403,7 +403,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -415,7 +415,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -454,7 +454,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -466,7 +466,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -496,7 +496,7 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -525,7 +525,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -561,7 +561,7 @@ jobs:
hashFiles('homeassistant/package_constraints.txt') }}" hashFiles('homeassistant/package_constraints.txt') }}"
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: >- key: >-
@ -598,7 +598,7 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -629,7 +629,7 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -663,7 +663,7 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -700,7 +700,7 @@ jobs:
-p no:sugar \ -p no:sugar \
tests tests
- name: Upload coverage artifact - name: Upload coverage artifact
uses: actions/upload-artifact@v2.2.3 uses: actions/upload-artifact@v2.2.4
with: with:
name: coverage-${{ matrix.python-version }}-group${{ matrix.group }} name: coverage-${{ matrix.python-version }}-group${{ matrix.group }}
path: .coverage path: .coverage
@ -721,7 +721,7 @@ jobs:
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.5 uses: actions/cache@v2.1.6
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -740,4 +740,4 @@ jobs:
coverage report --fail-under=94 coverage report --fail-under=94
coverage xml coverage xml
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v1.5.0 uses: codecov/codecov-action@v1.5.2

View File

@ -45,13 +45,13 @@ jobs:
) > .env_file ) > .env_file
- name: Upload env_file - name: Upload env_file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2.2.4
with: with:
name: env_file name: env_file
path: ./.env_file path: ./.env_file
- name: Upload requirements_diff - name: Upload requirements_diff
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2.2.4
with: with:
name: requirements_diff name: requirements_diff
path: ./requirements_diff.txt path: ./requirements_diff.txt
@ -65,7 +65,6 @@ jobs:
matrix: matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag: tag:
- "3.8-alpine3.12"
- "3.9-alpine3.13" - "3.9-alpine3.13"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
@ -82,7 +81,7 @@ jobs:
name: requirements_diff name: requirements_diff
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2021.05.4 uses: home-assistant/wheels@2021.06.0
with: with:
tag: ${{ matrix.tag }} tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
@ -106,7 +105,6 @@ jobs:
matrix: matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag: tag:
- "3.8-alpine3.12"
- "3.9-alpine3.13" - "3.9-alpine3.13"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
@ -152,7 +150,7 @@ jobs:
done done
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2021.05.4 uses: home-assistant/wheels@2021.06.0
with: with:
tag: ${{ matrix.tag }} tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}

View File

@ -5,7 +5,7 @@ repos:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py38-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.5b1 rev: 21.6b0
hooks: hooks:
- id: black - id: black
args: args:

View File

@ -12,6 +12,7 @@ homeassistant.components.airly.*
homeassistant.components.aladdin_connect.* homeassistant.components.aladdin_connect.*
homeassistant.components.alarm_control_panel.* homeassistant.components.alarm_control_panel.*
homeassistant.components.amazon_polly.* homeassistant.components.amazon_polly.*
homeassistant.components.ambee.*
homeassistant.components.ampio.* homeassistant.components.ampio.*
homeassistant.components.automation.* homeassistant.components.automation.*
homeassistant.components.binary_sensor.* homeassistant.components.binary_sensor.*
@ -24,15 +25,19 @@ homeassistant.components.canary.*
homeassistant.components.cover.* homeassistant.components.cover.*
homeassistant.components.device_automation.* homeassistant.components.device_automation.*
homeassistant.components.device_tracker.* homeassistant.components.device_tracker.*
homeassistant.components.dnsip.*
homeassistant.components.dsmr.*
homeassistant.components.dunehd.* homeassistant.components.dunehd.*
homeassistant.components.elgato.* homeassistant.components.elgato.*
homeassistant.components.fitbit.* homeassistant.components.fitbit.*
homeassistant.components.forecast_solar.*
homeassistant.components.fritzbox.* homeassistant.components.fritzbox.*
homeassistant.components.frontend.* homeassistant.components.frontend.*
homeassistant.components.geo_location.* homeassistant.components.geo_location.*
homeassistant.components.gios.* homeassistant.components.gios.*
homeassistant.components.group.* homeassistant.components.group.*
homeassistant.components.history.* homeassistant.components.history.*
homeassistant.components.homeassistant.triggers.event
homeassistant.components.http.* homeassistant.components.http.*
homeassistant.components.huawei_lte.* homeassistant.components.huawei_lte.*
homeassistant.components.hyperion.* homeassistant.components.hyperion.*
@ -41,24 +46,31 @@ homeassistant.components.integration.*
homeassistant.components.knx.* homeassistant.components.knx.*
homeassistant.components.kraken.* homeassistant.components.kraken.*
homeassistant.components.light.* homeassistant.components.light.*
homeassistant.components.local_ip.*
homeassistant.components.lock.* homeassistant.components.lock.*
homeassistant.components.mailbox.* homeassistant.components.mailbox.*
homeassistant.components.media_player.* homeassistant.components.media_player.*
homeassistant.components.mysensors.*
homeassistant.components.nam.* homeassistant.components.nam.*
homeassistant.components.network.* homeassistant.components.network.*
homeassistant.components.no_ip.*
homeassistant.components.notify.* homeassistant.components.notify.*
homeassistant.components.number.* homeassistant.components.number.*
homeassistant.components.onewire.* homeassistant.components.onewire.*
homeassistant.components.persistent_notification.* homeassistant.components.persistent_notification.*
homeassistant.components.pi_hole.*
homeassistant.components.proximity.* homeassistant.components.proximity.*
homeassistant.components.recorder.purge homeassistant.components.recorder.purge
homeassistant.components.recorder.repack homeassistant.components.recorder.repack
homeassistant.components.recorder.statistics homeassistant.components.recorder.statistics
homeassistant.components.remote.* homeassistant.components.remote.*
homeassistant.components.scene.* homeassistant.components.scene.*
homeassistant.components.select.*
homeassistant.components.sensor.* homeassistant.components.sensor.*
homeassistant.components.slack.* homeassistant.components.slack.*
homeassistant.components.sonos.media_player homeassistant.components.sonos.media_player
homeassistant.components.ssdp.*
homeassistant.components.stream.*
homeassistant.components.sun.* homeassistant.components.sun.*
homeassistant.components.switch.* homeassistant.components.switch.*
homeassistant.components.synology_dsm.* homeassistant.components.synology_dsm.*
@ -66,10 +78,12 @@ homeassistant.components.systemmonitor.*
homeassistant.components.tcp.* homeassistant.components.tcp.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.upcloud.* homeassistant.components.upcloud.*
homeassistant.components.uptime.*
homeassistant.components.vacuum.* homeassistant.components.vacuum.*
homeassistant.components.water_heater.* homeassistant.components.water_heater.*
homeassistant.components.weather.* homeassistant.components.weather.*
homeassistant.components.websocket_api.* homeassistant.components.websocket_api.*
homeassistant.components.zodiac.*
homeassistant.components.zeroconf.* homeassistant.components.zeroconf.*
homeassistant.components.zone.* homeassistant.components.zone.*
homeassistant.components.zwave_js.* homeassistant.components.zwave_js.*

View File

@ -33,6 +33,7 @@ homeassistant/components/alarmdecoder/* @ajschmidt8
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
homeassistant/components/almond/* @gcampax @balloob homeassistant/components/almond/* @gcampax @balloob
homeassistant/components/alpha_vantage/* @fabaff homeassistant/components/alpha_vantage/* @fabaff
homeassistant/components/ambee/* @frenck
homeassistant/components/ambiclimate/* @danielhiversen homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya homeassistant/components/ambient_station/* @bachya
homeassistant/components/analytics/* @home-assistant/core @ludeeus homeassistant/components/analytics/* @home-assistant/core @ludeeus
@ -71,7 +72,7 @@ homeassistant/components/bmp280/* @belidzs
homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe
homeassistant/components/bond/* @prystupa homeassistant/components/bond/* @prystupa
homeassistant/components/bosch_shc/* @tschamm homeassistant/components/bosch_shc/* @tschamm
homeassistant/components/braviatv/* @bieniu homeassistant/components/braviatv/* @bieniu @Drafteed
homeassistant/components/broadlink/* @danielhiversen @felipediel homeassistant/components/broadlink/* @danielhiversen @felipediel
homeassistant/components/brother/* @bieniu homeassistant/components/brother/* @bieniu
homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/brunt/* @eavanvalkenburg
@ -87,6 +88,7 @@ homeassistant/components/cisco_webex_teams/* @fbradyirl
homeassistant/components/climacell/* @raman325 homeassistant/components/climacell/* @raman325
homeassistant/components/cloud/* @home-assistant/cloud homeassistant/components/cloud/* @home-assistant/cloud
homeassistant/components/cloudflare/* @ludeeus @ctalkington homeassistant/components/cloudflare/* @ludeeus @ctalkington
homeassistant/components/coinbase/* @tombrien
homeassistant/components/color_extractor/* @GenericStudent homeassistant/components/color_extractor/* @GenericStudent
homeassistant/components/comfoconnect/* @michaelarnauts homeassistant/components/comfoconnect/* @michaelarnauts
homeassistant/components/compensation/* @Petro31 homeassistant/components/compensation/* @Petro31
@ -117,7 +119,7 @@ homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/directv/* @ctalkington homeassistant/components/directv/* @ctalkington
homeassistant/components/discogs/* @thibmaek homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7 @bdraco homeassistant/components/doorbird/* @oblogic7 @bdraco
homeassistant/components/dsmr/* @Robbie1221 homeassistant/components/dsmr/* @Robbie1221 @frenck
homeassistant/components/dsmr_reader/* @depl0y homeassistant/components/dsmr_reader/* @depl0y
homeassistant/components/dunehd/* @bieniu homeassistant/components/dunehd/* @bieniu
homeassistant/components/dwd_weather_warnings/* @runningman84 @stephan192 @Hummel95 homeassistant/components/dwd_weather_warnings/* @runningman84 @stephan192 @Hummel95
@ -146,7 +148,7 @@ homeassistant/components/ephember/* @ttroy50
homeassistant/components/epson/* @pszafer homeassistant/components/epson/* @pszafer
homeassistant/components/epsonworkforce/* @ThaStealth homeassistant/components/epsonworkforce/* @ThaStealth
homeassistant/components/eq3btsmart/* @rytilahti homeassistant/components/eq3btsmart/* @rytilahti
homeassistant/components/esphome/* @OttoWinter homeassistant/components/esphome/* @OttoWinter @jesserockz
homeassistant/components/essent/* @TheLastProject homeassistant/components/essent/* @TheLastProject
homeassistant/components/evohome/* @zxdavb homeassistant/components/evohome/* @zxdavb
homeassistant/components/ezviz/* @RenierM26 @baqs homeassistant/components/ezviz/* @RenierM26 @baqs
@ -162,10 +164,12 @@ homeassistant/components/flo/* @dmulcahey
homeassistant/components/flock/* @fabaff homeassistant/components/flock/* @fabaff
homeassistant/components/flume/* @ChrisMandich @bdraco homeassistant/components/flume/* @ChrisMandich @bdraco
homeassistant/components/flunearyou/* @bachya homeassistant/components/flunearyou/* @bachya
homeassistant/components/forecast_solar/* @klaasnicolaas @frenck
homeassistant/components/forked_daapd/* @uvjustin homeassistant/components/forked_daapd/* @uvjustin
homeassistant/components/fortios/* @kimfrellsen homeassistant/components/fortios/* @kimfrellsen
homeassistant/components/foscam/* @skgsergio homeassistant/components/foscam/* @skgsergio
homeassistant/components/freebox/* @hacf-fr @Quentame homeassistant/components/freebox/* @hacf-fr @Quentame
homeassistant/components/freedompro/* @stefano055415
homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74 homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74
homeassistant/components/fritzbox/* @mib1185 homeassistant/components/fritzbox/* @mib1185
homeassistant/components/fronius/* @nielstron homeassistant/components/fronius/* @nielstron
@ -300,6 +304,7 @@ homeassistant/components/minecraft_server/* @elmurato
homeassistant/components/minio/* @tkislan homeassistant/components/minio/* @tkislan
homeassistant/components/mobile_app/* @robbiet480 homeassistant/components/mobile_app/* @robbiet480
homeassistant/components/modbus/* @adamchengtkc @janiversen @vzahradnik homeassistant/components/modbus/* @adamchengtkc @janiversen @vzahradnik
homeassistant/components/modern_forms/* @wonderslug
homeassistant/components/monoprice/* @etsinko @OnFreund homeassistant/components/monoprice/* @etsinko @OnFreund
homeassistant/components/moon/* @fabaff homeassistant/components/moon/* @fabaff
homeassistant/components/motion_blinds/* @starkillerOG homeassistant/components/motion_blinds/* @starkillerOG
@ -421,12 +426,12 @@ homeassistant/components/scrape/* @fabaff
homeassistant/components/screenlogic/* @dieselrabbit homeassistant/components/screenlogic/* @dieselrabbit
homeassistant/components/script/* @home-assistant/core homeassistant/components/script/* @home-assistant/core
homeassistant/components/search/* @home-assistant/core homeassistant/components/search/* @home-assistant/core
homeassistant/components/select/* @home-assistant/core
homeassistant/components/sense/* @kbickar homeassistant/components/sense/* @kbickar
homeassistant/components/sensibo/* @andrey-git homeassistant/components/sensibo/* @andrey-git
homeassistant/components/sentry/* @dcramer @frenck homeassistant/components/sentry/* @dcramer @frenck
homeassistant/components/serial/* @fabaff homeassistant/components/serial/* @fabaff
homeassistant/components/seven_segments/* @fabaff homeassistant/components/seven_segments/* @fabaff
homeassistant/components/seventeentrack/* @bachya
homeassistant/components/sharkiq/* @ajmarks homeassistant/components/sharkiq/* @ajmarks
homeassistant/components/shell_command/* @home-assistant/core homeassistant/components/shell_command/* @home-assistant/core
homeassistant/components/shelly/* @balloob @bieniu @thecode @chemelli74 homeassistant/components/shelly/* @balloob @bieniu @thecode @chemelli74
@ -477,11 +482,11 @@ homeassistant/components/subaru/* @G-Two
homeassistant/components/suez_water/* @ooii homeassistant/components/suez_water/* @ooii
homeassistant/components/sun/* @Swamp-Ig homeassistant/components/sun/* @Swamp-Ig
homeassistant/components/supla/* @mwegrzynek homeassistant/components/supla/* @mwegrzynek
homeassistant/components/surepetcare/* @benleb homeassistant/components/surepetcare/* @benleb @danielhiversen
homeassistant/components/swiss_hydrological_data/* @fabaff homeassistant/components/swiss_hydrological_data/* @fabaff
homeassistant/components/swiss_public_transport/* @fabaff homeassistant/components/swiss_public_transport/* @fabaff
homeassistant/components/switchbot/* @danielhiversen homeassistant/components/switchbot/* @danielhiversen
homeassistant/components/switcher_kis/* @tomerfi homeassistant/components/switcher_kis/* @tomerfi @thecode
homeassistant/components/switchmate/* @danielhiversen homeassistant/components/switchmate/* @danielhiversen
homeassistant/components/syncthing/* @zhulik homeassistant/components/syncthing/* @zhulik
homeassistant/components/syncthru/* @nielstron homeassistant/components/syncthru/* @nielstron
@ -566,7 +571,7 @@ homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG
homeassistant/components/xiaomi_tv/* @simse homeassistant/components/xiaomi_tv/* @simse
homeassistant/components/xmpp/* @fabaff @flowolf homeassistant/components/xmpp/* @fabaff @flowolf
homeassistant/components/yale_smart_alarm/* @gjohansson-ST homeassistant/components/yale_smart_alarm/* @gjohansson-ST
homeassistant/components/yamaha_musiccast/* @jalmeroth homeassistant/components/yamaha_musiccast/* @vigonotion @micha91
homeassistant/components/yandex_transport/* @rishatik92 @devbis homeassistant/components/yandex_transport/* @rishatik92 @devbis
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn
homeassistant/components/yeelightsunflower/* @lindsaymarkward homeassistant/components/yeelightsunflower/* @lindsaymarkward

View File

@ -2,11 +2,11 @@
"image": "homeassistant/{arch}-homeassistant", "image": "homeassistant/{arch}-homeassistant",
"shadow_repository": "ghcr.io/home-assistant", "shadow_repository": "ghcr.io/home-assistant",
"build_from": { "build_from": {
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.05.0", "aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.06.2",
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.05.0", "armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.06.2",
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.05.0", "armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.06.2",
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.05.0", "amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.06.2",
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.05.0" "i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.06.2"
}, },
"labels": { "labels": {
"io.hass.type": "core", "io.hass.type": "core",

View File

@ -81,6 +81,17 @@ class TrustedNetworksAuthProvider(AuthProvider):
"""Return trusted users per network.""" """Return trusted users per network."""
return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS]) return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS])
@property
def trusted_proxies(self) -> list[IPNetwork]:
"""Return trusted proxies in the system."""
if not self.hass.http:
return []
return [
ip_network(trusted_proxy)
for trusted_proxy in self.hass.http.trusted_proxies
]
@property @property
def support_mfa(self) -> bool: def support_mfa(self) -> bool:
"""Trusted Networks auth provider does not support MFA.""" """Trusted Networks auth provider does not support MFA."""
@ -178,6 +189,9 @@ class TrustedNetworksAuthProvider(AuthProvider):
): ):
raise InvalidAuthError("Not in trusted_networks") raise InvalidAuthError("Not in trusted_networks")
if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies):
raise InvalidAuthError("Can't allow access from a proxy server")
@callback @callback
def async_validate_refresh_token( def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None self, refresh_token: RefreshToken, remote_ip: str | None = None

View File

@ -1,10 +1,26 @@
{ {
"config": { "config": {
"abort": {
"reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7",
"single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea."
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
},
"step": { "step": {
"reauth_confirm": {
"data": {
"password": "\u05e1\u05d9\u05e1\u05de\u05d4",
"username": "\u05d3\u05d5\u05d0\"\u05dc"
}
},
"user": { "user": {
"data": { "data": {
"username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" "password": "\u05e1\u05d9\u05e1\u05de\u05d4",
} "username": "\u05d3\u05d5\u05d0\"\u05dc"
},
"title": "\u05d9\u05e9 \u05dc\u05de\u05dc\u05d0 \u05d0\u05ea \u05e4\u05e8\u05d8\u05d9 \u05d4\u05db\u05e0\u05d9\u05e1\u05d4 \u05e9\u05dc\u05da \u05dc\u05d0\u05d3\u05d5\u05d1\u05d9"
} }
} }
} }

View File

@ -16,13 +16,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ( from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
ATTR_FORECAST,
CONF_FORECAST,
COORDINATOR,
DOMAIN,
UNDO_UPDATE_LISTENER,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,12 +39,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
undo_listener = entry.add_update_listener(update_listener) entry.async_on_unload(entry.add_update_listener(update_listener))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
COORDINATOR: coordinator,
UNDO_UPDATE_LISTENER: undo_listener,
}
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
@ -61,8 +52,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]()
if unload_ok: if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from typing import Final from typing import Final
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -48,12 +49,10 @@ ATTR_LABEL: Final = "label"
ATTR_UNIT_IMPERIAL: Final = "unit_imperial" ATTR_UNIT_IMPERIAL: Final = "unit_imperial"
ATTR_UNIT_METRIC: Final = "unit_metric" ATTR_UNIT_METRIC: Final = "unit_metric"
CONF_FORECAST: Final = "forecast" CONF_FORECAST: Final = "forecast"
COORDINATOR: Final = "coordinator"
DOMAIN: Final = "accuweather" DOMAIN: Final = "accuweather"
MANUFACTURER: Final = "AccuWeather, Inc." MANUFACTURER: Final = "AccuWeather, Inc."
MAX_FORECAST_DAYS: Final = 4 MAX_FORECAST_DAYS: Final = 4
NAME: Final = "AccuWeather" NAME: Final = "AccuWeather"
UNDO_UPDATE_LISTENER: Final = "undo_update_listener"
CONDITION_CLASSES: Final[dict[str, list[int]]] = { CONDITION_CLASSES: Final[dict[str, list[int]]] = {
ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37], ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37],
@ -235,6 +234,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: TEMP_CELSIUS, ATTR_UNIT_METRIC: TEMP_CELSIUS,
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT, ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"Ceiling": { "Ceiling": {
ATTR_DEVICE_CLASS: None, ATTR_DEVICE_CLASS: None,
@ -243,6 +243,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: LENGTH_METERS, ATTR_UNIT_METRIC: LENGTH_METERS,
ATTR_UNIT_IMPERIAL: LENGTH_FEET, ATTR_UNIT_IMPERIAL: LENGTH_FEET,
ATTR_ENABLED: True, ATTR_ENABLED: True,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"CloudCover": { "CloudCover": {
ATTR_DEVICE_CLASS: None, ATTR_DEVICE_CLASS: None,
@ -251,6 +252,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: PERCENTAGE, ATTR_UNIT_METRIC: PERCENTAGE,
ATTR_UNIT_IMPERIAL: PERCENTAGE, ATTR_UNIT_IMPERIAL: PERCENTAGE,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"DewPoint": { "DewPoint": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
@ -259,6 +261,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: TEMP_CELSIUS, ATTR_UNIT_METRIC: TEMP_CELSIUS,
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT, ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"RealFeelTemperature": { "RealFeelTemperature": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
@ -267,6 +270,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: TEMP_CELSIUS, ATTR_UNIT_METRIC: TEMP_CELSIUS,
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT, ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
ATTR_ENABLED: True, ATTR_ENABLED: True,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"RealFeelTemperatureShade": { "RealFeelTemperatureShade": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
@ -275,6 +279,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: TEMP_CELSIUS, ATTR_UNIT_METRIC: TEMP_CELSIUS,
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT, ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"Precipitation": { "Precipitation": {
ATTR_DEVICE_CLASS: None, ATTR_DEVICE_CLASS: None,
@ -283,6 +288,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: LENGTH_MILLIMETERS, ATTR_UNIT_METRIC: LENGTH_MILLIMETERS,
ATTR_UNIT_IMPERIAL: LENGTH_INCHES, ATTR_UNIT_IMPERIAL: LENGTH_INCHES,
ATTR_ENABLED: True, ATTR_ENABLED: True,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"PressureTendency": { "PressureTendency": {
ATTR_DEVICE_CLASS: "accuweather__pressure_tendency", ATTR_DEVICE_CLASS: "accuweather__pressure_tendency",
@ -299,6 +305,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: UV_INDEX, ATTR_UNIT_METRIC: UV_INDEX,
ATTR_UNIT_IMPERIAL: UV_INDEX, ATTR_UNIT_IMPERIAL: UV_INDEX,
ATTR_ENABLED: True, ATTR_ENABLED: True,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"WetBulbTemperature": { "WetBulbTemperature": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
@ -307,6 +314,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: TEMP_CELSIUS, ATTR_UNIT_METRIC: TEMP_CELSIUS,
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT, ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"WindChillTemperature": { "WindChillTemperature": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
@ -315,6 +323,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: TEMP_CELSIUS, ATTR_UNIT_METRIC: TEMP_CELSIUS,
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT, ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"Wind": { "Wind": {
ATTR_DEVICE_CLASS: None, ATTR_DEVICE_CLASS: None,
@ -323,6 +332,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR, ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR, ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
ATTR_ENABLED: True, ATTR_ENABLED: True,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
"WindGust": { "WindGust": {
ATTR_DEVICE_CLASS: None, ATTR_DEVICE_CLASS: None,
@ -331,5 +341,6 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR, ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR, ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
ATTR_ENABLED: False, ATTR_ENABLED: False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
} }

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from typing import TypedDict from typing import TypedDict
class SensorDescription(TypedDict): class SensorDescription(TypedDict, total=False):
"""Sensor description class.""" """Sensor description class."""
device_class: str | None device_class: str | None
@ -13,3 +13,4 @@ class SensorDescription(TypedDict):
unit_metric: str | None unit_metric: str | None
unit_imperial: str | None unit_imperial: str | None
enabled: bool enabled: bool
state_class: str | None

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
@ -28,7 +28,6 @@ from .const import (
ATTR_UNIT_IMPERIAL, ATTR_UNIT_IMPERIAL,
ATTR_UNIT_METRIC, ATTR_UNIT_METRIC,
ATTRIBUTION, ATTRIBUTION,
COORDINATOR,
DOMAIN, DOMAIN,
FORECAST_SENSOR_TYPES, FORECAST_SENSOR_TYPES,
MANUFACTURER, MANUFACTURER,
@ -46,9 +45,7 @@ async def async_setup_entry(
"""Add AccuWeather entities from a config_entry.""" """Add AccuWeather entities from a config_entry."""
name: str = entry.data[CONF_NAME] name: str = entry.data[CONF_NAME]
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
COORDINATOR
]
sensors: list[AccuWeatherSensor] = [] sensors: list[AccuWeatherSensor] = []
for sensor in SENSOR_TYPES: for sensor in SENSOR_TYPES:
@ -92,6 +89,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self._device_class = None self._device_class = None
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
self.forecast_day = forecast_day self.forecast_day = forecast_day
self._attr_state_class = self._description.get(ATTR_STATE_CLASS)
@property @property
def name(self) -> str: def name(self) -> str:

View File

@ -8,7 +8,7 @@ from accuweather.const import ENDPOINT
from homeassistant.components import system_health from homeassistant.components import system_health
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from .const import COORDINATOR, DOMAIN from .const import DOMAIN
@callback @callback
@ -21,8 +21,8 @@ def async_register(
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"""Get info for the info page.""" """Get info for the info page."""
remaining_requests = list(hass.data[DOMAIN].values())[0][ remaining_requests = list(hass.data[DOMAIN].values())[
COORDINATOR 0
].accuweather.requests_remaining ].accuweather.requests_remaining
return { return {

View File

@ -1,9 +1,19 @@
{ {
"config": { "config": {
"abort": {
"single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea."
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" "api_key": "\u05de\u05e4\u05ea\u05d7 API",
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
"name": "\u05e9\u05dd"
} }
} }
} }

View File

@ -13,6 +13,7 @@ from homeassistant.components.weather import (
ATTR_FORECAST_TIME, ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED, ATTR_FORECAST_WIND_SPEED,
Forecast,
WeatherEntity, WeatherEntity,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -30,7 +31,6 @@ from .const import (
ATTR_FORECAST, ATTR_FORECAST,
ATTRIBUTION, ATTRIBUTION,
CONDITION_CLASSES, CONDITION_CLASSES,
COORDINATOR,
DOMAIN, DOMAIN,
MANUFACTURER, MANUFACTURER,
NAME, NAME,
@ -45,9 +45,7 @@ async def async_setup_entry(
"""Add a AccuWeather weather entity from a config_entry.""" """Add a AccuWeather weather entity from a config_entry."""
name: str = entry.data[CONF_NAME] name: str = entry.data[CONF_NAME]
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
COORDINATOR
]
async_add_entities([AccuWeatherEntity(name, coordinator)]) async_add_entities([AccuWeatherEntity(name, coordinator)])
@ -156,12 +154,12 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
return None return None
@property @property
def forecast(self) -> list[dict[str, Any]] | None: def forecast(self) -> list[Forecast] | None:
"""Return the forecast array.""" """Return the forecast array."""
if not self.coordinator.forecast: if not self.coordinator.forecast:
return None return None
# remap keys from library to keys understood by the weather component # remap keys from library to keys understood by the weather component
forecast = [ return [
{ {
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(), ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
ATTR_FORECAST_TEMP: item["TemperatureMax"]["Value"], ATTR_FORECAST_TEMP: item["TemperatureMax"]["Value"],
@ -183,7 +181,6 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
} }
for item in self.coordinator.data[ATTR_FORECAST] for item in self.coordinator.data[ATTR_FORECAST]
] ]
return forecast
@staticmethod @staticmethod
def _calc_precipitation(day: dict[str, Any]) -> float: def _calc_precipitation(day: dict[str, Any]) -> float:

View File

@ -0,0 +1,14 @@
{
"config": {
"abort": {
"no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea"
},
"step": {
"user": {
"data": {
"id": "\u05de\u05d6\u05d4\u05d4 \u05de\u05d0\u05e8\u05d7"
}
}
}
}
}

View File

@ -1,11 +1,20 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "Host", "host": "\u05de\u05d0\u05e8\u05d7",
"password": "\u05e1\u05d9\u05e1\u05de\u05d4", "password": "\u05e1\u05d9\u05e1\u05de\u05d4",
"port": "\u05e4\u05d5\u05e8\u05d8" "port": "\u05e4\u05d5\u05e8\u05d8",
"ssl": "\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05d0\u05d9\u05e9\u05d5\u05e8 SSL",
"username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9",
"verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL"
} }
} }
} }

View File

@ -33,6 +33,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
"""Advantage Air Filter.""" """Advantage Air Filter."""
_attr_device_class = DEVICE_CLASS_PROBLEM
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
@ -43,11 +45,6 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
"""Return a unique id.""" """Return a unique id."""
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-filter' return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-filter'
@property
def device_class(self):
"""Return the device class of the vent."""
return DEVICE_CLASS_PROBLEM
@property @property
def is_on(self): def is_on(self):
"""Return if filter needs cleaning.""" """Return if filter needs cleaning."""
@ -57,6 +54,8 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
"""Advantage Air Zone Motion.""" """Advantage Air Zone Motion."""
_attr_device_class = DEVICE_CLASS_MOTION
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
@ -67,11 +66,6 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
"""Return a unique id.""" """Return a unique id."""
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-motion' return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-motion'
@property
def device_class(self):
"""Return the device class of the vent."""
return DEVICE_CLASS_MOTION
@property @property
def is_on(self): def is_on(self):
"""Return if motion is detect.""" """Return if motion is detect."""
@ -81,6 +75,8 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
"""Advantage Air Zone MyZone.""" """Advantage Air Zone MyZone."""
_attr_entity_registry_enabled_default = False
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
@ -95,8 +91,3 @@ class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
def is_on(self): def is_on(self):
"""Return if this zone is the myZone.""" """Return if this zone is the myZone."""
return self._zone["number"] == self._ac["myZone"] return self._zone["number"] == self._ac["myZone"]
@property
def entity_registry_enabled_default(self):
"""Return false to disable this entity by default."""
return False

View File

@ -6,6 +6,7 @@ from homeassistant.components.climate.const import (
FAN_HIGH, FAN_HIGH,
FAN_LOW, FAN_LOW,
FAN_MEDIUM, FAN_MEDIUM,
HVAC_MODE_AUTO,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_FAN_ONLY,
@ -31,9 +32,18 @@ ADVANTAGE_AIR_HVAC_MODES = {
"cool": HVAC_MODE_COOL, "cool": HVAC_MODE_COOL,
"vent": HVAC_MODE_FAN_ONLY, "vent": HVAC_MODE_FAN_ONLY,
"dry": HVAC_MODE_DRY, "dry": HVAC_MODE_DRY,
"myauto": HVAC_MODE_AUTO,
} }
HASS_HVAC_MODES = {v: k for k, v in ADVANTAGE_AIR_HVAC_MODES.items()} HASS_HVAC_MODES = {v: k for k, v in ADVANTAGE_AIR_HVAC_MODES.items()}
AC_HVAC_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_DRY,
]
ADVANTAGE_AIR_FAN_MODES = { ADVANTAGE_AIR_FAN_MODES = {
"auto": FAN_AUTO, "auto": FAN_AUTO,
"low": FAN_LOW, "low": FAN_LOW,
@ -43,13 +53,6 @@ ADVANTAGE_AIR_FAN_MODES = {
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()} HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100} FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
AC_HVAC_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_DRY,
]
ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone" ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone"
ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY]
@ -130,6 +133,8 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
@property @property
def hvac_modes(self): def hvac_modes(self):
"""Return the supported HVAC modes.""" """Return the supported HVAC modes."""
if self._ac.get("myAutoModeEnabled"):
return AC_HVAC_MODES + [HVAC_MODE_AUTO]
return AC_HVAC_MODES return AC_HVAC_MODES
@property @property

View File

@ -44,6 +44,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity): class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air timer control.""" """Representation of Advantage Air timer control."""
_attr_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
def __init__(self, instance, ac_key, action): def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control.""" """Initialize the Advantage Air timer control."""
super().__init__(instance, ac_key) super().__init__(instance, ac_key)
@ -65,11 +67,6 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
"""Return the current value.""" """Return the current value."""
return self._ac[self._time_key] return self._ac[self._time_key]
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
@property @property
def icon(self): def icon(self):
"""Return a representative icon of the timer.""" """Return a representative icon of the timer."""
@ -86,6 +83,8 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor.""" """Representation of Advantage Air Zone Vent Sensor."""
_attr_unit_of_measurement = PERCENTAGE
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
@ -103,11 +102,6 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
return self._zone["value"] return self._zone["value"]
return 0 return 0
@property
def unit_of_measurement(self):
"""Return the percent sign."""
return PERCENTAGE
@property @property
def icon(self): def icon(self):
"""Return a representative icon.""" """Return a representative icon."""
@ -119,6 +113,8 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """Representation of Advantage Air Zone wireless signal sensor."""
_attr_unit_of_measurement = PERCENTAGE
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
@ -134,11 +130,6 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Return the current value of the wireless signal.""" """Return the current value of the wireless signal."""
return self._zone["rssi"] return self._zone["rssi"]
@property
def unit_of_measurement(self):
"""Return the percent sign."""
return PERCENTAGE
@property @property
def icon(self): def icon(self):
"""Return a representative icon.""" """Return a representative icon."""

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": {
"user": {
"data": {
"ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP",
"port": "\u05e4\u05ea\u05d7\u05d4"
},
"description": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc- API \u05e9\u05dc \u05d4\u05d8\u05d0\u05d1\u05dc\u05d8 \u05e9\u05dc\u05da \u05d4\u05de\u05d5\u05ea\u05e7\u05df \u05e2\u05dc \u05d4\u05e7\u05d9\u05e8.",
"title": "\u05d4\u05ea\u05d7\u05d1\u05e8"
}
}
}
}

View File

@ -19,13 +19,13 @@ from .weather_update_coordinator import WeatherUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AEMET OpenData as config entry.""" """Set up AEMET OpenData as config entry."""
name = config_entry.data[CONF_NAME] name = entry.data[CONF_NAME]
api_key = config_entry.data[CONF_API_KEY] api_key = entry.data[CONF_API_KEY]
latitude = config_entry.data[CONF_LATITUDE] latitude = entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE] longitude = entry.data[CONF_LONGITUDE]
station_updates = config_entry.options.get(CONF_STATION_UPDATES, True) station_updates = entry.options.get(CONF_STATION_UPDATES, True)
aemet = AEMET(api_key) aemet = AEMET(api_key)
weather_coordinator = WeatherUpdateCoordinator( weather_coordinator = WeatherUpdateCoordinator(
@ -35,30 +35,28 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
await weather_coordinator.async_config_entry_first_refresh() await weather_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = { hass.data[DOMAIN][entry.entry_id] = {
ENTRY_NAME: name, ENTRY_NAME: name,
ENTRY_WEATHER_COORDINATOR: weather_coordinator, ENTRY_WEATHER_COORDINATOR: weather_coordinator,
} }
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
config_entry.async_on_unload(config_entry.add_update_listener(async_update_options)) entry.async_on_unload(entry.add_update_listener(async_update_options))
return True return True
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update options.""" """Update options."""
await hass.config_entries.async_reload(config_entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms( unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
config_entry, PLATFORMS
)
if unload_ok: if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok

View File

@ -18,5 +18,14 @@
"title": "[void]" "title": "[void]"
} }
} }
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "Sammeln von Daten von AEMET-Wetterstationen"
}
}
}
} }
} }

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
},
"step": {
"user": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API",
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da"
}
}
}
}
}

View File

@ -18,5 +18,14 @@
"title": "AEMET OpenData" "title": "AEMET OpenData"
} }
} }
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "Zbieraj dane ze stacji pogodowych AEMET"
}
}
}
} }
} }

View File

@ -3,10 +3,14 @@
"abort": { "abort": {
"already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" "already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8"
}, },
"error": {
"already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea",
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "Host", "host": "\u05de\u05d0\u05e8\u05d7",
"port": "\u05e4\u05d5\u05e8\u05d8" "port": "\u05e4\u05d5\u05e8\u05d8"
} }
} }

View File

@ -11,9 +11,11 @@ from airly import Airly
from airly.exceptions import AirlyError from airly.exceptions import AirlyError
import async_timeout import async_timeout
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.device_registry import async_get_registry
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -31,7 +33,7 @@ from .const import (
NO_AIRLY_SENSORS, NO_AIRLY_SENSORS,
) )
PLATFORMS = ["air_quality", "sensor"] PLATFORMS = ["sensor"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -111,6 +113,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
# Remove air_quality entities from registry if they exist
ent_reg = entity_registry.async_get(hass)
unique_id = f"{coordinator.latitude}-{coordinator.longitude}"
if entity_id := ent_reg.async_get_entity_id(
AIR_QUALITY_PLATFORM, DOMAIN, unique_id
):
_LOGGER.debug("Removing deprecated air_quality entity %s", entity_id)
ent_reg.async_remove(entity_id)
return True return True

View File

@ -1,143 +0,0 @@
"""Support for the Airly air_quality service."""
from __future__ import annotations
from typing import Any
from homeassistant.components.air_quality import (
ATTR_AQI,
ATTR_PM_2_5,
ATTR_PM_10,
AirQualityEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirlyDataUpdateCoordinator
from .const import (
ATTR_API_ADVICE,
ATTR_API_CAQI,
ATTR_API_CAQI_DESCRIPTION,
ATTR_API_CAQI_LEVEL,
ATTR_API_PM10,
ATTR_API_PM10_LIMIT,
ATTR_API_PM10_PERCENT,
ATTR_API_PM25,
ATTR_API_PM25_LIMIT,
ATTR_API_PM25_PERCENT,
ATTRIBUTION,
DEFAULT_NAME,
DOMAIN,
LABEL_ADVICE,
MANUFACTURER,
)
LABEL_AQI_DESCRIPTION = f"{ATTR_AQI}_description"
LABEL_AQI_LEVEL = f"{ATTR_AQI}_level"
LABEL_PM_2_5_LIMIT = f"{ATTR_PM_2_5}_limit"
LABEL_PM_2_5_PERCENT = f"{ATTR_PM_2_5}_percent_of_limit"
LABEL_PM_10_LIMIT = f"{ATTR_PM_10}_limit"
LABEL_PM_10_PERCENT = f"{ATTR_PM_10}_percent_of_limit"
PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Airly air_quality entity based on a config entry."""
name = entry.data[CONF_NAME]
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([AirlyAirQuality(coordinator, name)], False)
class AirlyAirQuality(CoordinatorEntity, AirQualityEntity):
"""Define an Airly air quality."""
coordinator: AirlyDataUpdateCoordinator
def __init__(self, coordinator: AirlyDataUpdateCoordinator, name: str) -> None:
"""Initialize."""
super().__init__(coordinator)
self._name = name
self._icon = "mdi:blur"
@property
def name(self) -> str:
"""Return the name."""
return self._name
@property
def icon(self) -> str:
"""Return the icon."""
return self._icon
@property
def air_quality_index(self) -> float | None:
"""Return the air quality index."""
return round_state(self.coordinator.data[ATTR_API_CAQI])
@property
def particulate_matter_2_5(self) -> float | None:
"""Return the particulate matter 2.5 level."""
return round_state(self.coordinator.data.get(ATTR_API_PM25))
@property
def particulate_matter_10(self) -> float | None:
"""Return the particulate matter 10 level."""
return round_state(self.coordinator.data.get(ATTR_API_PM10))
@property
def attribution(self) -> str:
"""Return the attribution."""
return ATTRIBUTION
@property
def unique_id(self) -> str:
"""Return a unique_id for this entity."""
return f"{self.coordinator.latitude}-{self.coordinator.longitude}"
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return {
"identifiers": {
(
DOMAIN,
f"{self.coordinator.latitude}-{self.coordinator.longitude}",
)
},
"name": DEFAULT_NAME,
"manufacturer": MANUFACTURER,
"entry_type": "service",
}
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
attrs = {
LABEL_AQI_DESCRIPTION: self.coordinator.data[ATTR_API_CAQI_DESCRIPTION],
LABEL_ADVICE: self.coordinator.data[ATTR_API_ADVICE],
LABEL_AQI_LEVEL: self.coordinator.data[ATTR_API_CAQI_LEVEL],
}
if ATTR_API_PM25 in self.coordinator.data:
attrs[LABEL_PM_2_5_LIMIT] = self.coordinator.data[ATTR_API_PM25_LIMIT]
attrs[LABEL_PM_2_5_PERCENT] = round(
self.coordinator.data[ATTR_API_PM25_PERCENT]
)
if ATTR_API_PM10 in self.coordinator.data:
attrs[LABEL_PM_10_LIMIT] = self.coordinator.data[ATTR_API_PM10_LIMIT]
attrs[LABEL_PM_10_PERCENT] = round(
self.coordinator.data[ATTR_API_PM10_PERCENT]
)
return attrs
def round_state(state: float | None) -> float | None:
"""Round state."""
return round(state) if state else state

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from typing import Final from typing import Final
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
ATTR_ICON, ATTR_ICON,
@ -23,16 +24,22 @@ ATTR_API_CAQI_DESCRIPTION: Final = "DESCRIPTION"
ATTR_API_CAQI_LEVEL: Final = "LEVEL" ATTR_API_CAQI_LEVEL: Final = "LEVEL"
ATTR_API_HUMIDITY: Final = "HUMIDITY" ATTR_API_HUMIDITY: Final = "HUMIDITY"
ATTR_API_PM10: Final = "PM10" ATTR_API_PM10: Final = "PM10"
ATTR_API_PM10_LIMIT: Final = "PM10_LIMIT"
ATTR_API_PM10_PERCENT: Final = "PM10_PERCENT"
ATTR_API_PM1: Final = "PM1" ATTR_API_PM1: Final = "PM1"
ATTR_API_PM25: Final = "PM25" ATTR_API_PM25: Final = "PM25"
ATTR_API_PM25_LIMIT: Final = "PM25_LIMIT"
ATTR_API_PM25_PERCENT: Final = "PM25_PERCENT"
ATTR_API_PRESSURE: Final = "PRESSURE" ATTR_API_PRESSURE: Final = "PRESSURE"
ATTR_API_TEMPERATURE: Final = "TEMPERATURE" ATTR_API_TEMPERATURE: Final = "TEMPERATURE"
ATTR_ADVICE: Final = "advice"
ATTR_DESCRIPTION: Final = "description"
ATTR_LABEL: Final = "label" ATTR_LABEL: Final = "label"
ATTR_LEVEL: Final = "level"
ATTR_LIMIT: Final = "limit"
ATTR_PERCENT: Final = "percent"
ATTR_UNIT: Final = "unit" ATTR_UNIT: Final = "unit"
ATTR_VALUE: Final = "value"
SUFFIX_PERCENT: Final = "PERCENT"
SUFFIX_LIMIT: Final = "LIMIT"
ATTRIBUTION: Final = "Data provided by Airly" ATTRIBUTION: Final = "Data provided by Airly"
CONF_USE_NEAREST: Final = "use_nearest" CONF_USE_NEAREST: Final = "use_nearest"
@ -45,28 +52,51 @@ MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet." NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: dict[str, SensorDescription] = { SENSOR_TYPES: dict[str, SensorDescription] = {
ATTR_API_CAQI: {
ATTR_LABEL: ATTR_API_CAQI,
ATTR_UNIT: "CAQI",
ATTR_VALUE: round,
},
ATTR_API_PM1: { ATTR_API_PM1: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur", ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_PM1, ATTR_LABEL: ATTR_API_PM1,
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_VALUE: round,
},
ATTR_API_PM25: {
ATTR_ICON: "mdi:blur",
ATTR_LABEL: "PM2.5",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_VALUE: round,
},
ATTR_API_PM10: {
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_PM10,
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_VALUE: round,
}, },
ATTR_API_HUMIDITY: { ATTR_API_HUMIDITY: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY, ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
ATTR_ICON: None,
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(), ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
ATTR_UNIT: PERCENTAGE, ATTR_UNIT: PERCENTAGE,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_VALUE: lambda value: round(value, 1),
}, },
ATTR_API_PRESSURE: { ATTR_API_PRESSURE: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
ATTR_ICON: None,
ATTR_LABEL: ATTR_API_PRESSURE.capitalize(), ATTR_LABEL: ATTR_API_PRESSURE.capitalize(),
ATTR_UNIT: PRESSURE_HPA, ATTR_UNIT: PRESSURE_HPA,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_VALUE: round,
}, },
ATTR_API_TEMPERATURE: { ATTR_API_TEMPERATURE: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(), ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(),
ATTR_UNIT: TEMP_CELSIUS, ATTR_UNIT: TEMP_CELSIUS,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_VALUE: lambda value: round(value, 1),
}, },
} }

View File

@ -1,13 +1,15 @@
"""Type definitions for Airly integration.""" """Type definitions for Airly integration."""
from __future__ import annotations from __future__ import annotations
from typing import TypedDict from typing import Callable, TypedDict
class SensorDescription(TypedDict): class SensorDescription(TypedDict, total=False):
"""Sensor description class.""" """Sensor description class."""
device_class: str | None device_class: str | None
icon: str | None icon: str | None
label: str label: str
unit: str unit: str
state_class: str | None
value: Callable

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
@ -19,15 +19,27 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirlyDataUpdateCoordinator from . import AirlyDataUpdateCoordinator
from .const import ( from .const import (
ATTR_API_PM1, ATTR_ADVICE,
ATTR_API_PRESSURE, ATTR_API_ADVICE,
ATTR_API_CAQI,
ATTR_API_CAQI_DESCRIPTION,
ATTR_API_CAQI_LEVEL,
ATTR_API_PM10,
ATTR_API_PM25,
ATTR_DESCRIPTION,
ATTR_LABEL, ATTR_LABEL,
ATTR_LEVEL,
ATTR_LIMIT,
ATTR_PERCENT,
ATTR_UNIT, ATTR_UNIT,
ATTR_VALUE,
ATTRIBUTION, ATTRIBUTION,
DEFAULT_NAME, DEFAULT_NAME,
DOMAIN, DOMAIN,
MANUFACTURER, MANUFACTURER,
SENSOR_TYPES, SENSOR_TYPES,
SUFFIX_LIMIT,
SUFFIX_PERCENT,
) )
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -60,46 +72,49 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._name = name self._description = description = SENSOR_TYPES[kind]
self._description = SENSOR_TYPES[kind] self._attr_device_class = description.get(ATTR_DEVICE_CLASS)
self._attr_icon = description.get(ATTR_ICON)
self._attr_name = f"{name} {description[ATTR_LABEL]}"
self._attr_state_class = description.get(ATTR_STATE_CLASS)
self._attr_unique_id = (
f"{coordinator.latitude}-{coordinator.longitude}-{kind.lower()}"
)
self._attr_unit_of_measurement = description.get(ATTR_UNIT)
self._attrs: dict[str, Any] = {ATTR_ATTRIBUTION: ATTRIBUTION}
self.kind = kind self.kind = kind
self._state = None
self._unit_of_measurement = None
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
@property
def name(self) -> str:
"""Return the name."""
return f"{self._name} {self._description[ATTR_LABEL]}"
@property @property
def state(self) -> StateType: def state(self) -> StateType:
"""Return the state.""" """Return the state."""
self._state = self.coordinator.data[self.kind] state = self.coordinator.data[self.kind]
if self.kind in [ATTR_API_PM1, ATTR_API_PRESSURE]: return cast(StateType, self._description[ATTR_VALUE](state))
return round(cast(float, self._state))
return round(cast(float, self._state), 1)
@property @property
def extra_state_attributes(self) -> dict[str, Any]: def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes.""" """Return the state attributes."""
if self.kind == ATTR_API_CAQI:
self._attrs[ATTR_LEVEL] = self.coordinator.data[ATTR_API_CAQI_LEVEL]
self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE]
self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[
ATTR_API_CAQI_DESCRIPTION
]
if self.kind == ATTR_API_PM25:
self._attrs[ATTR_LIMIT] = self.coordinator.data[
f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"
]
self._attrs[ATTR_PERCENT] = round(
self.coordinator.data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"]
)
if self.kind == ATTR_API_PM10:
self._attrs[ATTR_LIMIT] = self.coordinator.data[
f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"
]
self._attrs[ATTR_PERCENT] = round(
self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]
)
return self._attrs return self._attrs
@property
def icon(self) -> str | None:
"""Return the icon."""
return self._description[ATTR_ICON]
@property
def device_class(self) -> str | None:
"""Return the device_class."""
return self._description[ATTR_DEVICE_CLASS]
@property
def unique_id(self) -> str:
"""Return a unique_id for this entity."""
return f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return the device info.""" """Return the device info."""
@ -114,8 +129,3 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
"manufacturer": MANUFACTURER, "manufacturer": MANUFACTURER,
"entry_type": "service", "entry_type": "service",
} }
@property
def unit_of_measurement(self) -> str | None:
"""Return the unit the value is expressed in."""
return self._description[ATTR_UNIT]

View File

@ -1,10 +1,20 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" "api_key": "\u05de\u05e4\u05ea\u05d7 API",
} "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
"name": "\u05e9\u05dd"
},
"title": "\u05d0\u05d5\u05d5\u05e8\u05d9\u05e8\u05d9"
} }
} }
} }

View File

@ -36,7 +36,7 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AirNow from a config entry.""" """Set up AirNow from a config entry."""
api_key = entry.data[CONF_API_KEY] api_key = entry.data[CONF_API_KEY]
latitude = entry.data[CONF_LATITUDE] latitude = entry.data[CONF_LATITUDE]

View File

@ -14,7 +14,8 @@
"data": { "data": {
"api_key": "API-Schl\u00fcssel", "api_key": "API-Schl\u00fcssel",
"latitude": "Breitengrad", "latitude": "Breitengrad",
"longitude": "L\u00e4ngengrad" "longitude": "L\u00e4ngengrad",
"radius": "Stationsradius (Meilen; optional)"
}, },
"description": "Richten Sie die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuchen Sie https://docs.airnowapi.org/account/request/.", "description": "Richten Sie die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuchen Sie https://docs.airnowapi.org/account/request/.",
"title": "AirNow" "title": "AirNow"

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"step": {
"user": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API",
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da"
}
}
}
}
}

View File

@ -337,24 +337,12 @@ class AirVisualEntity(CoordinatorEntity):
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self._icon = None
self._unit = None
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
"""Return the device state attributes.""" """Return the device state attributes."""
return self._attrs return self._attrs
@property
def icon(self):
"""Return the icon."""
return self._icon
@property
def unit_of_measurement(self):
"""Return the unit the value is expressed in."""
return self._unit
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""

View File

@ -1,6 +1,5 @@
"""Support for AirVisual Node/Pro units.""" """Support for AirVisual Node/Pro units."""
from homeassistant.components.air_quality import AirQualityEntity from homeassistant.components.air_quality import AirQualityEntity
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
from homeassistant.core import callback from homeassistant.core import callback
from . import AirVisualEntity from . import AirVisualEntity
@ -34,8 +33,7 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
"""Initialize.""" """Initialize."""
super().__init__(airvisual) super().__init__(airvisual)
self._icon = "mdi:chemical-weapon" self._attr_icon = "mdi:chemical-weapon"
self._unit = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
@property @property
def air_quality_index(self): def air_quality_index(self):

View File

@ -56,57 +56,32 @@ NODE_PRO_SENSORS = [
(SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS), (SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS),
] ]
POLLUTANT_LABELS = {
"co": "Carbon Monoxide",
"n2": "Nitrogen Dioxide",
"o3": "Ozone",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Sulfur Dioxide",
}
@callback POLLUTANT_LEVELS = {
def async_get_pollutant_label(symbol): (0, 50): ("Good", "mdi:emoticon-excited"),
"""Get a pollutant's label based on its symbol.""" (51, 100): ("Moderate", "mdi:emoticon-happy"),
if symbol == "co": (101, 150): ("Unhealthy for sensitive groups", "mdi:emoticon-neutral"),
return "Carbon Monoxide" (151, 200): ("Unhealthy", "mdi:emoticon-sad"),
if symbol == "n2": (201, 300): ("Very unhealthy", "mdi:emoticon-dead"),
return "Nitrogen Dioxide" (301, 1000): ("Hazardous", "mdi:biohazard"),
if symbol == "o3": }
return "Ozone"
if symbol == "p1":
return "PM10"
if symbol == "p2":
return "PM2.5"
if symbol == "s2":
return "Sulfur Dioxide"
return symbol
POLLUTANT_UNITS = {
@callback "co": CONCENTRATION_PARTS_PER_MILLION,
def async_get_pollutant_level_info(value): "n2": CONCENTRATION_PARTS_PER_BILLION,
"""Return a verbal pollutant level (and associated icon) for a numeric value.""" "o3": CONCENTRATION_PARTS_PER_BILLION,
if 0 <= value <= 50: "p1": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
return ("Good", "mdi:emoticon-excited") "p2": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
if 51 <= value <= 100: "s2": CONCENTRATION_PARTS_PER_BILLION,
return ("Moderate", "mdi:emoticon-happy") }
if 101 <= value <= 150:
return ("Unhealthy for sensitive groups", "mdi:emoticon-neutral")
if 151 <= value <= 200:
return ("Unhealthy", "mdi:emoticon-sad")
if 201 <= value <= 300:
return ("Very Unhealthy", "mdi:emoticon-dead")
return ("Hazardous", "mdi:biohazard")
@callback
def async_get_pollutant_unit(symbol):
"""Get a pollutant's unit based on its symbol."""
if symbol == "co":
return CONCENTRATION_PARTS_PER_MILLION
if symbol == "n2":
return CONCENTRATION_PARTS_PER_BILLION
if symbol == "o3":
return CONCENTRATION_PARTS_PER_BILLION
if symbol == "p1":
return CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
if symbol == "p2":
return CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
if symbol == "s2":
return CONCENTRATION_PARTS_PER_BILLION
return None
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
@ -154,12 +129,13 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
} }
) )
self._config_entry = config_entry self._config_entry = config_entry
self._icon = icon
self._kind = kind self._kind = kind
self._locale = locale self._locale = locale
self._name = name self._name = name
self._state = None self._state = None
self._unit = unit
self._attr_icon = icon
self._attr_unit_of_measurement = unit
@property @property
def available(self): def available(self):
@ -196,16 +172,20 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
if self._kind == SENSOR_KIND_LEVEL: if self._kind == SENSOR_KIND_LEVEL:
aqi = data[f"aqi{self._locale}"] aqi = data[f"aqi{self._locale}"]
self._state, self._icon = async_get_pollutant_level_info(aqi) [(self._state, self._attr_icon)] = [
(name, icon)
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
if floor <= aqi <= ceiling
]
elif self._kind == SENSOR_KIND_AQI: elif self._kind == SENSOR_KIND_AQI:
self._state = data[f"aqi{self._locale}"] self._state = data[f"aqi{self._locale}"]
elif self._kind == SENSOR_KIND_POLLUTANT: elif self._kind == SENSOR_KIND_POLLUTANT:
symbol = data[f"main{self._locale}"] symbol = data[f"main{self._locale}"]
self._state = async_get_pollutant_label(symbol) self._state = POLLUTANT_LABELS[symbol]
self._attrs.update( self._attrs.update(
{ {
ATTR_POLLUTANT_SYMBOL: symbol, ATTR_POLLUTANT_SYMBOL: symbol,
ATTR_POLLUTANT_UNIT: async_get_pollutant_unit(symbol), ATTR_POLLUTANT_UNIT: POLLUTANT_UNITS[symbol],
} }
) )
@ -244,16 +224,12 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._device_class = device_class
self._kind = kind self._kind = kind
self._name = name self._name = name
self._state = None self._state = None
self._unit = unit
@property self._attr_device_class = device_class
def device_class(self): self._attr_unit_of_measurement = unit
"""Return the device class."""
return self._device_class
@property @property
def device_info(self): def device_info(self):

View File

@ -1,12 +1,37 @@
{ {
"config": { "config": {
"abort": {
"reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7"
},
"error": { "error": {
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9 \u05e1\u05d5\u05e4\u05e7" "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"general_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4",
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9 \u05e1\u05d5\u05e4\u05e7",
"location_not_found": "\u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0"
}, },
"step": { "step": {
"geography_by_coords": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API",
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da"
}
},
"geography_by_name": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
}
},
"node_pro": { "node_pro": {
"data": { "data": {
"ip_address": "\u05de\u05d0\u05e8\u05d7",
"password": "\u05e1\u05d9\u05e1\u05de\u05d4" "password": "\u05e1\u05d9\u05e1\u05de\u05d4"
},
"description": "\u05e2\u05e7\u05d5\u05d1 \u05d0\u05d7\u05e8 \u05d9\u05d7\u05d9\u05d3\u05ea AirVisual \u05d0\u05d9\u05e9\u05d9\u05ea. \u05e0\u05d9\u05ea\u05df \u05dc\u05d0\u05d7\u05d6\u05e8 \u05d0\u05ea \u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05de\u05de\u05e9\u05e7 \u05d4\u05de\u05e9\u05ea\u05de\u05e9 \u05e9\u05dc \u05d4\u05d9\u05d7\u05d9\u05d3\u05d4."
},
"reauth_confirm": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
} }
} }
} }

View File

@ -1,7 +1,6 @@
"""Component to interface with an alarm control panel.""" """Component to interface with an alarm control panel."""
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any, Final, final from typing import Any, Final, final
@ -113,20 +112,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
class AlarmControlPanelEntity(Entity): class AlarmControlPanelEntity(Entity):
"""An abstract class for alarm control entities.""" """An abstract class for alarm control entities."""
_attr_changed_by: str | None = None
_attr_code_arm_required: bool = True
_attr_code_format: str | None = None
_attr_supported_features: int
@property @property
def code_format(self) -> str | None: def code_format(self) -> str | None:
"""Regex for code format or None if no code is required.""" """Regex for code format or None if no code is required."""
return None return self._attr_code_format
@property @property
def changed_by(self) -> str | None: def changed_by(self) -> str | None:
"""Last change triggered by.""" """Last change triggered by."""
return None return self._attr_changed_by
@property @property
def code_arm_required(self) -> bool: def code_arm_required(self) -> bool:
"""Whether the code is required for arm actions.""" """Whether the code is required for arm actions."""
return True return self._attr_code_arm_required
def alarm_disarm(self, code: str | None = None) -> None: def alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
@ -177,9 +181,9 @@ class AlarmControlPanelEntity(Entity):
await self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code) await self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code)
@property @property
@abstractmethod
def supported_features(self) -> int: def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""
return self._attr_supported_features
@final @final
@property @property

View File

@ -8,7 +8,6 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
CONF_CODE, CONF_CODE,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DOMAIN, CONF_DOMAIN,
@ -23,6 +22,7 @@ from homeassistant.const import (
from homeassistant.core import Context, HomeAssistant from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ATTR_CODE_ARM_REQUIRED, DOMAIN from . import ATTR_CODE_ARM_REQUIRED, DOMAIN
@ -62,59 +62,24 @@ async def async_get_actions(
if entry.domain != DOMAIN: if entry.domain != DOMAIN:
continue continue
state = hass.states.get(entry.entity_id) supported_features = get_supported_features(hass, entry.entity_id)
# We need a state or else we can't populate the HVAC and preset modes. base_action = {
if state is None: CONF_DEVICE_ID: device_id,
continue CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] }
# Add actions for each entity that belongs to this integration # Add actions for each entity that belongs to this integration
if supported_features & SUPPORT_ALARM_ARM_AWAY: if supported_features & SUPPORT_ALARM_ARM_AWAY:
actions.append( actions.append({**base_action, CONF_TYPE: "arm_away"})
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_away",
}
)
if supported_features & SUPPORT_ALARM_ARM_HOME: if supported_features & SUPPORT_ALARM_ARM_HOME:
actions.append( actions.append({**base_action, CONF_TYPE: "arm_home"})
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_home",
}
)
if supported_features & SUPPORT_ALARM_ARM_NIGHT: if supported_features & SUPPORT_ALARM_ARM_NIGHT:
actions.append( actions.append({**base_action, CONF_TYPE: "arm_night"})
{ actions.append({**base_action, CONF_TYPE: "disarm"})
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_night",
}
)
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "disarm",
}
)
if supported_features & SUPPORT_ALARM_TRIGGER: if supported_features & SUPPORT_ALARM_TRIGGER:
actions.append( actions.append({**base_action, CONF_TYPE: "trigger"})
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "trigger",
}
)
return actions return actions
@ -147,6 +112,8 @@ async def async_get_action_capabilities(
hass: HomeAssistant, config: ConfigType hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]: ) -> dict[str, vol.Schema]:
"""List action capabilities.""" """List action capabilities."""
# We need to refer to the state directly because ATTR_CODE_ARM_REQUIRED is not a
# capability attribute
state = hass.states.get(config[CONF_ENTITY_ID]) state = hass.states.get(config[CONF_ENTITY_ID])
code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False

View File

@ -13,7 +13,6 @@ from homeassistant.components.alarm_control_panel.const import (
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
CONF_CONDITION, CONF_CONDITION,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DOMAIN, CONF_DOMAIN,
@ -29,6 +28,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import condition, config_validation as cv, entity_registry from homeassistant.helpers import condition, config_validation as cv, entity_registry
from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from . import DOMAIN from . import DOMAIN
@ -70,70 +70,29 @@ async def async_get_conditions(
if entry.domain != DOMAIN: if entry.domain != DOMAIN:
continue continue
state = hass.states.get(entry.entity_id) supported_features = get_supported_features(hass, entry.entity_id)
# We need a state or else we can't populate the different armed conditions
if state is None:
continue
supported_features = state.attributes[ATTR_SUPPORTED_FEATURES]
# Add conditions for each entity that belongs to this integration # Add conditions for each entity that belongs to this integration
base_condition = {
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
}
conditions += [ conditions += [
{ {**base_condition, CONF_TYPE: CONDITION_DISARMED},
CONF_CONDITION: "device", {**base_condition, CONF_TYPE: CONDITION_TRIGGERED},
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_DISARMED,
},
{
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_TRIGGERED,
},
] ]
if supported_features & SUPPORT_ALARM_ARM_HOME: if supported_features & SUPPORT_ALARM_ARM_HOME:
conditions.append( conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_HOME})
{
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_ARMED_HOME,
}
)
if supported_features & SUPPORT_ALARM_ARM_AWAY: if supported_features & SUPPORT_ALARM_ARM_AWAY:
conditions.append( conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_AWAY})
{
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_ARMED_AWAY,
}
)
if supported_features & SUPPORT_ALARM_ARM_NIGHT: if supported_features & SUPPORT_ALARM_ARM_NIGHT:
conditions.append( conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_NIGHT})
{
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_ARMED_NIGHT,
}
)
if supported_features & SUPPORT_ALARM_ARM_CUSTOM_BYPASS: if supported_features & SUPPORT_ALARM_ARM_CUSTOM_BYPASS:
conditions.append( conditions.append(
{ {**base_condition, CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS}
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS,
}
) )
return conditions return conditions

View File

@ -11,10 +11,9 @@ from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_NIGHT, SUPPORT_ALARM_ARM_NIGHT,
) )
from homeassistant.components.automation import AutomationActionType from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import ( from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DOMAIN, CONF_DOMAIN,
CONF_ENTITY_ID, CONF_ENTITY_ID,
@ -30,6 +29,7 @@ from homeassistant.const import (
) )
from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import DOMAIN from . import DOMAIN
@ -41,7 +41,7 @@ TRIGGER_TYPES: Final[set[str]] = BASIC_TRIGGER_TYPES | {
"armed_night", "armed_night",
} }
TRIGGER_SCHEMA: Final = TRIGGER_BASE_SCHEMA.extend( TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
@ -62,13 +62,7 @@ async def async_get_triggers(
if entry.domain != DOMAIN: if entry.domain != DOMAIN:
continue continue
entity_state = hass.states.get(entry.entity_id) supported_features = get_supported_features(hass, entry.entity_id)
# We need a state or else we can't populate the HVAC and preset modes.
if entity_state is None:
continue
supported_features = entity_state.attributes[ATTR_SUPPORTED_FEATURES]
# Add triggers for each entity that belongs to this integration # Add triggers for each entity that belongs to this integration
base_trigger = { base_trigger = {

View File

@ -16,40 +16,58 @@
"device_path": "Ger\u00e4tepfad", "device_path": "Ger\u00e4tepfad",
"host": "Host", "host": "Host",
"port": "Port" "port": "Port"
} },
"title": "Verbindungseinstellungen konfigurieren"
}, },
"user": { "user": {
"data": { "data": {
"protocol": "Protokoll" "protocol": "Protokoll"
} },
"title": "W\u00e4hlen Sie das AlarmDecoder-Protokoll"
} }
} }
}, },
"options": { "options": {
"error": {
"int": "Das Feld unten muss eine ganze Zahl sein.",
"loop_range": "RF Loop muss eine ganze Zahl zwischen 1 und 4 sein.",
"loop_rfid": "RF Loop kann nicht ohne RF Serial verwendet werden.",
"relay_inclusive": "Relaisadresse und Relaiskanal sind abh\u00e4ngig voneinander und m\u00fcssen zusammen aufgenommen werden."
},
"step": { "step": {
"arm_settings": { "arm_settings": {
"data": { "data": {
"alt_night_mode": "Alternativer Nachtmodus" "alt_night_mode": "Alternativer Nachtmodus",
} "auto_bypass": "Automatischer Bypass bei Scharfschaltung",
"code_arm_required": "Code f\u00fcr Scharfschaltung erforderlich"
},
"title": "AlarmDecoder konfigurieren"
}, },
"init": { "init": {
"data": { "data": {
"edit_select": "Bearbeiten" "edit_select": "Bearbeiten"
}, },
"description": "Was m\u00f6chtest du bearbeiten?" "description": "Was m\u00f6chtest du bearbeiten?",
"title": "AlarmDecoder konfigurieren"
}, },
"zone_details": { "zone_details": {
"data": { "data": {
"zone_loop": "RF Loop",
"zone_name": "Zonenname", "zone_name": "Zonenname",
"zone_relayaddr": "Relais-Adresse", "zone_relayaddr": "Relais-Adresse",
"zone_relaychan": "Relaiskanal",
"zone_rfid": "RF Serial",
"zone_type": "Zonentyp" "zone_type": "Zonentyp"
} },
"description": "Geben Sie Details f\u00fcr Zone {zone_number} ein. Um Zone {zone_number} zu l\u00f6schen, lassen Sie Zonenname leer.",
"title": "AlarmDecoder konfigurieren"
}, },
"zone_select": { "zone_select": {
"data": { "data": {
"zone_number": "Zonennummer" "zone_number": "Zonennummer"
}, },
"description": "Gib die die Zonennummer ein, die du hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chtest." "description": "Gib die die Zonennummer ein, die du hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chtest.",
"title": "AlarmDecoder konfigurieren"
} }
} }
} }

View File

@ -0,0 +1,41 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": {
"protocol": {
"data": {
"device_path": "\u05e0\u05ea\u05d9\u05d1 \u05d4\u05ea\u05e7\u05df",
"host": "\u05de\u05d0\u05e8\u05d7",
"port": "\u05e4\u05ea\u05d7\u05d4"
}
},
"user": {
"data": {
"protocol": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc"
}
}
}
},
"options": {
"error": {
"relay_inclusive": "\u05db\u05ea\u05d5\u05d1\u05ea \u05de\u05de\u05e1\u05e8 \u05d5\u05e2\u05e8\u05d5\u05e5 \u05de\u05de\u05e1\u05e8 \u05d4\u05dd \u05ea\u05dc\u05d5\u05d9\u05d9 \u05e7\u05d5\u05d3 \u05d5\u05d9\u05e9 \u05dc\u05db\u05dc\u05d5\u05dc \u05d0\u05d5\u05ea\u05dd \u05d9\u05d7\u05d3."
},
"step": {
"init": {
"data": {
"edit_select": "\u05e2\u05e8\u05d5\u05da"
}
},
"zone_details": {
"data": {
"zone_relaychan": "\u05e2\u05e8\u05d5\u05e5 \u05de\u05de\u05e1\u05e8"
}
}
}
}
}

View File

@ -1155,8 +1155,6 @@ class AlexaPowerLevelController(AlexaCapability):
if self.entity.domain == fan.DOMAIN: if self.entity.domain == fan.DOMAIN:
return self.entity.attributes.get(fan.ATTR_PERCENTAGE) or 0 return self.entity.attributes.get(fan.ATTR_PERCENTAGE) or 0
return None
class AlexaSecurityPanelController(AlexaCapability): class AlexaSecurityPanelController(AlexaCapability):
"""Implements Alexa.SecurityPanelController. """Implements Alexa.SecurityPanelController.
@ -1304,6 +1302,12 @@ class AlexaModeController(AlexaCapability):
if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN): if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN):
return f"{fan.ATTR_DIRECTION}.{mode}" return f"{fan.ATTR_DIRECTION}.{mode}"
# Fan preset_mode
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
mode = self.entity.attributes.get(fan.ATTR_PRESET_MODE, None)
if mode in self.entity.attributes.get(fan.ATTR_PRESET_MODES, None):
return f"{fan.ATTR_PRESET_MODE}.{mode}"
# Cover Position # Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
# Return state instead of position when using ModeController. # Return state instead of position when using ModeController.
@ -1342,6 +1346,17 @@ class AlexaModeController(AlexaCapability):
) )
return self._resource.serialize_capability_resources() return self._resource.serialize_capability_resources()
# Fan preset_mode
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
self._resource = AlexaModeResource(
[AlexaGlobalCatalog.SETTING_PRESET], False
)
for preset_mode in self.entity.attributes.get(fan.ATTR_PRESET_MODES, []):
self._resource.add_mode(
f"{fan.ATTR_PRESET_MODE}.{preset_mode}", [preset_mode]
)
return self._resource.serialize_capability_resources()
# Cover Position Resources # Cover Position Resources
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
self._resource = AlexaModeResource( self._resource = AlexaModeResource(

View File

@ -535,6 +535,7 @@ class FanCapabilities(AlexaEntity):
if supported & fan.SUPPORT_SET_SPEED: if supported & fan.SUPPORT_SET_SPEED:
yield AlexaPercentageController(self.entity) yield AlexaPercentageController(self.entity)
yield AlexaPowerLevelController(self.entity) yield AlexaPowerLevelController(self.entity)
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
yield AlexaRangeController( yield AlexaRangeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
) )
@ -542,6 +543,10 @@ class FanCapabilities(AlexaEntity):
yield AlexaToggleController( yield AlexaToggleController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
) )
if supported & fan.SUPPORT_PRESET_MODE:
yield AlexaModeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}"
)
if supported & fan.SUPPORT_DIRECTION: if supported & fan.SUPPORT_DIRECTION:
yield AlexaModeController( yield AlexaModeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}"

View File

@ -62,7 +62,6 @@ from .errors import (
AlexaInvalidDirectiveError, AlexaInvalidDirectiveError,
AlexaInvalidValueError, AlexaInvalidValueError,
AlexaSecurityPanelAuthorizationRequired, AlexaSecurityPanelAuthorizationRequired,
AlexaSecurityPanelUnauthorizedError,
AlexaTempRangeError, AlexaTempRangeError,
AlexaUnsupportedThermostatModeError, AlexaUnsupportedThermostatModeError,
AlexaVideoActionNotPermittedForContentError, AlexaVideoActionNotPermittedForContentError,
@ -927,11 +926,9 @@ async def async_api_disarm(hass, config, directive, context):
if payload["authorization"]["type"] == "FOUR_DIGIT_PIN": if payload["authorization"]["type"] == "FOUR_DIGIT_PIN":
data["code"] = value data["code"] = value
if not await hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_ALARM_DISARM, data, blocking=True, context=context entity.domain, SERVICE_ALARM_DISARM, data, blocking=True, context=context
): )
msg = "Invalid Code"
raise AlexaSecurityPanelUnauthorizedError(msg)
response.add_context_property( response.add_context_property(
{ {
@ -961,6 +958,16 @@ async def async_api_set_mode(hass, config, directive, context):
service = fan.SERVICE_SET_DIRECTION service = fan.SERVICE_SET_DIRECTION
data[fan.ATTR_DIRECTION] = direction data[fan.ATTR_DIRECTION] = direction
# Fan preset_mode
elif instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
preset_mode = mode.split(".")[1]
if preset_mode in entity.attributes.get(fan.ATTR_PRESET_MODES):
service = fan.SERVICE_SET_PRESET_MODE
data[fan.ATTR_PRESET_MODE] = preset_mode
else:
msg = f"Entity '{entity.entity_id}' does not support Preset '{preset_mode}'"
raise AlexaInvalidValueError(msg)
# Cover Position # Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
position = mode.split(".")[1] position = mode.split(".")[1]

View File

@ -11,9 +11,9 @@ import async_timeout
from pyalmond import AbstractAlmondWebAuth, AlmondLocalAuth, WebAlmondAPI from pyalmond import AbstractAlmondWebAuth, AlmondLocalAuth, WebAlmondAPI
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries
from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.components import conversation from homeassistant.components import conversation
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_CLIENT_ID, CONF_CLIENT_ID,
CONF_CLIENT_SECRET, CONF_CLIENT_SECRET,
@ -94,14 +94,14 @@ async def async_setup(hass, config):
hass.async_create_task( hass.async_create_task(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_IMPORT}, context={"source": SOURCE_IMPORT},
data={"type": TYPE_LOCAL, "host": conf[CONF_HOST]}, data={"type": TYPE_LOCAL, "host": conf[CONF_HOST]},
) )
) )
return True return True
async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Almond config entry.""" """Set up Almond config entry."""
websession = aiohttp_client.async_get_clientsession(hass) websession = aiohttp_client.async_get_clientsession(hass)
@ -150,7 +150,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEnt
async def _configure_almond_for_ha( async def _configure_almond_for_ha(
hass: HomeAssistant, entry: config_entries.ConfigEntry, api: WebAlmondAPI hass: HomeAssistant, entry: ConfigEntry, api: WebAlmondAPI
): ):
"""Configure Almond to connect to HA.""" """Configure Almond to connect to HA."""
try: try:
@ -248,7 +248,7 @@ class AlmondAgent(conversation.AbstractConversationAgent):
"""Almond conversation agent.""" """Almond conversation agent."""
def __init__( def __init__(
self, hass: HomeAssistant, api: WebAlmondAPI, entry: config_entries.ConfigEntry self, hass: HomeAssistant, api: WebAlmondAPI, entry: ConfigEntry
) -> None: ) -> None:
"""Initialize the agent.""" """Initialize the agent."""
self.hass = hass self.hass = hass

View File

@ -0,0 +1,15 @@
{
"config": {
"abort": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"missing_configuration": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05e8\u05db\u05d9\u05d1 \u05dc\u05d0 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e0\u05d0 \u05e2\u05e7\u05d5\u05d1 \u05d0\u05d7\u05e8 \u05d4\u05ea\u05d9\u05e2\u05d5\u05d3.",
"no_url_available": "\u05d0\u05d9\u05df \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05d6\u05de\u05d9\u05e0\u05d4. \u05e7\u05d1\u05dc\u05ea \u05de\u05d9\u05d3\u05e2 \u05e2\u05dc \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d6\u05d5, [\u05e2\u05d9\u05d9\u05df \u05d1\u05e1\u05e2\u05d9\u05e3 \u05d4\u05e2\u05d6\u05e8\u05d4] ({docs_url})",
"single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea."
},
"step": {
"pick_implementation": {
"title": "\u05d1\u05d7\u05e8 \u05e9\u05d9\u05d8\u05ea \u05d0\u05d9\u05de\u05d5\u05ea"
}
}
}
}

View File

@ -0,0 +1,71 @@
"""Support for Ambee."""
from __future__ import annotations
from ambee import AirQuality, Ambee, AmbeeAuthenticationError, Pollen
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN
PLATFORMS = (SENSOR_DOMAIN,)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Ambee from a config entry."""
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
client = Ambee(
api_key=entry.data[CONF_API_KEY],
latitude=entry.data[CONF_LATITUDE],
longitude=entry.data[CONF_LONGITUDE],
)
async def update_air_quality() -> AirQuality:
"""Update method for updating Ambee Air Quality data."""
try:
return await client.air_quality()
except AmbeeAuthenticationError as err:
raise ConfigEntryAuthFailed from err
air_quality: DataUpdateCoordinator[AirQuality] = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{DOMAIN}_{SERVICE_AIR_QUALITY}",
update_interval=SCAN_INTERVAL,
update_method=update_air_quality,
)
await air_quality.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][SERVICE_AIR_QUALITY] = air_quality
async def update_pollen() -> Pollen:
"""Update method for updating Ambee Pollen data."""
try:
return await client.pollen()
except AmbeeAuthenticationError as err:
raise ConfigEntryAuthFailed from err
pollen: DataUpdateCoordinator[Pollen] = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{DOMAIN}_{SERVICE_POLLEN}",
update_interval=SCAN_INTERVAL,
update_method=update_pollen,
)
await pollen.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][SERVICE_POLLEN] = pollen
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Ambee config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
del hass.data[DOMAIN][entry.entry_id]
return unload_ok

View File

@ -0,0 +1,115 @@
"""Config flow to configure the Ambee integration."""
from __future__ import annotations
from typing import Any
from ambee import Ambee, AmbeeAuthenticationError, AmbeeError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN
class AmbeeFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Ambee."""
VERSION = 1
entry: ConfigEntry | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:
session = async_get_clientsession(self.hass)
try:
client = Ambee(
api_key=user_input[CONF_API_KEY],
latitude=user_input[CONF_LATITUDE],
longitude=user_input[CONF_LONGITUDE],
session=session,
)
await client.air_quality()
except AmbeeAuthenticationError:
errors["base"] = "invalid_api_key"
except AmbeeError:
errors["base"] = "cannot_connect"
else:
return self.async_create_entry(
title=user_input[CONF_NAME],
data={
CONF_API_KEY: user_input[CONF_API_KEY],
CONF_LATITUDE: user_input[CONF_LATITUDE],
CONF_LONGITUDE: user_input[CONF_LONGITUDE],
},
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
vol.Optional(
CONF_LATITUDE, default=self.hass.config.latitude
): cv.latitude,
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
}
),
errors=errors,
)
async def async_step_reauth(self, data: dict[str, Any]) -> FlowResult:
"""Handle initiation of re-authentication with Ambee."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle re-authentication with Ambee."""
errors = {}
if user_input is not None and self.entry:
session = async_get_clientsession(self.hass)
client = Ambee(
api_key=user_input[CONF_API_KEY],
latitude=self.entry.data[CONF_LATITUDE],
longitude=self.entry.data[CONF_LONGITUDE],
session=session,
)
try:
await client.air_quality()
except AmbeeAuthenticationError:
errors["base"] = "invalid_api_key"
except AmbeeError:
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
self.entry,
data={
**self.entry.data,
CONF_API_KEY: user_input[CONF_API_KEY],
},
)
self.hass.async_create_task(
self.hass.config_entries.async_reload(self.entry.entry_id)
)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}),
errors=errors,
)

View File

@ -0,0 +1,212 @@
"""Constants for the Ambee integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Final
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_NAME,
ATTR_UNIT_OF_MEASUREMENT,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEVICE_CLASS_CO,
)
from .models import AmbeeSensor
DOMAIN: Final = "ambee"
LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(hours=1)
ATTR_ENABLED_BY_DEFAULT: Final = "enabled_by_default"
ATTR_ENTRY_TYPE: Final = "entry_type"
ENTRY_TYPE_SERVICE: Final = "service"
DEVICE_CLASS_AMBEE_RISK: Final = "ambee__risk"
SERVICE_AIR_QUALITY: Final = "air_quality"
SERVICE_POLLEN: Final = "pollen"
SERVICES: dict[str, str] = {
SERVICE_AIR_QUALITY: "Air Quality",
SERVICE_POLLEN: "Pollen",
}
SENSORS: dict[str, dict[str, AmbeeSensor]] = {
SERVICE_AIR_QUALITY: {
"particulate_matter_2_5": {
ATTR_NAME: "Particulate Matter < 2.5 μm",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
"particulate_matter_10": {
ATTR_NAME: "Particulate Matter < 10 μm",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
"sulphur_dioxide": {
ATTR_NAME: "Sulphur Dioxide (SO2)",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
"nitrogen_dioxide": {
ATTR_NAME: "Nitrogen Dioxide (NO2)",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
"ozone": {
ATTR_NAME: "Ozone",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
"carbon_monoxide": {
ATTR_NAME: "Carbon Monoxide (CO)",
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_MILLION,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
"air_quality_index": {
ATTR_NAME: "Air Quality Index (AQI)",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
},
SERVICE_POLLEN: {
"grass": {
ATTR_NAME: "Grass Pollen",
ATTR_ICON: "mdi:grass",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
},
"tree": {
ATTR_NAME: "Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
},
"weed": {
ATTR_NAME: "Weed Pollen",
ATTR_ICON: "mdi:sprout",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
},
"grass_risk": {
ATTR_NAME: "Grass Pollen Risk",
ATTR_ICON: "mdi:grass",
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK,
},
"tree_risk": {
ATTR_NAME: "Tree Pollen Risk",
ATTR_ICON: "mdi:tree",
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK,
},
"weed_risk": {
ATTR_NAME: "Weed Pollen Risk",
ATTR_ICON: "mdi:sprout",
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK,
},
"grass_poaceae": {
ATTR_NAME: "Poaceae Grass Pollen",
ATTR_ICON: "mdi:grass",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_alder": {
ATTR_NAME: "Alder Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_birch": {
ATTR_NAME: "Birch Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_cypress": {
ATTR_NAME: "Cypress Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_elm": {
ATTR_NAME: "Elm Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_hazel": {
ATTR_NAME: "Hazel Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_oak": {
ATTR_NAME: "Oak Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_pine": {
ATTR_NAME: "Pine Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_plane": {
ATTR_NAME: "Plane Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"tree_poplar": {
ATTR_NAME: "Poplar Tree Pollen",
ATTR_ICON: "mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"weed_chenopod": {
ATTR_NAME: "Chenopod Weed Pollen",
ATTR_ICON: "mdi:sprout",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"weed_mugwort": {
ATTR_NAME: "Mugwort Weed Pollen",
ATTR_ICON: "mdi:sprout",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"weed_nettle": {
ATTR_NAME: "Nettle Weed Pollen",
ATTR_ICON: "mdi:sprout",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
"weed_ragweed": {
ATTR_NAME: "Ragweed Weed Pollen",
ATTR_ICON: "mdi:sprout",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False,
},
},
}

View File

@ -0,0 +1,10 @@
{
"domain": "ambee",
"name": "Ambee",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambee",
"requirements": ["ambee==0.3.0"],
"codeowners": ["@frenck"],
"quality_scale": "platinum",
"iot_class": "cloud_polling"
}

View File

@ -0,0 +1,15 @@
"""Models helper class for the Ambee integration."""
from __future__ import annotations
from typing import TypedDict
class AmbeeSensor(TypedDict, total=False):
"""Represent an Ambee Sensor."""
device_class: str
enabled_by_default: bool
icon: str
name: str
state_class: str
unit_of_measurement: str

View File

@ -0,0 +1,99 @@
"""Support for Ambee sensors."""
from __future__ import annotations
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
DOMAIN as SENSOR_DOMAIN,
SensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_IDENTIFIERS,
ATTR_MANUFACTURER,
ATTR_NAME,
ATTR_UNIT_OF_MEASUREMENT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from .const import (
ATTR_ENABLED_BY_DEFAULT,
ATTR_ENTRY_TYPE,
DOMAIN,
ENTRY_TYPE_SERVICE,
SENSORS,
SERVICES,
)
from .models import AmbeeSensor
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Ambee sensors based on a config entry."""
async_add_entities(
AmbeeSensorEntity(
coordinator=hass.data[DOMAIN][entry.entry_id][service_key],
entry_id=entry.entry_id,
sensor_key=sensor_key,
sensor=sensor,
service_key=service_key,
service=SERVICES[service_key],
)
for service_key, service_sensors in SENSORS.items()
for sensor_key, sensor in service_sensors.items()
)
class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
"""Defines an Ambee sensor."""
def __init__(
self,
*,
coordinator: DataUpdateCoordinator,
entry_id: str,
sensor_key: str,
sensor: AmbeeSensor,
service_key: str,
service: str,
) -> None:
"""Initialize Ambee sensor."""
super().__init__(coordinator=coordinator)
self._sensor_key = sensor_key
self._service_key = service_key
self.entity_id = f"{SENSOR_DOMAIN}.{service_key}_{sensor_key}"
self._attr_device_class = sensor.get(ATTR_DEVICE_CLASS)
self._attr_entity_registry_enabled_default = sensor.get(
ATTR_ENABLED_BY_DEFAULT, True
)
self._attr_icon = sensor.get(ATTR_ICON)
self._attr_name = sensor.get(ATTR_NAME)
self._attr_state_class = sensor.get(ATTR_STATE_CLASS)
self._attr_unique_id = f"{entry_id}_{service_key}_{sensor_key}"
self._attr_unit_of_measurement = sensor.get(ATTR_UNIT_OF_MEASUREMENT)
self._attr_device_info = {
ATTR_IDENTIFIERS: {(DOMAIN, f"{entry_id}_{service_key}")},
ATTR_NAME: service,
ATTR_MANUFACTURER: "Ambee",
ATTR_ENTRY_TYPE: ENTRY_TYPE_SERVICE,
}
@property
def state(self) -> StateType:
"""Return the state of the sensor."""
value = getattr(self.coordinator.data, self._sensor_key)
if isinstance(value, str):
return value.lower()
return value # type: ignore[no-any-return]

View File

@ -0,0 +1,28 @@
{
"config": {
"step": {
"user": {
"description": "Set up Ambee to integrate with Home Assistant.",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"name": "[%key:common::config_flow::data::name%]"
}
},
"reauth_confirm": {
"data": {
"description": "Re-authenticate with your Ambee account.",
"api_key": "[%key:common::config_flow::data::api_key%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]"
},
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"low": "Low",
"moderate": "Moderate",
"high": "High",
"very high": "Very High"
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Re-autenticaci\u00f3 realitzada correctament"
},
"error": {
"cannot_connect": "Ha fallat la connexi\u00f3",
"invalid_api_key": "Clau API inv\u00e0lida"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "Clau API",
"description": "Torna a autenticar-te amb el compte d'Ambee."
}
},
"user": {
"data": {
"api_key": "Clau API",
"latitude": "Latitud",
"longitude": "Longitud",
"name": "Nom"
},
"description": "Configura Ambee per a integrar-lo amb Home Assistant."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
},
"error": {
"cannot_connect": "Verbindung fehlgeschlagen",
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API-Schl\u00fcssel",
"description": "Authentifiziere dich erneut mit deinem Ambee-Konto."
}
},
"user": {
"data": {
"api_key": "API-Schl\u00fcssel",
"latitude": "Breitengrad",
"longitude": "L\u00e4ngengrad",
"name": "Name"
},
"description": "Richte Ambee f\u00fcr die Integration mit Home Assistant ein."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Re-authentication was successful"
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_api_key": "Invalid API key"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API Key",
"description": "Re-authenticate with your Ambee account."
}
},
"user": {
"data": {
"api_key": "API Key",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Name"
},
"description": "Set up Ambee to integrate with Home Assistant."
}
}
}
}

View File

@ -0,0 +1,14 @@
{
"config": {
"step": {
"reauth_confirm": {
"data": {
"description": "Vuelva a autenticarse con su cuenta de Ambee."
}
},
"user": {
"description": "Configure Ambee para que se integre con Home Assistant."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Taastuvastamine \u00f5nnestus"
},
"error": {
"cannot_connect": "\u00dchendumine nurjus",
"invalid_api_key": "Vale API v\u00f5ti"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API v\u00f5ti",
"description": "Taastuvasta Ambee konto"
}
},
"user": {
"data": {
"api_key": "API v\u00f5ti",
"latitude": "Laiuskraad",
"longitude": "Pikkuskraad",
"name": "Nimi"
},
"description": "Seadista Ambee sidumine Home Assistantiga."
}
}
}
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
}
},
"user": {
"data": {
"api_key": "\u05de\u05e4\u05ea\u05d7 API",
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
"name": "\u05e9\u05dd"
}
}
}
}
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt"
},
"error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API kulcs"
}
},
"user": {
"data": {
"api_key": "API kulcs",
"latitude": "Sz\u00e9less\u00e9g",
"longitude": "Hossz\u00fas\u00e1g",
"name": "N\u00e9v"
}
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente"
},
"error": {
"cannot_connect": "Impossibile connettersi",
"invalid_api_key": "Chiave API non valida"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "Chiave API",
"description": "Riautenticati con il tuo account Ambee."
}
},
"user": {
"data": {
"api_key": "Chiave API",
"latitude": "Latitudine",
"longitude": "Logitudine",
"name": "Nome"
},
"description": "Configura Ambee per l'integrazione con Home Assistant."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Herauthenticatie was succesvol"
},
"error": {
"cannot_connect": "Kan geen verbinding maken",
"invalid_api_key": "Ongeldige API-sleutel"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API-sleutel",
"description": "Verifieer opnieuw met uw Ambee-account."
}
},
"user": {
"data": {
"api_key": "API-sleutel",
"latitude": "Breedtegraad",
"longitude": "Lengtegraad",
"name": "Naam"
},
"description": "Stel Ambee in om te integreren met Home Assistant."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Godkjenning p\u00e5 nytt var vellykket"
},
"error": {
"cannot_connect": "Tilkobling mislyktes",
"invalid_api_key": "Ugyldig API-n\u00f8kkel"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API-n\u00f8kkel",
"description": "Autentiser p\u00e5 nytt med Ambee-kontoen din."
}
},
"user": {
"data": {
"api_key": "API-n\u00f8kkel",
"latitude": "Breddegrad",
"longitude": "Lengdegrad",
"name": "Navn"
},
"description": "Sett opp Ambee for \u00e5 integrere med Home Assistant."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119"
},
"error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"invalid_api_key": "Nieprawid\u0142owy klucz API"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "Klucz API",
"description": "Ponownie uwierzytelnij za pomoc\u0105 konta Ambee."
}
},
"user": {
"data": {
"api_key": "Klucz API",
"latitude": "Szeroko\u015b\u0107 geograficzna",
"longitude": "D\u0142ugo\u015b\u0107 geograficzna",
"name": "Nazwa"
},
"description": "Skonfiguruj Ambee, aby zintegrowa\u0107 go z Home Assistantem."
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
"invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API."
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"description": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambee."
}
},
"user": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430",
"name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"
},
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Ambee."
}
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Alt",
"low": "Baix",
"moderate": "Moderat",
"very high": "Molt alt"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Hoch",
"low": "Niedrig",
"moderate": "M\u00e4\u00dfig",
"very high": "Sehr hoch"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "High",
"low": "Low",
"moderate": "Moderate",
"very high": "Very High"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Alto",
"low": "Bajo",
"moderate": "Moderado",
"very high": "Muy alto"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "K\u00f5rge",
"low": "Madal",
"moderate": "M\u00f5\u00f5dukas",
"very high": "V\u00e4ga k\u00f5rge"
}
}
}

View File

@ -0,0 +1,9 @@
{
"state": {
"ambee__risk": {
"high": "Magas",
"low": "Alacsony",
"very high": "Nagyon magas"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Alto",
"low": "Basso",
"moderate": "Moderato",
"very high": "Molto alto"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Hoog",
"low": "Laag",
"moderate": "Matig",
"very high": "Zeer hoog"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "H\u00f8y",
"low": "Lav",
"moderate": "Moderat",
"very high": "Veldig h\u00f8y"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Wysoki",
"low": "Niski",
"moderate": "Umiarkowany",
"very high": "Bardzo wysoki"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "\u0412\u044b\u0441\u043e\u043a\u0438\u0439",
"low": "\u041d\u0438\u0437\u043a\u0438\u0439",
"moderate": "\u0423\u043c\u0435\u0440\u0435\u043d\u043d\u044b\u0439",
"very high": "\u041e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0438\u0439"
}
}
}

View File

@ -0,0 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "\u9ad8",
"low": "\u4f4e",
"moderate": "\u4e2d",
"very high": "\u6975\u9ad8"
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f"
},
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"invalid_api_key": "API \u5bc6\u9470\u7121\u6548"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API \u5bc6\u9470",
"description": "\u91cd\u65b0\u8a8d\u8b49 Ambee \u5e33\u865f\u3002"
}
},
"user": {
"data": {
"api_key": "API \u5bc6\u9470",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d93\u5ea6",
"name": "\u540d\u7a31"
},
"description": "\u8a2d\u5b9a Ambee \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002"
}
}
}
}

View File

@ -0,0 +1,11 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"missing_configuration": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05e8\u05db\u05d9\u05d1 \u05dc\u05d0 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e0\u05d0 \u05e2\u05e7\u05d5\u05d1 \u05d0\u05d7\u05e8 \u05d4\u05ea\u05d9\u05e2\u05d5\u05d3."
},
"create_entry": {
"default": "\u05d0\u05d5\u05de\u05ea \u05d1\u05d4\u05e6\u05dc\u05d7\u05d4"
}
}
}

View File

@ -1,6 +1,10 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8"
},
"error": { "error": {
"invalid_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
"no_devices": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05d4\u05ea\u05e7\u05df \u05d1\u05d7\u05e9\u05d1\u05d5\u05df" "no_devices": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05d4\u05ea\u05e7\u05df \u05d1\u05d7\u05e9\u05d1\u05d5\u05df"
}, },
"step": { "step": {

View File

@ -3,8 +3,8 @@
"name": "Android TV", "name": "Android TV",
"documentation": "https://www.home-assistant.io/integrations/androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [ "requirements": [
"adb-shell[async]==0.3.1", "adb-shell[async]==0.3.4",
"androidtv[async]==0.0.59", "androidtv[async]==0.0.60",
"pure-python-adb[async]==0.3.0.dev0" "pure-python-adb[async]==0.3.0.dev0"
], ],
"codeowners": ["@JeffLIrion"], "codeowners": ["@JeffLIrion"],

View File

@ -270,7 +270,7 @@ class AppleTVManager:
self.hass.components.persistent_notification.create( self.hass.components.persistent_notification.create(
"An irrecoverable connection problem occurred when connecting to " "An irrecoverable connection problem occurred when connecting to "
f"`f{name}`. Please go to the Integrations page and reconfigure it", f"`{name}`. Please go to the Integrations page and reconfigure it",
title=NOTIFICATION_TITLE, title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID, notification_id=NOTIFICATION_ID,
) )

View File

@ -16,7 +16,7 @@
"no_usable_service": "Es wurde ein Ger\u00e4t gefunden, aber es konnte keine M\u00f6glichkeit gefunden werden, eine Verbindung zu diesem Ger\u00e4t herzustellen. Wenn diese Meldung weiterhin erscheint, versuche, die IP-Adresse anzugeben oder den Apple TV neu zu starten.", "no_usable_service": "Es wurde ein Ger\u00e4t gefunden, aber es konnte keine M\u00f6glichkeit gefunden werden, eine Verbindung zu diesem Ger\u00e4t herzustellen. Wenn diese Meldung weiterhin erscheint, versuche, die IP-Adresse anzugeben oder den Apple TV neu zu starten.",
"unknown": "Unerwarteter Fehler" "unknown": "Unerwarteter Fehler"
}, },
"flow_title": "Apple TV: {name}", "flow_title": "{name}",
"step": { "step": {
"confirm": { "confirm": {
"description": "Es wird der Apple TV mit dem Namen \" {name} \" zu Home Assistant hinzugef\u00fcgt. \n\n ** Um den Vorgang abzuschlie\u00dfen, m\u00fcssen m\u00f6glicherweise mehrere PIN-Codes eingegeben werden. ** \n\n Bitte beachte, dass der Apple TV mit dieser Integration * nicht * ausgeschalten werden kann. Nur der Media Player in Home Assistant wird ausgeschaltet!", "description": "Es wird der Apple TV mit dem Namen \" {name} \" zu Home Assistant hinzugef\u00fcgt. \n\n ** Um den Vorgang abzuschlie\u00dfen, m\u00fcssen m\u00f6glicherweise mehrere PIN-Codes eingegeben werden. ** \n\n Bitte beachte, dass der Apple TV mit dieser Integration * nicht * ausgeschalten werden kann. Nur der Media Player in Home Assistant wird ausgeschaltet!",

View File

@ -0,0 +1,29 @@
{
"config": {
"abort": {
"already_configured_device": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea",
"no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"error": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
"no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"flow_title": "{name}",
"step": {
"pair_with_pin": {
"data": {
"pin": "\u05e7\u05d5\u05d3 PIN"
}
},
"user": {
"data": {
"device_input": "\u05d4\u05ea\u05e7\u05df"
}
}
}
}
}

View File

@ -12,7 +12,7 @@
"no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton",
"unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt"
}, },
"flow_title": "Apple TV: {name}", "flow_title": "{name}",
"step": { "step": {
"confirm": { "confirm": {
"title": "Apple TV sikeresen hozz\u00e1adva" "title": "Apple TV sikeresen hozz\u00e1adva"

View File

@ -7,7 +7,7 @@ from arcam.fmj import ConnectionFailed
from arcam.fmj.client import Client from arcam.fmj.client import Client
import async_timeout import async_timeout
from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -51,7 +51,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType):
return True return True
async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up config entry.""" """Set up config entry."""
entries = hass.data[DOMAIN_DATA_ENTRIES] entries = hass.data[DOMAIN_DATA_ENTRIES]
tasks = hass.data[DOMAIN_DATA_TASKS] tasks = hass.data[DOMAIN_DATA_TASKS]

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import voluptuous as vol import voluptuous as vol
from homeassistant.components.automation import AutomationActionType from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
CONF_DEVICE_ID, CONF_DEVICE_ID,
@ -20,7 +20,7 @@ from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, EVENT_TURN_ON from .const import DOMAIN, EVENT_TURN_ON
TRIGGER_TYPES = {"turn_on"} TRIGGER_TYPES = {"turn_on"}
TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
@ -56,7 +56,7 @@ async def async_attach_trigger(
automation_info: dict, automation_info: dict,
) -> CALLBACK_TYPE: ) -> CALLBACK_TYPE:
"""Attach a trigger.""" """Attach a trigger."""
trigger_id = automation_info.get("trigger_id") if automation_info else None trigger_data = automation_info.get("trigger_data", {}) if automation_info else {}
job = HassJob(action) job = HassJob(action)
if config[CONF_TYPE] == "turn_on": if config[CONF_TYPE] == "turn_on":
@ -69,9 +69,9 @@ async def async_attach_trigger(
job, job,
{ {
"trigger": { "trigger": {
**trigger_data,
**config, **config,
"description": f"{DOMAIN} - {entity_id}", "description": f"{DOMAIN} - {entity_id}",
"id": trigger_id,
} }
}, },
event.context, event.context,

View File

@ -5,7 +5,6 @@
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}, },
"error": {},
"flow_title": "{host}", "flow_title": "{host}",
"step": { "step": {
"confirm": { "confirm": {

View File

@ -5,7 +5,7 @@
"already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt",
"cannot_connect": "Verbindung fehlgeschlagen" "cannot_connect": "Verbindung fehlgeschlagen"
}, },
"flow_title": "Arcam FMJ auf {host}", "flow_title": "{host}",
"step": { "step": {
"confirm": { "confirm": {
"description": "M\u00f6chtest du Arcam FMJ auf `{host}` zum Home Assistant hinzuf\u00fcgen?" "description": "M\u00f6chtest du Arcam FMJ auf `{host}` zum Home Assistant hinzuf\u00fcgen?"

View File

@ -0,0 +1,22 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea",
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"flow_title": "{host}",
"step": {
"confirm": {
"description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05ea Arcam FMJ \u05d1- '{host}' \u05dc-Home Assistant?"
},
"user": {
"data": {
"host": "\u05de\u05d0\u05e8\u05d7",
"port": "\u05e4\u05ea\u05d7\u05d4"
},
"description": "\u05d0\u05e0\u05d0 \u05d4\u05d6\u05df \u05d0\u05ea \u05e9\u05dd \u05d4\u05de\u05d0\u05e8\u05d7 \u05d0\u05d5 \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4-IP \u05e9\u05dc \u05d4\u05d4\u05ea\u05e7\u05df."
}
}
}
}

View File

@ -5,6 +5,7 @@
"already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.",
"cannot_connect": "Sikertelen csatlakoz\u00e1s" "cannot_connect": "Sikertelen csatlakoz\u00e1s"
}, },
"flow_title": "{host}",
"step": { "step": {
"user": { "user": {
"data": { "data": {

View File

@ -5,6 +5,10 @@
"already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso",
"cannot_connect": "Impossibile connettersi" "cannot_connect": "Impossibile connettersi"
}, },
"error": {
"one": "Pi\u00f9",
"other": "Altri"
},
"flow_title": "{host}", "flow_title": "{host}",
"step": { "step": {
"confirm": { "confirm": {

View File

@ -111,7 +111,7 @@ async def async_setup(hass, config):
return True return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AsusWrt platform.""" """Set up AsusWrt platform."""
# import options from yaml if empty # import options from yaml if empty

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