Merge pull request #61501 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2021-12-11 19:06:29 +01:00 committed by GitHub
commit 604a2ac327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4372 changed files with 117583 additions and 33776 deletions

120
.core_files.yaml Normal file
View File

@ -0,0 +1,120 @@
# Defines a list of files that are part of main core of Home Assistant.
# Changes to these files/filters define how our CI test suite is ran.
core: &core
- homeassistant/*.py
- homeassistant/auth/**
- homeassistant/helpers/*
- homeassistant/package_constraints.txt
- homeassistant/util/*
- pyproject.yaml
- requirements.txt
- setup.cfg
# Our base platforms, that are used by other integrations
base_platforms: &base_platforms
- homeassistant/components/air_quality/*
- homeassistant/components/alarm_control_panel/*
- homeassistant/components/binary_sensor/*
- homeassistant/components/button/*
- homeassistant/components/calendar/*
- homeassistant/components/camera/*
- homeassistant/components/climate/*
- homeassistant/components/cover/*
- homeassistant/components/device_tracker/*
- homeassistant/components/fan/*
- homeassistant/components/geo_location/*
- homeassistant/components/humidifier/*
- homeassistant/components/image_processing/*
- homeassistant/components/light/*
- homeassistant/components/lock/*
- homeassistant/components/media_player/*
- homeassistant/components/notify/*
- homeassistant/components/number/*
- homeassistant/components/remote/*
- homeassistant/components/scene/*
- homeassistant/components/select/*
- homeassistant/components/sensor/*
- homeassistant/components/siren/*
- homeassistant/components/stt/*
- homeassistant/components/switch/*
- homeassistant/components/tts/*
- homeassistant/components/vacuum/*
- homeassistant/components/water_heater/*
- homeassistant/components/weather/*
# Extra components that trigger the full suite
components: &components
- homeassistant/components/alert/*
- homeassistant/components/alexa/*
- homeassistant/components/auth/*
- homeassistant/components/automation/*
- homeassistant/components/cloud/*
- homeassistant/components/config/*
- homeassistant/components/configurator/*
- homeassistant/components/conversation/*
- homeassistant/components/demo/*
- homeassistant/components/device_automation/*
- homeassistant/components/dhcp/*
- homeassistant/components/discovery/*
- homeassistant/components/energy/*
- homeassistant/components/ffmpeg/*
- homeassistant/components/frontend/*
- homeassistant/components/google_assistant/*
- homeassistant/components/group/*
- homeassistant/components/hassio/*
- homeassistant/components/homeassistant/**
- homeassistant/components/image/*
- homeassistant/components/input_boolean/*
- homeassistant/components/input_datetime/*
- homeassistant/components/input_number/*
- homeassistant/components/input_select/*
- homeassistant/components/input_text/*
- homeassistant/components/logbook/*
- homeassistant/components/logger/*
- homeassistant/components/lovelace/*
- homeassistant/components/media_source/*
- homeassistant/components/mqtt/*
- homeassistant/components/network/*
- homeassistant/components/onboarding/*
- homeassistant/components/otp/*
- homeassistant/components/persistent_notification/*
- homeassistant/components/person/*
- homeassistant/components/recorder/*
- homeassistant/components/safe_mode/*
- homeassistant/components/script/*
- homeassistant/components/shopping_list/*
- homeassistant/components/ssdp/*
- homeassistant/components/stream/*
- homeassistant/components/sun/*
- homeassistant/components/system_health/*
- homeassistant/components/tag/*
- homeassistant/components/template/*
- homeassistant/components/timer/*
- homeassistant/components/usb/*
- homeassistant/components/webhook/*
- homeassistant/components/websocket_api/*
- homeassistant/components/zeroconf/*
- homeassistant/components/zone/*
# Testing related files that affect the whole test/linting suite
tests: &tests
- codecov.yaml
- requirements_test_pre_commit.txt
- requirements_test.txt
- tests/common.py
- tests/conftest.py
- tests/ignore_uncaught_exceptions.py
- tests/mock/*
- tests/test_util/*
- tests/testing_config/**
other: &other
- .github/workflows/*
- homeassistant/scripts/**
any:
- *base_platforms
- *components
- *core
- *other
- *tests

View File

@ -90,6 +90,8 @@ omit =
homeassistant/components/azure_devops/sensor.py
homeassistant/components/azure_service_bus/*
homeassistant/components/baidu/tts.py
homeassistant/components/balboa/__init__.py
homeassistant/components/balboa/entity.py
homeassistant/components/beewi_smartclim/sensor.py
homeassistant/components/bbb_gpio/*
homeassistant/components/bbox/device_tracker.py
@ -123,6 +125,7 @@ omit =
homeassistant/components/bosch_shc/__init__.py
homeassistant/components/bosch_shc/binary_sensor.py
homeassistant/components/bosch_shc/const.py
homeassistant/components/bosch_shc/cover.py
homeassistant/components/bosch_shc/entity.py
homeassistant/components/bosch_shc/sensor.py
homeassistant/components/braviatv/__init__.py
@ -137,7 +140,9 @@ omit =
homeassistant/components/broadlink/updater.py
homeassistant/components/brottsplatskartan/sensor.py
homeassistant/components/browser/*
homeassistant/components/brunt/__init__.py
homeassistant/components/brunt/cover.py
homeassistant/components/brunt/const.py
homeassistant/components/bsblan/climate.py
homeassistant/components/bt_home_hub_5/device_tracker.py
homeassistant/components/bt_smarthub/device_tracker.py
@ -281,6 +286,7 @@ omit =
homeassistant/components/eq3btsmart/climate.py
homeassistant/components/esphome/__init__.py
homeassistant/components/esphome/binary_sensor.py
homeassistant/components/esphome/button.py
homeassistant/components/esphome/camera.py
homeassistant/components/esphome/climate.py
homeassistant/components/esphome/cover.py
@ -369,7 +375,6 @@ omit =
homeassistant/components/fritzbox_callmonitor/const.py
homeassistant/components/fritzbox_callmonitor/base.py
homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/fronius/sensor.py
homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
@ -386,10 +391,6 @@ omit =
homeassistant/components/glances/sensor.py
homeassistant/components/gntp/notify.py
homeassistant/components/goalfeed/*
homeassistant/components/goalzero/__init__.py
homeassistant/components/goalzero/binary_sensor.py
homeassistant/components/goalzero/sensor.py
homeassistant/components/goalzero/switch.py
homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py
@ -399,8 +400,6 @@ omit =
homeassistant/components/google_travel_time/sensor.py
homeassistant/components/gpmdp/media_player.py
homeassistant/components/gpsd/sensor.py
homeassistant/components/greeneye_monitor/*
homeassistant/components/greeneye_monitor/sensor.py
homeassistant/components/greenwave/light.py
homeassistant/components/group/notify.py
homeassistant/components/growatt_server/sensor.py
@ -421,9 +420,6 @@ omit =
homeassistant/components/harmony/data.py
homeassistant/components/harmony/remote.py
homeassistant/components/harmony/util.py
homeassistant/components/hassio/binary_sensor.py
homeassistant/components/hassio/entity.py
homeassistant/components/hassio/sensor.py
homeassistant/components/haveibeenpwned/sensor.py
homeassistant/components/hdmi_cec/*
homeassistant/components/heatmiser/climate.py
@ -433,8 +429,9 @@ omit =
homeassistant/components/hisense_aehw4a1/climate.py
homeassistant/components/hitron_coda/device_tracker.py
homeassistant/components/hive/__init__.py
homeassistant/components/hive/climate.py
homeassistant/components/hive/alarm_control_panel.py
homeassistant/components/hive/binary_sensor.py
homeassistant/components/hive/climate.py
homeassistant/components/hive/light.py
homeassistant/components/hive/sensor.py
homeassistant/components/hive/switch.py
@ -502,7 +499,6 @@ omit =
homeassistant/components/incomfort/*
homeassistant/components/intesishome/*
homeassistant/components/ios/*
homeassistant/components/iota/*
homeassistant/components/iperf3/*
homeassistant/components/iqvia/*
homeassistant/components/irish_rail_transport/sensor.py
@ -521,6 +517,8 @@ omit =
homeassistant/components/isy994/switch.py
homeassistant/components/itach/remote.py
homeassistant/components/itunes/media_player.py
homeassistant/components/jellyfin/__init__.py
homeassistant/components/jellyfin/media_source.py
homeassistant/components/joaoapps_join/*
homeassistant/components/juicenet/__init__.py
homeassistant/components/juicenet/const.py
@ -541,7 +539,14 @@ omit =
homeassistant/components/keyboard_remote/*
homeassistant/components/kira/*
homeassistant/components/kiwi/lock.py
homeassistant/components/knx/*
homeassistant/components/knx/__init__.py
homeassistant/components/knx/climate.py
homeassistant/components/knx/cover.py
homeassistant/components/knx/expose.py
homeassistant/components/knx/knx_entity.py
homeassistant/components/knx/light.py
homeassistant/components/knx/notify.py
homeassistant/components/knx/schema.py
homeassistant/components/kodi/__init__.py
homeassistant/components/kodi/browse_media.py
homeassistant/components/kodi/const.py
@ -551,7 +556,9 @@ omit =
homeassistant/components/kostal_plenticore/__init__.py
homeassistant/components/kostal_plenticore/const.py
homeassistant/components/kostal_plenticore/helper.py
homeassistant/components/kostal_plenticore/select.py
homeassistant/components/kostal_plenticore/sensor.py
homeassistant/components/kostal_plenticore/switch.py
homeassistant/components/kwb/sensor.py
homeassistant/components/lacrosse/sensor.py
homeassistant/components/lametric/*
@ -590,7 +597,6 @@ omit =
homeassistant/components/lookin/models.py
homeassistant/components/lookin/sensor.py
homeassistant/components/lookin/climate.py
homeassistant/components/loopenergy/sensor.py
homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/__init__.py
homeassistant/components/luftdaten/sensor.py
@ -640,7 +646,6 @@ omit =
homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/hub.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/__init__.py
homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py
homeassistant/components/mill/sensor.py
@ -669,7 +674,6 @@ omit =
homeassistant/components/mutesync/binary_sensor.py
homeassistant/components/nest/const.py
homeassistant/components/mvglive/sensor.py
homeassistant/components/mychevy/*
homeassistant/components/mycroft/*
homeassistant/components/mysensors/__init__.py
homeassistant/components/mysensors/binary_sensor.py
@ -692,6 +696,8 @@ omit =
homeassistant/components/myq/light.py
homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/button.py
homeassistant/components/nanoleaf/entity.py
homeassistant/components/nanoleaf/light.py
homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py
@ -871,6 +877,8 @@ omit =
homeassistant/components/remote_rpi_gpio/*
homeassistant/components/rest/notify.py
homeassistant/components/rest/switch.py
homeassistant/components/ridwell/__init__.py
homeassistant/components/ridwell/sensor.py
homeassistant/components/ring/camera.py
homeassistant/components/ripple/sensor.py
homeassistant/components/rocketchat/notify.py
@ -906,6 +914,7 @@ omit =
homeassistant/components/screenlogic/binary_sensor.py
homeassistant/components/screenlogic/climate.py
homeassistant/components/screenlogic/light.py
homeassistant/components/screenlogic/number.py
homeassistant/components/screenlogic/sensor.py
homeassistant/components/screenlogic/services.py
homeassistant/components/screenlogic/switch.py
@ -926,6 +935,7 @@ omit =
homeassistant/components/shodan/sensor.py
homeassistant/components/shelly/__init__.py
homeassistant/components/shelly/binary_sensor.py
homeassistant/components/shelly/climate.py
homeassistant/components/shelly/entity.py
homeassistant/components/shelly/light.py
homeassistant/components/shelly/sensor.py
@ -1081,6 +1091,14 @@ omit =
homeassistant/components/todoist/calendar.py
homeassistant/components/todoist/const.py
homeassistant/components/tof/sensor.py
homeassistant/components/tolo/__init__.py
homeassistant/components/tolo/binary_sensor.py
homeassistant/components/tolo/button.py
homeassistant/components/tolo/climate.py
homeassistant/components/tolo/fan.py
homeassistant/components/tolo/light.py
homeassistant/components/tolo/select.py
homeassistant/components/tolo/sensor.py
homeassistant/components/tomato/device_tracker.py
homeassistant/components/toon/__init__.py
homeassistant/components/toon/binary_sensor.py
@ -1115,6 +1133,7 @@ omit =
homeassistant/components/tradfri/sensor.py
homeassistant/components/tradfri/switch.py
homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/__init__.py
homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/sensor.py
homeassistant/components/transmission/switch.py
@ -1124,6 +1143,7 @@ omit =
homeassistant/components/tuya/__init__.py
homeassistant/components/tuya/base.py
homeassistant/components/tuya/binary_sensor.py
homeassistant/components/tuya/button.py
homeassistant/components/tuya/camera.py
homeassistant/components/tuya/climate.py
homeassistant/components/tuya/const.py
@ -1139,8 +1159,6 @@ omit =
homeassistant/components/tuya/switch.py
homeassistant/components/tuya/util.py
homeassistant/components/tuya/vacuum.py
homeassistant/components/twentemilieu/const.py
homeassistant/components/twentemilieu/sensor.py
homeassistant/components/twilio_call/notify.py
homeassistant/components/twilio_sms/notify.py
homeassistant/components/twitter/notify.py
@ -1169,7 +1187,9 @@ omit =
homeassistant/components/velbus/switch.py
homeassistant/components/velux/*
homeassistant/components/venstar/__init__.py
homeassistant/components/venstar/binary_sensor.py
homeassistant/components/venstar/climate.py
homeassistant/components/venstar/sensor.py
homeassistant/components/verisure/__init__.py
homeassistant/components/verisure/alarm_control_panel.py
homeassistant/components/verisure/binary_sensor.py
@ -1186,7 +1206,12 @@ omit =
homeassistant/components/vesync/light.py
homeassistant/components/vesync/switch.py
homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/*
homeassistant/components/vicare/binary_sensor.py
homeassistant/components/vicare/climate.py
homeassistant/components/vicare/const.py
homeassistant/components/vicare/__init__.py
homeassistant/components/vicare/sensor.py
homeassistant/components/vicare/water_heater.py
homeassistant/components/vilfo/__init__.py
homeassistant/components/vilfo/sensor.py
homeassistant/components/vilfo/const.py
@ -1263,6 +1288,7 @@ omit =
homeassistant/components/yale_smart_alarm/coordinator.py
homeassistant/components/yamaha_musiccast/__init__.py
homeassistant/components/yamaha_musiccast/media_player.py
homeassistant/components/yamaha_musiccast/number.py
homeassistant/components/yandex_transport/*
homeassistant/components/yeelightsunflower/light.py
homeassistant/components/yi/camera.py

View File

@ -23,12 +23,12 @@ jobs:
publish: ${{ steps.version.outputs.publish }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -56,9 +56,7 @@ jobs:
uses: home-assistant/actions/helpers/codenotary@master
with:
source: file://${{ github.workspace }}/OFFICIAL_IMAGE
user: ${{ secrets.VCN_USER }}
password: ${{ secrets.VCN_PASSWORD }}
organisation: home-assistant.io
token: ${{ secrets.CAS_TOKEN }}
build_python:
name: Build PyPi package
@ -67,10 +65,10 @@ jobs:
if: needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -97,11 +95,11 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -133,15 +131,15 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2021.09.0
uses: home-assistant/builder@2021.11.4
with:
args: |
$BUILD_ARGS \
--${{ matrix.arch }} \
--target /data \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--generic ${{ needs.init.outputs.version }}
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
build_machine:
name: Build ${{ matrix.machine }} machine core image
@ -170,7 +168,7 @@ jobs:
- tinker
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
@ -186,14 +184,14 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2021.09.0
uses: home-assistant/builder@2021.11.4
with:
args: |
$BUILD_ARGS \
--target /data/machine \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
publish_ha:
name: Publish version files
@ -201,7 +199,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@ -233,7 +231,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
@ -248,8 +246,8 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install VCN tools
uses: home-assistant/actions/helpers/vcn@master
- name: Install CAS tools
uses: home-assistant/actions/helpers/cas@master
- name: Build Meta Image
shell: bash
@ -293,8 +291,7 @@ jobs:
function validate_image() {
local image=${1}
state="$(vcn authenticate --org home-assistant.io --output json docker://${image} | jq '.verification.status // 2')"
if [[ "${state}" != "0" ]]; then
if ! cas authenticate --signerID notary@home-assistant.io "docker://${image}"; then
echo "Invalid signature!"
exit 1
fi

View File

@ -15,7 +15,115 @@ env:
PRE_COMMIT_CACHE: ~/.cache/pre-commit
SQLALCHEMY_WARN_20: 1
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Determine what has changed
outputs:
# In case of issues with the partial run, use the following line instead:
# test_full_suite: 'true'
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
core: ${{ steps.core.outputs.changes }}
integrations: ${{ steps.integrations.outputs.changes }}
integrations_glob: ${{ steps.info.outputs.integrations_glob }}
tests: ${{ steps.info.outputs.tests }}
tests_glob: ${{ steps.info.outputs.tests_glob }}
test_groups: ${{ steps.info.outputs.test_groups }}
test_group_count: ${{ steps.info.outputs.test_group_count }}
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.4.0
- name: Filter for core changes
uses: dorny/paths-filter@v2.10.2
id: core
with:
filters: .core_files.yaml
- name: Create a list of integrations to filter for changes
run: |
integrations=$(ls -Ad ./homeassistant/components/[!_]* | xargs -n 1 basename)
touch .integration_paths.yaml
for integration in $integrations; do
echo "${integration}: [homeassistant/components/${integration}/*, tests/components/${integration}/*]" \
>> .integration_paths.yaml;
done
echo "Result:"
cat .integration_paths.yaml
- name: Filter for integration changes
uses: dorny/paths-filter@v2.10.2
id: integrations
with:
filters: .integration_paths.yaml
- name: Collect additional information
id: info
run: |
# Defaults
integrations_glob=""
test_full_suite="true"
test_groups="[1, 2, 3, 4, 5, 6]"
test_group_count=6
tests="[]"
tests_glob=""
if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]];
then
# Create a file glob for the integrations
integrations_glob=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '. | join(",")')
[[ "${integrations_glob}" == *","* ]] && integrations_glob="{${integrations_glob}}"
# Create list of testable integrations
possible_integrations=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '.[]')
tests=$(
for integration in ${possible_integrations};
do
if [[ -d "tests/components/${integration}" ]]; then
echo -n "\"${integration}\",";
fi;
done
)
[[ ! -z "${tests}" ]] && tests="${tests::-1}"
tests="[${tests}]"
test_groups="${tests}"
# Test group count should be 1, we don't split partial tests
test_group_count=1
# Create a file glob for the integrations tests
tests_glob=$(echo "${tests}" | jq -cSr '. | join(",")')
[[ "${tests_glob}" == *","* ]] && tests_glob="{${tests_glob}}"
test_full_suite="false"
fi
# We need to run the full suite on certain branches.
# Or, in case core files are touched, for the full suite as well.
if [[ "${{ github.ref }}" == "refs/heads/dev" ]] \
|| [[ "${{ github.ref }}" == "refs/heads/master" ]] \
|| [[ "${{ github.ref }}" == "refs/heads/rc" ]] \
|| [[ "${{ steps.core.outputs.any }}" == "true" ]];
then
test_groups="[1, 2, 3, 4, 5, 6]"
test_group_count=6
test_full_suite="true"
fi
# Output & sent to GitHub Actions
echo "test_full_suite: ${test_full_suite}"
echo "::set-output name=test_full_suite::${test_full_suite}"
echo "integrations_glob: ${integrations_glob}"
echo "::set-output name=integrations_glob::${integrations_glob}"
echo "test_group_count: ${test_group_count}"
echo "::set-output name=test_group_count::${test_group_count}"
echo "test_groups: ${test_groups}"
echo "::set-output name=test_groups::${test_groups}"
echo "tests: ${tests}"
echo "::set-output name=tests::${tests}"
echo "tests_glob: ${tests_glob}"
echo "::set-output name=tests_glob::${tests_glob}"
# Separate job to pre-populate the base dependency cache
# This prevent upcoming jobs to do the same individually
prepare-base:
@ -26,10 +134,10 @@ jobs:
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Generate partial Python venv restore key
@ -41,7 +149,7 @@ jobs:
hashFiles('homeassistant/package_constraints.txt') }}"
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: >-
@ -65,7 +173,7 @@ jobs:
hashFiles('.pre-commit-config.yaml') }}"
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: >-
@ -78,61 +186,23 @@ jobs:
. venv/bin/activate
pre-commit install-hooks
lint-bandit:
name: Check bandit
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Run bandit
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
lint-black:
name: Check black
runs-on: ubuntu-latest
needs: prepare-base
needs:
- changes
- prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -144,7 +214,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -153,131 +223,35 @@ jobs:
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Run black
- name: Run black (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
lint-codespell:
name: Check codespell
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Register codespell problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/codespell.json"
- name: Run codespell
- name: Run black (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files
lint-dockerfile:
name: Check Dockerfile
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
- name: Check Dockerfile
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile
- name: Check Dockerfile.dev
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile.dev
lint-executable-shebangs:
name: Check executables
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Register check executables problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json"
- name: Run executables check
run: |
. venv/bin/activate
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure
lint-flake8:
name: Check flake8
runs-on: ubuntu-latest
needs: prepare-base
needs:
- changes
- prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -289,7 +263,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -301,10 +275,17 @@ jobs:
- name: Register flake8 problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/flake8.json"
- name: Run flake8
- name: Run flake8 (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual flake8 --all-files
- name: Run flake8 (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/*
lint-isort:
name: Check isort
@ -312,15 +293,15 @@ jobs:
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -332,7 +313,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -346,21 +327,23 @@ jobs:
. venv/bin/activate
pre-commit run --hook-stage manual isort --all-files --show-diff-on-failure
lint-json:
name: Check JSON
lint-other:
name: Check other linters
runs-on: ubuntu-latest
needs: prepare-base
needs:
- changes
- prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -372,7 +355,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -381,6 +364,27 @@ jobs:
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Run pyupgrade (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
- name: Run pyupgrade (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure
- name: Register yamllint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Run yamllint
run: |
. venv/bin/activate
pre-commit run --hook-stage manual yamllint --all-files --show-diff-on-failure
- name: Register check-json problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/check-json.json"
@ -389,99 +393,45 @@ jobs:
. venv/bin/activate
pre-commit run --hook-stage manual check-json --all-files
lint-pyupgrade:
name: Check pyupgrade
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
- name: Register check executables problem matcher
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Run pyupgrade
echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json"
- name: Run executables check
run: |
. venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
# Disabled until we have the existing issues fixed
# lint-shellcheck:
# name: Check ShellCheck
# runs-on: ubuntu-latest
# needs: prepare-base
# steps:
# - name: Check out code from GitHub
# uses: actions/checkout@v2.3.5
# - name: Run ShellCheck
# uses: ludeeus/action-shellcheck@0.3.0
lint-yaml:
name: Check YAML
runs-on: ubuntu-latest
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
- name: Register codespell problem matcher
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2.1.6
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
- name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true'
run: |
echo "Failed to restore pre-commit environment from cache"
exit 1
- name: Register yamllint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Run yamllint
echo "::add-matcher::.github/workflows/matchers/codespell.json"
- name: Run codespell
run: |
. venv/bin/activate
pre-commit run --hook-stage manual yamllint --all-files --show-diff-on-failure
pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
- name: Check Dockerfile
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile
- name: Check Dockerfile.dev
uses: docker://hadolint/hadolint:v1.18.2
with:
args: hadolint Dockerfile.dev
- name: Run bandit (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
- name: Run bandit (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure
hassfest:
name: Check hassfest
@ -493,10 +443,10 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -517,15 +467,15 @@ jobs:
needs: prepare-base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -551,7 +501,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Generate partial Python venv restore key
id: generate-python-key
run: >-
@ -561,7 +511,7 @@ jobs:
hashFiles('homeassistant/package_constraints.txt') }}"
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: >-
@ -588,17 +538,19 @@ jobs:
pylint:
name: Check pylint
runs-on: ubuntu-latest
needs: prepare-tests
needs:
- changes
- prepare-tests
strategy:
matrix:
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -611,25 +563,34 @@ jobs:
- name: Register pylint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pylint.json"
- name: Run pylint
- name: Run pylint (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pylint homeassistant
- name: Run pylint (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
mypy:
name: Check mypy
runs-on: ubuntu-latest
needs: prepare-tests
needs:
- changes
- prepare-tests
strategy:
matrix:
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -642,28 +603,44 @@ jobs:
- name: Register mypy problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/mypy.json"
- name: Run mypy
- name: Run mypy (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
mypy homeassistant
- name: Run mypy (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
pytest:
runs-on: ubuntu-latest
needs: prepare-tests
if: needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob
needs:
- changes
- gen-requirements-all
- hassfest
- lint-black
- lint-other
- lint-isort
- mypy
- prepare-tests
strategy:
fail-fast: false
matrix:
group: [1, 2, 3, 4]
group: ${{ fromJson(needs.changes.outputs.test_groups) }}
python-version: [3.8, 3.9]
name: >-
Run tests Python ${{ matrix.python-version }} (group ${{ matrix.group }})
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -683,61 +660,82 @@ jobs:
# However this plugin is fairly new and doesn't run correctly
# on a non-GitHub environment.
pip install pytest-github-actions-annotate-failures==0.1.3
- name: Run pytest
- name: Register pytest slow test problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Run pytest (fully)
if: needs.changes.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
python3 -X dev -bb -m pytest \
python3 -X dev -m pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
--dist=loadfile \
--test-group-count 4 \
--test-group-count ${{ needs.changes.outputs.test_group_count }} \
--test-group=${{ matrix.group }} \
--cov homeassistant \
--cov-report= \
--cov-report=xml \
-o console_output_style=count \
-p no:sugar \
tests
- name: Run pytest (partially)
if: needs.changes.outputs.test_full_suite == 'false' && matrix.python-version != '3.8'
run: |
. venv/bin/activate
python3 -X dev -m pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
--cov homeassistant.components.${{ matrix.group }} \
--cov-report=xml \
--cov-report=term-missing \
-o console_output_style=count \
--durations=0 \
--durations-min=1 \
-p no:sugar \
tests/components/${{ matrix.group }}
- name: Run pytest (partially); no coverage
if: needs.changes.outputs.test_full_suite == 'false' && matrix.python-version == '3.8'
run: |
. venv/bin/activate
python3 -X dev -m pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
-o console_output_style=count \
--durations=0 \
--durations-min=1 \
-p no:sugar \
tests/components/${{ matrix.group }}
- name: Upload coverage artifact
uses: actions/upload-artifact@v2.2.4
with:
name: coverage-${{ matrix.python-version }}-group${{ matrix.group }}
path: .coverage
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
- name: Check dirty
run: |
./script/check_dirty
coverage:
name: Process test coverage
name: Upload test coverage to Codecov
runs-on: ubuntu-latest
needs: ["prepare-tests", "pytest"]
strategy:
matrix:
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
needs:
- changes
- pytest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.3.5
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.6
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
needs.prepare-tests.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
uses: actions/checkout@v2.4.0
- name: Download all coverage artifacts
uses: actions/download-artifact@v2
- name: Combine coverage results
run: |
. venv/bin/activate
coverage combine coverage*/.coverage*
coverage report --fail-under=94
coverage xml
- name: Upload coverage to Codecov
- name: Upload coverage to Codecov (full coverage)
if: needs.changes.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v2.1.0
with:
flags: full-suite
- name: Upload coverage to Codecov (partial coverage)
if: needs.changes.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v2.1.0

