Merge pull request #52627 from home-assistant/rc

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

View File

@ -115,12 +115,14 @@ omit =
homeassistant/components/bmw_connected_drive/notify.py
homeassistant/components/bmw_connected_drive/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

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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:

View File

@ -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.*

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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"
}
}
}

View File

@ -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)

View File

@ -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,
},
}

View File

@ -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

View File

@ -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:

View File

@ -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 {

View File

@ -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"
}
}
}

View File

@ -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:

View File

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

View File

@ -1,11 +1,20 @@
{
"config": {
"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"
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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."""

View File

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

View File

@ -19,13 +19,13 @@ from .weather_update_coordinator import WeatherUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
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

View File

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

View File

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

View File

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

View File

@ -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"
}
}

View File

@ -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

View File

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

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from typing import Final
from 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),
},
}

View File

@ -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

View File

@ -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]

View File

@ -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"
}
}
}

View File

@ -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]

View File

@ -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"

View File

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

View File

@ -337,24 +337,12 @@ class AirVisualEntity(CoordinatorEntity):
"""Initialize."""
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."""

View File

@ -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):

View File

@ -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):

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 = {

View File

@ -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"
}
}
}

View File

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

View File

@ -1155,8 +1155,6 @@ class AlexaPowerLevelController(AlexaCapability):
if self.entity.domain == fan.DOMAIN:
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(

View File

@ -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}"

View File

@ -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]

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,10 @@
{
"config": {
"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": {

View File

@ -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"],

View File

@ -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,
)

View File

@ -16,7 +16,7 @@
"no_usable_service": "Es wurde ein Ger\u00e4t gefunden, aber es konnte keine M\u00f6glichkeit gefunden werden, eine Verbindung zu diesem Ger\u00e4t herzustellen. Wenn diese Meldung weiterhin erscheint, versuche, die IP-Adresse anzugeben oder den Apple TV neu zu starten.",
"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!",

View File

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

View File

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

View File

@ -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]

View File

@ -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,

View File

@ -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": {

View File

@ -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?"

View File

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

View File

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

View File

@ -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": {

View File

@ -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