Merge pull request #41406 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2020-10-07 17:50:57 +02:00 committed by GitHub
commit 55958bcfb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1625 changed files with 30967 additions and 8947 deletions

View File

@ -33,7 +33,11 @@ omit =
homeassistant/components/airvisual/air_quality.py homeassistant/components/airvisual/air_quality.py
homeassistant/components/airvisual/sensor.py homeassistant/components/airvisual/sensor.py
homeassistant/components/aladdin_connect/cover.py homeassistant/components/aladdin_connect/cover.py
homeassistant/components/alarmdecoder/* homeassistant/components/alarmdecoder/__init__.py
homeassistant/components/alarmdecoder/alarm_control_panel.py
homeassistant/components/alarmdecoder/binary_sensor.py
homeassistant/components/alarmdecoder/const.py
homeassistant/components/alarmdecoder/sensor.py
homeassistant/components/alpha_vantage/sensor.py homeassistant/components/alpha_vantage/sensor.py
homeassistant/components/amazon_polly/tts.py homeassistant/components/amazon_polly/tts.py
homeassistant/components/ambiclimate/climate.py homeassistant/components/ambiclimate/climate.py
@ -117,7 +121,6 @@ omit =
homeassistant/components/buienradar/util.py homeassistant/components/buienradar/util.py
homeassistant/components/buienradar/weather.py homeassistant/components/buienradar/weather.py
homeassistant/components/caldav/calendar.py homeassistant/components/caldav/calendar.py
homeassistant/components/canary/alarm_control_panel.py
homeassistant/components/canary/camera.py homeassistant/components/canary/camera.py
homeassistant/components/cast/* homeassistant/components/cast/*
homeassistant/components/cert_expiry/helper.py homeassistant/components/cert_expiry/helper.py
@ -266,7 +269,9 @@ omit =
homeassistant/components/firmata/board.py homeassistant/components/firmata/board.py
homeassistant/components/firmata/const.py homeassistant/components/firmata/const.py
homeassistant/components/firmata/entity.py homeassistant/components/firmata/entity.py
homeassistant/components/firmata/light.py
homeassistant/components/firmata/pin.py homeassistant/components/firmata/pin.py
homeassistant/components/firmata/sensor.py
homeassistant/components/firmata/switch.py homeassistant/components/firmata/switch.py
homeassistant/components/fitbit/sensor.py homeassistant/components/fitbit/sensor.py
homeassistant/components/fixer/sensor.py homeassistant/components/fixer/sensor.py
@ -315,6 +320,8 @@ omit =
homeassistant/components/glances/sensor.py homeassistant/components/glances/sensor.py
homeassistant/components/gntp/notify.py homeassistant/components/gntp/notify.py
homeassistant/components/goalfeed/* homeassistant/components/goalfeed/*
homeassistant/components/goalzero/__init__.py
homeassistant/components/goalzero/binary_sensor.py
homeassistant/components/google/* homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py homeassistant/components/google_maps/device_tracker.py
@ -369,6 +376,7 @@ omit =
homeassistant/components/hunterdouglas_powerview/sensor.py homeassistant/components/hunterdouglas_powerview/sensor.py
homeassistant/components/hunterdouglas_powerview/cover.py homeassistant/components/hunterdouglas_powerview/cover.py
homeassistant/components/hunterdouglas_powerview/entity.py homeassistant/components/hunterdouglas_powerview/entity.py
homeassistant/components/hvv_departures/binary_sensor.py
homeassistant/components/hvv_departures/sensor.py homeassistant/components/hvv_departures/sensor.py
homeassistant/components/hvv_departures/__init__.py homeassistant/components/hvv_departures/__init__.py
homeassistant/components/hydrawise/* homeassistant/components/hydrawise/*
@ -478,7 +486,8 @@ omit =
homeassistant/components/london_underground/sensor.py homeassistant/components/london_underground/sensor.py
homeassistant/components/loopenergy/sensor.py homeassistant/components/loopenergy/sensor.py
homeassistant/components/luci/device_tracker.py homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/* homeassistant/components/luftdaten/__init__.py
homeassistant/components/luftdaten/sensor.py
homeassistant/components/lupusec/* homeassistant/components/lupusec/*
homeassistant/components/lutron/* homeassistant/components/lutron/*
homeassistant/components/lutron_caseta/__init__.py homeassistant/components/lutron_caseta/__init__.py
@ -530,7 +539,9 @@ omit =
homeassistant/components/mjpeg/camera.py homeassistant/components/mjpeg/camera.py
homeassistant/components/mobile_app/* homeassistant/components/mobile_app/*
homeassistant/components/mochad/* homeassistant/components/mochad/*
homeassistant/components/modbus/* homeassistant/components/modbus/climate.py
homeassistant/components/modbus/cover.py
homeassistant/components/modbus/switch.py
homeassistant/components/modem_callerid/sensor.py homeassistant/components/modem_callerid/sensor.py
homeassistant/components/mpchc/media_player.py homeassistant/components/mpchc/media_player.py
homeassistant/components/mpd/media_player.py homeassistant/components/mpd/media_player.py
@ -595,6 +606,10 @@ omit =
homeassistant/components/oasa_telematics/sensor.py homeassistant/components/oasa_telematics/sensor.py
homeassistant/components/ohmconnect/sensor.py homeassistant/components/ohmconnect/sensor.py
homeassistant/components/ombi/* homeassistant/components/ombi/*
homeassistant/components/omnilogic/__init__.py
homeassistant/components/omnilogic/common.py
homeassistant/components/omnilogic/sensor.py
homeassistant/components/onewire/const.py
homeassistant/components/onewire/sensor.py homeassistant/components/onewire/sensor.py
homeassistant/components/onkyo/media_player.py homeassistant/components/onkyo/media_player.py
homeassistant/components/onvif/__init__.py homeassistant/components/onvif/__init__.py
@ -803,6 +818,7 @@ omit =
homeassistant/components/spc/* homeassistant/components/spc/*
homeassistant/components/speedtestdotnet/* homeassistant/components/speedtestdotnet/*
homeassistant/components/spider/* homeassistant/components/spider/*
homeassistant/components/splunk/*
homeassistant/components/spotcrime/sensor.py homeassistant/components/spotcrime/sensor.py
homeassistant/components/spotify/__init__.py homeassistant/components/spotify/__init__.py
homeassistant/components/spotify/media_player.py homeassistant/components/spotify/media_player.py
@ -830,7 +846,9 @@ omit =
homeassistant/components/synology_chat/notify.py homeassistant/components/synology_chat/notify.py
homeassistant/components/synology_dsm/__init__.py homeassistant/components/synology_dsm/__init__.py
homeassistant/components/synology_dsm/binary_sensor.py homeassistant/components/synology_dsm/binary_sensor.py
homeassistant/components/synology_dsm/camera.py
homeassistant/components/synology_dsm/sensor.py homeassistant/components/synology_dsm/sensor.py
homeassistant/components/synology_dsm/switch.py
homeassistant/components/synology_srm/device_tracker.py homeassistant/components/synology_srm/device_tracker.py
homeassistant/components/syslog/notify.py homeassistant/components/syslog/notify.py
homeassistant/components/systemmonitor/sensor.py homeassistant/components/systemmonitor/sensor.py
@ -844,7 +862,13 @@ omit =
homeassistant/components/ted5000/sensor.py homeassistant/components/ted5000/sensor.py
homeassistant/components/telegram/notify.py homeassistant/components/telegram/notify.py
homeassistant/components/telegram_bot/* homeassistant/components/telegram_bot/*
homeassistant/components/tellduslive/* homeassistant/components/tellduslive/__init__.py
homeassistant/components/tellduslive/binary_sensor.py
homeassistant/components/tellduslive/cover.py
homeassistant/components/tellduslive/entry.py
homeassistant/components/tellduslive/light.py
homeassistant/components/tellduslive/sensor.py
homeassistant/components/tellduslive/switch.py
homeassistant/components/tellstick/* homeassistant/components/tellstick/*
homeassistant/components/telnet/switch.py homeassistant/components/telnet/switch.py
homeassistant/components/temper/sensor.py homeassistant/components/temper/sensor.py
@ -863,7 +887,9 @@ omit =
homeassistant/components/thingspeak/* homeassistant/components/thingspeak/*
homeassistant/components/thinkingcleaner/* homeassistant/components/thinkingcleaner/*
homeassistant/components/thomson/device_tracker.py homeassistant/components/thomson/device_tracker.py
homeassistant/components/tibber/* homeassistant/components/tibber/__init__.py
homeassistant/components/tibber/notify.py
homeassistant/components/tibber/sensor.py
homeassistant/components/tikteck/light.py homeassistant/components/tikteck/light.py
homeassistant/components/tile/__init__.py homeassistant/components/tile/__init__.py
homeassistant/components/tile/device_tracker.py homeassistant/components/tile/device_tracker.py

View File

@ -518,25 +518,24 @@ jobs:
hassfest: hassfest:
name: Check hassfest name: Check hassfest
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-base needs: prepare-tests
strategy:
matrix:
python-version: [3.7]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name:
uses: actions/setup-python@v2.1.2 Restore full Python ${{ matrix.python-version }} virtual environment
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: venv path: venv
key: >- key: >-
${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{
steps.python.outputs.python-version }}-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt')
hashFiles('requirements.txt') }}-${{ }}-${{ hashFiles('requirements_all.txt') }}-${{
hashFiles('requirements_test.txt') }}-${{
hashFiles('homeassistant/package_constraints.txt') }} hashFiles('homeassistant/package_constraints.txt') }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
@ -546,7 +545,7 @@ jobs:
- name: Run hassfest - name: Run hassfest
run: | run: |
. venv/bin/activate . venv/bin/activate
python -m script.hassfest --action validate python -m script.hassfest --requirements --action validate
gen-requirements-all: gen-requirements-all:
name: Check all requirements name: Check all requirements

View File

@ -39,7 +39,7 @@ repos:
- --configfile=tests/bandit.yaml - --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.5.1 rev: 5.5.3
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks

View File

@ -157,6 +157,7 @@ homeassistant/components/geonetnz_volcano/* @exxamalte
homeassistant/components/gios/* @bieniu homeassistant/components/gios/* @bieniu
homeassistant/components/gitter/* @fabaff homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/goalzero/* @tkdrob
homeassistant/components/gogogate2/* @vangorra homeassistant/components/gogogate2/* @vangorra
homeassistant/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_assistant/* @home-assistant/cloud
homeassistant/components/google_cloud/* @lufton homeassistant/components/google_cloud/* @lufton
@ -169,6 +170,7 @@ homeassistant/components/growatt_server/* @indykoning
homeassistant/components/guardian/* @bachya homeassistant/components/guardian/* @bachya
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco
homeassistant/components/hassio/* @home-assistant/hass-io homeassistant/components/hassio/* @home-assistant/hass-io
homeassistant/components/hdmi_cec/* @newAM
homeassistant/components/heatmiser/* @andylockran homeassistant/components/heatmiser/* @andylockran
homeassistant/components/heos/* @andrewsayre homeassistant/components/heos/* @andrewsayre
homeassistant/components/here_travel_time/* @eifinger homeassistant/components/here_travel_time/* @eifinger
@ -193,6 +195,7 @@ homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
homeassistant/components/hunterdouglas_powerview/* @bdraco homeassistant/components/hunterdouglas_powerview/* @bdraco
homeassistant/components/hvv_departures/* @vigonotion homeassistant/components/hvv_departures/* @vigonotion
homeassistant/components/hydrawise/* @ptcryan homeassistant/components/hydrawise/* @ptcryan
homeassistant/components/hyperion/* @dermotduffy
homeassistant/components/iammeter/* @lewei50 homeassistant/components/iammeter/* @lewei50
homeassistant/components/iaqualink/* @flz homeassistant/components/iaqualink/* @flz
homeassistant/components/icloud/* @Quentame homeassistant/components/icloud/* @Quentame
@ -226,7 +229,7 @@ homeassistant/components/keenetic_ndms2/* @foxel
homeassistant/components/kef/* @basnijholt homeassistant/components/kef/* @basnijholt
homeassistant/components/keyboard_remote/* @bendavid homeassistant/components/keyboard_remote/* @bendavid
homeassistant/components/knx/* @Julius2342 @farmio @marvin-w homeassistant/components/knx/* @Julius2342 @farmio @marvin-w
homeassistant/components/kodi/* @OnFreund homeassistant/components/kodi/* @OnFreund @cgtobi
homeassistant/components/konnected/* @heythisisnate @kit-klein homeassistant/components/konnected/* @heythisisnate @kit-klein
homeassistant/components/lametric/* @robbiet480 homeassistant/components/lametric/* @robbiet480
homeassistant/components/launch_library/* @ludeeus homeassistant/components/launch_library/* @ludeeus
@ -238,7 +241,7 @@ homeassistant/components/logger/* @home-assistant/core
homeassistant/components/logi_circle/* @evanjd homeassistant/components/logi_circle/* @evanjd
homeassistant/components/loopenergy/* @pavoni homeassistant/components/loopenergy/* @pavoni
homeassistant/components/lovelace/* @home-assistant/frontend homeassistant/components/lovelace/* @home-assistant/frontend
homeassistant/components/luci/* @fbradyirl @mzdrale homeassistant/components/luci/* @mzdrale
homeassistant/components/luftdaten/* @fabaff homeassistant/components/luftdaten/* @fabaff
homeassistant/components/lupusec/* @majuss homeassistant/components/lupusec/* @majuss
homeassistant/components/lutron/* @JonGilmore homeassistant/components/lutron/* @JonGilmore
@ -261,7 +264,7 @@ homeassistant/components/min_max/* @fabaff
homeassistant/components/minecraft_server/* @elmurato homeassistant/components/minecraft_server/* @elmurato
homeassistant/components/minio/* @tkislan homeassistant/components/minio/* @tkislan
homeassistant/components/mobile_app/* @robbiet480 homeassistant/components/mobile_app/* @robbiet480
homeassistant/components/modbus/* @adamchengtkc @janiversen homeassistant/components/modbus/* @adamchengtkc @janiversen @vzahradnik
homeassistant/components/monoprice/* @etsinko @OnFreund homeassistant/components/monoprice/* @etsinko @OnFreund
homeassistant/components/moon/* @fabaff homeassistant/components/moon/* @fabaff
homeassistant/components/mpd/* @fabaff homeassistant/components/mpd/* @fabaff
@ -300,6 +303,7 @@ homeassistant/components/nzbget/* @chriscla
homeassistant/components/obihai/* @dshokouhi homeassistant/components/obihai/* @dshokouhi
homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/ombi/* @larssont homeassistant/components/ombi/* @larssont
homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
homeassistant/components/onboarding/* @home-assistant/core homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/onewire/* @garbled1 homeassistant/components/onewire/* @garbled1
homeassistant/components/onvif/* @hunterjm homeassistant/components/onvif/* @hunterjm
@ -349,6 +353,7 @@ homeassistant/components/raincloud/* @vanstinator
homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert
homeassistant/components/rainmachine/* @bachya homeassistant/components/rainmachine/* @bachya
homeassistant/components/random/* @fabaff homeassistant/components/random/* @fabaff
homeassistant/components/rejseplanen/* @DarkFox
homeassistant/components/repetier/* @MTrab homeassistant/components/repetier/* @MTrab
homeassistant/components/rfxtrx/* @danielhiversen @elupus homeassistant/components/rfxtrx/* @danielhiversen @elupus
homeassistant/components/ring/* @balloob homeassistant/components/ring/* @balloob
@ -357,6 +362,7 @@ homeassistant/components/rmvtransport/* @cgtobi
homeassistant/components/roku/* @ctalkington homeassistant/components/roku/* @ctalkington
homeassistant/components/roomba/* @pschmitt @cyr-ius @shenxn homeassistant/components/roomba/* @pschmitt @cyr-ius @shenxn
homeassistant/components/roon/* @pavoni homeassistant/components/roon/* @pavoni
homeassistant/components/rpi_power/* @shenxn @swetoast
homeassistant/components/safe_mode/* @home-assistant/core homeassistant/components/safe_mode/* @home-assistant/core
homeassistant/components/saj/* @fredericvl homeassistant/components/saj/* @fredericvl
homeassistant/components/salt/* @bjornorri homeassistant/components/salt/* @bjornorri
@ -399,9 +405,11 @@ homeassistant/components/soma/* @ratsept
homeassistant/components/somfy/* @tetienne homeassistant/components/somfy/* @tetienne
homeassistant/components/sonarr/* @ctalkington homeassistant/components/sonarr/* @ctalkington
homeassistant/components/songpal/* @rytilahti @shenxn homeassistant/components/songpal/* @rytilahti @shenxn
homeassistant/components/sonos/* @cgtobi
homeassistant/components/spaceapi/* @fabaff homeassistant/components/spaceapi/* @fabaff
homeassistant/components/speedtestdotnet/* @rohankapoorcom @engrbm87 homeassistant/components/speedtestdotnet/* @rohankapoorcom @engrbm87
homeassistant/components/spider/* @peternijssen homeassistant/components/spider/* @peternijssen
homeassistant/components/splunk/* @Bre77
homeassistant/components/spotify/* @frenck homeassistant/components/spotify/* @frenck
homeassistant/components/sql/* @dgomes homeassistant/components/sql/* @dgomes
homeassistant/components/squeezebox/* @rajlaud homeassistant/components/squeezebox/* @rajlaud
@ -421,7 +429,7 @@ homeassistant/components/switchbot/* @danielhiversen
homeassistant/components/switcher_kis/* @tomerfi homeassistant/components/switcher_kis/* @tomerfi
homeassistant/components/switchmate/* @danielhiversen homeassistant/components/switchmate/* @danielhiversen
homeassistant/components/syncthru/* @nielstron homeassistant/components/syncthru/* @nielstron
homeassistant/components/synology_dsm/* @ProtoThis @Quentame homeassistant/components/synology_dsm/* @hacf-fr @Quentame
homeassistant/components/synology_srm/* @aerialls homeassistant/components/synology_srm/* @aerialls
homeassistant/components/syslog/* @fabaff homeassistant/components/syslog/* @fabaff
homeassistant/components/tado/* @michaelarnauts @bdraco homeassistant/components/tado/* @michaelarnauts @bdraco
@ -502,6 +510,7 @@ homeassistant/components/yi/* @bachya
homeassistant/components/zeroconf/* @Kane610 homeassistant/components/zeroconf/* @Kane610
homeassistant/components/zerproc/* @emlove homeassistant/components/zerproc/* @emlove
homeassistant/components/zha/* @dmulcahey @adminiuga homeassistant/components/zha/* @dmulcahey @adminiuga
homeassistant/components/zodiac/* @JulienTant
homeassistant/components/zone/* @home-assistant/core homeassistant/components/zone/* @home-assistant/core
homeassistant/components/zoneminder/* @rohankapoorcom homeassistant/components/zoneminder/* @rohankapoorcom
homeassistant/components/zwave/* @home-assistant/z-wave homeassistant/components/zwave/* @home-assistant/z-wave

View File

@ -5,6 +5,7 @@ from homeassistant.const import (
LENGTH_FEET, LENGTH_FEET,
LENGTH_INCHES, LENGTH_INCHES,
LENGTH_METERS, LENGTH_METERS,
LENGTH_MILLIMETERS,
PERCENTAGE, PERCENTAGE,
SPEED_KILOMETERS_PER_HOUR, SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR, SPEED_MILES_PER_HOUR,
@ -24,7 +25,6 @@ ATTR_UNIT_METRIC = "Metric"
CONCENTRATION_PARTS_PER_CUBIC_METER = f"p/{VOLUME_CUBIC_METERS}" CONCENTRATION_PARTS_PER_CUBIC_METER = f"p/{VOLUME_CUBIC_METERS}"
COORDINATOR = "coordinator" COORDINATOR = "coordinator"
DOMAIN = "accuweather" DOMAIN = "accuweather"
LENGTH_MILIMETERS = "mm"
MANUFACTURER = "AccuWeather, Inc." MANUFACTURER = "AccuWeather, Inc."
NAME = "AccuWeather" NAME = "AccuWeather"
UNDO_UPDATE_LISTENER = "undo_update_listener" UNDO_UPDATE_LISTENER = "undo_update_listener"
@ -238,7 +238,7 @@ SENSOR_TYPES = {
ATTR_DEVICE_CLASS: None, ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:weather-rainy", ATTR_ICON: "mdi:weather-rainy",
ATTR_LABEL: "Precipitation", ATTR_LABEL: "Precipitation",
ATTR_UNIT_METRIC: LENGTH_MILIMETERS, ATTR_UNIT_METRIC: LENGTH_MILLIMETERS,
ATTR_UNIT_IMPERIAL: LENGTH_INCHES, ATTR_UNIT_IMPERIAL: LENGTH_INCHES,
}, },
"PressureTendency": { "PressureTendency": {

View File

@ -0,0 +1,13 @@
{
"config": {
"step": {
"user": {
"data": {
"latitude": "Breitengrad",
"longitude": "L\u00e4ngengrad"
},
"title": "AccuWeather"
}
}
}
}

View File

@ -0,0 +1,35 @@
{
"config": {
"abort": {
"single_instance_allowed": "Sidumine juba tehtud. V\u00f5imalik on ainult 1 sidumine,"
},
"error": {
"cannot_connect": "\u00dchendus eba\u00f5nnestus",
"invalid_api_key": "API v\u00f5ti on vale",
"requests_exceeded": "Accuweatheri API-le esitatud p\u00e4ringute piirarv on \u00fcletatud. Peate ootama (v\u00f5i muutma API v\u00f5tit)."
},
"step": {
"user": {
"data": {
"api_key": "API v\u00f5ti",
"latitude": "Laiuskraad",
"longitude": "Pikkuskraad",
"name": "Sidumise nimi"
},
"description": "Kui vajate seadistamisel abi vaadake siit: https://www.home-assistant.io/integrations/accuweather/ \n\n M\u00f5ni andur pole vaikimisi lubatud. P\u00e4rast sidumise seadistamist saate need \u00fcksused lubada. \n Ilmapennustus pole vaikimisi lubatud. Saate selle lubada sidumise s\u00e4tetes.",
"title": "AccuWeather"
}
}
},
"options": {
"step": {
"user": {
"data": {
"forecast": "Ilmateade"
},
"description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 32 minuti asemel iga 64 minuti j\u00e4rel.",
"title": "AccuWeatheri valikud"
}
}
}
}

View File

@ -3,9 +3,32 @@
"abort": { "abort": {
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
}, },
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_api_key": "Cl\u00e9 API invalide",
"requests_exceeded": "Le nombre autoris\u00e9 de requ\u00eates adress\u00e9es \u00e0 l'API AccuWeather a \u00e9t\u00e9 d\u00e9pass\u00e9. Vous devez attendre ou modifier la cl\u00e9 API."
},
"step": { "step": {
"user": { "user": {
"description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration." "data": {
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom de l'int\u00e9gration"
},
"description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration.",
"title": "AccuWeather"
}
}
},
"options": {
"step": {
"user": {
"data": {
"forecast": "Pr\u00e9visions m\u00e9t\u00e9orologiques"
},
"description": "En raison des limitations de la version gratuite de la cl\u00e9 API AccuWeather, lorsque vous activez les pr\u00e9visions m\u00e9t\u00e9orologiques, les mises \u00e0 jour des donn\u00e9es seront effectu\u00e9es toutes les 64 minutes au lieu de toutes les 32 minutes.",
"title": "Options AccuWeather"
} }
} }
} }

View File

@ -16,7 +16,8 @@
"longitude": "Lengdegrad", "longitude": "Lengdegrad",
"name": "Navn p\u00e5 integrasjon" "name": "Navn p\u00e5 integrasjon"
}, },
"description": "Hvis du trenger hjelp med konfigurasjonen, kan du se her: https://www.home-assistant.io/integrations/accuweather/ \n\n Noen sensorer er ikke aktivert som standard. Du kan aktivere dem i enhetsregisteret etter integrasjonskonfigurasjonen. \n V\u00e6rmelding er ikke aktivert som standard. Du kan aktivere det i integrasjonsalternativene." "description": "Hvis du trenger hjelp med konfigurasjonen, kan du se her: https://www.home-assistant.io/integrations/accuweather/ \n\n Noen sensorer er ikke aktivert som standard. Du kan aktivere dem i enhetsregisteret etter integrasjonskonfigurasjonen. \n V\u00e6rmelding er ikke aktivert som standard. Du kan aktivere det i integrasjonsalternativene.",
"title": ""
} }
} }
}, },

View File

@ -4,7 +4,7 @@
"single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja."
}, },
"error": { "error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"invalid_api_key": "Nieprawid\u0142owy klucz API.", "invalid_api_key": "Nieprawid\u0142owy klucz API.",
"requests_exceeded": "Dozwolona liczba zapyta\u0144 do interfejsu API AccuWeather zosta\u0142a przekroczona. Musisz poczeka\u0107 lub zmieni\u0107 klucz API." "requests_exceeded": "Dozwolona liczba zapyta\u0144 do interfejsu API AccuWeather zosta\u0142a przekroczona. Musisz poczeka\u0107 lub zmieni\u0107 klucz API."
}, },

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "Langev",
"rising": "T\u00f5usev",
"steady": "\u00dchtlane"
}
}
}

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "En baisse",
"rising": "En hausse",
"steady": "Stable"
}
}
}

View File

@ -11,5 +11,6 @@
"title": "Velg en hub du vil legge til" "title": "Velg en hub du vil legge til"
} }
} }
} },
"title": ""
} }

View File

@ -16,6 +16,7 @@
"data": { "data": {
"host": "Vert", "host": "Vert",
"password": "Passord", "password": "Passord",
"port": "",
"ssl": "AdGuard Hjem bruker et SSL-sertifikat", "ssl": "AdGuard Hjem bruker et SSL-sertifikat",
"username": "Brukernavn", "username": "Brukernavn",
"verify_ssl": "AdGuard Home bruker et riktig sertifikat" "verify_ssl": "AdGuard Home bruker et riktig sertifikat"

View File

@ -5,7 +5,7 @@
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home." "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
}, },
"error": { "error": {
"connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -4,6 +4,7 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DEVICE_CLASS_MOVING,
DEVICE_CLASSES_SCHEMA, DEVICE_CLASSES_SCHEMA,
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
BinarySensorEntity, BinarySensorEntity,
@ -43,7 +44,7 @@ class AdsBinarySensor(AdsEntity, BinarySensorEntity):
def __init__(self, ads_hub, name, ads_var, device_class): def __init__(self, ads_hub, name, ads_var, device_class):
"""Initialize ADS binary sensor.""" """Initialize ADS binary sensor."""
super().__init__(ads_hub, name, ads_var) super().__init__(ads_hub, name, ads_var)
self._device_class = device_class or "moving" self._device_class = device_class or DEVICE_CLASS_MOVING
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register device notification.""" """Register device notification."""

View File

@ -10,7 +10,8 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "Vert" "host": "Vert",
"port": ""
}, },
"title": "Konfigurere Agent DVR" "title": "Konfigurere Agent DVR"
} }

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
}, },
"error": { "error": {
"already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.",

View File

@ -0,0 +1,14 @@
"""Describe group states."""
from homeassistant.components.group import GroupIntegrationRegistry
from homeassistant.core import callback
from homeassistant.helpers.typing import HomeAssistantType
@callback
def async_describe_on_off_states(
hass: HomeAssistantType, registry: GroupIntegrationRegistry
) -> None:
"""Describe group on off states."""
registry.exclude_domain()

View File

@ -0,0 +1,12 @@
{
"config": {
"step": {
"user": {
"data": {
"latitude": "Laiuskraad",
"longitude": "Pikkuskraad"
}
}
}
}
}

View File

@ -15,7 +15,8 @@
"longitude": "Lengdegrad", "longitude": "Lengdegrad",
"name": "Navn p\u00e5 integrasjonen" "name": "Navn p\u00e5 integrasjonen"
}, },
"description": "Sett opp Airly luftkvalitet integrasjon. For \u00e5 opprette API-n\u00f8kkel, g\u00e5 til [https://developer.airly.eu/register](https://developer.airly.eu/register)" "description": "Sett opp Airly luftkvalitet integrasjon. For \u00e5 opprette API-n\u00f8kkel, g\u00e5 til [https://developer.airly.eu/register](https://developer.airly.eu/register)",
"title": ""
} }
} }
} }

View File

@ -3,9 +3,11 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"api_key": "",
"latitude": "Latitude", "latitude": "Latitude",
"longitude": "Longitude" "longitude": "Longitude"
} },
"title": ""
} }
} }
} }

View File

@ -3,8 +3,13 @@ import asyncio
from datetime import timedelta from datetime import timedelta
from math import ceil from math import ceil
from pyairvisual import Client from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import AirVisualError, NodeProError from pyairvisual.errors import (
AirVisualError,
InvalidKeyError,
KeyExpiredError,
NodeProError,
)
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
@ -206,29 +211,36 @@ def _standardize_node_pro_config_entry(hass, config_entry):
async def async_setup_entry(hass, config_entry): async def async_setup_entry(hass, config_entry):
"""Set up AirVisual as config entry.""" """Set up AirVisual as config entry."""
websession = aiohttp_client.async_get_clientsession(hass)
if CONF_API_KEY in config_entry.data: if CONF_API_KEY in config_entry.data:
_standardize_geography_config_entry(hass, config_entry) _standardize_geography_config_entry(hass, config_entry)
client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession) websession = aiohttp_client.async_get_clientsession(hass)
cloud_api = CloudAPI(config_entry.data[CONF_API_KEY], session=websession)
async def async_update_data(): async def async_update_data():
"""Get new data from the API.""" """Get new data from the API."""
if CONF_CITY in config_entry.data: if CONF_CITY in config_entry.data:
api_coro = client.api.city( api_coro = cloud_api.air_quality.city(
config_entry.data[CONF_CITY], config_entry.data[CONF_CITY],
config_entry.data[CONF_STATE], config_entry.data[CONF_STATE],
config_entry.data[CONF_COUNTRY], config_entry.data[CONF_COUNTRY],
) )
else: else:
api_coro = client.api.nearest_city( api_coro = cloud_api.air_quality.nearest_city(
config_entry.data[CONF_LATITUDE], config_entry.data[CONF_LATITUDE],
config_entry.data[CONF_LONGITUDE], config_entry.data[CONF_LONGITUDE],
) )
try: try:
return await api_coro return await api_coro
except (InvalidKeyError, KeyExpiredError):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "reauth"},
data=config_entry.data,
)
)
except AirVisualError as err: except AirVisualError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err raise UpdateFailed(f"Error while retrieving data: {err}") from err
@ -254,17 +266,13 @@ async def async_setup_entry(hass, config_entry):
else: else:
_standardize_node_pro_config_entry(hass, config_entry) _standardize_node_pro_config_entry(hass, config_entry)
client = Client(session=websession)
async def async_update_data(): async def async_update_data():
"""Get new data from the API.""" """Get new data from the API."""
try: try:
return await client.node.from_samba( async with NodeSamba(
config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_PASSWORD]
config_entry.data[CONF_PASSWORD], ) as node:
include_history=False, return await node.async_get_latest_measurements()
include_trends=False,
)
except NodeProError as err: except NodeProError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err raise UpdateFailed(f"Error while retrieving data: {err}") from err

View File

@ -40,9 +40,9 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
@property @property
def air_quality_index(self): def air_quality_index(self):
"""Return the Air Quality Index (AQI).""" """Return the Air Quality Index (AQI)."""
if self.coordinator.data["current"]["settings"]["is_aqi_usa"]: if self.coordinator.data["settings"]["is_aqi_usa"]:
return self.coordinator.data["current"]["measurements"]["aqi_us"] return self.coordinator.data["measurements"]["aqi_us"]
return self.coordinator.data["current"]["measurements"]["aqi_cn"] return self.coordinator.data["measurements"]["aqi_cn"]
@property @property
def available(self): def available(self):
@ -52,61 +52,59 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity):
@property @property
def carbon_dioxide(self): def carbon_dioxide(self):
"""Return the CO2 (carbon dioxide) level.""" """Return the CO2 (carbon dioxide) level."""
return self.coordinator.data["current"]["measurements"].get("co2") return self.coordinator.data["measurements"].get("co2")
@property @property
def device_info(self): def device_info(self):
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return { return {
"identifiers": { "identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
(DOMAIN, self.coordinator.data["current"]["serial_number"]) "name": self.coordinator.data["settings"]["node_name"],
},
"name": self.coordinator.data["current"]["settings"]["node_name"],
"manufacturer": "AirVisual", "manufacturer": "AirVisual",
"model": f'{self.coordinator.data["current"]["status"]["model"]}', "model": f'{self.coordinator.data["status"]["model"]}',
"sw_version": ( "sw_version": (
f'Version {self.coordinator.data["current"]["status"]["system_version"]}' f'Version {self.coordinator.data["status"]["system_version"]}'
f'{self.coordinator.data["current"]["status"]["app_version"]}' f'{self.coordinator.data["status"]["app_version"]}'
), ),
} }
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
node_name = self.coordinator.data["current"]["settings"]["node_name"] node_name = self.coordinator.data["settings"]["node_name"]
return f"{node_name} Node/Pro: Air Quality" return f"{node_name} Node/Pro: Air Quality"
@property @property
def particulate_matter_2_5(self): def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level.""" """Return the particulate matter 2.5 level."""
return self.coordinator.data["current"]["measurements"].get("pm2_5") return self.coordinator.data["measurements"].get("pm2_5")
@property @property
def particulate_matter_10(self): def particulate_matter_10(self):
"""Return the particulate matter 10 level.""" """Return the particulate matter 10 level."""
return self.coordinator.data["current"]["measurements"].get("pm1_0") return self.coordinator.data["measurements"].get("pm1_0")
@property @property
def particulate_matter_0_1(self): def particulate_matter_0_1(self):
"""Return the particulate matter 0.1 level.""" """Return the particulate matter 0.1 level."""
return self.coordinator.data["current"]["measurements"].get("pm0_1") return self.coordinator.data["measurements"].get("pm0_1")
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique, Home Assistant friendly identifier for this entity.""" """Return a unique, Home Assistant friendly identifier for this entity."""
return self.coordinator.data["current"]["serial_number"] return self.coordinator.data["serial_number"]
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self):
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
self._attrs.update( self._attrs.update(
{ {
ATTR_VOC: self.coordinator.data["current"]["measurements"].get("voc"), ATTR_VOC: self.coordinator.data["measurements"].get("voc"),
**{ **{
ATTR_SENSOR_LIFE.format(pollutant): lifespan ATTR_SENSOR_LIFE.format(pollutant): lifespan
for pollutant, lifespan in self.coordinator.data["current"][ for pollutant, lifespan in self.coordinator.data["status"][
"status" "sensor_life"
]["sensor_life"].items() ].items()
}, },
} }
) )

View File

@ -1,7 +1,7 @@
"""Define a config flow manager for AirVisual.""" """Define a config flow manager for AirVisual."""
import asyncio import asyncio
from pyairvisual import Client from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import InvalidKeyError, NodeProError from pyairvisual.errors import InvalidKeyError, NodeProError
import voluptuous as vol import voluptuous as vol
@ -34,12 +34,19 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 2 VERSION = 2
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
def __init__(self):
"""Initialize the config flow."""
self._geo_id = None
self._latitude = None
self._longitude = None
self.api_key_data_schema = vol.Schema({vol.Required(CONF_API_KEY): str})
@property @property
def geography_schema(self): def geography_schema(self):
"""Return the data schema for the cloud API.""" """Return the data schema for the cloud API."""
return vol.Schema( return self.api_key_data_schema.extend(
{ {
vol.Required(CONF_API_KEY): str,
vol.Required( vol.Required(
CONF_LATITUDE, default=self.hass.config.latitude CONF_LATITUDE, default=self.hass.config.latitude
): cv.latitude, ): cv.latitude,
@ -85,8 +92,8 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
step_id="geography", data_schema=self.geography_schema step_id="geography", data_schema=self.geography_schema
) )
geo_id = async_get_geography_id(user_input) self._geo_id = async_get_geography_id(user_input)
await self._async_set_unique_id(geo_id) await self._async_set_unique_id(self._geo_id)
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
# Find older config entries without unique ID: # Find older config entries without unique ID:
@ -95,13 +102,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
continue continue
if any( if any(
geo_id == async_get_geography_id(geography) self._geo_id == async_get_geography_id(geography)
for geography in entry.data[CONF_GEOGRAPHIES] for geography in entry.data[CONF_GEOGRAPHIES]
): ):
return self.async_abort(reason="already_configured") return self.async_abort(reason="already_configured")
websession = aiohttp_client.async_get_clientsession(self.hass) websession = aiohttp_client.async_get_clientsession(self.hass)
client = Client(session=websession, api_key=user_input[CONF_API_KEY]) cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession)
# If this is the first (and only the first) time we've seen this API key, check # If this is the first (and only the first) time we've seen this API key, check
# that it's valid: # that it's valid:
@ -113,7 +120,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async with check_keys_lock: async with check_keys_lock:
if user_input[CONF_API_KEY] not in checked_keys: if user_input[CONF_API_KEY] not in checked_keys:
try: try:
await client.api.nearest_city() await cloud_api.air_quality.nearest_city()
except InvalidKeyError: except InvalidKeyError:
return self.async_show_form( return self.async_show_form(
step_id="geography", step_id="geography",
@ -123,10 +130,19 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
checked_keys.add(user_input[CONF_API_KEY]) checked_keys.add(user_input[CONF_API_KEY])
return self.async_create_entry( return await self.async_step_geography_finish(user_input)
title=f"Cloud API ({geo_id})",
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY}, async def async_step_geography_finish(self, user_input=None):
) """Handle the finalization of a Cloud API config entry."""
existing_entry = await self.async_set_unique_id(self._geo_id)
if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=user_input)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(
title=f"Cloud API ({self._geo_id})",
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY},
)
async def async_step_import(self, import_config): async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml.""" """Import a config entry from configuration.yaml."""
@ -141,16 +157,10 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await self._async_set_unique_id(user_input[CONF_IP_ADDRESS]) await self._async_set_unique_id(user_input[CONF_IP_ADDRESS])
websession = aiohttp_client.async_get_clientsession(self.hass) node = NodeSamba(user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD])
client = Client(session=websession)
try: try:
await client.node.from_samba( await node.async_connect()
user_input[CONF_IP_ADDRESS],
user_input[CONF_PASSWORD],
include_history=False,
include_trends=False,
)
except NodeProError as err: except NodeProError as err:
LOGGER.error("Error connecting to Node/Pro unit: %s", err) LOGGER.error("Error connecting to Node/Pro unit: %s", err)
return self.async_show_form( return self.async_show_form(
@ -159,11 +169,37 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors={CONF_IP_ADDRESS: "unable_to_connect"}, errors={CONF_IP_ADDRESS: "unable_to_connect"},
) )
await node.async_disconnect()
return self.async_create_entry( return self.async_create_entry(
title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})", title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})",
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO}, data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO},
) )
async def async_step_reauth(self, data):
"""Handle configuration by re-auth."""
self._latitude = data[CONF_LATITUDE]
self._longitude = data[CONF_LONGITUDE]
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""Handle re-auth completion."""
if not user_input:
return self.async_show_form(
step_id="reauth_confirm", data_schema=self.api_key_data_schema
)
conf = {
CONF_API_KEY: user_input[CONF_API_KEY],
CONF_LATITUDE: self._latitude,
CONF_LONGITUDE: self._longitude,
}
self._geo_id = async_get_geography_id(conf)
return await self.async_step_geography_finish(conf)
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle the start of the config flow.""" """Handle the start of the config flow."""
if not user_input: if not user_input:

View File

@ -3,6 +3,6 @@
"name": "AirVisual", "name": "AirVisual",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airvisual", "documentation": "https://www.home-assistant.io/integrations/airvisual",
"requirements": ["pyairvisual==4.4.0"], "requirements": ["pyairvisual==5.0.2"],
"codeowners": ["@bachya"] "codeowners": ["@bachya"]
} }

View File

@ -38,10 +38,6 @@ ATTR_POLLUTANT_SYMBOL = "pollutant_symbol"
ATTR_POLLUTANT_UNIT = "pollutant_unit" ATTR_POLLUTANT_UNIT = "pollutant_unit"
ATTR_REGION = "region" ATTR_REGION = "region"
MASS_PARTS_PER_MILLION = "ppm"
MASS_PARTS_PER_BILLION = "ppb"
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3"
SENSOR_KIND_LEVEL = "air_pollution_level" SENSOR_KIND_LEVEL = "air_pollution_level"
SENSOR_KIND_AQI = "air_quality_index" SENSOR_KIND_AQI = "air_quality_index"
SENSOR_KIND_POLLUTANT = "main_pollutant" SENSOR_KIND_POLLUTANT = "main_pollutant"
@ -229,22 +225,20 @@ class AirVisualNodeProSensor(AirVisualEntity):
def device_info(self): def device_info(self):
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return { return {
"identifiers": { "identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
(DOMAIN, self.coordinator.data["current"]["serial_number"]) "name": self.coordinator.data["settings"]["node_name"],
},
"name": self.coordinator.data["current"]["settings"]["node_name"],
"manufacturer": "AirVisual", "manufacturer": "AirVisual",
"model": f'{self.coordinator.data["current"]["status"]["model"]}', "model": f'{self.coordinator.data["status"]["model"]}',
"sw_version": ( "sw_version": (
f'Version {self.coordinator.data["current"]["status"]["system_version"]}' f'Version {self.coordinator.data["status"]["system_version"]}'
f'{self.coordinator.data["current"]["status"]["app_version"]}' f'{self.coordinator.data["status"]["app_version"]}'
), ),
} }
@property @property
def name(self): def name(self):
"""Return the name.""" """Return the name."""
node_name = self.coordinator.data["current"]["settings"]["node_name"] node_name = self.coordinator.data["settings"]["node_name"]
return f"{node_name} Node/Pro: {self._name}" return f"{node_name} Node/Pro: {self._name}"
@property @property
@ -255,18 +249,14 @@ class AirVisualNodeProSensor(AirVisualEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique, Home Assistant friendly identifier for this entity.""" """Return a unique, Home Assistant friendly identifier for this entity."""
return f"{self.coordinator.data['current']['serial_number']}_{self._kind}" return f"{self.coordinator.data['serial_number']}_{self._kind}"
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self):
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
if self._kind == SENSOR_KIND_BATTERY_LEVEL: if self._kind == SENSOR_KIND_BATTERY_LEVEL:
self._state = self.coordinator.data["current"]["status"]["battery"] self._state = self.coordinator.data["status"]["battery"]
elif self._kind == SENSOR_KIND_HUMIDITY: elif self._kind == SENSOR_KIND_HUMIDITY:
self._state = self.coordinator.data["current"]["measurements"].get( self._state = self.coordinator.data["measurements"].get("humidity")
"humidity"
)
elif self._kind == SENSOR_KIND_TEMPERATURE: elif self._kind == SENSOR_KIND_TEMPERATURE:
self._state = self.coordinator.data["current"]["measurements"].get( self._state = self.coordinator.data["measurements"].get("temperature_C")
"temperature_C"
)

View File

@ -0,0 +1,17 @@
{
"config": {
"step": {
"geography": {
"data": {
"latitude": "Laiuskraad",
"longitude": "Pikkuskraad"
}
},
"user": {
"data": {
"cloud_api": "Geograafiline asukoht"
}
}
}
}
}

View File

@ -29,6 +29,7 @@
"user": { "user": {
"data": { "data": {
"cloud_api": "Geografisk plassering", "cloud_api": "Geografisk plassering",
"node_pro": "",
"type": "Integrasjonstype" "type": "Integrasjonstype"
}, },
"description": "Velg hvilken type AirVisual-data du vil overv\u00e5ke.", "description": "Velg hvilken type AirVisual-data du vil overv\u00e5ke.",

View File

@ -4,7 +4,7 @@
"already_configured": "Ten klucz API jest ju\u017c w u\u017cyciu." "already_configured": "Ten klucz API jest ju\u017c w u\u017cyciu."
}, },
"error": { "error": {
"general_error": "Nieoczekiwany b\u0142\u0105d.", "general_error": "Nieoczekiwany b\u0142\u0105d",
"invalid_api_key": "Nieprawid\u0142owy klucz API.", "invalid_api_key": "Nieprawid\u0142owy klucz API.",
"unable_to_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z jednostk\u0105 Node/Pro." "unable_to_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z jednostk\u0105 Node/Pro."
}, },

View File

@ -6,6 +6,7 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
CONF_CODE, CONF_CODE,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DOMAIN, CONF_DOMAIN,
@ -56,7 +57,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
if state is None: if state is None:
continue continue
supported_features = state.attributes["supported_features"] supported_features = state.attributes[ATTR_SUPPORTED_FEATURES]
# Add actions for each entity that belongs to this integration # Add actions for each entity that belongs to this integration
if supported_features & SUPPORT_ALARM_ARM_AWAY: if supported_features & SUPPORT_ALARM_ARM_AWAY:

View File

@ -11,6 +11,7 @@ from homeassistant.components.alarm_control_panel.const import (
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
CONF_CONDITION, CONF_CONDITION,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DOMAIN, CONF_DOMAIN,
@ -73,7 +74,7 @@ async def async_get_conditions(
if state is None: if state is None:
continue continue
supported_features = state.attributes["supported_features"] supported_features = state.attributes[ATTR_SUPPORTED_FEATURES]
# Add conditions for each entity that belongs to this integration # Add conditions for each entity that belongs to this integration
conditions += [ conditions += [

View File

@ -12,6 +12,7 @@ from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import ( from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DOMAIN, CONF_DOMAIN,
CONF_ENTITY_ID, CONF_ENTITY_ID,
@ -64,7 +65,7 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
if entity_state is None: if entity_state is None:
continue continue
supported_features = entity_state.attributes["supported_features"] supported_features = entity_state.attributes[ATTR_SUPPORTED_FEATURES]
# Add triggers for each entity that belongs to this integration # Add triggers for each entity that belongs to this integration
triggers += [ triggers += [

View File

@ -0,0 +1,31 @@
"""Describe group states."""
from homeassistant.components.group import GroupIntegrationRegistry
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_TRIGGERED,
STATE_OFF,
)
from homeassistant.core import callback
from homeassistant.helpers.typing import HomeAssistantType
@callback
def async_describe_on_off_states(
hass: HomeAssistantType, registry: GroupIntegrationRegistry
) -> None:
"""Describe group on off states."""
registry.on_off_states(
{
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_TRIGGERED,
},
STATE_OFF,
)

View File

@ -1,4 +1,27 @@
{ {
"device_automation": {
"action_type": {
"arm_away": "Valvesta {entity_name}",
"arm_home": "Valvesta {entity_name} kodus re\u017eiimis",
"arm_night": "Valvesta {entity_name} \u00f6\u00f6re\u017eiimis",
"disarm": "V\u00f5ta {entity_name} valvest maha",
"trigger": "K\u00e4ivita {entity_name}"
},
"condition_type": {
"is_armed_away": "{entity_name} on valvestatud",
"is_armed_home": "{entity_name} on valvestatud kodure\u017eiimis",
"is_armed_night": "{entity_name} on valvestatud \u00f6\u00f6re\u017eiimis",
"is_disarmed": "{entity_name} on valve alt maas",
"is_triggered": "{entity_name} on h\u00e4iret andnud"
},
"trigger_type": {
"armed_away": "{entity_name} valvestatus",
"armed_home": "{entity_name} valvestatus kodure\u017eiimis",
"armed_night": "{entity_name} valvestatus \u00f6\u00f6re\u017eiimis",
"disarmed": "{entity_name} v\u00f5eti valvest maha",
"triggered": "{entity_name} andis h\u00e4iret"
}
},
"state": { "state": {
"_": { "_": {
"armed": "Valves", "armed": "Valves",

View File

@ -25,7 +25,7 @@
"state": { "state": {
"_": { "_": {
"armed": "Ingeschakeld", "armed": "Ingeschakeld",
"armed_away": "Afwezig Ingeschakeld", "armed_away": "Ingeschakeld afwezig",
"armed_custom_bypass": "Ingeschakeld met overbrugging(en)", "armed_custom_bypass": "Ingeschakeld met overbrugging(en)",
"armed_home": "Ingeschakeld thuis", "armed_home": "Ingeschakeld thuis",
"armed_night": "Ingeschakeld nacht", "armed_night": "Ingeschakeld nacht",

View File

@ -1,167 +1,82 @@
"""Support for AlarmDecoder devices.""" """Support for AlarmDecoder devices."""
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
from adext import AdExt from adext import AdExt
from alarmdecoder.devices import SerialDevice, SocketDevice, USBDevice from alarmdecoder.devices import SerialDevice, SocketDevice
from alarmdecoder.util import NoDeviceError from alarmdecoder.util import NoDeviceError
import voluptuous as vol
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP from homeassistant.const import (
import homeassistant.helpers.config_validation as cv CONF_HOST,
from homeassistant.helpers.discovery import load_platform CONF_PORT,
CONF_PROTOCOL,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from .const import (
CONF_DEVICE_BAUD,
CONF_DEVICE_PATH,
DATA_AD,
DATA_REMOVE_STOP_LISTENER,
DATA_REMOVE_UPDATE_LISTENER,
DATA_RESTART,
DOMAIN,
PROTOCOL_SERIAL,
PROTOCOL_SOCKET,
SIGNAL_PANEL_MESSAGE,
SIGNAL_REL_MESSAGE,
SIGNAL_RFX_MESSAGE,
SIGNAL_ZONE_FAULT,
SIGNAL_ZONE_RESTORE,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = "alarmdecoder" PLATFORMS = ["alarm_control_panel", "sensor", "binary_sensor"]
DATA_AD = "alarmdecoder"
CONF_DEVICE = "device"
CONF_DEVICE_BAUD = "baudrate"
CONF_DEVICE_PATH = "path"
CONF_DEVICE_PORT = "port"
CONF_DEVICE_TYPE = "type"
CONF_AUTO_BYPASS = "autobypass"
CONF_PANEL_DISPLAY = "panel_display"
CONF_ZONE_NAME = "name"
CONF_ZONE_TYPE = "type"
CONF_ZONE_LOOP = "loop"
CONF_ZONE_RFID = "rfid"
CONF_ZONES = "zones"
CONF_RELAY_ADDR = "relayaddr"
CONF_RELAY_CHAN = "relaychan"
CONF_CODE_ARM_REQUIRED = "code_arm_required"
DEFAULT_DEVICE_TYPE = "socket"
DEFAULT_DEVICE_HOST = "localhost"
DEFAULT_DEVICE_PORT = 10000
DEFAULT_DEVICE_PATH = "/dev/ttyUSB0"
DEFAULT_DEVICE_BAUD = 115200
DEFAULT_AUTO_BYPASS = False
DEFAULT_PANEL_DISPLAY = False
DEFAULT_CODE_ARM_REQUIRED = True
DEFAULT_ZONE_TYPE = "opening"
SIGNAL_PANEL_MESSAGE = "alarmdecoder.panel_message"
SIGNAL_PANEL_ARM_AWAY = "alarmdecoder.panel_arm_away"
SIGNAL_PANEL_ARM_HOME = "alarmdecoder.panel_arm_home"
SIGNAL_PANEL_DISARM = "alarmdecoder.panel_disarm"
SIGNAL_ZONE_FAULT = "alarmdecoder.zone_fault"
SIGNAL_ZONE_RESTORE = "alarmdecoder.zone_restore"
SIGNAL_RFX_MESSAGE = "alarmdecoder.rfx_message"
SIGNAL_REL_MESSAGE = "alarmdecoder.rel_message"
DEVICE_SOCKET_SCHEMA = vol.Schema(
{
vol.Required(CONF_DEVICE_TYPE): "socket",
vol.Optional(CONF_HOST, default=DEFAULT_DEVICE_HOST): cv.string,
vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_DEVICE_PORT): cv.port,
}
)
DEVICE_SERIAL_SCHEMA = vol.Schema(
{
vol.Required(CONF_DEVICE_TYPE): "serial",
vol.Optional(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): cv.string,
vol.Optional(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): cv.string,
}
)
DEVICE_USB_SCHEMA = vol.Schema({vol.Required(CONF_DEVICE_TYPE): "usb"})
ZONE_SCHEMA = vol.Schema(
{
vol.Required(CONF_ZONE_NAME): cv.string,
vol.Optional(CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE): vol.Any(
DEVICE_CLASSES_SCHEMA
),
vol.Optional(CONF_ZONE_RFID): cv.string,
vol.Optional(CONF_ZONE_LOOP): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
vol.Inclusive(
CONF_RELAY_ADDR,
"relaylocation",
"Relay address and channel must exist together",
): cv.byte,
vol.Inclusive(
CONF_RELAY_CHAN,
"relaylocation",
"Relay address and channel must exist together",
): cv.byte,
}
)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_DEVICE): vol.Any(
DEVICE_SOCKET_SCHEMA, DEVICE_SERIAL_SCHEMA, DEVICE_USB_SCHEMA
),
vol.Optional(
CONF_PANEL_DISPLAY, default=DEFAULT_PANEL_DISPLAY
): cv.boolean,
vol.Optional(CONF_AUTO_BYPASS, default=DEFAULT_AUTO_BYPASS): cv.boolean,
vol.Optional(
CONF_CODE_ARM_REQUIRED, default=DEFAULT_CODE_ARM_REQUIRED
): cv.boolean,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
}
)
},
extra=vol.ALLOW_EXTRA,
)
def setup(hass, config): async def async_setup(hass, config):
"""Set up for the AlarmDecoder devices.""" """Set up for the AlarmDecoder devices."""
conf = config.get(DOMAIN) return True
restart = False
device = conf[CONF_DEVICE]
display = conf[CONF_PANEL_DISPLAY]
auto_bypass = conf[CONF_AUTO_BYPASS]
code_arm_required = conf[CONF_CODE_ARM_REQUIRED]
zones = conf.get(CONF_ZONES)
device_type = device[CONF_DEVICE_TYPE] async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
host = DEFAULT_DEVICE_HOST """Set up AlarmDecoder config flow."""
port = DEFAULT_DEVICE_PORT undo_listener = entry.add_update_listener(_update_listener)
path = DEFAULT_DEVICE_PATH
baud = DEFAULT_DEVICE_BAUD ad_connection = entry.data
protocol = ad_connection[CONF_PROTOCOL]
def stop_alarmdecoder(event): def stop_alarmdecoder(event):
"""Handle the shutdown of AlarmDecoder.""" """Handle the shutdown of AlarmDecoder."""
if not hass.data.get(DOMAIN):
return
_LOGGER.debug("Shutting down alarmdecoder") _LOGGER.debug("Shutting down alarmdecoder")
nonlocal restart hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = False
restart = False
controller.close() controller.close()
def open_connection(now=None): async def open_connection(now=None):
"""Open a connection to AlarmDecoder.""" """Open a connection to AlarmDecoder."""
nonlocal restart
try: try:
controller.open(baud) await hass.async_add_executor_job(controller.open, baud)
except NoDeviceError: except NoDeviceError:
_LOGGER.debug("Failed to connect. Retrying in 5 seconds") _LOGGER.debug("Failed to connect. Retrying in 5 seconds")
hass.helpers.event.track_point_in_time( hass.helpers.event.async_track_point_in_time(
open_connection, dt_util.utcnow() + timedelta(seconds=5) open_connection, dt_util.utcnow() + timedelta(seconds=5)
) )
return return
_LOGGER.debug("Established a connection with the alarmdecoder") _LOGGER.debug("Established a connection with the alarmdecoder")
restart = True hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = True
def handle_closed_connection(event): def handle_closed_connection(event):
"""Restart after unexpected loss of connection.""" """Restart after unexpected loss of connection."""
nonlocal restart if not hass.data[DOMAIN][entry.entry_id][DATA_RESTART]:
if not restart:
return return
restart = False hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = False
_LOGGER.warning("AlarmDecoder unexpectedly lost connection") _LOGGER.warning("AlarmDecoder unexpectedly lost connection")
hass.add_job(open_connection) hass.add_job(open_connection)
@ -185,18 +100,14 @@ def setup(hass, config):
"""Handle relay or zone expander message from AlarmDecoder.""" """Handle relay or zone expander message from AlarmDecoder."""
hass.helpers.dispatcher.dispatcher_send(SIGNAL_REL_MESSAGE, message) hass.helpers.dispatcher.dispatcher_send(SIGNAL_REL_MESSAGE, message)
controller = False baud = ad_connection.get(CONF_DEVICE_BAUD)
if device_type == "socket": if protocol == PROTOCOL_SOCKET:
host = device[CONF_HOST] host = ad_connection[CONF_HOST]
port = device[CONF_DEVICE_PORT] port = ad_connection[CONF_PORT]
controller = AdExt(SocketDevice(interface=(host, port))) controller = AdExt(SocketDevice(interface=(host, port)))
elif device_type == "serial": if protocol == PROTOCOL_SERIAL:
path = device[CONF_DEVICE_PATH] path = ad_connection[CONF_DEVICE_PATH]
baud = device[CONF_DEVICE_BAUD]
controller = AdExt(SerialDevice(interface=path)) controller = AdExt(SerialDevice(interface=path))
elif device_type == "usb":
AdExt(USBDevice.find())
return False
controller.on_message += handle_message controller.on_message += handle_message
controller.on_rfx_message += handle_rfx_message controller.on_rfx_message += handle_rfx_message
@ -205,24 +116,56 @@ def setup(hass, config):
controller.on_close += handle_closed_connection controller.on_close += handle_closed_connection
controller.on_expander_message += handle_rel_message controller.on_expander_message += handle_rel_message
hass.data[DATA_AD] = controller remove_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder
open_connection()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
load_platform(
hass,
"alarm_control_panel",
DOMAIN,
{CONF_AUTO_BYPASS: auto_bypass, CONF_CODE_ARM_REQUIRED: code_arm_required},
config,
) )
if zones: hass.data.setdefault(DOMAIN, {})
load_platform(hass, "binary_sensor", DOMAIN, {CONF_ZONES: zones}, config) hass.data[DOMAIN][entry.entry_id] = {
DATA_AD: controller,
DATA_REMOVE_UPDATE_LISTENER: undo_listener,
DATA_REMOVE_STOP_LISTENER: remove_stop_listener,
DATA_RESTART: False,
}
if display: await open_connection()
load_platform(hass, "sensor", DOMAIN, conf, config)
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Unload a AlarmDecoder entry."""
hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = False
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)
if not unload_ok:
return False
hass.data[DOMAIN][entry.entry_id][DATA_REMOVE_UPDATE_LISTENER]()
hass.data[DOMAIN][entry.entry_id][DATA_REMOVE_STOP_LISTENER]()
await hass.async_add_executor_job(hass.data[DOMAIN][entry.entry_id][DATA_AD].close)
if hass.data[DOMAIN][entry.entry_id]:
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
return True return True
async def _update_listener(hass: HomeAssistantType, entry: ConfigEntry):
"""Handle options update."""
_LOGGER.debug("AlarmDecoder options updated: %s", entry.as_dict()["options"])
await hass.config_entries.async_reload(entry.entry_id)

View File

@ -12,6 +12,7 @@ from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT, SUPPORT_ALARM_ARM_NIGHT,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
@ -20,66 +21,70 @@ from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED, STATE_ALARM_TRIGGERED,
) )
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType
from . import ( from .const import (
CONF_ALT_NIGHT_MODE,
CONF_AUTO_BYPASS, CONF_AUTO_BYPASS,
CONF_CODE_ARM_REQUIRED, CONF_CODE_ARM_REQUIRED,
DATA_AD, DATA_AD,
DEFAULT_ARM_OPTIONS,
DOMAIN, DOMAIN,
OPTIONS_ARM,
SIGNAL_PANEL_MESSAGE, SIGNAL_PANEL_MESSAGE,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime" SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime"
ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string})
SERVICE_ALARM_KEYPRESS = "alarm_keypress" SERVICE_ALARM_KEYPRESS = "alarm_keypress"
ATTR_KEYPRESS = "keypress" ATTR_KEYPRESS = "keypress"
ALARM_KEYPRESS_SCHEMA = vol.Schema({vol.Required(ATTR_KEYPRESS): cv.string})
def setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
):
"""Set up for AlarmDecoder alarm panels.""" """Set up for AlarmDecoder alarm panels."""
if discovery_info is None: options = entry.options
return arm_options = options.get(OPTIONS_ARM, DEFAULT_ARM_OPTIONS)
client = hass.data[DOMAIN][entry.entry_id][DATA_AD]
auto_bypass = discovery_info[CONF_AUTO_BYPASS] entity = AlarmDecoderAlarmPanel(
code_arm_required = discovery_info[CONF_CODE_ARM_REQUIRED] client=client,
entity = AlarmDecoderAlarmPanel(auto_bypass, code_arm_required) auto_bypass=arm_options[CONF_AUTO_BYPASS],
add_entities([entity]) code_arm_required=arm_options[CONF_CODE_ARM_REQUIRED],
alt_night_mode=arm_options[CONF_ALT_NIGHT_MODE],
)
async_add_entities([entity])
def alarm_toggle_chime_handler(service): platform = entity_platform.current_platform.get()
"""Register toggle chime handler."""
code = service.data.get(ATTR_CODE)
entity.alarm_toggle_chime(code)
hass.services.register( platform.async_register_entity_service(
DOMAIN,
SERVICE_ALARM_TOGGLE_CHIME, SERVICE_ALARM_TOGGLE_CHIME,
alarm_toggle_chime_handler, {
schema=ALARM_TOGGLE_CHIME_SCHEMA, vol.Required(ATTR_CODE): cv.string,
},
"alarm_toggle_chime",
) )
def alarm_keypress_handler(service): platform.async_register_entity_service(
"""Register keypress handler."""
keypress = service.data[ATTR_KEYPRESS]
entity.alarm_keypress(keypress)
hass.services.register(
DOMAIN,
SERVICE_ALARM_KEYPRESS, SERVICE_ALARM_KEYPRESS,
alarm_keypress_handler, {
schema=ALARM_KEYPRESS_SCHEMA, vol.Required(ATTR_KEYPRESS): cv.string,
},
"alarm_keypress",
) )
class AlarmDecoderAlarmPanel(AlarmControlPanelEntity): class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
"""Representation of an AlarmDecoder-based alarm panel.""" """Representation of an AlarmDecoder-based alarm panel."""
def __init__(self, auto_bypass, code_arm_required): def __init__(self, client, auto_bypass, code_arm_required, alt_night_mode):
"""Initialize the alarm panel.""" """Initialize the alarm panel."""
self._client = client
self._display = "" self._display = ""
self._name = "Alarm Panel" self._name = "Alarm Panel"
self._state = None self._state = None
@ -95,6 +100,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
self._zone_bypassed = None self._zone_bypassed = None
self._auto_bypass = auto_bypass self._auto_bypass = auto_bypass
self._code_arm_required = code_arm_required self._code_arm_required = code_arm_required
self._alt_night_mode = alt_night_mode
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
@ -180,11 +186,11 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
if code: if code:
self.hass.data[DATA_AD].send(f"{code!s}1") self._client.send(f"{code!s}1")
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
"""Send arm away command.""" """Send arm away command."""
self.hass.data[DATA_AD].arm_away( self._client.arm_away(
code=code, code=code,
code_arm_required=self._code_arm_required, code_arm_required=self._code_arm_required,
auto_bypass=self._auto_bypass, auto_bypass=self._auto_bypass,
@ -192,7 +198,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
"""Send arm home command.""" """Send arm home command."""
self.hass.data[DATA_AD].arm_home( self._client.arm_home(
code=code, code=code,
code_arm_required=self._code_arm_required, code_arm_required=self._code_arm_required,
auto_bypass=self._auto_bypass, auto_bypass=self._auto_bypass,
@ -200,18 +206,19 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
def alarm_arm_night(self, code=None): def alarm_arm_night(self, code=None):
"""Send arm night command.""" """Send arm night command."""
self.hass.data[DATA_AD].arm_night( self._client.arm_night(
code=code, code=code,
code_arm_required=self._code_arm_required, code_arm_required=self._code_arm_required,
alt_night_mode=self._alt_night_mode,
auto_bypass=self._auto_bypass, auto_bypass=self._auto_bypass,
) )
def alarm_toggle_chime(self, code=None): def alarm_toggle_chime(self, code=None):
"""Send toggle chime command.""" """Send toggle chime command."""
if code: if code:
self.hass.data[DATA_AD].send(f"{code!s}9") self._client.send(f"{code!s}9")
def alarm_keypress(self, keypress): def alarm_keypress(self, keypress):
"""Send custom keypresses.""" """Send custom keypresses."""
if keypress: if keypress:
self.hass.data[DATA_AD].send(keypress) self._client.send(keypress)

View File

@ -2,20 +2,23 @@
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
from . import ( from .const import (
CONF_RELAY_ADDR, CONF_RELAY_ADDR,
CONF_RELAY_CHAN, CONF_RELAY_CHAN,
CONF_ZONE_LOOP, CONF_ZONE_LOOP,
CONF_ZONE_NAME, CONF_ZONE_NAME,
CONF_ZONE_NUMBER,
CONF_ZONE_RFID, CONF_ZONE_RFID,
CONF_ZONE_TYPE, CONF_ZONE_TYPE,
CONF_ZONES, DEFAULT_ZONE_OPTIONS,
OPTIONS_ZONES,
SIGNAL_REL_MESSAGE, SIGNAL_REL_MESSAGE,
SIGNAL_RFX_MESSAGE, SIGNAL_RFX_MESSAGE,
SIGNAL_ZONE_FAULT, SIGNAL_ZONE_FAULT,
SIGNAL_ZONE_RESTORE, SIGNAL_ZONE_RESTORE,
ZONE_SCHEMA,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -30,27 +33,28 @@ ATTR_RF_LOOP4 = "rf_loop4"
ATTR_RF_LOOP1 = "rf_loop1" ATTR_RF_LOOP1 = "rf_loop1"
def setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_entry(
"""Set up the AlarmDecoder binary sensor devices.""" hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
configured_zones = discovery_info[CONF_ZONES] ):
"""Set up for AlarmDecoder sensor."""
devices = [] zones = entry.options.get(OPTIONS_ZONES, DEFAULT_ZONE_OPTIONS)
for zone_num in configured_zones:
device_config_data = ZONE_SCHEMA(configured_zones[zone_num]) entities = []
zone_type = device_config_data[CONF_ZONE_TYPE] for zone_num in zones:
zone_name = device_config_data[CONF_ZONE_NAME] zone_info = zones[zone_num]
zone_rfid = device_config_data.get(CONF_ZONE_RFID) zone_type = zone_info[CONF_ZONE_TYPE]
zone_loop = device_config_data.get(CONF_ZONE_LOOP) zone_name = zone_info[CONF_ZONE_NAME]
relay_addr = device_config_data.get(CONF_RELAY_ADDR) zone_rfid = zone_info.get(CONF_ZONE_RFID)
relay_chan = device_config_data.get(CONF_RELAY_CHAN) zone_loop = zone_info.get(CONF_ZONE_LOOP)
device = AlarmDecoderBinarySensor( relay_addr = zone_info.get(CONF_RELAY_ADDR)
relay_chan = zone_info.get(CONF_RELAY_CHAN)
entity = AlarmDecoderBinarySensor(
zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr, relay_chan zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr, relay_chan
) )
devices.append(device) entities.append(entity)
add_entities(devices) async_add_entities(entities)
return True
class AlarmDecoderBinarySensor(BinarySensorEntity): class AlarmDecoderBinarySensor(BinarySensorEntity):
@ -67,7 +71,7 @@ class AlarmDecoderBinarySensor(BinarySensorEntity):
relay_chan, relay_chan,
): ):
"""Initialize the binary_sensor.""" """Initialize the binary_sensor."""
self._zone_number = zone_number self._zone_number = int(zone_number)
self._zone_type = zone_type self._zone_type = zone_type
self._state = None self._state = None
self._name = zone_name self._name = zone_name
@ -117,6 +121,7 @@ class AlarmDecoderBinarySensor(BinarySensorEntity):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
attr = {} attr = {}
attr[CONF_ZONE_NUMBER] = self._zone_number
if self._rfid and self._rfstate is not None: if self._rfid and self._rfstate is not None:
attr[ATTR_RF_BIT0] = bool(self._rfstate & 0x01) attr[ATTR_RF_BIT0] = bool(self._rfstate & 0x01)
attr[ATTR_RF_LOW_BAT] = bool(self._rfstate & 0x02) attr[ATTR_RF_LOW_BAT] = bool(self._rfstate & 0x02)

View File

@ -0,0 +1,360 @@
"""Config flow for AlarmDecoder."""
import logging
from adext import AdExt
from alarmdecoder.devices import SerialDevice, SocketDevice
from alarmdecoder.util import NoDeviceError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.binary_sensor import DEVICE_CLASSES
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PROTOCOL
from homeassistant.core import callback
from .const import ( # pylint: disable=unused-import
CONF_ALT_NIGHT_MODE,
CONF_AUTO_BYPASS,
CONF_CODE_ARM_REQUIRED,
CONF_DEVICE_BAUD,
CONF_DEVICE_PATH,
CONF_RELAY_ADDR,
CONF_RELAY_CHAN,
CONF_ZONE_LOOP,
CONF_ZONE_NAME,
CONF_ZONE_NUMBER,
CONF_ZONE_RFID,
CONF_ZONE_TYPE,
DEFAULT_ARM_OPTIONS,
DEFAULT_DEVICE_BAUD,
DEFAULT_DEVICE_HOST,
DEFAULT_DEVICE_PATH,
DEFAULT_DEVICE_PORT,
DEFAULT_ZONE_OPTIONS,
DEFAULT_ZONE_TYPE,
DOMAIN,
OPTIONS_ARM,
OPTIONS_ZONES,
PROTOCOL_SERIAL,
PROTOCOL_SOCKET,
)
EDIT_KEY = "edit_selection"
EDIT_ZONES = "Zones"
EDIT_SETTINGS = "Arming Settings"
_LOGGER = logging.getLogger(__name__)
class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a AlarmDecoder config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
def __init__(self):
"""Initialize AlarmDecoder ConfigFlow."""
self.protocol = None
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for AlarmDecoder."""
return AlarmDecoderOptionsFlowHandler(config_entry)
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is not None:
self.protocol = user_input[CONF_PROTOCOL]
return await self.async_step_protocol()
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_PROTOCOL): vol.In(
[PROTOCOL_SOCKET, PROTOCOL_SERIAL]
),
}
),
)
async def async_step_protocol(self, user_input=None):
"""Handle AlarmDecoder protocol setup."""
errors = {}
if user_input is not None:
if _device_already_added(
self._async_current_entries(), user_input, self.protocol
):
return self.async_abort(reason="already_configured")
connection = {}
baud = None
if self.protocol == PROTOCOL_SOCKET:
host = connection[CONF_HOST] = user_input[CONF_HOST]
port = connection[CONF_PORT] = user_input[CONF_PORT]
title = f"{host}:{port}"
device = SocketDevice(interface=(host, port))
if self.protocol == PROTOCOL_SERIAL:
path = connection[CONF_DEVICE_PATH] = user_input[CONF_DEVICE_PATH]
baud = connection[CONF_DEVICE_BAUD] = user_input[CONF_DEVICE_BAUD]
title = path
device = SerialDevice(interface=path)
controller = AdExt(device)
def test_connection():
controller.open(baud)
controller.close()
try:
await self.hass.async_add_executor_job(test_connection)
return self.async_create_entry(
title=title, data={CONF_PROTOCOL: self.protocol, **connection}
)
except NoDeviceError:
errors["base"] = "service_unavailable"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception during AlarmDecoder setup")
errors["base"] = "unknown"
if self.protocol == PROTOCOL_SOCKET:
schema = vol.Schema(
{
vol.Required(CONF_HOST, default=DEFAULT_DEVICE_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_DEVICE_PORT): int,
}
)
if self.protocol == PROTOCOL_SERIAL:
schema = vol.Schema(
{
vol.Required(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): str,
vol.Required(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): int,
}
)
return self.async_show_form(
step_id="protocol",
data_schema=schema,
errors=errors,
)
class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle AlarmDecoder options."""
def __init__(self, config_entry: config_entries.ConfigEntry):
"""Initialize AlarmDecoder options flow."""
self.arm_options = config_entry.options.get(OPTIONS_ARM, DEFAULT_ARM_OPTIONS)
self.zone_options = config_entry.options.get(
OPTIONS_ZONES, DEFAULT_ZONE_OPTIONS
)
self.selected_zone = None
async def async_step_init(self, user_input=None):
"""Manage the options."""
if user_input is not None:
if user_input[EDIT_KEY] == EDIT_SETTINGS:
return await self.async_step_arm_settings()
if user_input[EDIT_KEY] == EDIT_ZONES:
return await self.async_step_zone_select()
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(EDIT_KEY, default=EDIT_SETTINGS): vol.In(
[EDIT_SETTINGS, EDIT_ZONES]
)
},
),
)
async def async_step_arm_settings(self, user_input=None):
"""Arming options form."""
if user_input is not None:
return self.async_create_entry(
title="",
data={OPTIONS_ARM: user_input, OPTIONS_ZONES: self.zone_options},
)
return self.async_show_form(
step_id="arm_settings",
data_schema=vol.Schema(
{
vol.Optional(
CONF_ALT_NIGHT_MODE,
default=self.arm_options[CONF_ALT_NIGHT_MODE],
): bool,
vol.Optional(
CONF_AUTO_BYPASS, default=self.arm_options[CONF_AUTO_BYPASS]
): bool,
vol.Optional(
CONF_CODE_ARM_REQUIRED,
default=self.arm_options[CONF_CODE_ARM_REQUIRED],
): bool,
},
),
)
async def async_step_zone_select(self, user_input=None):
"""Zone selection form."""
errors = _validate_zone_input(user_input)
if user_input is not None and not errors:
self.selected_zone = str(
int(user_input[CONF_ZONE_NUMBER])
) # remove leading zeros
return await self.async_step_zone_details()
return self.async_show_form(
step_id="zone_select",
data_schema=vol.Schema({vol.Required(CONF_ZONE_NUMBER): str}),
errors=errors,
)
async def async_step_zone_details(self, user_input=None):
"""Zone details form."""
errors = _validate_zone_input(user_input)
if user_input is not None and not errors:
zone_options = self.zone_options.copy()
zone_id = self.selected_zone
zone_options[zone_id] = _fix_input_types(user_input)
# Delete zone entry if zone_name is omitted
if CONF_ZONE_NAME not in zone_options[zone_id]:
zone_options.pop(zone_id)
return self.async_create_entry(
title="",
data={OPTIONS_ARM: self.arm_options, OPTIONS_ZONES: zone_options},
)
existing_zone_settings = self.zone_options.get(self.selected_zone, {})
return self.async_show_form(
step_id="zone_details",
description_placeholders={CONF_ZONE_NUMBER: self.selected_zone},
data_schema=vol.Schema(
{
vol.Optional(
CONF_ZONE_NAME,
description={
"suggested_value": existing_zone_settings.get(
CONF_ZONE_NAME
)
},
): str,
vol.Optional(
CONF_ZONE_TYPE,
default=existing_zone_settings.get(
CONF_ZONE_TYPE, DEFAULT_ZONE_TYPE
),
): vol.In(DEVICE_CLASSES),
vol.Optional(
CONF_ZONE_RFID,
description={
"suggested_value": existing_zone_settings.get(
CONF_ZONE_RFID
)
},
): str,
vol.Optional(
CONF_ZONE_LOOP,
description={
"suggested_value": existing_zone_settings.get(
CONF_ZONE_LOOP
)
},
): str,
vol.Optional(
CONF_RELAY_ADDR,
description={
"suggested_value": existing_zone_settings.get(
CONF_RELAY_ADDR
)
},
): str,
vol.Optional(
CONF_RELAY_CHAN,
description={
"suggested_value": existing_zone_settings.get(
CONF_RELAY_CHAN
)
},
): str,
}
),
errors=errors,
)
def _validate_zone_input(zone_input):
if not zone_input:
return {}
errors = {}
# CONF_RELAY_ADDR & CONF_RELAY_CHAN are inclusive
if (CONF_RELAY_ADDR in zone_input and CONF_RELAY_CHAN not in zone_input) or (
CONF_RELAY_ADDR not in zone_input and CONF_RELAY_CHAN in zone_input
):
errors["base"] = "relay_inclusive"
# The following keys must be int
for key in [CONF_ZONE_NUMBER, CONF_ZONE_LOOP, CONF_RELAY_ADDR, CONF_RELAY_CHAN]:
if key in zone_input:
try:
int(zone_input[key])
except ValueError:
errors[key] = "int"
# CONF_ZONE_LOOP depends on CONF_ZONE_RFID
if CONF_ZONE_LOOP in zone_input and CONF_ZONE_RFID not in zone_input:
errors[CONF_ZONE_LOOP] = "loop_rfid"
# CONF_ZONE_LOOP must be 1-4
if (
CONF_ZONE_LOOP in zone_input
and zone_input[CONF_ZONE_LOOP].isdigit()
and int(zone_input[CONF_ZONE_LOOP]) not in list(range(1, 5))
):
errors[CONF_ZONE_LOOP] = "loop_range"
return errors
def _fix_input_types(zone_input):
"""Convert necessary keys to int.
Since ConfigFlow inputs of type int cannot default to an empty string, we collect the values below as
strings and then convert them to ints.
"""
for key in [CONF_ZONE_LOOP, CONF_RELAY_ADDR, CONF_RELAY_CHAN]:
if key in zone_input:
zone_input[key] = int(zone_input[key])
return zone_input
def _device_already_added(current_entries, user_input, protocol):
"""Determine if entry has already been added to HA."""
user_host = user_input.get(CONF_HOST)
user_port = user_input.get(CONF_PORT)
user_path = user_input.get(CONF_DEVICE_PATH)
user_baud = user_input.get(CONF_DEVICE_BAUD)
for entry in current_entries:
entry_host = entry.data.get(CONF_HOST)
entry_port = entry.data.get(CONF_PORT)
entry_path = entry.data.get(CONF_DEVICE_PATH)
entry_baud = entry.data.get(CONF_DEVICE_BAUD)
if protocol == PROTOCOL_SOCKET:
if user_host == entry_host and user_port == entry_port:
return True
if protocol == PROTOCOL_SERIAL:
if user_baud == entry_baud and user_path == entry_path:
return True
return False

View File

@ -0,0 +1,49 @@
"""Constants for the AlarmDecoder component."""
CONF_ALT_NIGHT_MODE = "alt_night_mode"
CONF_AUTO_BYPASS = "auto_bypass"
CONF_CODE_ARM_REQUIRED = "code_arm_required"
CONF_DEVICE_BAUD = "device_baudrate"
CONF_DEVICE_PATH = "device_path"
CONF_RELAY_ADDR = "zone_relayaddr"
CONF_RELAY_CHAN = "zone_relaychan"
CONF_ZONE_LOOP = "zone_loop"
CONF_ZONE_NAME = "zone_name"
CONF_ZONE_NUMBER = "zone_number"
CONF_ZONE_RFID = "zone_rfid"
CONF_ZONE_TYPE = "zone_type"
DATA_AD = "alarmdecoder"
DATA_REMOVE_STOP_LISTENER = "rm_stop_listener"
DATA_REMOVE_UPDATE_LISTENER = "rm_update_listener"
DATA_RESTART = "restart"
DEFAULT_ALT_NIGHT_MODE = False
DEFAULT_AUTO_BYPASS = False
DEFAULT_CODE_ARM_REQUIRED = True
DEFAULT_DEVICE_BAUD = 115200
DEFAULT_DEVICE_HOST = "alarmdecoder"
DEFAULT_DEVICE_PATH = "/dev/ttyUSB0"
DEFAULT_DEVICE_PORT = 10000
DEFAULT_ZONE_TYPE = "window"
DEFAULT_ARM_OPTIONS = {
CONF_ALT_NIGHT_MODE: DEFAULT_ALT_NIGHT_MODE,
CONF_AUTO_BYPASS: DEFAULT_AUTO_BYPASS,
CONF_CODE_ARM_REQUIRED: DEFAULT_CODE_ARM_REQUIRED,
}
DEFAULT_ZONE_OPTIONS = {}
DOMAIN = "alarmdecoder"
OPTIONS_ARM = "arm_options"
OPTIONS_ZONES = "zone_options"
PROTOCOL_SERIAL = "serial"
PROTOCOL_SOCKET = "socket"
SIGNAL_PANEL_MESSAGE = "alarmdecoder.panel_message"
SIGNAL_REL_MESSAGE = "alarmdecoder.rel_message"
SIGNAL_RFX_MESSAGE = "alarmdecoder.rfx_message"
SIGNAL_ZONE_FAULT = "alarmdecoder.zone_fault"
SIGNAL_ZONE_RESTORE = "alarmdecoder.zone_restore"

View File

@ -3,5 +3,6 @@
"name": "AlarmDecoder", "name": "AlarmDecoder",
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
"requirements": ["adext==0.3"], "requirements": ["adext==0.3"],
"codeowners": ["@ajschmidt8"] "codeowners": ["@ajschmidt8"],
"config_flow": true
} }

View File

@ -1,26 +1,29 @@
"""Support for AlarmDecoder sensors (Shows Panel Display).""" """Support for AlarmDecoder sensors (Shows Panel Display)."""
import logging import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType
from . import SIGNAL_PANEL_MESSAGE from .const import SIGNAL_PANEL_MESSAGE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_entry(
"""Set up for AlarmDecoder sensor devices.""" hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
_LOGGER.debug("AlarmDecoderSensor: setup_platform") ):
"""Set up for AlarmDecoder sensor."""
device = AlarmDecoderSensor(hass) entity = AlarmDecoderSensor()
async_add_entities([entity])
add_entities([device]) return True
class AlarmDecoderSensor(Entity): class AlarmDecoderSensor(Entity):
"""Representation of an AlarmDecoder keypad.""" """Representation of an AlarmDecoder keypad."""
def __init__(self, hass): def __init__(self):
"""Initialize the alarm panel.""" """Initialize the alarm panel."""
self._display = "" self._display = ""
self._state = None self._state = None

View File

@ -1,6 +1,9 @@
alarm_keypress: alarm_keypress:
description: Send custom keypresses to the alarm. description: Send custom keypresses to the alarm.
fields: fields:
entity_id:
description: Name of alarm control panel to deliver keypress.
example: "alarm_control_panel.main"
keypress: keypress:
description: "String to send to the alarm panel." description: "String to send to the alarm panel."
example: "*71" example: "*71"
@ -8,6 +11,9 @@ alarm_keypress:
alarm_toggle_chime: alarm_toggle_chime:
description: Send the alarm the toggle chime command. description: Send the alarm the toggle chime command.
fields: fields:
entity_id:
description: Name of alarm control panel to toggle chime.
example: "alarm_control_panel.main"
code: code:
description: A required code to toggle the alarm control panel chime with. description: A required code to toggle the alarm control panel chime with.
example: 1234 example: 1234

View File

@ -0,0 +1,72 @@
{
"config": {
"step": {
"user": {
"title": "Choose AlarmDecoder Protocol",
"data": {
"protocol": "Protocol"
}
},
"protocol": {
"title": "Configure connection settings",
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]",
"device_baudrate": "Device Baud Rate",
"device_path": "Device Path"
}
}
},
"error": {
"service_unavailable": "[%key:common::config_flow::error::cannot_connect%]"
},
"create_entry": { "default": "Successfully connected to AlarmDecoder." },
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"options": {
"step": {
"init": {
"title": "Configure AlarmDecoder",
"description": "What would you like to edit?",
"data": {
"edit_select": "Edit"
}
},
"arm_settings": {
"title": "Configure AlarmDecoder",
"data": {
"auto_bypass": "Auto Bypass on Arm",
"code_arm_required": "Code Required for Arming",
"alt_night_mode": "Alternative Night Mode"
}
},
"zone_select": {
"title": "Configure AlarmDecoder",
"description": "Enter the zone number you'd like to to add, edit, or remove.",
"data": {
"zone_number": "Zone Number"
}
},
"zone_details": {
"title": "Configure AlarmDecoder",
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave Zone Name blank.",
"data": {
"zone_name": "Zone Name",
"zone_type": "Zone Type",
"zone_rfid": "RF Serial",
"zone_loop": "RF Loop",
"zone_relayaddr": "Relay Address",
"zone_relaychan": "Relay Channel"
}
}
},
"error": {
"relay_inclusive": "Relay Address and Relay Channel are codependent and must be included together.",
"int": "The field below must be an integer.",
"loop_rfid": "RF Loop cannot be used without RF Serial.",
"loop_range": "RF Loop must be an integer between 1 and 4."
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat"
},
"create_entry": {
"default": "S'ha connectat correctament amb AlarmDecoder."
},
"error": {
"service_unavailable": "Ha fallat la connexi\u00f3"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Velocitat, en baudis, del dispositiu",
"device_path": "Ruta del dispositiu",
"host": "Amfitri\u00f3",
"port": "Port"
},
"title": "Configuraci\u00f3 dels par\u00e0metres de connexi\u00f3"
},
"user": {
"data": {
"protocol": "Protocol"
},
"title": "Selecciona el protocol d'AlarmDecoder"
}
}
},
"options": {
"error": {
"int": "El camp seg\u00fcent ha de ser un nombre enter.",
"loop_range": "El bucle RF ha de ser un nombre enter entre 1 i 4.",
"loop_rfid": "El bucle RF no es pot utilitzar sense RF s\u00e8rie.",
"relay_inclusive": "L'adre\u00e7a i el canal de rel\u00e9 s\u00f3n codependents i s'han d'incloure junts."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Mode nocturn alternatiu",
"auto_bypass": "Bypass autom\u00e0tic en l'activaci\u00f3",
"code_arm_required": "Codi necessari per a l'activaci\u00f3"
},
"title": "Configuraci\u00f3 d'AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Edita"
},
"description": "Qu\u00e8 voldries editar?",
"title": "Configuraci\u00f3 d'AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "Bucle RF",
"zone_name": "Nom de la zona",
"zone_relayaddr": "Adre\u00e7a del rel\u00e9",
"zone_relaychan": "Canal del rel\u00e9",
"zone_rfid": "RF s\u00e8rie",
"zone_type": "Tipus de zona"
},
"description": "Introdueix els detalls de la zona {zone_number}. Per suprimir la zona {zone_number}, deixa el nom de la zona en blanc.",
"title": "Configuraci\u00f3 d'AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "N\u00famero de zona"
},
"description": "Introdueix el n\u00famero de zona que vulguis afegir, editar o eliminar.",
"title": "Configuraci\u00f3 d'AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,35 @@
{
"options": {
"step": {
"arm_settings": {
"title": "Konfigurovat AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Upravit"
},
"description": "Co chcete upravit?",
"title": "Konfigurovat AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF Loop",
"zone_name": "N\u00e1zev z\u00f3ny",
"zone_relayaddr": "Relay adresa",
"zone_relaychan": "Relay kan\u00e1l",
"zone_rfid": "RF Serial",
"zone_type": "Typ z\u00f3ny"
},
"description": "Zadejte podrobnosti pro z\u00f3nu {zone_number}. Chcete-li odstranit z\u00f3nu {zone_number}, ponechejte n\u00e1zev z\u00f3ny pr\u00e1zdn\u00fd.",
"title": "Konfigurovat AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "\u010c\u00edslo z\u00f3ny"
},
"description": "Zadejte \u010d\u00edslo z\u00f3ny, kterou chcete p\u0159idat, upravit nebo odstranit.",
"title": "Konfigurovat AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,48 @@
{
"config": {
"error": {
"service_unavailable": "Verbindung konnte nicht hergestellt werden"
},
"step": {
"protocol": {
"data": {
"host": "Host",
"port": "Port"
}
},
"user": {
"data": {
"protocol": "Protokoll"
}
}
}
},
"options": {
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternativer Nachtmodus"
}
},
"init": {
"data": {
"edit_select": "Bearbeiten"
},
"description": "Was m\u00f6chtest du bearbeiten?"
},
"zone_details": {
"data": {
"zone_name": "Zonenname",
"zone_relayaddr": "Relais-Adresse",
"zone_type": "Zonentyp"
}
},
"zone_select": {
"data": {
"zone_number": "Zonennummer"
},
"description": "Geben Sie die Zonennummer ein, die Sie hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chten."
}
}
}
}

View File

@ -0,0 +1,34 @@
{
"config": {
"create_entry": {
"default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf AlarmDecoder."
},
"error": {
"service_unavailable": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "\u03a1\u03c5\u03b8\u03bc\u03cc\u03c2 Baud \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2",
"device_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2"
}
}
}
},
"options": {
"step": {
"zone_details": {
"data": {
"zone_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b6\u03ce\u03bd\u03b7\u03c2"
},
"title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b6\u03ce\u03bd\u03b7\u03c2"
},
"title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured"
},
"create_entry": {
"default": "Successfully connected to AlarmDecoder."
},
"error": {
"service_unavailable": "Failed to connect"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Device Baud Rate",
"device_path": "Device Path",
"host": "Host",
"port": "Port"
},
"title": "Configure connection settings"
},
"user": {
"data": {
"protocol": "Protocol"
},
"title": "Choose AlarmDecoder Protocol"
}
}
},
"options": {
"error": {
"int": "The field below must be an integer.",
"loop_range": "RF Loop must be an integer between 1 and 4.",
"loop_rfid": "RF Loop cannot be used without RF Serial.",
"relay_inclusive": "Relay Address and Relay Channel are codependent and must be included together."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternative Night Mode",
"auto_bypass": "Auto Bypass on Arm",
"code_arm_required": "Code Required for Arming"
},
"title": "Configure AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Edit"
},
"description": "What would you like to edit?",
"title": "Configure AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF Loop",
"zone_name": "Zone Name",
"zone_relayaddr": "Relay Address",
"zone_relaychan": "Relay Channel",
"zone_rfid": "RF Serial",
"zone_type": "Zone Type"
},
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave Zone Name blank.",
"title": "Configure AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Zone Number"
},
"description": "Enter the zone number you'd like to to add, edit, or remove.",
"title": "Configure AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "El dispositivo AlarmDecoder ya est\u00e1 configurado."
},
"create_entry": {
"default": "Conectado con \u00e9xito a AlarmDecoder."
},
"error": {
"service_unavailable": "No se pudo conectar"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Velocidad en baudios del dispositivo",
"device_path": "Ruta del dispositivo",
"host": "Host",
"port": "Puerto"
},
"title": "Configurar los ajustes de conexi\u00f3n"
},
"user": {
"data": {
"protocol": "Protocolo"
},
"title": "Elige el protocolo del AlarmDecoder"
}
}
},
"options": {
"error": {
"int": "El campo siguiente debe ser un n\u00famero entero.",
"loop_range": "El bucle RF debe ser un n\u00famero entero entre 1 y 4.",
"loop_rfid": "El bucle de RF no puede utilizarse sin el serie RF.",
"relay_inclusive": "La direcci\u00f3n de retransmisi\u00f3n y el canal de retransmisi\u00f3n son codependientes y deben incluirse a la vez."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Modo noche alternativo",
"auto_bypass": "Desv\u00edo autom\u00e1tico al armar",
"code_arm_required": "C\u00f3digo requerido para el armado"
},
"title": "Configurar AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Editar"
},
"description": "\u00bfQu\u00e9 te gustar\u00eda editar?",
"title": "Configurar AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "Bucle RF",
"zone_name": "Nombre de zona",
"zone_relayaddr": "Direcci\u00f3n de retransmisi\u00f3n",
"zone_relaychan": "Canal de retransmisi\u00f3n",
"zone_rfid": "Serie RF",
"zone_type": "Tipo de zona"
},
"description": "Introduce los detalles para la zona {zona_number}. Para borrar la zona {zone_number}, deja el nombre de la zona en blanco.",
"title": "Configurar AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "N\u00famero de zona"
},
"description": "Introduce el n\u00famero de zona que deseas a\u00f1adir, editar o eliminar.",
"title": "Configurar AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,49 @@
{
"config": {
"step": {
"user": {
"data": {
"protocol": "Protokoll"
}
}
}
},
"options": {
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternatiivne \u00f6\u00f6re\u017eiim",
"auto_bypass": "Automaatne m\u00f6\u00f6daviik valvestamisel",
"code_arm_required": "Valvestamise kood"
},
"title": "Seadista AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Muuda"
},
"description": "Mida Te soovite muuta?",
"title": "Seadista AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF silmus",
"zone_name": "Ala nimi",
"zone_relayaddr": "Relee aadress",
"zone_relaychan": "Relee kanalinumber",
"zone_rfid": "RF jada\u00fchendus",
"zone_type": "Ala t\u00fc\u00fcp"
},
"description": "Sisestage ala {zone_number} \u00fcksikasjad. Ala {zone_number} kustutamiseks j\u00e4tke ala nimi t\u00fchjaks.",
"title": "Seadista AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Ala number"
},
"description": "Sisestage ala number mida soovite lisada, muuta v\u00f5i eemaldada.",
"title": "Seadista AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"create_entry": {
"default": "Connexion r\u00e9ussie \u00e0 AlarmDecoder."
},
"error": {
"service_unavailable": "\u00c9chec de connexion"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "D\u00e9bit en bauds de l'appareil",
"device_path": "Chemin du p\u00e9riph\u00e9rique",
"host": "H\u00f4te",
"port": "Port"
},
"title": "Configurer les param\u00e8tres de connexion"
},
"user": {
"data": {
"protocol": "Protocole"
},
"title": "Choisissez le protocole AlarmDecoder"
}
}
},
"options": {
"error": {
"int": "Le champ ci-dessous doit \u00eatre un entier.",
"loop_range": "La boucle RF doit \u00eatre un entier compris entre 1 et 4.",
"loop_rfid": "La boucle RF ne peut pas \u00eatre utilis\u00e9e sans s\u00e9rie RF.",
"relay_inclusive": "L'adresse de relais et le canal de relais d\u00e9pendent du codage et doivent \u00eatre inclus ensemble."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Mode nuit alternatif",
"auto_bypass": "Bypass automatique \u00e0 l'armement",
"code_arm_required": "Code requis pour l'armement"
},
"title": "Configurer AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Modifier"
},
"description": "Que voulez-vous modifier?",
"title": "Configurer AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "Boucle RF",
"zone_name": "Nom de zone",
"zone_relayaddr": "Adresse de relais",
"zone_relaychan": "Canal de relais",
"zone_rfid": "RF S\u00e9rie",
"zone_type": "Type de zone"
},
"description": "Entrez les d\u00e9tails de la zone {zone_number} . Pour supprimer la zone {zone_number} , laissez le nom de zone vide.",
"title": "Configurer AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Num\u00e9ro de zone"
},
"description": "Saisissez le num\u00e9ro de zone que vous souhaitez ajouter, modifier ou supprimer.",
"title": "Configurer AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato"
},
"create_entry": {
"default": "Collegato con successo ad AlarmDecoder."
},
"error": {
"service_unavailable": "Impossibile connettersi"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Velocit\u00e0 di trasmissione del dispositivo",
"device_path": "Percorso del dispositivo",
"host": "Host",
"port": "Porta"
},
"title": "Configurare le impostazioni di connessione"
},
"user": {
"data": {
"protocol": "Protocollo"
},
"title": "Scegliere il protocollo AlarmDecoder"
}
}
},
"options": {
"error": {
"int": "Il campo sottostante deve essere un numero intero.",
"loop_range": "Il Ciclo RF deve essere un numero intero compreso tra 1 e 4.",
"loop_rfid": "Il Ciclo RF non pu\u00f2 essere utilizzato senza il Seriale RF ",
"relay_inclusive": "L'indirizzo del rel\u00e8 e il canale del rel\u00e8 sono codipendenti e devono essere inclusi insieme."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Modalit\u00e0 notturna alternativa",
"auto_bypass": "Bypass automatico all'attivazione",
"code_arm_required": "Codice richiesto per l'attivazione"
},
"title": "Configurare AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Modifica"
},
"description": "Cosa vorresti modificare?",
"title": "Configurare AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "Ciclo RF",
"zone_name": "Nome zona",
"zone_relayaddr": "Indirizzo rel\u00e8",
"zone_relaychan": "Canale rel\u00e8",
"zone_rfid": "Seriale RF",
"zone_type": "Tipo di zona"
},
"description": "Immettere i dettagli per la zona {zone_number}. Per eliminare la zona {zone_number}, lasciare vuoto il campo Nome zona.",
"title": "Configurare AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Numero di zona"
},
"description": "Immettere il numero di zona che si desidera aggiungere, modificare o rimuovere.",
"title": "Configurare AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4."
},
"create_entry": {
"default": "AlarmDecoder\uc5d0 \uc131\uacf5\uc801\uc73c\ub85c \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4."
},
"error": {
"service_unavailable": "\uc5f0\uacb0 \uc2e4\ud328"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "\uc7a5\uce58 \uc804\uc1a1 \uc18d\ub3c4",
"device_path": "\uc7a5\uce58 \uacbd\ub85c",
"host": "\ud638\uc2a4\ud2b8",
"port": "\ud3ec\ud2b8"
},
"title": "\uc5f0\uacb0 \uc124\uc815 \uad6c\uc131"
},
"user": {
"data": {
"protocol": "\ud504\ub85c\ud1a0\ucf5c"
},
"title": "AlarmDecoder \ud504\ub85c\ud1a0\ucf5c \uc120\ud0dd"
}
}
},
"options": {
"error": {
"int": "\uc544\ub798 \ud544\ub4dc\ub294 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.",
"loop_range": "RF \ub8e8\ud504\ub294 1\uc5d0\uc11c 4 \uc0ac\uc774\uc758 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.",
"loop_rfid": "RF \ub8e8\ud504\ub294 RF \uc2dc\ub9ac\uc5bc\uc5c6\uc774 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.",
"relay_inclusive": "\ub9b4\ub808\uc774 \uc8fc\uc18c\uc640 \ub9b4\ub808\uc774 \ucc44\ub110\uc740 \uc11c\ub85c \uc758\uc874\uc801\uc774\uba70 \ud568\uaed8 \ud3ec\ud568\ub418\uc5b4\uc57c\ud569\ub2c8\ub2e4."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "\ub300\uccb4 \uc57c\uac04 \ubaa8\ub4dc",
"auto_bypass": "\uacbd\ube44\uc911 \uc790\ub3d9 \uc6b0\ud68c",
"code_arm_required": "\uacbd\ube44\uc5d0 \ud544\uc694\ud55c \ucf54\ub4dc"
},
"title": "AlarmDecoder \uad6c\uc131"
},
"init": {
"data": {
"edit_select": "\ud3b8\uc9d1"
},
"description": "\ubb34\uc5c7\uc744 \ud3b8\uc9d1 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "AlarmDecoder \uad6c\uc131"
},
"zone_details": {
"data": {
"zone_loop": "RF \ub8e8\ud504",
"zone_name": "\uc601\uc5ed \uc774\ub984",
"zone_relayaddr": "\ub9b4\ub808\uc774 \uc8fc\uc18c",
"zone_relaychan": "\ub9b4\ub808\uc774 \ucc44\ub110",
"zone_rfid": "RF \uc2dc\ub9ac\uc5bc",
"zone_type": "\uc601\uc5ed \uc720\ud615"
},
"description": "{zone_number} \uc601\uc5ed\uc5d0 \ub300\ud55c \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud569\ub2c8\ub2e4. {zone_number} \uc601\uc5ed\uc744 \uc0ad\uc81c\ud558\ub824\uba74 \uc601\uc5ed \uc774\ub984\uc744 \ube44\uc6cc \ub461\ub2c8\ub2e4.",
"title": "AlarmDecoder \uad6c\uc131"
},
"zone_select": {
"data": {
"zone_number": "\uad6c\uc5ed \ubc88\ud638"
},
"description": "\ucd94\uac00, \ud3b8\uc9d1 \ub610\ub294 \uc81c\uac70\ud560 \uc601\uc5ed \ubc88\ud638\ub97c \uc785\ub825\ud569\ub2c8\ub2e4.",
"title": "AlarmDecoder \uad6c\uc131"
}
}
}
}

View File

@ -0,0 +1,64 @@
{
"config": {
"abort": {
"already_configured": "Apparat ass scho konfigur\u00e9iert"
},
"error": {
"service_unavailable": "Feeler beim verbannen"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Apparat Baudrate",
"device_path": "Pad vum Apparat",
"host": "Host",
"port": "Port"
}
},
"user": {
"data": {
"protocol": "Protokoll"
}
}
}
},
"options": {
"error": {
"int": "D'Feld hei \u00ebnnen muss eng ganz Zuel sinn.",
"relay_inclusive": "Relais Adress a Relais Kanal sin vuneneen ofh\u00e4ngeg a musse mat abegraff sinn."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternative Nuecht Modus",
"auto_bypass": "Auto Bypass beim aktiv\u00e9ieren",
"code_arm_required": "Code erfuerderlech fir d'Aktiv\u00e9ierung"
},
"title": "AlarmDecoder konfigur\u00e9ieren"
},
"init": {
"data": {
"edit_select": "\u00c4nneren"
},
"description": "Wat w\u00eblls du \u00e4nneren?",
"title": "AlarmDecoder konfigur\u00e9ieren"
},
"zone_details": {
"data": {
"zone_loop": "RF Schleef",
"zone_name": "Numm vun der Zone",
"zone_relayaddr": "Relais Adresse",
"zone_relaychan": "Relais Kanal",
"zone_rfid": "RF Serielle",
"zone_type": "Type vun der Zone"
},
"title": "AlarmDecoder konfigur\u00e9ieren"
},
"zone_select": {
"data": {
"zone_number": "Zone Nummer"
}
}
}
}
}

View File

@ -0,0 +1,73 @@
{
"config": {
"abort": {
"already_configured": "AlarmDecoder-apparaat is al geconfigureerd."
},
"create_entry": {
"default": "Succesvol verbonden met AlarmDecoder."
},
"error": {
"service_unavailable": "Kon niet verbinden"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Baudrate van apparaat",
"device_path": "Apparaatpad",
"host": "Host",
"port": "Poort"
},
"title": "Configureer de verbindingsinstellingen"
},
"user": {
"data": {
"protocol": "Protocol"
},
"title": "Kies AlarmDecoder Protocol"
}
}
},
"options": {
"error": {
"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."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternatieve nachtmodus",
"auto_bypass": "Automatische bypass bij inschakelen",
"code_arm_required": "Code vereist voor inschakelen"
},
"title": "Configureer AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Bewerk"
},
"description": "Wat wilt u bewerken?",
"title": "Configureer AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF Lus",
"zone_name": "Zone naam",
"zone_relayaddr": "Relais Adres",
"zone_relaychan": "Relais Kanaal",
"zone_rfid": "RF Serieel",
"zone_type": "Zone Type"
},
"title": "Configureer AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Zone nummer"
},
"description": "Voer het zone nummer in dat u wilt toevoegen, bewerken of verwijderen.",
"title": "Configureer AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert"
},
"create_entry": {
"default": "Vellykket koblet til AlarmDecoder."
},
"error": {
"service_unavailable": "Tilkobling mislyktes."
},
"step": {
"protocol": {
"data": {
"device_baudrate": "Baud-hastighet for enhet",
"device_path": "Bane til enheten",
"host": "Vert",
"port": "Port"
},
"title": "Konfigurer tilkoblingsinnstillinger"
},
"user": {
"data": {
"protocol": "Protokoll"
},
"title": "Velg AlarmDecoder Protokoll"
}
}
},
"options": {
"error": {
"int": "Feltet nedenfor m\u00e5 v\u00e6re et helt tall.",
"loop_range": "RF Loop m\u00e5 v\u00e6re et heltall mellom 1 og 4.",
"loop_rfid": "RF Loop kan ikke brukes uten RF Serial.",
"relay_inclusive": "Rel\u00e9adresse og rel\u00e9kanal er kodeavhengige og m\u00e5 inkluderes sammen."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternativ nattmodus",
"auto_bypass": "Auto bypass p\u00e5 Arm",
"code_arm_required": "Kode kreves for tilkobling"
},
"title": "Konfigurer AlarmDecoder"
},
"init": {
"data": {
"edit_select": "Rediger"
},
"description": "Hva \u00f8nsker du \u00e5 redigere?",
"title": "Konfigurer AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF Loop",
"zone_name": "Sonenavn",
"zone_relayaddr": "Rel\u00e9 adresse",
"zone_relaychan": "Rel\u00e9 kanal",
"zone_rfid": "RF seriell",
"zone_type": "Sone type"
},
"description": "Angi detaljer for sonen {zone_number}. Hvis du vil slette sonen {zone_number}, lar du Sonenavn st\u00e5 tomt.",
"title": "Konfigurer AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Sone nummer"
},
"description": "Angi sonenummeret du vil legge til, redigere eller fjerne.",
"title": "Konfigurer AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,37 @@
{
"config": {
"abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
},
"error": {
"service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia"
},
"step": {
"protocol": {
"data": {
"host": "Nazwa hosta lub adres IP",
"port": "Port"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"edit_select": "Edytuj"
}
},
"zone_details": {
"data": {
"zone_relaychan": "Kana\u0142 przeka\u017anika"
}
},
"zone_select": {
"data": {
"zone_number": "Numer strefy"
}
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"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."
},
"create_entry": {
"default": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a AlarmDecoder."
},
"error": {
"service_unavailable": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f."
},
"step": {
"protocol": {
"data": {
"device_baudrate": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
"device_path": "\u041f\u0443\u0442\u044c \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443",
"host": "\u0425\u043e\u0441\u0442",
"port": "\u041f\u043e\u0440\u0442"
},
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f"
},
"user": {
"data": {
"protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b"
},
"title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b AlarmDecoder"
}
}
},
"options": {
"error": {
"int": "\u041f\u043e\u043b\u0435 \u043d\u0438\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0446\u0435\u043b\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c.",
"loop_range": "RF Loop \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0446\u0435\u043b\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c \u043e\u0442 1 \u0434\u043e 4.",
"loop_rfid": "RF Loop \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0431\u0435\u0437 RF Serial.",
"relay_inclusive": "\u0410\u0434\u0440\u0435\u0441 \u0440\u0435\u043b\u0435 \u0438 \u043a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b \u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u043c\u0435\u0441\u0442\u0435."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043d\u043e\u0447\u043d\u043e\u0439 \u0440\u0435\u0436\u0438\u043c",
"auto_bypass": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u043d\u0430 \u043e\u0445\u0440\u0430\u043d\u0443",
"code_arm_required": "\u041a\u043e\u0434, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0430 \u043e\u0445\u0440\u0430\u043d\u0443"
},
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder"
},
"init": {
"data": {
"edit_select": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c"
},
"description": "\u0427\u0442\u043e \u0431\u044b \u0412\u044b \u0445\u043e\u0442\u0435\u043b\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c?",
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF Loop",
"zone_name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0437\u043e\u043d\u044b",
"zone_relayaddr": "\u0410\u0434\u0440\u0435\u0441 \u0440\u0435\u043b\u0435",
"zone_relaychan": "\u041a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435",
"zone_rfid": "RF Serial",
"zone_type": "\u0422\u0438\u043f \u0437\u043e\u043d\u044b"
},
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0437\u043e\u043d\u044b {zone_number}. \u0427\u0442\u043e\u0431\u044b \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0437\u043e\u043d\u0443 {zone_number}, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0437\u043e\u043d\u044b\" \u043f\u0443\u0441\u0442\u044b\u043c.",
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "\u041d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u044b"
},
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u044b, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0438\u0442\u044c.",
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder"
}
}
}
}

View File

@ -0,0 +1,27 @@
{
"config": {
"step": {
"protocol": {
"data": {
"device_path": "Enhetsv\u00e4g"
},
"title": "Konfigurera anslutningsinst\u00e4llningar"
},
"user": {
"data": {
"protocol": "Protokoll"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"edit_select": "Redigera"
},
"description": "Vad vill du redigera?"
}
}
}
}

View File

@ -0,0 +1,74 @@
{
"config": {
"abort": {
"already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210"
},
"create_entry": {
"default": "\u6210\u529f\u9023\u7dda\u81f3 AlarmDecoder\u3002"
},
"error": {
"service_unavailable": "\u9023\u7dda\u5931\u6557"
},
"step": {
"protocol": {
"data": {
"device_baudrate": "\u8a2d\u5099\u901a\u8a0a\u7387",
"device_path": "\u8a2d\u5099\u8def\u5f91",
"host": "\u4e3b\u6a5f\u7aef",
"port": "\u901a\u8a0a\u57e0"
},
"title": "\u8a2d\u5b9a\u9023\u7dda\u8a2d\u5b9a"
},
"user": {
"data": {
"protocol": "\u901a\u8a0a\u5354\u5b9a"
},
"title": "\u9078\u64c7 AlarmDecoder \u901a\u8a0a\u5354\u5b9a"
}
}
},
"options": {
"error": {
"int": "\u4e0b\u65b9\u6b04\u4f4d\u5fc5\u9808\u70ba\u6574\u6578\u3002",
"loop_range": "RF \u8ff4\u8def\u5fc5\u9808\u70ba\u4ecb\u65bc 1 \u81f3 4 \u9593\u7684\u6574\u6578\u3002",
"loop_rfid": "\u5982\u679c\u6c92\u6709 RF \u5e8f\u5217\u5247\u7121\u6cd5\u4f7f\u7528 RF \u8ff4\u8def\u3002",
"relay_inclusive": "\u4e2d\u7e7c\u5730\u5740\u8207\u4e2d\u7e7c\u983b\u9053\u70ba\u76f8\u4e92\u4f9d\u8cf4\uff0c\u4e26\u5fc5\u9808\u4e00\u8d77\u5305\u542b\u3002"
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "\u66ff\u4ee3\u591c\u9593\u6a21\u5f0f",
"auto_bypass": "\u81ea\u52d5\u5ffd\u7565\u8b66\u6212",
"code_arm_required": "\u8b66\u6212\u9700\u8981\u4ee3\u78bc"
},
"title": "\u8a2d\u5b9a AlarmDecoder"
},
"init": {
"data": {
"edit_select": "\u7de8\u8f2f"
},
"description": "\u662f\u5426\u8981\u9032\u884c\u7de8\u8f2f\uff1f",
"title": "\u8a2d\u5b9a AlarmDecoder"
},
"zone_details": {
"data": {
"zone_loop": "RF \u8ff4\u8def",
"zone_name": "\u5340\u57df\u540d\u7a31",
"zone_relayaddr": "\u4e2d\u7e7c\u4f4d\u5740",
"zone_relaychan": "\u4e2d\u7e7c\u983b\u9053",
"zone_rfid": "RF \u5e8f\u5217",
"zone_type": "\u5340\u57df\u985e\u578b"
},
"description": "\u8f38\u5165\u5340\u57df {zone_number} \u8a73\u7d30\u8cc7\u6599\u3002\u6b32\u522a\u9664\u5340\u57df {zone_number}\uff0c\u4fdd\u6301\u5340\u57df\u540d\u7a31\u7a7a\u767d\u3002",
"title": "\u8a2d\u5b9a AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "\u5340\u57df\u78bc"
},
"description": "\u8f38\u5165\u6240\u8981\u65b0\u589e\u3001\u7de8\u8f2f\u6216\u79fb\u9664\u7684\u5340\u57df\u78bc\u3002",
"title": "\u8a2d\u5b9a AlarmDecoder"
}
}
}
}

View File

@ -33,6 +33,7 @@ from homeassistant.const import (
CONF_NAME, CONF_NAME,
TEMP_CELSIUS, TEMP_CELSIUS,
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
__version__,
) )
from homeassistant.core import HomeAssistant, State, callback from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers import network from homeassistant.helpers import network
@ -286,6 +287,12 @@ class AlexaEntity:
"friendlyName": self.friendly_name(), "friendlyName": self.friendly_name(),
"description": self.description(), "description": self.description(),
"manufacturerName": "Home Assistant", "manufacturerName": "Home Assistant",
"additionalAttributes": {
"manufacturer": "Home Assistant",
"model": self.entity.domain,
"softwareVersion": __version__,
"customIdentifier": self.entity_id,
},
} }
locale = self.config.locale locale = self.config.locale

View File

@ -17,6 +17,7 @@ from homeassistant.components import (
from homeassistant.components.climate import const as climate from homeassistant.components.climate import const as climate
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_ENTITY_PICTURE,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_AWAY,
@ -1532,7 +1533,7 @@ async def async_api_initialize_camera_stream(hass, config, directive, context):
"""Process a InitializeCameraStreams request.""" """Process a InitializeCameraStreams request."""
entity = directive.entity entity = directive.entity
stream_source = await camera.async_request_stream(hass, entity.entity_id, fmt="hls") stream_source = await camera.async_request_stream(hass, entity.entity_id, fmt="hls")
camera_image = hass.states.get(entity.entity_id).attributes["entity_picture"] camera_image = hass.states.get(entity.entity_id).attributes[ATTR_ENTITY_PICTURE]
try: try:
external_url = network.get_url( external_url = network.get_url(

View File

@ -6,7 +6,7 @@ import logging
import aiohttp import aiohttp
import async_timeout import async_timeout
from homeassistant.const import MATCH_ALL, STATE_ON from homeassistant.const import HTTP_ACCEPTED, MATCH_ALL, STATE_ON
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import API_CHANGE, Cause from .const import API_CHANGE, Cause
@ -109,7 +109,7 @@ async def async_send_changereport_message(
_LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Sent: %s", json.dumps(message_serialized))
_LOGGER.debug("Received (%s): %s", response.status, response_text) _LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status == 202: if response.status == HTTP_ACCEPTED:
return return
response_json = json.loads(response_text) response_json = json.loads(response_text)
@ -240,7 +240,7 @@ async def async_send_doorbell_event_message(hass, config, alexa_entity):
_LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Sent: %s", json.dumps(message_serialized))
_LOGGER.debug("Received (%s): %s", response.status, response_text) _LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status == 202: if response.status == HTTP_ACCEPTED:
return return
response_json = json.loads(response_text) response_json = json.loads(response_text)

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"step": { "step": {
"pick_implementation": { "title": "Pick Authentication Method" }, "pick_implementation": { "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" },
"hassio_confirm": { "hassio_confirm": {
"title": "Almond via Hass.io add-on", "title": "Almond via Hass.io add-on",
"description": "Do you want to configure Home Assistant to connect to Almond provided by the Hass.io add-on: {addon}?" "description": "Do you want to configure Home Assistant to connect to Almond provided by the Hass.io add-on: {addon}?"

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "Nom\u00e9s pots configurar un \u00fanic compte amb Almond.", "already_setup": "Nom\u00e9s pots configurar un \u00fanic compte amb Almond.",
"cannot_connect": "No es pot connectar amb el servidor d'Almond.", "cannot_connect": "No es pot connectar amb el servidor d'Almond.",
"missing_configuration": "Consulta la documentaci\u00f3 sobre com configurar Almond." "missing_configuration": "Consulta la documentaci\u00f3 sobre com configurar Almond.",
"no_url_available": "No hi ha cap URL disponible. Per a m\u00e9s informaci\u00f3 sobre aquest error, [consulta la secci\u00f3 d'ajuda]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "You can only configure one Almond account.", "already_setup": "You can only configure one Almond account.",
"cannot_connect": "Unable to connect to the Almond server.", "cannot_connect": "Unable to connect to the Almond server.",
"missing_configuration": "Please check the documentation on how to set up Almond." "missing_configuration": "Please check the documentation on how to set up Almond.",
"no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "S\u00f3lo puede configurar una cuenta de Almond.", "already_setup": "S\u00f3lo puede configurar una cuenta de Almond.",
"cannot_connect": "No se puede conectar al servidor Almond.", "cannot_connect": "No se puede conectar al servidor Almond.",
"missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond." "missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond.",
"no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "Vous ne pouvez configurer qu'un seul compte Almond", "already_setup": "Vous ne pouvez configurer qu'un seul compte Almond",
"cannot_connect": "Impossible de se connecter au serveur Almond", "cannot_connect": "Impossible de se connecter au serveur Almond",
"missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond." "missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond.",
"no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "\u00c8 possibile configurare un solo account Almond.", "already_setup": "\u00c8 possibile configurare un solo account Almond.",
"cannot_connect": "Impossibile connettersi al server Almond.", "cannot_connect": "Impossibile connettersi al server Almond.",
"missing_configuration": "Si prega di controllare la documentazione su come impostare Almond." "missing_configuration": "Si prega di controllare la documentazione su come impostare Almond.",
"no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
@ -11,7 +12,7 @@
"title": "Almond tramite il componente aggiuntivo di Hass.io" "title": "Almond tramite il componente aggiuntivo di Hass.io"
}, },
"pick_implementation": { "pick_implementation": {
"title": "Seleziona metodo di autenticazione" "title": "Scegli il metodo di autenticazione"
} }
} }
} }

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "\ud558\ub098\uc758 Almond \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "already_setup": "\ud558\ub098\uc758 Almond \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.",
"cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.",
"missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.",
"no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Almond Kont konfigur\u00e9ieren.", "already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Almond Kont konfigur\u00e9ieren.",
"cannot_connect": "Kann sech net mam Almond Server verbannen.", "cannot_connect": "Kann sech net mam Almond Server verbannen.",
"missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond." "missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond.",
"no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "U kunt slechts \u00e9\u00e9n Almond-account configureren.", "already_setup": "U kunt slechts \u00e9\u00e9n Almond-account configureren.",
"cannot_connect": "Kan geen verbinding maken met de Almond-server.", "cannot_connect": "Kan geen verbinding maken met de Almond-server.",
"missing_configuration": "Raadpleeg de documentatie over het instellen van Almond." "missing_configuration": "Raadpleeg de documentatie over het instellen van Almond.",
"no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,11 +3,13 @@
"abort": { "abort": {
"already_setup": "Du kan bare konfigurere en Almond konto.", "already_setup": "Du kan bare konfigurere en Almond konto.",
"cannot_connect": "Kan ikke koble til Almond-serveren.", "cannot_connect": "Kan ikke koble til Almond-serveren.",
"missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond." "missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond.",
"no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
"description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io add-on: {addon}?" "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io add-on: {addon}?",
"title": ""
}, },
"pick_implementation": { "pick_implementation": {
"title": "Velg godkjenningsmetode" "title": "Velg godkjenningsmetode"

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Almond.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Almond.",
"missing_configuration": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 Almond." "missing_configuration": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 Almond.",
"no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435."
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Almond \u5e33\u865f\u3002", "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Almond \u5e33\u865f\u3002",
"cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Almond \u4f3a\u670d\u5668\u3002", "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Almond \u4f3a\u670d\u5668\u3002",
"missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002" "missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002",
"no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -6,8 +6,10 @@ from aioambient import Client
from aioambient.errors import WebsocketError from aioambient.errors import WebsocketError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
AREA_SQUARE_METERS,
ATTR_LOCATION, ATTR_LOCATION,
ATTR_NAME, ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
@ -15,8 +17,10 @@ from homeassistant.const import (
CONF_API_KEY, CONF_API_KEY,
DEGREE, DEGREE,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
LIGHT_LUX,
PERCENTAGE, PERCENTAGE,
POWER_WATT, POWER_WATT,
PRESSURE_INHG,
SPEED_MILES_PER_HOUR, SPEED_MILES_PER_HOUR,
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
) )
@ -141,8 +145,8 @@ TYPE_WINDSPEEDMPH = "windspeedmph"
TYPE_YEARLYRAININ = "yearlyrainin" TYPE_YEARLYRAININ = "yearlyrainin"
SENSOR_TYPES = { SENSOR_TYPES = {
TYPE_24HOURRAININ: ("24 Hr Rain", "in", TYPE_SENSOR, None), TYPE_24HOURRAININ: ("24 Hr Rain", "in", TYPE_SENSOR, None),
TYPE_BAROMABSIN: ("Abs Pressure", "inHg", TYPE_SENSOR, "pressure"), TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"),
TYPE_BAROMRELIN: ("Rel Pressure", "inHg", TYPE_SENSOR, "pressure"), TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"),
TYPE_BATT10: ("Battery 10", None, TYPE_BINARY_SENSOR, "battery"), TYPE_BATT10: ("Battery 10", None, TYPE_BINARY_SENSOR, "battery"),
TYPE_BATT1: ("Battery 1", None, TYPE_BINARY_SENSOR, "battery"), TYPE_BATT1: ("Battery 1", None, TYPE_BINARY_SENSOR, "battery"),
TYPE_BATT2: ("Battery 2", None, TYPE_BINARY_SENSOR, "battery"), TYPE_BATT2: ("Battery 2", None, TYPE_BINARY_SENSOR, "battery"),
@ -175,16 +179,16 @@ SENSOR_TYPES = {
TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"), TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"),
TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None), TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None),
TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, "connectivity"), TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"), TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"),
TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"), TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"),
TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"), TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"),
@ -205,8 +209,13 @@ SENSOR_TYPES = {
TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),
TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),
TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),
TYPE_SOLARRADIATION: ("Solar Rad", f"{POWER_WATT}/m^2", TYPE_SENSOR, None), TYPE_SOLARRADIATION: (
TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", "lx", TYPE_SENSOR, "illuminance"), "Solar Rad",
f"{POWER_WATT}/{AREA_SQUARE_METERS}",
TYPE_SENSOR,
None,
),
TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", LIGHT_LUX, TYPE_SENSOR, "illuminance"),
TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),
TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),
TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),

View File

@ -1,5 +1,8 @@
"""Support for Android IP Webcam binary sensors.""" """Support for Android IP Webcam binary sensors."""
from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import (
DEVICE_CLASS_MOTION,
BinarySensorEntity,
)
from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity
@ -47,4 +50,4 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorEntity):
@property @property
def device_class(self): def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""
return "motion" return DEVICE_CLASS_MOTION

View File

@ -502,14 +502,23 @@ class ADBDevice(MediaPlayerEntity):
return self._unique_id return self._unique_id
@adb_decorator() @adb_decorator()
async def _adb_screencap(self):
"""Take a screen capture from the device."""
return await self.aftv.adb_screencap()
async def async_get_media_image(self): async def async_get_media_image(self):
"""Fetch current playing image.""" """Fetch current playing image."""
if not self._screencap or self.state in [STATE_OFF, None] or not self.available: if not self._screencap or self.state in [STATE_OFF, None] or not self.available:
return None, None return None, None
media_data = await self.aftv.adb_screencap() media_data = await self._adb_screencap()
if media_data: if media_data:
return media_data, "image/png" return media_data, "image/png"
# If an exception occurred and the device is no longer available, write the state
if not self.available:
self.async_write_ha_state()
return None, None return None, None
@adb_decorator() @adb_decorator()

View File

@ -38,6 +38,7 @@ from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates from homeassistant.helpers.state import AsyncTrackStates
from homeassistant.helpers.system_info import async_get_system_info
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,6 +46,7 @@ ATTR_BASE_URL = "base_url"
ATTR_EXTERNAL_URL = "external_url" ATTR_EXTERNAL_URL = "external_url"
ATTR_INTERNAL_URL = "internal_url" ATTR_INTERNAL_URL = "internal_url"
ATTR_LOCATION_NAME = "location_name" ATTR_LOCATION_NAME = "location_name"
ATTR_INSTALLATION_TYPE = "installation_type"
ATTR_REQUIRES_API_PASSWORD = "requires_api_password" ATTR_REQUIRES_API_PASSWORD = "requires_api_password"
ATTR_UUID = "uuid" ATTR_UUID = "uuid"
ATTR_VERSION = "version" ATTR_VERSION = "version"
@ -181,6 +183,7 @@ class APIDiscoveryView(HomeAssistantView):
"""Get discovery information.""" """Get discovery information."""
hass = request.app["hass"] hass = request.app["hass"]
uuid = await hass.helpers.instance_id.async_get() uuid = await hass.helpers.instance_id.async_get()
system_info = await async_get_system_info(hass)
data = { data = {
ATTR_UUID: uuid, ATTR_UUID: uuid,
@ -188,6 +191,7 @@ class APIDiscoveryView(HomeAssistantView):
ATTR_EXTERNAL_URL: None, ATTR_EXTERNAL_URL: None,
ATTR_INTERNAL_URL: None, ATTR_INTERNAL_URL: None,
ATTR_LOCATION_NAME: hass.config.location_name, ATTR_LOCATION_NAME: hass.config.location_name,
ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE],
# always needs authentication # always needs authentication
ATTR_REQUIRES_API_PASSWORD: True, ATTR_REQUIRES_API_PASSWORD: True,
ATTR_VERSION: __version__, ATTR_VERSION: __version__,

View File

@ -2,6 +2,6 @@
"domain": "apprise", "domain": "apprise",
"name": "Apprise", "name": "Apprise",
"documentation": "https://www.home-assistant.io/integrations/apprise", "documentation": "https://www.home-assistant.io/integrations/apprise",
"requirements": ["apprise==0.8.8"], "requirements": ["apprise==0.8.9"],
"codeowners": ["@caronc"] "codeowners": ["@caronc"]
} }

View File

@ -28,12 +28,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
def get_service(hass, config, discovery_info=None): def get_service(hass, config, discovery_info=None):
"""Get the Apprise notification service.""" """Get the Apprise notification service."""
# Create our Apprise Asset Object
asset = apprise.AppriseAsset(async_mode=False)
# Create our Apprise Instance (reference our asset) # Create our Apprise Instance (reference our asset)
a_obj = apprise.Apprise(asset=asset) a_obj = apprise.Apprise()
if config.get(CONF_FILE): if config.get(CONF_FILE):
# Sourced from a Configuration File # Sourced from a Configuration File

View File

@ -12,7 +12,8 @@
}, },
"user": { "user": {
"data": { "data": {
"host": "Vert" "host": "Vert",
"port": ""
}, },
"description": "Vennligst skriv inn vertsnavnet eller IP-adressen til enheten." "description": "Vennligst skriv inn vertsnavnet eller IP-adressen til enheten."
} }

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
}, },
"step": { "step": {
"user": { "user": {

View File

@ -23,6 +23,12 @@ CONFIG_SCHEMA = vol.Schema(
def setup(hass, config): def setup(hass, config):
"""Set up the Arduino component.""" """Set up the Arduino component."""
_LOGGER.warning(
"The %s integration has been deprecated. Please move your "
"configuration to the firmata integration. "
"https://www.home-assistant.io/integrations/firmata",
DOMAIN,
)
port = config[DOMAIN][CONF_PORT] port = config[DOMAIN][CONF_PORT]

View File

@ -3,6 +3,6 @@
"name": "Atag", "name": "Atag",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/atag/", "documentation": "https://www.home-assistant.io/integrations/atag/",
"requirements": ["pyatag==0.3.3.4"], "requirements": ["pyatag==0.3.4.4"],
"codeowners": ["@MatsNL"] "codeowners": ["@MatsNL"]
} }

View File

@ -16,5 +16,6 @@
"title": "Verbinding maken met het apparaat" "title": "Verbinding maken met het apparaat"
} }
} }
} },
"title": ""
} }

View File

@ -11,10 +11,12 @@
"user": { "user": {
"data": { "data": {
"email": "E-post (valgfritt)", "email": "E-post (valgfritt)",
"host": "Vert" "host": "Vert",
"port": ""
}, },
"title": "Koble til enheten" "title": "Koble til enheten"
} }
} }
} },
"title": ""
} }

View File

@ -3,13 +3,18 @@ import asyncio
import itertools import itertools
import logging import logging
from aiohttp import ClientError from aiohttp import ClientError, ClientResponseError
from august.authenticator import ValidationResult from august.authenticator import ValidationResult
from august.exceptions import AugustApiAIOHTTPError from august.exceptions import AugustApiAIOHTTPError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.const import (
CONF_PASSWORD,
CONF_TIMEOUT,
CONF_USERNAME,
HTTP_UNAUTHORIZED,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -29,7 +34,7 @@ from .const import (
MIN_TIME_BETWEEN_DETAIL_UPDATES, MIN_TIME_BETWEEN_DETAIL_UPDATES,
VERIFICATION_CODE_KEY, VERIFICATION_CODE_KEY,
) )
from .exceptions import InvalidAuth, RequireValidation from .exceptions import CannotConnect, InvalidAuth, RequireValidation
from .gateway import AugustGateway from .gateway import AugustGateway
from .subscriber import AugustSubscriberMixin from .subscriber import AugustSubscriberMixin
@ -113,10 +118,7 @@ async def async_setup_august(hass, config_entry, august_gateway):
await august_gateway.async_authenticate() await august_gateway.async_authenticate()
except RequireValidation: except RequireValidation:
await async_request_validation(hass, config_entry, august_gateway) await async_request_validation(hass, config_entry, august_gateway)
return False raise
except InvalidAuth:
_LOGGER.error("Password is no longer valid. Please set up August again")
return False
# We still use the configurator to get a new 2fa code # We still use the configurator to get a new 2fa code
# when needed since config_flow doesn't have a way # when needed since config_flow doesn't have a way
@ -171,8 +173,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
try: try:
await august_gateway.async_setup(entry.data) await august_gateway.async_setup(entry.data)
return await async_setup_august(hass, entry, august_gateway) return await async_setup_august(hass, entry, august_gateway)
except asyncio.TimeoutError as err: except ClientResponseError as err:
if err.status == HTTP_UNAUTHORIZED:
_async_start_reauth(hass, entry)
return False
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except InvalidAuth:
_async_start_reauth(hass, entry)
return False
except RequireValidation:
return False
except (CannotConnect, asyncio.TimeoutError) as err:
raise ConfigEntryNotReady from err
def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "reauth"},
data=entry.data,
)
)
_LOGGER.error("Password is no longer valid. Please reauthenticate")
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):

View File

@ -4,7 +4,7 @@ import logging
from august.authenticator import ValidationResult from august.authenticator import ValidationResult
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries, core from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME
from .const import ( from .const import (
@ -19,18 +19,8 @@ from .gateway import AugustGateway
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS),
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int),
}
)
async def async_validate_input( async def async_validate_input(
hass: core.HomeAssistant,
data, data,
august_gateway, august_gateway,
): ):
@ -79,6 +69,7 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Store an AugustGateway().""" """Store an AugustGateway()."""
self._august_gateway = None self._august_gateway = None
self.user_auth_details = {} self.user_auth_details = {}
self._needs_reset = False
super().__init__() super().__init__()
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
@ -87,30 +78,45 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._august_gateway = AugustGateway(self.hass) self._august_gateway = AugustGateway(self.hass)
errors = {} errors = {}
if user_input is not None: if user_input is not None:
await self._august_gateway.async_setup(user_input) combined_inputs = {**self.user_auth_details, **user_input}
await self._august_gateway.async_setup(combined_inputs)
if self._needs_reset:
self._needs_reset = False
await self._august_gateway.async_reset_authentication()
try: try:
info = await async_validate_input( info = await async_validate_input(
self.hass, combined_inputs,
user_input,
self._august_gateway, self._august_gateway,
) )
await self.async_set_unique_id(user_input[CONF_USERNAME])
return self.async_create_entry(title=info["title"], data=info["data"])
except CannotConnect: except CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except InvalidAuth: except InvalidAuth:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except RequireValidation: except RequireValidation:
self.user_auth_details = user_input self.user_auth_details.update(user_input)
return await self.async_step_validation() return await self.async_step_validation()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors["base"] = "unknown" errors["base"] = "unknown"
if not errors:
self.user_auth_details.update(user_input)
existing_entry = await self.async_set_unique_id(
combined_inputs[CONF_USERNAME]
)
if existing_entry:
self.hass.config_entries.async_update_entry(
existing_entry, data=info["data"]
)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title=info["title"], data=info["data"])
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors step_id="user", data_schema=self._async_build_schema(), errors=errors
) )
async def async_step_validation(self, user_input=None): async def async_step_validation(self, user_input=None):
@ -135,3 +141,23 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
return await self.async_step_user(user_input) return await self.async_step_user(user_input)
async def async_step_reauth(self, data):
"""Handle configuration by re-auth."""
self.user_auth_details = dict(data)
self._needs_reset = True
return await self.async_step_user()
def _async_build_schema(self):
"""Generate the config flow schema."""
base_schema = {
vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS),
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int),
}
for key in self.user_auth_details:
if key == CONF_PASSWORD or key not in base_schema:
continue
del base_schema[key]
return vol.Schema(base_schema)

View File

@ -2,12 +2,18 @@
import asyncio import asyncio
import logging import logging
import os
from aiohttp import ClientError from aiohttp import ClientError, ClientResponseError
from august.api_async import ApiAsync from august.api_async import ApiAsync
from august.authenticator_async import AuthenticationState, AuthenticatorAsync from august.authenticator_async import AuthenticationState, AuthenticatorAsync
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.const import (
CONF_PASSWORD,
CONF_TIMEOUT,
CONF_USERNAME,
HTTP_UNAUTHORIZED,
)
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import ( from .const import (
@ -32,29 +38,14 @@ class AugustGateway:
self._access_token_cache_file = None self._access_token_cache_file = None
self._hass = hass self._hass = hass
self._config = None self._config = None
self._api = None self.api = None
self._authenticator = None self.authenticator = None
self._authentication = None self.authentication = None
@property
def authenticator(self):
"""August authentication object from py-august."""
return self._authenticator
@property
def authentication(self):
"""August authentication object from py-august."""
return self._authentication
@property @property
def access_token(self): def access_token(self):
"""Access token for the api.""" """Access token for the api."""
return self._authentication.access_token return self.authentication.access_token
@property
def api(self):
"""August api object from py-august."""
return self._api
def config_entry(self): def config_entry(self):
"""Config entry.""" """Config entry."""
@ -78,12 +69,12 @@ class AugustGateway:
) )
self._config = conf self._config = conf
self._api = ApiAsync( self.api = ApiAsync(
self._aiohttp_session, timeout=self._config.get(CONF_TIMEOUT) self._aiohttp_session, timeout=self._config.get(CONF_TIMEOUT)
) )
self._authenticator = AuthenticatorAsync( self.authenticator = AuthenticatorAsync(
self._api, self.api,
self._config[CONF_LOGIN_METHOD], self._config[CONF_LOGIN_METHOD],
self._config[CONF_USERNAME], self._config[CONF_USERNAME],
self._config[CONF_PASSWORD], self._config[CONF_PASSWORD],
@ -93,30 +84,47 @@ class AugustGateway:
), ),
) )
await self._authenticator.async_setup_authentication() await self.authenticator.async_setup_authentication()
async def async_authenticate(self): async def async_authenticate(self):
"""Authenticate with the details provided to setup.""" """Authenticate with the details provided to setup."""
self._authentication = None self.authentication = None
try: try:
self._authentication = await self.authenticator.async_authenticate() self.authentication = await self.authenticator.async_authenticate()
if self.authentication.state == AuthenticationState.AUTHENTICATED:
# Call the locks api to verify we are actually
# authenticated because we can be authenticated
# by have no access
await self.api.async_get_operable_locks(self.access_token)
except ClientResponseError as ex:
if ex.status == HTTP_UNAUTHORIZED:
raise InvalidAuth from ex
raise CannotConnect from ex
except ClientError as ex: except ClientError as ex:
_LOGGER.error("Unable to connect to August service: %s", str(ex)) _LOGGER.error("Unable to connect to August service: %s", str(ex))
raise CannotConnect from ex raise CannotConnect from ex
if self._authentication.state == AuthenticationState.BAD_PASSWORD: if self.authentication.state == AuthenticationState.BAD_PASSWORD:
raise InvalidAuth raise InvalidAuth
if self._authentication.state == AuthenticationState.REQUIRES_VALIDATION: if self.authentication.state == AuthenticationState.REQUIRES_VALIDATION:
raise RequireValidation raise RequireValidation
if self._authentication.state != AuthenticationState.AUTHENTICATED: if self.authentication.state != AuthenticationState.AUTHENTICATED:
_LOGGER.error( _LOGGER.error("Unknown authentication state: %s", self.authentication.state)
"Unknown authentication state: %s", self._authentication.state
)
raise InvalidAuth raise InvalidAuth
return self._authentication return self.authentication
async def async_reset_authentication(self):
"""Remove the cache file."""
await self._hass.async_add_executor_job(self._reset_authentication)
def _reset_authentication(self):
"""Remove the cache file."""
if os.path.exists(self._access_token_cache_file):
os.unlink(self._access_token_cache_file)
async def async_refresh_access_token_if_needed(self): async def async_refresh_access_token_if_needed(self):
"""Refresh the august access token if needed.""" """Refresh the august access token if needed."""
@ -130,4 +138,4 @@ class AugustGateway:
self.authentication.access_token_expires, self.authentication.access_token_expires,
refreshed_authentication.access_token_expires, refreshed_authentication.access_token_expires,
) )
self._authentication = refreshed_authentication self.authentication = refreshed_authentication

View File

@ -6,7 +6,8 @@
"invalid_auth": "Invalid authentication" "invalid_auth": "Invalid authentication"
}, },
"abort": { "abort": {
"already_configured": "Account is already configured" "already_configured": "Account is already configured",
"reauth_successful": "Re-authentication was successful"
}, },
"step": { "step": {
"validation": { "validation": {
@ -28,4 +29,4 @@
} }
} }
} }
} }

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "El compte ja ha estat configurat" "already_configured": "El compte ja ha estat configurat",
"reauth_successful": "Re-autenticaci\u00f3 realitzada correctament"
}, },
"error": { "error": {
"cannot_connect": "No s'ha pogut connectar, torna-ho a provar", "cannot_connect": "No s'ha pogut connectar, torna-ho a provar",

View File

@ -0,0 +1,7 @@
{
"config": {
"abort": {
"reauth_successful": "\u0397 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2"
}
}
}

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Account is already configured" "already_configured": "Account is already configured",
"reauth_successful": "Re-authentication was successful"
}, },
"error": { "error": {
"cannot_connect": "Failed to connect, please try again", "cannot_connect": "Failed to connect, please try again",

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "La cuenta ya est\u00e1 configurada" "already_configured": "La cuenta ya est\u00e1 configurada",
"reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente"
}, },
"error": { "error": {
"cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.",

View File

@ -0,0 +1,7 @@
{
"config": {
"abort": {
"reauth_successful": "Taasautentimine \u00f5nnestus"
}
}
}

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
}, },
"error": { "error": {
"cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer",

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "L'account \u00e8 gi\u00e0 configurato" "already_configured": "L'account \u00e8 gi\u00e0 configurato",
"reauth_successful": "La riautenticazione ha avuto successo"
}, },
"error": { "error": {
"cannot_connect": "Impossibile connettersi, si prega di riprovare.", "cannot_connect": "Impossibile connettersi, si prega di riprovare.",

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