View File

@ -0,0 +1,18 @@
{
"problemMatcher": [
{
"owner": "python",
"pattern": [
{
"regexp": "^=+ slowest durations =+$"
},
{
"regexp": "^((.*s)\\s(call|setup|teardown)\\s+(.*)::(.*))$",
"message": 1,
"file": 2,
"loop": true
}
]
}
]
}

View File

@ -20,10 +20,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -39,10 +39,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v2.3.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@ -21,7 +21,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Get information
id: info
@ -68,7 +68,7 @@ jobs:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Download env_file
uses: actions/download-artifact@v2
@ -108,7 +108,7 @@ jobs:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.5
uses: actions/checkout@v2.4.0
- name: Download env_file
uses: actions/download-artifact@v2

View File

@ -1,11 +1,11 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.27.0
rev: v2.29.0
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/psf/black
rev: 21.9b0
rev: 21.11b1
hooks:
- id: black
args:
@ -17,7 +17,7 @@ repos:
hooks:
- id: codespell
args:
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
@ -45,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.9.3
rev: 5.10.0
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
@ -61,7 +61,7 @@ repos:
- --branch=master
- --branch=rc
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.26.1
rev: v1.26.3
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier

View File

@ -24,6 +24,7 @@ homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.*
homeassistant.components.braviatv.*
homeassistant.components.brother.*
homeassistant.components.button.*
homeassistant.components.calendar.*
homeassistant.components.camera.*
homeassistant.components.canary.*
@ -32,6 +33,7 @@ homeassistant.components.crownstone.*
homeassistant.components.device_automation.*
homeassistant.components.device_tracker.*
homeassistant.components.devolo_home_control.*
homeassistant.components.devolo_home_network.*
homeassistant.components.dlna_dmr.*
homeassistant.components.dnsip.*
homeassistant.components.dsmr.*
@ -40,17 +42,20 @@ homeassistant.components.efergy.*
homeassistant.components.elgato.*
homeassistant.components.esphome.*
homeassistant.components.energy.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.fastdotcom.*
homeassistant.components.fitbit.*
homeassistant.components.flunearyou.*
homeassistant.components.flux_led.*
homeassistant.components.forecast_solar.*
homeassistant.components.fritzbox.*
homeassistant.components.fronius.*
homeassistant.components.frontend.*
homeassistant.components.fritz.*
homeassistant.components.geo_location.*
homeassistant.components.gios.*
homeassistant.components.goalzero.*
homeassistant.components.greeneye_monitor.*
homeassistant.components.group.*
homeassistant.components.guardian.*
homeassistant.components.history.*
@ -62,6 +67,7 @@ homeassistant.components.image_processing.*
homeassistant.components.input_select.*
homeassistant.components.integration.*
homeassistant.components.iqvia.*
homeassistant.components.jellyfin.*
homeassistant.components.jewish_calendar.*
homeassistant.components.knx.*
homeassistant.components.kraken.*
@ -93,12 +99,14 @@ homeassistant.components.persistent_notification.*
homeassistant.components.pi_hole.*
homeassistant.components.proximity.*
homeassistant.components.rainmachine.*
homeassistant.components.rdw.*
homeassistant.components.recollect_waste.*
homeassistant.components.recorder.purge
homeassistant.components.recorder.repack
homeassistant.components.recorder.statistics
homeassistant.components.remote.*
homeassistant.components.renault.*
homeassistant.components.ridwell.*
homeassistant.components.rituals_perfume_genie.*
homeassistant.components.rpi_power.*
homeassistant.components.samsungtv.*
@ -119,18 +127,24 @@ homeassistant.components.switcher_kis.*
homeassistant.components.synology_dsm.*
homeassistant.components.systemmonitor.*
homeassistant.components.tag.*
homeassistant.components.tailscale.*
homeassistant.components.tautulli.*
homeassistant.components.tcp.*
homeassistant.components.tile.*
homeassistant.components.tplink.*
homeassistant.components.tolo.*
homeassistant.components.tractive.*
homeassistant.components.tradfri.*
homeassistant.components.tts.*
homeassistant.components.twentemilieu.*
homeassistant.components.upcloud.*
homeassistant.components.uptime.*
homeassistant.components.uptimerobot.*
homeassistant.components.vacuum.*
homeassistant.components.vallox.*
homeassistant.components.velbus.*
homeassistant.components.vlc_telnet.*
homeassistant.components.wallbox.*
homeassistant.components.water_heater.*
homeassistant.components.watttime.*
homeassistant.components.weather.*

