Merge pull request #55532 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-09-01 11:28:21 -07:00 committed by GitHub
commit 245eec7041
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2189 changed files with 56776 additions and 23064 deletions

View File

@ -36,6 +36,9 @@ omit =
homeassistant/components/agent_dvr/helpers.py homeassistant/components/agent_dvr/helpers.py
homeassistant/components/airnow/__init__.py homeassistant/components/airnow/__init__.py
homeassistant/components/airnow/sensor.py homeassistant/components/airnow/sensor.py
homeassistant/components/airtouch4/__init__.py
homeassistant/components/airtouch4/climate.py
homeassistant/components/airtouch4/const.py
homeassistant/components/airvisual/__init__.py homeassistant/components/airvisual/__init__.py
homeassistant/components/airvisual/sensor.py homeassistant/components/airvisual/sensor.py
homeassistant/components/aladdin_connect/* homeassistant/components/aladdin_connect/*
@ -314,6 +317,12 @@ omit =
homeassistant/components/firmata/switch.py homeassistant/components/firmata/switch.py
homeassistant/components/fitbit/* homeassistant/components/fitbit/*
homeassistant/components/fixer/sensor.py homeassistant/components/fixer/sensor.py
homeassistant/components/fjaraskupan/__init__.py
homeassistant/components/fjaraskupan/binary_sensor.py
homeassistant/components/fjaraskupan/const.py
homeassistant/components/fjaraskupan/fan.py
homeassistant/components/fjaraskupan/light.py
homeassistant/components/fjaraskupan/sensor.py
homeassistant/components/fleetgo/device_tracker.py homeassistant/components/fleetgo/device_tracker.py
homeassistant/components/flexit/climate.py homeassistant/components/flexit/climate.py
homeassistant/components/flic/binary_sensor.py homeassistant/components/flic/binary_sensor.py
@ -375,6 +384,7 @@ omit =
homeassistant/components/google/* homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py homeassistant/components/google_maps/device_tracker.py
homeassistant/components/google_pubsub/__init__.py
homeassistant/components/google_travel_time/__init__.py homeassistant/components/google_travel_time/__init__.py
homeassistant/components/google_travel_time/helpers.py homeassistant/components/google_travel_time/helpers.py
homeassistant/components/google_travel_time/sensor.py homeassistant/components/google_travel_time/sensor.py
@ -636,6 +646,7 @@ omit =
homeassistant/components/modbus/cover.py homeassistant/components/modbus/cover.py
homeassistant/components/modbus/climate.py homeassistant/components/modbus/climate.py
homeassistant/components/modbus/modbus.py homeassistant/components/modbus/modbus.py
homeassistant/components/modbus/sensor.py
homeassistant/components/modbus/validators.py homeassistant/components/modbus/validators.py
homeassistant/components/modem_callerid/sensor.py homeassistant/components/modem_callerid/sensor.py
homeassistant/components/motion_blinds/__init__.py homeassistant/components/motion_blinds/__init__.py
@ -666,18 +677,21 @@ omit =
homeassistant/components/mysensors/helpers.py homeassistant/components/mysensors/helpers.py
homeassistant/components/mysensors/light.py homeassistant/components/mysensors/light.py
homeassistant/components/mysensors/notify.py homeassistant/components/mysensors/notify.py
homeassistant/components/mysensors/sensor.py
homeassistant/components/mysensors/switch.py homeassistant/components/mysensors/switch.py
homeassistant/components/mystrom/binary_sensor.py homeassistant/components/mystrom/binary_sensor.py
homeassistant/components/mystrom/light.py homeassistant/components/mystrom/light.py
homeassistant/components/mystrom/switch.py homeassistant/components/mystrom/switch.py
homeassistant/components/myq/__init__.py homeassistant/components/myq/__init__.py
homeassistant/components/myq/cover.py homeassistant/components/myq/cover.py
homeassistant/components/myq/light.py
homeassistant/components/nad/media_player.py homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/light.py homeassistant/components/nanoleaf/light.py
homeassistant/components/nanoleaf/util.py
homeassistant/components/neato/__init__.py homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py homeassistant/components/neato/api.py
homeassistant/components/neato/camera.py homeassistant/components/neato/camera.py
homeassistant/components/neato/hub.py
homeassistant/components/neato/sensor.py homeassistant/components/neato/sensor.py
homeassistant/components/neato/switch.py homeassistant/components/neato/switch.py
homeassistant/components/neato/vacuum.py homeassistant/components/neato/vacuum.py
@ -696,7 +710,8 @@ omit =
homeassistant/components/niko_home_control/light.py homeassistant/components/niko_home_control/light.py
homeassistant/components/nilu/air_quality.py homeassistant/components/nilu/air_quality.py
homeassistant/components/nissan_leaf/* homeassistant/components/nissan_leaf/*
homeassistant/components/nmap_tracker/* homeassistant/components/nmap_tracker/__init__.py
homeassistant/components/nmap_tracker/device_tracker.py
homeassistant/components/nmbs/sensor.py homeassistant/components/nmbs/sensor.py
homeassistant/components/notion/__init__.py homeassistant/components/notion/__init__.py
homeassistant/components/notion/binary_sensor.py homeassistant/components/notion/binary_sensor.py
@ -830,9 +845,9 @@ omit =
homeassistant/components/raincloud/* homeassistant/components/raincloud/*
homeassistant/components/rainmachine/__init__.py homeassistant/components/rainmachine/__init__.py
homeassistant/components/rainmachine/binary_sensor.py homeassistant/components/rainmachine/binary_sensor.py
homeassistant/components/rainmachine/model.py
homeassistant/components/rainmachine/sensor.py homeassistant/components/rainmachine/sensor.py
homeassistant/components/rainmachine/switch.py homeassistant/components/rainmachine/switch.py
homeassistant/components/rainforest_eagle/sensor.py
homeassistant/components/raspihats/* homeassistant/components/raspihats/*
homeassistant/components/raspyrfm/* homeassistant/components/raspyrfm/*
homeassistant/components/recollect_waste/__init__.py homeassistant/components/recollect_waste/__init__.py
@ -850,12 +865,8 @@ omit =
homeassistant/components/ring/camera.py homeassistant/components/ring/camera.py
homeassistant/components/ripple/sensor.py homeassistant/components/ripple/sensor.py
homeassistant/components/rituals_perfume_genie/binary_sensor.py homeassistant/components/rituals_perfume_genie/binary_sensor.py
homeassistant/components/rituals_perfume_genie/entity.py
homeassistant/components/rituals_perfume_genie/number.py homeassistant/components/rituals_perfume_genie/number.py
homeassistant/components/rituals_perfume_genie/select.py homeassistant/components/rituals_perfume_genie/select.py
homeassistant/components/rituals_perfume_genie/sensor.py
homeassistant/components/rituals_perfume_genie/switch.py
homeassistant/components/rituals_perfume_genie/__init__.py
homeassistant/components/rocketchat/notify.py homeassistant/components/rocketchat/notify.py
homeassistant/components/roomba/__init__.py homeassistant/components/roomba/__init__.py
homeassistant/components/roomba/binary_sensor.py homeassistant/components/roomba/binary_sensor.py
@ -893,7 +904,9 @@ omit =
homeassistant/components/screenlogic/switch.py homeassistant/components/screenlogic/switch.py
homeassistant/components/scsgate/* homeassistant/components/scsgate/*
homeassistant/components/sendgrid/notify.py homeassistant/components/sendgrid/notify.py
homeassistant/components/sense/* homeassistant/components/sense/__init__.py
homeassistant/components/sense/binary_sensor.py
homeassistant/components/sense/sensor.py
homeassistant/components/sensehat/light.py homeassistant/components/sensehat/light.py
homeassistant/components/sensehat/sensor.py homeassistant/components/sensehat/sensor.py
homeassistant/components/sensibo/climate.py homeassistant/components/sensibo/climate.py
@ -988,6 +1001,7 @@ omit =
homeassistant/components/suez_water/* homeassistant/components/suez_water/*
homeassistant/components/supervisord/sensor.py homeassistant/components/supervisord/sensor.py
homeassistant/components/surepetcare/__init__.py homeassistant/components/surepetcare/__init__.py
homeassistant/components/surepetcare/binary_sensor.py
homeassistant/components/surepetcare/sensor.py homeassistant/components/surepetcare/sensor.py
homeassistant/components/swiss_hydrological_data/sensor.py homeassistant/components/swiss_hydrological_data/sensor.py
homeassistant/components/swiss_public_transport/sensor.py homeassistant/components/swiss_public_transport/sensor.py
@ -1008,8 +1022,9 @@ omit =
homeassistant/components/synology_srm/device_tracker.py homeassistant/components/synology_srm/device_tracker.py
homeassistant/components/syslog/notify.py homeassistant/components/syslog/notify.py
homeassistant/components/system_bridge/__init__.py homeassistant/components/system_bridge/__init__.py
homeassistant/components/system_bridge/const.py
homeassistant/components/system_bridge/binary_sensor.py homeassistant/components/system_bridge/binary_sensor.py
homeassistant/components/system_bridge/const.py
homeassistant/components/system_bridge/coordinator.py
homeassistant/components/system_bridge/sensor.py homeassistant/components/system_bridge/sensor.py
homeassistant/components/systemmonitor/sensor.py homeassistant/components/systemmonitor/sensor.py
homeassistant/components/tado/* homeassistant/components/tado/*
@ -1079,6 +1094,10 @@ omit =
homeassistant/components/traccar/device_tracker.py homeassistant/components/traccar/device_tracker.py
homeassistant/components/traccar/const.py homeassistant/components/traccar/const.py
homeassistant/components/trackr/device_tracker.py homeassistant/components/trackr/device_tracker.py
homeassistant/components/tractive/__init__.py
homeassistant/components/tractive/device_tracker.py
homeassistant/components/tractive/entity.py
homeassistant/components/tractive/sensor.py
homeassistant/components/tradfri/* homeassistant/components/tradfri/*
homeassistant/components/trafikverket_train/sensor.py homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/trafikverket_weatherstation/sensor.py
@ -1112,7 +1131,6 @@ omit =
homeassistant/components/upcloud/switch.py homeassistant/components/upcloud/switch.py
homeassistant/components/upnp/* homeassistant/components/upnp/*
homeassistant/components/upc_connect/* homeassistant/components/upc_connect/*
homeassistant/components/uptimerobot/binary_sensor.py
homeassistant/components/uscis/sensor.py homeassistant/components/uscis/sensor.py
homeassistant/components/vallox/* homeassistant/components/vallox/*
homeassistant/components/vasttrafik/sensor.py homeassistant/components/vasttrafik/sensor.py
@ -1196,6 +1214,7 @@ omit =
homeassistant/components/xiaomi_miio/__init__.py homeassistant/components/xiaomi_miio/__init__.py
homeassistant/components/xiaomi_miio/air_quality.py homeassistant/components/xiaomi_miio/air_quality.py
homeassistant/components/xiaomi_miio/alarm_control_panel.py homeassistant/components/xiaomi_miio/alarm_control_panel.py
homeassistant/components/xiaomi_miio/binary_sensor.py
homeassistant/components/xiaomi_miio/device.py homeassistant/components/xiaomi_miio/device.py
homeassistant/components/xiaomi_miio/device_tracker.py homeassistant/components/xiaomi_miio/device_tracker.py
homeassistant/components/xiaomi_miio/fan.py homeassistant/components/xiaomi_miio/fan.py

View File

@ -24,7 +24,12 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnType": true, "editor.formatOnType": true,
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/usr/bin/zsh", "terminal.integrated.profiles.linux": {
"zsh": {
"path": "/usr/bin/zsh"
}
},
"terminal.integrated.defaultProfile.linux": "zsh",
"yaml.customTags": [ "yaml.customTags": [
"!input scalar", "!input scalar",
"!secret scalar", "!secret scalar",

View File

@ -71,6 +71,7 @@ If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running: `python3 -m script.hassfest`. Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`. - [ ] New or updated dependencies have been added to `requirements_all.txt`.
Updated by running `python3 -m script.gen_requirements_all`. Updated by running `python3 -m script.gen_requirements_all`.
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
- [ ] Untested files have been added to `.coveragerc`. - [ ] Untested files have been added to `.coveragerc`.
The integration reached or maintains the following [Integration Quality Scale][quality-scale]: The integration reached or maintains the following [Integration Quality Scale][quality-scale]:

View File

@ -47,6 +47,19 @@ jobs:
with: with:
ignore-dev: true ignore-dev: true
- name: Generate meta info
shell: bash
run: |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > OFFICIAL_IMAGE
- name: Signing meta info file
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
build_python: build_python:
name: Build PyPi package name: Build PyPi package
needs: init needs: init
@ -101,6 +114,11 @@ jobs:
python3 script/version_bump.py nightly python3 script/version_bump.py nightly
version="$(python setup.py -V)" version="$(python setup.py -V)"
- name: Write meta info file
shell: bash
run: |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.10.0
with: with:
@ -230,11 +248,12 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Install VCN tools
uses: home-assistant/actions/helpers/vcn@master
- name: Build Meta Image - name: Build Meta Image
shell: bash shell: bash
run: | run: |
bash <(curl https://getvcn.codenotary.com -L)
export DOCKER_CLI_EXPERIMENTAL=enabled export DOCKER_CLI_EXPERIMENTAL=enabled
function create_manifest() { function create_manifest() {

View File

@ -740,4 +740,4 @@ jobs:
coverage report --fail-under=94 coverage report --fail-under=94
coverage xml coverage xml
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v2.0.2 uses: codecov/codecov-action@v2.0.3

View File

@ -9,7 +9,7 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v2.1.1 - uses: dessant/lock-threads@v2.1.2
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-lock-inactive-days: "30" issue-lock-inactive-days: "30"

View File

@ -66,6 +66,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag: tag:
- "3.9-alpine3.13" - "3.9-alpine3.13"
- "3.9-alpine3.14"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
@ -106,6 +107,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag: tag:
- "3.9-alpine3.13" - "3.9-alpine3.13"
- "3.9-alpine3.14"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4

2
.gitignore vendored
View File

@ -2,7 +2,7 @@ config/*
config2/* config2/*
tests/testing_config/deps tests/testing_config/deps
tests/testing_config/home-assistant.log tests/testing_config/home-assistant.log*
# hass-release # hass-release
data/ data/

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.23.0 rev: v2.23.3
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py38-plus]
@ -45,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml - --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.8.0 rev: 5.9.3
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks

View File

@ -15,6 +15,7 @@ homeassistant.components.alarm_control_panel.*
homeassistant.components.amazon_polly.* homeassistant.components.amazon_polly.*
homeassistant.components.ambee.* homeassistant.components.ambee.*
homeassistant.components.ambient_station.* homeassistant.components.ambient_station.*
homeassistant.components.amcrest.*
homeassistant.components.ampio.* homeassistant.components.ampio.*
homeassistant.components.automation.* homeassistant.components.automation.*
homeassistant.components.binary_sensor.* homeassistant.components.binary_sensor.*
@ -63,6 +64,7 @@ homeassistant.components.mailbox.*
homeassistant.components.media_player.* homeassistant.components.media_player.*
homeassistant.components.mysensors.* homeassistant.components.mysensors.*
homeassistant.components.nam.* homeassistant.components.nam.*
homeassistant.components.neato.*
homeassistant.components.nest.* homeassistant.components.nest.*
homeassistant.components.netatmo.* homeassistant.components.netatmo.*
homeassistant.components.network.* homeassistant.components.network.*
@ -103,6 +105,7 @@ homeassistant.components.tile.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.upcloud.* homeassistant.components.upcloud.*
homeassistant.components.uptime.* homeassistant.components.uptime.*
homeassistant.components.uptimerobot.*
homeassistant.components.vacuum.* homeassistant.components.vacuum.*
homeassistant.components.water_heater.* homeassistant.components.water_heater.*
homeassistant.components.weather.* homeassistant.components.weather.*

22
.vscode/tasks.json vendored
View File

@ -60,6 +60,21 @@
}, },
"problemMatcher": [] "problemMatcher": []
}, },
{
"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",
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{ {
"label": "Generate Requirements", "label": "Generate Requirements",
"type": "shell", "type": "shell",
@ -102,5 +117,12 @@
}, },
"problemMatcher": [] "problemMatcher": []
} }
],
"inputs": [
{
"id": "integrationName",
"type": "promptString",
"description": "For which integration should the task run?"
}
] ]
} }

View File

@ -29,6 +29,7 @@ homeassistant/components/aemet/* @noltari
homeassistant/components/agent_dvr/* @ispysoftware homeassistant/components/agent_dvr/* @ispysoftware
homeassistant/components/airly/* @bieniu homeassistant/components/airly/* @bieniu
homeassistant/components/airnow/* @asymworks homeassistant/components/airnow/* @asymworks
homeassistant/components/airtouch4/* @LonePurpleWolf
homeassistant/components/airvisual/* @bachya homeassistant/components/airvisual/* @bachya
homeassistant/components/alarmdecoder/* @ajschmidt8 homeassistant/components/alarmdecoder/* @ajschmidt8
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
@ -37,6 +38,7 @@ homeassistant/components/alpha_vantage/* @fabaff
homeassistant/components/ambee/* @frenck homeassistant/components/ambee/* @frenck
homeassistant/components/ambiclimate/* @danielhiversen homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya homeassistant/components/ambient_station/* @bachya
homeassistant/components/amcrest/* @flacjacket
homeassistant/components/analytics/* @home-assistant/core @ludeeus homeassistant/components/analytics/* @home-assistant/core @ludeeus
homeassistant/components/androidtv/* @JeffLIrion homeassistant/components/androidtv/* @JeffLIrion
homeassistant/components/apache_kafka/* @bachya homeassistant/components/apache_kafka/* @bachya
@ -117,7 +119,6 @@ homeassistant/components/dexcom/* @gagebenne
homeassistant/components/dhcp/* @bdraco homeassistant/components/dhcp/* @bdraco
homeassistant/components/dht/* @thegardenmonkey homeassistant/components/dht/* @thegardenmonkey
homeassistant/components/digital_ocean/* @fabaff homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/directv/* @ctalkington
homeassistant/components/discogs/* @thibmaek homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7 @bdraco homeassistant/components/doorbird/* @oblogic7 @bdraco
homeassistant/components/dsmr/* @Robbie1221 @frenck homeassistant/components/dsmr/* @Robbie1221 @frenck
@ -161,6 +162,7 @@ homeassistant/components/filter/* @dgomes
homeassistant/components/fireservicerota/* @cyberjunky homeassistant/components/fireservicerota/* @cyberjunky
homeassistant/components/firmata/* @DaAwesomeP homeassistant/components/firmata/* @DaAwesomeP
homeassistant/components/fixer/* @fabaff homeassistant/components/fixer/* @fabaff
homeassistant/components/fjaraskupan/* @elupus
homeassistant/components/flick_electric/* @ZephireNZ homeassistant/components/flick_electric/* @ZephireNZ
homeassistant/components/flipr/* @cnico homeassistant/components/flipr/* @cnico
homeassistant/components/flo/* @dmulcahey homeassistant/components/flo/* @dmulcahey
@ -186,6 +188,7 @@ homeassistant/components/geo_rss_events/* @exxamalte
homeassistant/components/geonetnz_quakes/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/geonetnz_volcano/* @exxamalte homeassistant/components/geonetnz_volcano/* @exxamalte
homeassistant/components/gios/* @bieniu homeassistant/components/gios/* @bieniu
homeassistant/components/github/* @timmo001 @ludeeus
homeassistant/components/gitter/* @fabaff homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/goalzero/* @tkdrob homeassistant/components/goalzero/* @tkdrob
@ -196,7 +199,7 @@ homeassistant/components/gpsd/* @fabaff
homeassistant/components/gree/* @cmroche homeassistant/components/gree/* @cmroche
homeassistant/components/greeneye_monitor/* @jkeljo homeassistant/components/greeneye_monitor/* @jkeljo
homeassistant/components/group/* @home-assistant/core homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning @muppet3000 homeassistant/components/growatt_server/* @indykoning @muppet3000 @JasperPlant
homeassistant/components/guardian/* @bachya homeassistant/components/guardian/* @bachya
homeassistant/components/habitica/* @ASMfreaK @leikoilja homeassistant/components/habitica/* @ASMfreaK @leikoilja
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
@ -245,6 +248,7 @@ homeassistant/components/integration/* @dgomes
homeassistant/components/intent/* @home-assistant/core homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo homeassistant/components/intesishome/* @jnimmo
homeassistant/components/ios/* @robbiet480 homeassistant/components/ios/* @robbiet480
homeassistant/components/iotawatt/* @gtdiehl
homeassistant/components/iperf3/* @rohankapoorcom homeassistant/components/iperf3/* @rohankapoorcom
homeassistant/components/ipma/* @dgomes @abmantis homeassistant/components/ipma/* @dgomes @abmantis
homeassistant/components/ipp/* @ctalkington homeassistant/components/ipp/* @ctalkington
@ -319,10 +323,11 @@ homeassistant/components/msteams/* @peroyvind
homeassistant/components/mullvad/* @meichthys homeassistant/components/mullvad/* @meichthys
homeassistant/components/mutesync/* @currentoor homeassistant/components/mutesync/* @currentoor
homeassistant/components/my/* @home-assistant/core homeassistant/components/my/* @home-assistant/core
homeassistant/components/myq/* @bdraco homeassistant/components/myq/* @bdraco @ehendrix23
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
homeassistant/components/mystrom/* @fabaff homeassistant/components/mystrom/* @fabaff
homeassistant/components/nam/* @bieniu homeassistant/components/nam/* @bieniu
homeassistant/components/nanoleaf/* @milanmeu
homeassistant/components/neato/* @dshokouhi @Santobert homeassistant/components/neato/* @dshokouhi @Santobert
homeassistant/components/nederlandse_spoorwegen/* @YarmoM homeassistant/components/nederlandse_spoorwegen/* @YarmoM
homeassistant/components/nello/* @pschmitt homeassistant/components/nello/* @pschmitt
@ -337,6 +342,7 @@ homeassistant/components/nfandroidtv/* @tkdrob
homeassistant/components/nightscout/* @marciogranzotto homeassistant/components/nightscout/* @marciogranzotto
homeassistant/components/nilu/* @hfurubotten homeassistant/components/nilu/* @hfurubotten
homeassistant/components/nissan_leaf/* @filcole homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmap_tracker/* @bdraco
homeassistant/components/nmbs/* @thibmaek homeassistant/components/nmbs/* @thibmaek
homeassistant/components/no_ip/* @fabaff homeassistant/components/no_ip/* @fabaff
homeassistant/components/noaa_tides/* @jdelaney72 homeassistant/components/noaa_tides/* @jdelaney72
@ -370,6 +376,7 @@ homeassistant/components/orangepi_gpio/* @pascallj
homeassistant/components/oru/* @bvlaicu homeassistant/components/oru/* @bvlaicu
homeassistant/components/ovo_energy/* @timmo001 homeassistant/components/ovo_energy/* @timmo001
homeassistant/components/ozw/* @cgarwood @marcelveldt @MartinHjelmare homeassistant/components/ozw/* @cgarwood @marcelveldt @MartinHjelmare
homeassistant/components/p1_monitor/* @klaasnicolaas
homeassistant/components/panel_custom/* @home-assistant/frontend homeassistant/components/panel_custom/* @home-assistant/frontend
homeassistant/components/panel_iframe/* @home-assistant/frontend homeassistant/components/panel_iframe/* @home-assistant/frontend
homeassistant/components/pcal9535a/* @Shulyaka homeassistant/components/pcal9535a/* @Shulyaka
@ -502,7 +509,7 @@ homeassistant/components/synology_dsm/* @hacf-fr @Quentame @mib1185
homeassistant/components/synology_srm/* @aerialls homeassistant/components/synology_srm/* @aerialls
homeassistant/components/syslog/* @fabaff homeassistant/components/syslog/* @fabaff
homeassistant/components/system_bridge/* @timmo001 homeassistant/components/system_bridge/* @timmo001
homeassistant/components/tado/* @michaelarnauts @bdraco @noltari homeassistant/components/tado/* @michaelarnauts @noltari
homeassistant/components/tag/* @balloob @dmulcahey homeassistant/components/tag/* @balloob @dmulcahey
homeassistant/components/tahoma/* @philklei homeassistant/components/tahoma/* @philklei
homeassistant/components/tankerkoenig/* @guillempages homeassistant/components/tankerkoenig/* @guillempages
@ -525,6 +532,8 @@ homeassistant/components/totalconnect/* @austinmroczek
homeassistant/components/tplink/* @rytilahti @thegardenmonkey homeassistant/components/tplink/* @rytilahti @thegardenmonkey
homeassistant/components/traccar/* @ludeeus homeassistant/components/traccar/* @ludeeus
homeassistant/components/trace/* @home-assistant/core 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_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins homeassistant/components/transmission/* @engrbm87 @JPHutchins
@ -539,8 +548,9 @@ homeassistant/components/upb/* @gwww
homeassistant/components/upc_connect/* @pvizeli @fabaff homeassistant/components/upc_connect/* @pvizeli @fabaff
homeassistant/components/upcloud/* @scop homeassistant/components/upcloud/* @scop
homeassistant/components/updater/* @home-assistant/core homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @StevenLooman homeassistant/components/upnp/* @StevenLooman @ehendrix23
homeassistant/components/uptimerobot/* @ludeeus homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usb/* @bdraco
homeassistant/components/usgs_earthquakes_feed/* @exxamalte homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @Cereal2nd @brefra homeassistant/components/velbus/* @Cereal2nd @brefra
@ -576,13 +586,13 @@ homeassistant/components/worldclock/* @fabaff
homeassistant/components/xbox/* @hunterjm homeassistant/components/xbox/* @hunterjm
homeassistant/components/xbox_live/* @MartinHjelmare homeassistant/components/xbox_live/* @MartinHjelmare
homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi
homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG @bieniu
homeassistant/components/xiaomi_tv/* @simse homeassistant/components/xiaomi_tv/* @simse
homeassistant/components/xmpp/* @fabaff @flowolf homeassistant/components/xmpp/* @fabaff @flowolf
homeassistant/components/yale_smart_alarm/* @gjohansson-ST homeassistant/components/yale_smart_alarm/* @gjohansson-ST
homeassistant/components/yamaha_musiccast/* @vigonotion @micha91 homeassistant/components/yamaha_musiccast/* @vigonotion @micha91
homeassistant/components/yandex_transport/* @rishatik92 @devbis homeassistant/components/yandex_transport/* @rishatik92 @devbis
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn @starkillerOG
homeassistant/components/yeelightsunflower/* @lindsaymarkward homeassistant/components/yeelightsunflower/* @lindsaymarkward
homeassistant/components/yi/* @bachya homeassistant/components/yi/* @bachya
homeassistant/components/youless/* @gjong homeassistant/components/youless/* @gjong

View File

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

View File

@ -17,6 +17,8 @@ from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
from .permissions import PermissionLookup, system_policies from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType from .permissions.types import PolicyType
# mypy: disallow-any-generics
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth" STORAGE_KEY = "auth"
GROUP_NAME_ADMIN = "Administrators" GROUP_NAME_ADMIN = "Administrators"
@ -491,7 +493,7 @@ class AuthStore:
self._store.async_delay_save(self._data_to_save, 1) self._store.async_delay_save(self._data_to_save, 1)
@callback @callback
def _data_to_save(self) -> dict: def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
"""Return the data to store.""" """Return the data to store."""
assert self._users is not None assert self._users is not None
assert self._groups is not None assert self._groups is not None

View File

@ -22,6 +22,8 @@ from ..auth_store import AuthStore
from ..const import MFA_SESSION_EXPIRATION from ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, RefreshToken, User, UserMeta from ..models import Credentials, RefreshToken, User, UserMeta
# mypy: disallow-any-generics
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_REQS = "auth_prov_reqs_processed" DATA_REQS = "auth_prov_reqs_processed"
@ -96,7 +98,7 @@ class AuthProvider:
# Implement by extending class # Implement by extending class
async def async_login_flow(self, context: dict | None) -> LoginFlow: async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return the data flow for logging in with auth provider. """Return the data flow for logging in with auth provider.
Auth provider should extend LoginFlow and return an instance. Auth provider should extend LoginFlow and return an instance.

View File

@ -17,6 +17,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
CONF_ARGS = "args" CONF_ARGS = "args"
CONF_META = "meta" CONF_META = "meta"
@ -56,7 +58,7 @@ class CommandLineAuthProvider(AuthProvider):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._user_meta: dict[str, dict[str, Any]] = {} self._user_meta: dict[str, dict[str, Any]] = {}
async def async_login_flow(self, context: dict | None) -> LoginFlow: async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login.""" """Return a flow to login."""
return CommandLineLoginFlow(self) return CommandLineLoginFlow(self)

View File

@ -19,6 +19,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant" STORAGE_KEY = "auth_provider.homeassistant"
@ -235,7 +237,7 @@ class HassAuthProvider(AuthProvider):
await data.async_load() await data.async_load()
self.data = data self.data = data
async def async_login_flow(self, context: dict | None) -> LoginFlow: async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login.""" """Return a flow to login."""
return HassLoginFlow(self) return HassLoginFlow(self)

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Mapping from collections.abc import Mapping
import hmac import hmac
from typing import cast from typing import Any, cast
import voluptuous as vol import voluptuous as vol
@ -15,6 +15,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
USER_SCHEMA = vol.Schema( USER_SCHEMA = vol.Schema(
{ {
vol.Required("username"): str, vol.Required("username"): str,
@ -37,7 +39,7 @@ class InvalidAuthError(HomeAssistantError):
class ExampleAuthProvider(AuthProvider): class ExampleAuthProvider(AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords.""" """Example auth provider based on hardcoded usernames and passwords."""
async def async_login_flow(self, context: dict | None) -> LoginFlow: async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login.""" """Return a flow to login."""
return ExampleLoginFlow(self) return ExampleLoginFlow(self)

View File

@ -7,7 +7,7 @@ from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
import hmac import hmac
from typing import cast from typing import Any, cast
import voluptuous as vol import voluptuous as vol
@ -19,6 +19,8 @@ import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
AUTH_PROVIDER_TYPE = "legacy_api_password" AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password" CONF_API_PASSWORD = "api_password"
@ -44,7 +46,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
"""Return api_password.""" """Return api_password."""
return str(self.config[CONF_API_PASSWORD]) return str(self.config[CONF_API_PASSWORD])
async def async_login_flow(self, context: dict | None) -> LoginFlow: async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login.""" """Return a flow to login."""
return LegacyLoginFlow(self) return LegacyLoginFlow(self)

View File

@ -27,6 +27,8 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from .. import InvalidAuthError from .. import InvalidAuthError
from ..models import Credentials, RefreshToken, UserMeta from ..models import Credentials, RefreshToken, UserMeta
# mypy: disallow-any-generics
IPAddress = Union[IPv4Address, IPv6Address] IPAddress = Union[IPv4Address, IPv6Address]
IPNetwork = Union[IPv4Network, IPv6Network] IPNetwork = Union[IPv4Network, IPv6Network]
@ -97,7 +99,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
"""Trusted Networks auth provider does not support MFA.""" """Trusted Networks auth provider does not support MFA."""
return False return False
async def async_login_flow(self, context: dict | None) -> LoginFlow: async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login.""" """Return a flow to login."""
assert context is not None assert context is not None
ip_addr = cast(IPAddress, context.get("ip_address")) ip_addr = cast(IPAddress, context.get("ip_address"))

View File

@ -332,15 +332,17 @@ def async_enable_logging(
not err_path_exists and os.access(err_dir, os.W_OK) not err_path_exists and os.access(err_dir, os.W_OK)
): ):
err_handler: logging.handlers.RotatingFileHandler | logging.handlers.TimedRotatingFileHandler
if log_rotate_days: if log_rotate_days:
err_handler: logging.FileHandler = ( err_handler = logging.handlers.TimedRotatingFileHandler(
logging.handlers.TimedRotatingFileHandler( err_log_path, when="midnight", backupCount=log_rotate_days
err_log_path, when="midnight", backupCount=log_rotate_days
)
) )
else: else:
err_handler = logging.FileHandler(err_log_path, mode="w", delay=True) err_handler = logging.handlers.RotatingFileHandler(
err_log_path, backupCount=1
)
err_handler.doRollover()
err_handler.setLevel(logging.INFO if verbose else logging.WARNING) err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt)) err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))

View File

@ -1,4 +1,6 @@
"""Support for Abode Security System cameras.""" """Support for Abode Security System cameras."""
from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
@ -73,7 +75,9 @@ class AbodeCamera(AbodeDevice, Camera):
else: else:
self._response = None self._response = None
def camera_image(self): def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Get a camera image.""" """Get a camera image."""
self.refresh_image() self.refresh_image()

View File

@ -1,7 +1,9 @@
"""Support for Abode Security System sensors.""" """Support for Abode Security System sensors."""
from __future__ import annotations
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.const import ( from homeassistant.const import (
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
@ -11,12 +13,23 @@ from homeassistant.const import (
from . import AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN
# Sensor types: Name, icon SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SENSOR_TYPES = { SensorEntityDescription(
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE], key=CONST.TEMP_STATUS_KEY,
CONST.HUMI_STATUS_KEY: ["Humidity", DEVICE_CLASS_HUMIDITY], name="Temperature",
CONST.LUX_STATUS_KEY: ["Lux", DEVICE_CLASS_ILLUMINANCE], device_class=DEVICE_CLASS_TEMPERATURE,
} ),
SensorEntityDescription(
key=CONST.HUMI_STATUS_KEY,
name="Humidity",
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=CONST.LUX_STATUS_KEY,
name="Lux",
device_class=DEVICE_CLASS_ILLUMINANCE,
),
)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
@ -26,10 +39,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = [] entities = []
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR): for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
for sensor_type in SENSOR_TYPES: conditions = device.get_value(CONST.STATUSES_KEY)
if sensor_type not in device.get_value(CONST.STATUSES_KEY): entities.extend(
continue [
entities.append(AbodeSensor(data, device, sensor_type)) AbodeSensor(data, device, description)
for description in SENSOR_TYPES
if description.key in conditions
]
)
async_add_entities(entities) async_add_entities(entities)
@ -37,26 +54,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeSensor(AbodeDevice, SensorEntity): class AbodeSensor(AbodeDevice, SensorEntity):
"""A sensor implementation for Abode devices.""" """A sensor implementation for Abode devices."""
def __init__(self, data, device, sensor_type): def __init__(self, data, device, description: SensorEntityDescription):
"""Initialize a sensor for an Abode device.""" """Initialize a sensor for an Abode device."""
super().__init__(data, device) super().__init__(data, device)
self._sensor_type = sensor_type self.entity_description = description
self._attr_name = f"{device.name} {SENSOR_TYPES[sensor_type][0]}" self._attr_name = f"{device.name} {description.name}"
self._attr_device_class = SENSOR_TYPES[self._sensor_type][1] self._attr_unique_id = f"{device.device_uuid}-{description.key}"
self._attr_unique_id = f"{device.device_uuid}-{sensor_type}" if description.key == CONST.TEMP_STATUS_KEY:
if self._sensor_type == CONST.TEMP_STATUS_KEY: self._attr_native_unit_of_measurement = device.temp_unit
self._attr_unit_of_measurement = device.temp_unit elif description.key == CONST.HUMI_STATUS_KEY:
elif self._sensor_type == CONST.HUMI_STATUS_KEY: self._attr_native_unit_of_measurement = device.humidity_unit
self._attr_unit_of_measurement = device.humidity_unit elif description.key == CONST.LUX_STATUS_KEY:
elif self._sensor_type == CONST.LUX_STATUS_KEY: self._attr_native_unit_of_measurement = device.lux_unit
self._attr_unit_of_measurement = device.lux_unit
@property @property
def state(self): def native_value(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self._sensor_type == CONST.TEMP_STATUS_KEY: if self.entity_description.key == CONST.TEMP_STATUS_KEY:
return self._device.temp return self._device.temp
if self._sensor_type == CONST.HUMI_STATUS_KEY: if self.entity_description.key == CONST.HUMI_STATUS_KEY:
return self._device.humidity return self._device.humidity
if self._sensor_type == CONST.LUX_STATUS_KEY: if self.entity_description.key == CONST.LUX_STATUS_KEY:
return self._device.lux return self._device.lux

View File

@ -88,10 +88,10 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
) )
if coordinator.is_metric: if coordinator.is_metric:
self._unit_system = API_METRIC self._unit_system = API_METRIC
self._attr_unit_of_measurement = description.unit_metric self._attr_native_unit_of_measurement = description.unit_metric
else: else:
self._unit_system = API_IMPERIAL self._unit_system = API_IMPERIAL
self._attr_unit_of_measurement = description.unit_imperial self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = { self._attr_device_info = {
"identifiers": {(DOMAIN, coordinator.location_key)}, "identifiers": {(DOMAIN, coordinator.location_key)},
"name": NAME, "name": NAME,
@ -101,7 +101,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self.forecast_day = forecast_day self.forecast_day = forecast_day
@property @property
def state(self) -> StateType: def native_value(self) -> StateType:
"""Return the state.""" """Return the state."""
if self.forecast_day is not None: if self.forecast_day is not None:
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE: if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE:

View File

@ -5,7 +5,8 @@
}, },
"error": { "error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" "invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
"requests_exceeded": "\u05d0\u05d9\u05e8\u05e2\u05d4 \u05d7\u05e8\u05d9\u05d2\u05d4 \u05de\u05de\u05e1\u05e4\u05e8 \u05d4\u05d1\u05e7\u05e9\u05d5\u05ea \u05d4\u05de\u05d5\u05ea\u05e8 \u05dc-API \u05e9\u05dc Accuweather. \u05e2\u05dc\u05d9\u05da \u05dc\u05d4\u05de\u05ea\u05d9\u05df \u05d0\u05d5 \u05dc\u05e9\u05e0\u05d5\u05ea \u05d0\u05ea \u05de\u05e4\u05ea\u05d7 \u05d4-API."
}, },
"step": { "step": {
"user": { "user": {
@ -15,6 +16,7 @@
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
"name": "\u05e9\u05dd" "name": "\u05e9\u05dd"
}, },
"description": "\u05d0\u05dd \u05d4\u05d9\u05e0\u05da \u05d6\u05e7\u05d5\u05e7 \u05dc\u05e2\u05d6\u05e8\u05d4 \u05e2\u05dd \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4, \u05d9\u05e9 \u05dc\u05e2\u05d9\u05d9\u05df \u05db\u05d0\u05df: https://www.home-assistant.io/integrations/accuweather/\n\n\u05d7\u05d9\u05d9\u05e9\u05e0\u05d9\u05dd \u05de\u05e1\u05d5\u05d9\u05de\u05d9\u05dd \u05d0\u05d9\u05e0\u05dd \u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea\u05da \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05ea\u05dd \u05dc\u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05d1\u05e8\u05d9\u05e9\u05d5\u05dd \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05dc\u05d0\u05d7\u05e8 \u05e7\u05d1\u05d9\u05e2\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.\n\u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8 \u05d0\u05d9\u05e0\u05d4 \u05d6\u05de\u05d9\u05e0\u05d4 \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea\u05da \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05ea\u05d5 \u05dc\u05d6\u05de\u05d9\u05df \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.",
"title": "AccuWeather" "title": "AccuWeather"
} }
} }
@ -22,6 +24,9 @@
"options": { "options": {
"step": { "step": {
"user": { "user": {
"data": {
"forecast": "\u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8"
},
"description": "\u05d1\u05e9\u05dc \u05de\u05d2\u05d1\u05dc\u05d5\u05ea \u05d4\u05d2\u05d9\u05e8\u05e1\u05d4 \u05d4\u05d7\u05d9\u05e0\u05de\u05d9\u05ea \u05e9\u05dc \u05de\u05e4\u05ea\u05d7 \u05d4-API \u05e9\u05dc AccuWeather, \u05db\u05d0\u05e9\u05e8 \u05ea\u05e4\u05e2\u05d9\u05dc \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d0\u05d5\u05d5\u05d9\u05e8, \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d9\u05d1\u05d5\u05e6\u05e2\u05d5 \u05db\u05dc 80 \u05d3\u05e7\u05d5\u05ea \u05d1\u05de\u05e7\u05d5\u05dd \u05db\u05dc 40 \u05d3\u05e7\u05d5\u05ea.", "description": "\u05d1\u05e9\u05dc \u05de\u05d2\u05d1\u05dc\u05d5\u05ea \u05d4\u05d2\u05d9\u05e8\u05e1\u05d4 \u05d4\u05d7\u05d9\u05e0\u05de\u05d9\u05ea \u05e9\u05dc \u05de\u05e4\u05ea\u05d7 \u05d4-API \u05e9\u05dc AccuWeather, \u05db\u05d0\u05e9\u05e8 \u05ea\u05e4\u05e2\u05d9\u05dc \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d0\u05d5\u05d5\u05d9\u05e8, \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d9\u05d1\u05d5\u05e6\u05e2\u05d5 \u05db\u05dc 80 \u05d3\u05e7\u05d5\u05ea \u05d1\u05de\u05e7\u05d5\u05dd \u05db\u05dc 40 \u05d3\u05e7\u05d5\u05ea.",
"title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea AccuWeather" "title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea AccuWeather"
} }
@ -29,7 +34,8 @@
}, },
"system_health": { "system_health": {
"info": { "info": {
"can_reach_server": "\u05d4\u05e9\u05d2\u05ea \u05e9\u05e8\u05ea AccuWeather" "can_reach_server": "\u05d4\u05e9\u05d2\u05ea \u05e9\u05e8\u05ea AccuWeather",
"remaining_requests": "\u05d4\u05d1\u05e7\u05e9\u05d5\u05ea \u05d4\u05e0\u05d5\u05ea\u05e8\u05d5\u05ea \u05de\u05d5\u05ea\u05e8\u05d5\u05ea"
} }
} }
} }

View File

@ -5,7 +5,8 @@
}, },
"error": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s", "cannot_connect": "Sikertelen csatlakoz\u00e1s",
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs",
"requests_exceeded": "T\u00fall\u00e9pt\u00e9k az Accuweather API-hoz beny\u00fajtott k\u00e9relmek megengedett sz\u00e1m\u00e1t. Meg kell v\u00e1rnia vagy m\u00f3dos\u00edtania kell az API-kulcsot."
}, },
"step": { "step": {
"user": { "user": {
@ -15,6 +16,7 @@
"longitude": "Hossz\u00fas\u00e1g", "longitude": "Hossz\u00fas\u00e1g",
"name": "N\u00e9v" "name": "N\u00e9v"
}, },
"description": "Ha seg\u00edts\u00e9gre van sz\u00fcks\u00e9ge a konfigur\u00e1l\u00e1shoz, n\u00e9zze meg itt: https://www.home-assistant.io/integrations/accuweather/ \n\nEgyes \u00e9rz\u00e9kel\u0151k alap\u00e9rtelmez\u00e9s szerint nincsenek enged\u00e9lyezve. Az integr\u00e1ci\u00f3s konfigur\u00e1ci\u00f3 ut\u00e1n enged\u00e9lyezheti \u0151ket az entit\u00e1s-nyilv\u00e1ntart\u00e1sban.\nAz id\u0151j\u00e1r\u00e1s-el\u0151rejelz\u00e9s alap\u00e9rtelmez\u00e9s szerint nincs enged\u00e9lyezve. Ezt az integr\u00e1ci\u00f3s be\u00e1ll\u00edt\u00e1sokban enged\u00e9lyezheti.",
"title": "AccuWeather" "title": "AccuWeather"
} }
} }
@ -22,6 +24,10 @@
"options": { "options": {
"step": { "step": {
"user": { "user": {
"data": {
"forecast": "Id\u0151j\u00e1r\u00e1s el\u0151rejelz\u00e9s"
},
"description": "Az AccuWeather API kulcs ingyenes verzi\u00f3j\u00e1nak korl\u00e1tai miatt, amikor enged\u00e9lyezi az id\u0151j\u00e1r\u00e1s -el\u0151rejelz\u00e9st, az adatfriss\u00edt\u00e9seket 40 percenk\u00e9nt 80 percenk\u00e9nt hajtj\u00e1k v\u00e9gre.",
"title": "AccuWeather be\u00e1ll\u00edt\u00e1sok" "title": "AccuWeather be\u00e1ll\u00edt\u00e1sok"
} }
} }

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "\u05d9\u05d5\u05e8\u05d3",
"rising": "\u05e2\u05d5\u05dc\u05d4",
"steady": "\u05d9\u05e6\u05d9\u05d1"
}
}
}

View File

@ -61,7 +61,7 @@ class AcmedaCover(AcmedaBase, CoverEntity):
None is unknown, 0 is closed, 100 is fully open. None is unknown, 0 is closed, 100 is fully open.
""" """
position = None position = None
if self.roller.type in [7, 10]: if self.roller.type in (7, 10):
position = 100 - self.roller.closed_percent position = 100 - self.roller.closed_percent
return position return position

View File

@ -34,7 +34,7 @@ class AcmedaBattery(AcmedaBase, SensorEntity):
"""Representation of a Acmeda cover device.""" """Representation of a Acmeda cover device."""
device_class = DEVICE_CLASS_BATTERY device_class = DEVICE_CLASS_BATTERY
unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
@property @property
def name(self): def name(self):
@ -42,6 +42,6 @@ class AcmedaBattery(AcmedaBase, SensorEntity):
return f"{super().name} Battery" return f"{super().name} Battery"
@property @property
def state(self): def native_value(self):
"""Return the state of the device.""" """Return the state of the device."""
return self.roller.battery return self.roller.battery

View File

@ -2,6 +2,14 @@
"config": { "config": {
"abort": { "abort": {
"no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton"
},
"step": {
"user": {
"data": {
"id": "Gazdag\u00e9p azonos\u00edt\u00f3"
},
"title": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hubot"
}
} }
} }
} }

View File

@ -4,7 +4,9 @@ from __future__ import annotations
import re import re
from typing import Final from typing import Final
LEASES_REGEX: Final[re.Pattern] = re.compile( # mypy: disallow-any-generics
LEASES_REGEX: Final[re.Pattern[str]] = re.compile(
r"(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})" r"(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})"
+ r"\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))" + r"\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))"
+ r"\svalid\sfor:\s(?P<timevalid>(-?\d+))" + r"\svalid\sfor:\s(?P<timevalid>(-?\d+))"

View File

@ -49,20 +49,19 @@ async def async_setup_entry(
class AdaxDevice(ClimateEntity): class AdaxDevice(ClimateEntity):
"""Representation of a heater.""" """Representation of a heater."""
_attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
_attr_max_temp = 35
_attr_min_temp = 5
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
_attr_target_temperature_step = PRECISION_WHOLE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None: def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
"""Initialize the heater.""" """Initialize the heater."""
self._heater_data = heater_data self._heater_data = heater_data
self._adax_data_handler = adax_data_handler self._adax_data_handler = adax_data_handler
@property self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"{self._heater_data['homeId']}_{self._heater_data['id']}"
@property @property
def name(self) -> str: def name(self) -> str:
@ -83,11 +82,6 @@ class AdaxDevice(ClimateEntity):
return "mdi:radiator" return "mdi:radiator"
return "mdi:radiator-off" return "mdi:radiator-off"
@property
def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes."""
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode.""" """Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT: if hvac_mode == HVAC_MODE_HEAT:
@ -105,21 +99,6 @@ class AdaxDevice(ClimateEntity):
return return
await self._adax_data_handler.update() await self._adax_data_handler.update()
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement which this device uses."""
return TEMP_CELSIUS
@property
def min_temp(self) -> int:
"""Return the minimum temperature."""
return 5
@property
def max_temp(self) -> int:
"""Return the maximum temperature."""
return 35
@property @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
@ -130,11 +109,6 @@ class AdaxDevice(ClimateEntity):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self._heater_data.get("targetTemperature") return self._heater_data.get("targetTemperature")
@property
def target_temperature_step(self) -> int:
"""Return the supported step of target temperature."""
return PRECISION_WHOLE
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE) temperature = kwargs.get(ATTR_TEMPERATURE)

View File

@ -10,6 +10,7 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"account_id": "ID \u00fa\u010dtu",
"host": "Hostitel", "host": "Hostitel",
"password": "Heslo" "password": "Heslo"
} }

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"account_id": "ID de la cuenta"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van"
},
"error": {
"cannot_connect": "Nem siker\u00fclt csatlakozni",
"invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s"
},
"step": {
"user": {
"data": {
"account_id": "Fi\u00f3k ID",
"host": "Gazdag\u00e9p",
"password": "Jelsz\u00f3"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert"
},
"error": {
"cannot_connect": "Tilkobling mislyktes",
"invalid_auth": "Ugyldig godkjenning"
},
"step": {
"user": {
"data": {
"account_id": "Konto-ID",
"host": "Vert",
"password": "Passord"
}
}
}
}
}

View File

@ -0,0 +1,14 @@
{
"config": {
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": {
"user": {
"data": {
"password": "\u5bc6\u7801"
}
}
}
}
}

View File

@ -198,7 +198,7 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
"""Return device information about this AdGuard Home instance.""" """Return device information about this AdGuard Home instance."""
return { return {
"identifiers": { "identifiers": {
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) (DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore
}, },
"name": "AdGuard Home", "name": "AdGuard Home",
"manufacturer": "AdGuard Team", "manufacturer": "AdGuard Team",

View File

@ -51,6 +51,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
self, errors: dict[str, str] | None = None self, errors: dict[str, str] | None = None
) -> FlowResult: ) -> FlowResult:
"""Show the Hass.io confirmation form to the user.""" """Show the Hass.io confirmation form to the user."""
assert self._hassio_discovery
return self.async_show_form( return self.async_show_form(
step_id="hassio_confirm", step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]}, description_placeholders={"addon": self._hassio_discovery["addon"]},
@ -73,11 +74,13 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
session = async_get_clientsession(self.hass, user_input[CONF_VERIFY_SSL]) session = async_get_clientsession(self.hass, user_input[CONF_VERIFY_SSL])
username: str | None = user_input.get(CONF_USERNAME)
password: str | None = user_input.get(CONF_PASSWORD)
adguard = AdGuardHome( adguard = AdGuardHome(
user_input[CONF_HOST], user_input[CONF_HOST],
port=user_input[CONF_PORT], port=user_input[CONF_PORT],
username=user_input.get(CONF_USERNAME), username=username, # type:ignore[arg-type]
password=user_input.get(CONF_PASSWORD), password=password, # type:ignore[arg-type]
tls=user_input[CONF_SSL], tls=user_input[CONF_SSL],
verify_ssl=user_input[CONF_VERIFY_SSL], verify_ssl=user_input[CONF_VERIFY_SSL],
session=session, session=session,
@ -122,6 +125,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
session = async_get_clientsession(self.hass, False) session = async_get_clientsession(self.hass, False)
assert self._hassio_discovery
adguard = AdGuardHome( adguard = AdGuardHome(
self._hassio_discovery[CONF_HOST], self._hassio_discovery[CONF_HOST],
port=self._hassio_discovery[CONF_PORT], port=self._hassio_discovery[CONF_PORT],

View File

@ -62,7 +62,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
enabled_default: bool = True, enabled_default: bool = True,
) -> None: ) -> None:
"""Initialize AdGuard Home sensor.""" """Initialize AdGuard Home sensor."""
self._state = None self._state: int | str | None = None
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self.measurement = measurement self.measurement = measurement
@ -82,12 +82,12 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
) )
@property @property
def state(self) -> str | None: def native_value(self) -> int | str | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state
@property @property
def unit_of_measurement(self) -> str | None: def native_unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in.""" """Return the unit this state is expressed in."""
return self._unit_of_measurement return self._unit_of_measurement

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van",
"existing_instance_updated": "Friss\u00edtette a megl\u00e9v\u0151 konfigur\u00e1ci\u00f3t."
}, },
"error": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s" "cannot_connect": "Sikertelen csatlakoz\u00e1s"
@ -19,7 +20,8 @@
"ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata",
"username": "Felhaszn\u00e1l\u00f3n\u00e9v", "username": "Felhaszn\u00e1l\u00f3n\u00e9v",
"verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se"
} },
"description": "\u00c1ll\u00edtsa be az AdGuard Home p\u00e9ld\u00e1nyt, hogy lehet\u0151v\u00e9 tegye a fel\u00fcgyeletet \u00e9s az ir\u00e1ny\u00edt\u00e1st."
} }
} }
} }

View File

@ -1,14 +1,23 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e",
"existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002" "existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002"
}, },
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "\u4e3b\u673a\u5730\u5740",
"password": "\u5bc6\u7801", "password": "\u5bc6\u7801",
"username": "\u7528\u6237\u540d" "port": "\u7aef\u53e3",
} "ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66\u51ed\u8bc1",
"username": "\u7528\u6237\u540d",
"verify_ssl": "\u9a8c\u8bc1 SSL \u8bc1\u4e66\u51ed\u8bc1"
},
"description": "\u8bbe\u7f6e\u60a8\u7684 AdGuard Home \u5b9e\u4f8b\u4ee5\u5141\u8bb8\u76d1\u89c6\u548c\u63a7\u5236"
} }
} }
} }

View File

@ -50,7 +50,7 @@ class AdsSensor(AdsEntity, SensorEntity):
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor): def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor):
"""Initialize AdsSensor entity.""" """Initialize AdsSensor entity."""
super().__init__(ads_hub, name, ads_var) super().__init__(ads_hub, name, ads_var)
self._attr_unit_of_measurement = unit_of_measurement self._attr_native_unit_of_measurement = unit_of_measurement
self._ads_type = ads_type self._ads_type = ads_type
self._factor = factor self._factor = factor
@ -64,6 +64,6 @@ class AdsSensor(AdsEntity, SensorEntity):
) )
@property @property
def state(self) -> StateType: def native_value(self) -> StateType:
"""Return the state of the device.""" """Return the state of the device."""
return self._state_dict[STATE_KEY_STATE] return self._state_dict[STATE_KEY_STATE]

View File

@ -1,7 +1,11 @@
"""Sensor platform for Advantage Air integration.""" """Sensor platform for Advantage Air integration."""
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity from homeassistant.components.sensor import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
@ -45,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity): class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air timer control.""" """Representation of Advantage Air timer control."""
_attr_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT _attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
def __init__(self, instance, ac_key, action): def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control.""" """Initialize the Advantage Air timer control."""
@ -58,7 +62,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
) )
@property @property
def state(self): def native_value(self):
"""Return the current value.""" """Return the current value."""
return self._ac[self._time_key] return self._ac[self._time_key]
@ -78,7 +82,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor.""" """Representation of Advantage Air Zone Vent Sensor."""
_attr_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
@ -90,7 +94,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
) )
@property @property
def state(self): def native_value(self):
"""Return the current value of the air vent.""" """Return the current value of the air vent."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return self._zone["value"] return self._zone["value"]
@ -107,19 +111,19 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """Representation of Advantage Air Zone wireless signal sensor."""
_attr_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor.""" """Initialize an Advantage Air Zone wireless signal sensor."""
super().__init__(instance, ac_key, zone_key=zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Signal' self._attr_name = f'{self._zone["name"]} Signal'
self._attr_unique_id = ( self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal' f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
) )
@property @property
def state(self): def native_value(self):
"""Return the current value of the wireless signal.""" """Return the current value of the wireless signal."""
return self._zone["rssi"] return self._zone["rssi"]
@ -138,20 +142,22 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """Representation of Advantage Air Zone temperature sensor."""
_attr_unit_of_measurement = TEMP_CELSIUS _attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
_attr_icon = "mdi:thermometer"
_attr_entity_registry_enabled_default = False _attr_entity_registry_enabled_default = False
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Temp Sensor.""" """Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Temperature' self._attr_name = f'{self._zone["name"]} Temperature'
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-temp' self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-temp'
)
@property @property
def state(self): def native_value(self):
"""Return the current value of the measured temperature.""" """Return the current value of the measured temperature."""
return self._zone["measuredTemp"] return self._zone["measuredTemp"]

View File

@ -2,6 +2,7 @@
"config": { "config": {
"step": { "step": {
"user": { "user": {
"description": "Con\u00e9ctese a la API de su tableta de pared Advantage Air.",
"title": "Conectar" "title": "Conectar"
} }
} }

View File

@ -85,7 +85,7 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
self._attr_name = f"{self._name} {self._sensor_name}" self._attr_name = f"{self._name} {self._sensor_name}"
self._attr_unique_id = self._unique_id self._attr_unique_id = self._unique_id
self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS) self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
self._attr_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT) self._attr_native_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
class AemetSensor(AbstractAemetSensor): class AemetSensor(AbstractAemetSensor):
@ -106,7 +106,7 @@ class AemetSensor(AbstractAemetSensor):
self._weather_coordinator = weather_coordinator self._weather_coordinator = weather_coordinator
@property @property
def state(self): def native_value(self):
"""Return the state of the device.""" """Return the state of the device."""
return self._weather_coordinator.data.get(self._sensor_type) return self._weather_coordinator.data.get(self._sensor_type)
@ -134,7 +134,7 @@ class AemetForecastSensor(AbstractAemetSensor):
) )
@property @property
def state(self): def native_value(self):
"""Return the state of the device.""" """Return the state of the device."""
forecast = None forecast = None
forecasts = self._weather_coordinator.data.get( forecasts = self._weather_coordinator.data.get(

View File

@ -5,8 +5,18 @@
"data": { "data": {
"name": "Nombre de la integraci\u00f3n" "name": "Nombre de la integraci\u00f3n"
}, },
"description": "Configure la integraci\u00f3n de AEMET OpenData. Para generar la clave API vaya a https://opendata.aemet.es/centrodedescargas/altaUsuario",
"title": "AEMET OpenData" "title": "AEMET OpenData"
} }
} }
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "Recopile datos de las estaciones meteorol\u00f3gicas de AEMET"
}
}
}
} }
} }

View File

@ -1,4 +1,6 @@
"""Weather data coordinator for the AEMET OpenData service.""" """Weather data coordinator for the AEMET OpenData service."""
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -95,7 +97,7 @@ def format_condition(condition: str) -> str:
return condition return condition
def format_float(value) -> float: def format_float(value) -> float | None:
"""Try converting string to float.""" """Try converting string to float."""
try: try:
return float(value) return float(value)
@ -103,7 +105,7 @@ def format_float(value) -> float:
return None return None
def format_int(value) -> int: def format_int(value) -> int | None:
"""Try converting string to int.""" """Try converting string to int."""
try: try:
return int(value) return int(value)

View File

@ -109,7 +109,7 @@ async def async_setup_platform(
class AfterShipSensor(SensorEntity): class AfterShipSensor(SensorEntity):
"""Representation of a AfterShip sensor.""" """Representation of a AfterShip sensor."""
_attr_unit_of_measurement: str = "packages" _attr_native_unit_of_measurement: str = "packages"
_attr_icon: str = ICON _attr_icon: str = ICON
def __init__(self, aftership: Tracking, name: str) -> None: def __init__(self, aftership: Tracking, name: str) -> None:
@ -120,7 +120,7 @@ class AfterShipSensor(SensorEntity):
self._attr_name = name self._attr_name = name
@property @property
def state(self) -> int | None: def native_value(self) -> int | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state

View File

@ -12,7 +12,8 @@
"data": { "data": {
"host": "Hoszt", "host": "Hoszt",
"port": "Port" "port": "Port"
} },
"title": "\u00c1ll\u00edtsa be az Agent DVR-t"
} }
} }
} }

View File

@ -1,7 +1,20 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e"
},
"error": { "error": {
"already_in_progress": "\u914d\u7f6e\u6d41\u5df2\u8fdb\u884c\u4e2d",
"cannot_connect": "\u8fde\u63a5\u5931\u8d25" "cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u673a\u5730\u5740",
"port": "\u7aef\u53e3"
},
"title": "\u914d\u7f6e Agent DVR"
}
} }
} }
} }

View File

@ -6,7 +6,11 @@ from typing import Final
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
PERCENTAGE, PERCENTAGE,
@ -49,35 +53,36 @@ NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = ( SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_CAQI, key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI, name=ATTR_API_CAQI,
unit_of_measurement="CAQI", native_unit_of_measurement="CAQI",
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM1, key=ATTR_API_PM1,
icon="mdi:blur", device_class=DEVICE_CLASS_PM1,
name=ATTR_API_PM1, name=ATTR_API_PM1,
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM25, key=ATTR_API_PM25,
icon="mdi:blur", device_class=DEVICE_CLASS_PM25,
name="PM2.5", name="PM2.5",
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM10, key=ATTR_API_PM10,
icon="mdi:blur", device_class=DEVICE_CLASS_PM10,
name=ATTR_API_PM10, name=ATTR_API_PM10,
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY, key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(), name=ATTR_API_HUMIDITY.capitalize(),
unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1), value=lambda value: round(value, 1),
), ),
@ -85,14 +90,14 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
key=ATTR_API_PRESSURE, key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(), name=ATTR_API_PRESSURE.capitalize(),
unit_of_measurement=PRESSURE_HPA, native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE, key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(), name=ATTR_API_TEMPERATURE.capitalize(),
unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1), value=lambda value: round(value, 1),
), ),

View File

@ -84,7 +84,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
self.entity_description = description self.entity_description = description
@property @property
def state(self) -> StateType: def native_value(self) -> StateType:
"""Return the state.""" """Return the state."""
state = self.coordinator.data[self.entity_description.key] state = self.coordinator.data[self.entity_description.key]
return cast(StateType, self.entity_description.value(state)) return cast(StateType, self.entity_description.value(state))

View File

@ -72,11 +72,11 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}" self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON] self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS] self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
self._attr_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT] self._attr_native_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}" self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
@property @property
def state(self): def native_value(self):
"""Return the state.""" """Return the state."""
self._state = self.coordinator.data[self.kind] self._state = self.coordinator.data[self.kind]
return self._state return self._state

View File

@ -0,0 +1,81 @@
"""The AirTouch4 integration."""
import logging
from airtouch4pyapi import AirTouch
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.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["climate"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AirTouch4 from a config entry."""
hass.data.setdefault(DOMAIN, {})
host = entry.data[CONF_HOST]
airtouch = AirTouch(host)
await airtouch.UpdateInfo()
info = airtouch.GetAcs()
if not info:
raise ConfigEntryNotReady
coordinator = AirtouchDataUpdateCoordinator(hass, airtouch)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
class AirtouchDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Airtouch data."""
def __init__(self, hass, airtouch):
"""Initialize global Airtouch data updater."""
self.airtouch = airtouch
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
async def _async_update_data(self):
"""Fetch data from Airtouch."""
await self.airtouch.UpdateInfo()
if self.airtouch.Status != AirTouchStatus.OK:
raise UpdateFailed("Airtouch connection issue")
return {
"acs": [
{"ac_number": ac.AcNumber, "is_on": ac.IsOn}
for ac in self.airtouch.GetAcs()
],
"groups": [
{
"group_number": group.GroupNumber,
"group_name": group.GroupName,
"is_on": group.IsOn,
}
for group in self.airtouch.GetGroups()
],
}

View File

@ -0,0 +1,335 @@
"""AirTouch 4 component to control of AirTouch 4 Climate Devices."""
import logging
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
FAN_AUTO,
FAN_DIFFUSE,
FAN_FOCUS,
FAN_HIGH,
FAN_LOW,
FAN_MEDIUM,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
AT_TO_HA_STATE = {
"Heat": HVAC_MODE_HEAT,
"Cool": HVAC_MODE_COOL,
"AutoHeat": HVAC_MODE_AUTO, # airtouch reports either autoheat or autocool
"AutoCool": HVAC_MODE_AUTO,
"Auto": HVAC_MODE_AUTO,
"Dry": HVAC_MODE_DRY,
"Fan": HVAC_MODE_FAN_ONLY,
}
HA_STATE_TO_AT = {
HVAC_MODE_HEAT: "Heat",
HVAC_MODE_COOL: "Cool",
HVAC_MODE_AUTO: "Auto",
HVAC_MODE_DRY: "Dry",
HVAC_MODE_FAN_ONLY: "Fan",
HVAC_MODE_OFF: "Off",
}
AT_TO_HA_FAN_SPEED = {
"Quiet": FAN_DIFFUSE,
"Low": FAN_LOW,
"Medium": FAN_MEDIUM,
"High": FAN_HIGH,
"Powerful": FAN_FOCUS,
"Auto": FAN_AUTO,
"Turbo": "turbo",
}
AT_GROUP_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY]
HA_FAN_SPEED_TO_AT = {value: key for key, value in AT_TO_HA_FAN_SPEED.items()}
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Airtouch 4."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
info = coordinator.data
entities = [
AirtouchGroup(coordinator, group["group_number"], info)
for group in info["groups"]
] + [AirtouchAC(coordinator, ac["ac_number"], info) for ac in info["acs"]]
_LOGGER.debug(" Found entities %s", entities)
async_add_entities(entities)
class AirtouchAC(CoordinatorEntity, ClimateEntity):
"""Representation of an AirTouch 4 ac."""
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, coordinator, ac_number, info):
"""Initialize the climate device."""
super().__init__(coordinator)
self._ac_number = ac_number
self._airtouch = coordinator.airtouch
self._info = info
self._unit = self._airtouch.GetAcs()[self._ac_number]
@callback
def _handle_coordinator_update(self):
self._unit = self._airtouch.GetAcs()[self._ac_number]
return super()._handle_coordinator_update()
@property
def device_info(self):
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Airtouch",
"model": "Airtouch 4",
}
@property
def unique_id(self):
"""Return unique ID for this device."""
return f"ac_{self._ac_number}"
@property
def current_temperature(self):
"""Return the current temperature."""
return self._unit.Temperature
@property
def name(self):
"""Return the name of the climate device."""
return f"AC {self._ac_number}"
@property
def fan_mode(self):
"""Return fan mode of the AC this group belongs to."""
return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._ac_number].AcFanSpeed]
@property
def fan_modes(self):
"""Return the list of available fan modes."""
airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsForAc(self._ac_number)
return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds]
@property
def hvac_mode(self):
"""Return hvac target hvac state."""
is_off = self._unit.PowerState == "Off"
if is_off:
return HVAC_MODE_OFF
return AT_TO_HA_STATE[self._airtouch.acs[self._ac_number].AcMode]
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
airtouch_modes = self._airtouch.GetSupportedCoolingModesForAc(self._ac_number)
modes = [AT_TO_HA_STATE[mode] for mode in airtouch_modes]
modes.append(HVAC_MODE_OFF)
return modes
async def async_set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
if hvac_mode not in HA_STATE_TO_AT:
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
if hvac_mode == HVAC_MODE_OFF:
return await self.async_turn_off()
await self._airtouch.SetCoolingModeForAc(
self._ac_number, HA_STATE_TO_AT[hvac_mode]
)
# in case it isn't already, unless the HVAC mode was off, then the ac should be on
await self.async_turn_on()
self._unit = self._airtouch.GetAcs()[self._ac_number]
_LOGGER.debug("Setting operation mode of %s to %s", self._ac_number, hvac_mode)
self.async_write_ha_state()
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
if fan_mode not in self.fan_modes:
raise ValueError(f"Unsupported fan mode: {fan_mode}")
_LOGGER.debug("Setting fan mode of %s to %s", self._ac_number, fan_mode)
await self._airtouch.SetFanSpeedForAc(
self._ac_number, HA_FAN_SPEED_TO_AT[fan_mode]
)
self._unit = self._airtouch.GetAcs()[self._ac_number]
self.async_write_ha_state()
async def async_turn_on(self):
"""Turn on."""
_LOGGER.debug("Turning %s on", self.unique_id)
# in case ac is not on. Airtouch turns itself off if no groups are turned on
# (even if groups turned back on)
await self._airtouch.TurnAcOn(self._ac_number)
async def async_turn_off(self):
"""Turn off."""
_LOGGER.debug("Turning %s off", self.unique_id)
await self._airtouch.TurnAcOff(self._ac_number)
self.async_write_ha_state()
class AirtouchGroup(CoordinatorEntity, ClimateEntity):
"""Representation of an AirTouch 4 group."""
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
_attr_temperature_unit = TEMP_CELSIUS
_attr_hvac_modes = AT_GROUP_MODES
def __init__(self, coordinator, group_number, info):
"""Initialize the climate device."""
super().__init__(coordinator)
self._group_number = group_number
self._airtouch = coordinator.airtouch
self._info = info
self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number)
@callback
def _handle_coordinator_update(self):
self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number)
return super()._handle_coordinator_update()
@property
def device_info(self):
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Airtouch",
"model": "Airtouch 4",
}
@property
def unique_id(self):
"""Return unique ID for this device."""
return self._group_number
@property
def min_temp(self):
"""Return Minimum Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MinSetpoint
@property
def max_temp(self):
"""Return Max Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint
@property
def name(self):
"""Return the name of the climate device."""
return self._unit.GroupName
@property
def current_temperature(self):
"""Return the current temperature."""
return self._unit.Temperature
@property
def target_temperature(self):
"""Return the temperature we are trying to reach."""
return self._unit.TargetSetpoint
@property
def hvac_mode(self):
"""Return hvac target hvac state."""
# there are other power states that aren't 'on' but still count as on (eg. 'Turbo')
is_off = self._unit.PowerState == "Off"
if is_off:
return HVAC_MODE_OFF
return HVAC_MODE_FAN_ONLY
async def async_set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
if hvac_mode not in HA_STATE_TO_AT:
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
if hvac_mode == HVAC_MODE_OFF:
return await self.async_turn_off()
if self.hvac_mode == HVAC_MODE_OFF:
await self.async_turn_on()
self._unit = self._airtouch.GetGroups()[self._group_number]
_LOGGER.debug(
"Setting operation mode of %s to %s", self._group_number, hvac_mode
)
self.async_write_ha_state()
@property
def fan_mode(self):
"""Return fan mode of the AC this group belongs to."""
return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._unit.BelongsToAc].AcFanSpeed]
@property
def fan_modes(self):
"""Return the list of available fan modes."""
airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsByGroup(
self._group_number
)
return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds]
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Setting temp of %s to %s", self._group_number, str(temp))
self._unit = await self._airtouch.SetGroupToTemperature(
self._group_number, int(temp)
)
self.async_write_ha_state()
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
if fan_mode not in self.fan_modes:
raise ValueError(f"Unsupported fan mode: {fan_mode}")
_LOGGER.debug("Setting fan mode of %s to %s", self._group_number, fan_mode)
self._unit = await self._airtouch.SetFanSpeedByGroup(
self._group_number, HA_FAN_SPEED_TO_AT[fan_mode]
)
self.async_write_ha_state()
async def async_turn_on(self):
"""Turn on."""
_LOGGER.debug("Turning %s on", self.unique_id)
await self._airtouch.TurnGroupOn(self._group_number)
# in case ac is not on. Airtouch turns itself off if no groups are turned on
# (even if groups turned back on)
await self._airtouch.TurnAcOn(
self._airtouch.GetGroupByGroupNumber(self._group_number).BelongsToAc
)
# this might cause the ac object to be wrong, so force the shared data
# store to update
await self.coordinator.async_request_refresh()
self.async_write_ha_state()
async def async_turn_off(self):
"""Turn off."""
_LOGGER.debug("Turning %s off", self.unique_id)
await self._airtouch.TurnGroupOff(self._group_number)
# this will cause the ac object to be wrong
# (ac turns off automatically if no groups are running)
# so force the shared data store to update
await self.coordinator.async_request_refresh()
self.async_write_ha_state()

View File

@ -0,0 +1,50 @@
"""Config flow for AirTouch4."""
from airtouch4pyapi import AirTouch, AirTouchStatus
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST
from .const import DOMAIN
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
class AirtouchConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle an Airtouch config flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is None:
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
errors = {}
host = user_input[CONF_HOST]
self._async_abort_entries_match({CONF_HOST: host})
airtouch = AirTouch(host)
await airtouch.UpdateInfo()
airtouch_status = airtouch.Status
airtouch_has_groups = bool(
airtouch.Status == AirTouchStatus.OK and airtouch.GetGroups()
)
if airtouch_status != AirTouchStatus.OK:
errors["base"] = "cannot_connect"
elif not airtouch_has_groups:
errors["base"] = "no_units"
if errors:
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
return self.async_create_entry(
title=user_input[CONF_HOST],
data={
CONF_HOST: user_input[CONF_HOST],
},
)

View File

@ -0,0 +1,3 @@
"""Constants for the AirTouch4 integration."""
DOMAIN = "airtouch4"

View File

@ -0,0 +1,13 @@
{
"domain": "airtouch4",
"name": "AirTouch 4",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airtouch4",
"requirements": [
"airtouch4pyapi==1.0.5"
],
"codeowners": [
"@LonePurpleWolf"
],
"iot_class": "local_polling"
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_units": "Could not find any AirTouch 4 Groups."
},
"step": {
"user": {
"title": "Setup your AirTouch 4 connection details.",
"data": {
"host": "[%key:common::config_flow::data::host%]"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat"
},
"error": {
"cannot_connect": "Ha fallat la connexi\u00f3",
"no_units": "No s'han trobat grups AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Amfitri\u00f3"
},
"title": "Configura els detalls de connexi\u00f3 d'AirTouch 4."
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno"
},
"error": {
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit"
},
"step": {
"user": {
"data": {
"host": "Hostitel"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"error": {
"cannot_connect": "Verbindung fehlgeschlagen",
"no_units": "Es konnten keine AirTouch 4-Gruppen gefunden werden."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Richte deine AirTouch 4-Verbindungsdetails ein."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"no_units": "Could not find any AirTouch 4 Groups."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Setup your AirTouch 4 connection details."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Seade on juba h\u00e4\u00e4lestatud"
},
"error": {
"cannot_connect": "\u00dchendamine nurjus",
"no_units": "Ei leidnud \u00fchtegi AirTouch 4 gruppi."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "AirTouch 4 \u00fchenduse \u00fcksikasjade seadistamine."
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": {
"user": {
"data": {
"host": "\u05de\u05d0\u05e8\u05d7"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van"
},
"error": {
"cannot_connect": "Sikertelen kapcsol\u00f3d\u00e1s",
"no_units": "Nem tal\u00e1lhat\u00f3 AirTouch 4 csoport."
},
"step": {
"user": {
"data": {
"host": "Gazdag\u00e9p"
},
"title": "\u00c1ll\u00edtsa be az AirTouch 4 csatlakoz\u00e1si adatait."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato"
},
"error": {
"cannot_connect": "Impossibile connettersi",
"no_units": "Impossibile trovare alcun gruppo AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Imposta i dettagli della connessione AirTouch 4."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Apparaat is al geconfigureerd"
},
"error": {
"cannot_connect": "Kan geen verbinding maken",
"no_units": "Kan geen AirTouch 4-groepen vinden."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Stel uw AirTouch 4 verbindingsgegevens in."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert"
},
"error": {
"cannot_connect": "Tilkobling mislyktes",
"no_units": "Kan ikke finne noen AirTouch 4 -grupper."
},
"step": {
"user": {
"data": {
"host": "Vert"
},
"title": "Konfigurer AirTouch 4 -tilkoblingsdetaljer."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
},
"error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"no_units": "Nie mo\u017cna znale\u017a\u0107 \u017cadnych grup AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Nazwa hosta lub adres IP"
},
"title": "Konfiguracja po\u0142\u0105czenia AirTouch 4."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
"no_units": "\u0413\u0440\u0443\u043f\u043f\u044b AirTouch 4 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b."
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442"
},
"title": "AirTouch 4"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210"
},
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"no_units": "\u627e\u4e0d\u5230\u4efb\u4f55 AirTouch 4 \u7fa4\u7d44\u3002"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u6a5f\u7aef"
},
"title": "\u8a2d\u5b9a AirTouch 4 \u9023\u7dda\u8cc7\u8a0a\u3002"
}
}
}
}

View File

@ -32,6 +32,7 @@ from homeassistant.helpers import (
config_validation as cv, config_validation as cv,
entity_registry, entity_registry,
) )
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
DataUpdateCoordinator, DataUpdateCoordinator,
@ -358,11 +359,14 @@ async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
class AirVisualEntity(CoordinatorEntity): class AirVisualEntity(CoordinatorEntity):
"""Define a generic AirVisual entity.""" """Define a generic AirVisual entity."""
def __init__(self, coordinator: DataUpdateCoordinator) -> None: def __init__(
self, coordinator: DataUpdateCoordinator, description: EntityDescription
) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self.entity_description = description
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Register callbacks.""" """Register callbacks."""

View File

@ -1,7 +1,7 @@
"""Support for AirVisual air quality sensors.""" """Support for AirVisual air quality sensors."""
from __future__ import annotations from __future__ import annotations
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_LATITUDE, ATTR_LATITUDE,
@ -14,10 +14,15 @@ from homeassistant.const import (
CONF_LONGITUDE, CONF_LONGITUDE,
CONF_SHOW_ON_MAP, CONF_SHOW_ON_MAP,
CONF_STATE, CONF_STATE,
DEVICE_CLASS_AQI,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO2, DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
PERCENTAGE, PERCENTAGE,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
@ -59,60 +64,84 @@ SENSOR_KIND_SENSOR_LIFE = "sensor_life"
SENSOR_KIND_TEMPERATURE = "temperature" SENSOR_KIND_TEMPERATURE = "temperature"
SENSOR_KIND_VOC = "voc" SENSOR_KIND_VOC = "voc"
GEOGRAPHY_SENSORS = [ GEOGRAPHY_SENSOR_DESCRIPTIONS = (
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None), SensorEntityDescription(
(SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"), key=SENSOR_KIND_LEVEL,
(SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None), name="Air Pollution Level",
] device_class=DEVICE_CLASS_POLLUTANT_LEVEL,
icon="mdi:gauge",
),
SensorEntityDescription(
key=SENSOR_KIND_AQI,
name="Air Quality Index",
device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI",
),
SensorEntityDescription(
key=SENSOR_KIND_POLLUTANT,
name="Main Pollutant",
device_class=DEVICE_CLASS_POLLUTANT_LABEL,
icon="mdi:chemical-weapon",
),
)
GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."} GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
NODE_PRO_SENSORS = [ NODE_PRO_SENSOR_DESCRIPTIONS = (
(SENSOR_KIND_AQI, "Air Quality Index", None, "mdi:chart-line", "AQI"), SensorEntityDescription(
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, None, PERCENTAGE), key=SENSOR_KIND_AQI,
( name="Air Quality Index",
SENSOR_KIND_CO2, device_class=DEVICE_CLASS_AQI,
"C02", native_unit_of_measurement="AQI",
DEVICE_CLASS_CO2,
None,
CONCENTRATION_PARTS_PER_MILLION,
), ),
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, None, PERCENTAGE), SensorEntityDescription(
( key=SENSOR_KIND_BATTERY_LEVEL,
SENSOR_KIND_PM_0_1, name="Battery",
"PM 0.1", device_class=DEVICE_CLASS_BATTERY,
None, native_unit_of_measurement=PERCENTAGE,
"mdi:sprinkler",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
), ),
( SensorEntityDescription(
SENSOR_KIND_PM_1_0, key=SENSOR_KIND_CO2,
"PM 1.0", name="C02",
None, device_class=DEVICE_CLASS_CO2,
"mdi:sprinkler", native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
), ),
( SensorEntityDescription(
SENSOR_KIND_PM_2_5, key=SENSOR_KIND_HUMIDITY,
"PM 2.5", name="Humidity",
None, device_class=DEVICE_CLASS_HUMIDITY,
"mdi:sprinkler", native_unit_of_measurement=PERCENTAGE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
), ),
( SensorEntityDescription(
SENSOR_KIND_TEMPERATURE, key=SENSOR_KIND_PM_0_1,
"Temperature", name="PM 0.1",
DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_PM1,
None, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
TEMP_CELSIUS,
), ),
( SensorEntityDescription(
SENSOR_KIND_VOC, key=SENSOR_KIND_PM_1_0,
"VOC", name="PM 1.0",
None, device_class=DEVICE_CLASS_PM10,
"mdi:sprinkler", native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
), ),
] 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,
),
SensorEntityDescription(
key=SENSOR_KIND_TEMPERATURE,
name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
),
SensorEntityDescription(
key=SENSOR_KIND_VOC,
name="VOC",
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
)
STATE_POLLUTANT_LABEL_CO = "co" STATE_POLLUTANT_LABEL_CO = "co"
STATE_POLLUTANT_LABEL_N2 = "n2" STATE_POLLUTANT_LABEL_N2 = "n2"
@ -156,27 +185,19 @@ async def async_setup_entry(
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor] sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor]
if config_entry.data[CONF_INTEGRATION_TYPE] in [ if config_entry.data[CONF_INTEGRATION_TYPE] in (
INTEGRATION_TYPE_GEOGRAPHY_COORDS, INTEGRATION_TYPE_GEOGRAPHY_COORDS,
INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_GEOGRAPHY_NAME,
]: ):
sensors = [ sensors = [
AirVisualGeographySensor( AirVisualGeographySensor(coordinator, config_entry, description, locale)
coordinator,
config_entry,
kind,
name,
icon,
unit,
locale,
)
for locale in GEOGRAPHY_SENSOR_LOCALES for locale in GEOGRAPHY_SENSOR_LOCALES
for kind, name, icon, unit in GEOGRAPHY_SENSORS for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
] ]
else: else:
sensors = [ sensors = [
AirVisualNodeProSensor(coordinator, kind, name, device_class, icon, unit) AirVisualNodeProSensor(coordinator, description)
for kind, name, device_class, icon, unit in NODE_PRO_SENSORS for description in NODE_PRO_SENSOR_DESCRIPTIONS
] ]
async_add_entities(sensors, True) async_add_entities(sensors, True)
@ -189,19 +210,12 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
config_entry: ConfigEntry, config_entry: ConfigEntry,
kind: str, description: SensorEntityDescription,
name: str,
icon: str,
unit: str | None,
locale: str, locale: str,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator, description)
if kind == SENSOR_KIND_LEVEL:
self._attr_device_class = DEVICE_CLASS_POLLUTANT_LEVEL
elif kind == SENSOR_KIND_POLLUTANT:
self._attr_device_class = DEVICE_CLASS_POLLUTANT_LABEL
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
{ {
ATTR_CITY: config_entry.data.get(CONF_CITY), ATTR_CITY: config_entry.data.get(CONF_CITY),
@ -209,12 +223,9 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY), ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
} }
) )
self._attr_icon = icon self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {description.name}"
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {name}" self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{description.key}"
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{kind}"
self._attr_unit_of_measurement = unit
self._config_entry = config_entry self._config_entry = config_entry
self._kind = kind
self._locale = locale self._locale = locale
@property @property
@ -230,18 +241,18 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
except KeyError: except KeyError:
return return
if self._kind == SENSOR_KIND_LEVEL: if self.entity_description.key == SENSOR_KIND_LEVEL:
aqi = data[f"aqi{self._locale}"] aqi = data[f"aqi{self._locale}"]
[(self._attr_state, self._attr_icon)] = [ [(self._attr_native_value, self._attr_icon)] = [
(name, icon) (name, icon)
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items() for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
if floor <= aqi <= ceiling if floor <= aqi <= ceiling
] ]
elif self._kind == SENSOR_KIND_AQI: elif self.entity_description.key == SENSOR_KIND_AQI:
self._attr_state = data[f"aqi{self._locale}"] self._attr_native_value = data[f"aqi{self._locale}"]
elif self._kind == SENSOR_KIND_POLLUTANT: elif self.entity_description.key == SENSOR_KIND_POLLUTANT:
symbol = data[f"main{self._locale}"] symbol = data[f"main{self._locale}"]
self._attr_state = symbol self._attr_native_value = symbol
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
{ {
ATTR_POLLUTANT_SYMBOL: symbol, ATTR_POLLUTANT_SYMBOL: symbol,
@ -281,25 +292,15 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Define an AirVisual sensor related to a Node/Pro unit.""" """Define an AirVisual sensor related to a Node/Pro unit."""
def __init__( def __init__(
self, self, coordinator: DataUpdateCoordinator, description: SensorEntityDescription
coordinator: DataUpdateCoordinator,
kind: str,
name: str,
device_class: str | None,
icon: str | None,
unit: str,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator, description)
self._attr_device_class = device_class
self._attr_icon = icon
self._attr_name = ( self._attr_name = (
f"{coordinator.data['settings']['node_name']} Node/Pro: {name}" f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
) )
self._attr_unique_id = f"{coordinator.data['serial_number']}_{kind}" self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
self._attr_unit_of_measurement = unit
self._kind = kind
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
@ -318,26 +319,32 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
if self._kind == SENSOR_KIND_AQI: if self.entity_description.key == SENSOR_KIND_AQI:
if self.coordinator.data["settings"]["is_aqi_usa"]: if self.coordinator.data["settings"]["is_aqi_usa"]:
self._attr_state = self.coordinator.data["measurements"]["aqi_us"] self._attr_native_value = self.coordinator.data["measurements"][
"aqi_us"
]
else: else:
self._attr_state = self.coordinator.data["measurements"]["aqi_cn"] self._attr_native_value = self.coordinator.data["measurements"][
elif self._kind == SENSOR_KIND_BATTERY_LEVEL: "aqi_cn"
self._attr_state = self.coordinator.data["status"]["battery"] ]
elif self._kind == SENSOR_KIND_CO2: elif self.entity_description.key == SENSOR_KIND_BATTERY_LEVEL:
self._attr_state = self.coordinator.data["measurements"].get("co2") self._attr_native_value = self.coordinator.data["status"]["battery"]
elif self._kind == SENSOR_KIND_HUMIDITY: elif self.entity_description.key == SENSOR_KIND_CO2:
self._attr_state = self.coordinator.data["measurements"].get("humidity") self._attr_native_value = self.coordinator.data["measurements"].get("co2")
elif self._kind == SENSOR_KIND_PM_0_1: elif self.entity_description.key == SENSOR_KIND_HUMIDITY:
self._attr_state = self.coordinator.data["measurements"].get("pm0_1") self._attr_native_value = self.coordinator.data["measurements"].get(
elif self._kind == SENSOR_KIND_PM_1_0: "humidity"
self._attr_state = self.coordinator.data["measurements"].get("pm1_0") )
elif self._kind == SENSOR_KIND_PM_2_5: elif self.entity_description.key == SENSOR_KIND_PM_0_1:
self._attr_state = self.coordinator.data["measurements"].get("pm2_5") self._attr_native_value = self.coordinator.data["measurements"].get("pm0_1")
elif self._kind == SENSOR_KIND_TEMPERATURE: elif self.entity_description.key == SENSOR_KIND_PM_1_0:
self._attr_state = self.coordinator.data["measurements"].get( self._attr_native_value = self.coordinator.data["measurements"].get("pm1_0")
elif self.entity_description.key == SENSOR_KIND_PM_2_5:
self._attr_native_value = self.coordinator.data["measurements"].get("pm2_5")
elif self.entity_description.key == SENSOR_KIND_TEMPERATURE:
self._attr_native_value = self.coordinator.data["measurements"].get(
"temperature_C" "temperature_C"
) )
elif self._kind == SENSOR_KIND_VOC: elif self.entity_description.key == SENSOR_KIND_VOC:
self._attr_state = self.coordinator.data["measurements"].get("voc") self._attr_native_value = self.coordinator.data["measurements"].get("voc")

View File

@ -13,6 +13,15 @@
"description": "Utilice la API en la nube de AirVisual para monitorear una latitud / longitud.", "description": "Utilice la API en la nube de AirVisual para monitorear una latitud / longitud.",
"title": "Configurar una geograf\u00eda" "title": "Configurar una geograf\u00eda"
}, },
"geography_by_name": {
"data": {
"city": "Ciudad",
"country": "Pa\u00eds",
"state": "estado"
},
"description": "Utilice la API en la nube de AirVisual para monitorear una ciudad/estado/pa\u00eds.",
"title": "Configurar una geograf\u00eda"
},
"node_pro": { "node_pro": {
"data": { "data": {
"ip_address": "Direcci\u00f3n IP/nombre de host de la unidad", "ip_address": "Direcci\u00f3n IP/nombre de host de la unidad",
@ -21,6 +30,9 @@
"description": "Monitoree una unidad AirVisual personal. La contrase\u00f1a se puede recuperar de la interfaz de usuario de la unidad.", "description": "Monitoree una unidad AirVisual personal. La contrase\u00f1a se puede recuperar de la interfaz de usuario de la unidad.",
"title": "Configurar un AirVisual Node/Pro" "title": "Configurar un AirVisual Node/Pro"
}, },
"reauth_confirm": {
"title": "Vuelva a autenticar AirVisual"
},
"user": { "user": {
"description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.", "description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
"title": "Configurar AirVisual" "title": "Configurar AirVisual"

View File

@ -34,13 +34,29 @@
"data": { "data": {
"ip_address": "Hoszt", "ip_address": "Hoszt",
"password": "Jelsz\u00f3" "password": "Jelsz\u00f3"
} },
"description": "Szem\u00e9lyes AirVisual egys\u00e9g figyel\u00e9se. A jelsz\u00f3 lek\u00e9rhet\u0151 a k\u00e9sz\u00fcl\u00e9k felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9r\u0151l.",
"title": "AirVisual Node/Pro konfigur\u00e1l\u00e1sa"
}, },
"reauth_confirm": { "reauth_confirm": {
"data": { "data": {
"api_key": "API kulcs" "api_key": "API kulcs"
}, },
"title": "Az AirVisual \u00fajb\u00f3li hiteles\u00edt\u00e9se" "title": "Az AirVisual \u00fajb\u00f3li hiteles\u00edt\u00e9se"
},
"user": {
"description": "V\u00e1lassza ki, hogy milyen t\u00edpus\u00fa AirVisual adatokat szeretne figyelni.",
"title": "Az AirVisual konfigur\u00e1l\u00e1sa"
}
}
},
"options": {
"step": {
"init": {
"data": {
"show_on_map": "A megfigyelt f\u00f6ldrajz megjelen\u00edt\u00e9se a t\u00e9rk\u00e9pen"
},
"title": "Az AirVisual konfigur\u00e1l\u00e1sa"
} }
} }
} }

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Oxid uhelnat\u00fd",
"n2": "Oxid dusi\u010dit\u00fd",
"o3": "Oz\u00f3n",
"p1": "PM10",
"p2": "PM2,5",
"s2": "Oxid si\u0159i\u010dit\u00fd"
},
"airvisual__pollutant_level": {
"good": "Dobr\u00e9",
"hazardous": "Riskantn\u00ed",
"moderate": "M\u00edrn\u00e9",
"unhealthy": "Nezdrav\u00e9",
"unhealthy_sensitive": "Nezdrav\u00e9 pro citliv\u00e9 skupiny",
"very_unhealthy": "Velmi nezdrav\u00e9"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Mon\u00f3xido de carbono",
"n2": "Dioxido de nitrogeno",
"o3": "Ozono",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Di\u00f3xido de azufre"
},
"airvisual__pollutant_level": {
"good": "Bueno",
"hazardous": "Peligroso",
"moderate": "Moderado",
"unhealthy": "Insalubre",
"unhealthy_sensitive": "Insalubre para grupos sensibles",
"very_unhealthy": "Muy insalubre"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Mon\u00f3xido de carbono",
"n2": "Di\u00f3xido de nitr\u00f3geno",
"o3": "Ozono",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Di\u00f3xido de azufre"
},
"airvisual__pollutant_level": {
"good": "Bien",
"hazardous": "Peligroso",
"moderate": "Moderado",
"unhealthy": "Insalubre",
"unhealthy_sensitive": "Incorrecto para grupos sensibles",
"very_unhealthy": "Muy poco saludable"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Sz\u00e9n-monoxid",
"n2": "Nitrog\u00e9n-dioxid",
"o3": "\u00d3zon",
"p1": "PM10",
"p2": "PM2.5",
"s2": "K\u00e9n-dioxid"
},
"airvisual__pollutant_level": {
"good": "J\u00f3",
"hazardous": "Vesz\u00e9lyes",
"moderate": "M\u00e9rs\u00e9kelt",
"unhealthy": "Eg\u00e9szs\u00e9gtelen",
"unhealthy_sensitive": "Eg\u00e9szs\u00e9gtelen az \u00e9rz\u00e9keny csoportok sz\u00e1m\u00e1ra",
"very_unhealthy": "Nagyon eg\u00e9szs\u00e9gtelen"
}
}
}

View File

@ -1,8 +1,20 @@
{ {
"state": { "state": {
"airvisual__pollutant_label": { "airvisual__pollutant_label": {
"co": "Karbonmonoksid",
"n2": "Nitrogendioksid",
"o3": "Ozon",
"p1": "PM10", "p1": "PM10",
"p2": "PM2.5" "p2": "PM2.5",
"s2": "Svoveldioksid"
},
"airvisual__pollutant_level": {
"good": "Bra",
"hazardous": "Farlig",
"moderate": "Moderat",
"unhealthy": "Usunt",
"unhealthy_sensitive": "Usunt for sensitive grupper",
"very_unhealthy": "Veldig usunt"
} }
} }
} }

View File

@ -1,7 +1,7 @@
"""Provides device automations for Alarm control panel.""" """Provides device automations for Alarm control panel."""
from __future__ import annotations from __future__ import annotations
from typing import Final from typing import Any, Final
import voluptuous as vol import voluptuous as vol
@ -55,7 +55,7 @@ TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
async def async_get_triggers( async def async_get_triggers(
hass: HomeAssistant, device_id: str hass: HomeAssistant, device_id: str
) -> list[dict[str, str]]: ) -> list[dict[str, Any]]:
"""List device triggers for Alarm control panel devices.""" """List device triggers for Alarm control panel devices."""
registry = await entity_registry.async_get_registry(hass) registry = await entity_registry.async_get_registry(hass)
triggers: list[dict[str, str]] = [] triggers: list[dict[str, str]] = []

View File

@ -4,6 +4,7 @@
"arm_away": "Aktivovat {entity_name} v re\u017eimu nep\u0159\u00edtomnost", "arm_away": "Aktivovat {entity_name} v re\u017eimu nep\u0159\u00edtomnost",
"arm_home": "Aktivovat {entity_name} v re\u017eimu domov", "arm_home": "Aktivovat {entity_name} v re\u017eimu domov",
"arm_night": "Aktivovat {entity_name} v no\u010dn\u00edm re\u017eimu", "arm_night": "Aktivovat {entity_name} v no\u010dn\u00edm re\u017eimu",
"arm_vacation": "Aktivovat {entity_name} v re\u017eimu dovolen\u00e1",
"disarm": "Odbezpe\u010dit {entity_name}", "disarm": "Odbezpe\u010dit {entity_name}",
"trigger": "Spustit {entity_name}" "trigger": "Spustit {entity_name}"
}, },
@ -11,6 +12,7 @@
"is_armed_away": "{entity_name} je v re\u017eimu nep\u0159\u00edtomnost", "is_armed_away": "{entity_name} je v re\u017eimu nep\u0159\u00edtomnost",
"is_armed_home": "{entity_name} je v re\u017eimu domov", "is_armed_home": "{entity_name} je v re\u017eimu domov",
"is_armed_night": "{entity_name} je v no\u010dn\u00edm re\u017eimu", "is_armed_night": "{entity_name} je v no\u010dn\u00edm re\u017eimu",
"is_armed_vacation": "{entity_name} je v re\u017eimu dovolen\u00e1",
"is_disarmed": "{entity_name} nen\u00ed zabezpe\u010den", "is_disarmed": "{entity_name} nen\u00ed zabezpe\u010den",
"is_triggered": "{entity_name} je spu\u0161t\u011bn" "is_triggered": "{entity_name} je spu\u0161t\u011bn"
}, },
@ -18,6 +20,7 @@
"armed_away": "{entity_name} v re\u017eimu nep\u0159\u00edtomnost", "armed_away": "{entity_name} v re\u017eimu nep\u0159\u00edtomnost",
"armed_home": "{entity_name} v re\u017eimu domov", "armed_home": "{entity_name} v re\u017eimu domov",
"armed_night": "{entity_name} v no\u010dn\u00edm re\u017eimu", "armed_night": "{entity_name} v no\u010dn\u00edm re\u017eimu",
"armed_vacation": "{entity_name} v re\u017eimu dovolen\u00e1",
"disarmed": "{entity_name} nezabezpe\u010den", "disarmed": "{entity_name} nezabezpe\u010den",
"triggered": "{entity_name} spu\u0161t\u011bn" "triggered": "{entity_name} spu\u0161t\u011bn"
} }
@ -29,6 +32,7 @@
"armed_custom_bypass": "Zabezpe\u010deno u\u017eivatelsk\u00fdm obejit\u00edm", "armed_custom_bypass": "Zabezpe\u010deno u\u017eivatelsk\u00fdm obejit\u00edm",
"armed_home": "Re\u017eim domov", "armed_home": "Re\u017eim domov",
"armed_night": "No\u010dn\u00ed re\u017eim", "armed_night": "No\u010dn\u00ed re\u017eim",
"armed_vacation": "V re\u017eimu dovolen\u00e1",
"arming": "Zabezpe\u010dov\u00e1n\u00ed", "arming": "Zabezpe\u010dov\u00e1n\u00ed",
"disarmed": "Nezabezpe\u010deno", "disarmed": "Nezabezpe\u010deno",
"disarming": "Odbezpe\u010dov\u00e1n\u00ed", "disarming": "Odbezpe\u010dov\u00e1n\u00ed",

View File

@ -12,6 +12,7 @@
"is_armed_away": "{entity_name} est arm\u00e9", "is_armed_away": "{entity_name} est arm\u00e9",
"is_armed_home": "{entity_name} est arm\u00e9 \u00e0 la maison", "is_armed_home": "{entity_name} est arm\u00e9 \u00e0 la maison",
"is_armed_night": "{entity_name} est arm\u00e9 la nuit", "is_armed_night": "{entity_name} est arm\u00e9 la nuit",
"is_armed_vacation": "{entity_name} est arm\u00e9 en mode vacances",
"is_disarmed": "{entity_name} est d\u00e9sarm\u00e9", "is_disarmed": "{entity_name} est d\u00e9sarm\u00e9",
"is_triggered": "{entity_name} est d\u00e9clench\u00e9" "is_triggered": "{entity_name} est d\u00e9clench\u00e9"
}, },

View File

@ -9,7 +9,12 @@
"trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa" "trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa"
}, },
"condition_type": { "condition_type": {
"is_armed_vacation": "{entity_name} nyaral\u00e1s \u00e9les\u00edtve" "is_armed_away": "{entity_name} \u00e9les\u00edtve van",
"is_armed_home": "{entity_name} \u00e9les\u00edtett otthoni m\u00f3dban",
"is_armed_night": "{entity_name} \u00e9les\u00edtett \u00e9jszaka m\u00f3dban",
"is_armed_vacation": "{entity_name} nyaral\u00e1s \u00e9les\u00edtve",
"is_disarmed": "{entity_name} hat\u00e1stalan\u00edtva",
"is_triggered": "{entity_name} aktiv\u00e1lva van"
}, },
"trigger_type": { "trigger_type": {
"armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve", "armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve",

View File

@ -1,43 +1,43 @@
{ {
"device_automation": { "device_automation": {
"action_type": { "action_type": {
"arm_away": "Inschakelen {entity_name} afwezig", "arm_away": "Schakel {entity_name} in voor vertrek",
"arm_home": "Inschakelen {entity_name} thuis", "arm_home": "Schakel {entity_name} in voor thuis",
"arm_night": "Inschakelen {entity_name} nacht", "arm_night": "Schakel {entity_name} in voor 's nachts",
"arm_vacation": "Schakel {entity_name} in op vakantie", "arm_vacation": "Schakel {entity_name} in op vakantie",
"disarm": "Uitschakelen {entity_name}", "disarm": "Schakel {entity_name} uit",
"trigger": "Trigger {entity_name}" "trigger": "Laat {entity_name} afgaan"
}, },
"condition_type": { "condition_type": {
"is_armed_away": "{entity_name} afwezig ingeschakeld", "is_armed_away": "{entity_name} ingeschakeld voor vertrek",
"is_armed_home": "{entity_name} thuis ingeschakeld", "is_armed_home": "{entity_name} ingeschakeld voor thuis",
"is_armed_night": "{entity_name} nachtstand ingeschakeld", "is_armed_night": "{entity_name} is ingeschakeld voor 's nachts",
"is_armed_vacation": "{entity_name} is in vakantie geschakeld", "is_armed_vacation": "{entity_name} is in vakantie geschakeld",
"is_disarmed": "{entity_name} is uitgeschakeld", "is_disarmed": "{entity_name} is uitgeschakeld",
"is_triggered": "{entity_name} wordt geactiveerd" "is_triggered": "{entity_name} gaat af"
}, },
"trigger_type": { "trigger_type": {
"armed_away": "{entity_name} afwezig ingeschakeld", "armed_away": "{entity_name} ingeschakeld voor vertrek",
"armed_home": "{entity_name} thuis ingeschakeld", "armed_home": "{entity_name} ingeschakeld voor thuis",
"armed_night": "{entity_name} nachtstand ingeschakeld", "armed_night": "{entity_name} ingeschakeld voor 's nachts",
"armed_vacation": "{entity_name} schakelde vakantie in", "armed_vacation": "{entity_name} schakelde vakantie in",
"disarmed": "{entity_name} uitgeschakeld", "disarmed": "{entity_name} uitgeschakeld",
"triggered": "{entity_name} geactiveerd" "triggered": "{entity_name} afgegaan"
} }
}, },
"state": { "state": {
"_": { "_": {
"armed": "Ingeschakeld", "armed": "Ingeschakeld",
"armed_away": "Ingeschakeld afwezig", "armed_away": "Ingeschakeld voor vertrek",
"armed_custom_bypass": "Ingeschakeld met overbrugging(en)", "armed_custom_bypass": "Ingeschakeld met overbrugging",
"armed_home": "Ingeschakeld thuis", "armed_home": "Ingeschakeld voor thuis",
"armed_night": "Ingeschakeld nacht", "armed_night": "Ingeschakeld voor 's nachts",
"armed_vacation": "Vakantie ingeschakeld", "armed_vacation": "Vakantie ingeschakeld",
"arming": "Schakelt in", "arming": "Schakelt in",
"disarmed": "Uitgeschakeld", "disarmed": "Uitgeschakeld",
"disarming": "Schakelt uit", "disarming": "Schakelt uit",
"pending": "In wacht", "pending": "In wacht",
"triggered": "Geactiveerd" "triggered": "Gaat af"
} }
}, },
"title": "Alarm bedieningspaneel" "title": "Alarm bedieningspaneel"

View File

@ -4,6 +4,7 @@
"arm_away": "Aktiver {entity_name} borte", "arm_away": "Aktiver {entity_name} borte",
"arm_home": "Aktiver {entity_name} hjemme", "arm_home": "Aktiver {entity_name} hjemme",
"arm_night": "Aktiver {entity_name} natt", "arm_night": "Aktiver {entity_name} natt",
"arm_vacation": "{entity_name} ferie",
"disarm": "Deaktiver {entity_name}", "disarm": "Deaktiver {entity_name}",
"trigger": "Utl\u00f8ser {entity_name}" "trigger": "Utl\u00f8ser {entity_name}"
}, },
@ -11,6 +12,7 @@
"is_armed_away": "{entity_name} er aktivert borte", "is_armed_away": "{entity_name} er aktivert borte",
"is_armed_home": "{entity_name} er aktivert hjemme", "is_armed_home": "{entity_name} er aktivert hjemme",
"is_armed_night": "{entity_name} er aktivert natt", "is_armed_night": "{entity_name} er aktivert natt",
"is_armed_vacation": "{entity_name} er armert ferie",
"is_disarmed": "{entity_name} er deaktivert", "is_disarmed": "{entity_name} er deaktivert",
"is_triggered": "{entity_name} er utl\u00f8st" "is_triggered": "{entity_name} er utl\u00f8st"
}, },
@ -18,6 +20,7 @@
"armed_away": "{entity_name} aktivert borte", "armed_away": "{entity_name} aktivert borte",
"armed_home": "{entity_name} aktivert hjemme", "armed_home": "{entity_name} aktivert hjemme",
"armed_night": "{entity_name} aktivert natt", "armed_night": "{entity_name} aktivert natt",
"armed_vacation": "{entity_name} armert ferie",
"disarmed": "{entity_name} deaktivert", "disarmed": "{entity_name} deaktivert",
"triggered": "{entity_name} utl\u00f8st" "triggered": "{entity_name} utl\u00f8st"
} }
@ -29,6 +32,7 @@
"armed_custom_bypass": "Armert tilpasset unntak", "armed_custom_bypass": "Armert tilpasset unntak",
"armed_home": "Armert hjemme", "armed_home": "Armert hjemme",
"armed_night": "Armert natt", "armed_night": "Armert natt",
"armed_vacation": "Armert ferie",
"arming": "Armerer", "arming": "Armerer",
"disarmed": "Avsl\u00e5tt", "disarmed": "Avsl\u00e5tt",
"disarming": "Disarmer", "disarming": "Disarmer",

View File

@ -32,6 +32,6 @@ class AlarmDecoderSensor(SensorEntity):
) )
def _message_callback(self, message): def _message_callback(self, message):
if self._attr_state != message.text: if self._attr_native_value != message.text:
self._attr_state = message.text self._attr_native_value = message.text
self.schedule_update_ha_state() self.schedule_update_ha_state()

View File

@ -20,6 +20,10 @@
} }
}, },
"options": { "options": {
"error": {
"int": "El campo siguiente debe ser un n\u00famero entero.",
"loop_range": "El bucle de RF debe ser un n\u00famero entero entre 1 y 4."
},
"step": { "step": {
"arm_settings": { "arm_settings": {
"data": { "data": {
@ -30,6 +34,17 @@
"data": { "data": {
"edit_select": "Editar" "edit_select": "Editar"
} }
},
"zone_details": {
"data": {
"zone_name": "Nombre de zona",
"zone_rfid": "Serie RF"
}
},
"zone_select": {
"data": {
"zone_number": "N\u00famero de zona"
}
} }
} }
} }

View File

@ -31,6 +31,7 @@
"error": { "error": {
"int": "Az al\u00e1bbi mez\u0151nek eg\u00e9sz sz\u00e1mnak kell lennie.", "int": "Az al\u00e1bbi mez\u0151nek eg\u00e9sz sz\u00e1mnak kell lennie.",
"loop_range": "Az RF hurok eg\u00e9sz sz\u00e1m\u00e1nak 1 \u00e9s 4 k\u00f6z\u00f6tt kell lennie.", "loop_range": "Az RF hurok eg\u00e9sz sz\u00e1m\u00e1nak 1 \u00e9s 4 k\u00f6z\u00f6tt kell lennie.",
"loop_rfid": "Az RF hurok nem haszn\u00e1lhat\u00f3 RF sorozat n\u00e9lk\u00fcl.",
"relay_inclusive": "A rel\u00e9c\u00edm \u00e9s a rel\u00e9csatorna egym\u00e1st\u00f3l f\u00fcgg, \u00e9s egy\u00fctt kell felt\u00fcntetni." "relay_inclusive": "A rel\u00e9c\u00edm \u00e9s a rel\u00e9csatorna egym\u00e1st\u00f3l f\u00fcgg, \u00e9s egy\u00fctt kell felt\u00fcntetni."
}, },
"step": { "step": {
@ -55,6 +56,7 @@
"zone_name": "Z\u00f3na neve", "zone_name": "Z\u00f3na neve",
"zone_relayaddr": "Rel\u00e9 c\u00edm", "zone_relayaddr": "Rel\u00e9 c\u00edm",
"zone_relaychan": "Rel\u00e9 csatorna", "zone_relaychan": "Rel\u00e9 csatorna",
"zone_rfid": "RF soros",
"zone_type": "Z\u00f3na t\u00edpusa" "zone_type": "Z\u00f3na t\u00edpusa"
}, },
"description": "Adja meg a {zone_number} z\u00f3na adatait. {zone_number} z\u00f3na t\u00f6rl\u00e9s\u00e9hez hagyja \u00fcresen a Z\u00f3na neve elemet.", "description": "Adja meg a {zone_number} z\u00f3na adatait. {zone_number} z\u00f3na t\u00f6rl\u00e9s\u00e9hez hagyja \u00fcresen a Z\u00f3na neve elemet.",

View File

@ -32,7 +32,7 @@
"int": "Het onderstaande veld moet een geheel getal zijn.", "int": "Het onderstaande veld moet een geheel getal zijn.",
"loop_range": "RF Lus moet een geheel getal zijn tussen 1 en 4.", "loop_range": "RF Lus moet een geheel getal zijn tussen 1 en 4.",
"loop_rfid": "RF Lus kan niet worden gebruikt zonder RF Serieel.", "loop_rfid": "RF Lus kan niet worden gebruikt zonder RF Serieel.",
"relay_inclusive": "Het relais-adres en het relais-kanaal zijn codeafhankelijk en moeten samen worden opgenomen." "relay_inclusive": "Het relaisadres en het relaiskanaal zijn onderling afhankelijk en moeten samen worden opgenomen."
}, },
"step": { "step": {
"arm_settings": { "arm_settings": {
@ -53,18 +53,18 @@
"zone_details": { "zone_details": {
"data": { "data": {
"zone_loop": "RF Lus", "zone_loop": "RF Lus",
"zone_name": "Zone naam", "zone_name": "Zonenaam",
"zone_relayaddr": "Relais Adres", "zone_relayaddr": "Relaisadres",
"zone_relaychan": "Relais Kanaal", "zone_relaychan": "Relaiskanaal",
"zone_rfid": "RF Serieel", "zone_rfid": "RF Serieel",
"zone_type": "Zone Type" "zone_type": "Zonetype"
}, },
"description": "Voer details in voor zone {zone_number}. Om zone {zone_number} te verwijderen, laat u Zone Name leeg.", "description": "Voer details in voor zone {zone_number}. Om zone {zone_number} te verwijderen, laat u Zonenaam leeg.",
"title": "Configureer AlarmDecoder" "title": "Configureer AlarmDecoder"
}, },
"zone_select": { "zone_select": {
"data": { "data": {
"zone_number": "Zone nummer" "zone_number": "Zonenummer"
}, },
"description": "Voer het zone nummer in dat u wilt toevoegen, bewerken of verwijderen.", "description": "Voer het zone nummer in dat u wilt toevoegen, bewerken of verwijderen.",
"title": "Configureer AlarmDecoder" "title": "Configureer AlarmDecoder"

View File

@ -99,7 +99,7 @@ class AlexaCapability:
return False return False
@staticmethod @staticmethod
def properties_non_controllable() -> bool: def properties_non_controllable() -> bool | None:
"""Return True if non controllable.""" """Return True if non controllable."""
return None return None

View File

@ -1,4 +1,6 @@
"""Alexa related errors.""" """Alexa related errors."""
from __future__ import annotations
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from .const import API_TEMP_UNITS from .const import API_TEMP_UNITS
@ -22,8 +24,8 @@ class AlexaError(Exception):
A handler can raise subclasses of this to return an error to the request. A handler can raise subclasses of this to return an error to the request.
""" """
namespace = None namespace: str | None = None
error_type = None error_type: str | None = None
def __init__(self, error_message, payload=None): def __init__(self, error_message, payload=None):
"""Initialize an alexa error.""" """Initialize an alexa error."""

View File

@ -5,6 +5,7 @@ import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
import time import time
from typing import Optional, cast
from aiohttp import ClientError, ClientSession from aiohttp import ClientError, ClientSession
import async_timeout import async_timeout
@ -166,7 +167,7 @@ async def _configure_almond_for_ha(
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url) _LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
data = await store.async_load() data = cast(Optional[dict], await store.async_load())
if data is None: if data is None:
data = {} data = {}
@ -204,7 +205,7 @@ async def _configure_almond_for_ha(
) )
except (asyncio.TimeoutError, ClientError) as err: except (asyncio.TimeoutError, ClientError) as err:
if isinstance(err, asyncio.TimeoutError): if isinstance(err, asyncio.TimeoutError):
msg = "Request timeout" msg: str | ClientError = "Request timeout"
else: else:
msg = err msg = err
_LOGGER.warning("Unable to configure Almond: %s", msg) _LOGGER.warning("Unable to configure Almond: %s", msg)

View File

@ -1,6 +1,9 @@
"""Config flow to connect with Home Assistant.""" """Config flow to connect with Home Assistant."""
from __future__ import annotations
import asyncio import asyncio
import logging import logging
from typing import Any
from aiohttp import ClientError from aiohttp import ClientError
import async_timeout import async_timeout
@ -9,6 +12,7 @@ import voluptuous as vol
from yarl import URL from yarl import URL
from homeassistant import config_entries, core, data_entry_flow from homeassistant import config_entries, core, data_entry_flow
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
from .const import DOMAIN as ALMOND_DOMAIN, TYPE_LOCAL, TYPE_OAUTH2 from .const import DOMAIN as ALMOND_DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
@ -64,7 +68,7 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
return result return result
async def async_oauth_create_entry(self, data: dict) -> dict: async def async_oauth_create_entry(self, data: dict) -> FlowResult:
"""Create an entry for the flow. """Create an entry for the flow.
Ok to override if you want to fetch extra info or even add another step. Ok to override if you want to fetch extra info or even add another step.
@ -73,7 +77,7 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
data["host"] = self.host data["host"] = self.host
return self.async_create_entry(title=self.flow_impl.name, data=data) return self.async_create_entry(title=self.flow_impl.name, data=data)
async def async_step_import(self, user_input: dict = None) -> dict: async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
"""Import data.""" """Import data."""
# Only allow 1 instance. # Only allow 1 instance.
if self._async_current_entries(): if self._async_current_entries():

View File

@ -112,7 +112,7 @@ class AlphaVantageSensor(SensorEntity):
self._symbol = symbol[CONF_SYMBOL] self._symbol = symbol[CONF_SYMBOL]
self._attr_name = symbol.get(CONF_NAME, self._symbol) self._attr_name = symbol.get(CONF_NAME, self._symbol)
self._timeseries = timeseries self._timeseries = timeseries
self._attr_unit_of_measurement = symbol.get(CONF_CURRENCY, self._symbol) self._attr_native_unit_of_measurement = symbol.get(CONF_CURRENCY, self._symbol)
self._attr_icon = ICONS.get(symbol.get(CONF_CURRENCY, "USD")) self._attr_icon = ICONS.get(symbol.get(CONF_CURRENCY, "USD"))
def update(self): def update(self):
@ -120,7 +120,7 @@ class AlphaVantageSensor(SensorEntity):
_LOGGER.debug("Requesting new data for symbol %s", self._symbol) _LOGGER.debug("Requesting new data for symbol %s", self._symbol)
all_values, _ = self._timeseries.get_intraday(self._symbol) all_values, _ = self._timeseries.get_intraday(self._symbol)
values = next(iter(all_values.values())) values = next(iter(all_values.values()))
self._attr_state = values["1. open"] self._attr_native_value = values["1. open"]
self._attr_extra_state_attributes = ( self._attr_extra_state_attributes = (
{ {
ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_ATTRIBUTION: ATTRIBUTION,
@ -148,7 +148,7 @@ class AlphaVantageForeignExchange(SensorEntity):
else f"{self._to_currency}/{self._from_currency}" else f"{self._to_currency}/{self._from_currency}"
) )
self._attr_icon = ICONS.get(self._from_currency, "USD") self._attr_icon = ICONS.get(self._from_currency, "USD")
self._attr_unit_of_measurement = self._to_currency self._attr_native_unit_of_measurement = self._to_currency
def update(self): def update(self):
"""Get the latest data and updates the states.""" """Get the latest data and updates the states."""
@ -160,7 +160,7 @@ class AlphaVantageForeignExchange(SensorEntity):
values, _ = self._foreign_exchange.get_currency_exchange_rate( values, _ = self._foreign_exchange.get_currency_exchange_rate(
from_currency=self._from_currency, to_currency=self._to_currency from_currency=self._from_currency, to_currency=self._to_currency
) )
self._attr_state = round(float(values["5. Exchange Rate"]), 4) self._attr_native_value = round(float(values["5. Exchange Rate"]), 4)
self._attr_extra_state_attributes = ( self._attr_extra_state_attributes = (
{ {
ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_ATTRIBUTION: ATTRIBUTION,

View File

@ -39,38 +39,38 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
SensorEntityDescription( SensorEntityDescription(
key="particulate_matter_2_5", key="particulate_matter_2_5",
name="Particulate Matter < 2.5 μm", name="Particulate Matter < 2.5 μm",
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="particulate_matter_10", key="particulate_matter_10",
name="Particulate Matter < 10 μm", name="Particulate Matter < 10 μm",
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="sulphur_dioxide", key="sulphur_dioxide",
name="Sulphur Dioxide (SO2)", name="Sulphur Dioxide (SO2)",
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="nitrogen_dioxide", key="nitrogen_dioxide",
name="Nitrogen Dioxide (NO2)", name="Nitrogen Dioxide (NO2)",
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="ozone", key="ozone",
name="Ozone", name="Ozone",
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="carbon_monoxide", key="carbon_monoxide",
name="Carbon Monoxide (CO)", name="Carbon Monoxide (CO)",
device_class=DEVICE_CLASS_CO, device_class=DEVICE_CLASS_CO,
unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -85,21 +85,21 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Grass Pollen", name="Grass Pollen",
icon="mdi:grass", icon="mdi:grass",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree", key="tree",
name="Tree Pollen", name="Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed", key="weed",
name="Weed Pollen", name="Weed Pollen",
icon="mdi:sprout", icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
), ),
SensorEntityDescription( SensorEntityDescription(
key="grass_risk", key="grass_risk",
@ -124,7 +124,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Poaceae Grass Pollen", name="Poaceae Grass Pollen",
icon="mdi:grass", icon="mdi:grass",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -132,7 +132,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Alder Tree Pollen", name="Alder Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -140,7 +140,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Birch Tree Pollen", name="Birch Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -148,7 +148,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Cypress Tree Pollen", name="Cypress Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -156,7 +156,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Elm Tree Pollen", name="Elm Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -164,7 +164,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Hazel Tree Pollen", name="Hazel Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -172,7 +172,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Oak Tree Pollen", name="Oak Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -180,7 +180,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Pine Tree Pollen", name="Pine Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -188,7 +188,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Plane Tree Pollen", name="Plane Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -196,7 +196,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Poplar Tree Pollen", name="Poplar Tree Pollen",
icon="mdi:tree", icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -204,7 +204,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Chenopod Weed Pollen", name="Chenopod Weed Pollen",
icon="mdi:sprout", icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -212,7 +212,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Mugwort Weed Pollen", name="Mugwort Weed Pollen",
icon="mdi:sprout", icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -220,7 +220,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Nettle Weed Pollen", name="Nettle Weed Pollen",
icon="mdi:sprout", icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -228,7 +228,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Ragweed Weed Pollen", name="Ragweed Weed Pollen",
icon="mdi:sprout", icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
], ],

View File

@ -66,7 +66,7 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
} }
@property @property
def state(self) -> StateType: def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
value = getattr(self.coordinator.data, self.entity_description.key) value = getattr(self.coordinator.data, self.entity_description.key)
if isinstance(value, str): if isinstance(value, str):

View File

@ -21,7 +21,7 @@
"longitude": "Longitud", "longitude": "Longitud",
"name": "Nom" "name": "Nom"
}, },
"description": "Configura Ambee per a integrar-lo amb Home Assistant." "description": "Configura la integraci\u00f3 d'Ambee amb Home Assistant."
} }
} }
} }

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