Compare commits

..

117 Commits

Author SHA1 Message Date
Franck Nijhof 7ac944c537 Bump version to 2024.10.0b11 2024-10-02 16:01:13 +02:00
Bram Kragten 7d3dd2dd6b Update frontend to 20241002.1 (#127292) 2024-10-02 16:00:43 +02:00
Christopher Fenner 48538ef5d5 Fix climate entity in ViCare integration (#127128)
do not reset _attributes
2024-10-02 16:00:38 +02:00
Franck Nijhof 5365439fd4 Bump version to 2024.10.0b10 2024-10-02 10:52:33 +02:00
Erik Montnemery 9c28a4e8a0 Make recorder WS command recorder/clear_statistics wait (#127120) 2024-10-02 10:51:30 +02:00
Bram Kragten 565203047c Update frontend to 20241002.0 (#127264) 2024-10-02 10:50:39 +02:00
Erik Montnemery b9795a2ae7 Make recorder WS command recorder/update_statistics_metadata wait (#127179) 2024-10-02 10:50:35 +02:00
Franck Nijhof 4e4f8ee3a4 Bump version to 2024.10.0b9 2024-10-02 09:26:37 +02:00
Martin Hjelmare b8fd921c81 Revert "Support Z-Wave JS dimming lights using color intensity (#122639)" (#127256)
This reverts commit c7cfd56b72.
2024-10-02 09:26:28 +02:00
Teemu R. fcf91954ff Remove codefences from issue titles (#127254) 2024-10-02 09:26:25 +02:00
Michael Hansen 49708196ac Run unsubscribe callbacks when Assist satellite entity is removed from HA (#127234)
* Unsubscribe when removed from HA

* Use builtin async_on_remove
2024-10-02 09:26:22 +02:00
functionpointer 8c8a2eef21 Fix Tibber get_prices when called with aware datetime (#123289)
* Tibber: Add extra test to expose aware/naive datetime issue

* Tibber: Fix get_prices action not working with aware datetimes

* Tibber: Simplify comparison

* Tibber: Combine timezone tests into single parametrized one

* Tibber: Split test again to prevent if statement
2024-10-02 09:26:15 +02:00
Franck Nijhof 749a5b37c9 Bump version to 2024.10.0b8 2024-10-01 22:14:57 +02:00
epenet 60079a14e7 Update log error message for Samsung TV (#127231) 2024-10-01 22:14:50 +02:00
Erik Montnemery df6edd09c0 Don't create statistics issues when sensor is unavailable or unknown (#127226) 2024-10-01 22:14:47 +02:00
epenet 88ff94dd69 Use reconfigure_confirm in bryant_evolution config flow (#127222) 2024-10-01 22:14:44 +02:00
epenet 067b81a60b Use reconfigure_confirm in enphase_envoy config flow (#127221) 2024-10-01 22:14:40 +02:00
epenet 03553b8bb9 Use reconfigure_confirm in homeworks config flow (#127218)
* Use reconfigure_confirm in homeworks config flow

* Fix tests
2024-10-01 22:14:37 +02:00
Franck Nijhof bce7552d4d Update gotailwind to 0.2.4 (#127129) 2024-10-01 22:14:34 +02:00
Russell Cloran 53a2777831 Update prometheus-client to 0.21.0 (#126965) 2024-10-01 22:14:31 +02:00
Bill Flood 507492947a Fix Tailwind cover exception when door is already in the requested state (#124543) 2024-10-01 22:14:27 +02:00
Franck Nijhof 41b3eb9f79 Bump version to 2024.10.0b7 2024-10-01 14:54:05 +02:00
epenet f2c746122e Use reconfigure_confirm in google_travel_time config flow (#127220) 2024-10-01 14:53:39 +02:00
epenet e25a54aef4 Use reconfigure_confirm in lcn config flow (#127217) 2024-10-01 14:53:36 +02:00
epenet 5c42e45048 Fix reconfigure_confirm logic in madvr config flow (#127216) 2024-10-01 14:53:32 +02:00
epenet c8b92bc858 Use reconfigure_confirm in solarlog config flow (#127215)
* Use reconfigure_confirm in solarlog config flow

* Fix test
2024-10-01 14:53:29 +02:00
epenet 9d059fcfaa Use reconfigure_confirm in vallox config flow (#127214) 2024-10-01 14:53:26 +02:00
Martin Hjelmare ce5f193219 Fix Z-Wave rediscovery (#127213) 2024-10-01 14:53:22 +02:00
Erik Montnemery 92023ecbe6 Update assist_satellite connection test sound (#127183) 2024-10-01 14:53:19 +02:00
cdnninja 1e0164a96a Allows unload when unsupported devices vesync (#127153)
Allows unload when unsupported devices
2024-10-01 14:53:15 +02:00
G Johansson 6f5eac3143 Add config flow validation that calibration factor is not zero (#127136)
* Add config flow validation that calibration factor is not zero

* Add test
2024-10-01 14:50:10 +02:00
Nerdix 60dfccb747 Roborock fix "selected map" when first map in list is selected (#127126)
* avoid None when current_map = 0

* combine statements
2024-10-01 14:46:41 +02:00
Franck Nijhof e9dc09755e Bump version to 2024.10.0b6 2024-09-30 20:51:44 +02:00
G Johansson 1ce2b18aaf Allow negative calibration factor in mold_indicator (#127133) 2024-09-30 20:51:34 +02:00
Franck Nijhof abd351e326 Update RestrictedPython to 7.3 (#127130) 2024-09-30 20:51:31 +02:00
starkillerOG d3e6069095 Mark Reolink camera entities as unavailable when camera is offline (#127127)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-09-30 20:51:28 +02:00
Bram Kragten f0c3900842 Update frontend to 20240930.0 (#127125) 2024-09-30 20:51:25 +02:00
Martin Hjelmare 25247de6a6 Bump zwave-js-server-python to 0.58.1 (#127114)
* Bump zwave-js-server-python to 0.58.1

* Update tests
2024-09-30 20:51:21 +02:00
Franck Nijhof f3a72dda7b Bump version to 2024.10.0b5 2024-09-30 14:14:01 +02:00
Joost Lekkerkerker b6af6ddea2 Bump yt-dlp to 2024.09.27 (#127096) 2024-09-30 14:05:12 +02:00
Joost Lekkerkerker a2cd17ef0a Make Laundrify unique id a string (#127092) 2024-09-30 14:05:08 +02:00
Simon Goodall b8ed449944 Check "status" is present before access during device update (#127091) 2024-09-30 14:05:05 +02:00
Matthias Alphart dc79299301 Update xknxproject to 3.8.0 (#127072) 2024-09-30 14:05:02 +02:00
Luca Dibattista fa295b93a7 Fix Roomba help URL (#127065)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-09-30 14:04:58 +02:00
J. Nick Koston 725c361e9c Add missing OUI to august (#127064) 2024-09-30 14:04:55 +02:00
Jon Caruana a8f25b1b93 Bump pylitejet to 0.6.3 (#127063) 2024-09-30 14:04:51 +02:00
Kevin Stillhammer 3ee85b3356 Clarify excl/incl filter functionality for waze_travel_time (#127056) 2024-09-30 14:04:48 +02:00
J. Nick Koston 22c85bf5f7 Fix removing nulls when encoding events for PostgreSQL (#127053) 2024-09-30 14:04:45 +02:00
Shai Ungar 0a18838fb0 Fix timestamp isoformat in seventeentrack (#127052)
fix timestamp isoformat
2024-09-30 14:04:41 +02:00
Shai Ungar 62629a0b34 Fix repair when integration does not exist (#127050) 2024-09-30 14:04:38 +02:00
Allen Porter b42848fd7a Bump gcal_sync to 6.1.5 (#127049) 2024-09-30 14:04:34 +02:00
Franck Nijhof 9070806172 Update ical to 8.2.0 (#126954) 2024-09-30 14:04:31 +02:00
Allen Porter 4e11797d72 Update local_calendar/todo to avoid blocking in the event loop (#127048) 2024-09-30 14:01:12 +02:00
Michael 8f47b63762 Bump py-synologydsm-api to 2.5.3 (#127035) 2024-09-30 14:01:08 +02:00
Jan Bouwhuis daa13235e6 Allow null / None value for non numeric mqtt sensor without warnings (#127032)
Allow `null` / `None` value for mqtt sensor without warnings
2024-09-30 14:01:04 +02:00
J. Nick Koston 084c2d976e Bump anyio to 4.6.0 (#127013) 2024-09-30 14:01:00 +02:00
Michael Hansen 75363b609b Don't log voice assistant config timeout error (#127010)
Don't log config timeout error
2024-09-30 14:00:56 +02:00
J. Nick Koston 8d09982f3b Bump aiohttp to 3.10.8 (#127009)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.10.7...v3.10.8

Fixes a long standing cancellation leak on timeout
2024-09-30 14:00:51 +02:00
Paulus Schoutsen aa5e8eaf19 Exclude Text-to-Speech cache from backups (#127001)
Text-to-speech cache doesn't need to be included in backups.
2024-09-30 14:00:43 +02:00
G Johansson fc97eb8151 Workday raise issues only to next year (#126997)
* Workday - raise issues only for current and next year

* variable
2024-09-30 14:00:24 +02:00
G Johansson a68d7c9b9d Add unique id to mold_indicator (#126990) 2024-09-30 13:59:18 +02:00
Franck Nijhof 3bb13f76fa Bump version to 2024.10.0b4 2024-09-28 11:00:20 +02:00
J. Nick Koston f57ce96ff0 Bump aiohttp to 3.10.7 (#126970) 2024-09-28 11:00:10 +02:00
J. Nick Koston 105d7952fc Bump yarl to 1.13.1 (#126962) 2024-09-28 11:00:07 +02:00
Steven B. 6f4a488308 Bump python-kasa library to 0.7.4 (#126944) 2024-09-28 11:00:03 +02:00
ozadr1an 23a11dddb3 Bump nessclient to 1.1.2 (#125604)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-09-28 10:59:59 +02:00
Franck Nijhof 28aff1a90a Bump version to 2024.10.0b3 2024-09-27 19:39:22 +02:00
Jan Rieger 9a56381e28 Add missing icons to unifi (#126934) 2024-09-27 19:39:06 +02:00
Bram Kragten 2d1708e5e8 Update frontend to 20240927.0 (#126933) 2024-09-27 19:39:03 +02:00
Raj Laud 73deb076fe Squeezebox - bump pysqueezebox dependency to 0.9.3 to restore favorites support (#126929) 2024-09-27 19:38:59 +02:00
Michael Hansen c4f189863c Adjust "Assist in progress" sensor in ESPHome (#126928)
Adjust sensor
2024-09-27 19:38:56 +02:00
Michael Hansen 02e15a4ce7 Change Assist satellite state names (#126926)
* Change state names

* Update homeassistant/components/assist_satellite/strings.json

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-27 19:38:52 +02:00
Jon Seager ba8e9bc168 Bump pytouchlinesl to 0.1.7 (#126923) 2024-09-27 19:38:49 +02:00
mvn23 46d3bda80a Bump pyotgw to 2.2.1 (#126918) 2024-09-27 19:38:46 +02:00
Jon Seager 222006d106 Update pytouchlinesl to 0.1.6 (#126912) 2024-09-27 19:38:43 +02:00
Joost Lekkerkerker 7925aee91f Migrate Nexia unique id to str (#126911) 2024-09-27 19:38:40 +02:00
Joost Lekkerkerker 4e3b012f3e Fix Tado unloading (#126910) 2024-09-27 19:38:36 +02:00
Jan Bouwhuis b606b50cec Do not unsubscribe mqtt integration discovery if entry is already configured (#126907)
* Do not unsubscribe mqtt integration discovery if entry is already configured

* Test cases without unsubscribe
2024-09-27 19:38:33 +02:00
Jan Rieger 57028a0807 Use icon translations in unifi (#126903)
* Use icon translations in unifi

* Update snapshots

* Add state icons

* Address feedback

* Update snapshot
2024-09-27 19:38:30 +02:00
Joost Lekkerkerker 840cc483b0 Update airgradient device sw_version when changed (#126902) 2024-09-27 19:38:27 +02:00
Manu 8d1f944096 Revert "Add support for Xiaomi airpurifier and humidifier (#117791)" (#126873) 2024-09-27 19:38:24 +02:00
Manu a45c4ec8e9 Fix blocking call in Xiaomi Miio integration (#126871) 2024-09-27 19:38:21 +02:00
Manu 0f3f50e817 Add support for variant of Xiaomi Mi Air Purifier 3C (zhimi.airp.mb4a) (#126867)
Add model id zhimi.airp.mb4a
2024-09-27 19:38:17 +02:00
Erik Montnemery a4ff292231 Improve statistics issue title (#126851) 2024-09-27 19:38:14 +02:00
Simon Lamon e8636670d4 Bump python-linkplay to 0.0.12 (#126850)
Bump dependency
2024-09-27 19:38:11 +02:00
Simon bae6d679aa Use hass httpx client for ElevenLabs component (#126793) 2024-09-27 19:38:07 +02:00
Franck Nijhof a3e3edb9a2 Bump version to 2024.10.0b2 2024-09-27 11:53:10 +02:00
J. Nick Koston e66dd63516 Fix getting the current host for IPv6 urls (#126889) 2024-09-27 11:53:02 +02:00
Joost Lekkerkerker ec66c7e534 Add diagnostics platform to airgradient (#126886) 2024-09-27 11:52:58 +02:00
Franck Nijhof 2749b1f057 Mark custom panel integration as system type (#126883) 2024-09-27 11:52:55 +02:00
J. Nick Koston b079a94bef Fix getting the host for the current request (#126882) 2024-09-27 11:52:52 +02:00
J. Nick Koston 3d1bd626b0 Bump yarl to 1.13.0 (#126872) 2024-09-27 11:52:49 +02:00
Joost Lekkerkerker 60641d5a4e Fix restoring state class in mobile app (#126868) 2024-09-27 11:52:45 +02:00
EnjoyingM 28d491e997 Bump wolf-comm to 0.0.15 (#126857) 2024-09-27 11:52:42 +02:00
Jeef bb73529770 Monarch Money cashflow sensor bugfix (#125774)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-09-27 11:52:36 +02:00
Kareem ElFaramawi ebfd442b51 Fix Abode integration needing to reauthenticate after core update (#123035)
* bump jaraco.abode to 6.2.1

* update abode user_data path to HA config

* Move abode config call out of try block

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-27 11:52:33 +02:00
Alexey ALERT Rubashёff fdd8c0969b Update overkiz Atlantic Water Heater away mode switching (#121801) 2024-09-27 11:52:29 +02:00
Franck Nijhof bdc548b464 Bump version to 2024.10.0b1 2024-09-26 20:46:24 +02:00
Paulus Schoutsen b60e6082f7 Update the Selected Pipeline entity name (#126845) 2024-09-26 20:46:08 +02:00
Bram Kragten 9a7254e4ee Update frontend to 20240926.0 (#126843) 2024-09-26 20:46:05 +02:00
Mike Degatano 73e56e292a Bump aiohasupervisor to 0.1.0 (#126841) 2024-09-26 20:46:02 +02:00
Joost Lekkerkerker 42a4a89793 Fix Withings reauth title (#126838) 2024-09-26 20:45:59 +02:00
Joost Lekkerkerker bb7803b020 Fix last played icon in NYT Games (#126837) 2024-09-26 20:45:55 +02:00
Joost Lekkerkerker dd0fc0688d Bump nyt_games to 0.4.2 (#126834)
* Bump nyt_games to 0.4.1

* Bump nyt_games to 0.4.1

* Bump nyt_games to 0.4.2
2024-09-26 20:45:52 +02:00
Joost Lekkerkerker 1380ed7328 Add logging to NYT Games setup failures (#126832) 2024-09-26 20:45:49 +02:00
Joost Lekkerkerker 9d6569d515 Bump knocki to 0.3.5 (#126826) 2024-09-26 20:45:45 +02:00
Manu 20be8fd2d3 Fix typo in Mealie integration (#126824) 2024-09-26 20:45:42 +02:00
Joost Lekkerkerker 9d48c77861 Bump jaraco.abode to 6.2.1 (#126823) 2024-09-26 20:45:39 +02:00
Steven B. 7ab93a70dc Bump ring-doorbell to 0.9.6 (#126817) 2024-09-26 20:45:36 +02:00
Martin Hjelmare a435095e76 Fix missing template alarm control panel menu string (#126791) 2024-09-26 20:45:32 +02:00
starkillerOG eb763563f2 Bump reolink-aio to 0.9.11 (#126778) 2024-09-26 20:45:29 +02:00
Noah Husby 9bf0b5bff1 Bump aiorussound to 4.0.5 (#126774)
* Bump aiorussound to 4.0.4

* Remove unnecessary exception

* Bump aiorussound to 4.0.5

* Fixes

* Update homeassistant/components/russound_rio/media_player.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-26 20:45:26 +02:00
starkillerOG 638dd37545 Remove Reolink Home Hub main level switches (#126697)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-09-26 20:45:23 +02:00
G Johansson 11cc718273 Change Climate set temp action for incorrect feature will raise (#126692)
* Change Climate set temp action for incorrect feature will raise

* Fix some tests

* Fix review comments

* Fix tesla_fleet

* Fix tests

* Fix review comment
2024-09-26 20:45:19 +02:00
Steven B. cf6b07630b Deprecate tplink alarm button entities (#126349)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-09-26 20:45:14 +02:00
Michael Hansen 17e0db9da3 Fix ESPHome and VoIP Assist satellite entity names (#126229)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-09-26 20:45:09 +02:00
Franck Nijhof 53cf8628fa Bump version to 2024.10.0b0 2024-09-25 20:34:22 +02:00
218 changed files with 3230 additions and 1718 deletions
+7 -7
View File
@@ -27,7 +27,7 @@ jobs:
publish: ${{ steps.version.outputs.publish }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
with:
fetch-depth: 0
@@ -90,7 +90,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
@@ -242,7 +242,7 @@ jobs:
- green
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set build additional args
run: |
@@ -279,7 +279,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@@ -321,7 +321,7 @@ jobs:
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Install Cosign
uses: sigstore/cosign-installer@v3.6.0
@@ -451,7 +451,7 @@ jobs:
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
@@ -499,7 +499,7 @@ jobs:
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
+21 -21
View File
@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 10
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.11"
HA_SHORT_VERSION: "2024.10"
DEFAULT_PYTHON: "3.12"
ALL_PYTHON_VERSIONS: "['3.12']"
# 10.3 is the oldest supported version
@@ -93,7 +93,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Generate partial Python venv restore key
id: generate_python_cache_key
run: |
@@ -231,7 +231,7 @@ jobs:
- info
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -277,7 +277,7 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
id: python
@@ -317,7 +317,7 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
id: python
@@ -357,7 +357,7 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
id: python
@@ -447,7 +447,7 @@ jobs:
- script/hassfest/docker/Dockerfile
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
@@ -466,7 +466,7 @@ jobs:
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -550,7 +550,7 @@ jobs:
sudo apt-get -y install \
libturbojpeg
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -583,7 +583,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -617,7 +617,7 @@ jobs:
&& needs.info.outputs.requirements == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -660,7 +660,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -707,7 +707,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -752,7 +752,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -827,7 +827,7 @@ jobs:
libturbojpeg \
libgammu-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -891,7 +891,7 @@ jobs:
libturbojpeg \
libgammu-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1011,7 +1011,7 @@ jobs:
libturbojpeg \
libmariadb-dev-compat
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1137,7 +1137,7 @@ jobs:
libturbojpeg \
postgresql-server-dev-14
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1232,7 +1232,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Download all coverage artifacts
uses: actions/download-artifact@v4.1.8
with:
@@ -1283,7 +1283,7 @@ jobs:
libturbojpeg \
libgammu-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1370,7 +1370,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Download all coverage artifacts
uses: actions/download-artifact@v4.1.8
with:
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.26.9
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
+3 -3
View File
@@ -32,7 +32,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
@@ -119,7 +119,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Download env_file
uses: actions/download-artifact@v4.1.8
@@ -163,7 +163,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.2.0
uses: actions/checkout@v4.1.7
- name: Download env_file
uses: actions/download-artifact@v4.1.8
@@ -4,8 +4,10 @@ from __future__ import annotations
from dataclasses import dataclass, field
from functools import partial
from pathlib import Path
from jaraco.abode.client import Client as Abode
import jaraco.abode.config
from jaraco.abode.exceptions import (
AuthenticationException as AbodeAuthenticationException,
Exception as AbodeException,
@@ -93,6 +95,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
password = entry.data[CONF_PASSWORD]
polling = entry.data[CONF_POLLING]
# Configure abode library to use config directory for storing data
jaraco.abode.config.paths.override(user_data=Path(hass.config.path("Abode")))
# For previous config entries where unique_id is None
if entry.unique_id is None:
hass.config_entries.async_update_entry(
+1 -1
View File
@@ -9,5 +9,5 @@
},
"iot_class": "cloud_push",
"loggers": ["jaraco.abode", "lomond"],
"requirements": ["jaraco.abode==6.2.0"]
"requirements": ["jaraco.abode==6.2.1"]
}
@@ -9,9 +9,10 @@ from typing import TYPE_CHECKING
from airgradient import AirGradientClient, AirGradientError, Config, Measures
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import LOGGER
from .const import DOMAIN, LOGGER
if TYPE_CHECKING:
from . import AirGradientConfigEntry
@@ -29,6 +30,7 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
"""Class to manage fetching AirGradient data."""
config_entry: AirGradientConfigEntry
_current_version: str
def __init__(self, hass: HomeAssistant, client: AirGradientClient) -> None:
"""Initialize coordinator."""
@@ -42,11 +44,27 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
assert self.config_entry.unique_id
self.serial_number = self.config_entry.unique_id
async def _async_setup(self) -> None:
"""Set up the coordinator."""
self._current_version = (
await self.client.get_current_measures()
).firmware_version
async def _async_update_data(self) -> AirGradientData:
try:
measures = await self.client.get_current_measures()
config = await self.client.get_config()
except AirGradientError as error:
raise UpdateFailed(error) from error
else:
return AirGradientData(measures, config)
if measures.firmware_version != self._current_version:
device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, self.serial_number)}
)
assert device_entry
device_registry.async_update_device(
device_entry.id,
sw_version=measures.firmware_version,
)
self._current_version = measures.firmware_version
return AirGradientData(measures, config)
@@ -0,0 +1,18 @@
"""Diagnostics support for Airgradient."""
from __future__ import annotations
from dataclasses import asdict
from typing import Any
from homeassistant.core import HomeAssistant
from . import AirGradientConfigEntry
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: AirGradientConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return asdict(entry.runtime_data.data)
@@ -7,7 +7,7 @@
},
"select": {
"pipeline": {
"name": "Assist pipeline",
"name": "Assistant",
"state": {
"preferred": "Preferred"
}
Binary file not shown.
@@ -41,10 +41,10 @@ _LOGGER = logging.getLogger(__name__)
class AssistSatelliteState(StrEnum):
"""Valid states of an Assist satellite entity."""
LISTENING_WAKE_WORD = "listening_wake_word"
"""Device is streaming audio for wake word detection to Home Assistant."""
IDLE = "idle"
"""Device is waiting for user input, such as a wake word or a button press."""
LISTENING_COMMAND = "listening_command"
LISTENING = "listening"
"""Device is streaming audio with the voice command to Home Assistant."""
PROCESSING = "processing"
@@ -117,7 +117,7 @@ class AssistSatelliteEntity(entity.Entity):
_attr_tts_options: dict[str, Any] | None = None
_pipeline_task: asyncio.Task | None = None
__assist_satellite_state = AssistSatelliteState.LISTENING_WAKE_WORD
__assist_satellite_state = AssistSatelliteState.IDLE
@final
@property
@@ -242,7 +242,7 @@ class AssistSatelliteEntity(entity.Entity):
)
finally:
self._is_announcing = False
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
async def async_announce(self, announcement: AssistSatelliteAnnouncement) -> None:
"""Announce media on the satellite.
@@ -363,9 +363,9 @@ class AssistSatelliteEntity(entity.Entity):
def _internal_on_pipeline_event(self, event: PipelineEvent) -> None:
"""Set state based on pipeline stage."""
if event.type is PipelineEventType.WAKE_WORD_START:
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
elif event.type is PipelineEventType.STT_START:
self._set_state(AssistSatelliteState.LISTENING_COMMAND)
self._set_state(AssistSatelliteState.LISTENING)
elif event.type is PipelineEventType.INTENT_START:
self._set_state(AssistSatelliteState.PROCESSING)
elif event.type is PipelineEventType.INTENT_END:
@@ -379,7 +379,7 @@ class AssistSatelliteEntity(entity.Entity):
self._set_state(AssistSatelliteState.RESPONDING)
elif event.type is PipelineEventType.RUN_END:
if not self._run_has_tts:
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
self.on_pipeline_event(event)
@@ -392,7 +392,7 @@ class AssistSatelliteEntity(entity.Entity):
@callback
def tts_response_finished(self) -> None:
"""Tell entity that the text-to-speech response has finished playing."""
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
@callback
def _resolve_pipeline(self) -> str | None:
@@ -4,8 +4,8 @@
"_": {
"name": "Assist satellite",
"state": {
"listening_wake_word": "Wake word",
"listening_command": "Voice command",
"idle": "[%key:common::state::idle%]",
"listening": "Listening",
"responding": "Responding",
"processing": "Processing"
}
@@ -16,6 +16,10 @@
"hostname": "connect",
"macaddress": "2C9FFB*"
},
{
"hostname": "connect",
"macaddress": "789C85*"
},
{
"hostname": "august*",
"macaddress": "E076D0*"
+1
View File
@@ -13,4 +13,5 @@ EXCLUDE_FROM_BACKUP = [
"*.log",
"backups/*.tar",
"OZW_Log.txt",
"tts/*",
]
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
@@ -61,6 +62,12 @@ class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle integration reconfiguration."""
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle integration reconfiguration."""
@@ -83,5 +90,7 @@ class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="reconfigure", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
step_id="reconfigure_confirm",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors,
)
@@ -1,6 +1,11 @@
{
"config": {
"step": {
"reconfigure": {
"data": {
"filename": "[%key:component::bryant_evolution::config::step::user::data::filename%]"
}
},
"user": {
"data": {
"filename": "Serial port filename"
@@ -111,7 +111,7 @@
},
"issues": {
"deprecated_service_calendar_list_events": {
"title": "Detected use of deprecated action `calendar.list_events`",
"title": "Detected use of deprecated action calendar.list_events",
"fix_flow": {
"step": {
"confirm": {
+1 -1
View File
@@ -8,5 +8,5 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["hass_nabucasa"],
"requirements": ["hass-nabucasa==0.82.0"]
"requirements": ["hass-nabucasa==0.81.1"]
}
+1 -1
View File
@@ -25,7 +25,7 @@
},
"issues": {
"deprecated_gender": {
"title": "The `{deprecated_option}` text-to-speech option is deprecated",
"title": "The {deprecated_option} text-to-speech option is deprecated",
"fix_flow": {
"step": {
"confirm": {
@@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.httpx_client import get_async_client
from .const import CONF_MODEL
@@ -41,7 +42,10 @@ type EleventLabsConfigEntry = ConfigEntry[ElevenLabsData]
async def async_setup_entry(hass: HomeAssistant, entry: EleventLabsConfigEntry) -> bool:
"""Set up ElevenLabs text-to-speech from a config entry."""
entry.add_update_listener(update_listener)
client = AsyncElevenLabs(api_key=entry.data[CONF_API_KEY])
httpx_client = get_async_client(hass)
client = AsyncElevenLabs(
api_key=entry.data[CONF_API_KEY], httpx_client=httpx_client
)
model_id = entry.options[CONF_MODEL]
try:
model = await get_model_by_id(client, model_id)
@@ -17,6 +17,8 @@ from homeassistant.config_entries import (
OptionsFlowWithConfigEntry,
)
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
@@ -47,9 +49,12 @@ USER_STEP_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str})
_LOGGER = logging.getLogger(__name__)
async def get_voices_models(api_key: str) -> tuple[dict[str, str], dict[str, str]]:
async def get_voices_models(
hass: HomeAssistant, api_key: str
) -> tuple[dict[str, str], dict[str, str]]:
"""Get available voices and models as dicts."""
client = AsyncElevenLabs(api_key=api_key)
httpx_client = get_async_client(hass)
client = AsyncElevenLabs(api_key=api_key, httpx_client=httpx_client)
voices = (await client.voices.get_all()).voices
models = await client.models.get_all()
voices_dict = {
@@ -77,7 +82,7 @@ class ElevenLabsConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
try:
voices, _ = await get_voices_models(user_input[CONF_API_KEY])
voices, _ = await get_voices_models(self.hass, user_input[CONF_API_KEY])
except ApiError:
errors["base"] = "invalid_api_key"
else:
@@ -116,7 +121,7 @@ class ElevenLabsOptionsFlow(OptionsFlowWithConfigEntry):
) -> ConfigFlowResult:
"""Manage the options."""
if not self.voices or not self.models:
self.voices, self.models = await get_voices_models(self.api_key)
self.voices, self.models = await get_voices_models(self.hass, self.api_key)
assert self.models and self.voices
@@ -54,6 +54,8 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_reconnect_entry: ConfigEntry
def __init__(self) -> None:
"""Initialize an envoy flow."""
self.ip_address: str | None = None
@@ -233,17 +235,22 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Add reconfigure step to allow to manually reconfigure a config entry."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
self._reconnect_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Add reconfigure step to allow to manually reconfigure a config entry."""
errors: dict[str, str] = {}
description_placeholders: dict[str, str] = {}
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
suggested_values: dict[str, Any] | MappingProxyType[str, Any] = (
user_input or entry.data
user_input or self._reconnect_entry.data
)
host: Any = suggested_values.get(CONF_HOST)
@@ -284,7 +291,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
error="reconfigure_successful",
)
if not self.unique_id:
await self.async_set_unique_id(entry.unique_id)
await self.async_set_unique_id(self._reconnect_entry.unique_id)
self.context["title_placeholders"] = {
CONF_SERIAL: self.unique_id,
@@ -292,7 +299,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
}
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
self._async_generate_schema(), suggested_values
),
@@ -13,7 +13,7 @@
"host": "The hostname or IP address of your Enphase Envoy gateway."
}
},
"reconfigure": {
"reconfigure_confirm": {
"description": "[%key:component::enphase_envoy::config::step::user::description%]",
"data": {
"host": "[%key:common::config_flow::data::host%]",
@@ -133,7 +133,7 @@ class EsphomeAssistSatellite(
# Empty config. Updated when added to HA.
self._satellite_config = assist_satellite.AssistSatelliteConfiguration(
available_wake_words=[], active_wake_words=[], max_active_wake_words=0
available_wake_words=[], active_wake_words=[], max_active_wake_words=1
)
@property
@@ -179,7 +179,13 @@ class EsphomeAssistSatellite(
async def _update_satellite_config(self) -> None:
"""Get the latest satellite configuration from the device."""
config = await self.cli.get_voice_assistant_configuration(_CONFIG_TIMEOUT_SEC)
try:
config = await self.cli.get_voice_assistant_configuration(
_CONFIG_TIMEOUT_SEC
)
except TimeoutError:
# Placeholder config will be used
return
# Update available/active wake words
self._satellite_config.available_wake_words = [
@@ -206,7 +212,7 @@ class EsphomeAssistSatellite(
)
if feature_flags & VoiceAssistantFeature.API_AUDIO:
# TCP audio
self.entry_data.disconnect_callbacks.add(
self.async_on_remove(
self.cli.subscribe_voice_assistant(
handle_start=self.handle_pipeline_start,
handle_stop=self.handle_pipeline_stop,
@@ -216,7 +222,7 @@ class EsphomeAssistSatellite(
)
else:
# UDP audio
self.entry_data.disconnect_callbacks.add(
self.async_on_remove(
self.cli.subscribe_voice_assistant(
handle_start=self.handle_pipeline_start,
handle_stop=self.handle_pipeline_stop,
@@ -229,7 +235,7 @@ class EsphomeAssistSatellite(
assert (self.registry_entry is not None) and (
self.registry_entry.device_id is not None
)
self.entry_data.disconnect_callbacks.add(
self.async_on_remove(
async_register_timer_handler(
self.hass, self.registry_entry.device_id, self.handle_timer_event
)
@@ -315,6 +321,10 @@ class EsphomeAssistSatellite(
"code": event.data["code"],
"message": event.data["message"],
}
elif event_type == VoiceAssistantEventType.VOICE_ASSISTANT_RUN_END:
if self._tts_streaming_task is None:
# No TTS
self.entry_data.async_set_assist_pipeline_state(False)
self.cli.send_voice_assistant_event(event_type, data_to_send)
@@ -413,7 +423,6 @@ class EsphomeAssistSatellite(
# Run the pipeline
_LOGGER.debug("Running pipeline from %s to %s", start_stage, end_stage)
self.entry_data.async_set_assist_pipeline_state(True)
self._pipeline_task = self.config_entry.async_create_background_task(
self.hass,
self.async_accept_pipeline_from_satellite(
@@ -443,7 +452,6 @@ class EsphomeAssistSatellite(
def handle_pipeline_finished(self) -> None:
"""Handle when pipeline has finished running."""
self.entry_data.async_set_assist_pipeline_state(False)
self._stop_udp_server()
_LOGGER.debug("Pipeline finished")
@@ -561,6 +569,7 @@ class EsphomeAssistSatellite(
# State change
self.tts_response_finished()
self.entry_data.async_set_assist_pipeline_state(False)
async def _wrap_audio_stream(self) -> AsyncIterable[bytes]:
"""Yield audio chunks from the queue until None."""
@@ -59,6 +59,11 @@
}
},
"entity": {
"assist_satellite": {
"assist_satellite": {
"name": "[%key:component::assist_satellite::entity_component::_::name%]"
}
},
"binary_sensor": {
"assist_in_progress": {
"name": "[%key:component::assist_pipeline::entity::binary_sensor::assist_in_progress::name%]"
@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240925.0"]
"requirements": ["home-assistant-frontend==20241002.1"]
}
@@ -57,17 +57,44 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
def __init__(self, device, gps=None, location_name=None, attributes=None):
"""Set up Geofency entity."""
self._attr_extra_state_attributes = attributes or {}
self._attributes = attributes or {}
self._name = device
self._attr_location_name = location_name
if gps:
self._attr_latitude = gps[0]
self._attr_longitude = gps[1]
self._location_name = location_name
self._gps = gps
self._unsub_dispatcher = None
self._attr_unique_id = device
self._attr_device_info = DeviceInfo(
identifiers={(GF_DOMAIN, device)},
name=device,
self._unique_id = device
@property
def extra_state_attributes(self):
"""Return device specific attributes."""
return self._attributes
@property
def latitude(self):
"""Return latitude value of the device."""
return self._gps[0]
@property
def longitude(self):
"""Return longitude value of the device."""
return self._gps[1]
@property
def location_name(self):
"""Return a location name for the current location of the device."""
return self._location_name
@property
def unique_id(self):
"""Return the unique ID."""
return self._unique_id
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
identifiers={(GF_DOMAIN, self._unique_id)},
name=self._name,
)
async def async_added_to_hass(self) -> None:
@@ -77,23 +104,21 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
self.hass, TRACKER_UPDATE, self._async_receive_data
)
if self._attr_extra_state_attributes:
if self._attributes:
return
if (state := await self.async_get_last_state()) is None:
self._attr_latitude = None
self._attr_longitude = None
self._gps = (None, None)
return
attr = state.attributes
self._attr_latitude = attr.get(ATTR_LATITUDE)
self._attr_longitude = attr.get(ATTR_LONGITUDE)
self._gps = (attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))
async def async_will_remove_from_hass(self) -> None:
"""Clean up after entity before removal."""
await super().async_will_remove_from_hass()
self._unsub_dispatcher()
self.hass.data[GF_DOMAIN]["devices"].remove(self.unique_id)
self.hass.data[GF_DOMAIN]["devices"].remove(self._unique_id)
@callback
def _async_receive_data(self, device, gps, location_name, attributes):
@@ -101,8 +126,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
if device != self._name:
return
self._attr_extra_state_attributes.update(attributes)
self._attr_location_name = location_name
self._attr_latitude = gps[0]
self._attr_longitude = gps[1]
self._attributes.update(attributes)
self._location_name = location_name
self._gps = gps
self.async_write_ha_state()
@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==6.1.4", "oauth2client==4.1.3", "ical==8.1.1"]
"requirements": ["gcal-sync==6.1.5", "oauth2client==4.1.3", "ical==8.2.0"]
}
@@ -1,7 +1,5 @@
"""Media source for Google Photos."""
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
import logging
@@ -48,7 +46,7 @@ class PhotosIdentifierType(StrEnum):
ALBUM = "a"
@classmethod
def of(cls, name: str) -> PhotosIdentifierType:
def of(cls, name: str) -> "PhotosIdentifierType":
"""Parse a PhotosIdentifierType by string value."""
for enum in PhotosIdentifierType:
if enum.value == name:
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any
import voluptuous as vol
@@ -207,6 +208,8 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_context_entry: ConfigEntry
@staticmethod
@callback
def async_get_options_flow(
@@ -235,28 +238,33 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
if TYPE_CHECKING:
assert entry
self._context_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration."""
errors: dict[str, str] | None = None
user_input = user_input or {}
if user_input:
if user_input is not None:
errors = await validate_input(self.hass, user_input)
if not errors:
return self.async_update_reload_and_abort(
entry,
self._context_entry,
data=user_input,
reason="reconfigure_successful",
)
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
RECONFIGURE_SCHEMA, entry.data.copy()
RECONFIGURE_SCHEMA, self._context_entry.data.copy()
),
errors=errors,
)
@@ -11,7 +11,7 @@
"destination": "Destination"
}
},
"reconfigure": {
"reconfigure_confirm": {
"description": "[%key:component::google_travel_time::config::step::user::description%]",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
@@ -152,7 +152,7 @@
},
"issues": {
"deprecated_task_entity": {
"title": "The Habitica `{task_name}` sensor is deprecated",
"title": "The Habitica {task_name} sensor is deprecated",
"description": "The Habitica entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your automations and scripts to replace the sensor entity with the newly added todo entity.\nWhen you are done migrating you can disable `{entity}`."
}
},
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/hassio",
"iot_class": "local_polling",
"quality_scale": "internal",
"requirements": ["aiohasupervisor==0.1.0b1"]
"requirements": ["aiohasupervisor==0.1.0"]
}
+1 -1
View File
@@ -127,5 +127,5 @@ class HiveSensorEntity(HiveEntity, SensorEntity):
await self.hive.session.updateData(self.device)
self.device = await self.hive.sensor.getSensor(self.device)
self._attr_native_value = self.entity_description.fn(
self.device["status"]["state"]
self.device.get("status", {}).get("state")
)
@@ -19,7 +19,7 @@
"description": "The currency {currency} is no longer in use, please reconfigure the currency configuration."
},
"legacy_templates_false": {
"title": "`legacy_templates` config key is being removed",
"title": "legacy_templates config key is being removed",
"description": "Nothing will change with your templates.\n\nRemove the `legacy_templates` key from the `homeassistant` configuration in your configuration.yaml file and restart Home Assistant to fix this issue."
},
"legacy_templates_true": {
@@ -43,7 +43,7 @@
"description": "It's not possible to configure {platform} {domain} by adding `{platform_key}` to the {domain} configuration. Please check the documentation for more information on how to set up this integration.\n\nTo resolve this:\n1. Remove `{platform_key}` occurences from the `{domain}:` configuration in your YAML configuration file.\n2. Restart Home Assistant.\n\nExample that should be removed:\n{yaml_example}"
},
"storage_corruption": {
"title": "Storage corruption detected for `{storage_key}`",
"title": "Storage corruption detected for {storage_key}",
"fix_flow": {
"step": {
"confirm": {
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
from functools import partial
import logging
from typing import Any
@@ -557,6 +558,8 @@ OPTIONS_FLOW = {
class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Lutron Homeworks."""
_context_entry: ConfigEntry
async def _validate_edit_controller(
self, user_input: dict[str, Any]
) -> dict[str, Any]:
@@ -580,18 +583,24 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
return user_input
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle a reconfigure flow."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
self._context_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfigure flow."""
errors = {}
suggested_values = {
CONF_HOST: entry.options[CONF_HOST],
CONF_PORT: entry.options[CONF_PORT],
CONF_USERNAME: entry.data.get(CONF_USERNAME),
CONF_PASSWORD: entry.data.get(CONF_PASSWORD),
CONF_HOST: self._context_entry.options[CONF_HOST],
CONF_PORT: self._context_entry.options[CONF_PORT],
CONF_USERNAME: self._context_entry.data.get(CONF_USERNAME),
CONF_PASSWORD: self._context_entry.data.get(CONF_PASSWORD),
}
if user_input:
@@ -608,16 +617,16 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
else:
password = user_input.pop(CONF_PASSWORD, None)
username = user_input.pop(CONF_USERNAME, None)
new_data = entry.data | {
new_data = self._context_entry.data | {
CONF_PASSWORD: password,
CONF_USERNAME: username,
}
new_options = entry.options | {
new_options = self._context_entry.options | {
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
}
return self.async_update_reload_and_abort(
entry,
self._context_entry,
data=new_data,
options=new_options,
reason="reconfigure_successful",
@@ -625,7 +634,7 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
)
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
DATA_SCHEMA_EDIT_CONTROLLER, suggested_values
),
@@ -22,7 +22,7 @@
"name": "[%key:component::homeworks::config::step::user::data_description::name%]"
}
},
"reconfigure": {
"reconfigure_confirm": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]",
@@ -45,8 +45,8 @@
},
"data_description": {
"name": "A unique name identifying the Lutron Homeworks controller",
"password": "[%key:component::homeworks::config::step::reconfigure::data_description::password%]",
"username": "[%key:component::homeworks::config::step::reconfigure::data_description::username%]"
"password": "[%key:component::homeworks::config::step::reconfigure_confirm::data_description::password%]",
"username": "[%key:component::homeworks::config::step::reconfigure_confirm::data_description::username%]"
},
"description": "Add a Lutron Homeworks controller"
}
@@ -1,7 +1,5 @@
"""Config flow for the html5 component."""
from __future__ import annotations
import binascii
from typing import Any, cast
@@ -44,7 +42,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
@callback
def _async_create_html5_entry(
self: HTML5ConfigFlow, data: dict[str, str]
self: "HTML5ConfigFlow", data: dict[str, str]
) -> tuple[dict[str, str], ConfigFlowResult | None]:
"""Create an HTML5 entry."""
errors = {}
@@ -70,7 +68,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
return errors, flow_result
async def async_step_user(
self: HTML5ConfigFlow, user_input: dict[str, Any] | None = None
self: "HTML5ConfigFlow", user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
@@ -94,7 +92,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_import(
self: HTML5ConfigFlow, import_config: dict
self: "HTML5ConfigFlow", import_config: dict
) -> ConfigFlowResult:
"""Handle config import from yaml."""
_, flow_result = self._async_create_html5_entry(import_config)
+1 -1
View File
@@ -12,7 +12,7 @@
"quality_scale": "platinum",
"requirements": [
"xknx==3.2.0",
"xknxproject==3.7.1",
"xknxproject==3.8.0",
"knx-frontend==2024.9.10.221729"
],
"single_config_entry": true
@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
STORAGE_VERSION: Final = 1
STORAGE_KEY: Final = f"{DOMAIN}/config_store.json"
type KNXPlatformStoreModel = dict[str, dict[str, Any]] # unique_id: configuration
type KNXEntityStoreModel = dict[
KNXPlatformStoreModel = dict[str, dict[str, Any]] # unique_id: configuration
KNXEntityStoreModel = dict[
str, KNXPlatformStoreModel
] # platform: KNXPlatformStoreModel
@@ -108,6 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
bluetooth_client=bluetooth_client,
)
await coordinator.async_setup()
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
@@ -57,7 +57,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
self._last_statistics_data_update: float | None = None
self._local_client = local_client
async def _async_setup(self) -> None:
async def async_setup(self) -> None:
"""Set up the coordinator."""
if self._local_client is not None:
_LOGGER.debug("Init WebSocket in background task")
@@ -2,6 +2,8 @@
from __future__ import annotations
import logging
from laundrify_aio import LaundrifyAPI
from laundrify_aio.exceptions import ApiConnectionException, UnauthorizedException
@@ -14,6 +16,8 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DEFAULT_POLL_INTERVAL, DOMAIN
from .coordinator import LaundrifyUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
@@ -51,3 +55,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
_LOGGER.debug("Migration successful")
return True
@@ -29,6 +29,7 @@ class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for laundrify."""
VERSION = 1
MINOR_VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -64,7 +65,7 @@ class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN):
else:
entry_data = {CONF_ACCESS_TOKEN: access_token}
await self.async_set_unique_id(account_id)
await self.async_set_unique_id(str(account_id))
self._abort_if_unique_id_configured()
# Create a new entry if it doesn't exist
+25 -12
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
@@ -9,7 +10,7 @@ import pypck
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
from homeassistant.const import (
CONF_BASE,
CONF_DEVICES,
@@ -113,6 +114,8 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
MINOR_VERSION = 2
_context_entry: ConfigEntry
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Import existing configuration from LCN."""
# validate the imported connection parameters
@@ -193,31 +196,41 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=data[CONF_HOST], data=data)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> config_entries.ConfigFlowResult:
"""Reconfigure LCN configuration."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
self._context_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> config_entries.ConfigFlowResult:
"""Reconfigure LCN configuration."""
errors = None
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
if user_input is not None:
user_input[CONF_HOST] = entry.data[CONF_HOST]
user_input[CONF_HOST] = self._context_entry.data[CONF_HOST]
await self.hass.config_entries.async_unload(entry.entry_id)
await self.hass.config_entries.async_unload(self._context_entry.entry_id)
if (error := await validate_connection(user_input)) is not None:
errors = {CONF_BASE: error}
if errors is None:
data = entry.data.copy()
data = self._context_entry.data.copy()
data.update(user_input)
self.hass.config_entries.async_update_entry(entry, data=data)
await self.hass.config_entries.async_setup(entry.entry_id)
self.hass.config_entries.async_update_entry(
self._context_entry, data=data
)
await self.hass.config_entries.async_setup(self._context_entry.entry_id)
return self.async_abort(reason="reconfigure_successful")
await self.hass.config_entries.async_setup(entry.entry_id)
await self.hass.config_entries.async_setup(self._context_entry.entry_id)
return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(CONFIG_SCHEMA, entry.data),
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
CONFIG_SCHEMA, self._context_entry.data
),
errors=errors or {},
)
+1 -1
View File
@@ -34,7 +34,7 @@
"acknowledge": "Retry sendig commands if no response is received (increases bus traffic)."
}
},
"reconfigure": {
"reconfigure_confirm": {
"title": "Reconfigure LCN host",
"description": "Reconfigure connection to LCN host.",
"data": {
@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/linkplay",
"integration_type": "hub",
"iot_class": "local_polling",
"requirements": ["python-linkplay==0.0.9"],
"requirements": ["python-linkplay==0.0.12"],
"zeroconf": ["_linkplay._tcp.local."]
}
@@ -8,5 +8,5 @@
"iot_class": "local_push",
"loggers": ["pylitejet"],
"quality_scale": "platinum",
"requirements": ["pylitejet==0.6.2"]
"requirements": ["pylitejet==0.6.3"]
}
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from datetime import date, datetime, timedelta
import logging
from typing import Any
@@ -74,6 +75,7 @@ class LocalCalendarEntity(CalendarEntity):
"""Initialize LocalCalendarEntity."""
self._store = store
self._calendar = calendar
self._calendar_lock = asyncio.Lock()
self._event: CalendarEvent | None = None
self._attr_name = name
self._attr_unique_id = unique_id
@@ -110,8 +112,10 @@ class LocalCalendarEntity(CalendarEntity):
async def async_create_event(self, **kwargs: Any) -> None:
"""Add a new event to calendar."""
event = _parse_event(kwargs)
EventStore(self._calendar).add(event)
await self._async_store()
async with self._calendar_lock:
event_store = EventStore(self._calendar)
await self.hass.async_add_executor_job(event_store.add, event)
await self._async_store()
await self.async_update_ha_state(force_refresh=True)
async def async_delete_event(
@@ -124,15 +128,16 @@ class LocalCalendarEntity(CalendarEntity):
range_value: Range = Range.NONE
if recurrence_range == Range.THIS_AND_FUTURE:
range_value = Range.THIS_AND_FUTURE
try:
EventStore(self._calendar).delete(
uid,
recurrence_id=recurrence_id,
recurrence_range=range_value,
)
except EventStoreError as err:
raise HomeAssistantError(f"Error while deleting event: {err}") from err
await self._async_store()
async with self._calendar_lock:
try:
EventStore(self._calendar).delete(
uid,
recurrence_id=recurrence_id,
recurrence_range=range_value,
)
except EventStoreError as err:
raise HomeAssistantError(f"Error while deleting event: {err}") from err
await self._async_store()
await self.async_update_ha_state(force_refresh=True)
async def async_update_event(
@@ -147,16 +152,23 @@ class LocalCalendarEntity(CalendarEntity):
range_value: Range = Range.NONE
if recurrence_range == Range.THIS_AND_FUTURE:
range_value = Range.THIS_AND_FUTURE
try:
EventStore(self._calendar).edit(
uid,
new_event,
recurrence_id=recurrence_id,
recurrence_range=range_value,
)
except EventStoreError as err:
raise HomeAssistantError(f"Error while updating event: {err}") from err
await self._async_store()
async with self._calendar_lock:
event_store = EventStore(self._calendar)
def apply_edit() -> None:
event_store.edit(
uid,
new_event,
recurrence_id=recurrence_id,
recurrence_range=range_value,
)
try:
await self.hass.async_add_executor_job(apply_edit)
except EventStoreError as err:
raise HomeAssistantError(f"Error while updating event: {err}") from err
await self._async_store()
await self.async_update_ha_state(force_refresh=True)
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
"iot_class": "local_polling",
"loggers": ["ical"],
"requirements": ["ical==8.1.1"]
"requirements": ["ical==8.2.0"]
}
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/local_todo",
"iot_class": "local_polling",
"requirements": ["ical==8.1.1"]
"requirements": ["ical==8.2.0"]
}
+32 -24
View File
@@ -1,5 +1,6 @@
"""A Local To-do todo platform."""
import asyncio
import datetime
import logging
@@ -130,6 +131,7 @@ class LocalTodoListEntity(TodoListEntity):
"""Initialize LocalTodoListEntity."""
self._store = store
self._calendar = calendar
self._calendar_lock = asyncio.Lock()
self._attr_name = name.capitalize()
self._attr_unique_id = unique_id
@@ -159,23 +161,28 @@ class LocalTodoListEntity(TodoListEntity):
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
todo = _convert_item(item)
self._new_todo_store().add(todo)
await self.async_save()
async with self._calendar_lock:
todo_store = self._new_todo_store()
await self.hass.async_add_executor_job(todo_store.add, todo)
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_update_todo_item(self, item: TodoItem) -> None:
"""Update an item to the To-do list."""
todo = _convert_item(item)
self._new_todo_store().edit(todo.uid, todo)
await self.async_save()
async with self._calendar_lock:
todo_store = self._new_todo_store()
await self.hass.async_add_executor_job(todo_store.edit, todo.uid, todo)
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_delete_todo_items(self, uids: list[str]) -> None:
"""Delete an item from the To-do list."""
store = self._new_todo_store()
for uid in uids:
store.delete(uid)
await self.async_save()
async with self._calendar_lock:
for uid in uids:
store.delete(uid)
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_move_todo_item(
@@ -184,23 +191,24 @@ class LocalTodoListEntity(TodoListEntity):
"""Re-order an item to the To-do list."""
if uid == previous_uid:
return
todos = self._calendar.todos
item_idx: dict[str, int] = {itm.uid: idx for idx, itm in enumerate(todos)}
if uid not in item_idx:
raise HomeAssistantError(
"Item '{uid}' not found in todo list {self.entity_id}"
)
if previous_uid and previous_uid not in item_idx:
raise HomeAssistantError(
"Item '{previous_uid}' not found in todo list {self.entity_id}"
)
dst_idx = item_idx[previous_uid] + 1 if previous_uid else 0
src_idx = item_idx[uid]
src_item = todos.pop(src_idx)
if dst_idx > src_idx:
dst_idx -= 1
todos.insert(dst_idx, src_item)
await self.async_save()
async with self._calendar_lock:
todos = self._calendar.todos
item_idx: dict[str, int] = {itm.uid: idx for idx, itm in enumerate(todos)}
if uid not in item_idx:
raise HomeAssistantError(
"Item '{uid}' not found in todo list {self.entity_id}"
)
if previous_uid and previous_uid not in item_idx:
raise HomeAssistantError(
"Item '{previous_uid}' not found in todo list {self.entity_id}"
)
dst_idx = item_idx[previous_uid] + 1 if previous_uid else 0
src_idx = item_idx[uid]
src_item = todos.pop(src_idx)
if dst_idx > src_idx:
dst_idx -= 1
todos.insert(dst_idx, src_item)
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_save(self) -> None:
@@ -1,6 +1,7 @@
"""Config flow for the integration."""
import asyncio
from collections.abc import Mapping
import logging
from typing import Any
@@ -41,17 +42,17 @@ class MadVRConfigFlow(ConfigFlow, domain=DOMAIN):
return await self._handle_config_step(user_input)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration of the device."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reconfigure_confirm(user_input)
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
return await self._handle_config_step(user_input, step_id="reconfigure")
return await self._handle_config_step(user_input, step_id="reconfigure_confirm")
async def _handle_config_step(
self, user_input: dict[str, Any] | None = None, step_id: str = "user"
+1 -1
View File
@@ -13,7 +13,7 @@
"port": "The port your madVR Envy is listening on. In 99% of cases, leave this as the default."
}
},
"reconfigure": {
"reconfigure_confirm": {
"title": "Reconfigure madVR Envy",
"description": "Your device needs to be on in order to reconfigure the integation.",
"data": {
@@ -8,6 +8,6 @@
"iot_class": "calculated",
"loggers": ["yt_dlp"],
"quality_scale": "internal",
"requirements": ["yt-dlp==2024.08.06"],
"requirements": ["yt-dlp==2024.09.27"],
"single_config_entry": true
}
@@ -59,6 +59,8 @@ async def async_setup_entry(
ATTR_SENSOR_UOM: entry.unit_of_measurement,
ATTR_SENSOR_ENTITY_CATEGORY: entry.entity_category,
}
if capabilities := entry.capabilities:
config[ATTR_SENSOR_STATE_CLASS] = capabilities.get(ATTR_SENSOR_STATE_CLASS)
entities.append(MobileAppSensor(config, config_entry))
async_add_entities(entities)
+4 -4
View File
@@ -71,15 +71,15 @@
},
"issues": {
"removed_lazy_error_count": {
"title": "`{config_key}` configuration key is being removed",
"title": "{config_key} configuration key is being removed",
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue. All errors will be reported, as lazy_error_count is accepted but ignored"
},
"deprecated_retries": {
"title": "`{config_key}` configuration key is being removed",
"title": "{config_key} configuration key is being removed",
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3."
},
"missing_modbus_name": {
"title": "Modbus entry with host `{sub_2}` missing name",
"title": "Modbus entry with host {sub_2} missing name",
"description": "Please add `{sub_1}` key to the {integration} entry with host `{sub_2}` in your configuration.yaml file and restart Home Assistant to fix this issue\n\n. `{sub_1}: {sub_3}` have been added."
},
"duplicate_modbus_entry": {
@@ -99,7 +99,7 @@
"description": "Please add at least one entity to Modbus {sub_1} in your configuration.yaml file and restart Home Assistant to fix this issue."
},
"deprecated_restart": {
"title": "`modbus.restart` is being removed",
"title": "modbus.restart is being removed",
"description": "Please use reload yaml via the developer tools in the UI instead of via the `modbus.restart` action."
}
}
@@ -12,6 +12,7 @@ from homeassistant.const import CONF_NAME, Platform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowError,
SchemaFlowFormStep,
)
from homeassistant.helpers.selector import (
@@ -33,18 +34,20 @@ from .const import (
)
async def validate_duplicate(
async def validate_input(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate already existing entry."""
handler.parent_handler._async_abort_entries_match({**handler.options, **user_input}) # noqa: SLF001
if user_input[CONF_CALIBRATION_FACTOR] == 0.0:
raise SchemaFlowError("calibration_is_zero")
return user_input
DATA_SCHEMA_OPTIONS = vol.Schema(
{
vol.Required(CONF_CALIBRATION_FACTOR): NumberSelector(
NumberSelectorConfig(min=0, step="any", mode=NumberSelectorMode.BOX)
NumberSelectorConfig(step=0.1, mode=NumberSelectorMode.BOX)
)
}
)
@@ -74,13 +77,13 @@ DATA_SCHEMA_CONFIG = vol.Schema(
CONFIG_FLOW = {
"user": SchemaFlowFormStep(
schema=DATA_SCHEMA_CONFIG,
validate_user_input=validate_duplicate,
validate_user_input=validate_input,
),
}
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(
DATA_SCHEMA_OPTIONS,
validate_user_input=validate_duplicate,
validate_user_input=validate_input,
)
}
@@ -90,6 +90,7 @@ async def async_setup_platform(
outdoor_temp_sensor,
indoor_humidity_sensor,
calib_factor,
None,
)
],
False,
@@ -118,6 +119,7 @@ async def async_setup_entry(
outdoor_temp_sensor,
indoor_humidity_sensor,
calib_factor,
entry.entry_id,
)
],
False,
@@ -141,10 +143,12 @@ class MoldIndicator(SensorEntity):
outdoor_temp_sensor: str,
indoor_humidity_sensor: str,
calib_factor: float,
unique_id: str | None,
) -> None:
"""Initialize the sensor."""
self._state: str | None = None
self._attr_name = name
self._attr_unique_id = unique_id
self._indoor_temp_sensor = indoor_temp_sensor
self._indoor_humidity_sensor = indoor_humidity_sensor
self._outdoor_temp_sensor = outdoor_temp_sensor
@@ -3,6 +3,9 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"calibration_is_zero": "Calibration factor can't be zero."
},
"step": {
"user": {
"description": "Add Mold indicator helper",
@@ -27,6 +30,9 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"calibration_is_zero": "Calibration factor can't be zero."
},
"step": {
"init": {
"description": "Adjust the calibration factor as required",
@@ -2,7 +2,7 @@
import asyncio
from dataclasses import dataclass
from datetime import timedelta
from datetime import datetime, timedelta
from aiohttp import ClientResponseError
from gql.transport.exceptions import TransportServerError
@@ -63,9 +63,13 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]):
async def _async_update_data(self) -> MonarchData:
"""Fetch data for all accounts."""
now = datetime.now()
account_data, cashflow_summary = await asyncio.gather(
self.client.get_accounts_as_dict_with_id_key(),
self.client.get_cashflow_summary(),
self.client.get_cashflow_summary(
start_date=f"{now.year}-01-01", end_date=f"{now.year}-12-31"
),
)
return MonarchData(account_data=account_data, cashflow_summary=cashflow_summary)
+1 -2
View File
@@ -393,8 +393,7 @@ async def async_start( # noqa: C901
if (
result
and result["type"] == FlowResultType.ABORT
and result["reason"]
in ("already_configured", "single_instance_allowed")
and result["reason"] == "single_instance_allowed"
):
integration_unsubscribe.pop(key)()
+6 -2
View File
@@ -260,14 +260,18 @@ class MqttSensor(MqttEntity, RestoreSensor):
msg.topic,
)
return
if payload == PAYLOAD_NONE:
self._attr_native_value = None
return
if self._numeric_state_expected:
if payload == "":
_LOGGER.debug("Ignore empty state from '%s'", msg.topic)
elif payload == PAYLOAD_NONE:
self._attr_native_value = None
else:
self._attr_native_value = payload
return
if self.options and payload not in self.options:
_LOGGER.warning(
"Ignoring invalid option received on topic '%s', got '%s', allowed: %s",
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/ness_alarm",
"iot_class": "local_push",
"loggers": ["nessclient"],
"requirements": ["nessclient==1.0.0"]
"requirements": ["nessclient==1.1.2"]
}
@@ -86,3 +86,21 @@ async def async_remove_config_entry_device(
if zone_id in dev_ids:
return False
return True
async def async_migrate_entry(hass: HomeAssistant, entry: NexiaConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
_LOGGER.debug("Migration successful")
return True
@@ -81,6 +81,7 @@ class NexiaConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Nexia."""
VERSION = 1
MINOR_VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -99,7 +100,7 @@ class NexiaConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "unknown"
if "base" not in errors:
await self.async_set_unique_id(info["house_id"])
await self.async_set_unique_id(str(info["house_id"]))
self._abort_if_unique_id_configured()
return self.async_create_entry(title=info["title"], data=user_input)
+1 -1
View File
@@ -74,7 +74,7 @@
}
},
"migrate_notify_service": {
"title": "Legacy action `notify.{service_name}` stll being used",
"title": "Legacy action notify.{service_name} stll being used",
"fix_flow": {
"step": {
"confirm": {
@@ -9,7 +9,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .const import DOMAIN, LOGGER
class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
@@ -30,6 +30,7 @@ class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
except NYTGamesError:
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected error")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(str(user_id))
@@ -22,7 +22,7 @@ class NYTGamesData:
"""Class for NYT Games data."""
wordle: Wordle
spelling_bee: SpellingBee
spelling_bee: SpellingBee | None
connections: Connections
@@ -26,7 +26,7 @@
"default": "mdi:table-large"
},
"last_played": {
"default": "mdi:beehive-outline"
"default": "mdi:calendar"
}
}
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nyt_games",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["nyt_games==0.4.0"]
"requirements": ["nyt_games==0.4.2"]
}
+6 -4
View File
@@ -156,10 +156,11 @@ async def async_setup_entry(
entities: list[SensorEntity] = [
NYTGamesWordleSensor(coordinator, description) for description in WORDLE_SENSORS
]
entities.extend(
NYTGamesSpellingBeeSensor(coordinator, description)
for description in SPELLING_BEE_SENSORS
)
if coordinator.data.spelling_bee is not None:
entities.extend(
NYTGamesSpellingBeeSensor(coordinator, description)
for description in SPELLING_BEE_SENSORS
)
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
@@ -211,6 +212,7 @@ class NYTGamesSpellingBeeSensor(SpellingBeeEntity, SensorEntity):
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
assert self.coordinator.data.spelling_bee is not None
return self.entity_description.value_fn(self.coordinator.data.spelling_bee)
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
"iot_class": "local_push",
"loggers": ["pyotgw"],
"requirements": ["pyotgw==2.2.0"]
"requirements": ["pyotgw==2.2.1"]
}
+10 -4
View File
@@ -81,8 +81,14 @@ class OverkizExecutor:
return None
async def async_execute_command(self, command_name: str, *args: Any) -> None:
"""Execute device command in async context."""
async def async_execute_command(
self, command_name: str, *args: Any, refresh_afterwards: bool = True
) -> None:
"""Execute device command in async context.
:param refresh_afterwards: Whether to refresh the device state after the command is executed.
If several commands are executed, it will be refreshed only once.
"""
parameters = [arg for arg in args if arg is not None]
# Set the execution duration to 0 seconds for RTS devices on supported commands
# Default execution duration is 30 seconds and will block consecutive commands
@@ -107,8 +113,8 @@ class OverkizExecutor:
"device_url": self.device.device_url,
"command_name": command_name,
}
await self.coordinator.async_refresh()
if refresh_afterwards:
await self.coordinator.async_refresh()
async def async_cancel_command(
self, commands_to_cancel: list[OverkizCommand]
@@ -97,9 +97,9 @@ class AtlanticDomesticHotWaterProductionMBLComponent(OverkizEntity, WaterHeaterE
@property
def is_away_mode_on(self) -> bool:
"""Return true if away mode is on."""
return (
self.executor.select_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE)
== OverkizCommandParam.ON
return self.executor.select_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE) in (
OverkizCommandParam.ON,
OverkizCommandParam.PROG,
)
@property
@@ -151,10 +151,40 @@ class AtlanticDomesticHotWaterProductionMBLComponent(OverkizEntity, WaterHeaterE
await self.async_turn_away_mode_on()
async def async_turn_away_mode_on(self) -> None:
"""Turn away mode on."""
await self.executor.async_execute_command(
OverkizCommand.SET_ABSENCE_MODE, OverkizCommandParam.ON
"""Turn away mode on.
This requires the start date and the end date to be also set.
The API accepts setting dates in the format of the core:DateTimeState state for the DHW
{'day': 11, 'hour': 21, 'minute': 12, 'month': 7, 'second': 53, 'weekday': 3, 'year': 2024})
The dict is then passed as an away mode start date, and then as an end date, but with the year incremented by 1,
so the away mode is getting turned on for the next year.
The weekday number seems to have no effect so the calculation of the future date's weekday number is redundant,
but possible via homeassistant dt_util to form both start and end dates dictionaries from scratch
based on datetime.now() and datetime.timedelta into the future.
If you execute `setAbsenceStartDate`, `setAbsenceEndDate` and `setAbsenceMode`,
the API answers with "too many requests", as there's a polling update after each command execution,
and the device becomes unavailable until the API is available again.
With `refresh_afterwards=False` on the first commands, and `refresh_afterwards=True` only the last command,
the API is not choking and the transition is smooth without the unavailability state.
"""
now_date = cast(
dict,
self.executor.select_state(OverkizState.CORE_DATETIME),
)
await self.executor.async_execute_command(
OverkizCommand.SET_ABSENCE_MODE,
OverkizCommandParam.PROG,
refresh_afterwards=False,
)
await self.executor.async_execute_command(
OverkizCommand.SET_ABSENCE_START_DATE, now_date, refresh_afterwards=False
)
now_date["year"] = now_date["year"] + 1
await self.executor.async_execute_command(
OverkizCommand.SET_ABSENCE_END_DATE, now_date, refresh_afterwards=False
)
await self.coordinator.async_refresh()
async def async_turn_away_mode_off(self) -> None:
"""Turn away mode off."""
@@ -4,5 +4,6 @@
"codeowners": ["@home-assistant/frontend"],
"dependencies": ["frontend"],
"documentation": "https://www.home-assistant.io/integrations/panel_custom",
"integration_type": "system",
"quality_scale": "internal"
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/prometheus",
"iot_class": "assumed_state",
"loggers": ["prometheus_client"],
"requirements": ["prometheus-client==0.17.1"]
"requirements": ["prometheus-client==0.21.0"]
}
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/python_script",
"loggers": ["RestrictedPython"],
"quality_scale": "internal",
"requirements": ["RestrictedPython==7.2"]
"requirements": ["RestrictedPython==7.3"]
}
+6 -3
View File
@@ -570,9 +570,11 @@ class Recorder(threading.Thread):
)
@callback
def async_clear_statistics(self, statistic_ids: list[str]) -> None:
def async_clear_statistics(
self, statistic_ids: list[str], *, on_done: Callable[[], None] | None = None
) -> None:
"""Clear statistics for a list of statistic_ids."""
self.queue_task(ClearStatisticsTask(statistic_ids))
self.queue_task(ClearStatisticsTask(on_done, statistic_ids))
@callback
def async_update_statistics_metadata(
@@ -581,11 +583,12 @@ class Recorder(threading.Thread):
*,
new_statistic_id: str | UndefinedType = UNDEFINED,
new_unit_of_measurement: str | None | UndefinedType = UNDEFINED,
on_done: Callable[[], None] | None = None,
) -> None:
"""Update statistics metadata for a statistic_id."""
self.queue_task(
UpdateStatisticsMetadataTask(
statistic_id, new_statistic_id, new_unit_of_measurement
on_done, statistic_id, new_statistic_id, new_unit_of_measurement
)
)
@@ -375,9 +375,8 @@ class EventData(Base):
event: Event, dialect: SupportedDialect | None
) -> bytes:
"""Create shared_data from an event."""
if dialect == SupportedDialect.POSTGRESQL:
bytes_result = json_bytes_strip_null(event.data)
bytes_result = json_bytes(event.data)
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
bytes_result = encoder(event.data)
if len(bytes_result) > MAX_EVENT_DATA_BYTES:
_LOGGER.warning(
"Event data for %s exceed maximum size of %s bytes. "
@@ -60,17 +60,21 @@ class ChangeStatisticsUnitTask(RecorderTask):
class ClearStatisticsTask(RecorderTask):
"""Object to store statistics_ids which for which to remove statistics."""
on_done: Callable[[], None] | None
statistic_ids: list[str]
def run(self, instance: Recorder) -> None:
"""Handle the task."""
statistics.clear_statistics(instance, self.statistic_ids)
if self.on_done:
self.on_done()
@dataclass(slots=True)
class UpdateStatisticsMetadataTask(RecorderTask):
"""Object to store statistics_id and unit for update of statistics metadata."""
on_done: Callable[[], None] | None
statistic_id: str
new_statistic_id: str | None | UndefinedType
new_unit_of_measurement: str | None | UndefinedType
@@ -83,6 +87,8 @@ class UpdateStatisticsMetadataTask(RecorderTask):
self.new_statistic_id,
self.new_unit_of_measurement,
)
if self.on_done:
self.on_done()
@dataclass(slots=True)
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from datetime import datetime as dt
from typing import Any, Literal, cast
@@ -48,6 +49,9 @@ from .statistics import (
)
from .util import PERIOD_SCHEMA, get_instance, resolve_period
CLEAR_STATISTICS_TIME_OUT = 10
UPDATE_STATISTICS_METADATA_TIME_OUT = 10
UNIT_SCHEMA = vol.Schema(
{
vol.Optional("conductivity"): vol.In(ConductivityConverter.VALID_UNITS),
@@ -319,8 +323,8 @@ async def ws_update_statistics_issues(
vol.Required("statistic_ids"): [str],
}
)
@callback
def ws_clear_statistics(
@websocket_api.async_response
async def ws_clear_statistics(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Clear statistics for a list of statistic_ids.
@@ -328,7 +332,23 @@ def ws_clear_statistics(
Note: The WS call posts a job to the recorder's queue and then returns, it doesn't
wait until the job is completed.
"""
get_instance(hass).async_clear_statistics(msg["statistic_ids"])
done_event = asyncio.Event()
def clear_statistics_done() -> None:
hass.loop.call_soon_threadsafe(done_event.set)
get_instance(hass).async_clear_statistics(
msg["statistic_ids"], on_done=clear_statistics_done
)
try:
async with asyncio.timeout(CLEAR_STATISTICS_TIME_OUT):
await done_event.wait()
except TimeoutError:
connection.send_error(
msg["id"], websocket_api.ERR_TIMEOUT, "clear_statistics timed out"
)
return
connection.send_result(msg["id"])
@@ -357,17 +377,33 @@ async def ws_get_statistics_metadata(
vol.Required("unit_of_measurement"): vol.Any(str, None),
}
)
@callback
def ws_update_statistics_metadata(
@websocket_api.async_response
async def ws_update_statistics_metadata(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Update statistics metadata for a statistic_id.
Only the normalized unit of measurement can be updated.
"""
done_event = asyncio.Event()
def update_statistics_metadata_done() -> None:
hass.loop.call_soon_threadsafe(done_event.set)
get_instance(hass).async_update_statistics_metadata(
msg["statistic_id"], new_unit_of_measurement=msg["unit_of_measurement"]
msg["statistic_id"],
new_unit_of_measurement=msg["unit_of_measurement"],
on_done=update_statistics_metadata_done,
)
try:
async with asyncio.timeout(UPDATE_STATISTICS_METADATA_TIME_OUT):
await done_event.wait()
except TimeoutError:
connection.send_error(
msg["id"], websocket_api.ERR_TIMEOUT, "update_statistics_metadata timed out"
)
return
connection.send_result(msg["id"])
@@ -155,6 +155,11 @@ class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity):
configuration_url=self._conf_url,
)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available and self._host.api.camera_online(self._channel)
async def async_added_to_hass(self) -> None:
"""Entity created."""
await super().async_added_to_hass()
+1 -1
View File
@@ -120,7 +120,7 @@
},
"issues": {
"deprecated_entity": {
"title": "Detected deprecated `{platform}` entity usage",
"title": "Detected deprecated {platform} entity usage",
"description": "We detected that entity `{entity}` is being used in `{info}`\n\nWe have created a new `{new_platform}` entity and you should migrate `{info}` to use this new entity.\n\nWhen you are done migrating `{info}` and are ready to have the deprecated `{entity}` entity removed, disable the entity and restart Home Assistant."
}
}
+1 -1
View File
@@ -148,6 +148,6 @@ class RoborockCurrentMapSelectEntity(RoborockCoordinatedEntityV1, SelectEntity):
@property
def current_option(self) -> str | None:
"""Get the current status of the select entity from device_status."""
if current_map := self.coordinator.current_map:
if (current_map := self.coordinator.current_map) is not None:
return self.coordinator.maps[current_map].name
return None
@@ -41,7 +41,9 @@ DEFAULT_OPTIONS = {CONF_CONTINUOUS: DEFAULT_CONTINUOUS, CONF_DELAY: DEFAULT_DELA
MAX_NUM_DEVICES_TO_DISCOVER = 25
AUTH_HELP_URL_KEY = "auth_help_url"
AUTH_HELP_URL_VALUE = "https://www.home-assistant.io/integrations/roomba/#manually-retrieving-your-credentials"
AUTH_HELP_URL_VALUE = (
"https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials"
)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
@@ -249,7 +249,7 @@ async def _async_create_bridge_with_updated_data(
updated_data[CONF_MODEL] = model
if model_requires_encryption(model) and method != METHOD_ENCRYPTED_WEBSOCKET:
LOGGER.warning(
LOGGER.debug(
(
"Detected model %s for %s. Some televisions from H and J series use "
"an encrypted protocol but you are using %s which may not be supported"
+16 -9
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable, Iterable
from contextlib import suppress
import datetime
from functools import partial
import itertools
@@ -179,6 +180,14 @@ def _entity_history_to_float_and_state(
return float_states
def _is_numeric(state: State) -> bool:
"""Return if the state is numeric."""
with suppress(ValueError, TypeError):
if (num_state := float(state.state)) is not None and math.isfinite(num_state):
return True
return False
def _normalize_states(
hass: HomeAssistant,
old_metadatas: dict[str, tuple[int, StatisticMetaData]],
@@ -684,29 +693,27 @@ def _update_issues(
"""Update repair issues."""
for state in sensor_states:
entity_id = state.entity_id
numeric = _is_numeric(state)
state_class = try_parse_enum(
SensorStateClass, state.attributes.get(ATTR_STATE_CLASS)
)
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if metadata := metadatas.get(entity_id):
if state_class is None:
if numeric and state_class is None:
# Sensor no longer has a valid state class
report_issue(
"unsupported_state_class",
"state_class_removed",
entity_id,
{
"statistic_id": entity_id,
"state_class": state_class,
},
{"statistic_id": entity_id},
)
else:
clear_issue("unsupported_state_class", entity_id)
clear_issue("state_class_removed", entity_id)
metadata_unit = metadata[1]["unit_of_measurement"]
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
if not converter:
if not _equivalent_units({state_unit, metadata_unit}):
if numeric and not _equivalent_units({state_unit, metadata_unit}):
# The unit has changed, and it's not possible to convert
report_issue(
"units_changed",
@@ -720,7 +727,7 @@ def _update_issues(
)
else:
clear_issue("units_changed", entity_id)
elif state_unit not in converter.VALID_UNITS:
elif numeric and state_unit not in converter.VALID_UNITS:
# The state unit can't be converted to the unit in metadata
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
valid_units_str = ", ".join(sorted(valid_units))
+4 -4
View File
@@ -289,12 +289,12 @@
}
},
"issues": {
"units_changed": {
"title": "The unit of {statistic_id} has changed",
"state_class_removed": {
"title": "{statistic_id} no longer has a state class",
"description": ""
},
"unsupported_state_class": {
"title": "The state class of {statistic_id} is not supported",
"units_changed": {
"title": "The unit of {statistic_id} has changed",
"description": ""
}
}
@@ -42,8 +42,8 @@ async def async_create_fix_flow(
hass: HomeAssistant, issue_id: str, data: dict
) -> RepairsFlow:
"""Create flow."""
if issue_id.startswith("deprecate_sensor_"):
entry = hass.config_entries.async_get_entry(data["entry_id"])
assert entry
if issue_id.startswith("deprecate_sensor_") and (
entry := hass.config_entries.async_get_entry(data["entry_id"])
):
return SensorDeprecationRepairFlow(entry)
return ConfirmRepairFlow()
@@ -89,7 +89,7 @@ def setup_services(hass: HomeAssistant) -> None:
ATTR_TRACKING_NUMBER: package.tracking_number,
ATTR_LOCATION: package.location,
ATTR_STATUS: package.status,
ATTR_TIMESTAMP: package.timestamp,
ATTR_TIMESTAMP: package.timestamp.isoformat(),
ATTR_INFO_TEXT: package.info_text,
ATTR_FRIENDLY_NAME: package.friendly_name,
}
@@ -138,40 +138,47 @@ class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
if TYPE_CHECKING:
assert entry is not None
assert self._entry is not None
if user_input is not None:
if not user_input[CONF_HAS_PWD] or user_input.get(CONF_PASSWORD, "") == "":
user_input[CONF_PASSWORD] = ""
user_input[CONF_HAS_PWD] = False
return self.async_update_reload_and_abort(
entry,
self._entry,
reason="reconfigure_successful",
data={**entry.data, **user_input},
data={**self._entry.data, **user_input},
)
if await self._test_extended_data(
entry.data[CONF_HOST], user_input.get(CONF_PASSWORD, "")
self._entry.data[CONF_HOST], user_input.get(CONF_PASSWORD, "")
):
# if password has been provided, only save if extended data is available
return self.async_update_reload_and_abort(
entry,
self._entry,
reason="reconfigure_successful",
data={**entry.data, **user_input},
data={**self._entry.data, **user_input},
)
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=vol.Schema(
{
vol.Optional(CONF_HAS_PWD, default=entry.data[CONF_HAS_PWD]): bool,
vol.Optional(
CONF_HAS_PWD, default=self._entry.data[CONF_HAS_PWD]
): bool,
vol.Optional(CONF_PASSWORD): str,
}
),
@@ -29,7 +29,7 @@
"password": "[%key:common::config_flow::data::password%]"
}
},
"reconfigure": {
"reconfigure_confirm": {
"title": "Configure SolarLog",
"data": {
"has_password": "[%key:component::solarlog::config::step::user::data::has_password%]"
@@ -12,5 +12,5 @@
"documentation": "https://www.home-assistant.io/integrations/squeezebox",
"iot_class": "local_polling",
"loggers": ["pysqueezebox"],
"requirements": ["pysqueezebox==0.9.2"]
"requirements": ["pysqueezebox==0.9.3"]
}
@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
"iot_class": "local_polling",
"loggers": ["synology_dsm"],
"requirements": ["py-synologydsm-api==2.5.2"],
"requirements": ["py-synologydsm-api==2.5.3"],
"ssdp": [
{
"manufacturer": "Synology",
+17 -42
View File
@@ -1,9 +1,7 @@
"""Support for the (unofficial) Tado API."""
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import Any
import requests.exceptions
@@ -22,9 +20,6 @@ from .const import (
CONST_OVERLAY_TADO_MODE,
CONST_OVERLAY_TADO_OPTIONS,
DOMAIN,
UPDATE_LISTENER,
UPDATE_MOBILE_DEVICE_TRACK,
UPDATE_TRACK,
)
from .services import setup_services
from .tado_connector import TadoConnector
@@ -55,17 +50,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
type TadoConfigEntry = ConfigEntry[TadoRuntimeData]
@dataclass
class TadoRuntimeData:
"""Dataclass for Tado runtime data."""
tadoconnector: TadoConnector
update_track: Any
update_mobile_device_track: Any
update_listener: Any
type TadoConfigEntry = ConfigEntry[TadoConnector]
async def async_setup_entry(hass: HomeAssistant, entry: TadoConfigEntry) -> bool:
@@ -99,26 +84,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: TadoConfigEntry) -> bool
await hass.async_add_executor_job(tadoconnector.update)
# Poll for updates in the background
update_track = async_track_time_interval(
hass,
lambda now: tadoconnector.update(),
SCAN_INTERVAL,
entry.async_on_unload(
async_track_time_interval(
hass,
lambda now: tadoconnector.update(),
SCAN_INTERVAL,
)
)
update_mobile_devices = async_track_time_interval(
hass,
lambda now: tadoconnector.update_mobile_devices(),
SCAN_MOBILE_DEVICE_INTERVAL,
entry.async_on_unload(
async_track_time_interval(
hass,
lambda now: tadoconnector.update_mobile_devices(),
SCAN_MOBILE_DEVICE_INTERVAL,
)
)
update_listener = entry.add_update_listener(_async_update_listener)
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
entry.runtime_data = TadoRuntimeData(
tadoconnector=tadoconnector,
update_track=update_track,
update_mobile_device_track=update_mobile_devices,
update_listener=update_listener,
)
entry.runtime_data = tadoconnector
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -147,15 +131,6 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> Non
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: TadoConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN][entry.entry_id][UPDATE_TRACK]()
hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER]()
hass.data[DOMAIN][entry.entry_id][UPDATE_MOBILE_DEVICE_TRACK]()
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@@ -121,7 +121,7 @@ async def async_setup_entry(
) -> None:
"""Set up the Tado sensor platform."""
tado: TadoConnector = entry.runtime_data.tadoconnector
tado = entry.runtime_data
devices = tado.devices
zones = tado.zones
entities: list[BinarySensorEntity] = []
+1 -1
View File
@@ -105,7 +105,7 @@ async def async_setup_entry(
) -> None:
"""Set up the Tado climate platform."""
tado: TadoConnector = entry.runtime_data.tadoconnector
tado = entry.runtime_data
entities = await hass.async_add_executor_job(_generate_entities, tado)
platform = entity_platform.async_get_current_platform()
-4
View File
@@ -38,8 +38,6 @@ TADO_HVAC_ACTION_TO_HA_HVAC_ACTION = {
CONF_FALLBACK = "fallback"
CONF_HOME_ID = "home_id"
DATA = "data"
UPDATE_TRACK = "update_track"
UPDATE_MOBILE_DEVICE_TRACK = "update_mobile_device_track"
# Weather
CONDITIONS_MAP = {
@@ -207,8 +205,6 @@ DEFAULT_NAME = "Tado"
TADO_HOME = "Home"
TADO_ZONE = "Zone"
UPDATE_LISTENER = "update_listener"
# Constants for Temperature Offset
INSIDE_TEMPERATURE_MEASUREMENT = "INSIDE_TEMPERATURE_MEASUREMENT"
TEMP_OFFSET = "temperatureOffset"

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