Compare commits

..

2 Commits

Author SHA1 Message Date
jbouwh
5f3bcee97e Refactor url fetch code to use base platform 2023-06-27 08:42:38 +02:00
jbouwh
51edc007fe Add url support for mqtt image platform 2023-06-27 08:42:38 +02:00
2990 changed files with 27517 additions and 73541 deletions

View File

@@ -24,7 +24,6 @@ base_platforms: &base_platforms
- homeassistant/components/datetime/**
- homeassistant/components/device_tracker/**
- homeassistant/components/diagnostics/**
- homeassistant/components/event/**
- homeassistant/components/fan/**
- homeassistant/components/geo_location/**
- homeassistant/components/humidifier/**

View File

@@ -82,7 +82,6 @@ omit =
homeassistant/components/arwn/sensor.py
homeassistant/components/aseko_pool_live/__init__.py
homeassistant/components/aseko_pool_live/binary_sensor.py
homeassistant/components/aseko_pool_live/coordinator.py
homeassistant/components/aseko_pool_live/entity.py
homeassistant/components/aseko_pool_live/sensor.py
homeassistant/components/asterisk_cdr/mailbox.py
@@ -125,6 +124,7 @@ omit =
homeassistant/components/bluetooth_tracker/*
homeassistant/components/bmw_connected_drive/__init__.py
homeassistant/components/bmw_connected_drive/binary_sensor.py
homeassistant/components/bmw_connected_drive/button.py
homeassistant/components/bmw_connected_drive/coordinator.py
homeassistant/components/bmw_connected_drive/lock.py
homeassistant/components/bmw_connected_drive/notify.py
@@ -183,6 +183,7 @@ omit =
homeassistant/components/crownstone/listeners.py
homeassistant/components/cups/sensor.py
homeassistant/components/currencylayer/sensor.py
homeassistant/components/daikin/__init__.py
homeassistant/components/daikin/climate.py
homeassistant/components/daikin/sensor.py
homeassistant/components/daikin/switch.py
@@ -230,10 +231,6 @@ omit =
homeassistant/components/dublin_bus_transport/sensor.py
homeassistant/components/dunehd/__init__.py
homeassistant/components/dunehd/media_player.py
homeassistant/components/duotecno/__init__.py
homeassistant/components/duotecno/entity.py
homeassistant/components/duotecno/switch.py
homeassistant/components/duotecno/cover.py
homeassistant/components/dwd_weather_warnings/const.py
homeassistant/components/dwd_weather_warnings/coordinator.py
homeassistant/components/dwd_weather_warnings/sensor.py
@@ -265,11 +262,6 @@ omit =
homeassistant/components/eight_sleep/__init__.py
homeassistant/components/eight_sleep/binary_sensor.py
homeassistant/components/eight_sleep/sensor.py
homeassistant/components/electric_kiwi/__init__.py
homeassistant/components/electric_kiwi/api.py
homeassistant/components/electric_kiwi/oauth2.py
homeassistant/components/electric_kiwi/sensor.py
homeassistant/components/electric_kiwi/coordinator.py
homeassistant/components/eliqonline/sensor.py
homeassistant/components/elkm1/__init__.py
homeassistant/components/elkm1/alarm_control_panel.py
@@ -314,8 +306,12 @@ omit =
homeassistant/components/escea/__init__.py
homeassistant/components/escea/climate.py
homeassistant/components/escea/discovery.py
homeassistant/components/esphome/__init__.py
homeassistant/components/esphome/bluetooth/*
homeassistant/components/esphome/manager.py
homeassistant/components/esphome/camera.py
homeassistant/components/esphome/domain_data.py
homeassistant/components/esphome/entry_data.py
homeassistant/components/esphome/light.py
homeassistant/components/etherscan/sensor.py
homeassistant/components/eufy/*
homeassistant/components/eufylife_ble/__init__.py
@@ -323,16 +319,12 @@ omit =
homeassistant/components/everlights/light.py
homeassistant/components/evohome/*
homeassistant/components/ezviz/__init__.py
homeassistant/components/ezviz/alarm_control_panel.py
homeassistant/components/ezviz/binary_sensor.py
homeassistant/components/ezviz/button.py
homeassistant/components/ezviz/camera.py
homeassistant/components/ezviz/image.py
homeassistant/components/ezviz/light.py
homeassistant/components/ezviz/coordinator.py
homeassistant/components/ezviz/number.py
homeassistant/components/ezviz/entity.py
homeassistant/components/ezviz/select.py
homeassistant/components/ezviz/sensor.py
homeassistant/components/ezviz/switch.py
homeassistant/components/ezviz/update.py
@@ -365,13 +357,10 @@ omit =
homeassistant/components/fitbit/*
homeassistant/components/fivem/__init__.py
homeassistant/components/fivem/binary_sensor.py
homeassistant/components/fivem/coordinator.py
homeassistant/components/fivem/entity.py
homeassistant/components/fivem/sensor.py
homeassistant/components/fixer/sensor.py
homeassistant/components/fjaraskupan/__init__.py
homeassistant/components/fjaraskupan/binary_sensor.py
homeassistant/components/fjaraskupan/coordinator.py
homeassistant/components/fjaraskupan/fan.py
homeassistant/components/fjaraskupan/light.py
homeassistant/components/fjaraskupan/number.py
@@ -605,7 +594,6 @@ omit =
homeassistant/components/keymitt_ble/entity.py
homeassistant/components/keymitt_ble/switch.py
homeassistant/components/keymitt_ble/coordinator.py
homeassistant/components/kitchen_sink/weather.py
homeassistant/components/kiwi/lock.py
homeassistant/components/kodi/__init__.py
homeassistant/components/kodi/browse_media.py
@@ -663,7 +651,6 @@ omit =
homeassistant/components/lookin/light.py
homeassistant/components/lookin/media_player.py
homeassistant/components/lookin/sensor.py
homeassistant/components/loqed/sensor.py
homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/sensor.py
homeassistant/components/lupusec/*
@@ -711,14 +698,13 @@ omit =
homeassistant/components/metoffice/sensor.py
homeassistant/components/metoffice/weather.py
homeassistant/components/microsoft/tts.py
homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/hub.py
homeassistant/components/mill/climate.py
homeassistant/components/mill/sensor.py
homeassistant/components/minecraft_server/__init__.py
homeassistant/components/minecraft_server/binary_sensor.py
homeassistant/components/minecraft_server/entity.py
homeassistant/components/minecraft_server/sensor.py
homeassistant/components/minio/minio_helper.py
homeassistant/components/mitemp_bt/sensor.py
homeassistant/components/mjpeg/camera.py
homeassistant/components/mjpeg/util.py
homeassistant/components/mochad/__init__.py
@@ -769,6 +755,7 @@ omit =
homeassistant/components/neato/switch.py
homeassistant/components/neato/vacuum.py
homeassistant/components/nederlandse_spoorwegen/sensor.py
homeassistant/components/nest/legacy/*
homeassistant/components/netdata/sensor.py
homeassistant/components/netgear/__init__.py
homeassistant/components/netgear/button.py
@@ -871,9 +858,6 @@ omit =
homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather_update_coordinator.py
homeassistant/components/opnsense/__init__.py
homeassistant/components/opower/__init__.py
homeassistant/components/opower/coordinator.py
homeassistant/components/opower/sensor.py
homeassistant/components/opnsense/device_tracker.py
homeassistant/components/opple/light.py
homeassistant/components/oru/*
@@ -956,8 +940,6 @@ omit =
homeassistant/components/pyload/sensor.py
homeassistant/components/qbittorrent/__init__.py
homeassistant/components/qbittorrent/sensor.py
homeassistant/components/qnap/__init__.py
homeassistant/components/qnap/coordinator.py
homeassistant/components/qnap/sensor.py
homeassistant/components/qrcode/image_processing.py
homeassistant/components/quantum_gateway/device_tracker.py
@@ -1005,7 +987,6 @@ omit =
homeassistant/components/reolink/light.py
homeassistant/components/reolink/number.py
homeassistant/components/reolink/select.py
homeassistant/components/reolink/sensor.py
homeassistant/components/reolink/siren.py
homeassistant/components/reolink/switch.py
homeassistant/components/reolink/update.py
@@ -1063,6 +1044,12 @@ omit =
homeassistant/components/sense/__init__.py
homeassistant/components/sense/binary_sensor.py
homeassistant/components/sense/sensor.py
homeassistant/components/senseme/__init__.py
homeassistant/components/senseme/discovery.py
homeassistant/components/senseme/entity.py
homeassistant/components/senseme/fan.py
homeassistant/components/senseme/light.py
homeassistant/components/senseme/switch.py
homeassistant/components/senz/__init__.py
homeassistant/components/senz/api.py
homeassistant/components/senz/climate.py
@@ -1331,7 +1318,6 @@ omit =
homeassistant/components/tradfri/sensor.py
homeassistant/components/tradfri/switch.py
homeassistant/components/trafikverket_train/__init__.py
homeassistant/components/trafikverket_train/coordinator.py
homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/__init__.py
homeassistant/components/trafikverket_weatherstation/coordinator.py

View File

@@ -7,46 +7,42 @@
"containerEnv": { "DEVCONTAINER": "1" },
"appPort": ["8123:8123"],
"runArgs": ["-e", "GIT_EDITOR=code --wait"],
"customizations": {
"vscode": {
"extensions": [
"ms-python.vscode-pylance",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"esbenp.prettier-vscode",
"GitHub.vscode-pull-request-github"
],
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.blackPath": "/usr/local/bin/black",
"python.linting.pycodestylePath": "/usr/local/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/bin/pydocstyle",
"python.linting.mypyPath": "/usr/local/bin/mypy",
"python.linting.pylintPath": "/usr/local/bin/pylint",
"python.formatting.provider": "black",
"python.testing.pytestArgs": ["--no-cov"],
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/usr/bin/zsh"
}
},
"terminal.integrated.defaultProfile.linux": "zsh",
"yaml.customTags": [
"!input scalar",
"!secret scalar",
"!include_dir_named scalar",
"!include_dir_list scalar",
"!include_dir_merge_list scalar",
"!include_dir_merge_named scalar"
]
"extensions": [
"ms-python.vscode-pylance",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"esbenp.prettier-vscode",
"GitHub.vscode-pull-request-github"
],
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.blackPath": "/usr/local/bin/black",
"python.linting.pycodestylePath": "/usr/local/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/bin/pydocstyle",
"python.linting.mypyPath": "/usr/local/bin/mypy",
"python.linting.pylintPath": "/usr/local/bin/pylint",
"python.formatting.provider": "black",
"python.testing.pytestArgs": ["--no-cov"],
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/usr/bin/zsh"
}
}
},
"terminal.integrated.defaultProfile.linux": "zsh",
"yaml.customTags": [
"!input scalar",
"!secret scalar",
"!include_dir_named scalar",
"!include_dir_list scalar",
"!include_dir_merge_list scalar",
"!include_dir_merge_named scalar"
]
}
}

View File

@@ -59,15 +59,15 @@ body:
attributes:
label: Integration causing the issue
description: >
The name of the integration, for example Automation or Philips Hue.
The name of the integration. For example: Automation, Philips Hue
- type: input
id: integration_link
attributes:
label: Link to integration documentation on our website
placeholder: "https://www.home-assistant.io/integrations/..."
description: |
Providing a link [to the documentation][docs] helps us categorize the issue and might speed up the
investigation by automatically informing a contributor, while also providing a useful reference for others.
Providing a link [to the documentation][docs] helps us categorize the
issue, while also providing a useful reference for others.
[docs]: https://www.home-assistant.io/integrations

View File

@@ -10,7 +10,7 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.10"
jobs:
init:
@@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -59,7 +59,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -124,7 +124,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -197,7 +197,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2023.06.1
uses: home-assistant/builder@2023.06.0
with:
args: |
$BUILD_ARGS \
@@ -274,7 +274,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2023.06.1
uses: home-assistant/builder@2023.06.0
with:
args: |
$BUILD_ARGS \
@@ -324,16 +324,12 @@ jobs:
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.3
- name: Install Cosign
uses: sigstore/cosign-installer@v3.1.1
uses: sigstore/cosign-installer@v3.0.5
with:
cosign-release: "v2.0.2"

View File

@@ -32,9 +32,9 @@ env:
CACHE_VERSION: 5
PIP_CACHE_VERSION: 4
MYPY_CACHE_VERSION: 4
HA_SHORT_VERSION: 2023.8
DEFAULT_PYTHON: "3.11"
ALL_PYTHON_VERSIONS: "['3.11']"
HA_SHORT_VERSION: 2023.7
DEFAULT_PYTHON: "3.10"
ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support
@@ -209,7 +209,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -253,7 +253,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -299,7 +299,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -348,7 +348,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -443,7 +443,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -492,10 +492,10 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
PIP_CACHE_DIR=$PIP_CACHE pip install -U "pip>=21.3.1" setuptools wheel
PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_all.txt
PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_test.txt
pip install -e . --config-settings editable_mode=compat
pip install --cache-dir=$PIP_CACHE -U "pip>=21.3.1,<23.2" setuptools wheel
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
pip install .
hassfest:
name: Check hassfest
@@ -511,7 +511,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -543,7 +543,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -576,7 +576,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -620,7 +620,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -702,7 +702,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -827,7 +827,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -934,7 +934,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1019,7 +1019,6 @@ jobs:
with: |
fail_ci_if_error: true
flags: full-suite
token: ${{ env.CODECOV_TOKEN }}
attempt_limit: 5
attempt_delay: 30000
- name: Upload coverage to Codecov (partial coverage)
@@ -1029,6 +1028,5 @@ jobs:
action: codecov/codecov-action@v3.1.3
with: |
fail_ci_if_error: true
token: ${{ env.CODECOV_TOKEN }}
attempt_limit: 5
attempt_delay: 30000

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.10"
jobs:
upload:
@@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.6.1
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@@ -47,7 +47,10 @@ jobs:
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
# Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
@@ -80,7 +83,7 @@ jobs:
strategy:
fail-fast: false
matrix:
abi: ["cp311"]
abi: ["cp310", "cp311"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
@@ -110,7 +113,7 @@ jobs:
requirements-diff: "requirements_diff.txt"
requirements: "requirements.txt"
integrations_cp311:
integrations_cp310:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant'
needs: init
@@ -118,7 +121,7 @@ jobs:
strategy:
fail-fast: false
matrix:
abi: ["cp311"]
abi: ["cp310"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
@@ -134,30 +137,20 @@ jobs:
with:
name: requirements_diff
- name: (Un)comment packages
- name: Uncomment packages
run: |
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora-wifi|decora-wifi|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
# Some packages are not buildable on armhf anymore
if [ "${{ matrix.arch }}" = "armhf" ]; then
# Pandas has issues building on armhf, it is expected they
# will drop the platform in the near future (they consider it
# "flimsy" on 386). The following packages depend on pandas,
# so we comment them out.
sed -i "s|env-canada|# env-canada|g" ${requirement_file}
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
fi
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
done
- name: Split requirements all
@@ -174,6 +167,165 @@ jobs:
echo "NPY_DISABLE_SVML=1" >> .env_file
fi
(
# cmake > 3.22.2 have issue on arm
# Tested until 3.22.5
echo "cmake==3.22.2"
) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels (part 1)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtaa"
- name: Build wheels (part 2)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtab"
- name: Build wheels (part 3)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtac"
# Wheels building for the cp311 ABI is currently split
# This is mainly until we have figured out to get all wheels built.
# Without harming our current workflow.
integrations_cp311:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
abi: ["cp311"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.3
- name: Write alternative env-file for cp311
run: |
(
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
# GRPC on armv7 needed -lexecinfo (issue #56669) since home assistant installed
# execinfo-dev when building wheels. However, this package is no longer available
# Alpine 3.17, which we use for the cp311 ABI, so the flag should no longer be needed.
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc" # -lexecinfo
# Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
# OpenCV headless installation
echo "CI_BUILD=1"
echo "ENABLE_HEADLESS=1"
# Use C-Extension for sqlalchemy
echo "REQUIRE_SQLALCHEMY_CEXT=1"
) > .env_file
- name: Download requirements_diff
uses: actions/download-artifact@v3
with:
name: requirements_diff
- name: (Un)comment packages
run: |
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
# PyBluez no longer compiles. Commented it out for now.
# It need further cleanup down the line, as all machine images
# try to install it.
# sed -i "s|# pybluez|pybluez|g" ${requirement_file}
# beacontools requires PyBluez.
# sed -i "s|# beacontools|beacontools|g" ${requirement_file}
# It doesn't build for some reason, so we skip it for now.
# Bumping to the latest version (4.7.0.72) supporting Python 3.11
# doesn't help. Reverted bump in #91871. There are 8 registered
# instances using this integration according to analytics.
# sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora-wifi|decora-wifi|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
# Some packages are not buildable on armhf anymore
if [ "${{ matrix.arch }}" = "armhf" ]; then
# Pandas has issues building on armhf, it is expected they
# will drop the platform in the near future (they consider it
# "flimsy" on 386). The following packages depend on pandas,
# so we comment them out.
sed -i "s|env-canada|# env-canada|g" ${requirement_file}
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
fi
done
- name: Split requirements all
run: |
# We split requirements all into two different files.
# This is to prevent the build from running out of memory when
# resolving packages on 32-bits systems (like armhf, armv7).
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all.txt requirements_all.txt
- name: Adjust build env
run: |
if [ "${{ matrix.arch }}" = "i386" ]; then
echo "NPY_DISABLE_SVML=1" >> .env_file
fi
# Probably not an issue anymore. Removing for now.
# (
# # cmake > 3.22.2 have issue on arm
# # Tested until 3.22.5
# echo "cmake==3.22.2"
# ) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt

View File

@@ -1,12 +1,12 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.280
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.272
hooks:
- id: ruff
args:
- --fix
- repo: https://github.com/psf/black
rev: 23.7.0
rev: 23.3.0
hooks:
- id: black
args:
@@ -17,8 +17,8 @@ repos:
hooks:
- id: codespell
args:
- --ignore-words-list=additionals,alle,alot,bund,currenty,datas,farenheit,falsy,fo,haa,hass,iif,incomfort,ines,ist,nam,nd,pres,pullrequests,resset,rime,ser,serie,te,technik,ue,unsecure,withing,zar
- --skip="./.*,*.csv,*.json,*.ambr"
- --ignore-words-list=additionals,alle,alot,ba,bre,bund,currenty,datas,dof,dur,ether,farenheit,falsy,fo,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nam,nd,pres,pullrequests,referer,resset,rime,ser,serie,sur,te,technik,ue,uint,unsecure,visability,wan,wanna,withing,zar
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
exclude: ^tests/fixtures/|homeassistant/generated/
@@ -35,7 +35,7 @@ repos:
- --branch=master
- --branch=rc
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.32.0
rev: v1.28.0
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier

View File

@@ -108,13 +108,11 @@ homeassistant.components.dsmr.*
homeassistant.components.dunehd.*
homeassistant.components.efergy.*
homeassistant.components.electrasmart.*
homeassistant.components.electric_kiwi.*
homeassistant.components.elgato.*
homeassistant.components.elkm1.*
homeassistant.components.emulated_hue.*
homeassistant.components.energy.*
homeassistant.components.esphome.*
homeassistant.components.event.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.fan.*
homeassistant.components.fastdotcom.*
@@ -279,6 +277,7 @@ homeassistant.components.scene.*
homeassistant.components.schedule.*
homeassistant.components.scrape.*
homeassistant.components.select.*
homeassistant.components.senseme.*
homeassistant.components.sensibo.*
homeassistant.components.sensirion_ble.*
homeassistant.components.sensor.*

View File

@@ -195,8 +195,8 @@ build.json @home-assistant/supervisor
/tests/components/camera/ @home-assistant/core
/homeassistant/components/cast/ @emontnemery
/tests/components/cast/ @emontnemery
/homeassistant/components/cert_expiry/ @jjlawren
/tests/components/cert_expiry/ @jjlawren
/homeassistant/components/cert_expiry/ @Cereal2nd @jjlawren
/tests/components/cert_expiry/ @Cereal2nd @jjlawren
/homeassistant/components/circuit/ @braam
/homeassistant/components/cisco_ios/ @fbradyirl
/homeassistant/components/cisco_mobility_express/ @fbradyirl
@@ -277,6 +277,8 @@ build.json @home-assistant/supervisor
/tests/components/discord/ @tkdrob
/homeassistant/components/discovergy/ @jpbede
/tests/components/discovergy/ @jpbede
/homeassistant/components/discovery/ @home-assistant/core
/tests/components/discovery/ @home-assistant/core
/homeassistant/components/dlink/ @tkdrob
/tests/components/dlink/ @tkdrob
/homeassistant/components/dlna_dmr/ @StevenLooman @chishm
@@ -297,8 +299,6 @@ build.json @home-assistant/supervisor
/tests/components/dsmr_reader/ @depl0y @glodenox
/homeassistant/components/dunehd/ @bieniu
/tests/components/dunehd/ @bieniu
/homeassistant/components/duotecno/ @cereal2nd
/tests/components/duotecno/ @cereal2nd
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95 @andarotajo
/tests/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95 @andarotajo
/homeassistant/components/dynalite/ @ziv1234
@@ -321,8 +321,6 @@ build.json @home-assistant/supervisor
/tests/components/eight_sleep/ @mezz64 @raman325
/homeassistant/components/electrasmart/ @jafar-atili
/tests/components/electrasmart/ @jafar-atili
/homeassistant/components/electric_kiwi/ @mikey0000
/tests/components/electric_kiwi/ @mikey0000
/homeassistant/components/elgato/ @frenck
/tests/components/elgato/ @frenck
/homeassistant/components/elkm1/ @gwww @bdraco
@@ -362,8 +360,6 @@ build.json @home-assistant/supervisor
/tests/components/esphome/ @OttoWinter @jesserockz @bdraco
/homeassistant/components/eufylife_ble/ @bdr99
/tests/components/eufylife_ble/ @bdr99
/homeassistant/components/event/ @home-assistant/core
/tests/components/event/ @home-assistant/core
/homeassistant/components/evil_genius_labs/ @balloob
/tests/components/evil_genius_labs/ @balloob
/homeassistant/components/evohome/ @zxdavb
@@ -429,8 +425,6 @@ build.json @home-assistant/supervisor
/tests/components/fully_kiosk/ @cgarwood
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
/tests/components/garages_amsterdam/ @klaasnicolaas
/homeassistant/components/gardena_bluetooth/ @elupus
/tests/components/gardena_bluetooth/ @elupus
/homeassistant/components/gdacs/ @exxamalte
/tests/components/gdacs/ @exxamalte
/homeassistant/components/generic/ @davet2001
@@ -709,8 +703,6 @@ build.json @home-assistant/supervisor
/tests/components/logi_circle/ @evanjd
/homeassistant/components/lookin/ @ANMalko @bdraco
/tests/components/lookin/ @ANMalko @bdraco
/homeassistant/components/loqed/ @mikewoudenberg
/tests/components/loqed/ @mikewoudenberg
/homeassistant/components/lovelace/ @home-assistant/frontend
/tests/components/lovelace/ @home-assistant/frontend
/homeassistant/components/luci/ @mzdrale
@@ -751,6 +743,7 @@ build.json @home-assistant/supervisor
/tests/components/meteoclimatic/ @adrianmo
/homeassistant/components/metoffice/ @MrHarcombe @avee87
/tests/components/metoffice/ @MrHarcombe @avee87
/homeassistant/components/miflora/ @danielhiversen @basnijholt
/homeassistant/components/mikrotik/ @engrbm87
/tests/components/mikrotik/ @engrbm87
/homeassistant/components/mill/ @danielhiversen
@@ -894,7 +887,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/openhome/ @bazwilliams
/tests/components/openhome/ @bazwilliams
/homeassistant/components/opensky/ @joostlek
/tests/components/opensky/ @joostlek
/homeassistant/components/opentherm_gw/ @mvn23
/tests/components/opentherm_gw/ @mvn23
/homeassistant/components/openuv/ @bachya
@@ -903,8 +895,6 @@ build.json @home-assistant/supervisor
/tests/components/openweathermap/ @fabaff @freekode @nzapponi
/homeassistant/components/opnsense/ @mtreinish
/tests/components/opnsense/ @mtreinish
/homeassistant/components/opower/ @tronikos
/tests/components/opower/ @tronikos
/homeassistant/components/oralb/ @bdraco @Lash-L
/tests/components/oralb/ @bdraco @Lash-L
/homeassistant/components/oru/ @bvlaicu
@@ -922,8 +912,6 @@ build.json @home-assistant/supervisor
/tests/components/panel_iframe/ @home-assistant/frontend
/homeassistant/components/peco/ @IceBotYT
/tests/components/peco/ @IceBotYT
/homeassistant/components/pegel_online/ @mib1185
/tests/components/pegel_online/ @mib1185
/homeassistant/components/persistent_notification/ @home-assistant/core
/tests/components/persistent_notification/ @home-assistant/core
/homeassistant/components/philips_js/ @elupus
@@ -982,7 +970,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/qld_bushfire/ @exxamalte
/tests/components/qld_bushfire/ @exxamalte
/homeassistant/components/qnap/ @disforw
/tests/components/qnap/ @disforw
/homeassistant/components/qnap_qsw/ @Noltari
/tests/components/qnap_qsw/ @Noltari
/homeassistant/components/quantum_gateway/ @cisasteelersfan
@@ -1010,8 +997,8 @@ build.json @home-assistant/supervisor
/tests/components/rapt_ble/ @sairon
/homeassistant/components/raspberry_pi/ @home-assistant/core
/tests/components/raspberry_pi/ @home-assistant/core
/homeassistant/components/rdw/ @frenck @joostlek
/tests/components/rdw/ @frenck @joostlek
/homeassistant/components/rdw/ @frenck
/tests/components/rdw/ @frenck
/homeassistant/components/recollect_waste/ @bachya
/tests/components/recollect_waste/ @bachya
/homeassistant/components/recorder/ @home-assistant/core
@@ -1092,6 +1079,8 @@ build.json @home-assistant/supervisor
/tests/components/select/ @home-assistant/core
/homeassistant/components/sense/ @kbickar
/tests/components/sense/ @kbickar
/homeassistant/components/senseme/ @mikelawrence @bdraco
/tests/components/senseme/ @mikelawrence @bdraco
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
/tests/components/sensibo/ @andrey-git @gjohansson-ST
/homeassistant/components/sensirion_ble/ @akx

View File

@@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-homeassistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.07.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.07.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.07.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.07.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.07.0
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.1
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.1
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.1
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.1
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.1
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

@@ -1,15 +1,32 @@
"""Enum backports from standard lib.
This file contained the backport of the StrEnum of Python 3.11.
Since we have dropped support for Python 3.10, we can remove this backport.
This file is kept for now to avoid breaking custom components that might
import it.
"""
"""Enum backports from standard lib."""
from __future__ import annotations
from enum import StrEnum
from enum import Enum
from typing import Any
__all__ = [
"StrEnum",
]
from typing_extensions import Self
class StrEnum(str, Enum):
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
def __new__(cls, value: str, *args: Any, **kwargs: Any) -> Self:
"""Create a new StrEnum instance."""
if not isinstance(value, str):
raise TypeError(f"{value!r} is not a string")
return super().__new__(cls, value, *args, **kwargs)
def __str__(self) -> str:
"""Return self.value."""
return str(self.value)
@staticmethod
def _generate_next_value_(
name: str, start: int, count: int, last_values: list[Any]
) -> Any:
"""Make `auto()` explicitly unsupported.
We may revisit this when it's very clear that Python 3.11's
`StrEnum.auto()` behavior will no longer change.
"""
raise TypeError("auto() is not supported by this implementation")

View File

@@ -3,24 +3,27 @@ from __future__ import annotations
from collections.abc import Callable
from types import GenericAlias
from typing import Any, Generic, Self, TypeVar, overload
from typing import Any, Generic, TypeVar, overload
from typing_extensions import Self
_T = TypeVar("_T")
_R = TypeVar("_R")
class cached_property(Generic[_T]): # pylint: disable=invalid-name
class cached_property(Generic[_T, _R]): # pylint: disable=invalid-name
"""Backport of Python 3.12's cached_property.
Includes https://github.com/python/cpython/pull/101890/files
"""
def __init__(self, func: Callable[[Any], _T]) -> None:
def __init__(self, func: Callable[[_T], _R]) -> None:
"""Initialize."""
self.func: Callable[[Any], _T] = func
self.attrname: str | None = None
self.func = func
self.attrname: Any = None
self.__doc__ = func.__doc__
def __set_name__(self, owner: type[Any], name: str) -> None:
def __set_name__(self, owner: type[_T], name: str) -> None:
"""Set name."""
if self.attrname is None:
self.attrname = name
@@ -31,16 +34,14 @@ class cached_property(Generic[_T]): # pylint: disable=invalid-name
)
@overload
def __get__(self, instance: None, owner: type[Any] | None = None) -> Self:
def __get__(self, instance: None, owner: type[_T]) -> Self:
...
@overload
def __get__(self, instance: Any, owner: type[Any] | None = None) -> _T:
def __get__(self, instance: _T, owner: type[_T]) -> _R:
...
def __get__(
self, instance: Any | None, owner: type[Any] | None = None
) -> _T | Self:
def __get__(self, instance: _T | None, owner: type[_T] | None = None) -> _R | Self:
"""Get."""
if instance is None:
return self

View File

@@ -1,5 +1,5 @@
{
"domain": "u_tec",
"name": "U-tec",
"iot_standards": ["zwave"]
"integrations": ["ultraloq"]
}

View File

@@ -34,7 +34,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
"""An alarm_control_panel implementation for Abode."""
_attr_icon = ICON
_attr_name = None
_attr_code_arm_required = False
_attr_supported_features = (
AlarmControlPanelEntityFeature.ARM_HOME

View File

@@ -39,7 +39,6 @@ class AbodeCamera(AbodeDevice, Camera):
"""Representation of an Abode camera."""
_device: AbodeCam
_attr_name = None
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
"""Initialize the Abode device."""

View File

@@ -29,7 +29,6 @@ class AbodeCover(AbodeDevice, CoverEntity):
"""Representation of an Abode cover."""
_device: AbodeCV
_attr_name = None
@property
def is_closed(self) -> bool:

View File

@@ -42,7 +42,6 @@ class AbodeLight(AbodeDevice, LightEntity):
"""Representation of an Abode light."""
_device: AbodeLT
_attr_name = None
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""

View File

@@ -29,7 +29,6 @@ class AbodeLock(AbodeDevice, LockEntity):
"""Representation of an Abode lock."""
_device: AbodeLK
_attr_name = None
def lock(self, **kwargs: Any) -> None:
"""Lock the device."""

View File

@@ -53,6 +53,7 @@ class AbodeSensor(AbodeDevice, SensorEntity):
"""A sensor implementation for Abode devices."""
_device: AbodeSense
_attr_has_entity_name = True
def __init__(
self,

View File

@@ -1,6 +1,10 @@
capture_image:
name: Capture image
description: Request a new image capture from a camera device.
fields:
entity_id:
name: Entity
description: Entity id of the camera to request an image.
required: true
selector:
entity:
@@ -8,21 +12,31 @@ capture_image:
domain: camera
change_setting:
name: Change setting
description: Change an Abode system setting.
fields:
setting:
name: Setting
description: Setting to change.
required: true
example: beeper_mute
selector:
text:
value:
name: Value
description: Value of the setting.
required: true
example: "1"
selector:
text:
trigger_automation:
name: Trigger automation
description: Trigger an Abode automation.
fields:
entity_id:
name: Entity
description: Entity id of the automation to trigger.
required: true
selector:
entity:

View File

@@ -15,7 +15,7 @@
}
},
"reauth_confirm": {
"title": "[%key:component::abode::config::step::user::title%]",
"title": "Fill in your Abode login information",
"data": {
"username": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
@@ -31,41 +31,5 @@
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"services": {
"capture_image": {
"name": "Capture image",
"description": "Request a new image capture from a camera device.",
"fields": {
"entity_id": {
"name": "Entity",
"description": "Entity id of the camera to request an image."
}
}
},
"change_setting": {
"name": "Change setting",
"description": "Change an Abode system setting.",
"fields": {
"setting": {
"name": "Setting",
"description": "Setting to change."
},
"value": {
"name": "Value",
"description": "Value of the setting."
}
}
},
"trigger_automation": {
"name": "Trigger automation",
"description": "Trigger an Abode automation.",
"fields": {
"entity_id": {
"name": "Entity",
"description": "Entity id of the automation to trigger."
}
}
}
}
}

View File

@@ -44,7 +44,6 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
"""Representation of an Abode switch."""
_device: AbodeSW
_attr_name = None
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the device."""

View File

@@ -3,7 +3,7 @@
"name": "AccuWeather",
"codeowners": ["@bieniu"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/accuweather",
"documentation": "https://www.home-assistant.io/integrations/accuweather/",
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["accuweather"],

View File

@@ -25,6 +25,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AccuWeatherDataUpdateCoordinator
@@ -49,7 +50,7 @@ PARALLEL_UPDATES = 1
class AccuWeatherSensorDescriptionMixin:
"""Mixin for AccuWeather sensor."""
value_fn: Callable[[dict[str, Any]], str | int | float | None]
value_fn: Callable[[dict[str, Any]], StateType]
@dataclass
@@ -58,7 +59,7 @@ class AccuWeatherSensorDescription(
):
"""Class describing AccuWeather sensor entities."""
attr_fn: Callable[[dict[str, Any]], dict[str, Any]] = lambda _: {}
attr_fn: Callable[[dict[str, Any]], dict[str, StateType]] = lambda _: {}
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
@@ -427,7 +428,7 @@ class AccuWeatherSensor(
self.forecast_day = forecast_day
@property
def native_value(self) -> str | int | float | None:
def native_value(self) -> StateType:
"""Return the state."""
return self.entity_description.value_fn(self._sensor_data)

View File

@@ -14,7 +14,6 @@ from homeassistant.components.weather import (
ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TIME,
ATTR_FORECAST_UV_INDEX,
ATTR_FORECAST_WIND_BEARING,
Forecast,
WeatherEntity,
@@ -148,11 +147,6 @@ class AccuWeatherEntity(
"""Return the visibility."""
return cast(float, self.coordinator.data["Visibility"][API_METRIC][ATTR_VALUE])
@property
def uv_index(self) -> float:
"""Return the UV index."""
return cast(float, self.coordinator.data["UVIndex"])
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
@@ -178,7 +172,6 @@ class AccuWeatherEntity(
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: item["WindGustDay"][ATTR_SPEED][
ATTR_VALUE
],
ATTR_FORECAST_UV_INDEX: item["UVIndex"][ATTR_VALUE],
ATTR_FORECAST_WIND_BEARING: item["WindDay"][ATTR_DIRECTION]["Degrees"],
ATTR_FORECAST_CONDITION: [
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
import logging
import telnetlib # pylint: disable=deprecated-module
import telnetlib
from typing import Final
import voluptuous as vol

View File

@@ -1,43 +1,65 @@
add_url:
name: Add url
description: Add a new filter subscription to AdGuard Home.
fields:
name:
name: Name
description: The name of the filter subscription.
required: true
example: Example
selector:
text:
url:
name: Url
description: The filter URL to subscribe to, containing the filter rules.
required: true
example: https://www.example.com/filter/1.txt
selector:
text:
remove_url:
name: Remove url
description: Removes a filter subscription from AdGuard Home.
fields:
url:
name: Url
description: The filter subscription URL to remove.
required: true
example: https://www.example.com/filter/1.txt
selector:
text:
enable_url:
name: Enable url
description: Enables a filter subscription in AdGuard Home.
fields:
url:
name: Url
description: The filter subscription URL to enable.
required: true
example: https://www.example.com/filter/1.txt
selector:
text:
disable_url:
name: Disable url
description: Disables a filter subscription in AdGuard Home.
fields:
url:
name: Url
description: The filter subscription URL to disable.
required: true
example: https://www.example.com/filter/1.txt
selector:
text:
refresh:
name: Refresh
description: Refresh all filter subscriptions in AdGuard Home.
fields:
force:
name: Force
description: Force update (bypasses AdGuard Home throttling). "true" to force, or "false" to omit for a regular refresh.
default: false
selector:
boolean:

View File

@@ -72,61 +72,5 @@
"name": "Query log"
}
}
},
"services": {
"add_url": {
"name": "Add URL",
"description": "Add a new filter subscription to AdGuard Home.",
"fields": {
"name": {
"name": "[%key:common::config_flow::data::name%]",
"description": "The name of the filter subscription."
},
"url": {
"name": "[%key:common::config_flow::data::url%]",
"description": "The filter URL to subscribe to, containing the filter rules."
}
}
},
"remove_url": {
"name": "Remove URL",
"description": "Removes a filter subscription from AdGuard Home.",
"fields": {
"url": {
"name": "[%key:common::config_flow::data::url%]",
"description": "The filter subscription URL to remove."
}
}
},
"enable_url": {
"name": "Enable URL",
"description": "Enables a filter subscription in AdGuard Home.",
"fields": {
"url": {
"name": "[%key:common::config_flow::data::url%]",
"description": "The filter subscription URL to enable."
}
}
},
"disable_url": {
"name": "Disable URL",
"description": "Disables a filter subscription in AdGuard Home.",
"fields": {
"url": {
"name": "[%key:common::config_flow::data::url%]",
"description": "The filter subscription URL to disable."
}
}
},
"refresh": {
"name": "Refresh",
"description": "Refresh all filter subscriptions in AdGuard Home.",
"fields": {
"force": {
"name": "Force",
"description": "Force update (bypasses AdGuard Home throttling). \"true\" to force, or \"false\" to omit for a regular refresh."
}
}
}
}
}

View File

@@ -1,13 +1,19 @@
# Describes the format for available ADS services
write_data_by_name:
name: Write data by name
description: Write a value to the connected ADS device.
fields:
adsvar:
name: ADS variable
description: The name of the variable to write to.
required: true
example: ".global_var"
selector:
text:
adstype:
name: ADS type
description: The data type of the variable to write to.
required: true
selector:
select:
@@ -19,6 +25,8 @@ write_data_by_name:
- "udint"
- "uint"
value:
name: Value
description: The value to write to the variable.
required: true
selector:
number:

View File

@@ -1,22 +0,0 @@
{
"services": {
"write_data_by_name": {
"name": "Write data by name",
"description": "Write a value to the connected ADS device.",
"fields": {
"adsvar": {
"name": "ADS variable",
"description": "The name of the variable to write to."
},
"adstype": {
"name": "ADS type",
"description": "The data type of the variable to write to."
},
"value": {
"name": "Value",
"description": "The value to write to the variable."
}
}
}
}
}

View File

@@ -90,7 +90,6 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
_attr_target_temperature_step = PRECISION_WHOLE
_attr_max_temp = 32
_attr_min_temp = 16
_attr_name = None
_attr_hvac_modes = [
HVACMode.OFF,

View File

@@ -9,30 +9,17 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
TO_REDACT = [
"dealerPhoneNumber",
"latitude",
"logoPIN",
"longitude",
"postCode",
"rid",
"deviceNames",
"deviceIds",
"deviceIdsV2",
"backupId",
]
TO_REDACT = ["dealerPhoneNumber", "latitude", "logoPIN", "longitude", "postCode"]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id].coordinator.data
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]["coordinator"].data
# Return only the relevant children
return {
"aircons": data.get("aircons"),
"myLights": data.get("myLights"),
"myThings": data.get("myThings"),
"aircons": data["aircons"],
"system": async_redact_data(data["system"], TO_REDACT),
}

View File

@@ -84,8 +84,6 @@ class AdvantageAirZoneEntity(AdvantageAirAcEntity):
class AdvantageAirThingEntity(AdvantageAirEntity):
"""Parent class for Advantage Air Things Entities."""
_attr_name = None
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
"""Initialize common aspects of an Advantage Air Things entity."""
super().__init__(instance)

View File

@@ -41,7 +41,6 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
"""Representation of Advantage Air Light."""
_attr_supported_color_modes = {ColorMode.ONOFF}
_attr_name = None
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
"""Initialize an Advantage Air Light."""

View File

@@ -1,10 +1,14 @@
set_time_to:
name: Set Time To
description: Control timers to turn the system on or off after a set number of minutes
target:
entity:
integration: advantage_air
domain: sensor
fields:
minutes:
name: Minutes
description: Minutes until action
required: true
selector:
number:

View File

@@ -13,19 +13,7 @@
"port": "[%key:common::config_flow::data::port%]"
},
"description": "Connect to the API of your Advantage Air wall mounted tablet.",
"title": "[%key:common::action::connect%]"
}
}
},
"services": {
"set_time_to": {
"name": "Set time to",
"description": "Controls timers to turn the system on or off after a set number of minutes.",
"fields": {
"minutes": {
"name": "Minutes",
"description": "Minutes until action."
}
"title": "Connect"
}
}
}

View File

@@ -1,29 +1,43 @@
# Describes the format for available aftership services
add_tracking:
name: Add tracking
description: Add new tracking number to Aftership.
fields:
tracking_number:
name: Tracking number
description: Tracking number for the new tracking
required: true
example: "123456789"
selector:
text:
title:
name: Title
description: A custom title for the new tracking
example: "Laptop"
selector:
text:
slug:
name: Slug
description: Slug (carrier) of the new tracking
example: "USPS"
selector:
text:
remove_tracking:
name: Remove tracking
description: Remove a tracking number from Aftership.
fields:
tracking_number:
name: Tracking number
description: Tracking number of the tracking to remove
required: true
example: "123456789"
selector:
text:
slug:
name: Slug
description: Slug (carrier) of the tracking to remove
example: "USPS"
selector:
text:

View File

@@ -1,36 +0,0 @@
{
"services": {
"add_tracking": {
"name": "Add tracking",
"description": "Adds a new tracking number to Aftership.",
"fields": {
"tracking_number": {
"name": "Tracking number",
"description": "Tracking number for the new tracking."
},
"title": {
"name": "Title",
"description": "A custom title for the new tracking."
},
"slug": {
"name": "Slug",
"description": "Slug (carrier) of the new tracking."
}
}
},
"remove_tracking": {
"name": "Remove tracking",
"description": "Removes a tracking number from Aftership.",
"fields": {
"tracking_number": {
"name": "[%key:component::aftership::services::add_tracking::fields::tracking_number::name%]",
"description": "Tracking number of the tracking to remove."
},
"slug": {
"name": "[%key:component::aftership::services::add_tracking::fields::slug::name%]",
"description": "Slug (carrier) of the tracking to remove."
}
}
}
}
}

View File

@@ -47,16 +47,14 @@ class AgentBaseStation(AlarmControlPanelEntity):
| AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_NIGHT
)
_attr_has_entity_name = True
_attr_name = None
def __init__(self, client):
"""Initialize the alarm control panel."""
self._client = client
self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}"
self._attr_unique_id = f"{client.unique}_CP"
self._attr_device_info = DeviceInfo(
identifiers={(AGENT_DOMAIN, client.unique)},
name=f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}",
manufacturer="Agent",
model=CONST_ALARM_CONTROL_PANEL_NAME,
sw_version=client.version,

View File

@@ -72,13 +72,12 @@ class AgentCamera(MjpegCamera):
_attr_attribution = ATTRIBUTION
_attr_should_poll = True # Cameras default to False
_attr_supported_features = CameraEntityFeature.ON_OFF
_attr_has_entity_name = True
_attr_name = None
def __init__(self, device):
"""Initialize as a subclass of MjpegCamera."""
self.device = device
self._removed = False
self._attr_name = f"{device.client.name} {device.name}"
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
super().__init__(
name=device.name,
@@ -89,7 +88,7 @@ class AgentCamera(MjpegCamera):
identifiers={(AGENT_DOMAIN, self.unique_id)},
manufacturer="Agent",
model="Camera",
name=f"{device.client.name} {device.name}",
name=self.name,
sw_version=device.client.version,
)

View File

@@ -3,7 +3,7 @@
"name": "Agent DVR",
"codeowners": ["@ispysoftware"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/agent_dvr",
"documentation": "https://www.home-assistant.io/integrations/agent_dvr/",
"iot_class": "local_polling",
"loggers": ["agent"],
"requirements": ["agent-py==0.0.23"]

View File

@@ -1,28 +1,38 @@
start_recording:
name: Start recording
description: Enable continuous recording.
target:
entity:
integration: agent_dvr
domain: camera
stop_recording:
name: Stop recording
description: Disable continuous recording.
target:
entity:
integration: agent_dvr
domain: camera
enable_alerts:
name: Enable alerts
description: Enable alerts
target:
entity:
integration: agent_dvr
domain: camera
disable_alerts:
name: Disable alerts
description: Disable alerts
target:
entity:
integration: agent_dvr
domain: camera
snapshot:
name: Snapshot
description: Take a photo
target:
entity:
integration: agent_dvr

View File

@@ -16,27 +16,5 @@
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}
},
"services": {
"start_recording": {
"name": "Start recording",
"description": "Enables continuous recording."
},
"stop_recording": {
"name": "Stop recording",
"description": "Disables continuous recording."
},
"enable_alerts": {
"name": "Enable alerts",
"description": "Enables alerts."
},
"disable_alerts": {
"name": "Disable alerts",
"description": "Disables alerts."
},
"snapshot": {
"name": "Snapshot",
"description": "Takes a photo."
}
}
}

View File

@@ -90,7 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
str(longitude),
),
):
device_entry = device_registry.async_get_device(identifiers={old_ids}) # type: ignore[arg-type]
device_entry = device_registry.async_get_device({old_ids}) # type: ignore[arg-type]
if device_entry and entry.entry_id in device_entry.config_entries:
new_ids = (DOMAIN, f"{latitude}-{longitude}")
device_registry.async_update_device(

View File

@@ -17,7 +17,7 @@
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
"wrong_location": "[%key:component::airly::config::error::wrong_location%]"
"wrong_location": "No Airly measuring stations in this area."
}
},
"system_health": {

View File

@@ -2,7 +2,7 @@
import logging
from pyairnow import WebServiceAPI
from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError
from pyairnow.errors import AirNowError, InvalidKeyError
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
@@ -35,8 +35,6 @@ async def validate_input(hass: core.HomeAssistant, data):
raise InvalidAuth from exc
except AirNowError as exc:
raise CannotConnect from exc
except EmptyResponseError as exc:
raise InvalidLocation from exc
if not test_data:
raise InvalidLocation

View File

@@ -14,7 +14,7 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_location": "No results found for that location, try changing the location or station radius.",
"invalid_location": "No results found for that location",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {

View File

@@ -3,20 +3,7 @@
"name": "Airthings BLE",
"bluetooth": [
{
"manufacturer_id": 820,
"service_uuid": "b42e1f6e-ade7-11e4-89d3-123b93f75cba"
},
{
"manufacturer_id": 820,
"service_uuid": "b42e4a8e-ade7-11e4-89d3-123b93f75cba"
},
{
"manufacturer_id": 820,
"service_uuid": "b42e1c08-ade7-11e4-89d3-123b93f75cba"
},
{
"manufacturer_id": 820,
"service_uuid": "b42e3882-ade7-11e4-89d3-123b93f75cba"
"manufacturer_id": 820
}
],
"codeowners": ["@vincegio"],

View File

@@ -84,9 +84,6 @@ async def async_setup_entry(
class AirtouchAC(CoordinatorEntity, ClimateEntity):
"""Representation of an AirTouch 4 ac."""
_attr_has_entity_name = True
_attr_name = None
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
)
@@ -110,7 +107,7 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
"""Return device info for this device."""
return DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
name=f"AC {self._ac_number}",
name=self.name,
manufacturer="Airtouch",
model="Airtouch 4",
)
@@ -125,6 +122,11 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
"""Return the current temperature."""
return self._unit.Temperature
@property
def name(self):
"""Return the name of the climate device."""
return f"AC {self._ac_number}"
@property
def fan_mode(self):
"""Return fan mode of the AC this group belongs to."""
@@ -198,8 +200,6 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
class AirtouchGroup(CoordinatorEntity, ClimateEntity):
"""Representation of an AirTouch 4 group."""
_attr_has_entity_name = True
_attr_name = None
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_hvac_modes = AT_GROUP_MODES
@@ -224,7 +224,7 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Airtouch",
model="Airtouch 4",
name=self._unit.GroupName,
name=self.name,
)
@property
@@ -242,6 +242,11 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
"""Return Max Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint
@property
def name(self):
"""Return the name of the climate device."""
return self._unit.GroupName
@property
def current_temperature(self):
"""Return the current temperature."""

View File

@@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["pyairvisual", "pysmb"],
"requirements": ["pyairvisual==2023.08.1"]
"requirements": ["pyairvisual==2022.12.1"]
}

View File

@@ -11,13 +11,13 @@
}
},
"geography_by_name": {
"title": "[%key:component::airvisual::config::step::geography_by_coords::title%]",
"title": "Configure a Geography",
"description": "Use the AirVisual cloud API to monitor a city/state/country.",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"city": "City",
"country": "Country",
"state": "State"
"state": "state"
}
},
"reauth_confirm": {
@@ -45,7 +45,7 @@
"options": {
"step": {
"init": {
"title": "[%key:component::airvisual::config::step::user::title%]",
"title": "Configure AirVisual",
"data": {
"show_on_map": "Show monitored geography on the map"
}

View File

@@ -60,10 +60,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Get data from the device."""
try:
data = await node.async_get_latest_measurements()
data["history"] = {}
if data["settings"].get("follow_mode") == "device":
history = await node.async_get_history(include_trends=False)
data["history"] = history.get("measurements", [])[-1]
except InvalidAuthenticationError as err:
raise ConfigEntryAuthFailed("Invalid Samba password") from err
except NodeConnectionError as err:

View File

@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pyairvisual", "pysmb"],
"requirements": ["pyairvisual==2023.08.1"]
"requirements": ["pyairvisual==2022.12.1"]
}

View File

@@ -30,9 +30,7 @@ from .const import DOMAIN
class AirVisualProMeasurementKeyMixin:
"""Define an entity description mixin to include a measurement key."""
value_fn: Callable[
[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], float | int
]
value_fn: Callable[[dict[str, Any], dict[str, Any], dict[str, Any]], float | int]
@dataclass
@@ -45,81 +43,75 @@ class AirVisualProMeasurementDescription(
SENSOR_DESCRIPTIONS = (
AirVisualProMeasurementDescription(
key="air_quality_index",
name="Air quality index",
device_class=SensorDeviceClass.AQI,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements[
value_fn=lambda settings, status, measurements: measurements[
async_get_aqi_locale(settings)
],
),
AirVisualProMeasurementDescription(
key="outdoor_air_quality_index",
device_class=SensorDeviceClass.AQI,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: int(
history.get(
f'Outdoor {"AQI(US)" if settings["is_aqi_usa"] else "AQI(CN)"}', -1
)
),
translation_key="outdoor_air_quality_index",
),
AirVisualProMeasurementDescription(
key="battery_level",
name="Battery",
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda settings, status, measurements, history: status["battery"],
value_fn=lambda settings, status, measurements: status["battery"],
),
AirVisualProMeasurementDescription(
key="carbon_dioxide",
name="C02",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["co2"],
value_fn=lambda settings, status, measurements: measurements["co2"],
),
AirVisualProMeasurementDescription(
key="humidity",
name="Humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda settings, status, measurements, history: measurements[
"humidity"
],
value_fn=lambda settings, status, measurements: measurements["humidity"],
),
AirVisualProMeasurementDescription(
key="particulate_matter_0_1",
translation_key="pm01",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["pm0_1"],
),
AirVisualProMeasurementDescription(
key="particulate_matter_1_0",
name="PM 0.1",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["pm1_0"],
value_fn=lambda settings, status, measurements: measurements["pm0_1"],
),
AirVisualProMeasurementDescription(
key="particulate_matter_1_0",
name="PM 1.0",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements: measurements["pm1_0"],
),
AirVisualProMeasurementDescription(
key="particulate_matter_2_5",
name="PM 2.5",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["pm2_5"],
value_fn=lambda settings, status, measurements: measurements["pm2_5"],
),
AirVisualProMeasurementDescription(
key="temperature",
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements[
"temperature_C"
],
value_fn=lambda settings, status, measurements: measurements["temperature_C"],
),
AirVisualProMeasurementDescription(
key="voc",
name="VOC",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["voc"],
value_fn=lambda settings, status, measurements: measurements["voc"],
),
)
@@ -158,5 +150,4 @@ class AirVisualProSensor(AirVisualProEntity, SensorEntity):
self.coordinator.data["settings"],
self.coordinator.data["status"],
self.coordinator.data["measurements"],
self.coordinator.data["history"],
)

View File

@@ -24,15 +24,5 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"entity": {
"sensor": {
"pm01": {
"name": "PM0.1"
},
"outdoor_air_quality_index": {
"name": "Outdoor air quality index"
}
}
}
}

View File

@@ -193,8 +193,6 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode."""
slave_raise = False
params = {}
if hvac_mode == HVACMode.OFF:
params[API_ON] = 0
@@ -204,13 +202,12 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
if self.get_airzone_value(AZD_MASTER):
params[API_MODE] = mode
else:
slave_raise = True
raise HomeAssistantError(
f"Mode can't be changed on slave zone {self.name}"
)
params[API_ON] = 1
await self._async_update_hvac_params(params)
if slave_raise:
raise HomeAssistantError(f"Mode can't be changed on slave zone {self.name}")
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
params = {}

View File

@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==0.6.5"]
"requirements": ["aioairzone==0.6.4"]
}

View File

@@ -4,14 +4,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Final
from aioairzone_cloud.const import (
AZD_ACTIVE,
AZD_AIDOOS,
AZD_ERRORS,
AZD_PROBLEMS,
AZD_WARNINGS,
AZD_ZONES,
)
from aioairzone_cloud.const import AZD_PROBLEMS, AZD_WARNINGS, AZD_ZONES
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@@ -25,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator
from .entity import AirzoneAidooEntity, AirzoneEntity, AirzoneZoneEntity
from .entity import AirzoneEntity, AirzoneZoneEntity
@dataclass
@@ -35,27 +28,7 @@ class AirzoneBinarySensorEntityDescription(BinarySensorEntityDescription):
attributes: dict[str, str] | None = None
AIDOO_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = (
AirzoneBinarySensorEntityDescription(
device_class=BinarySensorDeviceClass.RUNNING,
key=AZD_ACTIVE,
),
AirzoneBinarySensorEntityDescription(
attributes={
"errors": AZD_ERRORS,
"warnings": AZD_WARNINGS,
},
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
key=AZD_PROBLEMS,
),
)
ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = (
AirzoneBinarySensorEntityDescription(
device_class=BinarySensorDeviceClass.RUNNING,
key=AZD_ACTIVE,
),
AirzoneBinarySensorEntityDescription(
attributes={
"warnings": AZD_WARNINGS,
@@ -75,18 +48,6 @@ async def async_setup_entry(
binary_sensors: list[AirzoneBinarySensor] = []
for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items():
for description in AIDOO_BINARY_SENSOR_TYPES:
if description.key in aidoo_data:
binary_sensors.append(
AirzoneAidooBinarySensor(
coordinator,
description,
aidoo_id,
aidoo_data,
)
)
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items():
for description in ZONE_BINARY_SENSOR_TYPES:
if description.key in zone_data:
@@ -124,27 +85,6 @@ class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity):
}
class AirzoneAidooBinarySensor(AirzoneAidooEntity, AirzoneBinarySensor):
"""Define an Airzone Cloud Aidoo binary sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
description: AirzoneBinarySensorEntityDescription,
aidoo_id: str,
aidoo_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator, aidoo_id, aidoo_data)
self._attr_unique_id = f"{aidoo_id}_{description.key}"
self.entity_description = description
self._async_update_attrs()
class AirzoneZoneBinarySensor(AirzoneZoneEntity, AirzoneBinarySensor):
"""Define an Airzone Cloud Zone binary sensor."""

View File

@@ -7,7 +7,6 @@ from typing import Any
from aioairzone_cloud.const import (
API_CITY,
API_GROUP_ID,
API_GROUPS,
API_LOCATION_ID,
API_OLD_ID,
API_PIN,
@@ -30,6 +29,7 @@ from .coordinator import AirzoneUpdateCoordinator
TO_REDACT_API = [
API_CITY,
API_GROUP_ID,
API_LOCATION_ID,
API_OLD_ID,
API_PIN,
@@ -58,17 +58,11 @@ def gather_ids(api_data: dict[str, Any]) -> dict[str, Any]:
ids[dev_id] = f"device{dev_idx}"
dev_idx += 1
group_idx = 1
inst_idx = 1
for inst_id, inst_data in api_data[RAW_INSTALLATIONS].items():
for inst_id in api_data[RAW_INSTALLATIONS]:
if inst_id not in ids:
ids[inst_id] = f"installation{inst_idx}"
inst_idx += 1
for group in inst_data[API_GROUPS]:
group_id = group[API_GROUP_ID]
if group_id not in ids:
ids[group_id] = f"group{group_idx}"
group_idx += 1
ws_idx = 1
for ws_id in api_data[RAW_WEBSERVERS]:

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_polling",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.2.1"]
"requirements": ["aioairzone-cloud==0.1.9"]
}

View File

@@ -141,8 +141,6 @@ class AirzoneSensor(AirzoneEntity, SensorEntity):
class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
"""Define an Airzone Cloud Aidoo sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
@@ -153,6 +151,7 @@ class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
"""Initialize."""
super().__init__(coordinator, aidoo_id, aidoo_data)
self._attr_has_entity_name = True
self._attr_unique_id = f"{aidoo_id}_{description.key}"
self.entity_description = description
@@ -162,8 +161,6 @@ class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
"""Define an Airzone Cloud WebServer sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
@@ -174,6 +171,7 @@ class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
"""Initialize."""
super().__init__(coordinator, ws_id, ws_data)
self._attr_has_entity_name = True
self._attr_unique_id = f"{ws_id}_{description.key}"
self.entity_description = description
@@ -183,8 +181,6 @@ class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
"""Define an Airzone Cloud Zone sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
@@ -195,6 +191,7 @@ class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
"""Initialize."""
super().__init__(coordinator, zone_id, zone_data)
self._attr_has_entity_name = True
self._attr_unique_id = f"{zone_id}_{description.key}"
self.entity_description = description

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
"iot_class": "cloud_polling",
"loggers": ["aladdin_connect"],
"requirements": ["AIOAladdinConnect==0.1.57"]
"requirements": ["AIOAladdinConnect==0.1.56"]
}

View File

@@ -1,7 +1,9 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from enum import IntFlag
from typing import Final
from homeassistant.backports.enum import StrEnum
DOMAIN: Final = "alarm_control_panel"
ATTR_CHANGED_BY: Final = "changed_by"

View File

@@ -1,83 +1,99 @@
# Describes the format for available alarm control panel services
alarm_disarm:
name: Disarm
description: Send the alarm the command for disarm.
target:
entity:
domain: alarm_control_panel
fields:
code:
name: Code
description: An optional code to disarm the alarm control panel with.
example: "1234"
selector:
text:
alarm_arm_custom_bypass:
name: Arm with custom bypass
description: Send arm custom bypass command.
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
fields:
code:
name: Code
description: An optional code to arm custom bypass the alarm control panel with.
example: "1234"
selector:
text:
alarm_arm_home:
name: Arm home
description: Send the alarm the command for arm home.
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_HOME
fields:
code:
name: Code
description: An optional code to arm home the alarm control panel with.
example: "1234"
selector:
text:
alarm_arm_away:
name: Arm away
description: Send the alarm the command for arm away.
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_AWAY
fields:
code:
name: Code
description: An optional code to arm away the alarm control panel with.
example: "1234"
selector:
text:
alarm_arm_night:
name: Arm night
description: Send the alarm the command for arm night.
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_NIGHT
fields:
code:
name: Code
description: An optional code to arm night the alarm control panel with.
example: "1234"
selector:
text:
alarm_arm_vacation:
name: Arm vacation
description: Send the alarm the command for arm vacation.
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_VACATION
fields:
code:
name: Code
description: An optional code to arm vacation the alarm control panel with.
example: "1234"
selector:
text:
alarm_trigger:
name: Trigger
description: Send the alarm the command for trigger.
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.TRIGGER
fields:
code:
name: Code
description: An optional code to trigger the alarm control panel with.
example: "1234"
selector:
text:

View File

@@ -63,76 +63,10 @@
}
}
},
"services": {
"alarm_disarm": {
"name": "Disarm",
"description": "Disarms the alarm.",
"fields": {
"code": {
"name": "Code",
"description": "Code to disarm the alarm."
}
}
},
"alarm_arm_custom_bypass": {
"name": "Arm with custom bypass",
"description": "Arms the alarm while allowing to bypass a custom area.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
"description": "Code to arm the alarm."
}
}
},
"alarm_arm_home": {
"name": "Arm home",
"description": "Sets the alarm to: _armed, but someone is home_.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
}
}
},
"alarm_arm_away": {
"name": "Arm away",
"description": "Sets the alarm to: _armed, no one home_.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
}
}
},
"alarm_arm_night": {
"name": "Arm night",
"description": "Sets the alarm to: _armed for the night_.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
}
}
},
"alarm_arm_vacation": {
"name": "Arm vacation",
"description": "Sets the alarm to: _armed for vacation_.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
}
}
},
"alarm_trigger": {
"name": "Trigger",
"description": "Enables an external alarm trigger.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
}
}
"issues": {
"platform_integration_no_support": {
"title": "[%key:common::issues::platform_integration_no_support_title%]",
"description": "[%key:common::issues::platform_integration_no_support_description%]"
}
}
}

View File

@@ -1,22 +1,30 @@
alarm_keypress:
name: Key press
description: Send custom keypresses to the alarm.
target:
entity:
integration: alarmdecoder
domain: alarm_control_panel
fields:
keypress:
name: Key press
description: "String to send to the alarm panel."
required: true
example: "*71"
selector:
text:
alarm_toggle_chime:
name: Toggle Chime
description: Send the alarm the toggle chime command.
target:
entity:
integration: alarmdecoder
domain: alarm_control_panel
fields:
code:
name: Code
description: A code to toggle the alarm control panel chime with.
required: true
example: 1234
selector:

View File

@@ -20,9 +20,7 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"create_entry": {
"default": "Successfully connected to AlarmDecoder."
},
"create_entry": { "default": "Successfully connected to AlarmDecoder." },
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
@@ -37,7 +35,7 @@
}
},
"arm_settings": {
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
"title": "Configure AlarmDecoder",
"data": {
"auto_bypass": "Auto Bypass on Arm",
"code_arm_required": "Code Required for Arming",
@@ -45,14 +43,14 @@
}
},
"zone_select": {
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
"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": "[%key:component::alarmdecoder::options::step::init::title%]",
"title": "Configure AlarmDecoder",
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave Zone Name blank.",
"data": {
"zone_name": "Zone Name",
@@ -70,27 +68,5 @@
"loop_rfid": "RF Loop cannot be used without RF Serial.",
"loop_range": "RF Loop must be an integer between 1 and 4."
}
},
"services": {
"alarm_keypress": {
"name": "Key press",
"description": "Sends custom keypresses to the alarm.",
"fields": {
"keypress": {
"name": "[%key:component::alarmdecoder::services::alarm_keypress::name%]",
"description": "String to send to the alarm panel."
}
}
},
"alarm_toggle_chime": {
"name": "Toggle chime",
"description": "Sends the alarm the toggle chime command.",
"fields": {
"code": {
"name": "Code",
"description": "Code to toggle the alarm control panel chime with."
}
}
}
}
}

View File

@@ -25,18 +25,16 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
)
from homeassistant.core import HassJob, HomeAssistant
from homeassistant.exceptions import ServiceNotFound
from homeassistant.core import Event, HassJob, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import (
EventStateChangedData,
async_track_point_in_time,
async_track_state_change_event,
)
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, EventType
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.dt import now
from .const import (
@@ -198,13 +196,11 @@ class Alert(Entity):
return STATE_ON
return STATE_IDLE
async def watched_entity_change(
self, event: EventType[EventStateChangedData]
) -> None:
async def watched_entity_change(self, event: Event) -> None:
"""Determine if the alert should start or stop."""
if (to_state := event.data["new_state"]) is None:
if (to_state := event.data.get("new_state")) is None:
return
LOGGER.debug("Watched entity (%s) has changed", event.data["entity_id"])
LOGGER.debug("Watched entity (%s) has changed", event.data.get("entity_id"))
if to_state.state == self._alert_state and not self._firing:
await self.begin_alerting()
if to_state.state != self._alert_state and self._firing:
@@ -294,15 +290,9 @@ class Alert(Entity):
LOGGER.debug(msg_payload)
for target in self._notifiers:
try:
await self.hass.services.async_call(
DOMAIN_NOTIFY, target, msg_payload, context=self._context
)
except ServiceNotFound:
LOGGER.error(
"Failed to call notify.%s, retrying at next notification interval",
target,
)
await self.hass.services.async_call(
DOMAIN_NOTIFY, target, msg_payload, context=self._context
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Async Unacknowledge alert."""

View File

@@ -1,14 +1,20 @@
toggle:
name: Toggle
description: Toggle alert's notifications.
target:
entity:
domain: alert
turn_off:
name: Turn off
description: Silence alert's notifications.
target:
entity:
domain: alert
turn_on:
name: Turn on
description: Reset alert's notifications.
target:
entity:
domain: alert

View File

@@ -9,19 +9,5 @@
"on": "[%key:common::state::active%]"
}
}
},
"services": {
"toggle": {
"name": "[%key:common::action::toggle%]",
"description": "Toggles alert's notifications."
},
"turn_off": {
"name": "[%key:common::action::turn_off%]",
"description": "Silences alert's notifications."
},
"turn_on": {
"name": "[%key:common::action::turn_on%]",
"description": "Resets alert's notifications."
}
}
}

View File

@@ -14,7 +14,6 @@ from homeassistant.components import (
camera,
climate,
cover,
event,
fan,
group,
humidifier,
@@ -528,26 +527,6 @@ class CoverCapabilities(AlexaEntity):
yield Alexa(self.entity)
@ENTITY_ADAPTERS.register(event.DOMAIN)
class EventCapabilities(AlexaEntity):
"""Class to represent doorbel event capabilities."""
def default_display_categories(self) -> list[str] | None:
"""Return the display categories for this entity."""
attrs = self.entity.attributes
device_class: event.EventDeviceClass | None = attrs.get(ATTR_DEVICE_CLASS)
if device_class == event.EventDeviceClass.DOORBELL:
return [DisplayCategory.DOORBELL]
return None
def interfaces(self) -> Generator[AlexaCapability, None, None]:
"""Yield the supported interfaces."""
if self.default_display_categories() is not None:
yield AlexaDoorbellEventSource(self.entity)
yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.entity)
@ENTITY_ADAPTERS.register(light.DOMAIN)
class LightCapabilities(AlexaEntity):
"""Class to represent Light capabilities."""

View File

@@ -857,55 +857,14 @@ async def async_api_adjust_target_temp(
temp_delta = temperature_from_object(
hass, directive.payload["targetSetpointDelta"], interval=True
)
target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta
if target_temp < min_temp or target_temp > max_temp:
raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp)
data = {ATTR_ENTITY_ID: entity.entity_id, ATTR_TEMPERATURE: target_temp}
response = directive.response()
current_target_temp_high = entity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH)
current_target_temp_low = entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW)
if current_target_temp_high and current_target_temp_low:
target_temp_high = float(current_target_temp_high) + temp_delta
if target_temp_high < min_temp or target_temp_high > max_temp:
raise AlexaTempRangeError(hass, target_temp_high, min_temp, max_temp)
target_temp_low = float(current_target_temp_low) + temp_delta
if target_temp_low < min_temp or target_temp_low > max_temp:
raise AlexaTempRangeError(hass, target_temp_low, min_temp, max_temp)
data = {
ATTR_ENTITY_ID: entity.entity_id,
climate.ATTR_TARGET_TEMP_HIGH: target_temp_high,
climate.ATTR_TARGET_TEMP_LOW: target_temp_low,
}
response.add_context_property(
{
"name": "upperSetpoint",
"namespace": "Alexa.ThermostatController",
"value": {"value": target_temp_high, "scale": API_TEMP_UNITS[unit]},
}
)
response.add_context_property(
{
"name": "lowerSetpoint",
"namespace": "Alexa.ThermostatController",
"value": {"value": target_temp_low, "scale": API_TEMP_UNITS[unit]},
}
)
else:
target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta
if target_temp < min_temp or target_temp > max_temp:
raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp)
data = {ATTR_ENTITY_ID: entity.entity_id, ATTR_TEMPERATURE: target_temp}
response.add_context_property(
{
"name": "targetSetpoint",
"namespace": "Alexa.ThermostatController",
"value": {"value": target_temp, "scale": API_TEMP_UNITS[unit]},
}
)
await hass.services.async_call(
entity.domain,
climate.SERVICE_SET_TEMPERATURE,
@@ -913,6 +872,13 @@ async def async_api_adjust_target_temp(
blocking=False,
context=context,
)
response.add_context_property(
{
"name": "targetSetpoint",
"namespace": "Alexa.ThermostatController",
"value": {"value": target_temp, "scale": API_TEMP_UNITS[unit]},
}
)
return response

View File

@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, cast
import aiohttp
import async_timeout
from homeassistant.components import event
from homeassistant.const import MATCH_ALL, STATE_ON
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -92,10 +91,8 @@ async def async_enable_proactive_mode(hass, smart_home_config):
return
if should_doorbell:
if (
new_state.domain == event.DOMAIN
or new_state.state == STATE_ON
and (old_state is None or old_state.state != STATE_ON)
if new_state.state == STATE_ON and (
old_state is None or old_state.state != STATE_ON
):
await async_send_doorbell_event_message(
hass, smart_home_config, alexa_changed_entity

View File

@@ -4,10 +4,9 @@ from amberelectric import Configuration
from amberelectric.api import amber_api
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_TOKEN
from homeassistant.core import HomeAssistant
from .const import CONF_SITE_ID, DOMAIN, PLATFORMS
from .const import CONF_API_TOKEN, CONF_SITE_ID, DOMAIN, PLATFORMS
from .coordinator import AmberUpdateCoordinator

View File

@@ -3,7 +3,7 @@
"step": {
"user": {
"data": {
"api_token": "[%key:common::config_flow::data::api_token%]",
"api_token": "API Token",
"site_id": "Site ID"
},
"description": "Go to {api_url} to generate an API key"

View File

@@ -154,18 +154,17 @@ class AmbiclimateEntity(ClimateEntity):
_attr_target_temperature_step = 1
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
_attr_has_entity_name = True
_attr_name = None
def __init__(self, heater, store):
"""Initialize the thermostat."""
self._heater = heater
self._store = store
self._attr_unique_id = heater.device_id
self._attr_name = heater.name
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Ambiclimate",
name=heater.name,
name=self.name,
)
async def async_set_temperature(self, **kwargs: Any) -> None:

View File

@@ -1,34 +1,53 @@
# Describes the format for available services for ambiclimate
set_comfort_mode:
name: Set comfort mode
description: >
Enable comfort mode on your AC.
fields:
name:
description: >
String with device name.
required: true
example: Bedroom
selector:
text:
send_comfort_feedback:
name: Send comfort feedback
description: >
Send feedback for comfort mode.
fields:
name:
description: >
String with device name.
required: true
example: Bedroom
selector:
text:
value:
description: >
Send any of the following comfort values: too_hot, too_warm, bit_warm, comfortable, bit_cold, too_cold, freezing
required: true
example: bit_warm
selector:
text:
set_temperature_mode:
name: Set temperature mode
description: >
Enable temperature mode on your AC.
fields:
name:
description: >
String with device name.
required: true
example: Bedroom
selector:
text:
value:
description: >
Target value in celsius
required: true
example: 22
selector:

View File

@@ -18,45 +18,5 @@
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"access_token": "Unknown error generating an access token."
}
},
"services": {
"set_comfort_mode": {
"name": "Set comfort mode",
"description": "Enables comfort mode on your AC.",
"fields": {
"name": {
"name": "Device name",
"description": "String with device name."
}
}
},
"send_comfort_feedback": {
"name": "Send comfort feedback",
"description": "Sends feedback for comfort mode.",
"fields": {
"name": {
"name": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::name%]",
"description": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::description%]"
},
"value": {
"name": "Comfort value",
"description": "Send any of the following comfort values: too_hot, too_warm, bit_warm, comfortable, bit_cold, too_cold, freezing\n."
}
}
},
"set_temperature_mode": {
"name": "Set temperature mode",
"description": "Enables temperature mode on your AC.",
"fields": {
"name": {
"name": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::name%]",
"description": "[%key:component::ambiclimate::services::set_comfort_mode::fields::name::description%]"
},
"value": {
"name": "Temperature",
"description": "Target value in celsius."
}
}
}
}
}

View File

@@ -210,10 +210,9 @@ class AmcrestChecker(ApiWrapper):
self, *args: Any, **kwargs: Any
) -> AsyncIterator[httpx.Response]:
"""amcrest.ApiWrapper.command wrapper to catch errors."""
async with self._async_command_wrapper(), super().async_stream_command(
*args, **kwargs
) as ret:
yield ret
async with self._async_command_wrapper():
async with super().async_stream_command(*args, **kwargs) as ret:
yield ret
@asynccontextmanager
async def _async_command_wrapper(self) -> AsyncIterator[None]:

View File

@@ -1,53 +1,82 @@
enable_recording:
name: Enable recording
description: Enable continuous recording to camera storage.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
disable_recording:
name: Disable recording
description: Disable continuous recording to camera storage.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
enable_audio:
name: Enable audio
description: Enable audio stream.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
disable_audio:
name: Disable audio
description: Disable audio stream.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
enable_motion_recording:
name: Enable motion recording
description: Enable recording a clip to camera storage when motion is detected.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
disable_motion_recording:
name: Disable motion recording
description: Disable recording a clip to camera storage when motion is detected.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
goto_preset:
name: Go to preset
description: Move camera to PTZ preset.
fields:
entity_id:
description: "Name(s) of the cameras, or 'all' for all cameras."
selector:
entity:
integration: amcrest
domain: camera
preset:
name: Preset
description: Preset number.
required: true
selector:
number:
@@ -55,12 +84,18 @@ goto_preset:
max: 1000
set_color_bw:
name: Set color
description: Set camera color mode.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
color_bw:
name: Color
description: Color mode.
selector:
select:
options:
@@ -69,26 +104,40 @@ set_color_bw:
- "color"
start_tour:
name: Start tour
description: Start camera's PTZ tour function.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
stop_tour:
name: Stop tour
description: Stop camera's PTZ tour function.
fields:
entity_id:
name: Entity
description: "Name(s) of the cameras, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
ptz_control:
name: PTZ control
description: Move (Pan/Tilt) and/or Zoom a PTZ camera.
fields:
entity_id:
name: Entity
description: "Name of the camera, or 'all' for all cameras."
example: "camera.house_front"
selector:
text:
movement:
name: Movement
description: "Direction to move the camera."
required: true
selector:
select:
@@ -104,6 +153,8 @@ ptz_control:
- "zoom_in"
- "zoom_out"
travel_time:
name: Travel time
description: "Travel time in fractional seconds: from 0 to 1."
default: .2
selector:
number:

View File

@@ -1,130 +0,0 @@
{
"services": {
"enable_recording": {
"name": "Enable recording",
"description": "Enables continuous recording to camera storage.",
"fields": {
"entity_id": {
"name": "Entity",
"description": "Name(s) of the cameras, or 'all' for all cameras."
}
}
},
"disable_recording": {
"name": "Disable recording",
"description": "Disables continuous recording to camera storage.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"enable_audio": {
"name": "Enable audio",
"description": "Enables audio stream.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"disable_audio": {
"name": "Disable audio",
"description": "Disables audio stream.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"enable_motion_recording": {
"name": "Enables motion recording",
"description": "Enables recording a clip to camera storage when motion is detected.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"disable_motion_recording": {
"name": "Disables motion recording",
"description": "Disable recording a clip to camera storage when motion is detected.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"goto_preset": {
"name": "Go to preset",
"description": "Moves camera to PTZ preset.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
},
"preset": {
"name": "Preset",
"description": "Preset number."
}
}
},
"set_color_bw": {
"name": "Set color",
"description": "Sets camera color mode.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
},
"color_bw": {
"name": "Color",
"description": "Color mode."
}
}
},
"start_tour": {
"name": "Start tour",
"description": "Starts camera's PTZ tour function.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"stop_tour": {
"name": "Stop tour",
"description": "Stops camera's PTZ tour function.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
}
}
},
"ptz_control": {
"name": "PTZ control",
"description": "Moves (pan/tilt) and/or zoom a PTZ camera.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]"
},
"movement": {
"name": "Movement",
"description": "Direction to move the camera."
},
"travel_time": {
"name": "Travel time",
"description": "Travel time in fractional seconds: from 0 to 1."
}
}
}
}
}

View File

@@ -21,22 +21,12 @@ from homeassistant.components.recorder import (
DOMAIN as RECORDER_DOMAIN,
get_instance as get_recorder_instance,
)
import homeassistant.config as conf_util
from homeassistant.config_entries import (
SOURCE_IGNORE,
)
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.entity_registry as er
from homeassistant.helpers.storage import Store
from homeassistant.helpers.system_info import async_get_system_info
from homeassistant.loader import (
Integration,
IntegrationNotFound,
async_get_integrations,
)
from homeassistant.loader import IntegrationNotFound, async_get_integrations
from homeassistant.setup import async_get_loaded_integrations
from .const import (
@@ -216,25 +206,8 @@ class Analytics:
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
ATTR_STATISTICS, False
):
ent_reg = er.async_get(self.hass)
try:
yaml_configuration = await conf_util.async_hass_config_yaml(self.hass)
except HomeAssistantError as err:
LOGGER.error(err)
return
configuration_set = set(yaml_configuration)
er_platforms = {
entity.platform
for entity in ent_reg.entities.values()
if not entity.disabled
}
domains = async_get_loaded_integrations(self.hass)
configured_integrations = await async_get_integrations(self.hass, domains)
enabled_domains = set(configured_integrations)
for integration in configured_integrations.values():
if isinstance(integration, IntegrationNotFound):
continue
@@ -242,11 +215,7 @@ class Analytics:
if isinstance(integration, BaseException):
raise integration
if not self._async_should_report_integration(
integration=integration,
yaml_domains=configuration_set,
entity_registry_platforms=er_platforms,
):
if integration.disabled:
continue
if not integration.is_built_in:
@@ -284,12 +253,12 @@ class Analytics:
if supervisor_info is not None:
payload[ATTR_ADDONS] = addons
if ENERGY_DOMAIN in enabled_domains:
if ENERGY_DOMAIN in integrations:
payload[ATTR_ENERGY] = {
ATTR_CONFIGURED: await energy_is_configured(self.hass)
}
if RECORDER_DOMAIN in enabled_domains:
if RECORDER_DOMAIN in integrations:
instance = get_recorder_instance(self.hass)
engine = instance.database_engine
if engine and engine.version is not None:
@@ -337,34 +306,3 @@ class Analytics:
LOGGER.error(
"Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
)
@callback
def _async_should_report_integration(
self,
integration: Integration,
yaml_domains: set[str],
entity_registry_platforms: set[str],
) -> bool:
"""Return a bool to indicate if this integration should be reported."""
if integration.disabled:
return False
# Check if the integration is defined in YAML or in the entity registry
if (
integration.domain in yaml_domains
or integration.domain in entity_registry_platforms
):
return True
# Check if the integration provide a config flow
if not integration.config_flow:
return False
entries = self.hass.config_entries.async_entries(integration.domain)
# Filter out ignored and disabled entries
return any(
entry
for entry in entries
if entry.source != SOURCE_IGNORE and entry.disabled_by is None
)

View File

@@ -17,5 +17,11 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"issues": {
"deprecated_yaml": {
"title": "The Android IP Webcam YAML configuration is being removed",
"description": "Configuring Android IP Webcam using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Android IP Webcam YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
}
}
}

View File

@@ -2,15 +2,13 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable, Coroutine
from datetime import timedelta
from datetime import datetime
import functools
import hashlib
import logging
from typing import Any, Concatenate, ParamSpec, TypeVar
from androidtv.constants import APPS, KEYS
from androidtv.exceptions import LockNotAcquiredException
from androidtv.setup_async import AndroidTVAsync, FireTVAsync
import voluptuous as vol
from homeassistant.components import persistent_notification
@@ -36,7 +34,6 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle
from . import ADB_PYTHON_EXCEPTIONS, ADB_TCP_EXCEPTIONS, get_androidtv_mac
from .const import (
@@ -67,8 +64,6 @@ ATTR_DEVICE_PATH = "device_path"
ATTR_HDMI_INPUT = "hdmi_input"
ATTR_LOCAL_PATH = "local_path"
MIN_TIME_BETWEEN_SCREENCAPS = timedelta(seconds=60)
SERVICE_ADB_COMMAND = "adb_command"
SERVICE_DOWNLOAD = "download"
SERVICE_LEARN_SENDEVENT = "learn_sendevent"
@@ -93,15 +88,13 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Android Debug Bridge entity."""
aftv: AndroidTVAsync | FireTVAsync = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV]
aftv = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV]
device_class = aftv.DEVICE_CLASS
device_type = (
PREFIX_ANDROIDTV if device_class == DEVICE_ANDROIDTV else PREFIX_FIRETV
)
# CONF_NAME may be present in entry.data for configuration imported from YAML
device_name: str = entry.data.get(
CONF_NAME, f"{device_type} {entry.data[CONF_HOST]}"
)
device_name = entry.data.get(CONF_NAME) or f"{device_type} {entry.data[CONF_HOST]}"
device_args = [
aftv,
@@ -178,11 +171,8 @@ def adb_decorator(
except LockNotAcquiredException:
# If the ADB lock could not be acquired, skip this command
_LOGGER.info(
(
"ADB command %s not executed because the connection is"
" currently in use"
),
func.__name__,
"ADB command not executed because the connection is currently"
" in use"
)
return None
except self.exceptions as err:
@@ -214,27 +204,23 @@ class ADBDevice(MediaPlayerEntity):
"""Representation of an Android or Fire TV device."""
_attr_device_class = MediaPlayerDeviceClass.TV
_attr_has_entity_name = True
_attr_name = None
def __init__(
self,
aftv: AndroidTVAsync | FireTVAsync,
name: str,
dev_type: str,
unique_id: str,
entry_id: str,
entry_data: dict[str, Any],
) -> None:
aftv,
name,
dev_type,
unique_id,
entry_id,
entry_data,
):
"""Initialize the Android / Fire TV device."""
self.aftv = aftv
self._attr_name = name
self._attr_unique_id = unique_id
self._entry_id = entry_id
self._entry_data = entry_data
self._media_image: tuple[bytes | None, str | None] = None, None
self._attr_media_image_hash = None
info = aftv.device_properties
model = info.get(ATTR_MODEL)
self._attr_device_info = DeviceInfo(
@@ -249,13 +235,13 @@ class ADBDevice(MediaPlayerEntity):
if mac := get_androidtv_mac(info):
self._attr_device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, mac)}
self._app_id_to_name: dict[str, str] = {}
self._app_name_to_id: dict[str, str] = {}
self._app_id_to_name = {}
self._app_name_to_id = {}
self._get_sources = DEFAULT_GET_SOURCES
self._exclude_unnamed_apps = DEFAULT_EXCLUDE_UNNAMED_APPS
self._screencap = DEFAULT_SCREENCAP
self.turn_on_command: str | None = None
self.turn_off_command: str | None = None
self.turn_on_command = None
self.turn_off_command = None
# ADB exceptions to catch
if not aftv.adb_server_ip:
@@ -274,7 +260,7 @@ class ADBDevice(MediaPlayerEntity):
# The number of consecutive failed connect attempts
self._failed_connect_count = 0
def _process_config(self) -> None:
def _process_config(self):
"""Load the config options."""
_LOGGER.debug("Loading configuration options")
options = self._entry_data[ANDROID_DEV_OPT]
@@ -310,40 +296,36 @@ class ADBDevice(MediaPlayerEntity):
self._process_config,
)
)
return
@property
def media_image_hash(self) -> str | None:
"""Hash value for media image."""
return f"{datetime.now().timestamp()}" if self._screencap else None
@adb_decorator()
async def _adb_screencap(self) -> bytes | None:
async def _adb_screencap(self):
"""Take a screen capture from the device."""
return await self.aftv.adb_screencap()
async def _async_get_screencap(self, prev_app_id: str | None = None) -> None:
"""Take a screen capture from the device when enabled."""
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
"""Fetch current playing image."""
if (
not self._screencap
or self.state in {MediaPlayerState.OFF, None}
or not self.available
):
self._media_image = None, None
self._attr_media_image_hash = None
else:
force: bool = prev_app_id is not None
if force:
force = prev_app_id != self._attr_app_id
await self._adb_get_screencap(no_throttle=force)
return None, None
@Throttle(MIN_TIME_BETWEEN_SCREENCAPS)
async def _adb_get_screencap(self, **kwargs) -> None:
"""Take a screen capture from the device every 60 seconds."""
if media_data := await self._adb_screencap():
self._media_image = media_data, "image/png"
self._attr_media_image_hash = hashlib.sha256(media_data).hexdigest()[:16]
else:
self._media_image = None, None
self._attr_media_image_hash = None
media_data = await self._adb_screencap()
if media_data:
return media_data, "image/png"
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
"""Fetch current playing image."""
return self._media_image
# 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
@adb_decorator()
async def async_media_play(self) -> None:
@@ -401,7 +383,7 @@ class ADBDevice(MediaPlayerEntity):
await self.aftv.stop_app(self._app_name_to_id.get(source_, source_))
@adb_decorator()
async def adb_command(self, command: str) -> None:
async def adb_command(self, command):
"""Send an ADB command to an Android / Fire TV device."""
if key := KEYS.get(command):
await self.aftv.adb_shell(f"input keyevent {key}")
@@ -426,7 +408,7 @@ class ADBDevice(MediaPlayerEntity):
return
@adb_decorator()
async def learn_sendevent(self) -> None:
async def learn_sendevent(self):
"""Translate a key press on a remote to ADB 'sendevent' commands."""
output = await self.aftv.learn_sendevent()
if output:
@@ -445,7 +427,7 @@ class ADBDevice(MediaPlayerEntity):
_LOGGER.info("%s", msg)
@adb_decorator()
async def service_download(self, device_path: str, local_path: str) -> None:
async def service_download(self, device_path, local_path):
"""Download a file from your Android / Fire TV device to your Home Assistant instance."""
if not self.hass.config.is_allowed_path(local_path):
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
@@ -454,7 +436,7 @@ class ADBDevice(MediaPlayerEntity):
await self.aftv.adb_pull(local_path, device_path)
@adb_decorator()
async def service_upload(self, device_path: str, local_path: str) -> None:
async def service_upload(self, device_path, local_path):
"""Upload a file from your Home Assistant instance to an Android / Fire TV device."""
if not self.hass.config.is_allowed_path(local_path):
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
@@ -479,7 +461,6 @@ class AndroidTVDevice(ADBDevice):
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
)
aftv: AndroidTVAsync
@adb_decorator(override_available=True)
async def async_update(self) -> None:
@@ -497,7 +478,6 @@ class AndroidTVDevice(ADBDevice):
if not self.available:
return
prev_app_id = self._attr_app_id
# Get the updated state and attributes.
(
state,
@@ -513,7 +493,7 @@ class AndroidTVDevice(ADBDevice):
if self._attr_state is None:
self._attr_available = False
if running_apps and self._attr_app_id:
if running_apps:
self._attr_source = self._attr_app_name = self._app_id_to_name.get(
self._attr_app_id, self._attr_app_id
)
@@ -527,8 +507,6 @@ class AndroidTVDevice(ADBDevice):
else:
self._attr_source_list = None
await self._async_get_screencap(prev_app_id)
@adb_decorator()
async def async_media_stop(self) -> None:
"""Send stop command."""
@@ -572,7 +550,6 @@ class FireTVDevice(ADBDevice):
| MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.STOP
)
aftv: FireTVAsync
@adb_decorator(override_available=True)
async def async_update(self) -> None:
@@ -590,7 +567,6 @@ class FireTVDevice(ADBDevice):
if not self.available:
return
prev_app_id = self._attr_app_id
# Get the `state`, `current_app`, `running_apps` and `hdmi_input`.
(
state,
@@ -603,7 +579,7 @@ class FireTVDevice(ADBDevice):
if self._attr_state is None:
self._attr_available = False
if running_apps and self._attr_app_id:
if running_apps:
self._attr_source = self._app_id_to_name.get(
self._attr_app_id, self._attr_app_id
)
@@ -617,8 +593,6 @@ class FireTVDevice(ADBDevice):
else:
self._attr_source_list = None
await self._async_get_screencap(prev_app_id)
@adb_decorator()
async def async_media_stop(self) -> None:
"""Send stop (back) command."""

View File

@@ -1,49 +1,67 @@
# Describes the format for available Android and Fire TV services
adb_command:
name: ADB command
description: Send an ADB command to an Android / Fire TV device.
target:
entity:
integration: androidtv
domain: media_player
fields:
command:
name: Command
description: Either a key command or an ADB shell command.
required: true
example: "HOME"
selector:
text:
download:
name: Download
description: Download a file from your Android / Fire TV device to your Home Assistant instance.
target:
entity:
integration: androidtv
domain: media_player
fields:
device_path:
name: Device path
description: The filepath on the Android / Fire TV device.
required: true
example: "/storage/emulated/0/Download/example.txt"
selector:
text:
local_path:
name: Local path
description: The filepath on your Home Assistant instance.
required: true
example: "/config/www/example.txt"
selector:
text:
upload:
name: Upload
description: Upload a file from your Home Assistant instance to an Android / Fire TV device.
target:
entity:
integration: androidtv
domain: media_player
fields:
device_path:
name: Device path
description: The filepath on the Android / Fire TV device.
required: true
example: "/storage/emulated/0/Download/example.txt"
selector:
text:
local_path:
name: Local path
description: The filepath on your Home Assistant instance.
required: true
example: "/config/www/example.txt"
selector:
text:
learn_sendevent:
name: Learn sendevent
description: Translate a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of calling this service.
target:
entity:
integration: androidtv

View File

@@ -50,7 +50,7 @@
"title": "Configure Android state detection rules",
"description": "Configure detection rule for application id {rule_id}",
"data": {
"rule_id": "[%key:component::androidtv::options::step::apps::data::app_id%]",
"rule_id": "Application ID",
"rule_values": "List of state detection rules (see documentation)",
"rule_delete": "Check to delete this rule"
}
@@ -59,49 +59,5 @@
"error": {
"invalid_det_rules": "Invalid state detection rules"
}
},
"services": {
"adb_command": {
"name": "ADB command",
"description": "Sends an ADB command to an Android / Fire TV device.",
"fields": {
"command": {
"name": "Command",
"description": "Either a key command or an ADB shell command."
}
}
},
"download": {
"name": "Download",
"description": "Downloads a file from your Android / Fire TV device to your Home Assistant instance.",
"fields": {
"device_path": {
"name": "Device path",
"description": "The filepath on the Android / Fire TV device."
},
"local_path": {
"name": "Local path",
"description": "The filepath on your Home Assistant instance."
}
}
},
"upload": {
"name": "Upload",
"description": "Uploads a file from your Home Assistant instance to an Android / Fire TV device.",
"fields": {
"device_path": {
"name": "[%key:component::androidtv::services::download::fields::device_path::name%]",
"description": "[%key:component::androidtv::services::download::fields::device_path::description%]"
},
"local_path": {
"name": "[%key:component::androidtv::services::download::fields::local_path::name%]",
"description": "[%key:component::androidtv::services::download::fields::local_path::description%]"
}
}
},
"learn_sendevent": {
"name": "Learn sendevent",
"description": "Translates a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of calling this service."
}
}
}

View File

@@ -1,7 +1,6 @@
"""The Android TV Remote integration."""
from __future__ import annotations
import asyncio
import logging
from androidtvremote2 import (
@@ -10,7 +9,6 @@ from androidtvremote2 import (
ConnectionClosed,
InvalidAuth,
)
import async_timeout
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, Platform
@@ -18,7 +16,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN
from .helpers import create_api, get_enable_ime
from .helpers import create_api
_LOGGER = logging.getLogger(__name__)
@@ -27,7 +25,7 @@ PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.REMOTE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Android TV Remote from a config entry."""
api = create_api(hass, entry.data[CONF_HOST], get_enable_ime(entry))
api = create_api(hass, entry.data[CONF_HOST])
@callback
def is_available_updated(is_available: bool) -> None:
@@ -45,12 +43,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api.add_is_available_updated_callback(is_available_updated)
try:
async with async_timeout.timeout(5.0):
await api.async_connect()
await api.async_connect()
except InvalidAuth as exc:
# The Android TV is hard reset or the certificate and key files were deleted.
raise ConfigEntryAuthFailed from exc
except (CannotConnect, ConnectionClosed, asyncio.TimeoutError) as exc:
except (CannotConnect, ConnectionClosed) as exc:
# The Android TV is network unreachable. Raise exception and let Home Assistant retry
# later. If device gets a new IP address the zeroconf flow will update the config.
raise ConfigEntryNotReady from exc
@@ -76,7 +73,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
)
entry.async_on_unload(entry.add_update_listener(update_listener))
return True
@@ -88,8 +84,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api.disconnect()
return unload_ok
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -15,12 +15,11 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.device_registry import format_mac
from .const import CONF_ENABLE_IME, DOMAIN
from .helpers import create_api, get_enable_ime
from .const import DOMAIN
from .helpers import create_api
STEP_USER_DATA_SCHEMA = vol.Schema(
{
@@ -56,9 +55,8 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None:
self.host = user_input["host"]
assert self.host
api = create_api(self.hass, self.host, enable_ime=False)
api = create_api(self.hass, self.host)
try:
await api.async_generate_cert_if_missing()
self.name, self.mac = await api.async_get_name_and_mac()
assert self.mac
await self.async_set_unique_id(format_mac(self.mac))
@@ -77,7 +75,7 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def _async_start_pair(self) -> FlowResult:
"""Start pairing with the Android TV. Navigate to the pair flow to enter the PIN shown on screen."""
assert self.host
self.api = create_api(self.hass, self.host, enable_ime=False)
self.api = create_api(self.hass, self.host)
await self.api.async_generate_cert_if_missing()
await self.api.async_start_pairing()
return await self.async_step_pair()
@@ -188,38 +186,3 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
description_placeholders={CONF_NAME: self.name},
errors=errors,
)
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Android TV Remote options flow."""
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_ENABLE_IME,
default=get_enable_ime(self.config_entry),
): bool,
}
),
)

View File

@@ -4,6 +4,3 @@ from __future__ import annotations
from typing import Final
DOMAIN: Final = "androidtv_remote"
CONF_ENABLE_IME: Final = "enable_ime"
CONF_ENABLE_IME_DEFAULT_VALUE: Final = True

View File

@@ -16,7 +16,6 @@ from .const import DOMAIN
class AndroidTVRemoteBaseEntity(Entity):
"""Android TV Remote Base Entity."""
_attr_name = None
_attr_has_entity_name = True
_attr_should_poll = False

View File

@@ -3,14 +3,11 @@ from __future__ import annotations
from androidtvremote2 import AndroidTVRemote
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.storage import STORAGE_DIR
from .const import CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE
def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRemote:
def create_api(hass: HomeAssistant, host: str) -> AndroidTVRemote:
"""Create an AndroidTVRemote instance."""
return AndroidTVRemote(
client_name="Home Assistant",
@@ -18,10 +15,4 @@ def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRem
keyfile=hass.config.path(STORAGE_DIR, "androidtv_remote_key.pem"),
host=host,
loop=hass.loop,
enable_ime=enable_ime,
)
def get_enable_ime(entry: ConfigEntry) -> bool:
"""Get value of enable_ime option or its default value."""
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE)

View File

@@ -8,6 +8,6 @@
"iot_class": "local_push",
"loggers": ["androidtvremote2"],
"quality_scale": "platinum",
"requirements": ["androidtvremote2==0.0.13"],
"requirements": ["androidtvremote2==0.0.9"],
"zeroconf": ["_androidtvremote2._tcp.local."]
}

View File

@@ -34,14 +34,5 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"options": {
"step": {
"init": {
"data": {
"enable_ime": "Enable IME. Needed for getting the current app. Disable for devices that show 'Use keyboard on mobile device screen' instead of the on screen keyboard."
}
}
}
}
}

View File

@@ -29,7 +29,7 @@
"name": "State"
},
"mode": {
"name": "[%key:common::config_flow::data::mode%]"
"name": "Mode"
},
"target_temperature": {
"name": "Target temperature"

View File

@@ -80,7 +80,6 @@ class AnthemAVR(MediaPlayerEntity):
self._attr_name = f"zone {zone_number}"
self._attr_unique_id = f"{mac_address}_{zone_number}"
else:
self._attr_name = None
self._attr_unique_id = mac_address
self._attr_device_info = DeviceInfo(

View File

@@ -11,7 +11,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
@@ -80,6 +79,16 @@ class APCUPSdData:
return self.status[model_key]
return None
@property
def sw_version(self) -> str | None:
"""Return the software version of the APCUPSd, if available."""
return self.status.get("VERSION")
@property
def hw_version(self) -> str | None:
"""Return the firmware version of the UPS, if available."""
return self.status.get("FIRMWARE")
@property
def serial_no(self) -> str | None:
"""Return the unique serial number of the UPS, if available."""
@@ -90,21 +99,6 @@ class APCUPSdData:
"""Return the STATFLAG indicating the status of the UPS, if available."""
return self.status.get("STATFLAG")
@property
def device_info(self) -> DeviceInfo | None:
"""Return the DeviceInfo of this APC UPS for the sensors, if serial number is available."""
if self.serial_no is None:
return None
return DeviceInfo(
identifiers={(DOMAIN, self.serial_no)},
model=self.model,
manufacturer="APC",
name=self.name if self.name is not None else "APC UPS",
hw_version=self.status.get("FIRMWARE"),
sw_version=self.status.get("VERSION"),
)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs: Any) -> None:
"""Fetch the latest status from APCUPSd.

View File

@@ -9,6 +9,7 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN, VALUE_ONLINE, APCUPSdData
@@ -52,8 +53,13 @@ class OnlineStatus(BinarySensorEntity):
# Set up unique id and device info if serial number is available.
if (serial_no := data_service.serial_no) is not None:
self._attr_unique_id = f"{serial_no}_{description.key}"
self._attr_device_info = data_service.device_info
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, serial_no)},
model=data_service.model,
manufacturer="APC",
hw_version=data_service.hw_version,
sw_version=data_service.sw_version,
)
self.entity_description = description
self._data_service = data_service

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