mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Merge pull request #55532 from home-assistant/rc
This commit is contained in:
commit
245eec7041
39
.coveragerc
39
.coveragerc
@ -36,6 +36,9 @@ omit =
|
||||
homeassistant/components/agent_dvr/helpers.py
|
||||
homeassistant/components/airnow/__init__.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/sensor.py
|
||||
homeassistant/components/aladdin_connect/*
|
||||
@ -314,6 +317,12 @@ omit =
|
||||
homeassistant/components/firmata/switch.py
|
||||
homeassistant/components/fitbit/*
|
||||
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/flexit/climate.py
|
||||
homeassistant/components/flic/binary_sensor.py
|
||||
@ -375,6 +384,7 @@ omit =
|
||||
homeassistant/components/google/*
|
||||
homeassistant/components/google_cloud/tts.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/helpers.py
|
||||
homeassistant/components/google_travel_time/sensor.py
|
||||
@ -636,6 +646,7 @@ omit =
|
||||
homeassistant/components/modbus/cover.py
|
||||
homeassistant/components/modbus/climate.py
|
||||
homeassistant/components/modbus/modbus.py
|
||||
homeassistant/components/modbus/sensor.py
|
||||
homeassistant/components/modbus/validators.py
|
||||
homeassistant/components/modem_callerid/sensor.py
|
||||
homeassistant/components/motion_blinds/__init__.py
|
||||
@ -666,18 +677,21 @@ omit =
|
||||
homeassistant/components/mysensors/helpers.py
|
||||
homeassistant/components/mysensors/light.py
|
||||
homeassistant/components/mysensors/notify.py
|
||||
homeassistant/components/mysensors/sensor.py
|
||||
homeassistant/components/mysensors/switch.py
|
||||
homeassistant/components/mystrom/binary_sensor.py
|
||||
homeassistant/components/mystrom/light.py
|
||||
homeassistant/components/mystrom/switch.py
|
||||
homeassistant/components/myq/__init__.py
|
||||
homeassistant/components/myq/cover.py
|
||||
homeassistant/components/myq/light.py
|
||||
homeassistant/components/nad/media_player.py
|
||||
homeassistant/components/nanoleaf/__init__.py
|
||||
homeassistant/components/nanoleaf/light.py
|
||||
homeassistant/components/nanoleaf/util.py
|
||||
homeassistant/components/neato/__init__.py
|
||||
homeassistant/components/neato/api.py
|
||||
homeassistant/components/neato/camera.py
|
||||
homeassistant/components/neato/hub.py
|
||||
homeassistant/components/neato/sensor.py
|
||||
homeassistant/components/neato/switch.py
|
||||
homeassistant/components/neato/vacuum.py
|
||||
@ -696,7 +710,8 @@ omit =
|
||||
homeassistant/components/niko_home_control/light.py
|
||||
homeassistant/components/nilu/air_quality.py
|
||||
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/notion/__init__.py
|
||||
homeassistant/components/notion/binary_sensor.py
|
||||
@ -830,9 +845,9 @@ omit =
|
||||
homeassistant/components/raincloud/*
|
||||
homeassistant/components/rainmachine/__init__.py
|
||||
homeassistant/components/rainmachine/binary_sensor.py
|
||||
homeassistant/components/rainmachine/model.py
|
||||
homeassistant/components/rainmachine/sensor.py
|
||||
homeassistant/components/rainmachine/switch.py
|
||||
homeassistant/components/rainforest_eagle/sensor.py
|
||||
homeassistant/components/raspihats/*
|
||||
homeassistant/components/raspyrfm/*
|
||||
homeassistant/components/recollect_waste/__init__.py
|
||||
@ -850,12 +865,8 @@ omit =
|
||||
homeassistant/components/ring/camera.py
|
||||
homeassistant/components/ripple/sensor.py
|
||||
homeassistant/components/rituals_perfume_genie/binary_sensor.py
|
||||
homeassistant/components/rituals_perfume_genie/entity.py
|
||||
homeassistant/components/rituals_perfume_genie/number.py
|
||||
homeassistant/components/rituals_perfume_genie/select.py
|
||||
homeassistant/components/rituals_perfume_genie/sensor.py
|
||||
homeassistant/components/rituals_perfume_genie/switch.py
|
||||
homeassistant/components/rituals_perfume_genie/__init__.py
|
||||
homeassistant/components/rocketchat/notify.py
|
||||
homeassistant/components/roomba/__init__.py
|
||||
homeassistant/components/roomba/binary_sensor.py
|
||||
@ -893,7 +904,9 @@ omit =
|
||||
homeassistant/components/screenlogic/switch.py
|
||||
homeassistant/components/scsgate/*
|
||||
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/sensor.py
|
||||
homeassistant/components/sensibo/climate.py
|
||||
@ -988,6 +1001,7 @@ omit =
|
||||
homeassistant/components/suez_water/*
|
||||
homeassistant/components/supervisord/sensor.py
|
||||
homeassistant/components/surepetcare/__init__.py
|
||||
homeassistant/components/surepetcare/binary_sensor.py
|
||||
homeassistant/components/surepetcare/sensor.py
|
||||
homeassistant/components/swiss_hydrological_data/sensor.py
|
||||
homeassistant/components/swiss_public_transport/sensor.py
|
||||
@ -1008,8 +1022,9 @@ omit =
|
||||
homeassistant/components/synology_srm/device_tracker.py
|
||||
homeassistant/components/syslog/notify.py
|
||||
homeassistant/components/system_bridge/__init__.py
|
||||
homeassistant/components/system_bridge/const.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/systemmonitor/sensor.py
|
||||
homeassistant/components/tado/*
|
||||
@ -1079,6 +1094,10 @@ omit =
|
||||
homeassistant/components/traccar/device_tracker.py
|
||||
homeassistant/components/traccar/const.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/trafikverket_train/sensor.py
|
||||
homeassistant/components/trafikverket_weatherstation/sensor.py
|
||||
@ -1112,7 +1131,6 @@ omit =
|
||||
homeassistant/components/upcloud/switch.py
|
||||
homeassistant/components/upnp/*
|
||||
homeassistant/components/upc_connect/*
|
||||
homeassistant/components/uptimerobot/binary_sensor.py
|
||||
homeassistant/components/uscis/sensor.py
|
||||
homeassistant/components/vallox/*
|
||||
homeassistant/components/vasttrafik/sensor.py
|
||||
@ -1196,6 +1214,7 @@ omit =
|
||||
homeassistant/components/xiaomi_miio/__init__.py
|
||||
homeassistant/components/xiaomi_miio/air_quality.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_tracker.py
|
||||
homeassistant/components/xiaomi_miio/fan.py
|
||||
|
@ -24,7 +24,12 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": 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": [
|
||||
"!input scalar",
|
||||
"!secret scalar",
|
||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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`.
|
||||
- [ ] New or updated dependencies have been added to `requirements_all.txt`.
|
||||
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`.
|
||||
|
||||
The integration reached or maintains the following [Integration Quality Scale][quality-scale]:
|
||||
|
23
.github/workflows/builder.yml
vendored
23
.github/workflows/builder.yml
vendored
@ -47,6 +47,19 @@ jobs:
|
||||
with:
|
||||
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:
|
||||
name: Build PyPi package
|
||||
needs: init
|
||||
@ -101,6 +114,11 @@ jobs:
|
||||
python3 script/version_bump.py nightly
|
||||
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
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
@ -230,11 +248,12 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install VCN tools
|
||||
uses: home-assistant/actions/helpers/vcn@master
|
||||
|
||||
- name: Build Meta Image
|
||||
shell: bash
|
||||
run: |
|
||||
bash <(curl https://getvcn.codenotary.com -L)
|
||||
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
|
||||
function create_manifest() {
|
||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -740,4 +740,4 @@ jobs:
|
||||
coverage report --fail-under=94
|
||||
coverage xml
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2.0.2
|
||||
uses: codecov/codecov-action@v2.0.3
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2.1.1
|
||||
- uses: dessant/lock-threads@v2.1.2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: "30"
|
||||
|
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@ -66,6 +66,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
tag:
|
||||
- "3.9-alpine3.13"
|
||||
- "3.9-alpine3.14"
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2.3.4
|
||||
@ -106,6 +107,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
tag:
|
||||
- "3.9-alpine3.13"
|
||||
- "3.9-alpine3.14"
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@ config/*
|
||||
config2/*
|
||||
|
||||
tests/testing_config/deps
|
||||
tests/testing_config/home-assistant.log
|
||||
tests/testing_config/home-assistant.log*
|
||||
|
||||
# hass-release
|
||||
data/
|
||||
|
@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.23.0
|
||||
rev: v2.23.3
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
@ -45,7 +45,7 @@ repos:
|
||||
- --configfile=tests/bandit.yaml
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.8.0
|
||||
rev: 5.9.3
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
|
@ -15,6 +15,7 @@ homeassistant.components.alarm_control_panel.*
|
||||
homeassistant.components.amazon_polly.*
|
||||
homeassistant.components.ambee.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
homeassistant.components.ampio.*
|
||||
homeassistant.components.automation.*
|
||||
homeassistant.components.binary_sensor.*
|
||||
@ -63,6 +64,7 @@ homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.nam.*
|
||||
homeassistant.components.neato.*
|
||||
homeassistant.components.nest.*
|
||||
homeassistant.components.netatmo.*
|
||||
homeassistant.components.network.*
|
||||
@ -103,6 +105,7 @@ homeassistant.components.tile.*
|
||||
homeassistant.components.tts.*
|
||||
homeassistant.components.upcloud.*
|
||||
homeassistant.components.uptime.*
|
||||
homeassistant.components.uptimerobot.*
|
||||
homeassistant.components.vacuum.*
|
||||
homeassistant.components.water_heater.*
|
||||
homeassistant.components.weather.*
|
||||
|
22
.vscode/tasks.json
vendored
22
.vscode/tasks.json
vendored
@ -60,6 +60,21 @@
|
||||
},
|
||||
"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",
|
||||
"type": "shell",
|
||||
@ -102,5 +117,12 @@
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "integrationName",
|
||||
"type": "promptString",
|
||||
"description": "For which integration should the task run?"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
24
CODEOWNERS
24
CODEOWNERS
@ -29,6 +29,7 @@ homeassistant/components/aemet/* @noltari
|
||||
homeassistant/components/agent_dvr/* @ispysoftware
|
||||
homeassistant/components/airly/* @bieniu
|
||||
homeassistant/components/airnow/* @asymworks
|
||||
homeassistant/components/airtouch4/* @LonePurpleWolf
|
||||
homeassistant/components/airvisual/* @bachya
|
||||
homeassistant/components/alarmdecoder/* @ajschmidt8
|
||||
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
|
||||
@ -37,6 +38,7 @@ homeassistant/components/alpha_vantage/* @fabaff
|
||||
homeassistant/components/ambee/* @frenck
|
||||
homeassistant/components/ambiclimate/* @danielhiversen
|
||||
homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/amcrest/* @flacjacket
|
||||
homeassistant/components/analytics/* @home-assistant/core @ludeeus
|
||||
homeassistant/components/androidtv/* @JeffLIrion
|
||||
homeassistant/components/apache_kafka/* @bachya
|
||||
@ -117,7 +119,6 @@ homeassistant/components/dexcom/* @gagebenne
|
||||
homeassistant/components/dhcp/* @bdraco
|
||||
homeassistant/components/dht/* @thegardenmonkey
|
||||
homeassistant/components/digital_ocean/* @fabaff
|
||||
homeassistant/components/directv/* @ctalkington
|
||||
homeassistant/components/discogs/* @thibmaek
|
||||
homeassistant/components/doorbird/* @oblogic7 @bdraco
|
||||
homeassistant/components/dsmr/* @Robbie1221 @frenck
|
||||
@ -161,6 +162,7 @@ homeassistant/components/filter/* @dgomes
|
||||
homeassistant/components/fireservicerota/* @cyberjunky
|
||||
homeassistant/components/firmata/* @DaAwesomeP
|
||||
homeassistant/components/fixer/* @fabaff
|
||||
homeassistant/components/fjaraskupan/* @elupus
|
||||
homeassistant/components/flick_electric/* @ZephireNZ
|
||||
homeassistant/components/flipr/* @cnico
|
||||
homeassistant/components/flo/* @dmulcahey
|
||||
@ -186,6 +188,7 @@ homeassistant/components/geo_rss_events/* @exxamalte
|
||||
homeassistant/components/geonetnz_quakes/* @exxamalte
|
||||
homeassistant/components/geonetnz_volcano/* @exxamalte
|
||||
homeassistant/components/gios/* @bieniu
|
||||
homeassistant/components/github/* @timmo001 @ludeeus
|
||||
homeassistant/components/gitter/* @fabaff
|
||||
homeassistant/components/glances/* @fabaff @engrbm87
|
||||
homeassistant/components/goalzero/* @tkdrob
|
||||
@ -196,7 +199,7 @@ homeassistant/components/gpsd/* @fabaff
|
||||
homeassistant/components/gree/* @cmroche
|
||||
homeassistant/components/greeneye_monitor/* @jkeljo
|
||||
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/habitica/* @ASMfreaK @leikoilja
|
||||
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
|
||||
@ -245,6 +248,7 @@ homeassistant/components/integration/* @dgomes
|
||||
homeassistant/components/intent/* @home-assistant/core
|
||||
homeassistant/components/intesishome/* @jnimmo
|
||||
homeassistant/components/ios/* @robbiet480
|
||||
homeassistant/components/iotawatt/* @gtdiehl
|
||||
homeassistant/components/iperf3/* @rohankapoorcom
|
||||
homeassistant/components/ipma/* @dgomes @abmantis
|
||||
homeassistant/components/ipp/* @ctalkington
|
||||
@ -319,10 +323,11 @@ homeassistant/components/msteams/* @peroyvind
|
||||
homeassistant/components/mullvad/* @meichthys
|
||||
homeassistant/components/mutesync/* @currentoor
|
||||
homeassistant/components/my/* @home-assistant/core
|
||||
homeassistant/components/myq/* @bdraco
|
||||
homeassistant/components/myq/* @bdraco @ehendrix23
|
||||
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
|
||||
homeassistant/components/mystrom/* @fabaff
|
||||
homeassistant/components/nam/* @bieniu
|
||||
homeassistant/components/nanoleaf/* @milanmeu
|
||||
homeassistant/components/neato/* @dshokouhi @Santobert
|
||||
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
|
||||
homeassistant/components/nello/* @pschmitt
|
||||
@ -337,6 +342,7 @@ homeassistant/components/nfandroidtv/* @tkdrob
|
||||
homeassistant/components/nightscout/* @marciogranzotto
|
||||
homeassistant/components/nilu/* @hfurubotten
|
||||
homeassistant/components/nissan_leaf/* @filcole
|
||||
homeassistant/components/nmap_tracker/* @bdraco
|
||||
homeassistant/components/nmbs/* @thibmaek
|
||||
homeassistant/components/no_ip/* @fabaff
|
||||
homeassistant/components/noaa_tides/* @jdelaney72
|
||||
@ -370,6 +376,7 @@ homeassistant/components/orangepi_gpio/* @pascallj
|
||||
homeassistant/components/oru/* @bvlaicu
|
||||
homeassistant/components/ovo_energy/* @timmo001
|
||||
homeassistant/components/ozw/* @cgarwood @marcelveldt @MartinHjelmare
|
||||
homeassistant/components/p1_monitor/* @klaasnicolaas
|
||||
homeassistant/components/panel_custom/* @home-assistant/frontend
|
||||
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
||||
homeassistant/components/pcal9535a/* @Shulyaka
|
||||
@ -502,7 +509,7 @@ homeassistant/components/synology_dsm/* @hacf-fr @Quentame @mib1185
|
||||
homeassistant/components/synology_srm/* @aerialls
|
||||
homeassistant/components/syslog/* @fabaff
|
||||
homeassistant/components/system_bridge/* @timmo001
|
||||
homeassistant/components/tado/* @michaelarnauts @bdraco @noltari
|
||||
homeassistant/components/tado/* @michaelarnauts @noltari
|
||||
homeassistant/components/tag/* @balloob @dmulcahey
|
||||
homeassistant/components/tahoma/* @philklei
|
||||
homeassistant/components/tankerkoenig/* @guillempages
|
||||
@ -525,6 +532,8 @@ homeassistant/components/totalconnect/* @austinmroczek
|
||||
homeassistant/components/tplink/* @rytilahti @thegardenmonkey
|
||||
homeassistant/components/traccar/* @ludeeus
|
||||
homeassistant/components/trace/* @home-assistant/core
|
||||
homeassistant/components/tractive/* @Danielhiversen @zhulik @bieniu
|
||||
homeassistant/components/tradfri/* @janiversen
|
||||
homeassistant/components/trafikverket_train/* @endor-force
|
||||
homeassistant/components/trafikverket_weatherstation/* @endor-force
|
||||
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
||||
@ -539,8 +548,9 @@ homeassistant/components/upb/* @gwww
|
||||
homeassistant/components/upc_connect/* @pvizeli @fabaff
|
||||
homeassistant/components/upcloud/* @scop
|
||||
homeassistant/components/updater/* @home-assistant/core
|
||||
homeassistant/components/upnp/* @StevenLooman
|
||||
homeassistant/components/upnp/* @StevenLooman @ehendrix23
|
||||
homeassistant/components/uptimerobot/* @ludeeus
|
||||
homeassistant/components/usb/* @bdraco
|
||||
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
||||
homeassistant/components/utility_meter/* @dgomes
|
||||
homeassistant/components/velbus/* @Cereal2nd @brefra
|
||||
@ -576,13 +586,13 @@ homeassistant/components/worldclock/* @fabaff
|
||||
homeassistant/components/xbox/* @hunterjm
|
||||
homeassistant/components/xbox_live/* @MartinHjelmare
|
||||
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/xmpp/* @fabaff @flowolf
|
||||
homeassistant/components/yale_smart_alarm/* @gjohansson-ST
|
||||
homeassistant/components/yamaha_musiccast/* @vigonotion @micha91
|
||||
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/yi/* @bachya
|
||||
homeassistant/components/youless/* @gjong
|
||||
|
10
build.json
10
build.json
@ -2,11 +2,11 @@
|
||||
"image": "homeassistant/{arch}-homeassistant",
|
||||
"shadow_repository": "ghcr.io/home-assistant",
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.07.0",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.07.0",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.07.0",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.07.0",
|
||||
"i386": "ghcr.io/home-assistant/i386-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.08.0",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.08.0",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.08.0",
|
||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.08.0"
|
||||
},
|
||||
"labels": {
|
||||
"io.hass.type": "core",
|
||||
|
@ -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.types import PolicyType
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth"
|
||||
GROUP_NAME_ADMIN = "Administrators"
|
||||
@ -491,7 +493,7 @@ class AuthStore:
|
||||
self._store.async_delay_save(self._data_to_save, 1)
|
||||
|
||||
@callback
|
||||
def _data_to_save(self) -> dict:
|
||||
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Return the data to store."""
|
||||
assert self._users is not None
|
||||
assert self._groups is not None
|
||||
|
@ -22,6 +22,8 @@ from ..auth_store import AuthStore
|
||||
from ..const import MFA_SESSION_EXPIRATION
|
||||
from ..models import Credentials, RefreshToken, User, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_REQS = "auth_prov_reqs_processed"
|
||||
|
||||
@ -96,7 +98,7 @@ class AuthProvider:
|
||||
|
||||
# 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.
|
||||
|
||||
Auth provider should extend LoginFlow and return an instance.
|
||||
|
@ -17,6 +17,8 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
CONF_ARGS = "args"
|
||||
CONF_META = "meta"
|
||||
|
||||
@ -56,7 +58,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
super().__init__(*args, **kwargs)
|
||||
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 CommandLineLoginFlow(self)
|
||||
|
||||
|
@ -19,6 +19,8 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth_provider.homeassistant"
|
||||
|
||||
@ -235,7 +237,7 @@ class HassAuthProvider(AuthProvider):
|
||||
await data.async_load()
|
||||
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 HassLoginFlow(self)
|
||||
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
import hmac
|
||||
from typing import cast
|
||||
from typing import Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -15,6 +15,8 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
USER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("username"): str,
|
||||
@ -37,7 +39,7 @@ class InvalidAuthError(HomeAssistantError):
|
||||
class ExampleAuthProvider(AuthProvider):
|
||||
"""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 ExampleLoginFlow(self)
|
||||
|
||||
|
@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import hmac
|
||||
from typing import cast
|
||||
from typing import Any, cast
|
||||
|
||||
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 ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||
CONF_API_PASSWORD = "api_password"
|
||||
|
||||
@ -44,7 +46,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
|
||||
"""Return 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 LegacyLoginFlow(self)
|
||||
|
||||
|
@ -27,6 +27,8 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from .. import InvalidAuthError
|
||||
from ..models import Credentials, RefreshToken, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
IPAddress = Union[IPv4Address, IPv6Address]
|
||||
IPNetwork = Union[IPv4Network, IPv6Network]
|
||||
|
||||
@ -97,7 +99,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
"""Trusted Networks auth provider does not support MFA."""
|
||||
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."""
|
||||
assert context is not None
|
||||
ip_addr = cast(IPAddress, context.get("ip_address"))
|
||||
|
@ -332,15 +332,17 @@ def async_enable_logging(
|
||||
not err_path_exists and os.access(err_dir, os.W_OK)
|
||||
):
|
||||
|
||||
err_handler: logging.handlers.RotatingFileHandler | logging.handlers.TimedRotatingFileHandler
|
||||
if log_rotate_days:
|
||||
err_handler: logging.FileHandler = (
|
||||
logging.handlers.TimedRotatingFileHandler(
|
||||
err_log_path, when="midnight", backupCount=log_rotate_days
|
||||
)
|
||||
err_handler = logging.handlers.TimedRotatingFileHandler(
|
||||
err_log_path, when="midnight", backupCount=log_rotate_days
|
||||
)
|
||||
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.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Support for Abode Security System cameras."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
@ -73,7 +75,9 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
else:
|
||||
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."""
|
||||
self.refresh_image()
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
"""Support for Abode Security System sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
@ -11,12 +13,23 @@ from homeassistant.const import (
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
# Sensor types: Name, icon
|
||||
SENSOR_TYPES = {
|
||||
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE],
|
||||
CONST.HUMI_STATUS_KEY: ["Humidity", DEVICE_CLASS_HUMIDITY],
|
||||
CONST.LUX_STATUS_KEY: ["Lux", DEVICE_CLASS_ILLUMINANCE],
|
||||
}
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=CONST.TEMP_STATUS_KEY,
|
||||
name="Temperature",
|
||||
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):
|
||||
@ -26,10 +39,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
entities = []
|
||||
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
if sensor_type not in device.get_value(CONST.STATUSES_KEY):
|
||||
continue
|
||||
entities.append(AbodeSensor(data, device, sensor_type))
|
||||
conditions = device.get_value(CONST.STATUSES_KEY)
|
||||
entities.extend(
|
||||
[
|
||||
AbodeSensor(data, device, description)
|
||||
for description in SENSOR_TYPES
|
||||
if description.key in conditions
|
||||
]
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@ -37,26 +54,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
"""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."""
|
||||
super().__init__(data, device)
|
||||
self._sensor_type = sensor_type
|
||||
self._attr_name = f"{device.name} {SENSOR_TYPES[sensor_type][0]}"
|
||||
self._attr_device_class = SENSOR_TYPES[self._sensor_type][1]
|
||||
self._attr_unique_id = f"{device.device_uuid}-{sensor_type}"
|
||||
if self._sensor_type == CONST.TEMP_STATUS_KEY:
|
||||
self._attr_unit_of_measurement = device.temp_unit
|
||||
elif self._sensor_type == CONST.HUMI_STATUS_KEY:
|
||||
self._attr_unit_of_measurement = device.humidity_unit
|
||||
elif self._sensor_type == CONST.LUX_STATUS_KEY:
|
||||
self._attr_unit_of_measurement = device.lux_unit
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{device.name} {description.name}"
|
||||
self._attr_unique_id = f"{device.device_uuid}-{description.key}"
|
||||
if description.key == CONST.TEMP_STATUS_KEY:
|
||||
self._attr_native_unit_of_measurement = device.temp_unit
|
||||
elif description.key == CONST.HUMI_STATUS_KEY:
|
||||
self._attr_native_unit_of_measurement = device.humidity_unit
|
||||
elif description.key == CONST.LUX_STATUS_KEY:
|
||||
self._attr_native_unit_of_measurement = device.lux_unit
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""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
|
||||
if self._sensor_type == CONST.HUMI_STATUS_KEY:
|
||||
if self.entity_description.key == CONST.HUMI_STATUS_KEY:
|
||||
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
|
||||
|
@ -88,10 +88,10 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
)
|
||||
if coordinator.is_metric:
|
||||
self._unit_system = API_METRIC
|
||||
self._attr_unit_of_measurement = description.unit_metric
|
||||
self._attr_native_unit_of_measurement = description.unit_metric
|
||||
else:
|
||||
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 = {
|
||||
"identifiers": {(DOMAIN, coordinator.location_key)},
|
||||
"name": NAME,
|
||||
@ -101,7 +101,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
self.forecast_day = forecast_day
|
||||
|
||||
@property
|
||||
def state(self) -> StateType:
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state."""
|
||||
if self.forecast_day is not None:
|
||||
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE:
|
||||
|
@ -5,7 +5,8 @@
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
|
||||
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
|
||||
"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": {
|
||||
"user": {
|
||||
@ -15,6 +16,7 @@
|
||||
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@ -22,6 +24,9 @@
|
||||
"options": {
|
||||
"step": {
|
||||
"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.",
|
||||
"title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea AccuWeather"
|
||||
}
|
||||
@ -29,7 +34,8 @@
|
||||
},
|
||||
"system_health": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@
|
||||
},
|
||||
"error": {
|
||||
"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": {
|
||||
"user": {
|
||||
@ -15,6 +16,7 @@
|
||||
"longitude": "Hossz\u00fas\u00e1g",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@ -22,6 +24,10 @@
|
||||
"options": {
|
||||
"step": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"state": {
|
||||
"accuweather__pressure_tendency": {
|
||||
"falling": "\u05d9\u05d5\u05e8\u05d3",
|
||||
"rising": "\u05e2\u05d5\u05dc\u05d4",
|
||||
"steady": "\u05d9\u05e6\u05d9\u05d1"
|
||||
}
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ class AcmedaCover(AcmedaBase, CoverEntity):
|
||||
None is unknown, 0 is closed, 100 is fully open.
|
||||
"""
|
||||
position = None
|
||||
if self.roller.type in [7, 10]:
|
||||
if self.roller.type in (7, 10):
|
||||
position = 100 - self.roller.closed_percent
|
||||
return position
|
||||
|
||||
|
@ -34,7 +34,7 @@ class AcmedaBattery(AcmedaBase, SensorEntity):
|
||||
"""Representation of a Acmeda cover device."""
|
||||
|
||||
device_class = DEVICE_CLASS_BATTERY
|
||||
unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -42,6 +42,6 @@ class AcmedaBattery(AcmedaBase, SensorEntity):
|
||||
return f"{super().name} Battery"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the state of the device."""
|
||||
return self.roller.battery
|
||||
|
@ -2,6 +2,14 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,9 @@ from __future__ import annotations
|
||||
import re
|
||||
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"\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))"
|
||||
+ r"\svalid\sfor:\s(?P<timevalid>(-?\d+))"
|
||||
|
@ -49,20 +49,19 @@ async def async_setup_entry(
|
||||
class AdaxDevice(ClimateEntity):
|
||||
"""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:
|
||||
"""Initialize the heater."""
|
||||
self._heater_data = heater_data
|
||||
self._adax_data_handler = adax_data_handler
|
||||
|
||||
@property
|
||||
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']}"
|
||||
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@ -83,11 +82,6 @@ class AdaxDevice(ClimateEntity):
|
||||
return "mdi:radiator"
|
||||
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:
|
||||
"""Set hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
@ -105,21 +99,6 @@ class AdaxDevice(ClimateEntity):
|
||||
return
|
||||
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
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the current temperature."""
|
||||
@ -130,11 +109,6 @@ class AdaxDevice(ClimateEntity):
|
||||
"""Return the temperature we try to reach."""
|
||||
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:
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -10,6 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "ID \u00fa\u010dtu",
|
||||
"host": "Hostitel",
|
||||
"password": "Heslo"
|
||||
}
|
||||
|
11
homeassistant/components/adax/translations/es.json
Normal file
11
homeassistant/components/adax/translations/es.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "ID de la cuenta"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/hu.json
Normal file
20
homeassistant/components/adax/translations/hu.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
homeassistant/components/adax/translations/no.json
Normal file
20
homeassistant/components/adax/translations/no.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
homeassistant/components/adax/translations/zh-Hans.json
Normal file
14
homeassistant/components/adax/translations/zh-Hans.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "\u5bc6\u7801"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -198,7 +198,7 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
|
||||
"""Return device information about this AdGuard Home instance."""
|
||||
return {
|
||||
"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",
|
||||
"manufacturer": "AdGuard Team",
|
||||
|
@ -51,6 +51,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self, errors: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
"""Show the Hass.io confirmation form to the user."""
|
||||
assert self._hassio_discovery
|
||||
return self.async_show_form(
|
||||
step_id="hassio_confirm",
|
||||
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])
|
||||
|
||||
username: str | None = user_input.get(CONF_USERNAME)
|
||||
password: str | None = user_input.get(CONF_PASSWORD)
|
||||
adguard = AdGuardHome(
|
||||
user_input[CONF_HOST],
|
||||
port=user_input[CONF_PORT],
|
||||
username=user_input.get(CONF_USERNAME),
|
||||
password=user_input.get(CONF_PASSWORD),
|
||||
username=username, # type:ignore[arg-type]
|
||||
password=password, # type:ignore[arg-type]
|
||||
tls=user_input[CONF_SSL],
|
||||
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||
session=session,
|
||||
@ -122,6 +125,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
session = async_get_clientsession(self.hass, False)
|
||||
|
||||
assert self._hassio_discovery
|
||||
adguard = AdGuardHome(
|
||||
self._hassio_discovery[CONF_HOST],
|
||||
port=self._hassio_discovery[CONF_PORT],
|
||||
|
@ -62,7 +62,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
|
||||
enabled_default: bool = True,
|
||||
) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
self._state = None
|
||||
self._state: int | str | None = None
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self.measurement = measurement
|
||||
|
||||
@ -82,12 +82,12 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self) -> str | None:
|
||||
def native_value(self) -> int | str | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@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 self._unit_of_measurement
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
||||
@ -19,7 +20,8 @@
|
||||
"ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata",
|
||||
"username": "Felhaszn\u00e1l\u00f3n\u00e9v",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,23 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e",
|
||||
"existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "\u4e3b\u673a\u5730\u5740",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class AdsSensor(AdsEntity, SensorEntity):
|
||||
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor):
|
||||
"""Initialize AdsSensor entity."""
|
||||
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._factor = factor
|
||||
|
||||
@ -64,6 +64,6 @@ class AdsSensor(AdsEntity, SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self) -> StateType:
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the device."""
|
||||
return self._state_dict[STATE_KEY_STATE]
|
||||
|
@ -1,7 +1,11 @@
|
||||
"""Sensor platform for Advantage Air integration."""
|
||||
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.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):
|
||||
"""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):
|
||||
"""Initialize the Advantage Air timer control."""
|
||||
@ -58,7 +62,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the current value."""
|
||||
return self._ac[self._time_key]
|
||||
|
||||
@ -78,7 +82,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone Vent Sensor."""
|
||||
|
||||
_attr_unit_of_measurement = PERCENTAGE
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
@ -90,7 +94,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the current value of the air vent."""
|
||||
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
|
||||
return self._zone["value"]
|
||||
@ -107,19 +111,19 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
"""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
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""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_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the current value of the wireless signal."""
|
||||
return self._zone["rssi"]
|
||||
|
||||
@ -138,20 +142,22 @@ class AdvantageAirZoneSignal(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_icon = "mdi:thermometer"
|
||||
_attr_entity_registry_enabled_default = False
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Temp Sensor."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
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
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the current value of the measured temperature."""
|
||||
return self._zone["measuredTemp"]
|
||||
|
@ -2,6 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Con\u00e9ctese a la API de su tableta de pared Advantage Air.",
|
||||
"title": "Conectar"
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
|
||||
self._attr_name = f"{self._name} {self._sensor_name}"
|
||||
self._attr_unique_id = self._unique_id
|
||||
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):
|
||||
@ -106,7 +106,7 @@ class AemetSensor(AbstractAemetSensor):
|
||||
self._weather_coordinator = weather_coordinator
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the state of the device."""
|
||||
return self._weather_coordinator.data.get(self._sensor_type)
|
||||
|
||||
@ -134,7 +134,7 @@ class AemetForecastSensor(AbstractAemetSensor):
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the state of the device."""
|
||||
forecast = None
|
||||
forecasts = self._weather_coordinator.data.get(
|
||||
|
@ -5,8 +5,18 @@
|
||||
"data": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"station_updates": "Recopile datos de las estaciones meteorol\u00f3gicas de AEMET"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
"""Weather data coordinator for the AEMET OpenData service."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
@ -95,7 +97,7 @@ def format_condition(condition: str) -> str:
|
||||
return condition
|
||||
|
||||
|
||||
def format_float(value) -> float:
|
||||
def format_float(value) -> float | None:
|
||||
"""Try converting string to float."""
|
||||
try:
|
||||
return float(value)
|
||||
@ -103,7 +105,7 @@ def format_float(value) -> float:
|
||||
return None
|
||||
|
||||
|
||||
def format_int(value) -> int:
|
||||
def format_int(value) -> int | None:
|
||||
"""Try converting string to int."""
|
||||
try:
|
||||
return int(value)
|
||||
|
@ -109,7 +109,7 @@ async def async_setup_platform(
|
||||
class AfterShipSensor(SensorEntity):
|
||||
"""Representation of a AfterShip sensor."""
|
||||
|
||||
_attr_unit_of_measurement: str = "packages"
|
||||
_attr_native_unit_of_measurement: str = "packages"
|
||||
_attr_icon: str = ICON
|
||||
|
||||
def __init__(self, aftership: Tracking, name: str) -> None:
|
||||
@ -120,7 +120,7 @@ class AfterShipSensor(SensorEntity):
|
||||
self._attr_name = name
|
||||
|
||||
@property
|
||||
def state(self) -> int | None:
|
||||
def native_value(self) -> int | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
"data": {
|
||||
"host": "Hoszt",
|
||||
"port": "Port"
|
||||
}
|
||||
},
|
||||
"title": "\u00c1ll\u00edtsa be az Agent DVR-t"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e"
|
||||
},
|
||||
"error": {
|
||||
"already_in_progress": "\u914d\u7f6e\u6d41\u5df2\u8fdb\u884c\u4e2d",
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "\u4e3b\u673a\u5730\u5740",
|
||||
"port": "\u7aef\u53e3"
|
||||
},
|
||||
"title": "\u914d\u7f6e Agent DVR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,11 @@ from typing import Final
|
||||
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
DEVICE_CLASS_AQI,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PM1,
|
||||
DEVICE_CLASS_PM10,
|
||||
DEVICE_CLASS_PM25,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
PERCENTAGE,
|
||||
@ -49,35 +53,36 @@ NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
|
||||
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_CAQI,
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
name=ATTR_API_CAQI,
|
||||
unit_of_measurement="CAQI",
|
||||
native_unit_of_measurement="CAQI",
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM1,
|
||||
icon="mdi:blur",
|
||||
device_class=DEVICE_CLASS_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,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM25,
|
||||
icon="mdi:blur",
|
||||
device_class=DEVICE_CLASS_PM25,
|
||||
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,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
icon="mdi:blur",
|
||||
device_class=DEVICE_CLASS_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,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_HUMIDITY,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
name=ATTR_API_HUMIDITY.capitalize(),
|
||||
unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
value=lambda value: round(value, 1),
|
||||
),
|
||||
@ -85,14 +90,14 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
key=ATTR_API_PRESSURE,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
name=ATTR_API_PRESSURE.capitalize(),
|
||||
unit_of_measurement=PRESSURE_HPA,
|
||||
native_unit_of_measurement=PRESSURE_HPA,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_TEMPERATURE,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
name=ATTR_API_TEMPERATURE.capitalize(),
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
value=lambda value: round(value, 1),
|
||||
),
|
||||
|
@ -84,7 +84,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def state(self) -> StateType:
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state."""
|
||||
state = self.coordinator.data[self.entity_description.key]
|
||||
return cast(StateType, self.entity_description.value(state))
|
||||
|
@ -72,11 +72,11 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
|
||||
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
|
||||
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
|
||||
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()}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
"""Return the state."""
|
||||
self._state = self.coordinator.data[self.kind]
|
||||
return self._state
|
||||
|
81
homeassistant/components/airtouch4/__init__.py
Normal file
81
homeassistant/components/airtouch4/__init__.py
Normal 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()
|
||||
],
|
||||
}
|
335
homeassistant/components/airtouch4/climate.py
Normal file
335
homeassistant/components/airtouch4/climate.py
Normal 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()
|
50
homeassistant/components/airtouch4/config_flow.py
Normal file
50
homeassistant/components/airtouch4/config_flow.py
Normal 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],
|
||||
},
|
||||
)
|
3
homeassistant/components/airtouch4/const.py
Normal file
3
homeassistant/components/airtouch4/const.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""Constants for the AirTouch4 integration."""
|
||||
|
||||
DOMAIN = "airtouch4"
|
13
homeassistant/components/airtouch4/manifest.json
Normal file
13
homeassistant/components/airtouch4/manifest.json
Normal 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"
|
||||
}
|
19
homeassistant/components/airtouch4/strings.json
Normal file
19
homeassistant/components/airtouch4/strings.json
Normal 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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/ca.json
Normal file
19
homeassistant/components/airtouch4/translations/ca.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/airtouch4/translations/cs.json
Normal file
17
homeassistant/components/airtouch4/translations/cs.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/de.json
Normal file
19
homeassistant/components/airtouch4/translations/de.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/en.json
Normal file
19
homeassistant/components/airtouch4/translations/en.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/et.json
Normal file
19
homeassistant/components/airtouch4/translations/et.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/airtouch4/translations/he.json
Normal file
17
homeassistant/components/airtouch4/translations/he.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/hu.json
Normal file
19
homeassistant/components/airtouch4/translations/hu.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/it.json
Normal file
19
homeassistant/components/airtouch4/translations/it.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/nl.json
Normal file
19
homeassistant/components/airtouch4/translations/nl.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/no.json
Normal file
19
homeassistant/components/airtouch4/translations/no.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/pl.json
Normal file
19
homeassistant/components/airtouch4/translations/pl.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/ru.json
Normal file
19
homeassistant/components/airtouch4/translations/ru.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/airtouch4/translations/zh-Hant.json
Normal file
19
homeassistant/components/airtouch4/translations/zh-Hant.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
entity_registry,
|
||||
)
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@ -358,11 +359,14 @@ async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
class AirVisualEntity(CoordinatorEntity):
|
||||
"""Define a generic AirVisual entity."""
|
||||
|
||||
def __init__(self, coordinator: DataUpdateCoordinator) -> None:
|
||||
def __init__(
|
||||
self, coordinator: DataUpdateCoordinator, description: EntityDescription
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self.entity_description = description
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Support for AirVisual air quality sensors."""
|
||||
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.const import (
|
||||
ATTR_LATITUDE,
|
||||
@ -14,10 +14,15 @@ from homeassistant.const import (
|
||||
CONF_LONGITUDE,
|
||||
CONF_SHOW_ON_MAP,
|
||||
CONF_STATE,
|
||||
DEVICE_CLASS_AQI,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_CO2,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PM1,
|
||||
DEVICE_CLASS_PM10,
|
||||
DEVICE_CLASS_PM25,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
PERCENTAGE,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
@ -59,60 +64,84 @@ SENSOR_KIND_SENSOR_LIFE = "sensor_life"
|
||||
SENSOR_KIND_TEMPERATURE = "temperature"
|
||||
SENSOR_KIND_VOC = "voc"
|
||||
|
||||
GEOGRAPHY_SENSORS = [
|
||||
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
||||
(SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"),
|
||||
(SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None),
|
||||
]
|
||||
GEOGRAPHY_SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_LEVEL,
|
||||
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."}
|
||||
|
||||
NODE_PRO_SENSORS = [
|
||||
(SENSOR_KIND_AQI, "Air Quality Index", None, "mdi:chart-line", "AQI"),
|
||||
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, None, PERCENTAGE),
|
||||
(
|
||||
SENSOR_KIND_CO2,
|
||||
"C02",
|
||||
DEVICE_CLASS_CO2,
|
||||
None,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
NODE_PRO_SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_AQI,
|
||||
name="Air Quality Index",
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
native_unit_of_measurement="AQI",
|
||||
),
|
||||
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, None, PERCENTAGE),
|
||||
(
|
||||
SENSOR_KIND_PM_0_1,
|
||||
"PM 0.1",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_BATTERY_LEVEL,
|
||||
name="Battery",
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_PM_1_0,
|
||||
"PM 1.0",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_CO2,
|
||||
name="C02",
|
||||
device_class=DEVICE_CLASS_CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_PM_2_5,
|
||||
"PM 2.5",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_HUMIDITY,
|
||||
name="Humidity",
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_TEMPERATURE,
|
||||
"Temperature",
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
None,
|
||||
TEMP_CELSIUS,
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_PM_0_1,
|
||||
name="PM 0.1",
|
||||
device_class=DEVICE_CLASS_PM1,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
(
|
||||
SENSOR_KIND_VOC,
|
||||
"VOC",
|
||||
None,
|
||||
"mdi:sprinkler",
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_PM_1_0,
|
||||
name="PM 1.0",
|
||||
device_class=DEVICE_CLASS_PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
]
|
||||
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_N2 = "n2"
|
||||
@ -156,27 +185,19 @@ async def async_setup_entry(
|
||||
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||
|
||||
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_NAME,
|
||||
]:
|
||||
):
|
||||
sensors = [
|
||||
AirVisualGeographySensor(
|
||||
coordinator,
|
||||
config_entry,
|
||||
kind,
|
||||
name,
|
||||
icon,
|
||||
unit,
|
||||
locale,
|
||||
)
|
||||
AirVisualGeographySensor(coordinator, config_entry, description, locale)
|
||||
for locale in GEOGRAPHY_SENSOR_LOCALES
|
||||
for kind, name, icon, unit in GEOGRAPHY_SENSORS
|
||||
for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
|
||||
]
|
||||
else:
|
||||
sensors = [
|
||||
AirVisualNodeProSensor(coordinator, kind, name, device_class, icon, unit)
|
||||
for kind, name, device_class, icon, unit in NODE_PRO_SENSORS
|
||||
AirVisualNodeProSensor(coordinator, description)
|
||||
for description in NODE_PRO_SENSOR_DESCRIPTIONS
|
||||
]
|
||||
|
||||
async_add_entities(sensors, True)
|
||||
@ -189,19 +210,12 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
self,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
config_entry: ConfigEntry,
|
||||
kind: str,
|
||||
name: str,
|
||||
icon: str,
|
||||
unit: str | None,
|
||||
description: SensorEntityDescription,
|
||||
locale: str,
|
||||
) -> None:
|
||||
"""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(
|
||||
{
|
||||
ATTR_CITY: config_entry.data.get(CONF_CITY),
|
||||
@ -209,12 +223,9 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
|
||||
}
|
||||
)
|
||||
self._attr_icon = icon
|
||||
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {name}"
|
||||
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{kind}"
|
||||
self._attr_unit_of_measurement = unit
|
||||
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {description.name}"
|
||||
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{description.key}"
|
||||
self._config_entry = config_entry
|
||||
self._kind = kind
|
||||
self._locale = locale
|
||||
|
||||
@property
|
||||
@ -230,18 +241,18 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
if self._kind == SENSOR_KIND_LEVEL:
|
||||
if self.entity_description.key == SENSOR_KIND_LEVEL:
|
||||
aqi = data[f"aqi{self._locale}"]
|
||||
[(self._attr_state, self._attr_icon)] = [
|
||||
[(self._attr_native_value, self._attr_icon)] = [
|
||||
(name, icon)
|
||||
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
|
||||
if floor <= aqi <= ceiling
|
||||
]
|
||||
elif self._kind == SENSOR_KIND_AQI:
|
||||
self._attr_state = data[f"aqi{self._locale}"]
|
||||
elif self._kind == SENSOR_KIND_POLLUTANT:
|
||||
elif self.entity_description.key == SENSOR_KIND_AQI:
|
||||
self._attr_native_value = data[f"aqi{self._locale}"]
|
||||
elif self.entity_description.key == SENSOR_KIND_POLLUTANT:
|
||||
symbol = data[f"main{self._locale}"]
|
||||
self._attr_state = symbol
|
||||
self._attr_native_value = symbol
|
||||
self._attr_extra_state_attributes.update(
|
||||
{
|
||||
ATTR_POLLUTANT_SYMBOL: symbol,
|
||||
@ -281,25 +292,15 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
kind: str,
|
||||
name: str,
|
||||
device_class: str | None,
|
||||
icon: str | None,
|
||||
unit: str,
|
||||
self, coordinator: DataUpdateCoordinator, description: SensorEntityDescription
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
super().__init__(coordinator, description)
|
||||
|
||||
self._attr_device_class = device_class
|
||||
self._attr_icon = icon
|
||||
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_unit_of_measurement = unit
|
||||
self._kind = kind
|
||||
self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
@ -318,26 +319,32 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||
@callback
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""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"]:
|
||||
self._attr_state = self.coordinator.data["measurements"]["aqi_us"]
|
||||
self._attr_native_value = self.coordinator.data["measurements"][
|
||||
"aqi_us"
|
||||
]
|
||||
else:
|
||||
self._attr_state = self.coordinator.data["measurements"]["aqi_cn"]
|
||||
elif self._kind == SENSOR_KIND_BATTERY_LEVEL:
|
||||
self._attr_state = self.coordinator.data["status"]["battery"]
|
||||
elif self._kind == SENSOR_KIND_CO2:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("co2")
|
||||
elif self._kind == SENSOR_KIND_HUMIDITY:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("humidity")
|
||||
elif self._kind == SENSOR_KIND_PM_0_1:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("pm0_1")
|
||||
elif self._kind == SENSOR_KIND_PM_1_0:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("pm1_0")
|
||||
elif self._kind == SENSOR_KIND_PM_2_5:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("pm2_5")
|
||||
elif self._kind == SENSOR_KIND_TEMPERATURE:
|
||||
self._attr_state = self.coordinator.data["measurements"].get(
|
||||
self._attr_native_value = self.coordinator.data["measurements"][
|
||||
"aqi_cn"
|
||||
]
|
||||
elif self.entity_description.key == SENSOR_KIND_BATTERY_LEVEL:
|
||||
self._attr_native_value = self.coordinator.data["status"]["battery"]
|
||||
elif self.entity_description.key == SENSOR_KIND_CO2:
|
||||
self._attr_native_value = self.coordinator.data["measurements"].get("co2")
|
||||
elif self.entity_description.key == SENSOR_KIND_HUMIDITY:
|
||||
self._attr_native_value = self.coordinator.data["measurements"].get(
|
||||
"humidity"
|
||||
)
|
||||
elif self.entity_description.key == SENSOR_KIND_PM_0_1:
|
||||
self._attr_native_value = self.coordinator.data["measurements"].get("pm0_1")
|
||||
elif self.entity_description.key == SENSOR_KIND_PM_1_0:
|
||||
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"
|
||||
)
|
||||
elif self._kind == SENSOR_KIND_VOC:
|
||||
self._attr_state = self.coordinator.data["measurements"].get("voc")
|
||||
elif self.entity_description.key == SENSOR_KIND_VOC:
|
||||
self._attr_native_value = self.coordinator.data["measurements"].get("voc")
|
||||
|
@ -13,6 +13,15 @@
|
||||
"description": "Utilice la API en la nube de AirVisual para monitorear una latitud / longitud.",
|
||||
"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": {
|
||||
"data": {
|
||||
"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.",
|
||||
"title": "Configurar un AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "Vuelva a autenticar AirVisual"
|
||||
},
|
||||
"user": {
|
||||
"description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
|
||||
"title": "Configurar AirVisual"
|
||||
|
@ -34,13 +34,29 @@
|
||||
"data": {
|
||||
"ip_address": "Hoszt",
|
||||
"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": {
|
||||
"data": {
|
||||
"api_key": "API kulcs"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,20 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Karbonmonoksid",
|
||||
"n2": "Nitrogendioksid",
|
||||
"o3": "Ozon",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
"""Provides device automations for Alarm control panel."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
from typing import Any, Final
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -55,7 +55,7 @@ TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||
|
||||
async def async_get_triggers(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> list[dict[str, str]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""List device triggers for Alarm control panel devices."""
|
||||
registry = await entity_registry.async_get_registry(hass)
|
||||
triggers: list[dict[str, str]] = []
|
||||
|
@ -4,6 +4,7 @@
|
||||
"arm_away": "Aktivovat {entity_name} v re\u017eimu nep\u0159\u00edtomnost",
|
||||
"arm_home": "Aktivovat {entity_name} v re\u017eimu domov",
|
||||
"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}",
|
||||
"trigger": "Spustit {entity_name}"
|
||||
},
|
||||
@ -11,6 +12,7 @@
|
||||
"is_armed_away": "{entity_name} je v re\u017eimu nep\u0159\u00edtomnost",
|
||||
"is_armed_home": "{entity_name} je v re\u017eimu domov",
|
||||
"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_triggered": "{entity_name} je spu\u0161t\u011bn"
|
||||
},
|
||||
@ -18,6 +20,7 @@
|
||||
"armed_away": "{entity_name} v re\u017eimu nep\u0159\u00edtomnost",
|
||||
"armed_home": "{entity_name} v re\u017eimu domov",
|
||||
"armed_night": "{entity_name} v no\u010dn\u00edm re\u017eimu",
|
||||
"armed_vacation": "{entity_name} v re\u017eimu dovolen\u00e1",
|
||||
"disarmed": "{entity_name} nezabezpe\u010den",
|
||||
"triggered": "{entity_name} spu\u0161t\u011bn"
|
||||
}
|
||||
@ -29,6 +32,7 @@
|
||||
"armed_custom_bypass": "Zabezpe\u010deno u\u017eivatelsk\u00fdm obejit\u00edm",
|
||||
"armed_home": "Re\u017eim domov",
|
||||
"armed_night": "No\u010dn\u00ed re\u017eim",
|
||||
"armed_vacation": "V re\u017eimu dovolen\u00e1",
|
||||
"arming": "Zabezpe\u010dov\u00e1n\u00ed",
|
||||
"disarmed": "Nezabezpe\u010deno",
|
||||
"disarming": "Odbezpe\u010dov\u00e1n\u00ed",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"is_armed_away": "{entity_name} est arm\u00e9",
|
||||
"is_armed_home": "{entity_name} est arm\u00e9 \u00e0 la maison",
|
||||
"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_triggered": "{entity_name} est d\u00e9clench\u00e9"
|
||||
},
|
||||
|
@ -9,7 +9,12 @@
|
||||
"trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa"
|
||||
},
|
||||
"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": {
|
||||
"armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve",
|
||||
|
@ -1,43 +1,43 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Inschakelen {entity_name} afwezig",
|
||||
"arm_home": "Inschakelen {entity_name} thuis",
|
||||
"arm_night": "Inschakelen {entity_name} nacht",
|
||||
"arm_away": "Schakel {entity_name} in voor vertrek",
|
||||
"arm_home": "Schakel {entity_name} in voor thuis",
|
||||
"arm_night": "Schakel {entity_name} in voor 's nachts",
|
||||
"arm_vacation": "Schakel {entity_name} in op vakantie",
|
||||
"disarm": "Uitschakelen {entity_name}",
|
||||
"trigger": "Trigger {entity_name}"
|
||||
"disarm": "Schakel {entity_name} uit",
|
||||
"trigger": "Laat {entity_name} afgaan"
|
||||
},
|
||||
"condition_type": {
|
||||
"is_armed_away": "{entity_name} afwezig ingeschakeld",
|
||||
"is_armed_home": "{entity_name} thuis ingeschakeld",
|
||||
"is_armed_night": "{entity_name} nachtstand ingeschakeld",
|
||||
"is_armed_away": "{entity_name} ingeschakeld voor vertrek",
|
||||
"is_armed_home": "{entity_name} ingeschakeld voor thuis",
|
||||
"is_armed_night": "{entity_name} is ingeschakeld voor 's nachts",
|
||||
"is_armed_vacation": "{entity_name} is in vakantie geschakeld",
|
||||
"is_disarmed": "{entity_name} is uitgeschakeld",
|
||||
"is_triggered": "{entity_name} wordt geactiveerd"
|
||||
"is_triggered": "{entity_name} gaat af"
|
||||
},
|
||||
"trigger_type": {
|
||||
"armed_away": "{entity_name} afwezig ingeschakeld",
|
||||
"armed_home": "{entity_name} thuis ingeschakeld",
|
||||
"armed_night": "{entity_name} nachtstand ingeschakeld",
|
||||
"armed_away": "{entity_name} ingeschakeld voor vertrek",
|
||||
"armed_home": "{entity_name} ingeschakeld voor thuis",
|
||||
"armed_night": "{entity_name} ingeschakeld voor 's nachts",
|
||||
"armed_vacation": "{entity_name} schakelde vakantie in",
|
||||
"disarmed": "{entity_name} uitgeschakeld",
|
||||
"triggered": "{entity_name} geactiveerd"
|
||||
"triggered": "{entity_name} afgegaan"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"_": {
|
||||
"armed": "Ingeschakeld",
|
||||
"armed_away": "Ingeschakeld afwezig",
|
||||
"armed_custom_bypass": "Ingeschakeld met overbrugging(en)",
|
||||
"armed_home": "Ingeschakeld thuis",
|
||||
"armed_night": "Ingeschakeld nacht",
|
||||
"armed_away": "Ingeschakeld voor vertrek",
|
||||
"armed_custom_bypass": "Ingeschakeld met overbrugging",
|
||||
"armed_home": "Ingeschakeld voor thuis",
|
||||
"armed_night": "Ingeschakeld voor 's nachts",
|
||||
"armed_vacation": "Vakantie ingeschakeld",
|
||||
"arming": "Schakelt in",
|
||||
"disarmed": "Uitgeschakeld",
|
||||
"disarming": "Schakelt uit",
|
||||
"pending": "In wacht",
|
||||
"triggered": "Geactiveerd"
|
||||
"triggered": "Gaat af"
|
||||
}
|
||||
},
|
||||
"title": "Alarm bedieningspaneel"
|
||||
|
@ -4,6 +4,7 @@
|
||||
"arm_away": "Aktiver {entity_name} borte",
|
||||
"arm_home": "Aktiver {entity_name} hjemme",
|
||||
"arm_night": "Aktiver {entity_name} natt",
|
||||
"arm_vacation": "{entity_name} ferie",
|
||||
"disarm": "Deaktiver {entity_name}",
|
||||
"trigger": "Utl\u00f8ser {entity_name}"
|
||||
},
|
||||
@ -11,6 +12,7 @@
|
||||
"is_armed_away": "{entity_name} er aktivert borte",
|
||||
"is_armed_home": "{entity_name} er aktivert hjemme",
|
||||
"is_armed_night": "{entity_name} er aktivert natt",
|
||||
"is_armed_vacation": "{entity_name} er armert ferie",
|
||||
"is_disarmed": "{entity_name} er deaktivert",
|
||||
"is_triggered": "{entity_name} er utl\u00f8st"
|
||||
},
|
||||
@ -18,6 +20,7 @@
|
||||
"armed_away": "{entity_name} aktivert borte",
|
||||
"armed_home": "{entity_name} aktivert hjemme",
|
||||
"armed_night": "{entity_name} aktivert natt",
|
||||
"armed_vacation": "{entity_name} armert ferie",
|
||||
"disarmed": "{entity_name} deaktivert",
|
||||
"triggered": "{entity_name} utl\u00f8st"
|
||||
}
|
||||
@ -29,6 +32,7 @@
|
||||
"armed_custom_bypass": "Armert tilpasset unntak",
|
||||
"armed_home": "Armert hjemme",
|
||||
"armed_night": "Armert natt",
|
||||
"armed_vacation": "Armert ferie",
|
||||
"arming": "Armerer",
|
||||
"disarmed": "Avsl\u00e5tt",
|
||||
"disarming": "Disarmer",
|
||||
|
@ -32,6 +32,6 @@ class AlarmDecoderSensor(SensorEntity):
|
||||
)
|
||||
|
||||
def _message_callback(self, message):
|
||||
if self._attr_state != message.text:
|
||||
self._attr_state = message.text
|
||||
if self._attr_native_value != message.text:
|
||||
self._attr_native_value = message.text
|
||||
self.schedule_update_ha_state()
|
||||
|
@ -20,6 +20,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"arm_settings": {
|
||||
"data": {
|
||||
@ -30,6 +34,17 @@
|
||||
"data": {
|
||||
"edit_select": "Editar"
|
||||
}
|
||||
},
|
||||
"zone_details": {
|
||||
"data": {
|
||||
"zone_name": "Nombre de zona",
|
||||
"zone_rfid": "Serie RF"
|
||||
}
|
||||
},
|
||||
"zone_select": {
|
||||
"data": {
|
||||
"zone_number": "N\u00famero de zona"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
"error": {
|
||||
"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_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."
|
||||
},
|
||||
"step": {
|
||||
@ -55,6 +56,7 @@
|
||||
"zone_name": "Z\u00f3na neve",
|
||||
"zone_relayaddr": "Rel\u00e9 c\u00edm",
|
||||
"zone_relaychan": "Rel\u00e9 csatorna",
|
||||
"zone_rfid": "RF soros",
|
||||
"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.",
|
||||
|
@ -32,7 +32,7 @@
|
||||
"int": "Het onderstaande veld moet een geheel getal zijn.",
|
||||
"loop_range": "RF Lus moet een geheel getal zijn tussen 1 en 4.",
|
||||
"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": {
|
||||
"arm_settings": {
|
||||
@ -53,18 +53,18 @@
|
||||
"zone_details": {
|
||||
"data": {
|
||||
"zone_loop": "RF Lus",
|
||||
"zone_name": "Zone naam",
|
||||
"zone_relayaddr": "Relais Adres",
|
||||
"zone_relaychan": "Relais Kanaal",
|
||||
"zone_name": "Zonenaam",
|
||||
"zone_relayaddr": "Relaisadres",
|
||||
"zone_relaychan": "Relaiskanaal",
|
||||
"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"
|
||||
},
|
||||
"zone_select": {
|
||||
"data": {
|
||||
"zone_number": "Zone nummer"
|
||||
"zone_number": "Zonenummer"
|
||||
},
|
||||
"description": "Voer het zone nummer in dat u wilt toevoegen, bewerken of verwijderen.",
|
||||
"title": "Configureer AlarmDecoder"
|
||||
|
@ -99,7 +99,7 @@ class AlexaCapability:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def properties_non_controllable() -> bool:
|
||||
def properties_non_controllable() -> bool | None:
|
||||
"""Return True if non controllable."""
|
||||
return None
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Alexa related errors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
namespace = None
|
||||
error_type = None
|
||||
namespace: str | None = None
|
||||
error_type: str | None = None
|
||||
|
||||
def __init__(self, error_message, payload=None):
|
||||
"""Initialize an alexa error."""
|
||||
|
@ -5,6 +5,7 @@ import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, cast
|
||||
|
||||
from aiohttp import ClientError, ClientSession
|
||||
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)
|
||||
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:
|
||||
data = {}
|
||||
@ -204,7 +205,7 @@ async def _configure_almond_for_ha(
|
||||
)
|
||||
except (asyncio.TimeoutError, ClientError) as err:
|
||||
if isinstance(err, asyncio.TimeoutError):
|
||||
msg = "Request timeout"
|
||||
msg: str | ClientError = "Request timeout"
|
||||
else:
|
||||
msg = err
|
||||
_LOGGER.warning("Unable to configure Almond: %s", msg)
|
||||
|
@ -1,6 +1,9 @@
|
||||
"""Config flow to connect with Home Assistant."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
import async_timeout
|
||||
@ -9,6 +12,7 @@ import voluptuous as vol
|
||||
from yarl import URL
|
||||
|
||||
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 .const import DOMAIN as ALMOND_DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
|
||||
@ -64,7 +68,7 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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."""
|
||||
# Only allow 1 instance.
|
||||
if self._async_current_entries():
|
||||
|
@ -112,7 +112,7 @@ class AlphaVantageSensor(SensorEntity):
|
||||
self._symbol = symbol[CONF_SYMBOL]
|
||||
self._attr_name = symbol.get(CONF_NAME, self._symbol)
|
||||
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"))
|
||||
|
||||
def update(self):
|
||||
@ -120,7 +120,7 @@ class AlphaVantageSensor(SensorEntity):
|
||||
_LOGGER.debug("Requesting new data for symbol %s", self._symbol)
|
||||
all_values, _ = self._timeseries.get_intraday(self._symbol)
|
||||
values = next(iter(all_values.values()))
|
||||
self._attr_state = values["1. open"]
|
||||
self._attr_native_value = values["1. open"]
|
||||
self._attr_extra_state_attributes = (
|
||||
{
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
@ -148,7 +148,7 @@ class AlphaVantageForeignExchange(SensorEntity):
|
||||
else f"{self._to_currency}/{self._from_currency}"
|
||||
)
|
||||
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):
|
||||
"""Get the latest data and updates the states."""
|
||||
@ -160,7 +160,7 @@ class AlphaVantageForeignExchange(SensorEntity):
|
||||
values, _ = self._foreign_exchange.get_currency_exchange_rate(
|
||||
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 = (
|
||||
{
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
|
@ -39,38 +39,38 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
SensorEntityDescription(
|
||||
key="particulate_matter_2_5",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="particulate_matter_10",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="sulphur_dioxide",
|
||||
name="Sulphur Dioxide (SO2)",
|
||||
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="nitrogen_dioxide",
|
||||
name="Nitrogen Dioxide (NO2)",
|
||||
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="ozone",
|
||||
name="Ozone",
|
||||
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="carbon_monoxide",
|
||||
name="Carbon Monoxide (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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -85,21 +85,21 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Grass Pollen",
|
||||
icon="mdi:grass",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree",
|
||||
name="Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed",
|
||||
name="Weed Pollen",
|
||||
icon="mdi:sprout",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="grass_risk",
|
||||
@ -124,7 +124,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Poaceae Grass Pollen",
|
||||
icon="mdi:grass",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -132,7 +132,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Alder Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -140,7 +140,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Birch Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -148,7 +148,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Cypress Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -156,7 +156,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Elm Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -164,7 +164,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Hazel Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -172,7 +172,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Oak Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -180,7 +180,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Pine Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -188,7 +188,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Plane Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -196,7 +196,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Poplar Tree Pollen",
|
||||
icon="mdi:tree",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -204,7 +204,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Chenopod Weed Pollen",
|
||||
icon="mdi:sprout",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -212,7 +212,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Mugwort Weed Pollen",
|
||||
icon="mdi:sprout",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -220,7 +220,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Nettle Weed Pollen",
|
||||
icon="mdi:sprout",
|
||||
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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
@ -228,7 +228,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
name="Ragweed Weed Pollen",
|
||||
icon="mdi:sprout",
|
||||
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,
|
||||
),
|
||||
],
|
||||
|
@ -66,7 +66,7 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
|
||||
}
|
||||
|
||||
@property
|
||||
def state(self) -> StateType:
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
value = getattr(self.coordinator.data, self.entity_description.key)
|
||||
if isinstance(value, str):
|
||||
|
@ -21,7 +21,7 @@
|
||||
"longitude": "Longitud",
|
||||
"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
Loading…
x
Reference in New Issue
Block a user