mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Merge pull request #52627 from home-assistant/rc
This commit is contained in:
commit
933e016150
26
.coveragerc
26
.coveragerc
@ -115,12 +115,14 @@ omit =
|
||||
homeassistant/components/bmw_connected_drive/notify.py
|
||||
homeassistant/components/bmw_connected_drive/sensor.py
|
||||
homeassistant/components/bosch_shc/__init__.py
|
||||
homeassistant/components/bosch_shc/const.py
|
||||
homeassistant/components/bosch_shc/binary_sensor.py
|
||||
homeassistant/components/bosch_shc/const.py
|
||||
homeassistant/components/bosch_shc/entity.py
|
||||
homeassistant/components/bosch_shc/sensor.py
|
||||
homeassistant/components/braviatv/__init__.py
|
||||
homeassistant/components/braviatv/const.py
|
||||
homeassistant/components/braviatv/media_player.py
|
||||
homeassistant/components/braviatv/remote.py
|
||||
homeassistant/components/broadlink/__init__.py
|
||||
homeassistant/components/broadlink/const.py
|
||||
homeassistant/components/broadlink/remote.py
|
||||
@ -152,7 +154,7 @@ omit =
|
||||
homeassistant/components/clicksend_tts/notify.py
|
||||
homeassistant/components/cmus/media_player.py
|
||||
homeassistant/components/co2signal/*
|
||||
homeassistant/components/coinbase/*
|
||||
homeassistant/components/coinbase/sensor.py
|
||||
homeassistant/components/comed_hourly_pricing/sensor.py
|
||||
homeassistant/components/comfoconnect/fan.py
|
||||
homeassistant/components/concord232/alarm_control_panel.py
|
||||
@ -182,11 +184,9 @@ omit =
|
||||
homeassistant/components/denonavr/media_player.py
|
||||
homeassistant/components/denonavr/receiver.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/const.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/light.py
|
||||
homeassistant/components/devolo_home_control/sensor.py
|
||||
@ -221,6 +221,7 @@ omit =
|
||||
homeassistant/components/ecobee/__init__.py
|
||||
homeassistant/components/ecobee/binary_sensor.py
|
||||
homeassistant/components/ecobee/climate.py
|
||||
homeassistant/components/ecobee/humidifier.py
|
||||
homeassistant/components/ecobee/notify.py
|
||||
homeassistant/components/ecobee/sensor.py
|
||||
homeassistant/components/ecobee/weather.py
|
||||
@ -273,6 +274,7 @@ omit =
|
||||
homeassistant/components/esphome/entry_data.py
|
||||
homeassistant/components/esphome/fan.py
|
||||
homeassistant/components/esphome/light.py
|
||||
homeassistant/components/esphome/number.py
|
||||
homeassistant/components/esphome/sensor.py
|
||||
homeassistant/components/esphome/switch.py
|
||||
homeassistant/components/essent/sensor.py
|
||||
@ -341,6 +343,7 @@ omit =
|
||||
homeassistant/components/fritz/device_tracker.py
|
||||
homeassistant/components/fritz/sensor.py
|
||||
homeassistant/components/fritz/services.py
|
||||
homeassistant/components/fritz/switch.py
|
||||
homeassistant/components/fritzbox_callmonitor/__init__.py
|
||||
homeassistant/components/fritzbox_callmonitor/const.py
|
||||
homeassistant/components/fritzbox_callmonitor/base.py
|
||||
@ -541,15 +544,12 @@ omit =
|
||||
homeassistant/components/lastfm/sensor.py
|
||||
homeassistant/components/launch_library/const.py
|
||||
homeassistant/components/launch_library/sensor.py
|
||||
homeassistant/components/lcn/__init__.py
|
||||
homeassistant/components/lcn/binary_sensor.py
|
||||
homeassistant/components/lcn/climate.py
|
||||
homeassistant/components/lcn/const.py
|
||||
homeassistant/components/lcn/cover.py
|
||||
homeassistant/components/lcn/helpers.py
|
||||
homeassistant/components/lcn/light.py
|
||||
homeassistant/components/lcn/scene.py
|
||||
homeassistant/components/lcn/schemas.py
|
||||
homeassistant/components/lcn/sensor.py
|
||||
homeassistant/components/lcn/services.py
|
||||
homeassistant/components/lcn/switch.py
|
||||
@ -613,6 +613,7 @@ omit =
|
||||
homeassistant/components/meteoalarm/*
|
||||
homeassistant/components/meteoclimatic/__init__.py
|
||||
homeassistant/components/meteoclimatic/const.py
|
||||
homeassistant/components/meteoclimatic/sensor.py
|
||||
homeassistant/components/meteoclimatic/weather.py
|
||||
homeassistant/components/metoffice/sensor.py
|
||||
homeassistant/components/metoffice/weather.py
|
||||
@ -690,7 +691,7 @@ omit =
|
||||
homeassistant/components/niko_home_control/light.py
|
||||
homeassistant/components/nilu/air_quality.py
|
||||
homeassistant/components/nissan_leaf/*
|
||||
homeassistant/components/nmap_tracker/device_tracker.py
|
||||
homeassistant/components/nmap_tracker/*
|
||||
homeassistant/components/nmbs/sensor.py
|
||||
homeassistant/components/notion/__init__.py
|
||||
homeassistant/components/notion/binary_sensor.py
|
||||
@ -769,6 +770,7 @@ omit =
|
||||
homeassistant/components/pcal9535a/*
|
||||
homeassistant/components/pencom/switch.py
|
||||
homeassistant/components/philips_js/__init__.py
|
||||
homeassistant/components/philips_js/light.py
|
||||
homeassistant/components/philips_js/media_player.py
|
||||
homeassistant/components/philips_js/remote.py
|
||||
homeassistant/components/pi_hole/sensor.py
|
||||
@ -846,6 +848,8 @@ omit =
|
||||
homeassistant/components/ripple/sensor.py
|
||||
homeassistant/components/rituals_perfume_genie/binary_sensor.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/switch.py
|
||||
homeassistant/components/rituals_perfume_genie/__init__.py
|
||||
@ -921,9 +925,11 @@ omit =
|
||||
homeassistant/components/slack/notify.py
|
||||
homeassistant/components/sia/__init__.py
|
||||
homeassistant/components/sia/alarm_control_panel.py
|
||||
homeassistant/components/sia/binary_sensor.py
|
||||
homeassistant/components/sia/const.py
|
||||
homeassistant/components/sia/hub.py
|
||||
homeassistant/components/sia/utils.py
|
||||
homeassistant/components/sia/sia_entity_base.py
|
||||
homeassistant/components/sinch/*
|
||||
homeassistant/components/slide/*
|
||||
homeassistant/components/sma/__init__.py
|
||||
@ -943,6 +949,7 @@ omit =
|
||||
homeassistant/components/snmp/*
|
||||
homeassistant/components/sochain/sensor.py
|
||||
homeassistant/components/solaredge/__init__.py
|
||||
homeassistant/components/solaredge/coordinator.py
|
||||
homeassistant/components/solaredge/sensor.py
|
||||
homeassistant/components/solaredge_local/sensor.py
|
||||
homeassistant/components/solarlog/*
|
||||
@ -970,6 +977,7 @@ omit =
|
||||
homeassistant/components/squeezebox/__init__.py
|
||||
homeassistant/components/squeezebox/browse_media.py
|
||||
homeassistant/components/squeezebox/media_player.py
|
||||
homeassistant/components/ssdp/util.py
|
||||
homeassistant/components/starline/*
|
||||
homeassistant/components/starlingbank/sensor.py
|
||||
homeassistant/components/steam_online/sensor.py
|
||||
@ -985,6 +993,7 @@ omit =
|
||||
homeassistant/components/swiss_public_transport/sensor.py
|
||||
homeassistant/components/swisscom/device_tracker.py
|
||||
homeassistant/components/switchbot/switch.py
|
||||
homeassistant/components/switcher_kis/sensor.py
|
||||
homeassistant/components/switcher_kis/switch.py
|
||||
homeassistant/components/switchmate/switch.py
|
||||
homeassistant/components/syncthing/__init__.py
|
||||
@ -1206,6 +1215,7 @@ omit =
|
||||
homeassistant/components/xmpp/notify.py
|
||||
homeassistant/components/xs1/*
|
||||
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
|
||||
homeassistant/components/yamaha_musiccast/__init__.py
|
||||
homeassistant/components/yamaha_musiccast/media_player.py
|
||||
homeassistant/components/yandex_transport/*
|
||||
homeassistant/components/yeelightsunflower/light.py
|
||||
|
16
.github/workflows/builder.yml
vendored
16
.github/workflows/builder.yml
vendored
@ -102,13 +102,13 @@ jobs:
|
||||
version="$(python setup.py -V)"
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -154,13 +154,13 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -217,13 +217,13 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -307,5 +307,9 @@ jobs:
|
||||
create_manifest "${docker_reg}" "latest" "${{ needs.init.outputs.version }}"
|
||||
create_manifest "${docker_reg}" "beta" "${{ 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
|
||||
done
|
||||
|
60
.github/workflows/ci.yaml
vendored
60
.github/workflows/ci.yaml
vendored
@ -10,7 +10,7 @@ on:
|
||||
pull_request: ~
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 1
|
||||
CACHE_VERSION: 2
|
||||
DEFAULT_PYTHON: 3.8
|
||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||
SQLALCHEMY_WARN_20: 1
|
||||
@ -41,7 +41,7 @@ jobs:
|
||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@ -65,7 +65,7 @@ jobs:
|
||||
hashFiles('.pre-commit-config.yaml') }}"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: >-
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -104,7 +104,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -132,7 +132,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -144,7 +144,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -172,7 +172,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -184,7 +184,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -234,7 +234,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -246,7 +246,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -277,7 +277,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -289,7 +289,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -320,7 +320,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -332,7 +332,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -360,7 +360,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -372,7 +372,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -403,7 +403,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -415,7 +415,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -454,7 +454,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -466,7 +466,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
@ -496,7 +496,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
@ -525,7 +525,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
@ -561,7 +561,7 @@ jobs:
|
||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@ -598,7 +598,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
@ -629,7 +629,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
@ -663,7 +663,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
@ -700,7 +700,7 @@ jobs:
|
||||
-p no:sugar \
|
||||
tests
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v2.2.3
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-group${{ matrix.group }}
|
||||
path: .coverage
|
||||
@ -721,7 +721,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.5
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
@ -740,4 +740,4 @@ jobs:
|
||||
coverage report --fail-under=94
|
||||
coverage xml
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1.5.0
|
||||
uses: codecov/codecov-action@v1.5.2
|
||||
|
10
.github/workflows/wheels.yml
vendored
10
.github/workflows/wheels.yml
vendored
@ -45,13 +45,13 @@ jobs:
|
||||
) > .env_file
|
||||
|
||||
- name: Upload env_file
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
name: env_file
|
||||
path: ./.env_file
|
||||
|
||||
- name: Upload requirements_diff
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
name: requirements_diff
|
||||
path: ./requirements_diff.txt
|
||||
@ -65,7 +65,6 @@ jobs:
|
||||
matrix:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
tag:
|
||||
- "3.8-alpine3.12"
|
||||
- "3.9-alpine3.13"
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@ -82,7 +81,7 @@ jobs:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2021.05.4
|
||||
uses: home-assistant/wheels@2021.06.0
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
@ -106,7 +105,6 @@ jobs:
|
||||
matrix:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
tag:
|
||||
- "3.8-alpine3.12"
|
||||
- "3.9-alpine3.13"
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@ -152,7 +150,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2021.05.4
|
||||
uses: home-assistant/wheels@2021.06.0
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
|
@ -5,7 +5,7 @@ repos:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.5b1
|
||||
rev: 21.6b0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
@ -12,6 +12,7 @@ homeassistant.components.airly.*
|
||||
homeassistant.components.aladdin_connect.*
|
||||
homeassistant.components.alarm_control_panel.*
|
||||
homeassistant.components.amazon_polly.*
|
||||
homeassistant.components.ambee.*
|
||||
homeassistant.components.ampio.*
|
||||
homeassistant.components.automation.*
|
||||
homeassistant.components.binary_sensor.*
|
||||
@ -24,15 +25,19 @@ homeassistant.components.canary.*
|
||||
homeassistant.components.cover.*
|
||||
homeassistant.components.device_automation.*
|
||||
homeassistant.components.device_tracker.*
|
||||
homeassistant.components.dnsip.*
|
||||
homeassistant.components.dsmr.*
|
||||
homeassistant.components.dunehd.*
|
||||
homeassistant.components.elgato.*
|
||||
homeassistant.components.fitbit.*
|
||||
homeassistant.components.forecast_solar.*
|
||||
homeassistant.components.fritzbox.*
|
||||
homeassistant.components.frontend.*
|
||||
homeassistant.components.geo_location.*
|
||||
homeassistant.components.gios.*
|
||||
homeassistant.components.group.*
|
||||
homeassistant.components.history.*
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.http.*
|
||||
homeassistant.components.huawei_lte.*
|
||||
homeassistant.components.hyperion.*
|
||||
@ -41,24 +46,31 @@ homeassistant.components.integration.*
|
||||
homeassistant.components.knx.*
|
||||
homeassistant.components.kraken.*
|
||||
homeassistant.components.light.*
|
||||
homeassistant.components.local_ip.*
|
||||
homeassistant.components.lock.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.nam.*
|
||||
homeassistant.components.network.*
|
||||
homeassistant.components.no_ip.*
|
||||
homeassistant.components.notify.*
|
||||
homeassistant.components.number.*
|
||||
homeassistant.components.onewire.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.recorder.purge
|
||||
homeassistant.components.recorder.repack
|
||||
homeassistant.components.recorder.statistics
|
||||
homeassistant.components.remote.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.sensor.*
|
||||
homeassistant.components.slack.*
|
||||
homeassistant.components.sonos.media_player
|
||||
homeassistant.components.ssdp.*
|
||||
homeassistant.components.stream.*
|
||||
homeassistant.components.sun.*
|
||||
homeassistant.components.switch.*
|
||||
homeassistant.components.synology_dsm.*
|
||||
@ -66,10 +78,12 @@ homeassistant.components.systemmonitor.*
|
||||
homeassistant.components.tcp.*
|
||||
homeassistant.components.tts.*
|
||||
homeassistant.components.upcloud.*
|
||||
homeassistant.components.uptime.*
|
||||
homeassistant.components.vacuum.*
|
||||
homeassistant.components.water_heater.*
|
||||
homeassistant.components.weather.*
|
||||
homeassistant.components.websocket_api.*
|
||||
homeassistant.components.zodiac.*
|
||||
homeassistant.components.zeroconf.*
|
||||
homeassistant.components.zone.*
|
||||
homeassistant.components.zwave_js.*
|
||||
|
19
CODEOWNERS
19
CODEOWNERS
@ -33,6 +33,7 @@ homeassistant/components/alarmdecoder/* @ajschmidt8
|
||||
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
|
||||
homeassistant/components/almond/* @gcampax @balloob
|
||||
homeassistant/components/alpha_vantage/* @fabaff
|
||||
homeassistant/components/ambee/* @frenck
|
||||
homeassistant/components/ambiclimate/* @danielhiversen
|
||||
homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/analytics/* @home-assistant/core @ludeeus
|
||||
@ -71,7 +72,7 @@ homeassistant/components/bmp280/* @belidzs
|
||||
homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe
|
||||
homeassistant/components/bond/* @prystupa
|
||||
homeassistant/components/bosch_shc/* @tschamm
|
||||
homeassistant/components/braviatv/* @bieniu
|
||||
homeassistant/components/braviatv/* @bieniu @Drafteed
|
||||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||
homeassistant/components/brother/* @bieniu
|
||||
homeassistant/components/brunt/* @eavanvalkenburg
|
||||
@ -87,6 +88,7 @@ homeassistant/components/cisco_webex_teams/* @fbradyirl
|
||||
homeassistant/components/climacell/* @raman325
|
||||
homeassistant/components/cloud/* @home-assistant/cloud
|
||||
homeassistant/components/cloudflare/* @ludeeus @ctalkington
|
||||
homeassistant/components/coinbase/* @tombrien
|
||||
homeassistant/components/color_extractor/* @GenericStudent
|
||||
homeassistant/components/comfoconnect/* @michaelarnauts
|
||||
homeassistant/components/compensation/* @Petro31
|
||||
@ -117,7 +119,7 @@ homeassistant/components/digital_ocean/* @fabaff
|
||||
homeassistant/components/directv/* @ctalkington
|
||||
homeassistant/components/discogs/* @thibmaek
|
||||
homeassistant/components/doorbird/* @oblogic7 @bdraco
|
||||
homeassistant/components/dsmr/* @Robbie1221
|
||||
homeassistant/components/dsmr/* @Robbie1221 @frenck
|
||||
homeassistant/components/dsmr_reader/* @depl0y
|
||||
homeassistant/components/dunehd/* @bieniu
|
||||
homeassistant/components/dwd_weather_warnings/* @runningman84 @stephan192 @Hummel95
|
||||
@ -146,7 +148,7 @@ homeassistant/components/ephember/* @ttroy50
|
||||
homeassistant/components/epson/* @pszafer
|
||||
homeassistant/components/epsonworkforce/* @ThaStealth
|
||||
homeassistant/components/eq3btsmart/* @rytilahti
|
||||
homeassistant/components/esphome/* @OttoWinter
|
||||
homeassistant/components/esphome/* @OttoWinter @jesserockz
|
||||
homeassistant/components/essent/* @TheLastProject
|
||||
homeassistant/components/evohome/* @zxdavb
|
||||
homeassistant/components/ezviz/* @RenierM26 @baqs
|
||||
@ -162,10 +164,12 @@ homeassistant/components/flo/* @dmulcahey
|
||||
homeassistant/components/flock/* @fabaff
|
||||
homeassistant/components/flume/* @ChrisMandich @bdraco
|
||||
homeassistant/components/flunearyou/* @bachya
|
||||
homeassistant/components/forecast_solar/* @klaasnicolaas @frenck
|
||||
homeassistant/components/forked_daapd/* @uvjustin
|
||||
homeassistant/components/fortios/* @kimfrellsen
|
||||
homeassistant/components/foscam/* @skgsergio
|
||||
homeassistant/components/freebox/* @hacf-fr @Quentame
|
||||
homeassistant/components/freedompro/* @stefano055415
|
||||
homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74
|
||||
homeassistant/components/fritzbox/* @mib1185
|
||||
homeassistant/components/fronius/* @nielstron
|
||||
@ -300,6 +304,7 @@ homeassistant/components/minecraft_server/* @elmurato
|
||||
homeassistant/components/minio/* @tkislan
|
||||
homeassistant/components/mobile_app/* @robbiet480
|
||||
homeassistant/components/modbus/* @adamchengtkc @janiversen @vzahradnik
|
||||
homeassistant/components/modern_forms/* @wonderslug
|
||||
homeassistant/components/monoprice/* @etsinko @OnFreund
|
||||
homeassistant/components/moon/* @fabaff
|
||||
homeassistant/components/motion_blinds/* @starkillerOG
|
||||
@ -421,12 +426,12 @@ homeassistant/components/scrape/* @fabaff
|
||||
homeassistant/components/screenlogic/* @dieselrabbit
|
||||
homeassistant/components/script/* @home-assistant/core
|
||||
homeassistant/components/search/* @home-assistant/core
|
||||
homeassistant/components/select/* @home-assistant/core
|
||||
homeassistant/components/sense/* @kbickar
|
||||
homeassistant/components/sensibo/* @andrey-git
|
||||
homeassistant/components/sentry/* @dcramer @frenck
|
||||
homeassistant/components/serial/* @fabaff
|
||||
homeassistant/components/seven_segments/* @fabaff
|
||||
homeassistant/components/seventeentrack/* @bachya
|
||||
homeassistant/components/sharkiq/* @ajmarks
|
||||
homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/shelly/* @balloob @bieniu @thecode @chemelli74
|
||||
@ -477,11 +482,11 @@ homeassistant/components/subaru/* @G-Two
|
||||
homeassistant/components/suez_water/* @ooii
|
||||
homeassistant/components/sun/* @Swamp-Ig
|
||||
homeassistant/components/supla/* @mwegrzynek
|
||||
homeassistant/components/surepetcare/* @benleb
|
||||
homeassistant/components/surepetcare/* @benleb @danielhiversen
|
||||
homeassistant/components/swiss_hydrological_data/* @fabaff
|
||||
homeassistant/components/swiss_public_transport/* @fabaff
|
||||
homeassistant/components/switchbot/* @danielhiversen
|
||||
homeassistant/components/switcher_kis/* @tomerfi
|
||||
homeassistant/components/switcher_kis/* @tomerfi @thecode
|
||||
homeassistant/components/switchmate/* @danielhiversen
|
||||
homeassistant/components/syncthing/* @zhulik
|
||||
homeassistant/components/syncthru/* @nielstron
|
||||
@ -566,7 +571,7 @@ homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG
|
||||
homeassistant/components/xiaomi_tv/* @simse
|
||||
homeassistant/components/xmpp/* @fabaff @flowolf
|
||||
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/yeelight/* @rytilahti @zewelor @shenxn
|
||||
homeassistant/components/yeelightsunflower/* @lindsaymarkward
|
||||
|
10
build.json
10
build.json
@ -2,11 +2,11 @@
|
||||
"image": "homeassistant/{arch}-homeassistant",
|
||||
"shadow_repository": "ghcr.io/home-assistant",
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.05.0",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.05.0",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.05.0",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.05.0",
|
||||
"i386": "ghcr.io/home-assistant/i386-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.06.2",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.06.2",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.06.2",
|
||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.06.2"
|
||||
},
|
||||
"labels": {
|
||||
"io.hass.type": "core",
|
||||
|
@ -81,6 +81,17 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
"""Return trusted users per network."""
|
||||
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
|
||||
def support_mfa(self) -> bool:
|
||||
"""Trusted Networks auth provider does not support MFA."""
|
||||
@ -178,6 +189,9 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
):
|
||||
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
|
||||
def async_validate_refresh_token(
|
||||
self, refresh_token: RefreshToken, remote_ip: str | None = None
|
||||
|
@ -1,10 +1,26 @@
|
||||
{
|
||||
"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": {
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "\u05e1\u05d9\u05e1\u05de\u05d4",
|
||||
"username": "\u05d3\u05d5\u05d0\"\u05dc"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
ATTR_FORECAST,
|
||||
CONF_FORECAST,
|
||||
COORDINATOR,
|
||||
DOMAIN,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
)
|
||||
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
|
||||
|
||||
_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()
|
||||
|
||||
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] = {
|
||||
COORDINATOR: coordinator,
|
||||
UNDO_UPDATE_LISTENER: undo_listener,
|
||||
}
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
|
||||
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_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]()
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_CLOUDY,
|
||||
@ -48,12 +49,10 @@ ATTR_LABEL: Final = "label"
|
||||
ATTR_UNIT_IMPERIAL: Final = "unit_imperial"
|
||||
ATTR_UNIT_METRIC: Final = "unit_metric"
|
||||
CONF_FORECAST: Final = "forecast"
|
||||
COORDINATOR: Final = "coordinator"
|
||||
DOMAIN: Final = "accuweather"
|
||||
MANUFACTURER: Final = "AccuWeather, Inc."
|
||||
MAX_FORECAST_DAYS: Final = 4
|
||||
NAME: Final = "AccuWeather"
|
||||
UNDO_UPDATE_LISTENER: Final = "undo_update_listener"
|
||||
|
||||
CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||
ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37],
|
||||
@ -235,6 +234,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"Ceiling": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
@ -243,6 +243,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: LENGTH_METERS,
|
||||
ATTR_UNIT_IMPERIAL: LENGTH_FEET,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"CloudCover": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
@ -251,6 +252,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: PERCENTAGE,
|
||||
ATTR_UNIT_IMPERIAL: PERCENTAGE,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"DewPoint": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
@ -259,6 +261,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"RealFeelTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
@ -267,6 +270,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"RealFeelTemperatureShade": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
@ -275,6 +279,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"Precipitation": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
@ -283,6 +288,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: LENGTH_MILLIMETERS,
|
||||
ATTR_UNIT_IMPERIAL: LENGTH_INCHES,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"PressureTendency": {
|
||||
ATTR_DEVICE_CLASS: "accuweather__pressure_tendency",
|
||||
@ -299,6 +305,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: UV_INDEX,
|
||||
ATTR_UNIT_IMPERIAL: UV_INDEX,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"WetBulbTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
@ -307,6 +314,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"WindChillTemperature": {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
@ -315,6 +323,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: TEMP_CELSIUS,
|
||||
ATTR_UNIT_IMPERIAL: TEMP_FAHRENHEIT,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"Wind": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
@ -323,6 +332,7 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: True,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
"WindGust": {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
@ -331,5 +341,6 @@ SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_UNIT_METRIC: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_UNIT_IMPERIAL: SPEED_MILES_PER_HOUR,
|
||||
ATTR_ENABLED: False,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class SensorDescription(TypedDict):
|
||||
class SensorDescription(TypedDict, total=False):
|
||||
"""Sensor description class."""
|
||||
|
||||
device_class: str | None
|
||||
@ -13,3 +13,4 @@ class SensorDescription(TypedDict):
|
||||
unit_metric: str | None
|
||||
unit_imperial: str | None
|
||||
enabled: bool
|
||||
state_class: str | None
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
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.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
@ -28,7 +28,6 @@ from .const import (
|
||||
ATTR_UNIT_IMPERIAL,
|
||||
ATTR_UNIT_METRIC,
|
||||
ATTRIBUTION,
|
||||
COORDINATOR,
|
||||
DOMAIN,
|
||||
FORECAST_SENSOR_TYPES,
|
||||
MANUFACTURER,
|
||||
@ -46,9 +45,7 @@ async def async_setup_entry(
|
||||
"""Add AccuWeather entities from a config_entry."""
|
||||
name: str = entry.data[CONF_NAME]
|
||||
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
|
||||
COORDINATOR
|
||||
]
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
sensors: list[AccuWeatherSensor] = []
|
||||
for sensor in SENSOR_TYPES:
|
||||
@ -92,6 +89,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
self._device_class = None
|
||||
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
self.forecast_day = forecast_day
|
||||
self._attr_state_class = self._description.get(ATTR_STATE_CLASS)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
@ -8,7 +8,7 @@ from accuweather.const import ENDPOINT
|
||||
from homeassistant.components import system_health
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@callback
|
||||
@ -21,8 +21,8 @@ def async_register(
|
||||
|
||||
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
"""Get info for the info page."""
|
||||
remaining_requests = list(hass.data[DOMAIN].values())[0][
|
||||
COORDINATOR
|
||||
remaining_requests = list(hass.data[DOMAIN].values())[
|
||||
0
|
||||
].accuweather.requests_remaining
|
||||
|
||||
return {
|
||||
|
@ -1,9 +1,19 @@
|
||||
{
|
||||
"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": {
|
||||
"user": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
Forecast,
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -30,7 +31,6 @@ from .const import (
|
||||
ATTR_FORECAST,
|
||||
ATTRIBUTION,
|
||||
CONDITION_CLASSES,
|
||||
COORDINATOR,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
NAME,
|
||||
@ -45,9 +45,7 @@ async def async_setup_entry(
|
||||
"""Add a AccuWeather weather entity from a config_entry."""
|
||||
name: str = entry.data[CONF_NAME]
|
||||
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
|
||||
COORDINATOR
|
||||
]
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities([AccuWeatherEntity(name, coordinator)])
|
||||
|
||||
@ -156,12 +154,12 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
|
||||
return None
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[dict[str, Any]] | None:
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast array."""
|
||||
if not self.coordinator.forecast:
|
||||
return None
|
||||
# remap keys from library to keys understood by the weather component
|
||||
forecast = [
|
||||
return [
|
||||
{
|
||||
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
||||
ATTR_FORECAST_TEMP: item["TemperatureMax"]["Value"],
|
||||
@ -183,7 +181,6 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
|
||||
}
|
||||
for item in self.coordinator.data[ATTR_FORECAST]
|
||||
]
|
||||
return forecast
|
||||
|
||||
@staticmethod
|
||||
def _calc_precipitation(day: dict[str, Any]) -> float:
|
||||
|
14
homeassistant/components/acmeda/translations/he.json
Normal file
14
homeassistant/components/acmeda/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,20 @@
|
||||
{
|
||||
"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": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"host": "\u05de\u05d0\u05e8\u05d7",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Advantage Air Filter."""
|
||||
|
||||
_attr_device_class = DEVICE_CLASS_PROBLEM
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
@ -43,11 +45,6 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Return a unique id."""
|
||||
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
|
||||
def is_on(self):
|
||||
"""Return if filter needs cleaning."""
|
||||
@ -57,6 +54,8 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
||||
class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Advantage Air Zone Motion."""
|
||||
|
||||
_attr_device_class = DEVICE_CLASS_MOTION
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
@ -67,11 +66,6 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Return a unique id."""
|
||||
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
|
||||
def is_on(self):
|
||||
"""Return if motion is detect."""
|
||||
@ -81,6 +75,8 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
||||
class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Advantage Air Zone MyZone."""
|
||||
|
||||
_attr_entity_registry_enabled_default = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
@ -95,8 +91,3 @@ class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
|
||||
def is_on(self):
|
||||
"""Return if this zone is the 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
|
||||
|
@ -6,6 +6,7 @@ from homeassistant.components.climate.const import (
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
@ -31,9 +32,18 @@ ADVANTAGE_AIR_HVAC_MODES = {
|
||||
"cool": HVAC_MODE_COOL,
|
||||
"vent": HVAC_MODE_FAN_ONLY,
|
||||
"dry": HVAC_MODE_DRY,
|
||||
"myauto": HVAC_MODE_AUTO,
|
||||
}
|
||||
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 = {
|
||||
"auto": FAN_AUTO,
|
||||
"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()}
|
||||
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"
|
||||
ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY]
|
||||
|
||||
@ -130,6 +133,8 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the supported HVAC modes."""
|
||||
if self._ac.get("myAutoModeEnabled"):
|
||||
return AC_HVAC_MODES + [HVAC_MODE_AUTO]
|
||||
return AC_HVAC_MODES
|
||||
|
||||
@property
|
||||
|
@ -44,6 +44,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air timer control."""
|
||||
|
||||
_attr_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
|
||||
|
||||
def __init__(self, instance, ac_key, action):
|
||||
"""Initialize the Advantage Air timer control."""
|
||||
super().__init__(instance, ac_key)
|
||||
@ -65,11 +67,6 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
"""Return the current value."""
|
||||
return self._ac[self._time_key]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return a representative icon of the timer."""
|
||||
@ -86,6 +83,8 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone Vent Sensor."""
|
||||
|
||||
_attr_unit_of_measurement = PERCENTAGE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
@ -103,11 +102,6 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
return self._zone["value"]
|
||||
return 0
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the percent sign."""
|
||||
return PERCENTAGE
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return a representative icon."""
|
||||
@ -119,6 +113,8 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone wireless signal sensor."""
|
||||
|
||||
_attr_unit_of_measurement = PERCENTAGE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
@ -134,11 +130,6 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
"""Return the current value of the wireless signal."""
|
||||
return self._zone["rssi"]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the percent sign."""
|
||||
return PERCENTAGE
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return a representative icon."""
|
||||
|
20
homeassistant/components/advantage_air/translations/he.json
Normal file
20
homeassistant/components/advantage_air/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,13 +19,13 @@ from .weather_update_coordinator import WeatherUpdateCoordinator
|
||||
_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."""
|
||||
name = config_entry.data[CONF_NAME]
|
||||
api_key = config_entry.data[CONF_API_KEY]
|
||||
latitude = config_entry.data[CONF_LATITUDE]
|
||||
longitude = config_entry.data[CONF_LONGITUDE]
|
||||
station_updates = config_entry.options.get(CONF_STATION_UPDATES, True)
|
||||
name = entry.data[CONF_NAME]
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
latitude = entry.data[CONF_LATITUDE]
|
||||
longitude = entry.data[CONF_LONGITUDE]
|
||||
station_updates = entry.options.get(CONF_STATION_UPDATES, True)
|
||||
|
||||
aemet = AEMET(api_key)
|
||||
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()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = {
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
ENTRY_NAME: name,
|
||||
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
|
||||
|
||||
|
||||
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""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_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
)
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
@ -18,5 +18,14 @@
|
||||
"title": "[void]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "Sammeln von Daten von AEMET-Wetterstationen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/aemet/translations/he.json
Normal file
19
homeassistant/components/aemet/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,5 +18,14 @@
|
||||
"title": "AEMET OpenData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "Zbieraj dane ze stacji pogodowych AEMET"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,14 @@
|
||||
"abort": {
|
||||
"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": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"host": "\u05de\u05d0\u05e8\u05d7",
|
||||
"port": "\u05e4\u05d5\u05e8\u05d8"
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ from airly import Airly
|
||||
from airly.exceptions import AirlyError
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import async_get_registry
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
@ -31,7 +33,7 @@ from .const import (
|
||||
NO_AIRLY_SENSORS,
|
||||
)
|
||||
|
||||
PLATFORMS = ["air_quality", "sensor"]
|
||||
PLATFORMS = ["sensor"]
|
||||
|
||||
_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)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
@ -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
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
@ -23,16 +24,22 @@ ATTR_API_CAQI_DESCRIPTION: Final = "DESCRIPTION"
|
||||
ATTR_API_CAQI_LEVEL: Final = "LEVEL"
|
||||
ATTR_API_HUMIDITY: Final = "HUMIDITY"
|
||||
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_PM25: Final = "PM25"
|
||||
ATTR_API_PM25_LIMIT: Final = "PM25_LIMIT"
|
||||
ATTR_API_PM25_PERCENT: Final = "PM25_PERCENT"
|
||||
ATTR_API_PRESSURE: Final = "PRESSURE"
|
||||
ATTR_API_TEMPERATURE: Final = "TEMPERATURE"
|
||||
|
||||
ATTR_ADVICE: Final = "advice"
|
||||
ATTR_DESCRIPTION: Final = "description"
|
||||
ATTR_LABEL: Final = "label"
|
||||
ATTR_LEVEL: Final = "level"
|
||||
ATTR_LIMIT: Final = "limit"
|
||||
ATTR_PERCENT: Final = "percent"
|
||||
ATTR_UNIT: Final = "unit"
|
||||
ATTR_VALUE: Final = "value"
|
||||
|
||||
SUFFIX_PERCENT: Final = "PERCENT"
|
||||
SUFFIX_LIMIT: Final = "LIMIT"
|
||||
|
||||
ATTRIBUTION: Final = "Data provided by Airly"
|
||||
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."
|
||||
|
||||
SENSOR_TYPES: dict[str, SensorDescription] = {
|
||||
ATTR_API_CAQI: {
|
||||
ATTR_LABEL: ATTR_API_CAQI,
|
||||
ATTR_UNIT: "CAQI",
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_PM1: {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:blur",
|
||||
ATTR_LABEL: ATTR_API_PM1,
|
||||
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_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
|
||||
ATTR_UNIT: PERCENTAGE,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: lambda value: round(value, 1),
|
||||
},
|
||||
ATTR_API_PRESSURE: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: ATTR_API_PRESSURE.capitalize(),
|
||||
ATTR_UNIT: PRESSURE_HPA,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
ATTR_API_TEMPERATURE: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(),
|
||||
ATTR_UNIT: TEMP_CELSIUS,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: lambda value: round(value, 1),
|
||||
},
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
"""Type definitions for Airly integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypedDict
|
||||
from typing import Callable, TypedDict
|
||||
|
||||
|
||||
class SensorDescription(TypedDict):
|
||||
class SensorDescription(TypedDict, total=False):
|
||||
"""Sensor description class."""
|
||||
|
||||
device_class: str | None
|
||||
icon: str | None
|
||||
label: str
|
||||
unit: str
|
||||
state_class: str | None
|
||||
value: Callable
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
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.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
@ -19,15 +19,27 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AirlyDataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_API_PM1,
|
||||
ATTR_API_PRESSURE,
|
||||
ATTR_ADVICE,
|
||||
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_LEVEL,
|
||||
ATTR_LIMIT,
|
||||
ATTR_PERCENT,
|
||||
ATTR_UNIT,
|
||||
ATTR_VALUE,
|
||||
ATTRIBUTION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
SENSOR_TYPES,
|
||||
SUFFIX_LIMIT,
|
||||
SUFFIX_PERCENT,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
@ -60,46 +72,49 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._name = name
|
||||
self._description = SENSOR_TYPES[kind]
|
||||
self._description = 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._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
|
||||
def state(self) -> StateType:
|
||||
"""Return the state."""
|
||||
self._state = self.coordinator.data[self.kind]
|
||||
if self.kind in [ATTR_API_PM1, ATTR_API_PRESSURE]:
|
||||
return round(cast(float, self._state))
|
||||
return round(cast(float, self._state), 1)
|
||||
state = self.coordinator.data[self.kind]
|
||||
return cast(StateType, self._description[ATTR_VALUE](state))
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""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
|
||||
|
||||
@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
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
@ -114,8 +129,3 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
||||
"manufacturer": MANUFACTURER,
|
||||
"entry_type": "service",
|
||||
}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit the value is expressed in."""
|
||||
return self._description[ATTR_UNIT]
|
||||
|
@ -1,10 +1,20 @@
|
||||
{
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
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."""
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
latitude = entry.data[CONF_LATITUDE]
|
||||
|
@ -14,7 +14,8 @@
|
||||
"data": {
|
||||
"api_key": "API-Schl\u00fcssel",
|
||||
"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/.",
|
||||
"title": "AirNow"
|
||||
|
21
homeassistant/components/airnow/translations/he.json
Normal file
21
homeassistant/components/airnow/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -337,24 +337,12 @@ class AirVisualEntity(CoordinatorEntity):
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._icon = None
|
||||
self._unit = None
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
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):
|
||||
"""Register callbacks."""
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Support for AirVisual Node/Pro units."""
|
||||
from homeassistant.components.air_quality import AirQualityEntity
|
||||
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import AirVisualEntity
|
||||
@ -34,8 +33,7 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
|
||||
"""Initialize."""
|
||||
super().__init__(airvisual)
|
||||
|
||||
self._icon = "mdi:chemical-weapon"
|
||||
self._unit = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
self._attr_icon = "mdi:chemical-weapon"
|
||||
|
||||
@property
|
||||
def air_quality_index(self):
|
||||
|
@ -56,57 +56,32 @@ NODE_PRO_SENSORS = [
|
||||
(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
|
||||
def async_get_pollutant_label(symbol):
|
||||
"""Get a pollutant's label based on its symbol."""
|
||||
if symbol == "co":
|
||||
return "Carbon Monoxide"
|
||||
if symbol == "n2":
|
||||
return "Nitrogen Dioxide"
|
||||
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_LEVELS = {
|
||||
(0, 50): ("Good", "mdi:emoticon-excited"),
|
||||
(51, 100): ("Moderate", "mdi:emoticon-happy"),
|
||||
(101, 150): ("Unhealthy for sensitive groups", "mdi:emoticon-neutral"),
|
||||
(151, 200): ("Unhealthy", "mdi:emoticon-sad"),
|
||||
(201, 300): ("Very unhealthy", "mdi:emoticon-dead"),
|
||||
(301, 1000): ("Hazardous", "mdi:biohazard"),
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_pollutant_level_info(value):
|
||||
"""Return a verbal pollutant level (and associated icon) for a numeric value."""
|
||||
if 0 <= value <= 50:
|
||||
return ("Good", "mdi:emoticon-excited")
|
||||
if 51 <= value <= 100:
|
||||
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
|
||||
POLLUTANT_UNITS = {
|
||||
"co": CONCENTRATION_PARTS_PER_MILLION,
|
||||
"n2": CONCENTRATION_PARTS_PER_BILLION,
|
||||
"o3": CONCENTRATION_PARTS_PER_BILLION,
|
||||
"p1": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
"p2": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
"s2": CONCENTRATION_PARTS_PER_BILLION,
|
||||
}
|
||||
|
||||
|
||||
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._icon = icon
|
||||
self._kind = kind
|
||||
self._locale = locale
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._unit = unit
|
||||
|
||||
self._attr_icon = icon
|
||||
self._attr_unit_of_measurement = unit
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
@ -196,16 +172,20 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
|
||||
if self._kind == SENSOR_KIND_LEVEL:
|
||||
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:
|
||||
self._state = data[f"aqi{self._locale}"]
|
||||
elif self._kind == SENSOR_KIND_POLLUTANT:
|
||||
symbol = data[f"main{self._locale}"]
|
||||
self._state = async_get_pollutant_label(symbol)
|
||||
self._state = POLLUTANT_LABELS[symbol]
|
||||
self._attrs.update(
|
||||
{
|
||||
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."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._device_class = device_class
|
||||
self._kind = kind
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._unit = unit
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
self._attr_device_class = device_class
|
||||
self._attr_unit_of_measurement = unit
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
|
@ -1,12 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7"
|
||||
},
|
||||
"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": {
|
||||
"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": {
|
||||
"data": {
|
||||
"ip_address": "\u05de\u05d0\u05e8\u05d7",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Component to interface with an alarm control panel."""
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Final, final
|
||||
@ -113,20 +112,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
class AlarmControlPanelEntity(Entity):
|
||||
"""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
|
||||
def code_format(self) -> str | None:
|
||||
"""Regex for code format or None if no code is required."""
|
||||
return None
|
||||
return self._attr_code_format
|
||||
|
||||
@property
|
||||
def changed_by(self) -> str | None:
|
||||
"""Last change triggered by."""
|
||||
return None
|
||||
return self._attr_changed_by
|
||||
|
||||
@property
|
||||
def code_arm_required(self) -> bool:
|
||||
"""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:
|
||||
"""Send disarm command."""
|
||||
@ -177,9 +181,9 @@ class AlarmControlPanelEntity(Entity):
|
||||
await self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@final
|
||||
@property
|
||||
|
@ -8,7 +8,6 @@ import voluptuous as vol
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_CODE,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
@ -23,6 +22,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.helpers import entity_registry
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import get_supported_features
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ATTR_CODE_ARM_REQUIRED, DOMAIN
|
||||
@ -62,59 +62,24 @@ async def async_get_actions(
|
||||
if entry.domain != DOMAIN:
|
||||
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.
|
||||
if state is None:
|
||||
continue
|
||||
|
||||
supported_features = state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
base_action = {
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
}
|
||||
|
||||
# Add actions for each entity that belongs to this integration
|
||||
if supported_features & SUPPORT_ALARM_ARM_AWAY:
|
||||
actions.append(
|
||||
{
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: "arm_away",
|
||||
}
|
||||
)
|
||||
actions.append({**base_action, CONF_TYPE: "arm_away"})
|
||||
if supported_features & SUPPORT_ALARM_ARM_HOME:
|
||||
actions.append(
|
||||
{
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: "arm_home",
|
||||
}
|
||||
)
|
||||
actions.append({**base_action, CONF_TYPE: "arm_home"})
|
||||
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
|
||||
actions.append(
|
||||
{
|
||||
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",
|
||||
}
|
||||
)
|
||||
actions.append({**base_action, CONF_TYPE: "arm_night"})
|
||||
actions.append({**base_action, CONF_TYPE: "disarm"})
|
||||
if supported_features & SUPPORT_ALARM_TRIGGER:
|
||||
actions.append(
|
||||
{
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: "trigger",
|
||||
}
|
||||
)
|
||||
actions.append({**base_action, CONF_TYPE: "trigger"})
|
||||
|
||||
return actions
|
||||
|
||||
@ -147,6 +112,8 @@ async def async_get_action_capabilities(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> dict[str, vol.Schema]:
|
||||
"""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])
|
||||
code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False
|
||||
|
||||
|
@ -13,7 +13,6 @@ from homeassistant.components.alarm_control_panel.const import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_CONDITION,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
@ -29,6 +28,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import condition, config_validation as cv, entity_registry
|
||||
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 . import DOMAIN
|
||||
@ -70,70 +70,29 @@ async def async_get_conditions(
|
||||
if entry.domain != DOMAIN:
|
||||
continue
|
||||
|
||||
state = hass.states.get(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]
|
||||
supported_features = get_supported_features(hass, entry.entity_id)
|
||||
|
||||
# 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 += [
|
||||
{
|
||||
CONF_CONDITION: "device",
|
||||
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,
|
||||
},
|
||||
{**base_condition, CONF_TYPE: CONDITION_DISARMED},
|
||||
{**base_condition, CONF_TYPE: CONDITION_TRIGGERED},
|
||||
]
|
||||
if supported_features & SUPPORT_ALARM_ARM_HOME:
|
||||
conditions.append(
|
||||
{
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: CONDITION_ARMED_HOME,
|
||||
}
|
||||
)
|
||||
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_HOME})
|
||||
if supported_features & SUPPORT_ALARM_ARM_AWAY:
|
||||
conditions.append(
|
||||
{
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: CONDITION_ARMED_AWAY,
|
||||
}
|
||||
)
|
||||
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_AWAY})
|
||||
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
|
||||
conditions.append(
|
||||
{
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: CONDITION_ARMED_NIGHT,
|
||||
}
|
||||
)
|
||||
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_NIGHT})
|
||||
if supported_features & SUPPORT_ALARM_ARM_CUSTOM_BYPASS:
|
||||
conditions.append(
|
||||
{
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS,
|
||||
}
|
||||
{**base_condition, CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS}
|
||||
)
|
||||
|
||||
return conditions
|
||||
|
@ -11,10 +11,9 @@ from homeassistant.components.alarm_control_panel.const import (
|
||||
SUPPORT_ALARM_ARM_NIGHT,
|
||||
)
|
||||
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.const import (
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITY_ID,
|
||||
@ -30,6 +29,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
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 . import DOMAIN
|
||||
@ -41,7 +41,7 @@ TRIGGER_TYPES: Final[set[str]] = BASIC_TRIGGER_TYPES | {
|
||||
"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_TYPE): vol.In(TRIGGER_TYPES),
|
||||
@ -62,13 +62,7 @@ async def async_get_triggers(
|
||||
if entry.domain != DOMAIN:
|
||||
continue
|
||||
|
||||
entity_state = hass.states.get(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]
|
||||
supported_features = get_supported_features(hass, entry.entity_id)
|
||||
|
||||
# Add triggers for each entity that belongs to this integration
|
||||
base_trigger = {
|
||||
|
@ -16,40 +16,58 @@
|
||||
"device_path": "Ger\u00e4tepfad",
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
}
|
||||
},
|
||||
"title": "Verbindungseinstellungen konfigurieren"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"protocol": "Protokoll"
|
||||
}
|
||||
},
|
||||
"title": "W\u00e4hlen Sie das AlarmDecoder-Protokoll"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"arm_settings": {
|
||||
"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": {
|
||||
"data": {
|
||||
"edit_select": "Bearbeiten"
|
||||
},
|
||||
"description": "Was m\u00f6chtest du bearbeiten?"
|
||||
"description": "Was m\u00f6chtest du bearbeiten?",
|
||||
"title": "AlarmDecoder konfigurieren"
|
||||
},
|
||||
"zone_details": {
|
||||
"data": {
|
||||
"zone_loop": "RF Loop",
|
||||
"zone_name": "Zonenname",
|
||||
"zone_relayaddr": "Relais-Adresse",
|
||||
"zone_relaychan": "Relaiskanal",
|
||||
"zone_rfid": "RF Serial",
|
||||
"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": {
|
||||
"data": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
41
homeassistant/components/alarmdecoder/translations/he.json
Normal file
41
homeassistant/components/alarmdecoder/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1155,8 +1155,6 @@ class AlexaPowerLevelController(AlexaCapability):
|
||||
if self.entity.domain == fan.DOMAIN:
|
||||
return self.entity.attributes.get(fan.ATTR_PERCENTAGE) or 0
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class AlexaSecurityPanelController(AlexaCapability):
|
||||
"""Implements Alexa.SecurityPanelController.
|
||||
@ -1304,6 +1302,12 @@ class AlexaModeController(AlexaCapability):
|
||||
if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN):
|
||||
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
|
||||
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
# Return state instead of position when using ModeController.
|
||||
@ -1342,6 +1346,17 @@ class AlexaModeController(AlexaCapability):
|
||||
)
|
||||
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
|
||||
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
self._resource = AlexaModeResource(
|
||||
|
@ -535,6 +535,7 @@ class FanCapabilities(AlexaEntity):
|
||||
if supported & fan.SUPPORT_SET_SPEED:
|
||||
yield AlexaPercentageController(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(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
|
||||
)
|
||||
@ -542,6 +543,10 @@ class FanCapabilities(AlexaEntity):
|
||||
yield AlexaToggleController(
|
||||
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:
|
||||
yield AlexaModeController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}"
|
||||
|
@ -62,7 +62,6 @@ from .errors import (
|
||||
AlexaInvalidDirectiveError,
|
||||
AlexaInvalidValueError,
|
||||
AlexaSecurityPanelAuthorizationRequired,
|
||||
AlexaSecurityPanelUnauthorizedError,
|
||||
AlexaTempRangeError,
|
||||
AlexaUnsupportedThermostatModeError,
|
||||
AlexaVideoActionNotPermittedForContentError,
|
||||
@ -927,11 +926,9 @@ async def async_api_disarm(hass, config, directive, context):
|
||||
if payload["authorization"]["type"] == "FOUR_DIGIT_PIN":
|
||||
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
|
||||
):
|
||||
msg = "Invalid Code"
|
||||
raise AlexaSecurityPanelUnauthorizedError(msg)
|
||||
)
|
||||
|
||||
response.add_context_property(
|
||||
{
|
||||
@ -961,6 +958,16 @@ async def async_api_set_mode(hass, config, directive, context):
|
||||
service = fan.SERVICE_SET_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
|
||||
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
position = mode.split(".")[1]
|
||||
|
@ -11,9 +11,9 @@ import async_timeout
|
||||
from pyalmond import AbstractAlmondWebAuth, AlmondLocalAuth, WebAlmondAPI
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
@ -94,14 +94,14 @@ async def async_setup(hass, config):
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={"type": TYPE_LOCAL, "host": conf[CONF_HOST]},
|
||||
)
|
||||
)
|
||||
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."""
|
||||
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(
|
||||
hass: HomeAssistant, entry: config_entries.ConfigEntry, api: WebAlmondAPI
|
||||
hass: HomeAssistant, entry: ConfigEntry, api: WebAlmondAPI
|
||||
):
|
||||
"""Configure Almond to connect to HA."""
|
||||
try:
|
||||
@ -248,7 +248,7 @@ class AlmondAgent(conversation.AbstractConversationAgent):
|
||||
"""Almond conversation agent."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: WebAlmondAPI, entry: config_entries.ConfigEntry
|
||||
self, hass: HomeAssistant, api: WebAlmondAPI, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the agent."""
|
||||
self.hass = hass
|
||||
|
15
homeassistant/components/almond/translations/he.json
Normal file
15
homeassistant/components/almond/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
homeassistant/components/ambee/__init__.py
Normal file
71
homeassistant/components/ambee/__init__.py
Normal 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
|
115
homeassistant/components/ambee/config_flow.py
Normal file
115
homeassistant/components/ambee/config_flow.py
Normal 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,
|
||||
)
|
212
homeassistant/components/ambee/const.py
Normal file
212
homeassistant/components/ambee/const.py
Normal 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,
|
||||
},
|
||||
},
|
||||
}
|
10
homeassistant/components/ambee/manifest.json
Normal file
10
homeassistant/components/ambee/manifest.json
Normal 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"
|
||||
}
|
15
homeassistant/components/ambee/models.py
Normal file
15
homeassistant/components/ambee/models.py
Normal 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
|
99
homeassistant/components/ambee/sensor.py
Normal file
99
homeassistant/components/ambee/sensor.py
Normal 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]
|
28
homeassistant/components/ambee/strings.json
Normal file
28
homeassistant/components/ambee/strings.json
Normal 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%]"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/strings.sensor.json
Normal file
10
homeassistant/components/ambee/strings.sensor.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"low": "Low",
|
||||
"moderate": "Moderate",
|
||||
"high": "High",
|
||||
"very high": "Very High"
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/ca.json
Normal file
28
homeassistant/components/ambee/translations/ca.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/de.json
Normal file
28
homeassistant/components/ambee/translations/de.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/en.json
Normal file
28
homeassistant/components/ambee/translations/en.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
homeassistant/components/ambee/translations/es.json
Normal file
14
homeassistant/components/ambee/translations/es.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/et.json
Normal file
28
homeassistant/components/ambee/translations/et.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
homeassistant/components/ambee/translations/he.json
Normal file
26
homeassistant/components/ambee/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
homeassistant/components/ambee/translations/hu.json
Normal file
26
homeassistant/components/ambee/translations/hu.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/it.json
Normal file
28
homeassistant/components/ambee/translations/it.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/nl.json
Normal file
28
homeassistant/components/ambee/translations/nl.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/no.json
Normal file
28
homeassistant/components/ambee/translations/no.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/pl.json
Normal file
28
homeassistant/components/ambee/translations/pl.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/ru.json
Normal file
28
homeassistant/components/ambee/translations/ru.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.ca.json
Normal file
10
homeassistant/components/ambee/translations/sensor.ca.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Alt",
|
||||
"low": "Baix",
|
||||
"moderate": "Moderat",
|
||||
"very high": "Molt alt"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.de.json
Normal file
10
homeassistant/components/ambee/translations/sensor.de.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Hoch",
|
||||
"low": "Niedrig",
|
||||
"moderate": "M\u00e4\u00dfig",
|
||||
"very high": "Sehr hoch"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.en.json
Normal file
10
homeassistant/components/ambee/translations/sensor.en.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"moderate": "Moderate",
|
||||
"very high": "Very High"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.es.json
Normal file
10
homeassistant/components/ambee/translations/sensor.es.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Alto",
|
||||
"low": "Bajo",
|
||||
"moderate": "Moderado",
|
||||
"very high": "Muy alto"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.et.json
Normal file
10
homeassistant/components/ambee/translations/sensor.et.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "K\u00f5rge",
|
||||
"low": "Madal",
|
||||
"moderate": "M\u00f5\u00f5dukas",
|
||||
"very high": "V\u00e4ga k\u00f5rge"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Magas",
|
||||
"low": "Alacsony",
|
||||
"very high": "Nagyon magas"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.it.json
Normal file
10
homeassistant/components/ambee/translations/sensor.it.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Alto",
|
||||
"low": "Basso",
|
||||
"moderate": "Moderato",
|
||||
"very high": "Molto alto"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.nl.json
Normal file
10
homeassistant/components/ambee/translations/sensor.nl.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Hoog",
|
||||
"low": "Laag",
|
||||
"moderate": "Matig",
|
||||
"very high": "Zeer hoog"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.no.json
Normal file
10
homeassistant/components/ambee/translations/sensor.no.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "H\u00f8y",
|
||||
"low": "Lav",
|
||||
"moderate": "Moderat",
|
||||
"very high": "Veldig h\u00f8y"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.pl.json
Normal file
10
homeassistant/components/ambee/translations/sensor.pl.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "Wysoki",
|
||||
"low": "Niski",
|
||||
"moderate": "Umiarkowany",
|
||||
"very high": "Bardzo wysoki"
|
||||
}
|
||||
}
|
||||
}
|
10
homeassistant/components/ambee/translations/sensor.ru.json
Normal file
10
homeassistant/components/ambee/translations/sensor.ru.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"state": {
|
||||
"ambee__risk": {
|
||||
"high": "\u9ad8",
|
||||
"low": "\u4f4e",
|
||||
"moderate": "\u4e2d",
|
||||
"very high": "\u6975\u9ad8"
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/ambee/translations/zh-Hant.json
Normal file
28
homeassistant/components/ambee/translations/zh-Hant.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
homeassistant/components/ambiclimate/translations/he.json
Normal file
11
homeassistant/components/ambiclimate/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"step": {
|
||||
|
@ -3,8 +3,8 @@
|
||||
"name": "Android TV",
|
||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||
"requirements": [
|
||||
"adb-shell[async]==0.3.1",
|
||||
"androidtv[async]==0.0.59",
|
||||
"adb-shell[async]==0.3.4",
|
||||
"androidtv[async]==0.0.60",
|
||||
"pure-python-adb[async]==0.3.0.dev0"
|
||||
],
|
||||
"codeowners": ["@JeffLIrion"],
|
||||
|
@ -270,7 +270,7 @@ class AppleTVManager:
|
||||
|
||||
self.hass.components.persistent_notification.create(
|
||||
"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,
|
||||
notification_id=NOTIFICATION_ID,
|
||||
)
|
||||
|
@ -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.",
|
||||
"unknown": "Unerwarteter Fehler"
|
||||
},
|
||||
"flow_title": "Apple TV: {name}",
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"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!",
|
||||
|
29
homeassistant/components/apple_tv/translations/he.json
Normal file
29
homeassistant/components/apple_tv/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
"no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton",
|
||||
"unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt"
|
||||
},
|
||||
"flow_title": "Apple TV: {name}",
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "Apple TV sikeresen hozz\u00e1adva"
|
||||
|
@ -7,7 +7,7 @@ from arcam.fmj import ConnectionFailed
|
||||
from arcam.fmj.client import Client
|
||||
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.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -51,7 +51,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType):
|
||||
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."""
|
||||
entries = hass.data[DOMAIN_DATA_ENTRIES]
|
||||
tasks = hass.data[DOMAIN_DATA_TASKS]
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
import voluptuous as vol
|
||||
|
||||
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 (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@ -20,7 +20,7 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from .const import DOMAIN, EVENT_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_TYPE): vol.In(TRIGGER_TYPES),
|
||||
@ -56,7 +56,7 @@ async def async_attach_trigger(
|
||||
automation_info: dict,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""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)
|
||||
|
||||
if config[CONF_TYPE] == "turn_on":
|
||||
@ -69,9 +69,9 @@ async def async_attach_trigger(
|
||||
job,
|
||||
{
|
||||
"trigger": {
|
||||
**trigger_data,
|
||||
**config,
|
||||
"description": f"{DOMAIN} - {entity_id}",
|
||||
"id": trigger_id,
|
||||
}
|
||||
},
|
||||
event.context,
|
||||
|
@ -5,7 +5,6 @@
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"error": {},
|
||||
"flow_title": "{host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
@ -5,7 +5,7 @@
|
||||
"already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt",
|
||||
"cannot_connect": "Verbindung fehlgeschlagen"
|
||||
},
|
||||
"flow_title": "Arcam FMJ auf {host}",
|
||||
"flow_title": "{host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "M\u00f6chtest du Arcam FMJ auf `{host}` zum Home Assistant hinzuf\u00fcgen?"
|
||||
|
22
homeassistant/components/arcam_fmj/translations/he.json
Normal file
22
homeassistant/components/arcam_fmj/translations/he.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
"already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.",
|
||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
||||
},
|
||||
"flow_title": "{host}",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
|
@ -5,6 +5,10 @@
|
||||
"already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso",
|
||||
"cannot_connect": "Impossibile connettersi"
|
||||
},
|
||||
"error": {
|
||||
"one": "Pi\u00f9",
|
||||
"other": "Altri"
|
||||
},
|
||||
"flow_title": "{host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
|
@ -111,7 +111,7 @@ async def async_setup(hass, config):
|
||||
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."""
|
||||
|
||||
# import options from yaml if empty
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user