2
.vscode/tasks.json vendored
View File

@ -64,7 +64,7 @@
"label": "Code Coverage",
"detail": "Generate code coverage report for a given integration.",
"type": "shell",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0",
"group": {
"kind": "test",
"isDefault": true

View File

@ -67,12 +67,14 @@ homeassistant/components/axis/* @Kane610
homeassistant/components/azure_devops/* @timmo001
homeassistant/components/azure_event_hub/* @eavanvalkenburg
homeassistant/components/azure_service_bus/* @hfurubotten
homeassistant/components/balboa/* @garbled1
homeassistant/components/beewi_smartclim/* @alemuro
homeassistant/components/bitcoin/* @fabaff
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
homeassistant/components/blebox/* @bbx-a @bbx-jp
homeassistant/components/blink/* @fronzbot
homeassistant/components/blueprint/* @home-assistant/core
homeassistant/components/bluesound/* @thrawnarn
homeassistant/components/bmp280/* @belidzs
homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe
homeassistant/components/bond/* @bdraco @prystupa @joshs85
@ -84,6 +86,7 @@ homeassistant/components/brunt/* @eavanvalkenburg
homeassistant/components/bsblan/* @liudger
homeassistant/components/bt_smarthub/* @jxwolstenholme
homeassistant/components/buienradar/* @mjj4791 @ties @Robbie1221
homeassistant/components/button/* @home-assistant/core
homeassistant/components/cast/* @emontnemery
homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren
homeassistant/components/circuit/* @braam
@ -118,6 +121,7 @@ homeassistant/components/denonavr/* @ol-iver @starkillerOG
homeassistant/components/derivative/* @afaucogney
homeassistant/components/device_automation/* @home-assistant/core
homeassistant/components/devolo_home_control/* @2Fake @Shutgun
homeassistant/components/devolo_home_network/* @2Fake @Shutgun
homeassistant/components/dexcom/* @gagebenne
homeassistant/components/dhcp/* @bdraco
homeassistant/components/dht/* @thegardenmonkey
@ -157,6 +161,7 @@ homeassistant/components/epson/* @pszafer
homeassistant/components/epsonworkforce/* @ThaStealth
homeassistant/components/eq3btsmart/* @rytilahti
homeassistant/components/esphome/* @OttoWinter @jesserockz
homeassistant/components/evil_genius_labs/* @balloob
homeassistant/components/evohome/* @zxdavb
homeassistant/components/ezviz/* @RenierM26 @baqs
homeassistant/components/faa_delays/* @ntilley905
@ -182,7 +187,7 @@ homeassistant/components/freebox/* @hacf-fr @Quentame
homeassistant/components/freedompro/* @stefano055415
homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74
homeassistant/components/fritzbox/* @mib1185 @flabbamann
homeassistant/components/fronius/* @nielstron
homeassistant/components/fronius/* @nielstron @farmio
homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/garages_amsterdam/* @klaasnicolaas
homeassistant/components/gdacs/* @exxamalte
@ -227,7 +232,7 @@ homeassistant/components/homematic/* @pvizeli @danielperna84
homeassistant/components/honeywell/* @rdfurman
homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop @fphammerle
homeassistant/components/hue/* @balloob @frenck
homeassistant/components/hue/* @balloob @marcelveldt
homeassistant/components/huisbaasje/* @dennisschroer
homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
homeassistant/components/hunterdouglas_powerview/* @bdraco
@ -261,6 +266,7 @@ homeassistant/components/irish_rail_transport/* @ttroy50
homeassistant/components/islamic_prayer_times/* @engrbm87
homeassistant/components/isy994/* @bdraco @shbatm
homeassistant/components/izone/* @Swamp-Ig
homeassistant/components/jellyfin/* @j-stienstra
homeassistant/components/jewish_calendar/* @tsvi
homeassistant/components/juicenet/* @jesserockz
homeassistant/components/kaiterra/* @Michsior14
@ -287,7 +293,6 @@ homeassistant/components/local_ip/* @issacg
homeassistant/components/logger/* @home-assistant/core
homeassistant/components/logi_circle/* @evanjd
homeassistant/components/lookin/* @ANMalko
homeassistant/components/loopenergy/* @pavoni
homeassistant/components/lovelace/* @home-assistant/frontend
homeassistant/components/luci/* @mzdrale
homeassistant/components/luftdaten/* @fabaff
@ -422,6 +427,7 @@ homeassistant/components/raincloud/* @vanstinator
homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert
homeassistant/components/rainmachine/* @bachya
homeassistant/components/random/* @fabaff
homeassistant/components/rdw/* @frenck
homeassistant/components/recollect_waste/* @bachya
homeassistant/components/recorder/* @home-assistant/core
homeassistant/components/rejseplanen/* @DarkFox
@ -429,6 +435,7 @@ homeassistant/components/renault/* @epenet
homeassistant/components/repetier/* @MTrab
homeassistant/components/rflink/* @javicalle
homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221
homeassistant/components/ridwell/* @bachya
homeassistant/components/ring/* @balloob
homeassistant/components/risco/* @OnFreund
homeassistant/components/rituals_perfume_genie/* @milanmeu
@ -520,12 +527,14 @@ homeassistant/components/system_bridge/* @timmo001
homeassistant/components/tado/* @michaelarnauts @noltari
homeassistant/components/tag/* @balloob @dmulcahey
homeassistant/components/tahoma/* @philklei
homeassistant/components/tailscale/* @frenck
homeassistant/components/tankerkoenig/* @guillempages
homeassistant/components/tapsaff/* @bazwilliams
homeassistant/components/tasmota/* @emontnemery
homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike
homeassistant/components/template/* @PhracturedBlue @tetienne @home-assistant/core
homeassistant/components/tesla_wall_connector/* @einarhauks
homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff
homeassistant/components/threshold/* @fabaff
@ -534,12 +543,12 @@ homeassistant/components/tile/* @bachya
homeassistant/components/time_date/* @fabaff
homeassistant/components/tmb/* @alemuro
homeassistant/components/todoist/* @boralyl
homeassistant/components/tolo/* @MatthiasLohr
homeassistant/components/totalconnect/* @austinmroczek
homeassistant/components/tplink/* @rytilahti @thegardenmonkey
homeassistant/components/traccar/* @ludeeus
homeassistant/components/trace/* @home-assistant/core
homeassistant/components/tractive/* @Danielhiversen @zhulik @bieniu
homeassistant/components/tradfri/* @janiversen
homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins
@ -575,6 +584,7 @@ homeassistant/components/vizio/* @raman325
homeassistant/components/vlc_telnet/* @rodripf @dmcc @MartinHjelmare
homeassistant/components/volkszaehler/* @fabaff
homeassistant/components/volumio/* @OnFreund
homeassistant/components/volvooncall/* @molobrakos @decompil3d
homeassistant/components/wake_on_lan/* @ntilley905
homeassistant/components/wallbox/* @hesselonline
homeassistant/components/waqi/* @andrey-git

View File

@ -7,12 +7,21 @@ ENV \
WORKDIR /usr/src
## Setup Home Assistant
## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements.txt
COPY requirements_all.txt homeassistant/
RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements_all.txt
## Setup Home Assistant Core
COPY . homeassistant/
RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements_all.txt \
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-e ./homeassistant \
&& python3 -m compileall homeassistant/homeassistant

View File

@ -30,11 +30,12 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
WORKDIR /workspaces
# Install Python dependencies from requirements
COPY requirements.txt requirements_test.txt requirements_test_pre_commit.txt ./
COPY requirements.txt ./
COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt
RUN pip3 install -r requirements.txt \
&& pip3 install -r requirements_test.txt \
&& rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
RUN pip3 install -r requirements.txt
COPY requirements_test.txt requirements_test_pre_commit.txt ./
RUN pip3 install -r requirements_test.txt
RUN rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
# Set the default shell to bash instead of sh
ENV SHELL /bin/bash

View File

@ -1,22 +0,0 @@
{
"image": "homeassistant/{arch}-homeassistant",
"shadow_repository": "ghcr.io/home-assistant",
"build_from": {
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.09.0",
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.09.0",
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.09.0",
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.09.0",
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.09.0"
},
"labels": {
"io.hass.type": "core",
"org.opencontainers.image.title": "Home Assistant",
"org.opencontainers.image.description": "Open-source home automation platform running on Python 3",
"org.opencontainers.image.source": "https://github.com/home-assistant/core",
"org.opencontainers.image.authors": "The Home Assistant Authors",
"org.opencontainers.image.url": "https://www.home-assistant.io/",
"org.opencontainers.image.documentation": "https://www.home-assistant.io/docs/",
"org.opencontainers.image.licenses": "Apache License 2.0"
},
"version_tag": true
}

20
build.yaml Normal file
View File

@ -0,0 +1,20 @@
image: homeassistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2021.09.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2021.09.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2021.09.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2021.09.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2021.09.0
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io
labels:
io.hass.type: core
org.opencontainers.image.title: Home Assistant
org.opencontainers.image.description: Open-source home automation platform running on Python 3
org.opencontainers.image.source: https://github.com/home-assistant/core
org.opencontainers.image.authors: The Home Assistant Authors
org.opencontainers.image.url: https://www.home-assistant.io/
org.opencontainers.image.documentation: https://www.home-assistant.io/docs/
org.opencontainers.image.licenses: Apache License 2.0

View File

@ -6,4 +6,28 @@ coverage:
default:
target: 90
threshold: 0.09
config-flows:
target: auto
threshold: 0
paths:
- homeassistant/components/*/config_flow.py
patch:
default:
target: auto
config-flows:
target: 100
threshold: 0
paths:
- homeassistant/components/*/config_flow.py
comment: false
# To make partial tests possible,
# we need to carry forward.
flag_management:
default_rules:
carryforward: false
individual_flags:
- name: full-suite
paths:
- ".*"
carryforward: true

View File

@ -0,0 +1,42 @@
"""Provide backwards compat for async_timeout."""
from __future__ import annotations
import asyncio
from typing import Any
import async_timeout
from homeassistant.helpers.frame import report
def timeout(
delay: float | None, loop: asyncio.AbstractEventLoop | None = None
) -> async_timeout.Timeout:
"""Backwards compatible timeout context manager that warns with loop usage."""
if loop is None:
loop = asyncio.get_running_loop()
else:
report(
"called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.2",
error_if_core=False,
)
if delay is not None:
deadline: float | None = loop.time() + delay
else:
deadline = None
return async_timeout.Timeout(deadline, loop)
def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None:
"""Backwards compatible current_task."""
report(
"called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.2; use asyncio.current_task instead",
error_if_core=False,
)
return asyncio.current_task()
def enable() -> None:
"""Enable backwards compat transitions."""
async_timeout.timeout = timeout
async_timeout.current_task = current_task # type: ignore[attr-defined]

View File

@ -214,11 +214,19 @@ class AuthManager:
return None
async def async_create_system_user(
self, name: str, group_ids: list[str] | None = None
self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User:
"""Create a system user."""
user = await self._store.async_create_user(
name=name, system_generated=True, is_active=True, group_ids=group_ids or []
name=name,
system_generated=True,
is_active=True,
group_ids=group_ids or [],
local_only=local_only,
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id})
@ -226,13 +234,18 @@ class AuthManager:
return user
async def async_create_user(
self, name: str, group_ids: list[str] | None = None
self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User:
"""Create a user."""
kwargs: dict[str, Any] = {
"name": name,
"is_active": True,
"group_ids": group_ids or [],
"local_only": local_only,
}
if await self._user_should_be_owner():
@ -304,13 +317,18 @@ class AuthManager:
name: str | None = None,
is_active: bool | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None:
"""Update a user."""
kwargs: dict[str, Any] = {}
if name is not None:
kwargs["name"] = name
if group_ids is not None:
kwargs["group_ids"] = group_ids
for attr_name, value in (
("name", name),
("group_ids", group_ids),
("local_only", local_only),
):
if value is not None:
kwargs[attr_name] = value
await self._store.async_update_user(user, **kwargs)
if is_active is not None:

View File

@ -42,7 +42,7 @@ class AuthStore:
self._groups: dict[str, models.Group] | None = None
self._perm_lookup: PermissionLookup | None = None
self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
)
self._lock = asyncio.Lock()
@ -86,6 +86,7 @@ class AuthStore:
system_generated: bool | None = None,
credentials: models.Credentials | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User:
"""Create a new user."""
if self._users is None:
@ -108,14 +109,14 @@ class AuthStore:
"perm_lookup": self._perm_lookup,
}
if is_owner is not None:
kwargs["is_owner"] = is_owner
if is_active is not None:
kwargs["is_active"] = is_active
if system_generated is not None:
kwargs["system_generated"] = system_generated
for attr_name, value in (
("is_owner", is_owner),
("is_active", is_active),
("local_only", local_only),
("system_generated", system_generated),
):
if value is not None:
kwargs[attr_name] = value
new_user = models.User(**kwargs)
@ -152,6 +153,7 @@ class AuthStore:
name: str | None = None,
is_active: bool | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None:
"""Update a user."""
assert self._groups is not None
@ -166,7 +168,11 @@ class AuthStore:
user.groups = groups
user.invalidate_permission_cache()
for attr_name, value in (("name", name), ("is_active", is_active)):
for attr_name, value in (
("name", name),
("is_active", is_active),
("local_only", local_only),
):
if value is not None:
setattr(user, attr_name, value)
@ -417,6 +423,8 @@ class AuthStore:
is_active=user_dict["is_active"],
system_generated=user_dict["system_generated"],
perm_lookup=perm_lookup,
# New in 2021.11
local_only=user_dict.get("local_only", False),
)
for cred_dict in data["credentials"]:
@ -502,6 +510,7 @@ class AuthStore:
"is_active": user.is_active,
"name": user.name,
"system_generated": user.system_generated,
"local_only": user.local_only,
}
for user in self._users.values()
]

View File

@ -100,7 +100,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
super().__init__(hass, config)
self._user_settings: _UsersDict | None = None
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
)
self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, [])

View File

@ -77,7 +77,7 @@ class TotpAuthModule(MultiFactorAuthModule):
super().__init__(hass, config)
self._users: dict[str, str] | None = None
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
)
self._init_lock = asyncio.Lock()

View File

@ -39,6 +39,7 @@ class User:
is_owner: bool = attr.ib(default=False)
is_active: bool = attr.ib(default=False)
system_generated: bool = attr.ib(default=False)
local_only: bool = attr.ib(default=False)
groups: list[Group] = attr.ib(factory=list, eq=False, order=False)

View File

@ -63,7 +63,7 @@ class Data:
"""Initialize the user data store."""
self.hass = hass
self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
)
self._data: dict[str, Any] | None = None
# Legacy mode will allow usernames to start/end with whitespace

View File

@ -102,7 +102,7 @@ class ExampleLoginFlow(LoginFlow):
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the step of the form."""
errors = {}
errors = None
if user_input is not None:
try:
@ -110,7 +110,7 @@ class ExampleLoginFlow(LoginFlow):
user_input["username"], user_input["password"]
)
except InvalidAuthError:
errors["base"] = "invalid_auth"
errors = {"base": "invalid_auth"}
if not errors:
user_input.pop("password")

View File

@ -194,6 +194,12 @@ class TrustedNetworksAuthProvider(AuthProvider):
if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies):
raise InvalidAuthError("Can't allow access from a proxy server")
if "cloud" in self.hass.config.components:
from hass_nabucasa import remote # pylint: disable=import-outside-toplevel
if remote.is_cloud_request.get():
raise InvalidAuthError("Can't allow access from Home Assistant Cloud")
@callback
def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None

View File

@ -0,0 +1 @@
"""Backports from newer Python versions."""

View File

@ -0,0 +1,33 @@
"""Enum backports from standard lib."""
from __future__ import annotations
from enum import Enum
from typing import Any, TypeVar
T = TypeVar("T", bound="StrEnum")
class StrEnum(str, Enum):
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
def __new__(cls: type[T], value: str, *args: Any, **kwargs: Any) -> T:
"""Create a new StrEnum instance."""
if not isinstance(value, str):
raise TypeError(f"{value!r} is not a string")
return super().__new__(cls, value, *args, **kwargs) # type: ignore[call-overload,no-any-return]
def __str__(self) -> str:
"""Return self.value."""
return str(self.value)
@staticmethod
def _generate_next_value_( # pylint: disable=arguments-differ # https://github.com/PyCQA/pylint/issues/5371
name: str, start: int, count: int, last_values: list[Any]
) -> Any:
"""
Make `auto()` explicitly unsupported.
We may revisit this when it's very clear that Python 3.11's
`StrEnum.auto()` behavior will no longer change.
"""
raise TypeError("auto() is not supported by this implementation")

View File

@ -252,8 +252,7 @@ async def async_from_config_dict(
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
f"be removed in Home Assistant {REQUIRED_NEXT_PYTHON_HA_RELEASE}. "
"Please upgrade Python to "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
"higher."
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER[:2])}."
)
_LOGGER.warning(msg)
hass.components.persistent_notification.async_create(

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
@ -53,14 +54,14 @@ CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [
"alarm_control_panel",
"binary_sensor",
"lock",
"switch",
"cover",
"camera",
"light",
"sensor",
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.LOCK,
Platform.SWITCH,
Platform.COVER,
Platform.CAMERA,
Platform.LIGHT,
Platform.SENSOR,
]

View File

@ -0,0 +1,35 @@
{
"config": {
"abort": {
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f",
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c",
"invalid_mfa_code": "\u7121\u52b9\u306aMFA\u30b3\u30fc\u30c9"
},
"step": {
"mfa": {
"data": {
"mfa_code": "MFA\u30b3\u30fc\u30c9(6\u6841)"
},
"title": "Abode\u306eMFA\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044"
},
"reauth_confirm": {
"data": {
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9",
"username": "E\u30e1\u30fc\u30eb"
},
"title": "Abode\u306e\u30ed\u30b0\u30a4\u30f3\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044"
},
"user": {
"data": {
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9",
"username": "E\u30e1\u30fc\u30eb"
},
"title": "Abode\u306e\u30ed\u30b0\u30a4\u30f3\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044"
}
}
}
}

View File

@ -27,7 +27,8 @@
"data": {
"password": "Parola",
"username": "E-posta"
}
},
"title": "Abode giri\u015f bilgilerinizi doldurun"
}
}
}

View File

@ -11,7 +11,7 @@ from aiohttp.client_exceptions import ClientConnectorError
from async_timeout import timeout
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -20,7 +20,7 @@ from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor", "weather"]
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Final
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.components.sensor import SensorStateClass
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
@ -220,7 +220,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Ceiling",
@ -228,7 +228,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Cloud Ceiling",
unit_metric=LENGTH_METERS,
unit_imperial=LENGTH_FEET,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="CloudCover",
@ -237,7 +237,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="DewPoint",
@ -246,7 +246,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperature",
@ -254,7 +254,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="RealFeel Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShade",
@ -263,7 +263,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Precipitation",
@ -271,7 +271,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Precipitation",
unit_metric=LENGTH_MILLIMETERS,
unit_imperial=LENGTH_INCHES,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="PressureTendency",
@ -287,7 +287,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="UV Index",
unit_metric=UV_INDEX,
unit_imperial=UV_INDEX,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WetBulbTemperature",
@ -296,7 +296,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindChillTemperature",
@ -305,7 +305,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Wind",
@ -313,7 +313,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Wind",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindGust",
@ -322,6 +322,6 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
)

View File

@ -7,6 +7,7 @@ from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
@ -95,7 +96,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self._unit_system = API_IMPERIAL
self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = DeviceInfo(
entry_type="service",
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER,
name=NAME,

View File

@ -1,7 +1,22 @@
{
"config": {
"abort": {
"single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f."
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},
"step": {
"user": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447",
"latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430",
"longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430",
"name": "\u0418\u043c\u0435"
},
"title": "AccuWeather"
}
}
}
}

View File

@ -0,0 +1,41 @@
{
"config": {
"abort": {
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc",
"requests_exceeded": "Accuweather API\u3078\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u6570\u304c\u8a31\u53ef\u3055\u308c\u305f\u6570\u3092\u8d85\u3048\u307e\u3057\u305f\u3002\u6642\u9593\u3092\u7f6e\u304f\u304b\u3001API\u30ad\u30fc\u3092\u5909\u66f4\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"name": "\u540d\u524d"
},
"description": "\u8a2d\u5b9a\u306b\u3064\u3044\u3066\u30d8\u30eb\u30d7\u304c\u5fc5\u8981\u306a\u5834\u5408\u306f\u3001https://www.home-assistant.io/integrations/accuweather/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306e\u8a2d\u5b9a\u5f8c\u306b\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u6709\u52b9\u306b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002",
"title": "AccuWeather"
}
}
},
"options": {
"step": {
"user": {
"data": {
"forecast": "\u5929\u6c17\u4e88\u5831"
},
"description": "\u5236\u9650\u306b\u3088\u308a\u7121\u6599\u30d0\u30fc\u30b8\u30e7\u30f3\u306eAccuWeather API\u30ad\u30fc\u3067\u306f\u3001\u5929\u6c17\u4e88\u5831\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u30c7\u30fc\u30bf\u306e\u66f4\u65b0\u306f40\u5206\u3067\u306f\u306a\u304f80\u5206\u3054\u3068\u3067\u3059\u3002",
"title": "AccuWeather\u306e\u30aa\u30d7\u30b7\u30e7\u30f3"
}
}
},
"system_health": {
"info": {
"can_reach_server": "AccuWeather\u30b5\u30fc\u30d0\u30fc\u306b\u5230\u9054",
"remaining_requests": "\u6b8b\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8"
}
}
}

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "\u4e0b\u964d",
"rising": "\u4e0a\u6607",
"steady": "\u5b89\u5b9a"
}
}
}

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "D\u00fc\u015f\u00fcyor",
"rising": "Y\u00fckseliyor",
"steady": "Sabit"
}
}
}

View File

@ -5,7 +5,8 @@
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131",
"requests_exceeded": "Accuweather API i\u00e7in izin verilen istek say\u0131s\u0131 a\u015f\u0131ld\u0131. API Anahtar\u0131n\u0131 beklemeniz veya de\u011fi\u015ftirmeniz gerekir."
},
"step": {
"user": {
@ -15,6 +16,7 @@
"longitude": "Boylam",
"name": "Ad"
},
"description": "Yap\u0131land\u0131rmayla ilgili yard\u0131ma ihtiyac\u0131n\u0131z varsa buraya bak\u0131n: https://www.home-assistant.io/integrations/accuweather/ \n\n Baz\u0131 sens\u00f6rler varsay\u0131lan olarak etkin de\u011fildir. Bunlar\u0131, entegrasyon yap\u0131land\u0131rmas\u0131ndan sonra varl\u0131k kay\u0131t defterinde etkinle\u015ftirebilirsiniz.\n Hava tahmini varsay\u0131lan olarak etkin de\u011fildir. Entegrasyon se\u00e7eneklerinde etkinle\u015ftirebilirsiniz.",
"title": "AccuWeather"
}
}
@ -25,6 +27,7 @@
"data": {
"forecast": "Hava Durumu tahmini"
},
"description": "AccuWeather API anahtar\u0131n\u0131n \u00fccretsiz s\u00fcr\u00fcm\u00fcn\u00fcn s\u0131n\u0131rlamalar\u0131 nedeniyle, hava tahminini etkinle\u015ftirdi\u011finizde, veri g\u00fcncellemeleri her 40 dakikada bir yerine 80 dakikada bir ger\u00e7ekle\u015ftirilir.",
"title": "AccuWeather Se\u00e7enekleri"
}
}

View File

@ -19,6 +19,7 @@ from homeassistant.components.weather import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -68,10 +69,16 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
)
self._attr_attribution = ATTRIBUTION
self._attr_device_info = DeviceInfo(
entry_type="service",
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER,
name=NAME,
# You don't need to provide specific details for the URL,
# so passing in _ characters is fine if the location key
# is correct
configuration_url="http://accuweather.com/en/"
f"_/_/{coordinator.location_key}/"
f"weather-forecast/{coordinator.location_key}/",
)
@property

View File

@ -111,8 +111,7 @@ class AcerSwitch(SwitchEntity):
"""Write msg, obtain answer and format output."""
# answers are formatted as ***\answer\r***
awns = self._write_read(msg)
match = re.search(r"\r(.+)\r", awns)
if match:
if match := re.search(r"\r(.+)\r", awns):
return match.group(1)
return STATE_UNKNOWN

View File

@ -1,13 +1,14 @@
"""The Rollease Acmeda Automate integration."""
from homeassistant import config_entries, core
from homeassistant.const import Platform
from .const import DOMAIN
from .hub import PulseHub
CONF_HUBS = "hubs"
PLATFORMS = ["cover", "sensor"]
PLATFORMS = [Platform.COVER, Platform.SENSOR]
async def async_setup_entry(

View File

@ -9,6 +9,7 @@ import async_timeout
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_ID
from .const import DOMAIN
@ -27,9 +28,9 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if (
user_input is not None
and self.discovered_hubs is not None
and user_input["id"] in self.discovered_hubs
and user_input[CONF_ID] in self.discovered_hubs
):
return await self.async_create(self.discovered_hubs[user_input["id"]])
return await self.async_create(self.discovered_hubs[user_input[CONF_ID]])
# Already configured hosts
already_configured = {
@ -55,7 +56,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user",
data_schema=vol.Schema(
{
vol.Required("id"): vol.In(
vol.Required(CONF_ID): vol.In(
{hub.id: f"{hub.id} {hub.host}" for hub in hubs}
)
}
@ -65,4 +66,4 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_create(self, hub):
"""Create the Acmeda Hub entry."""
await self.async_set_unique_id(hub.id, raise_on_progress=False)
return self.async_create_entry(title=hub.id, data={"host": hub.host})
return self.async_create_entry(title=hub.id, data={CONF_HOST: hub.host})

View File

@ -3,7 +3,7 @@
"name": "Rollease Acmeda Automate",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/acmeda",
"requirements": ["aiopulse==0.4.2"],
"requirements": ["aiopulse==0.4.3"],
"codeowners": ["@atmurray"],
"iot_class": "local_push"
}

View File

@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093"
},
"step": {
"user": {
"data": {
"id": "\u30db\u30b9\u30c8ID"
},
"title": "\u8ffd\u52a0\u3059\u308b\u30cf\u30d6\u306e\u9078\u629e"
}
}
}
}

View File

@ -1,10 +1,14 @@
{
"config": {
"abort": {
"no_devices_found": "A\u011fda cihaz bulunamad\u0131"
},
"step": {
"user": {
"data": {
"id": "Ana bilgisayar kimli\u011fi"
}
},
"title": "Eklemek i\u00e7in bir merkez se\u00e7in"
}
}
}

View File

@ -71,8 +71,7 @@ class ActiontecDeviceScanner(DeviceScanner):
if not self.success_init:
return False
actiontec_data = self.get_actiontec_data()
if actiontec_data is None:
if (actiontec_data := self.get_actiontec_data()) is None:
return False
self.last_results = [
device for device in actiontec_data if device.timevalid > -60

View File

@ -2,9 +2,10 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
PLATFORMS = ["climate"]
PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adax",
"requirements": [
"adax==0.1.1"
"adax==0.2.0"
],
"codeowners": [
"@danielhiversen"

View File

@ -4,7 +4,7 @@
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435"
},
"step": {

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c"
},
"step": {
"user": {
"data": {
"account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID",
"host": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama"
},
"step": {
"user": {
"data": {
"account_id": "Hesap Kimli\u011fi",
"host": "Ana bilgisayar",
"password": "Parola"
}
}
}
}
}

View File

@ -6,7 +6,7 @@ import logging
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
@ -16,11 +16,13 @@ from homeassistant.const import (
CONF_URL,
CONF_USERNAME,
CONF_VERIFY_SSL,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import (
@ -45,7 +47,7 @@ SERVICE_REFRESH_SCHEMA = vol.Schema(
{vol.Optional(CONF_FORCE, default=False): cv.boolean}
)
PLATFORMS = ["sensor", "switch"]
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -196,8 +198,16 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
@property
def device_info(self) -> DeviceInfo:
"""Return device information about this AdGuard Home instance."""
if self._entry.source == SOURCE_HASSIO:
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
else:
if self.adguard.tls:
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
else:
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
return DeviceInfo(
entry_type="service",
entry_type=DeviceEntryType.SERVICE,
identifiers={
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore
},
@ -206,4 +216,5 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
DATA_ADGUARD_VERSION
),
configuration_url=config_url,
)

View File

@ -6,6 +6,7 @@ from typing import Any
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
import voluptuous as vol
from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.config_entries import ConfigFlow
from homeassistant.const import (
CONF_HOST,
@ -104,14 +105,14 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
},
)
async def async_step_hassio(self, discovery_info: dict[str, Any]) -> FlowResult:
async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult:
"""Prepare configuration for a Hass.io AdGuard Home add-on.
This flow is triggered by the discovery component.
"""
await self._async_handle_discovery_without_unique_id()
self._hassio_discovery = discovery_info
self._hassio_discovery = discovery_info.config
return await self.async_step_hassio_confirm()
async def async_step_hassio_confirm(

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059",
"existing_instance_updated": "\u65e2\u5b58\u306e\u8a2d\u5b9a\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f"
},
"step": {
"hassio_confirm": {
"description": "\u30a2\u30c9\u30aa\u30f3 {addon} \u304c\u3001\u63d0\u4f9b\u3059\u308bAdGuard Home\u306b\u63a5\u7d9a\u3059\u308b\u3088\u3046\u306bHome Assistant\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u304b\uff1f",
"title": "Home Assistant\u30a2\u30c9\u30aa\u30f3\u7d4c\u7531\u306eAdGuard Home"
},
"user": {
"data": {
"host": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9",
"port": "\u30dd\u30fc\u30c8",
"ssl": "SSL\u8a3c\u660e\u66f8\u3092\u4f7f\u7528\u3059\u308b",
"username": "\u30e6\u30fc\u30b6\u30fc\u540d",
"verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b"
},
"description": "\u76e3\u8996\u3068\u5236\u5fa1\u304c\u3067\u304d\u308b\u3088\u3046\u306b\u3001AdGuardHome\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u3057\u307e\u3059\u3002"
}
}
}
}

View File

@ -1,16 +1,27 @@
{
"config": {
"abort": {
"already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f",
"existing_instance_updated": "Mevcut yap\u0131land\u0131rma g\u00fcncellendi."
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131"
},
"step": {
"hassio_confirm": {
"description": "{addon} taraf\u0131ndan sa\u011flanan AdGuard Home'a ba\u011flanmak i\u00e7in Home Assistant'\u0131 yap\u0131land\u0131rmak istiyor musunuz?",
"title": "Home Assistant eklentisi arac\u0131l\u0131\u011f\u0131yla AdGuard Home"
},
"user": {
"data": {
"host": "Ana Bilgisayar",
"host": "Ana bilgisayar",
"password": "Parola",
"port": "Port",
"username": "Kullan\u0131c\u0131 Ad\u0131"
}
"ssl": "SSL sertifikas\u0131 kullan\u0131r",
"username": "Kullan\u0131c\u0131 Ad\u0131",
"verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n"
},
"description": "AdGuard Home \u00f6rne\u011finizi, izleme ve kontrole izin verecek \u015fekilde ayarlay\u0131n."
}
}
}

View File

@ -307,7 +307,7 @@ class AdsEntity(Entity):
self._ads_hub.add_device_notification, ads_var, plctype, update
)
try:
with async_timeout.timeout(10):
async with async_timeout.timeout(10):
await self._event.wait()
except asyncio.TimeoutError:
_LOGGER.debug("Variable %s: Timeout during first update", ads_var)

View File

@ -5,14 +5,21 @@ import logging
from advantage_air import ApiError, advantage_air
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, Platform
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ADVANTAGE_AIR_RETRY, DOMAIN
ADVANTAGE_AIR_SYNC_INTERVAL = 15
PLATFORMS = ["binary_sensor", "climate", "cover", "select", "sensor", "switch"]
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.COVER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
_LOGGER = logging.getLogger(__name__)

View File

@ -10,6 +10,7 @@ from homeassistant.components.climate.const import (
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE,
@ -53,7 +54,7 @@ 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}
ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone"
ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY]
ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL]
PARALLEL_UPDATES = 0
@ -169,7 +170,7 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
def hvac_mode(self):
"""Return the current state as HVAC mode."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return HVAC_MODE_FAN_ONLY
return HVAC_MODE_HEAT_COOL
return HVAC_MODE_OFF
@property

View File

@ -3,8 +3,8 @@ import voluptuous as vol
from homeassistant.components.sensor import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
SensorEntity,
SensorStateClass,
)
from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC, PERCENTAGE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv, entity_platform
@ -84,7 +84,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor."""
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key):
@ -114,7 +114,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor."""
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key):
@ -149,7 +149,7 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
_attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_registry_enabled_default = False
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f"
},
"step": {
"user": {
"data": {
"ip_address": "IP\u30a2\u30c9\u30ec\u30b9",
"port": "\u30dd\u30fc\u30c8"
},
"description": "Advantage Air wall mounted tablet\u306eAPI\u306b\u63a5\u7d9a\u3057\u307e\u3059\u3002",
"title": "\u63a5\u7d9a"
}
}
}
}

View File

@ -9,9 +9,10 @@
"step": {
"user": {
"data": {
"ip_address": "\u0130p Adresi",
"ip_address": "IP Adresi",
"port": "Port"
},
"description": "Advantage Air duvara monte tabletinizin API'sine ba\u011flan\u0131n.",
"title": "Ba\u011flan"
}
}

View File

@ -1,10 +1,7 @@
"""Constant values for the AEMET OpenData component."""
from __future__ import annotations
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntityDescription,
)
from homeassistant.components.sensor import SensorEntityDescription, SensorStateClass
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
@ -36,11 +33,12 @@ from homeassistant.const import (
PRESSURE_HPA,
SPEED_KILOMETERS_PER_HOUR,
TEMP_CELSIUS,
Platform,
)
ATTRIBUTION = "Powered by AEMET OpenData"
CONF_STATION_UPDATES = "station_updates"
PLATFORMS = ["sensor", "weather"]
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
DEFAULT_NAME = "AEMET"
DOMAIN = "aemet"
ENTRY_NAME = "name"
@ -255,14 +253,14 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_PRESSURE,
name="Pressure",
native_unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_RAIN,
@ -273,7 +271,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_RAIN_PROB,
name="Rain probability",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_SNOW,
@ -284,7 +282,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_SNOW_PROB,
name="Snow probability",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_STATION_ID,
@ -303,21 +301,21 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_STORM_PROB,
name="Storm probability",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING,
name="Temperature feeling",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TOWN_ID,
@ -336,7 +334,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED,
@ -347,7 +345,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
)

View File

@ -0,0 +1,14 @@
{
"config": {
"error": {
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},
"step": {
"user": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447"
}
}
}
}
}

View File

@ -0,0 +1,31 @@
{
"config": {
"abort": {
"already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d"
},
"description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044",
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "AEMET weather station\u304b\u3089\u30c7\u30fc\u30bf\u3092\u53ce\u96c6\u3059\u308b"
}
}
}
}
}

View File

@ -14,7 +14,7 @@
"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 AEMET OpenData. \u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://opendata.aemet.es/centrodedescargas/altaUsuario.",
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AEMET OpenData. \u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://opendata.aemet.es/centrodedescargas/altaUsuario.",
"title": "AEMET OpenData"
}
}

View File

@ -0,0 +1,31 @@
{
"config": {
"abort": {
"already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
},
"step": {
"user": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam",
"name": "Entegrasyonun ad\u0131"
},
"description": "AEMET OpenData entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://opendata.aemet.es/centrodedescargas/altaUsuario adresine gidin.",
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "AEMET hava istasyonlar\u0131ndan veri toplay\u0131n"
}
}
}
}
}

View File

@ -140,7 +140,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
async def _async_update_data(self):
data = {}
with async_timeout.timeout(120):
async with async_timeout.timeout(120):
weather_response = await self._get_aemet_weather()
data = self._convert_weather_response(weather_response)
return data
@ -398,8 +398,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
return None
def _convert_forecast_day(self, date, day):
condition = self._get_condition_day(day)
if not condition:
if not (condition := self._get_condition_day(day)):
return None
return {
@ -415,8 +414,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
}
def _convert_forecast_hour(self, date, day, hour):
condition = self._get_condition(day, hour)
if not condition:
if not (condition := self._get_condition(day, hour)):
return None
forecast_dt = date.replace(hour=hour, minute=0, second=0)
@ -435,13 +433,8 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
def _calc_precipitation(self, day, hour):
"""Calculate the precipitation."""
rain_value = self._get_rain(day, hour)
if not rain_value:
rain_value = 0
snow_value = self._get_snow(day, hour)
if not snow_value:
snow_value = 0
rain_value = self._get_rain(day, hour) or 0
snow_value = self._get_snow(day, hour) or 0
if round(rain_value + snow_value, 1) == 0:
return None
@ -449,13 +442,8 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
def _calc_precipitation_prob(self, day, hour):
"""Calculate the precipitation probability (hour)."""
rain_value = self._get_rain_prob(day, hour)
if not rain_value:
rain_value = 0
snow_value = self._get_snow_prob(day, hour)
if not snow_value:
snow_value = 0
rain_value = self._get_rain_prob(day, hour) or 0
snow_value = self._get_snow_prob(day, hour) or 0
if rain_value == 0 and snow_value == 0:
return None

View File

@ -3,6 +3,7 @@
from agent import AgentError
from agent.a import Agent
from homeassistant.const import Platform
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -12,7 +13,7 @@ from .const import CONNECTION, DOMAIN as AGENT_DOMAIN, SERVER_URL
ATTRIBUTION = "ispyconnect.com"
DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
FORWARDS = ["alarm_control_panel", "camera"]
PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.CAMERA]
async def async_setup_entry(hass, config_entry):
@ -35,7 +36,7 @@ async def async_setup_entry(hass, config_entry):
hass.data[AGENT_DOMAIN][config_entry.entry_id] = {CONNECTION: agent_client}
device_registry = await dr.async_get_registry(hass)
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
@ -46,14 +47,16 @@ async def async_setup_entry(hass, config_entry):
sw_version=agent_client.version,
)
hass.config_entries.async_setup_platforms(config_entry, FORWARDS)
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass, config_entry):
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, FORWARDS)
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
await hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION].close()

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059",
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f"
},
"step": {
"user": {
"data": {
"host": "\u30db\u30b9\u30c8",
"port": "\u30dd\u30fc\u30c8"
},
"title": "Agent DVR\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
}
}
}
}

View File

@ -10,7 +10,7 @@
"step": {
"user": {
"data": {
"host": "Ana Bilgisayar",
"host": "Ana bilgisayar",
"port": "Port"
},
"title": "Agent DVR'\u0131 kurun"

View File

@ -137,8 +137,7 @@ class AirQualityEntity(Entity):
data: dict[str, str | int | float] = {}
for prop, attr in PROP_TO_ATTR.items():
value = getattr(self, prop)
if value is not None:
if (value := getattr(self, prop)) is not None:
data[attr] = value
return data

View File

@ -13,7 +13,7 @@ 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.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -33,7 +33,7 @@ from .const import (
NO_AIRLY_SENSORS,
)
PLATFORMS = ["sensor"]
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
@ -167,7 +167,7 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator):
measurements = self.airly.create_measurements_session_point(
self.latitude, self.longitude
)
with async_timeout.timeout(20):
async with async_timeout.timeout(20):
try:
await measurements.update()
except (AirlyError, ClientConnectorError) as error:

View File

@ -103,7 +103,7 @@ async def test_location(
measurements = airly.create_measurements_session_point(
latitude=latitude, longitude=longitude
)
with async_timeout.timeout(10):
async with async_timeout.timeout(10):
await measurements.update()
current = measurements.current

View File

@ -27,6 +27,7 @@ from homeassistant.const import (
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
@ -154,7 +155,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
"""Initialize."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
entry_type="service",
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
manufacturer=MANUFACTURER,
name=DEFAULT_NAME,

View File

@ -1,6 +1,10 @@
{
"config": {
"abort": {
"already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447",
"wrong_location": "\u0412 \u0442\u0430\u0437\u0438 \u043e\u0431\u043b\u0430\u0441\u0442 \u043d\u044f\u043c\u0430 \u0438\u0437\u043c\u0435\u0440\u0432\u0430\u0442\u0435\u043b\u043d\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 Airly."
},
"step": {

View File

@ -0,0 +1,30 @@
{
"config": {
"abort": {
"already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc",
"wrong_location": "\u3053\u306e\u30a8\u30ea\u30a2\u306b\u3001Airly\u306e\u6e2c\u5b9a\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u306f\u3042\u308a\u307e\u305b\u3093\u3002"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"name": "\u540d\u524d"
},
"description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044",
"title": "Airly"
}
}
},
"system_health": {
"info": {
"can_reach_server": "Airly\u30b5\u30fc\u30d0\u30fc\u306b\u5230\u9054",
"requests_per_day": "1\u65e5\u3042\u305f\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8",
"requests_remaining": "\u6b8b\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8"
}
}
}

View File

@ -4,21 +4,27 @@
"already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131",
"wrong_location": "Bu b\u00f6lgede Airly \u00f6l\u00e7\u00fcm istasyonu yok."
},
"step": {
"user": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam"
}
"longitude": "Boylam",
"name": "Ad"
},
"description": "Airly hava kalitesi entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.airly.eu/register adresine gidin.",
"title": "Airly"
}
}
},
"system_health": {
"info": {
"can_reach_server": "Airly sunucusuna eri\u015fin"
"can_reach_server": "Airly sunucusuna eri\u015fin",
"requests_per_day": "G\u00fcnl\u00fck izin verilen istek say\u0131s\u0131",
"requests_remaining": "Kalan izin verilen istekler"
}
}
}

View File

@ -8,7 +8,13 @@ from pyairnow.conv import aqi_to_concentration
from pyairnow.errors import AirNowError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_RADIUS,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -33,7 +39,7 @@ from .const import (
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"]
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -86,7 +86,8 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
@property
def native_value(self):
"""Return the state."""
self._state = self.coordinator.data[self.entity_description.key]
self._state = self.coordinator.data.get(self.entity_description.key)
return self._state
@property

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c",
"invalid_location": "\u305d\u306e\u5834\u6240\u306b\u5bfe\u3059\u308b\u7d50\u679c\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093",
"unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)"
},
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -17,6 +17,7 @@
"longitude": "Boylam",
"radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)"
},
"description": "AirNow hava kalitesi entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://docs.airnowapi.org/account/request/ adresine gidin.",
"title": "AirNow"
}
}

View File

@ -7,6 +7,7 @@ import logging
from airthings import Airthings, AirthingsError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -15,7 +16,7 @@ from .const import CONF_ID, CONF_SECRET, DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[str] = ["sensor"]
PLATFORMS: list[Platform] = [Platform.SENSOR]
SCAN_INTERVAL = timedelta(minutes=6)

View File

@ -3,7 +3,7 @@
"name": "Airthings",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airthings",
"requirements": ["airthings_cloud==0.0.1"],
"requirements": ["airthings_cloud==0.1.0"],
"codeowners": [
"@danielhiversen"
],

View File

@ -4,9 +4,10 @@ from __future__ import annotations
from airthings import AirthingsDevice
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
StateType,
)
from homeassistant.config_entries import ConfigEntry
@ -14,14 +15,6 @@ from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE,
PRESSURE_MBAR,
@ -46,32 +39,32 @@ SENSORS: dict[str, SensorEntityDescription] = {
),
"temp": SensorEntityDescription(
key="temp",
device_class=DEVICE_CLASS_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
name="Temperature",
),
"humidity": SensorEntityDescription(
key="humidity",
device_class=DEVICE_CLASS_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
name="Humidity",
),
"pressure": SensorEntityDescription(
key="pressure",
device_class=DEVICE_CLASS_PRESSURE,
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=PRESSURE_MBAR,
name="Pressure",
),
"battery": SensorEntityDescription(
key="battery",
device_class=DEVICE_CLASS_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
name="Battery",
),
"co2": SensorEntityDescription(
key="co2",
device_class=DEVICE_CLASS_CO2,
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
name="CO2",
),
@ -96,7 +89,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"rssi": SensorEntityDescription(
key="rssi",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
name="RSSI",
entity_registry_enabled_default=False,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@ -104,13 +97,13 @@ SENSORS: dict[str, SensorEntityDescription] = {
"pm1": SensorEntityDescription(
key="pm1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM1,
device_class=SensorDeviceClass.PM1,
name="PM1",
),
"pm25": SensorEntityDescription(
key="pm25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
device_class=SensorDeviceClass.PM25,
name="PM25",
),
}
@ -140,7 +133,7 @@ async def async_setup_entry(
class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
"""Representation of a Airthings Sensor device."""
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(
self,

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Akun sudah dikonfigurasi"
},
"error": {
"cannot_connect": "Gagal terhubung",
"invalid_auth": "Autentikasi tidak valid",
"unknown": "Kesalahan yang tidak diharapkan"
},
"step": {
"user": {
"data": {
"description": "Masuk di {url} untuk menemukan kredensial Anda",
"id": "ID",
"secret": "Kode Rahasia"
}
}
}
}
}

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c",
"unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc"
},
"step": {
"user": {
"data": {
"description": "{url} \u306b\u30ed\u30b0\u30a4\u30f3\u3057\u3066\u3001\u8cc7\u683c\u60c5\u5831\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044",
"id": "ID",
"secret": "\u30b7\u30fc\u30af\u30ec\u30c3\u30c8"
}
}
}
}
}

View File

@ -11,6 +11,7 @@
"step": {
"user": {
"data": {
"description": "Zaloguj si\u0119 pod {url}, aby znale\u017a\u0107 swoje dane uwierzytelniaj\u0105ce",
"id": "ID",
"secret": "Sekret"
}

View File

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama",
"unknown": "Beklenmeyen hata"
},
"step": {
"user": {
"data": {
"description": "Kimlik bilgilerinizi bulmak i\u00e7in {url} adresinden giri\u015f yap\u0131n",
"id": "ID",
"secret": "Gizli"
}
}
}
}
}

View File

@ -6,7 +6,7 @@ from airtouch4pyapi.airtouch import AirTouchStatus
from homeassistant.components.climate import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -15,7 +15,7 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["climate"]
PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -4,13 +4,15 @@
"already_configured": "Perangkat sudah dikonfigurasi"
},
"error": {
"cannot_connect": "Gagal terhubung"
"cannot_connect": "Gagal terhubung",
"no_units": "Tidak dapat menemukan Grup AirTouch 4 apa pun."
},
"step": {
"user": {
"data": {
"host": "Host"
}
},
"title": "Siapkan detail koneksi AirTouch 4 Anda."
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"no_units": "AirTouch 4 Groups\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002"
},
"step": {
"user": {
"data": {
"host": "\u30db\u30b9\u30c8"
},
"title": "AirTouch 4\u63a5\u7d9a\u306e\u8a73\u7d30\u8a2d\u5b9a\u3092\u3057\u307e\u3059\u3002"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"no_units": "Herhangi bir AirTouch 4 Grubu bulunamad\u0131."
},
"step": {
"user": {
"data": {
"host": "Ana bilgisayar"
},
"title": "AirTouch 4 ba\u011flant\u0131 ayr\u0131nt\u0131lar\u0131n\u0131z\u0131 ayarlay\u0131n."
}
}
}
}

View File

@ -16,7 +16,6 @@ from pyairvisual.errors import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ATTRIBUTION,
CONF_API_KEY,
CONF_IP_ADDRESS,
CONF_LATITUDE,
@ -24,6 +23,7 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_SHOW_ON_MAP,
CONF_STATE,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
@ -52,7 +52,7 @@ from .const import (
LOGGER,
)
PLATFORMS = ["sensor"]
PLATFORMS = [Platform.SENSOR]
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
DEFAULT_NODE_PRO_UPDATE_INTERVAL = timedelta(minutes=1)
@ -191,9 +191,6 @@ def _standardize_node_pro_config_entry(hass: HomeAssistant, entry: ConfigEntry)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AirVisual as config entry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {}
if CONF_API_KEY in entry.data:
_standardize_geography_config_entry(hass, entry)
@ -272,7 +269,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] = coordinator
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {DATA_COORDINATOR: coordinator}
# Reassess the interval between 2 server requests
if CONF_API_KEY in entry.data:
@ -356,7 +354,7 @@ class AirVisualEntity(CoordinatorEntity):
"""Initialize."""
super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self._attr_extra_state_attributes = {}
self._entry = entry
self.entity_description = description

View File

@ -91,6 +91,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, str], integration_type: str
) -> FlowResult:
"""Validate a Cloud API key."""
errors = {}
websession = aiohttp_client.async_get_clientsession(self.hass)
cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
@ -117,27 +118,20 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
try:
await coro
except InvalidKeyError:
return self.async_show_form(
step_id=error_step,
data_schema=error_schema,
errors={CONF_API_KEY: "invalid_api_key"},
)
errors[CONF_API_KEY] = "invalid_api_key"
except NotFoundError:
return self.async_show_form(
step_id=error_step,
data_schema=error_schema,
errors={CONF_CITY: "location_not_found"},
)
errors[CONF_CITY] = "location_not_found"
except AirVisualError as err:
LOGGER.error(err)
return self.async_show_form(
step_id=error_step,
data_schema=error_schema,
errors={"base": "unknown"},
)
errors["base"] = "unknown"
valid_keys.add(user_input[CONF_API_KEY])
if errors:
return self.async_show_form(
step_id=error_step, data_schema=error_schema, errors=errors
)
existing_entry = await self.async_set_unique_id(self._geo_id)
if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=user_input)

View File

@ -2,9 +2,9 @@
from __future__ import annotations
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@ -81,7 +81,7 @@ GEOGRAPHY_SENSOR_DESCRIPTIONS = (
name="Air Quality Index",
device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI",
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_POLLUTANT,
@ -98,7 +98,7 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
name="Air Quality Index",
device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI",
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_BATTERY_LEVEL,
@ -112,7 +112,7 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
name="C02",
device_class=DEVICE_CLASS_CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_HUMIDITY,
@ -125,35 +125,35 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
name="PM 0.1",
device_class=DEVICE_CLASS_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_PM_1_0,
name="PM 1.0",
device_class=DEVICE_CLASS_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_PM_2_5,
name="PM 2.5",
device_class=DEVICE_CLASS_PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_TEMPERATURE,
name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=SENSOR_KIND_VOC,
name="VOC",
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT,
),
)

View File

@ -9,8 +9,14 @@
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},
"step": {
"geography_by_coords": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447"
}
},
"geography_by_name": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447",
"city": "\u0413\u0440\u0430\u0434",
"country": "\u0421\u0442\u0440\u0430\u043d\u0430"
}

View File

@ -0,0 +1,63 @@
{
"config": {
"abort": {
"already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u307e\u305f\u306f\u3001Node/Pro ID\u306f\u65e2\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u307e\u3059\u3002",
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"general_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc",
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc",
"location_not_found": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093"
},
"step": {
"geography_by_coords": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6"
},
"description": "AirVisual cloud API\u3092\u4f7f\u7528\u3057\u3066\u3001\u7def\u5ea6/\u7d4c\u5ea6\u3092\u76e3\u8996\u3057\u307e\u3059\u3002",
"title": "Geography\u306e\u8a2d\u5b9a"
},
"geography_by_name": {
"data": {
"api_key": "API\u30ad\u30fc",
"city": "\u90fd\u5e02",
"country": "\u56fd",
"state": "\u5dde"
},
"description": "AirVisual cloud API\u3092\u4f7f\u7528\u3057\u3066\u3001\u90fd\u5e02/\u5dde/\u56fd\u3092\u76e3\u8996\u3057\u307e\u3059\u3002",
"title": "Geography\u306e\u8a2d\u5b9a"
},
"node_pro": {
"data": {
"ip_address": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
},
"description": "\u500b\u4eba\u306eAirVisual\u30e6\u30cb\u30c3\u30c8\u3092\u76e3\u8996\u3057\u307e\u3059\u3002\u30d1\u30b9\u30ef\u30fc\u30c9\u306f\u3001\u672c\u4f53\u306eUI\u304b\u3089\u53d6\u5f97\u3067\u304d\u307e\u3059\u3002",
"title": "AirVisual Node/Pro\u306e\u8a2d\u5b9a"
},
"reauth_confirm": {
"data": {
"api_key": "API\u30ad\u30fc"
},
"title": "AirVisual\u3092\u518d\u8a8d\u8a3c"
},
"user": {
"description": "\u76e3\u8996\u3057\u305f\u3044\u3001AirVisual\u306e\u30c7\u30fc\u30bf\u306e\u7a2e\u985e\u3092\u9078\u629e\u3057\u307e\u3059\u3002",
"title": "AirVisual\u306e\u8a2d\u5b9a"
}
}
},
"options": {
"step": {
"init": {
"data": {
"show_on_map": "\u76e3\u8996\u5bfe\u8c61\u306e\u5730\u7406\u3092\u5730\u56f3\u306b\u8868\u793a"
},
"title": "AirVisual\u306e\u8a2d\u5b9a"
}
}
}
}

View File

@ -1,9 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "\u0412\u044a\u0433\u043b\u0435\u0440\u043e\u0434\u0435\u043d \u043e\u043a\u0438\u0441",
"n2": "\u0410\u0437\u043e\u0442\u0435\u043d \u0434\u0438\u043e\u043a\u0441\u0438\u0434",
"o3": "\u041e\u0437\u043e\u043d",
"p1": "PM10",
"p2": "PM2.5"
"p2": "PM2.5",
"s2": "\u0421\u0435\u0440\u0435\u043d \u0434\u0438\u043e\u043a\u0441\u0438\u0434"
},
"airvisual__pollutant_level": {
"good": "\u0414\u043e\u0431\u0440\u043e",
"hazardous": "\u041e\u043f\u0430\u0441\u043d\u043e",
"moderate": "\u0423\u043c\u0435\u0440\u0435\u043d\u043e",
"unhealthy": "\u041d\u0435\u0437\u0434\u0440\u0430\u0432\u043e\u0441\u043b\u043e\u0432\u043d\u043e",
"unhealthy_sensitive": "\u041d\u0435\u0437\u0434\u0440\u0430\u0432\u043e\u0441\u043b\u043e\u0432\u043d\u043e \u0437\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u043d\u0438 \u0433\u0440\u0443\u043f\u0438",
"very_unhealthy": "\u041c\u043d\u043e\u0433\u043e \u043d\u0435\u0437\u0434\u0440\u0430\u0432\u043e\u0441\u043b\u043e\u0432\u043d\u043e"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Karbon monoksida",
"n2": "Nitrogen dioksida",
"o3": "Ozon",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Sulfur Dioksida"
},
"airvisual__pollutant_level": {
"good": "Bagus",
"hazardous": "Berbahaya",
"moderate": "Sedang",
"unhealthy": "Tidak sehat",
"unhealthy_sensitive": "Tidak sehat untuk kelompok sensitif",
"very_unhealthy": "Sangat tidak sehat"
}
}
}

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