Compare commits

...

371 Commits

Author SHA1 Message Date
Erik
0e00e15fa5 Update snapshots 2025-05-23 11:14:53 +02:00
Erik
3e19b5f6d5 Handle current entity id matches the automatic entity id 2025-05-23 11:14:22 +02:00
Erik
3f1d3bfc4a Update folder_watcher test 2025-05-23 11:14:20 +02:00
Erik
08a8bfaf8f Adjust test 2025-05-23 11:13:27 +02:00
Erik
31f8ea523f Fix logic and add additional tests 2025-05-23 11:13:26 +02:00
Erik
0924740cb4 Calculate suggested object id from entity properties 2025-05-23 11:13:26 +02:00
Erik
9f039002ff Add WS command to help reset custom entity_id 2025-05-12 08:10:43 +02:00
Manu
77e9142722 Increase test coverage for ntfy integration (#144701)
Increase test coverage
2025-05-11 22:25:43 -07:00
Allen Porter
943998e57e Bump voluptuous-openapi to 0.1.0 (#144703) 2025-05-11 21:01:20 -04:00
starkillerOG
58802b71c4 Bump reolink_aio to 0.13.3 (#144583) 2025-05-11 23:15:30 +01:00
Manu
ca89aa7a94 Sort list items alphabetically in Bring integration (#144700) 2025-05-11 22:42:02 +02:00
Paulus Schoutsen
4faa920318 Move Assist Pipeline tests to right file (#144696) 2025-05-11 21:38:21 +02:00
peteS-UK
b394c07a3d Override available property in button platform for Squeezebox (#144693) 2025-05-11 21:15:12 +02:00
Ruben van Dijk
554cb27703 Close Octoprint aiohttp session on unload (#144670) 2025-05-11 21:06:04 +02:00
Åke Strandberg
80a04314fc Add program phases for Miele washer-dryer (#144664) 2025-05-11 21:05:43 +02:00
Simone Chemelli
6516cd388f Avoid closing shared session for Comelit (#144682) 2025-05-11 21:00:21 +02:00
Åke Strandberg
4f6141581e Bump dependency pymiele to 0.5.1 (#144688) 2025-05-11 20:59:23 +02:00
Norbert Rittel
597c386bc2 Fix missing sentence-casing in alarmdecoder (#144690) 2025-05-11 20:58:13 +02:00
Seweryn Zeman
494c7aa3da Removed unused file_id param from open_ai_conversation request (#143878) 2025-05-11 20:33:17 +02:00
Norbert Rittel
8840970d64 Add missing hyphen to "WebSocket-based" in mqtt (#144686)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-05-11 20:30:19 +02:00
Simone Chemelli
867624fc59 Take into account coordinator availability for SamsungTV (#144545) 2025-05-11 19:38:53 +02:00
Norbert Rittel
ea4120a7d4 Add missing hyphens to "condition-based" and "pre-entry" in bmw_connected_drive (#144685) 2025-05-11 17:44:27 +02:00
Erik Montnemery
158bbf1f52 Remove unused constant from entity_platform tests (#144601) 2025-05-11 17:33:09 +02:00
Andrea Turri
61f8970aca Fix typos in Miele device names to match enum (#144609) 2025-05-11 17:26:02 +02:00
peteS-UK
6f41fbeb22 Add PARALLEL_UPDATES to Squeezebox (#144618) 2025-05-11 17:21:01 +02:00
Josef Zweck
a540c62594 Bump pylamarzocco to 2.0.2 (#144635)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-05-11 17:07:33 +02:00
Simone Chemelli
3e6a216806 Fix strings typo for Comelit (#144672) 2025-05-11 17:01:51 +02:00
G Johansson
85535b2cbd Bump holidays to 0.72 (#144671) 2025-05-11 17:00:44 +02:00
Norbert Rittel
05796dcd51 Fix grammar in description of unifi.remove_clients action (#144632) 2025-05-11 14:53:42 +03:00
Norbert Rittel
40e2c7b9b7 Improve user-facing strings of plaato (#144633)
- make all references to "auth token" consistent, using sentence-casing
- remove "Paste … here" so the description correctly refers to the field name 'Auth token'
- make the clickable URL text longer by using "these instructions" instead of just "these"
- slightly reword using "If you prefer to …"
- add the missing hyphen to "built-in"
2025-05-11 14:53:21 +03:00
Arie Catsman
d0fe7de501 bump pyenphase to 1.26.1 (#144641) 2025-05-11 14:07:35 +03:00
Norbert Rittel
0dadd31221 Add missing hyphen to "volume-weighted" in kraken (#144636)
Also fix sentence-casing in one string.
2025-05-11 13:58:55 +03:00
Norbert Rittel
09515bf174 Add missing hyphen to "time-weighted" in derivative (#144637) 2025-05-11 13:58:04 +03:00
Norbert Rittel
773a2a9db6 Add missing hyphen to "time-based" in integration (#144638) 2025-05-11 13:57:44 +03:00
Norbert Rittel
31a576b206 Add missing hyphen to "time-based" in filter (#144639)
Fix spelling of "time-based" in `filter`

Also sentence-case the complete string.
2025-05-11 13:57:28 +03:00
Simon Lamon
58161b5fa2 Bump python-linkplay to v0.2.5 (#144666)
Bump linkplay to 0.2.5
2025-05-11 12:56:40 +02:00
Norbert Rittel
ebb61caa53 Add missing hyphen to "file-based" in file (#144640) 2025-05-11 13:20:30 +03:00
Manu
54a7691a80 Fix typo in ntfy integration (#144650)
fix typo in ntfy integratrion
2025-05-11 11:14:10 +03:00
Norbert Rittel
996839cb67 Fix sentence-casing and spelling of "SIA-based" in sia (#144659)
Fix sentence-casing and spelling of `SIA-based` in `sia`
2025-05-11 11:12:35 +03:00
Marc Mueller
e065f1e097 Update pylint to 3.3.7 + astroid to 3.3.10 (#144630)
* Update pylint to 3.3.7 + astroid to 3.3.10

* Remove unnecessary pylint disable comment
2025-05-11 07:06:42 +02:00
Allen Porter
882565a8e5 Bump ical to 9.2.1 (#144642) 2025-05-10 23:59:01 -04:00
Marc Mueller
4501303beb Fix licenses check for jaraco.itertools (#144631) 2025-05-10 22:11:32 +02:00
Arie Catsman
1416580f8b fix enphase_envoy diagnostics home endpoint name (#144634) 2025-05-10 13:23:52 -05:00
wittypluck
5e58032745 Add Codeowner to OpenWeatherMap (#144605) 2025-05-10 10:54:11 +02:00
J. Diego Rodríguez Royo
86cf01a901 Delete deprecated program switches from Home Connect (#144606) 2025-05-10 10:53:16 +02:00
Erik Montnemery
45c0a19a68 Fix squeezebox test serializing mocks (#144600) 2025-05-10 09:44:55 +02:00
Ludovic BOUÉ
35ab2a21d6 Matter Oven fixture (#144603)
* Create cooktop.json

* Update conftest.py

* Fix format

* Add snapshots

* Add snapshots

* Oven fixture

* Oven fixture

* Add snapshot
2025-05-10 09:43:40 +02:00
Retha Runolfsson
977d2fe8b3 Add switchbot vacuum support (#144550)
* add support for vacuum

* add vacuum unit test
2025-05-10 09:34:51 +02:00
Peter Åslund
626f8a9166 Add codeowner to Adax (#144587)
* Add codeowner to Adax

* Reformatted manifest file
2025-05-10 00:54:36 +02:00
tdfountain
1654249dab Use strict typing for ConfigEntry on remove in NUT (#144588) 2025-05-09 17:20:03 -05:00
J. Nick Koston
5fadc56475 Mark inkbird coordinator as not needing connectable (#144584) 2025-05-09 17:19:00 -05:00
Erwin Douna
2bce697aa7 SMA add snapshots & tests (#144555)
* Refactor the sensor test to use snapshots

* Review feedback

* Remove leftover
2025-05-09 22:55:08 +02:00
Norbert Rittel
970edbed40 Sentence-case names and remove "True/False" in emulated_roku setup (#144579)
Sentence-case names and remove "True/False" in `emulated_roku`setup

As a binary field is shown as an on/off toggle in the UI there is no need to  include "(True/False)" in the field label.
2025-05-09 22:06:07 +03:00
Norbert Rittel
131ba3cdef Fix sentence-casing in config fields of aurora_abb_powerone (#144577)
* Fix sentence-casing in data field names of `aurora_abb_powerone`

* Add suggestion from review.

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 20:12:32 +02:00
Åke Strandberg
85f1c89808 Fix sensor setup during dynamic addition of Miele devices (#144551)
Fix sensors when dynamic addition of devices
2025-05-09 20:07:23 +02:00
J. Nick Koston
2940cb0fa0 Bump aiodiscover to 2.7.0 (#144571) 2025-05-09 11:59:38 -05:00
Markus Adrario
ba8d40f7d3 Add homee fan platform (#143524)
* Initial fan

* add more tests

* add last fan tests and small fixes

* fix tests after latest change

* another small correction

* use common strings

* add snapshot test

* fix review comments

* fix typing

* remove uneeded None

* remove unwanted file

* fix turn_on function

* typo

* Use constants for preset modes.

* fix  review notes.
2025-05-09 18:51:57 +02:00
Luke Lashley
cac0e0f6e8 Don't scale Roborock mop Path (#144421)
don't scale mop path
2025-05-09 18:50:55 +02:00
Ståle Storø Hauknes
ad7cfe49c8 Airthings DHCP discovery (#144280)
* Add DHCP to Airthings manifest

* Update manifest

* Update manifest

* Add tests

* Fix pr comments

* fix naming for all tests

* Fix pr comment
2025-05-09 18:49:22 +02:00
wittypluck
e29fc37bb1 Use device and entity name for OpenWeather map entities (#144513)
* Use entity name

* Update snapshot with expected chnages
2025-05-09 18:47:18 +02:00
starkillerOG
e892744328 Reolink fix privacy mode availability for NVR IPC cams (#144569)
* Correct "available" for IPC cams

* Check privacy mode when updating
2025-05-09 18:46:32 +02:00
Matrix
356775c19b Add water flowing status for YoLink water meter(YS5018). (#144535)
* Add water flowing status for YoLink water meter(YS5018).

* Fixes
2025-05-09 18:44:43 +02:00
Ludovic BOUÉ
87bd6e3ca0 Matter pump fixture (#144572)
* Create pump.json

* Add pump fixture

* Add snapshots
2025-05-09 18:40:56 +02:00
TimL
ad6f66c945 Allow dns hostnames to be retained for SMLIGHT user flow. (#142514)
* Dont overwrite host with local IP

* adjust test for user flow change
2025-05-09 18:31:00 +02:00
Ted van den Brink
9537229c92 Add status to whois (#141051)
* Add status to whois component

* Fix tests

* Added translations for statuses

* Convert status to enum

* Fix tests, add test for status sensor
2025-05-09 18:23:50 +02:00
Sanjay Govind
c18b6d736a Add switch platform to bosch alarm (#142157)
* add switch platform to bosch alarm

* fix tests

* one device per output

* add switch for door

* add switch entities for door

* fix switch devices

* apply changes from review

* update identifiers

* add missing entity

* use base entity for switch

* rename var

* fix icons

* give user a nice error if they try to lock or secure a door that is in the process of being cycled

* fix test

* Update homeassistant/components/bosch_alarm/switch.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/bosch_alarm/switch.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use service constants

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 18:17:26 +02:00
wittypluck
a7afeb078c Avoid split of unique id to build OpenWeatherMap sensors (#144546)
* Avoid split of unique id

* Assert that unique_id is not None
2025-05-09 17:14:02 +02:00
Manu
9a2f17c2b2 Refactor Bring! integration to poll activity data at a slower interval (#142621)
* Refactor Bring integration to poll activity with slower interval

* add test
2025-05-09 16:42:22 +02:00
Louis Christ
4cecb6c851 Replace custom actions for sleep timer with buttons in bluesound integration (#133604)
* Use entity services

* Add buttons for sleep timer

* Fix merge

* Replace hass.data with runtime_data from config_entries

* Disable button by default

* Remove duplicate dispatchers

* Add tests for buttons

* Fix merge commit

* Fix merge commit

* Update deprecation version

* Remove update_before_add

* Use entity_registry_enabled_by_default

* Use EnitiyDescriptions for buttons

* Update version for deprecate

* Use tranlation_key; Move default disable to EntityDescription

* Fix merge commit

* Fix callback type; fix breaks version

* Use normal issue

* Apply suggestions from code review

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 16:15:52 +02:00
Andre Lengwenus
b6c4b06fc7 Skip check for entry updated by current flow in _async_abort_entries_match (#141003)
* Ignore entries with source reconfigure in _async_abort_entries_match

* Exclude reconfigure and reauth entry from match check

* Add tests

* Fix tests for other components

* Revert unrelated changes

* Update docstring

* Make test more realistic

* Change name and docstring for sabnzbd test

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 16:15:17 +02:00
ichbinsteffen
3e0e807c96 Add control bus mode selector to Cambridge Audio (#139131)
* [CambridgeAudio Integration] Add switch to enable Control Bus Mode

* remove load_fn

* Add import for ControlBusMode

* Add strings for control_bus_mode

* Add icons for control_bus_mode

* Add test case for the select ControlBusMode.Amplifier

* Change the set of icons

* Fix the usage of the wrong property name

* Fix test

* Fix test 2

* add new snapshot

* fix test name

* Fix

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 15:53:37 +02:00
Renier Moorcroft
7dad6ebe67 Switch to PyEzvizApi (#135926)
* Update library

* update library

* Bump api to pin mqtt to compatable version

* fix after rebase

* Update code owners

* codeowners
2025-05-09 15:45:18 +02:00
Markus Adrario
75b8cb19cf Deprecate Homee valve sensor (#139578)
* remove valve sensor

* deprecate valve sensor

* fix

* Add deprecation issue test

* Add test for deleting disabled deprecated entities

* parametrize issue test

* eliminate one if iteration

* review change 1

* review change 2

* add info where to find valve

* Update homeassistant/components/homee/sensor.py

---------

Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 15:37:23 +02:00
Phill (pssc)
bd28452807 Add Squeezebox service update entities (#125764)
* first cut at update entties

* remove sensors for now

* make update vserion less wordy

* fix re escape

* Use name

* use Caps

* fix translation

* move all data manipulation to data prepare fn, refine regexes and provide as much info as possible

* fix formatting

* update return type

* fix class inherit

* Fix ruff

* update tests

* fix spelling

* ruff

* Update homeassistant/components/squeezebox/update.py

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>

* Update tests/components/squeezebox/test_update.py

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>

* Update tests/components/squeezebox/test_update.py

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>

* Update tests/components/squeezebox/test_update.py

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>

* Update tests/components/squeezebox/test_update.py

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>

* Update tests/components/squeezebox/test_update.py

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>

* fix tests

* ruff

* update text based on feedback from docs

* make the plugin update entity smarter

* update plugin updater tests

* define attr

* Callable type

* callable guard

* ruff

* add local release info page

* fix typing

* refactor use release notes for LMS update

* Make update simple and produce a release summary instead

* Update tests

* Fix tests

* Tighten english

* test for restart fail

* be more explicit with coordinator error

* remove unused regex

* revert error msg unrealted

* Fix newline

* Fix socket usage during tests

* Simplify based on new lib version

* CI Fixes

* fix typing

* fix enitiy call back

* fix enitiy call back types

* remove some unrelated titdying

---------

Co-authored-by: Raj Laud <50647620+rajlaud@users.noreply.github.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-05-09 15:35:10 +02:00
Guido Schmitz
ed6cfa42f0 Make all devolo Home Network conflig flow tests end correctly (#144378) 2025-05-09 15:33:13 +02:00
Robert Resch
763f2bcfcc Remove deprecated address argument in all lcn services (#144557) 2025-05-09 15:15:09 +02:00
agorecki
9e3684b001 Add Lux sensor to Airthings Cloud (#141035)
* change light to lux for airthings cloud

* Add back light sensor for airthings

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-09 15:11:22 +02:00
starkillerOG
93fd82d1fa Prevent errors during cleaning of connections/identifiers in device registry (#144558) 2025-05-09 14:50:00 +02:00
starkillerOG
9757009d8f Reolink clean device registry mac (#144554) 2025-05-09 14:47:38 +02:00
Robert Resch
920d281d45 Remove deprecated core set_time_zone function (#144559) 2025-05-09 14:40:23 +02:00
Robert Resch
d2bdc85a7b Remove deprecated async_forward_entry_setup function (#144560) 2025-05-09 14:34:55 +02:00
Robert Resch
1f84c5e1f1 Remove deprecated legacy WebRTC provider (#144547) 2025-05-09 13:50:38 +02:00
Åke Strandberg
a93bf3c150 Add vacuum platform to miele (#143757)
* Add vacuum platform

* Add comments

* Update snapshot

* Use class defined in pymiele

* Use device class transation

* Fix typo

* Cleanup consts

* Clean up activity property

* Address review comments

* Address review comments
2025-05-09 13:17:37 +02:00
Brett Adams
7bad07ac10 Add left & right temp request entities to Teslemetry (#144364)
Add left right
2025-05-09 13:16:54 +02:00
Simone Chemelli
af019144e5 Exempt entity categories for Comelit (#142858)
* Add entity categories to Comelit

* update snapshots

* revert EntityCategory changes

* update quality scale

* update tests
2025-05-09 13:15:35 +02:00
markhannon
e69b3ebf1e Add fan entity to Zimi integration (#144327)
* Import fan.py

* Align to light design

* Consistency for debug message

* ruff (post merge)

* Fixed stale docstring

* refactor init

* Combine aysnc_add_entities
Use _attr_speed_range instead of property

* Remove unused self._speed (and useless init)

* Refactor self._device to device in Entity init
2025-05-09 13:10:28 +02:00
Retha Runolfsson
4271d3f32f Add exception-translations for switchbot integration (#143444)
* add exception handler

* add unit test

* test exception per platform

* optimize unit tests

* update quality scale
2025-05-09 13:09:18 +02:00
Bram Kragten
d6e5fdceb7 Update frontend to 20250509.0 (#144549) 2025-05-09 13:09:05 +02:00
Robert Resch
c4ceb4759a Remove deprecated camera frontend_stream_type (#144539) 2025-05-09 13:08:34 +02:00
wittypluck
6350ed3415 Add snapshot tests for OpenWeatherMap sensors (#139657)
* Add snapshot tests for sensors

* Code cleanup

* Patch during async_setup only

* Use snapshot_platform, split platforms for snapshot tests

* Make mock_config_entry and mode fixtures

* Update snapshots with  latest device and state class changes

* Move setup_platform to __init__.py and patch HA object instead of library

* Remove if statements in tests

* Add client mock fixture to patch get_weather instead of internal call

* Code cleanup

* Test explicit list of modes

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-09 13:06:38 +02:00
wittypluck
031b25cd1e Remove redundant coordinator reference in OpenWeatherMap sensor (#144548)
Remove redundant coordinator reference
2025-05-09 12:44:36 +02:00
Erwin Douna
47455fee41 SMA add re-authentication flow (#144538)
* Add reauth flow

* Small adjustment

* Update homeassistant/components/sma/config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Review feedback

* Update tests/components/sma/test_config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update tests/components/sma/test_config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Feedback

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 12:16:49 +02:00
Åke Strandberg
9abb4ffc97 Add drying step sensor for Miele tumble dryers (#144515)
Add drying step sensor for tumple dryers
2025-05-09 11:52:30 +02:00
DukeChocula
90a7ecdce3 Add LAP-V102S-AUSR to VeSync (#144437)
Update const.py

Added LAP-V102S-AUSR to Vital 100S
2025-05-09 11:49:26 +02:00
Matthias Alphart
a84b8b49f3 Update knx-frontend to 2025.4.1.91934 - Enable UI to create KNX Cover entities (#141993)
Update knx-frontend to 2025.4.1.91934
2025-05-09 11:43:05 +02:00
Ludovic BOUÉ
ff6f213664 Matter refrigerator fixture (#144491)
* Add files via upload

* Add snapshots
2025-05-09 11:41:52 +02:00
Retha Runolfsson
e4b686bc43 Bump PySwitchbot to 0.62.0 (#144527)
bump pyswitchbot to 0.62.0
2025-05-09 11:40:56 +02:00
Matthias Alphart
307bb05653 Add support to create KNX Cover entities from UI (#141944)
* Add UI to create KNX Cover entities

* Use common constants source for UI and YAML config keys
2025-05-09 11:28:25 +02:00
epenet
b4ae08f83d Move hardware initialisation to package module (#144540) 2025-05-09 10:55:41 +02:00
Matthias Alphart
21e2bbd066 Ignore Fronius Gen24 firmware 1.35.4-1 SSL verification issue (#144463) 2025-05-09 10:39:00 +02:00
Josef Zweck
0d85cec770 Fix statistics coordinator subscription for lamarzocco (#144541) 2025-05-09 10:37:48 +02:00
Brett Adams
a1e6f596d7 Add common translation section to Teslemetry (#144361) 2025-05-09 10:04:43 +02:00
epenet
eab1d5717f Use HassKey in hardware (#144337) 2025-05-09 10:04:11 +02:00
Erik Montnemery
19b1dc8d65 Add backup tests showing that unknown files are not ciphered (#144529) 2025-05-09 09:56:07 +02:00
Erik Montnemery
2c8e33558e Catch and log unexpected backup ciphering errors (#144531) 2025-05-09 09:55:32 +02:00
dependabot[bot]
7287f302f6 Bump actions/dependency-review-action from 4.6.0 to 4.7.0 (#144532)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 09:51:56 +02:00
Åke Strandberg
1342dc142c Update test fixture for Miele dishwasher (#144537) 2025-05-09 09:50:32 +02:00
Tamer Wahba
96a8902365 fix homekit air purifier temperature sensor to convert unit (#144435) 2025-05-08 17:41:14 -05:00
Marc Mueller
d1b85cd452 Fix voip test RuntimeWarning (#144519) 2025-05-09 00:26:43 +02:00
Franck Nijhof
8977458e48 Merge branch 'master' into dev 2025-05-08 22:07:36 +00:00
Tsvi Mostovicz
a37f8b1f4e Jewish calendar entity translations (#144414)
* Move strings from entity descriptions to strings.json

* Use the original name values

* Fix casing

* Use "real" english names as well as transliterated names
2025-05-09 00:00:28 +02:00
Jesse Hills
bdf4a21976 Use async_release_notes in ESPHome update entity (#144440) 2025-05-08 23:52:43 +02:00
epenet
1322d54371 Use runtime_data in hive (#144367) 2025-05-08 23:47:24 +02:00
epenet
fbe63e8d03 Use runtime_data in hlk_sw16 (#144370) 2025-05-08 23:44:44 +02:00
epenet
6b2a4c975c Cleanup unused CONF_IP_ADDRESS from SamsungTV tests (#144379) 2025-05-08 23:44:06 +02:00
J. Nick Koston
b1392e1fc8 Bump aiodns to 3.4.0 (#144511) 2025-05-08 22:43:45 +02:00
Thomas55555
7100481abc Improve Husqvarna Automower tests (#143113) 2025-05-08 22:38:26 +02:00
DeerMaximum
4c43640d0d Bump pynina to 0.3.6 (#144494) 2025-05-08 22:36:22 +02:00
Erik Montnemery
42f53ff917 Don't encrypt or decrypt unknown files in backup archives (#144495) 2025-05-08 22:30:35 +02:00
Petar Petrov
e0fb612e82 Show warning message for Z-Wave devices in interview stage (#144483)
* Show warning message for devices in interview stage

* remove debug code
2025-05-08 16:21:43 -04:00
wittypluck
d13f9be9d8 Remove unused OpenWeatherMap const values (#144510) 2025-05-08 21:57:20 +02:00
J. Nick Koston
2396b1e73c Bump forecast-solar to 4.2.0 (#144502) 2025-05-08 21:55:03 +02:00
Michael
374b3ac6c6 Fix removing of smarthome templates on startup of AVM Fritz!SmartHome integration (#144506) 2025-05-08 21:54:49 +02:00
Norbert Rittel
5df09c4f13 Add missing hyphen to "single-board computers" in homekit (#144505) 2025-05-08 21:54:09 +02:00
Åke Strandberg
337c64d69d Add miele devices dynamically (#144216)
* Use device class transation

* WIP Cleanup tests

* First 3

* First 3

* Button

* Climate

* Light

* Switch

* New and cleaner variant

* Update homeassistant/components/miele/entity.py

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-05-08 21:20:02 +02:00
Joost Lekkerkerker
34dbd1fb10 Add hob support to SmartThings (#144493)
* Add hob support to SmartThings

* Add hob support to SmartThings

* Add hob support to SmartThings

* Fix

* Update homeassistant/components/smartthings/icons.json
2025-05-08 21:03:41 +02:00
Joost Lekkerkerker
7ee9f0af2d Add cooktop operating state to SmartThings (#144500) 2025-05-08 20:41:04 +02:00
Joost Lekkerkerker
cb6847b64c Remove deprecated services in SABnzbd (#144405)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-05-08 20:02:24 +02:00
Norbert Rittel
04867f6ecc Fix capitalization and grammar in simplefin (#144246) 2025-05-08 19:16:37 +02:00
Robert Resch
9e94e94075 Remove RTSPtoWebRTC (#144328) 2025-05-08 17:38:13 +02:00
Martin Hjelmare
014c5dc764 Set Z-Wave platforms fixture in config flow tests (#144470) 2025-05-08 17:46:41 +03:00
Martin Hjelmare
a1599d5f7d Set Z-Wave platforms fixture in helpers tests (#144472) 2025-05-08 17:46:00 +03:00
Martin Hjelmare
2fd678bb59 Set Z-Wave platforms fixture in light tests (#144473) 2025-05-08 17:45:26 +03:00
Fredrik Erlandsson
3c4c3dc08e Fix point import error (#144462)
* fix import error

* fix failing tests

* Apply suggestions from code review

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-08 14:28:56 +02:00
Martin Hjelmare
bbc3862fec Fix Z-Wave reset accumulated values button entity category (#144459) 2025-05-08 11:59:20 +02:00
Josef Zweck
678e25d0b1 Bump pylamarzocco to 2.0.1 (#144454) 2025-05-08 11:13:42 +03:00
Norbert Rittel
a6f91177b6 Small fixes in user-facing strings of nest (#144444)
- add the missing hyphen to "one-time setup fee"
- capitalize one occurrence of "Cloud Project" (treated as name in all other strings)
- sentence-case "name" as this can be translated
2025-05-08 11:13:00 +03:00
starkillerOG
ff637ef046 Include channel in Reolink device URL (#144456)
Include channel in device URL
2025-05-08 11:12:22 +03:00
Norbert Rittel
1cb813e0c5 Fix sentence-casing and missing hyphen in electrasmart (#144443) 2025-05-08 11:01:14 +03:00
J. Nick Koston
ce4e51078f Add test coverage for inkbird IBS-P02B (#144433)
Was reported not working in https://github.com/Bluetooth-Devices/inkbird-ble/issues/95#issuecomment-2860473798
but cannot reproduce the issue. The tests are still useful
2025-05-08 11:00:32 +03:00
Norbert Rittel
066d0f4143 Add missing hyphen to "two-factor authentication" in subaru (#144446) 2025-05-08 10:59:58 +03:00
Norbert Rittel
1294918f5b Add missing hyphen to "two-factor authentication" in august (#144447) 2025-05-08 10:58:30 +03:00
Norbert Rittel
4a556f89aa Add missing hyphen to "two-factor authentication" in nextcloud (#144448) 2025-05-08 10:58:16 +03:00
Norbert Rittel
e74a29c87a Sentence-case "multi-factor authentication" in sense (#144450) 2025-05-08 10:58:07 +03:00
Marc Mueller
50d57852a6 Include runner arch in CI cache key (#144038) 2025-05-08 09:27:17 +02:00
Ludovic BOUÉ
744d5f7bd4 Matter Mounted dimmable load control fixture (#144097)
* Create mounted_dimmable_load_control_fixture.json

* Add fixture

* Add snapshots
2025-05-08 09:10:30 +02:00
Norbert Rittel
0b0a239ed4 Fix sentence-casing in user-facing strings of isy994 (#144428) 2025-05-07 23:28:08 +03:00
Josef Zweck
4cc538b5ae Add sensor for brew start time to lamarzocco (#144423)
* Add sensor for brew start time to lamarzocco

* pytestmark
2025-05-07 22:48:52 +03:00
Norbert Rittel
f5c67e2fd1 Fix user-facing strings in totalconnect (#144411)
- replace two inconsistent occurrences of camel-cased "TotalConnect" with "Total Connect"
- apply sentence-casing to all strings
- add a missing hyphen to "4-digit numer"
2025-05-07 22:48:40 +03:00
Norbert Rittel
9ec5d90f4d Add missing hyphen to "6-digit … codes" in opower (#144417) 2025-05-07 12:21:43 -07:00
Norbert Rittel
e1344fca6c Fix spelling of "HomeKit" and "Gateway" in tradfri (#144420) 2025-05-07 20:11:41 +02:00
Norbert Rittel
e290829bc0 Add missing hyphen to "eight-digit HomeKit pairing code" (#144416) 2025-05-07 11:53:38 -05:00
Martin Hjelmare
dc0998d95d Fix Z-Wave restore nvm command to wait for driver ready (#144413) 2025-05-07 19:50:45 +03:00
Norbert Rittel
5456cd0ac1 Fix spelling in user-facing strings of auth component (#144412) 2025-05-07 17:06:46 +01:00
Michael Hansen
1ce44800ab Bump intents to 2025.5.7 (#144404) 2025-05-07 09:05:08 -05:00
Ludovic BOUÉ
c26b3f519a Add discovery schema for Matter CumulativeEnergyExported (#144061)
* Update sensor.py to add CumulativeEnergyExported attribute

* Report exported energy as positive value

* Add snapshot

* Add translation key
2025-05-07 15:25:18 +02:00
epenet
2f7fcb4f5e Do not duplicate model and model_id in SamsungTV device info (#144402) 2025-05-07 14:50:18 +02:00
Robert Resch
c4e4c52c6c Bump deebot-client to 13.1.0 (#144397) 2025-05-07 14:36:28 +02:00
Bram Kragten
e6912b94df Update frontend to 20250507.0 (#144398) 2025-05-07 14:14:39 +02:00
epenet
704e4221f7 Improve SamsungTV ssdp test fixtures (#144376)
* Improve SamsungTV ssdp fixtures

* More

* More

* More

* More

* Improve
2025-05-07 14:13:38 +02:00
Petar Petrov
48a2dde16b Add more missing device_class translations for template helper (#144399) 2025-05-07 15:08:17 +03:00
Thomas55555
293e01f2e9 Improve activity logic in Husqvarna Automower (#144057)
* Improve activity logic in Husqvarna Automower

* add test
2025-05-07 13:12:10 +02:00
Guido Schmitz
e2820787bf Bump devolo_home_control_api to 0.19.0 (#144374) 2025-05-07 13:09:43 +02:00
Joost Lekkerkerker
ed1eea9b50 Set SmartThings power energy state class to Total (#144395) 2025-05-07 13:06:53 +02:00
Wilbert
f7d8e4e7b9 Add typing to smartthings climate target_temperature_low (#143713)
Fix climate target_temperature_low
2025-05-07 13:06:07 +02:00
Joakim Sørensen
a2ab28286f Bump hass-nabucasa from 0.96.0 to 0.100.0 (#144341)
* Bump hass-nabucasa from 0.96.0 to 0.98.0

* Bump hass-nabucasa from 0.98.0 to 0.99.0

* Bump hass-nabucasa from 0.99.0 to 0.100.0
2025-05-07 13:05:56 +02:00
Barry vd. Heuvel
99f55665a5 Bump wh-python to 2025.4.29 for Weheat integration (#144384) 2025-05-07 13:04:46 +02:00
Joost Lekkerkerker
0aa817e300 Bump pySmartThings to 3.2.1 (#144393) 2025-05-07 13:00:19 +02:00
Petar Petrov
4cdb7a9887 Add missing device_class translations for template helper (#144392) 2025-05-07 12:55:43 +02:00
Joost Lekkerkerker
92a19357d3 Fix variables in MELCloud (#144396) 2025-05-07 12:55:28 +02:00
epenet
dded1305ec Cleanup old config flow IMPORT constants in samsungtv tests (#144394) 2025-05-07 12:40:01 +02:00
Joost Lekkerkerker
d6e85eef48 Fix SmartThings machine operating state with no options (#144390) 2025-05-07 12:37:53 +02:00
Martin Hjelmare
0b1875de14 Fix Z-Wave controller hard reset (#144389) 2025-05-07 13:32:27 +03:00
Joakim Sørensen
c5ef8659a7 Allow no_subscription repair issue in cloud (#144380) 2025-05-07 11:07:15 +02:00
epenet
9a332f19c2 Use runtime_data in hko (#144368) 2025-05-07 10:47:36 +02:00
Erik Montnemery
65ad39f5be Modify require_admin decorator to take parameters for Unauthorized (#144346) 2025-05-07 09:30:40 +02:00
Jan Bouwhuis
358d904c2c Fix field validation for mqtt subentry options in sections (#144355) 2025-05-07 09:24:51 +02:00
epenet
65278100a0 Remove entity name input from Samsung TV config flow (#144372) 2025-05-07 09:13:14 +02:00
Raphael Hehl
dbffd8c0ff Bump uiprotect to version 7.6.0 (#144369) 2025-05-07 09:11:31 +02:00
epenet
2a25dcd44e Bump renault-api to 0.3.1 (#144366)
* Bump renault-api to 0.3.1

* Adjust tests
2025-05-07 09:44:55 +03:00
markhannon
6e7f57383a Add switch entity to Zimi integration (#144236)
* Import switch.py

* Alignment to light.py

* Use default switch attributes

* Update homeassistant/components/zimi/switch.py

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-05-07 08:34:32 +02:00
John Hillery
946172d530 Bump nexia to 2.10.0 (#144363) 2025-05-06 23:23:06 -05:00
Brett Adams
2791329460 Use config location for Homelink in Teslemetry (#144171) 2025-05-06 23:45:07 +02:00
Paulus Schoutsen
320df710a4 Remove some media player intent checks for when paused (#144351) 2025-05-06 21:24:32 +02:00
Bram Kragten
76df7de0cf Update frontend to 20250506.0 (#144354) 2025-05-06 21:24:09 +02:00
Jan Bouwhuis
da7e9f3ab6 Ensure all default MQTT subentry option values are saved (#144347)
* Ensure all default MQTT subentry option values are saved

* Apply correct filter
2025-05-06 19:30:48 +02:00
epenet
a673bd7a91 Use runtime_data in here_travel_time (#144340) 2025-05-06 19:16:14 +02:00
J. Nick Koston
121e9e4e7f Bump aioesphomeapi to 30.2.0 (#144348) 2025-05-06 19:13:51 +02:00
J. Nick Koston
452e946509 Bump bluemaestro-ble to 0.4.1 (#144345)
changelog: https://github.com/Bluetooth-Devices/bluemaestro-ble/compare/v0.4.0...v0.4.1

fixes #https://github.com/home-assistant/core/issues/144339
2025-05-06 18:32:35 +03:00
Martin Hjelmare
c3ce82d874 Fix Z-Wave migration flow to unload config entry before unplugging controller (#144343)
* Fix Z-Wave migration unload config entry before unplugging controller

* Remove typo
2025-05-06 17:57:11 +03:00
epenet
253217958b Use runtime_data in google (#144331)
* Use runtime_data in google

* Quality scale
2025-05-06 07:55:04 -07:00
epenet
1447392847 Use runtime_data in guardian (#144344)
* Use runtime_data in guardian

* Adjust tests
2025-05-06 16:16:16 +02:00
epenet
32a6b8a0f8 Use runtime_data in goodwe (#144325) 2025-05-06 16:04:12 +02:00
Robert Resch
0ec7dc5654 Add endpoint validation for AWS S3 (#144334) 2025-05-06 15:54:42 +02:00
epenet
bdf6f7f590 Use config entry title to name SamsungTV entities (#144254) 2025-05-06 15:52:33 +02:00
epenet
fbae79fab2 Use runtime_data in google_assistant_sdk (#144335) 2025-05-06 15:52:00 +02:00
epenet
2c34712069 Move service definitions to separate module in guardian (#144306)
* Move service definitions to separate module in guardian

* docstring
2025-05-06 15:37:10 +02:00
Martin Hjelmare
40e3038775 Fix Z-Wave to reload config entry after migration nvm restore (#144338) 2025-05-06 15:26:45 +02:00
epenet
e2c02706a0 Use runtime_data in google_assistant (#144332) 2025-05-06 15:20:18 +02:00
Stefan Agner
313be7b30a Update Home Assistant base image to 2025.05.0 (#144333) 2025-05-06 14:49:47 +02:00
Jan Bouwhuis
d0ed8b67c4 Add MQTT button as entity platform on MQTT subentries (#144204) 2025-05-06 13:57:27 +02:00
epenet
deaaf2f082 Drop alias from local const DOMAIN import (#144312) 2025-05-06 13:35:27 +02:00
Robert Resch
ce95876d03 Rename S3 to AWS_S3 (#144324) 2025-05-06 13:29:37 +02:00
epenet
5475d7ef58 Use runtime_data in freebox (#144326) 2025-05-06 13:09:56 +02:00
epenet
687c74ee4c Remove deprecated freebox reboot service (#144303) 2025-05-06 12:21:48 +02:00
Simone Chemelli
c9a9488ff5 Manage unsupported sources on Samsung TV (#144221) 2025-05-06 12:20:04 +02:00
epenet
57217b46ed Use runtime_data in gogogate2 (#144322) 2025-05-06 12:14:46 +02:00
epenet
5a01521ff8 Use runtime_data in geonetnz_volcano (#144320) 2025-05-06 12:06:00 +02:00
Robert Resch
19a0a16915 Revert "Disable S3 checksums" (#144092) (#144318) 2025-05-06 12:01:27 +02:00
epenet
62877c2c58 Use runtime_data in geonetnz_quakes (#144319) 2025-05-06 11:58:25 +02:00
Joakim Sørensen
9479874bb4 Remove ThingTalk server configuration and related websocket command from cloud integration (#144301) 2025-05-06 11:49:44 +02:00
epenet
241b6a0170 Improve type hints in gc100 (#144308) 2025-05-06 11:01:46 +02:00
Arnie97
babc183834 Allow liter for gas sensor device class (#141518) 2025-05-06 10:59:09 +02:00
Joakim Sørensen
f3371bcf39 Add async_delete_repair_issue method to CloudClient (#144302) 2025-05-06 10:57:56 +02:00
epenet
33da5465bd Use runtime_data in gdacs (#144309) 2025-05-06 10:44:16 +02:00
epenet
5df3a9d76d Use runtime_data in geocaching (#144310) 2025-05-06 10:41:56 +02:00
Martin Hjelmare
ec4f4a4a1f Fix Z-Wave USB discovery to use serial by id path (#144314) 2025-05-06 11:33:58 +03:00
Jan Bouwhuis
46df29b390 Add MQTT binary_sensor as entity platform on MQTT subentries (#144142)
Add MQTT binary_sensor subentry support
2025-05-06 10:23:19 +02:00
epenet
60846434d3 Invert DOMAIN alias in telegram (#144313) 2025-05-06 10:05:12 +02:00
epenet
66c86c0461 Drop alias from local DOMAIN import (#144311) 2025-05-06 09:55:57 +02:00
Cerallin
73996fb916 Bump xiaomi-ble to 0.38.0 (#143885) 2025-05-06 09:55:43 +02:00
Ivan Lopez Hernandez
0edfbded23 Fixes #140182 by checking file status before sending the prompt. (#144131)
* Added unit tests

* Addressed review comments

* Fixed tests

* PR comments
2025-05-05 23:45:39 -07:00
Norbert Rittel
212c3ddcca Use international English spelling for "authorization" in reolink (#144305)
Use international English for "authorization" in `reolink`
2025-05-06 08:34:40 +02:00
Jamin
edcb090209 Bump VoIP utils to 0.3.2 (#144298) 2025-05-05 22:50:46 -04:00
Michael
92010e1fca Fix un-/re-load of Feedreader integration (#144285)
fix unload platforms call
2025-05-05 22:39:25 -04:00
Jan Bouwhuis
12f9a11716 Fix mqtt subentry device name is not required but should be (#144289)
Fix mqtt subentry device name is not required
2025-05-05 22:39:03 -04:00
Pete Sage
0dd21f4c89 Rehlko adjust timeouts for coordinator polls (#144297) 2025-05-05 21:37:54 -05:00
Jamin
14f967cdd0 Improve Voip pipeline stability (#137620)
* Improve Voip pipeline stability

It appears the pipeline is being unexpectedly cancelled in some
instances. In order to mitigate this issue hang ups will be detected
using a separate task rather than relying on timeouts in the STT read
method. Also reading STT events will be retried once if it is cancelled.
The pipeline will also catch and log any CancelledErrors to help with
further debugging.

* Update Voip tests

* Remove unnecessary changes

Remove unnecessary logging and cancelled error handling in wyoming STT.

* Remove comment about clearing system prompt

The test no longer checks for clearing the system prompt. Since that
logic exists completely in the assist_satellite component I think it is
reasonable to only test that logic in the unit tests for that component.

* Re-raise cancellation

Re-raise CancelledError if the current task is cancelling in the check hangup task

Co-authored-by: J. Nick Koston <nick@koston.org>

* Re-raise CancelledError in pipeline as well

* Fix formatting issue

* Remove unnecessary logging

* Add MockResultStream import to tests

This was presumably missed while merging

* Cancel check hangup task on disconnect

* Add myself as codeowner for VoIP

* Update CODEOWNERS

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-05-05 20:25:52 -04:00
Norbert Rittel
f3b23afc92 Change "recognized" to international English spelling in hive (#144284)
Change "recognized" to international English in `hive`
2025-05-06 00:15:24 +03:00
Jan Bouwhuis
0bf807b96e Fix default entity name not the device default entity when no name set on MQTT subentry entity (#144263) 2025-05-05 21:26:56 +02:00
Martin Hjelmare
1879b8c27f Fix Z-Wave config flow forms (#144279) 2025-05-05 15:02:20 -04:00
Paul Bottein
e3ed9fac78 Update frontend to 20250502.1 (#144276) 2025-05-05 14:47:15 -04:00
Josef Zweck
b98a27d3d0 Bump pylamarzocco to 2.0.0 (#144275) 2025-05-05 20:43:51 +02:00
Eliz
c73383ded3 Fix missing head forwarding in ingress (#144231)
* Add support for connect, head and trace in ingress

* added tests

* update the testutil

* fix

* fix empty space

* removed connect

* remove trace
2025-05-05 19:11:41 +02:00
Erik Montnemery
36a08d04c5 Fail tests which JSON serialize mocks (#144261)
* Fail tests which JSON serialize mocks

* Patch JSON helper earlier

* Check type instead of attribute
2025-05-05 19:06:54 +02:00
Åke Strandberg
8a95fffbab Remove program phase sensor from miele vacuum robot (#144257)
* Use device class transation

* Remove program pghses sensor from robot vacuum cleaner
2025-05-05 18:41:45 +02:00
Erik Montnemery
633c770a48 Fix matter mocks (#144271) 2025-05-05 17:59:57 +02:00
Erik Montnemery
826d28974b Fix fibaro mocks (#144270) 2025-05-05 17:59:50 +02:00
Erik Montnemery
135df5a24e Fix palazzetti mocks (#144268) 2025-05-05 17:59:17 +02:00
Erik Montnemery
2e8e13bffb Fix velbus mocks (#144267) 2025-05-05 17:59:08 +02:00
Erik Montnemery
5e8def837e Fix imeon_inverter mocks (#144266) 2025-05-05 17:58:58 +02:00
Erik Montnemery
14735cce26 Fix deako mocks (#144265) 2025-05-05 17:58:48 +02:00
Erik Montnemery
d775e443f8 Fix balboa mocks (#144264) 2025-05-05 17:58:39 +02:00
Luke Lashley
aa8dfa760d Bump Roborock Map Parser to 0.1.4 (#144260)
Bump to 0.1.4
2025-05-05 07:40:48 -07:00
tronikos
0043b18135 Use names instead of statistic IDs in the Opower repair issue (#144018)
* Use names instead of statistic IDs in the Opower repair issue

* target_ids
2025-05-05 08:36:58 -04:00
Luca De Petrillo
c14ddedfae Fix message corruption in picotts component (#141182) 2025-05-05 14:30:36 +02:00
Markus Jacobsen
a073a6b01e Fix hassfest expecting strings file for custom components (#135789)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Robert Resch <robert@resch.dev>
2025-05-05 14:23:02 +02:00
epenet
0713ac4977 Cleanup invalid CONF_ID from samsungtv tests (#144252) 2025-05-05 13:47:07 +02:00
Allen Porter
3390dc0dbb Fix Office 365 calendars to be compatible with rfc5545 (#144230) 2025-05-05 13:13:08 +02:00
dependabot[bot]
445b38f25d Bump github/codeql-action from 3.28.16 to 3.28.17 (#144245)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.28.17.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3.28.16...v3.28.17)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 12:51:19 +02:00
John Hillery
9e4a20c267 Bump nexia to 2.9.0 (#144153)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Robert Resch <robert@resch.dev>
2025-05-05 11:40:37 +01:00
epenet
d88cd72d13 Move more SamsungTV test constants to fixture files (#144249)
* Add SSDP fixtures to SamsungTV

* Adjust

* Improve

* Improve
2025-05-05 11:58:24 +02:00
tronikos
66b2e06cd3 Fix Invalid statistic_id for Opower: National Grid (#144243)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-05-05 11:35:32 +02:00
Michael
58906008b9 Add last attempted automatic backup sensor (#144194)
add last_attempted_automatic_backup sensor
2025-05-05 10:39:06 +02:00
Ville Skyttä
aa062515b8 Remove unused huawei_lte YAML schemas, error out on YAML config (#144217) 2025-05-05 10:46:30 +03:00
Norbert Rittel
65da1e79b9 Change some strings to international English in fronius (#144244)
Change to international English in `fronius`
2025-05-05 10:39:23 +03:00
Brett Adams
41ecb24135 Set api type more specifically in Teslemetry (#144178)
* Set api type more specifically

* remove extra spacing

* Fix class after rebase

---------

Co-authored-by: Allen Porter <allen@thebends.org>
2025-05-04 21:54:00 -07:00
Brett Adams
e3b3c32751 Add valet switch to Teslemetry (#144167)
* Add valet switch

* Add snapshot
2025-05-05 06:28:01 +02:00
Allen Porter
e2a8137140 Bump ical to 9.2.0 (#144240) 2025-05-04 23:40:49 -04:00
Allen Porter
fa6a2f08ab Update remote calendar to do all event handling in an executor (#144232) 2025-05-04 23:07:02 -04:00
Allen Porter
68d62ab58e Update local calendar to process calendar events in the executor (#144233) 2025-05-04 23:06:27 -04:00
Allen Porter
c6b9a40234 Increase the local calendar update interval to avoid re-parsing the calendar state unnecessarily (#144234) 2025-05-04 23:05:33 -04:00
Luke Lashley
e0916fdd26 Change roborock to use home_data_v3 (#144238) 2025-05-04 20:02:32 -07:00
Luke Lashley
cad2d72ed9 Bump python-roborock to 2.18.2 (#144235) 2025-05-04 16:59:49 -07:00
Norbert Rittel
8eaddbf2b2 Replace "log-in" with "log in" in zwave_me (#144223) 2025-05-04 21:56:33 +03:00
Norbert Rittel
9b30f32cad Replace "Sign-in …" with "Sign in …" in ring (#144222)
The config flow titles in Home Assistant use verbs by default ("Set up …" / "Configure …" / "Select …" etc.

Therefore "Sign-in …" (noun) for the initial setup in `ring` is replaced with "Sign in …" (verb).
2025-05-04 21:12:01 +03:00
Norbert Rittel
c2a69bcb20 Improve user-facing strings of blink (#144219)
- treat "sign in" as verb for consistency, removing the hyphen
- fix sentence-casing of two strings
2025-05-04 20:36:57 +03:00
Norbert Rittel
2e7b60c3ca Fix spelling of "sign in" and "setup" in verisure (#144214)
- use "sign in" for the verb
- use "setup" for the noun
- fix sentence-casing of "Verification code"
2025-05-04 20:36:24 +03:00
Norbert Rittel
eca811d0d4 Fix sentence-casing in user-facing strings of tami4 (#144212)
Fix sentence-casing in user-facing string of `tami4`
2025-05-04 20:35:59 +03:00
Norbert Rittel
8e202bc202 Improve the user-facing strings of heos (#144218) 2025-05-04 12:13:53 -05:00
J. Nick Koston
429682cecd Remove unnecessary intermediate functions in entry_data for ESPHome (#144173) 2025-05-04 11:42:07 -05:00
Pete Sage
9cd2080de2 Avoid delaying HA startup in Rehlko (#144202) 2025-05-04 11:41:39 -05:00
Pete Sage
2960271b81 bump aiokem to 0.5.10 (#144203) 2025-05-04 11:15:01 -05:00
Paulus Schoutsen
8048d2bfb8 Fix intent TurnOn creating stack trace for buttons (#144205) 2025-05-04 09:00:40 -07:00
Norbert Rittel
490bb46a82 Make spelling of "Auto-charge" switch consistent in TechnoVE (#144206)
* Make spelling of "Auto-charge" switch consistent in TechnoVE

Also fix sentence-casing in "Charging enabled" switch.

* Update test_switch.ambr
2025-05-04 18:56:25 +03:00
Norbert Rittel
1199353204 Fix sentence-casing of "Phone number" in peco (#144208) 2025-05-04 18:55:58 +03:00
Oliver
2c368c79d1 Update denonavr to 1.1.0 (#144199) 2025-05-04 16:41:44 +02:00
Michael Hannon
095318114b Add Zimi Cloud Connect Integration (#129876)
* Give entry unique id with MAC, strings.json tweaks

* Update codeowners

* Add config_flow tests

* Update requirements

* Update homeassistant/components/zimi/__init__.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Store controller reference in entry.runtime_data instead of hass.data

* Add typing

* Removed hass data pop on unload.  (No longer needed when hass data moved for runtime_data)

* Refactor config_flow based on feedback from @zweckj with inline validation, simpler defaults, better description data

* Add Michael to codeowners

* Remove manual debug override in entity

* Populate via_device

* remove empty keys from manifest.json

* Refactor with DataUpdateCoordinator
Device Entities use existing push update method

* set via_device to match zcc identifier

* Changed logger to use debug level

* Define the zimi constants

* Move extraaneous code out from try

* Move __del__ to async_wil_remove_from_hass

* Use zcc device for name

* Print debug if mac mismatch
Add final exception if api is not ready after connect

* Re-work configuration flow:
1. Remove unused CONF_TIMEOUT, CONF_VERBOSITY and CONF_WATCHDOG
2. Move connect() logic out of ZimiCoordinator
3. Add fast connect check during ConfigFlow to check mac matches
4. Use zcc version 3.2.3 with default watchdog time value (and remove this from HA)

* Add error detail to mac mismatch

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/const.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/coordinator.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/coordinator.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Remove coordinator and move setup to __init__

* Set name in _attr_name

* Use _light directly for status etc; Remove _state and _brightness; SImplify update()

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* No need to delete device, fix return

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Remove non-failing items from try
Abort duplicate configurations

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Move attr change to notify

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/__init__.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Remove superflous defalt

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Move aysnc_connect_to_controller to helpers.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Invert if api

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Added ZimiConfigEntry to type runtime_data correctly.
Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Use _abort_if_unique_id_configured
Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Invert error logic for cleaner flow
Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Add ZimiDimmer class

* Set colour_mode only in ZimiDimmer

* Use device name instead of entity name
Update deviceinfo for zcc
Update deviceinfo for lights
More ZimiDimmer and ZimiLight cleanup

* Update homeassistant/components/zimi/__init__.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/__init__.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Add missing import for CONNECTION_NETWORK_MAC

* @mhannon11 Fixed some minor style changes BUT these tests need re-working now that the config_flow has a second call to the zcc helper to check the API.   The tests as written now fail with connect_fail

* Remove some code from try

* Moved static items from initialiser

* Remove superflous assert when unloading entry

* refactor - move title out of data

* One call to async_add_entities
Update ZimiDimmer to initialise color_modes after calling super()

* Create ZimiEntity base class
(as ToggleEntity)

* Updated test of config_flow

* Move api_mock parameters to test cases

* Much improved tests

* Test for input value mismatch and then recovery of flow

* Import FlowResultType

* Implement Entities event setup correctly

* Initial quality_scale.yml

* Update homeassistant/components/zimi/quality_scale.yml

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/manifest.json

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Add link to zcc repo

* Update homeassistant/components/zimi/entity.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/entity.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Removed unecessary f-strings

* Filled in all of the quality scale

* Updated in line with latest documentation improvements

* FIx missing import for Entity

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Simplify logger and throw

* Update homeassistant/components/zimi/helpers.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Re-factor config_flow with multi-stage steps

* Add comments to notify

* Don't set hw_version

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/quality_scale.yml

Co-authored-by: Josef Zweck <josef@zweck.dev>

* mark docs-troubleshooting done

* Update with zcc-helper version supporting PEP 625 sdist rules on PyPi

* Comment re characteristic ID

* Pulls in latest zcc that closes UDP listening port correctly after discovery timeout

* Re-factored config_flow

1. Try discovery and auto-populate
2. Try manual configuration (with optional values for port and mac)

In most cases, auto-discovery does it all.

Discovery will only fail if UDP broadcast is not possible to/from zcc.

* Do not show error message if discovery fails

* Refactor with self.data and async_show_step_finish()

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/quality_scale.yml

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/quality_scale.yml

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/entity.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/light.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* refactor import to use ConfigFlow

* Change status for discovery

* Add dynamic title to config flow

* string

* Revert title from form but add IP:port to static title

* Automatically finish configuration if possible, if not show form

* Use StrEnum instead of Exception class

* Remove MAC from user forms

* Disconnect api before form completion

* Assign to self.mac instead of returning as detail

* Updated test suite

* Update test status

* mark action exemptions todo

* Remove mac related error cases from flow completely

* Remove unused MAC error strings

* Moved error details to logs
Removed _error_tuple
Removed error details

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* rename check_errors

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Update zcc-helper and support HA devices via zcc manufacter_info fields

* Partial implementation - Use updated zcc-helper to discover multiple controllers

* Config_flow with support for auto-discovery of one or more zcc or fallback to manual configuration.

* Don't re-connect to api if validate_connection already did

* Make fast=False is used for creation

* Pull in improved zcc_helper version to address data completeness after machine_info implementation

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Import and use ConfigFlowResult

* Latest zcc to fix discovers() return value bug

* Update config_flow.py

* Update homeassistant/components/zimi/manifest.json

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Use latest release version of 3.3 (no changes to rc4)

* Improved sentence casing

* Update strings.json

* Update homeassistant/components/zimi/entity.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Remove superflous logging
Use Zimi network_name as ZCC name
Cleanup device info inputs

* Remove __del__

* Rename arguments

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Move PLATFORMS to init

* Update homeassistant/components/zimi/light.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Remove debug at init

* Update homeassistant/components/zimi/helpers.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Remove _attr_has_entity = False

* More naming changes

* Revised config_flow to use zcc-helper for validation using new zcc-helper version

* Update homeassistant/components/zimi/__init__.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/__init__.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Removed commented enum

* s/_entity/_device/g

* Update homeassistant/components/zimi/entity.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/helpers.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Don't log error when raising exception

* Updated tests for new config_flow

* Refactor with new zcc that uses Exception classes to pass errors

* Updated tests for config_flow to use Exceptions

* Device name is based on model

* Device name is None

Maps better to ZCC concept where devices do not have a name but the individual entities have names.

* Fix quality filename

* Bump zcc-helper to 3.4 release version

* Remove name override

* Bump zcc-helper to 3.4.1 with new device_name attribute used to populate devinfo

* Update homeassistant/components/zimi/light.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Add missing transalation picked up by CI

* Update homeassistant/components/zimi/light.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Bump zcc-helper to only classify light and dimmer controlPointType as lights

* Bump to non dev version of zcc-helper

* Ruff fixes

* Add missing data description for pytest

* Remove confusing comment

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/const.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/const.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/const.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/strings.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zimi/light.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* f-strings

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Assert result type, step and errors between each step

* test for duplicate entry

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/zimi/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Remove duplicate test for discovery failure

* Calculate brightness

* Don't re-raise Exception in helper

* Fix ruff and mypi errors

* Add tests for missing connection exceptions

* Added standard invalid_host and timeout strings

* Explain limitations in discovery.

* Update quality_scale.yaml

* Update quality_scale.yaml

* Removed duplicate strings with reference

---------

Co-authored-by: markhannon <mark.hannon@gmail.com>
Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-04 15:58:32 +02:00
Norbert Rittel
9e388f5b13 Fix spelling of "comma-separated (network addresses)" in nmap_tracker (#144197) 2025-05-04 15:21:06 +03:00
Brett Adams
87fab1fa14 Rename classes in Teslemetry (#144179) 2025-05-04 14:18:39 +02:00
Brett Adams
8046684179 Update models const in Teslemetry (#144175) 2025-05-04 13:44:56 +02:00
Brett Adams
5a475ec7ea Improve typing of binary sensors in Teslemetry (#144169) 2025-05-04 13:42:25 +02:00
Brett Adams
8c6edd8b81 Add better typing to Teslemetry switch platform (#144168) 2025-05-04 13:41:45 +02:00
Brett Adams
de496c693e Add hazard lights binary sensor to Teslemetry (#144166) 2025-05-04 13:36:13 +02:00
Norbert Rittel
cb37d4d36a Fix spelling of "comma-separated (list / event name)" in doorbird (#144190) 2025-05-04 13:09:31 +03:00
Norbert Rittel
2aa82da615 Fix spelling of "comma-separated (list)" in huawei_lte (#144189) 2025-05-04 13:09:09 +03:00
Maciej Bieniek
04982f5e12 Add missing pollen category to AccuWeather (#144185)
* Add extreme level to pollen map

* Sort

* Sort
2025-05-04 13:08:07 +03:00
Norbert Rittel
b9e11b0f45 Fix spelling of "comma-separated" and "IP address" in cast (#144188) 2025-05-04 13:07:30 +03:00
hahn-th
1e0d1c46ab Bump homematicip to 2.0.1.1 (#144182)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-05-04 12:07:18 +02:00
Norbert Rittel
b5d499dda8 Fix spelling of "comma-separated (list)" in fritzbox_callmonitor (#144191)
Also fix one missing sentence-casing in corresponding "title" string.
2025-05-04 13:06:46 +03:00
Åke Strandberg
d1615f9a6e Bump pymiele to 0.4.3 (#144176)
* Use device class transation

* Bump pymiele to 0.4.3

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-05-04 11:30:37 +02:00
Marc Mueller
516a3c0504 Fix licenses check for setuptools (#144181) 2025-05-04 11:02:11 +03:00
J. Nick Koston
2a5c0d9b88 Add support for updating ESPHome deep sleep devices (#144161)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-05-03 20:50:17 -05:00
J. Nick Koston
a15a3c12d5 Pass requestor_uuid to bond API calls (#144128) 2025-05-03 20:40:28 -04:00
J. Nick Koston
a6131b3ebf Bump habluetooth to 3.48.2 (#144157) 2025-05-03 18:22:48 -05:00
Paulus Schoutsen
b9aadb252f Point thumbnail TTS media source to right logo (#144162) 2025-05-03 17:21:22 -04:00
J. Nick Koston
1264c2cbfa Bump zeroconf to 0.147.0 (#144158) 2025-05-03 14:21:03 -05:00
tronikos
716b559e5d Skip the update right after the migration in Opower (#144088)
* Wait for the migration to finish in Opower

* Don't call async_block_till_done since this can timeout and seems to meant for tests

* Don't call async_block_till_done since this can timeout and seems to meant for tests
2025-05-03 15:12:01 -04:00
Charlie Rusbridger
30e4264aa9 Use kodi posters, fall back to thumbnails if unavailable. (#144066) 2025-05-03 15:10:33 -04:00
Michael
fb94f8ea18 Make the network device tracking feature optional in AVM Fritz!Tools (#144149)
* make the network device tracking feature optional

* fix doc strings

* Apply suggestions from code review

Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>

---------

Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-05-03 21:04:59 +02:00
Florian Sabonchi
aea5760424 Fix check for locked device in AVM Fritz!SmartHome (#141697)
* feat: raise execption on hvac mode while device is locked

* fix: test for setting hvac mode while device is locked.

* feat: update translation

* feat: add separate translations for HVAC and temperature

* fix: test cases

* fix: test cases for test_set_preset_mode_boost

* rev: code review

* rev: exception string

* feat: updated  error message and added helper function

* Update homeassistant/components/fritzbox/strings.json

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>

* fix: translation key

* remove check_active_or_lock_mode from async_set_preset_mode

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2025-05-03 20:25:27 +02:00
Jan Bouwhuis
debec3bfbc Improve supported color modes description (#144144) 2025-05-03 17:13:43 +01:00
J. Diego Rodríguez Royo
4122f94fb6 Add DHCP discovery to Home Connect (#144095)
* Add DHCP discovery to Home Connect

* Added tests

* Use enums

* Use more enums
2025-05-03 17:16:02 +02:00
J. Nick Koston
b48a2cf2b5 Add tests to ensure ESPHome entity_ids are preserved on upgrade (#144116) 2025-05-03 10:12:37 -05:00
Shay Levy
0ca9ad1cc0 Mark Shelly docs-data-update as done (#144151) 2025-05-03 16:17:37 +02:00
Shay Levy
ee555a3700 Mark Shelly icon-translations as done (#144148) 2025-05-03 17:08:34 +03:00
Josef Zweck
a2bc3e3908 Switch to common clientsession for lamarzocco (#144137) 2025-05-03 14:44:18 +02:00
Thomas55555
64b7f2c285 Improve select platform in Husqvarna Automower (#144117) 2025-05-03 15:39:46 +03:00
Marc Mueller
db2435dc36 Fix litterrobot entity typing (#144147) 2025-05-03 14:35:17 +02:00
Marc Mueller
1d500fda67 Fix fritz coordinator typing (#144146) 2025-05-03 14:35:04 +02:00
Jan Bouwhuis
558b0ec3b1 Fix small issues with mqtt translations and improve readability (#144091) 2025-05-03 11:51:26 +02:00
J. Nick Koston
9780db1c22 Bump Bluetooth deps to improve auto recovery process (#144133) 2025-05-03 10:09:28 +02:00
J. Nick Koston
5e39fb6da1 Bump bleak-esphome to 2.15.1 (#144129) 2025-05-03 10:08:56 +02:00
J. Nick Koston
4450f919c3 Bump PyISY to 3.4.1 (#144127) 2025-05-02 17:46:59 -05:00
Marc Hörsken
3183bb78ff Update pywmspro to 0.2.2 to make error handling more robust (#144124) 2025-05-03 00:16:49 +02:00
J. Nick Koston
e74f918382 Bump aiodns to 3.3.0 (#144115) 2025-05-02 15:53:19 -05:00
Thomas55555
247d2e7efd Bump aioautomower to 2025.5.1 (#144118) 2025-05-02 22:35:34 +02:00
Bram Kragten
32b7edb608 Update frontend to 20250502.0 (#144114) 2025-05-02 22:33:39 +02:00
Josef Zweck
df4297be62 Fix intermittent unavailability for lamarzocco brew active sensor (#144120)
* Fix brew active intermittent unavailability for lamarzocco

* Whitespaces
2025-05-02 22:29:54 +02:00
Brett Adams
4c2e9fc759 Bump teslemetry-stream to 0.7.7 (#144085) 2025-05-02 21:13:12 +02:00
J. Nick Koston
2890fc7dd2 Only create a single resolver object if there are multiple aiohttp sessions (#144090) 2025-05-02 13:43:06 -05:00
Ian
97be2c4ac9 Bump py-nextbusnext to 2.1.2 (#144081)r
Bump py-nextbusnext version

Fixes #144059
2025-05-02 20:17:58 +02:00
Åke Strandberg
762d284102 Improve naming of miele freezers and fridges (#144062)
* Use device class transation

* Improve naming of miele freezers and fridges

* Address review

* Address review comment

* Simplify
2025-05-02 19:31:56 +02:00
Joost Lekkerkerker
4967c287f8 Add DHCP discovery to Knocki (#144048)
* Add DHCP discovery to Knocki

* Update homeassistant/components/knocki/quality_scale.yaml

Co-authored-by: Josef Zweck <josef@zweck.dev>

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-05-02 18:34:09 +02:00
Pete Sage
5e463d6af4 bump aiokem to 0.5.9 (#144098)
fix: bump aiokem to 0.5.9
2025-05-02 17:34:58 +02:00
Åke Strandberg
cbf4676ae4 Improve handling of missing miele program codes (#144093)
* Use device class transation

* Improve handling of unknown program codes

* Address review comment
2025-05-02 17:31:11 +02:00
Tomáš Bedřich
81444c8f4a Disable S3 checksums (#144092)
Disable S3 checksums (#143995)
2025-05-02 13:49:33 +02:00
J. Nick Koston
9861bd88b9 Avoid working out suggested id in entity_platform when already registered (#144079)
If the entity is already registered, avoid trying to work
out the suggested_entity_id and suggested_object_id as
async_get_or_create will discard them anyways.
2025-05-02 11:44:38 +02:00
Simone Chemelli
b0f1c71129 Handle missing action exceptions in SamsungTV (#143630)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-05-02 11:39:28 +02:00
Shay Levy
86b845f04a Mark exception-translations done in Shelly (#144073) 2025-05-02 12:32:41 +03:00
J. Diego Rodríguez Royo
3af0d6e484 Use is instead of == on check against enum value at Home Connect (#144083)
* Use `is` instead of `==` on check against enum value at Home Connect

* Revert HTTP status checks
2025-05-02 10:08:46 +02:00
epenet
fca62f1ae8 Move SamsungTV test constants to fixture files (#144086) 2025-05-02 08:32:44 +02:00
Andreas Kölsch
4e8d68a2ef Fix brightness calculation when using brightness_step_pct (#143786) 2025-05-01 23:07:52 +01:00
J. Diego Rodríguez Royo
883ab44437 Move Home Connect entry state assertion at tests (#144027) 2025-05-01 23:04:03 +02:00
tronikos
abd17d9af9 Pass empty set instead of empty dict to get_last_statistics (#144022) 2025-05-01 16:47:48 -04:00
J. Nick Koston
a906a1754e Avoid DomainData lookup in ESPHome update platform (#144072)
We can get this from entry.runtime_data
2025-05-01 16:45:44 -04:00
Josef Zweck
255beafe08 Add connect/disconnect callbacks to lamarzocco (#144011) 2025-05-01 16:29:44 -04:00
Josef Zweck
e2679004a1 Add bluetooth connection availability to diagnostics for lamarzocco (#144012)
* Add bluetooth connection availability to diagnostics for lamarzocco

* make even more detailed
2025-05-01 16:26:50 -04:00
J. Nick Koston
06bb692522 Bump inkbird-ble to 0.16.1 (#144074)
I made a mistake in one of the data lengths as I forgot to add
the length of the id which is 2 bytes. I really wish vendors
would stop putting raw data in this field.

changelog: https://github.com/Bluetooth-Devices/inkbird-ble/compare/v0.16.0...v0.16.1
2025-05-01 23:23:56 +03:00
Shay Levy
71599b8e75 Set Shelly PARALLEL_UPDATES (#144070) 2025-05-01 21:20:50 +02:00
J. Nick Koston
79f8bea48d Avoid validation of ESPHome MAC when discovered entry is ignored or unchanged (#144071)
fixes #144033
fixes #143991
2025-05-01 14:51:38 -04:00
Åke Strandberg
82b335a2c1 Flag strict typing for miele (#144060) 2025-05-01 19:10:24 +02:00
Thomas55555
361d93eb96 Remove deprecated binary sensor in Husqvarna Automower (#144064)
* Remove deprecated binary sensor in Husqvarna Automower

* snapshot
2025-05-01 18:35:48 +02:00
Ludovic BOUÉ
bab699eb0c Matter Solar power fixture (#144058) 2025-05-01 17:06:08 +02:00
Thomas55555
b8881ed85b Fix test in Husqvarna Automower (#144055) 2025-05-01 16:36:05 +02:00
Åke Strandberg
4013b418dd Use device class transation for door in miele (#144053) 2025-05-01 16:33:30 +02:00
Åke Strandberg
80d714b865 Use action property defined in MieleEntity (#144052) 2025-05-01 16:06:49 +02:00
Åke Strandberg
7fcad580cb Update miele program codes and strings (#144049) 2025-05-01 15:14:11 +02:00
Ludovic BOUÉ
60b6ff4064 Matter Laundry Dryer fixture (#144043)
* Create laundry_dryer.json

* Add snapshots

* Format fixture

* Set CurrentPhase attribute

* Set OperationalState attribute

* Update snapshot
2025-05-01 14:52:32 +02:00
Josef Zweck
24252edf38 Handle TimeoutError for lamarzocco (#144042) 2025-05-01 14:02:39 +02:00
J. Diego Rodríguez Royo
79aa7aacec Sort Home Connect test params (#144035) 2025-05-01 12:04:25 +02:00
OzGav
92944fa509 Media Player strings adjust grammar (#144030) 2025-05-01 11:40:04 +02:00
J. Diego Rodríguez Royo
c0f0a4a1ac Listen for an event just once at Home Connect test (#144031) 2025-05-01 11:24:29 +02:00
J. Diego Rodríguez Royo
a084b9fdde Set autouse to setup_credentials Home Connect fixture (#144028) 2025-05-01 11:24:05 +02:00
Andrea Turri
83b9b8b032 Fix state of fan entity for Miele hobs with extractor when turned off (#144025) 2025-05-01 10:42:27 +02:00
J. Diego Rodríguez Royo
bc47049d42 Remove non required Home Connect tests (#144024) 2025-05-01 10:18:32 +02:00
J. Diego Rodríguez Royo
17360ede28 Use common percentage const at Home Connect (#144021) 2025-05-01 09:57:42 +02:00
J. Diego Rodríguez Royo
f441f4d7c0 Remove translation key for battery level in Home Connect sensor (#144020) 2025-05-01 09:57:30 +02:00
J. Diego Rodríguez Royo
5ddc449247 Remove default brightness values from Home Connect light entities (#144019) 2025-05-01 09:57:17 +02:00
J. Diego Rodríguez Royo
dd8d714c94 Remove _attr_should_poll from Home Connect base entity (#144016) 2025-05-01 09:49:49 +02:00
J. Diego Rodríguez Royo
c2079ddf6f Remove unused client param at Home Connect diagnostics (#144017) 2025-05-01 09:49:25 +02:00
Manu
5250590b17 Remove deprecated action api_call from Habitica integration (#143978) 2025-05-01 09:28:25 +02:00
Jan-Philipp Benecke
93f4f14b2a Default backup encryption to true when updating only location retention (#143997) 2025-04-30 22:45:41 +01:00
Ville Skyttä
ba712ed514 Move huawei_lte sensor icons to icons.json where applicable (#143999) 2025-04-30 23:26:10 +02:00
Norbert Rittel
6e76ca0fb3 Add translations for "energy_distance" and "wind_direction" in random (#143994)
* Add translations for "energy_distance" and "wind_direction" in `random`

* Comma
2025-05-01 00:13:04 +03:00
Megamind
b0345cce68 Bump pushover-complete to 1.2.0 (#143966)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-30 23:04:56 +02:00
Paulus Schoutsen
c4eddc8d11 Ensure legacy TTS providers are hidden if entity exists (#143992) 2025-04-30 16:57:02 -04:00
Josef Zweck
7d89804a87 Bump pylamarzocco to 2.0.0b7 (#143989) 2025-04-30 22:56:04 +02:00
Ludovic BOUÉ
b92f718e08 Matter Cooktop fixture (#143984) 2025-04-30 22:09:29 +02:00
Franck Nijhof
ad0209a4a0 Bump version to 2025.6.0dev0 (#143983) 2025-04-30 21:44:19 +02:00
J. Diego Rodríguez Royo
d23d25c6b7 Add units of measurement for Home Connect counter entities (#143982) 2025-04-30 21:03:17 +02:00
1295 changed files with 39606 additions and 8399 deletions

View File

@@ -37,10 +37,10 @@ on:
type: boolean
env:
CACHE_VERSION: 12
CACHE_VERSION: 2
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2025.5"
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.6"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version
@@ -259,7 +259,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-venv-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
@@ -276,7 +276,7 @@ jobs:
path: ${{ env.PRE_COMMIT_CACHE }}
lookup-only: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Install pre-commit dependencies
if: steps.cache-precommit.outputs.cache-hit != 'true'
@@ -306,7 +306,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-venv-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
@@ -315,7 +315,7 @@ jobs:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Run ruff-format
run: |
@@ -346,7 +346,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-venv-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
@@ -355,7 +355,7 @@ jobs:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Run ruff
run: |
@@ -386,7 +386,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-venv-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
@@ -395,7 +395,7 @@ jobs:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Register yamllint problem matcher
@@ -501,7 +501,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore uv wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
@@ -509,10 +509,10 @@ jobs:
with:
path: ${{ env.UV_CACHE_DIR }}
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-uv-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ steps.python.outputs.python-version }}-uv-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-uv-${{
env.UV_CACHE_VERSION }}-${{ steps.generate-uv-key.outputs.version }}-${{
env.HA_SHORT_VERSION }}-
- name: Install additional OS dependencies
@@ -598,7 +598,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Run hassfest
run: |
@@ -631,7 +631,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Run gen_requirements_all.py
run: |
@@ -653,7 +653,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Dependency review
uses: actions/dependency-review-action@v4.6.0
uses: actions/dependency-review-action@v4.7.0
with:
license-check: false # We use our own license audit checks
@@ -688,7 +688,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Extract license data
run: |
@@ -731,7 +731,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Register pylint problem matcher
run: |
@@ -778,7 +778,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Register pylint problem matcher
run: |
@@ -830,17 +830,17 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore mypy cache
uses: actions/cache@v4.2.3
with:
path: .mypy_cache
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-mypy-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ steps.python.outputs.python-version }}-mypy-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-mypy-${{
env.MYPY_CACHE_VERSION }}-${{ steps.generate-mypy-key.outputs.version }}-${{
env.HA_SHORT_VERSION }}-
- name: Register mypy problem matcher
@@ -900,7 +900,7 @@ jobs:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Run split_tests.py
run: |
@@ -959,7 +959,8 @@ jobs:
with:
path: venv
fail-on-cache-miss: true
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Register Python problem matcher
run: |
@@ -1084,7 +1085,8 @@ jobs:
with:
path: venv
fail-on-cache-miss: true
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Register Python problem matcher
run: |
@@ -1218,7 +1220,8 @@ jobs:
with:
path: venv
fail-on-cache-miss: true
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Register Python problem matcher
run: |
@@ -1369,7 +1372,8 @@ jobs:
with:
path: venv
fail-on-cache-miss: true
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Register Python problem matcher
run: |

View File

@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.28.16
uses: github/codeql-action/init@v3.28.17
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.28.16
uses: github/codeql-action/analyze@v3.28.17
with:
category: "/language:python"

View File

@@ -332,6 +332,7 @@ homeassistant.components.media_player.*
homeassistant.components.media_source.*
homeassistant.components.met_eireann.*
homeassistant.components.metoffice.*
homeassistant.components.miele.*
homeassistant.components.mikrotik.*
homeassistant.components.min_max.*
homeassistant.components.minecraft_server.*
@@ -433,7 +434,6 @@ homeassistant.components.roku.*
homeassistant.components.romy.*
homeassistant.components.rpi_power.*
homeassistant.components.rss_feed_template.*
homeassistant.components.rtsp_to_webrtc.*
homeassistant.components.russound_rio.*
homeassistant.components.ruuvi_gateway.*
homeassistant.components.ruuvitag_ble.*

16
CODEOWNERS generated
View File

@@ -46,8 +46,8 @@ build.json @home-assistant/supervisor
/tests/components/accuweather/ @bieniu
/homeassistant/components/acmeda/ @atmurray
/tests/components/acmeda/ @atmurray
/homeassistant/components/adax/ @danielhiversen
/tests/components/adax/ @danielhiversen
/homeassistant/components/adax/ @danielhiversen @lazytarget
/tests/components/adax/ @danielhiversen @lazytarget
/homeassistant/components/adguard/ @frenck
/tests/components/adguard/ @frenck
/homeassistant/components/ads/ @mrpasztoradam
@@ -455,8 +455,8 @@ build.json @home-assistant/supervisor
/tests/components/evil_genius_labs/ @balloob
/homeassistant/components/evohome/ @zxdavb
/tests/components/evohome/ @zxdavb
/homeassistant/components/ezviz/ @RenierM26 @baqs
/tests/components/ezviz/ @RenierM26 @baqs
/homeassistant/components/ezviz/ @RenierM26
/tests/components/ezviz/ @RenierM26
/homeassistant/components/faa_delays/ @ntilley905
/tests/components/faa_delays/ @ntilley905
/homeassistant/components/fan/ @home-assistant/core
@@ -1111,8 +1111,8 @@ build.json @home-assistant/supervisor
/tests/components/opentherm_gw/ @mvn23
/homeassistant/components/openuv/ @bachya
/tests/components/openuv/ @bachya
/homeassistant/components/openweathermap/ @fabaff @freekode @nzapponi
/tests/components/openweathermap/ @fabaff @freekode @nzapponi
/homeassistant/components/openweathermap/ @fabaff @freekode @nzapponi @wittypluck
/tests/components/openweathermap/ @fabaff @freekode @nzapponi @wittypluck
/homeassistant/components/opnsense/ @mtreinish
/tests/components/opnsense/ @mtreinish
/homeassistant/components/opower/ @tronikos
@@ -1307,8 +1307,6 @@ build.json @home-assistant/supervisor
/tests/components/rpi_power/ @shenxn @swetoast
/homeassistant/components/rss_feed_template/ @home-assistant/core
/tests/components/rss_feed_template/ @home-assistant/core
/homeassistant/components/rtsp_to_webrtc/ @allenporter
/tests/components/rtsp_to_webrtc/ @allenporter
/homeassistant/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
/tests/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
/homeassistant/components/russound_rio/ @noahhusby
@@ -1796,6 +1794,8 @@ build.json @home-assistant/supervisor
/tests/components/zeversolar/ @kvanzuijlen
/homeassistant/components/zha/ @dmulcahey @adminiuga @puddly @TheJulianJES
/tests/components/zha/ @dmulcahey @adminiuga @puddly @TheJulianJES
/homeassistant/components/zimi/ @markhannon
/tests/components/zimi/ @markhannon
/homeassistant/components/zodiac/ @JulienTant
/tests/components/zodiac/ @JulienTant
/homeassistant/components/zone/ @home-assistant/core

View File

@@ -1,7 +1,7 @@
{
"domain": "adax",
"name": "Adax",
"codeowners": ["@danielhiversen"],
"codeowners": ["@danielhiversen", "@lazytarget"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adax",
"iot_class": "local_polling",

View File

@@ -8,7 +8,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN as ADVANTAGE_AIR_DOMAIN
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN
from .entity import AdvantageAirEntity, AdvantageAirThingEntity
from .models import AdvantageAirData
@@ -52,8 +52,8 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
self._id: str = light["id"]
self._attr_unique_id += f"-{self._id}"
self._attr_device_info = DeviceInfo(
identifiers={(ADVANTAGE_AIR_DOMAIN, self._attr_unique_id)},
via_device=(ADVANTAGE_AIR_DOMAIN, self.coordinator.data["system"]["rid"]),
identifiers={(DOMAIN, self._attr_unique_id)},
via_device=(DOMAIN, self.coordinator.data["system"]["rid"]),
manufacturer="Advantage Air",
model=light.get("moduleType"),
name=light["name"],

View File

@@ -6,7 +6,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .const import DOMAIN
from .entity import AdvantageAirEntity
from .models import AdvantageAirData
@@ -32,9 +32,7 @@ class AdvantageAirApp(AdvantageAirEntity, UpdateEntity):
"""Initialize the Advantage Air App."""
super().__init__(instance)
self._attr_device_info = DeviceInfo(
identifiers={
(ADVANTAGE_AIR_DOMAIN, self.coordinator.data["system"]["rid"])
},
identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])},
manufacturer="Advantage Air",
model=self.coordinator.data["system"]["sysType"],
name=self.coordinator.data["system"]["name"],

View File

@@ -10,7 +10,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN as AGENT_DOMAIN, SERVER_URL
from .const import DOMAIN, SERVER_URL
ATTRIBUTION = "ispyconnect.com"
DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
@@ -46,7 +46,7 @@ async def async_setup_entry(
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(AGENT_DOMAIN, agent_client.unique)},
identifiers={(DOMAIN, agent_client.unique)},
manufacturer="iSpyConnect",
name=f"Agent {agent_client.name}",
model="Agent DVR",

View File

@@ -12,7 +12,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AgentDVRConfigEntry
from .const import DOMAIN as AGENT_DOMAIN
from .const import DOMAIN
CONF_HOME_MODE_NAME = "home"
CONF_AWAY_MODE_NAME = "away"
@@ -47,7 +47,7 @@ class AgentBaseStation(AlarmControlPanelEntity):
self._client = client
self._attr_unique_id = f"{client.unique}_CP"
self._attr_device_info = DeviceInfo(
identifiers={(AGENT_DOMAIN, client.unique)},
identifiers={(DOMAIN, client.unique)},
name=f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}",
manufacturer="Agent",
model=CONST_ALARM_CONTROL_PANEL_NAME,

View File

@@ -3,6 +3,19 @@
"name": "Airthings",
"codeowners": ["@danielhiversen", "@LaStrada"],
"config_flow": true,
"dhcp": [
{
"hostname": "airthings-view"
},
{
"hostname": "airthings-hub",
"macaddress": "D0141190*"
},
{
"hostname": "airthings-hub",
"macaddress": "70B3D52A0*"
}
],
"documentation": "https://www.home-assistant.io/integrations/airthings",
"iot_class": "cloud_polling",
"loggers": ["airthings"],

View File

@@ -14,6 +14,7 @@ from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS,
EntityCategory,
@@ -78,6 +79,12 @@ SENSORS: dict[str, SensorEntityDescription] = {
translation_key="light",
state_class=SensorStateClass.MEASUREMENT,
),
"lux": SensorEntityDescription(
key="lux",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
),
"virusRisk": SensorEntityDescription(
key="virusRisk",
translation_key="virus_risk",

View File

@@ -2,7 +2,7 @@
"config": {
"step": {
"user": {
"title": "Choose AlarmDecoder Protocol",
"title": "Choose AlarmDecoder protocol",
"data": {
"protocol": "Protocol"
}
@@ -12,8 +12,8 @@
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]",
"device_baudrate": "Device Baud Rate",
"device_path": "Device Path"
"device_baudrate": "Device baud rate",
"device_path": "Device path"
},
"data_description": {
"host": "The hostname or IP address of the AlarmDecoder device that is connected to your alarm panel.",
@@ -44,36 +44,36 @@
"arm_settings": {
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
"data": {
"auto_bypass": "Auto Bypass on Arm",
"code_arm_required": "Code Required for Arming",
"alt_night_mode": "Alternative Night Mode"
"auto_bypass": "Auto-bypass on arm",
"code_arm_required": "Code required for arming",
"alt_night_mode": "Alternative night mode"
}
},
"zone_select": {
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
"description": "Enter the zone number you'd like to to add, edit, or remove.",
"data": {
"zone_number": "Zone Number"
"zone_number": "Zone number"
}
},
"zone_details": {
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave Zone Name blank.",
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave 'Zone name' blank.",
"data": {
"zone_name": "Zone Name",
"zone_type": "Zone Type",
"zone_rfid": "RF Serial",
"zone_loop": "RF Loop",
"zone_relayaddr": "Relay Address",
"zone_relaychan": "Relay Channel"
"zone_name": "Zone name",
"zone_type": "Zone type",
"zone_rfid": "RF serial",
"zone_loop": "RF loop",
"zone_relayaddr": "Relay address",
"zone_relaychan": "Relay channel"
}
}
},
"error": {
"relay_inclusive": "Relay Address and Relay Channel are codependent and must be included together.",
"relay_inclusive": "'Relay address' and 'Relay channel' are codependent and must be included together.",
"int": "The field below must be an integer.",
"loop_rfid": "RF Loop cannot be used without RF Serial.",
"loop_range": "RF Loop must be an integer between 1 and 4."
"loop_rfid": "'RF loop' cannot be used without 'RF serial'.",
"loop_range": "'RF loop' must be an integer between 1 and 4."
}
},
"services": {

View File

@@ -18,7 +18,7 @@
},
"step": {
"validation": {
"title": "Two factor authentication",
"title": "Two-factor authentication",
"data": {
"verification_code": "Verification code"
},

View File

@@ -4,8 +4,8 @@
"user": {
"description": "The inverter must be connected via an RS485 adaptor, please select serial port and the inverter's address as configured on the LCD panel",
"data": {
"port": "RS485 or USB-RS485 Adaptor Port",
"address": "Inverter Address"
"port": "RS485 or USB-RS485 adaptor port",
"address": "Inverter address"
}
}
},
@@ -16,7 +16,7 @@
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_serial_ports": "No com ports found. Need a valid RS485 device to communicate."
"no_serial_ports": "No com ports found. The integration needs a valid RS485 device to communicate."
}
},
"entity": {

View File

@@ -5,7 +5,7 @@
"step": {
"init": {
"title": "Set up two-factor authentication using TOTP",
"description": "To activate two factor authentication using time-based one-time passwords, scan the QR code with your authentication app. If you don't have one, we recommend either [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/).\n\n{qr_code}\n\nAfter scanning the code, enter the six digit code from your app to verify the setup. If you have problems scanning the QR code, do a manual setup with code **`{code}`**."
"description": "To activate two-factor authentication using time-based one-time passwords, scan the QR code with your authentication app. If you don't have one, we recommend either [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/).\n\n{qr_code}\n\nAfter scanning the code, enter the six-digit code from your app to verify the setup. If you have problems scanning the QR code, do a manual setup with code **`{code}`**."
}
},
"error": {
@@ -13,7 +13,7 @@
}
},
"notify": {
"title": "Notify One-Time Password",
"title": "Notify one-time password",
"step": {
"init": {
"title": "Set up one-time password delivered by notify component",

View File

@@ -14,7 +14,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity, EntityDescription
from .const import DOMAIN as AXIS_DOMAIN
from .const import DOMAIN
if TYPE_CHECKING:
from .hub import AxisHub
@@ -61,7 +61,7 @@ class AxisEntity(Entity):
self.hub = hub
self._attr_device_info = DeviceInfo(
identifiers={(AXIS_DOMAIN, hub.unique_id)},
identifiers={(DOMAIN, hub.unique_id)},
serial_number=hub.unique_id,
)

View File

@@ -30,6 +30,7 @@ class BackupCoordinatorData:
"""Class to hold backup data."""
backup_manager_state: BackupManagerState
last_attempted_automatic_backup: datetime | None
last_successful_automatic_backup: datetime | None
next_scheduled_automatic_backup: datetime | None
@@ -70,6 +71,7 @@ class BackupDataUpdateCoordinator(DataUpdateCoordinator[BackupCoordinatorData]):
"""Update backup manager data."""
return BackupCoordinatorData(
self.backup_manager.state,
self.backup_manager.config.data.last_attempted_automatic_backup,
self.backup_manager.config.data.last_completed_automatic_backup,
self.backup_manager.config.data.schedule.next_automatic_backup,
)

View File

@@ -22,7 +22,7 @@ from . import util
from .agent import BackupAgent
from .const import DATA_MANAGER
from .manager import BackupManager
from .models import BackupNotFound
from .models import AgentBackup, BackupNotFound
@callback
@@ -85,7 +85,15 @@ class DownloadBackupView(HomeAssistantView):
request, headers, backup_id, agent_id, agent, manager
)
return await self._send_backup_with_password(
hass, request, headers, backup_id, agent_id, password, agent, manager
hass,
backup,
request,
headers,
backup_id,
agent_id,
password,
agent,
manager,
)
except BackupNotFound:
return Response(status=HTTPStatus.NOT_FOUND)
@@ -116,6 +124,7 @@ class DownloadBackupView(HomeAssistantView):
async def _send_backup_with_password(
self,
hass: HomeAssistant,
backup: AgentBackup,
request: Request,
headers: dict[istr, str],
backup_id: str,
@@ -144,7 +153,8 @@ class DownloadBackupView(HomeAssistantView):
stream = util.AsyncIteratorWriter(hass)
worker = threading.Thread(
target=util.decrypt_backup, args=[reader, stream, password, on_done, 0, []]
target=util.decrypt_backup,
args=[backup, reader, stream, password, on_done, 0, []],
)
try:
worker.start()

View File

@@ -46,6 +46,12 @@ BACKUP_MANAGER_DESCRIPTIONS = (
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: data.last_successful_automatic_backup,
),
BackupSensorEntityDescription(
key="last_attempted_automatic_backup",
translation_key="last_attempted_automatic_backup",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: data.last_attempted_automatic_backup,
),
)

View File

@@ -37,6 +37,9 @@
"next_scheduled_automatic_backup": {
"name": "Next scheduled automatic backup"
},
"last_attempted_automatic_backup": {
"name": "Last attempted automatic backup"
},
"last_successful_automatic_backup": {
"name": "Last successful automatic backup"
}

View File

@@ -295,13 +295,26 @@ def validate_password_stream(
raise BackupEmpty
def _get_expected_archives(backup: AgentBackup) -> set[str]:
"""Get the expected archives in the backup."""
expected_archives = set()
if backup.homeassistant_included:
expected_archives.add("homeassistant")
for addon in backup.addons:
expected_archives.add(addon.slug)
for folder in backup.folders:
expected_archives.add(folder.value)
return expected_archives
def decrypt_backup(
backup: AgentBackup,
input_stream: IO[bytes],
output_stream: IO[bytes],
password: str | None,
on_done: Callable[[Exception | None], None],
minimum_size: int,
nonces: list[bytes],
nonces: NonceGenerator,
) -> None:
"""Decrypt a backup."""
error: Exception | None = None
@@ -315,10 +328,13 @@ def decrypt_backup(
fileobj=output_stream, mode="w|", bufsize=BUF_SIZE
) as output_tar,
):
_decrypt_backup(input_tar, output_tar, password)
_decrypt_backup(backup, input_tar, output_tar, password)
except (DecryptError, SecureTarError, tarfile.TarError) as err:
LOGGER.warning("Error decrypting backup: %s", err)
error = err
except Exception as err: # noqa: BLE001
LOGGER.exception("Unexpected error when decrypting backup: %s", err)
error = err
else:
# Pad the output stream to the requested minimum size
padding = max(minimum_size - output_stream.tell(), 0)
@@ -333,15 +349,18 @@ def decrypt_backup(
def _decrypt_backup(
backup: AgentBackup,
input_tar: tarfile.TarFile,
output_tar: tarfile.TarFile,
password: str | None,
) -> None:
"""Decrypt a backup."""
expected_archives = _get_expected_archives(backup)
for obj in input_tar:
# We compare with PurePath to avoid issues with different path separators,
# for example when backup.json is added as "./backup.json"
if PurePath(obj.name) == PurePath("backup.json"):
object_path = PurePath(obj.name)
if object_path == PurePath("backup.json"):
# Rewrite the backup.json file to indicate that the backup is decrypted
if not (reader := input_tar.extractfile(obj)):
raise DecryptError
@@ -352,7 +371,13 @@ def _decrypt_backup(
metadata_obj.size = len(updated_metadata_b)
output_tar.addfile(metadata_obj, BytesIO(updated_metadata_b))
continue
if not obj.name.endswith((".tar", ".tgz", ".tar.gz")):
prefix, _, suffix = object_path.name.partition(".")
if suffix not in ("tar", "tgz", "tar.gz"):
LOGGER.debug("Unknown file %s will not be decrypted", obj.name)
output_tar.addfile(obj, input_tar.extractfile(obj))
continue
if prefix not in expected_archives:
LOGGER.debug("Unknown inner tar file %s will not be decrypted", obj.name)
output_tar.addfile(obj, input_tar.extractfile(obj))
continue
istf = SecureTarFile(
@@ -371,12 +396,13 @@ def _decrypt_backup(
def encrypt_backup(
backup: AgentBackup,
input_stream: IO[bytes],
output_stream: IO[bytes],
password: str | None,
on_done: Callable[[Exception | None], None],
minimum_size: int,
nonces: list[bytes],
nonces: NonceGenerator,
) -> None:
"""Encrypt a backup."""
error: Exception | None = None
@@ -390,10 +416,13 @@ def encrypt_backup(
fileobj=output_stream, mode="w|", bufsize=BUF_SIZE
) as output_tar,
):
_encrypt_backup(input_tar, output_tar, password, nonces)
_encrypt_backup(backup, input_tar, output_tar, password, nonces)
except (EncryptError, SecureTarError, tarfile.TarError) as err:
LOGGER.warning("Error encrypting backup: %s", err)
error = err
except Exception as err: # noqa: BLE001
LOGGER.exception("Unexpected error when decrypting backup: %s", err)
error = err
else:
# Pad the output stream to the requested minimum size
padding = max(minimum_size - output_stream.tell(), 0)
@@ -408,17 +437,20 @@ def encrypt_backup(
def _encrypt_backup(
backup: AgentBackup,
input_tar: tarfile.TarFile,
output_tar: tarfile.TarFile,
password: str | None,
nonces: list[bytes],
nonces: NonceGenerator,
) -> None:
"""Encrypt a backup."""
inner_tar_idx = 0
expected_archives = _get_expected_archives(backup)
for obj in input_tar:
# We compare with PurePath to avoid issues with different path separators,
# for example when backup.json is added as "./backup.json"
if PurePath(obj.name) == PurePath("backup.json"):
object_path = PurePath(obj.name)
if object_path == PurePath("backup.json"):
# Rewrite the backup.json file to indicate that the backup is encrypted
if not (reader := input_tar.extractfile(obj)):
raise EncryptError
@@ -429,16 +461,21 @@ def _encrypt_backup(
metadata_obj.size = len(updated_metadata_b)
output_tar.addfile(metadata_obj, BytesIO(updated_metadata_b))
continue
if not obj.name.endswith((".tar", ".tgz", ".tar.gz")):
prefix, _, suffix = object_path.name.partition(".")
if suffix not in ("tar", "tgz", "tar.gz"):
LOGGER.debug("Unknown file %s will not be encrypted", obj.name)
output_tar.addfile(obj, input_tar.extractfile(obj))
continue
if prefix not in expected_archives:
LOGGER.debug("Unknown inner tar file %s will not be encrypted", obj.name)
continue
istf = SecureTarFile(
None, # Not used
gzip=False,
key=password_to_key(password) if password is not None else None,
mode="r",
fileobj=input_tar.extractfile(obj),
nonce=nonces[inner_tar_idx],
nonce=nonces.get(inner_tar_idx),
)
inner_tar_idx += 1
with istf.encrypt(obj) as encrypted:
@@ -456,17 +493,33 @@ class _CipherWorkerStatus:
writer: AsyncIteratorWriter
class NonceGenerator:
"""Generate nonces for encryption."""
def __init__(self) -> None:
"""Initialize the generator."""
self._nonces: dict[int, bytes] = {}
def get(self, index: int) -> bytes:
"""Get a nonce for the given index."""
if index not in self._nonces:
# Generate a new nonce for the given index
self._nonces[index] = os.urandom(16)
return self._nonces[index]
class _CipherBackupStreamer:
"""Encrypt or decrypt a backup."""
_cipher_func: Callable[
[
AgentBackup,
IO[bytes],
IO[bytes],
str | None,
Callable[[Exception | None], None],
int,
list[bytes],
NonceGenerator,
],
None,
]
@@ -484,7 +537,7 @@ class _CipherBackupStreamer:
self._hass = hass
self._open_stream = open_stream
self._password = password
self._nonces: list[bytes] = []
self._nonces = NonceGenerator()
def size(self) -> int:
"""Return the maximum size of the decrypted or encrypted backup."""
@@ -508,7 +561,15 @@ class _CipherBackupStreamer:
writer = AsyncIteratorWriter(self._hass)
worker = threading.Thread(
target=self._cipher_func,
args=[reader, writer, self._password, on_done, self.size(), self._nonces],
args=[
self._backup,
reader,
writer,
self._password,
on_done,
self.size(),
self._nonces,
],
)
worker_status = _CipherWorkerStatus(
done=asyncio.Event(), reader=reader, thread=worker, writer=writer
@@ -538,17 +599,6 @@ class DecryptedBackupStreamer(_CipherBackupStreamer):
class EncryptedBackupStreamer(_CipherBackupStreamer):
"""Encrypt a backup."""
def __init__(
self,
hass: HomeAssistant,
backup: AgentBackup,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
password: str | None,
) -> None:
"""Initialize."""
super().__init__(hass, backup, open_stream, password)
self._nonces = [os.urandom(16) for _ in range(self._num_tar_files())]
_cipher_func = staticmethod(encrypt_backup)
def backup(self) -> AgentBackup:

View File

@@ -2,7 +2,7 @@
"config": {
"step": {
"user": {
"title": "Sign-in with Blink account",
"title": "Sign in with Blink account",
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
@@ -30,7 +30,7 @@
"step": {
"simple_options": {
"data": {
"scan_interval": "Scan Interval (seconds)"
"scan_interval": "Scan interval (seconds)"
},
"title": "Blink options",
"description": "Configure Blink integration"
@@ -93,7 +93,7 @@
},
"config_entry_id": {
"name": "Integration ID",
"description": "The Blink Integration ID."
"description": "The Blink integration ID."
}
}
}

View File

@@ -21,6 +21,7 @@ from .coordinator import (
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = [
Platform.BUTTON,
Platform.MEDIA_PLAYER,
]

View File

@@ -0,0 +1,128 @@
"""Button entities for Bluesound."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING
from pyblu import Player
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.const import CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
DeviceInfo,
format_mac,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import BluesoundCoordinator
from .media_player import DEFAULT_PORT
from .utils import format_unique_id
if TYPE_CHECKING:
from . import BluesoundConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BluesoundConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Bluesound entry."""
async_add_entities(
BluesoundButton(
config_entry.runtime_data.coordinator,
config_entry.runtime_data.player,
config_entry.data[CONF_PORT],
description,
)
for description in BUTTON_DESCRIPTIONS
)
@dataclass(kw_only=True, frozen=True)
class BluesoundButtonEntityDescription(ButtonEntityDescription):
"""Description for Bluesound button entities."""
press_fn: Callable[[Player], Awaitable[None]]
async def clear_sleep_timer(player: Player) -> None:
"""Clear the sleep timer."""
sleep = -1
while sleep != 0:
sleep = await player.sleep_timer()
async def set_sleep_timer(player: Player) -> None:
"""Set the sleep timer."""
await player.sleep_timer()
BUTTON_DESCRIPTIONS = [
BluesoundButtonEntityDescription(
key="set_sleep_timer",
translation_key="set_sleep_timer",
entity_registry_enabled_default=False,
press_fn=set_sleep_timer,
),
BluesoundButtonEntityDescription(
key="clear_sleep_timer",
translation_key="clear_sleep_timer",
entity_registry_enabled_default=False,
press_fn=clear_sleep_timer,
),
]
class BluesoundButton(CoordinatorEntity[BluesoundCoordinator], ButtonEntity):
"""Base class for Bluesound buttons."""
_attr_has_entity_name = True
entity_description: BluesoundButtonEntityDescription
def __init__(
self,
coordinator: BluesoundCoordinator,
player: Player,
port: int,
description: BluesoundButtonEntityDescription,
) -> None:
"""Initialize the Bluesound button."""
super().__init__(coordinator)
sync_status = coordinator.data.sync_status
self.entity_description = description
self._player = player
self._attr_unique_id = (
f"{description.key}-{format_unique_id(sync_status.mac, port)}"
)
if port == DEFAULT_PORT:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, format_mac(sync_status.mac))},
connections={(CONNECTION_NETWORK_MAC, format_mac(sync_status.mac))},
name=sync_status.name,
manufacturer=sync_status.brand,
model=sync_status.model_name,
model_id=sync_status.model,
)
else:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, format_unique_id(sync_status.mac, port))},
name=sync_status.name,
manufacturer=sync_status.brand,
model=sync_status.model_name,
model_id=sync_status.model,
via_device=(DOMAIN, format_mac(sync_status.mac)),
)
async def async_press(self) -> None:
"""Handle the button press."""
await self.entity_description.press_fn(self._player)

View File

@@ -22,7 +22,11 @@ from homeassistant.components.media_player import (
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers import (
config_validation as cv,
entity_platform,
issue_registry as ir,
)
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
DeviceInfo,
@@ -34,7 +38,7 @@ from homeassistant.helpers.dispatcher import (
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from homeassistant.util import dt as dt_util, slugify
from .const import ATTR_BLUESOUND_GROUP, ATTR_MASTER, DOMAIN
from .coordinator import BluesoundCoordinator
@@ -488,10 +492,36 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
async def async_increase_timer(self) -> int:
"""Increase sleep time on player."""
ir.async_create_issue(
self.hass,
DOMAIN,
f"deprecated_service_{SERVICE_SET_TIMER}",
is_fixable=False,
breaks_in_ha_version="2025.12.0",
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_service_set_sleep_timer",
translation_placeholders={
"name": slugify(self.sync_status.name),
},
)
return await self._player.sleep_timer()
async def async_clear_timer(self) -> None:
"""Clear sleep timer on player."""
ir.async_create_issue(
self.hass,
DOMAIN,
f"deprecated_service_{SERVICE_CLEAR_TIMER}",
is_fixable=False,
breaks_in_ha_version="2025.12.0",
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_service_clear_sleep_timer",
translation_placeholders={
"name": slugify(self.sync_status.name),
},
)
sleep = 1
while sleep > 0:
sleep = await self._player.sleep_timer()

View File

@@ -26,6 +26,16 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}
},
"issues": {
"deprecated_service_set_sleep_timer": {
"title": "Detected use of deprecated action bluesound.set_sleep_timer",
"description": "Use `button.{name}_set_sleep_timer` instead.\n\nPlease replace this action and adjust your automations and scripts."
},
"deprecated_service_clear_sleep_timer": {
"title": "Detected use of deprecated action bluesound.clear_sleep_timer",
"description": "Use `button.{name}_clear_sleep_timer` instead.\n\nPlease replace this action and adjust your automations and scripts."
}
},
"services": {
"join": {
"name": "Join",
@@ -71,5 +81,15 @@
}
}
}
},
"entity": {
"button": {
"set_sleep_timer": {
"name": "Set sleep timer"
},
"clear_sleep_timer": {
"name": "Clear sleep timer"
}
}
}
}

View File

@@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from . import DOMAIN, BMWConfigEntry
from .entity import BMWBaseEntity
if TYPE_CHECKING:
@@ -111,7 +111,7 @@ class BMWButton(BMWBaseEntity, ButtonEntity):
await self.entity_description.remote_function(self.vehicle)
except MyBMWAPIError as ex:
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from . import DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity
@@ -71,7 +71,7 @@ class BMWLock(BMWBaseEntity, LockEntity):
self._attr_is_locked = None
self.async_write_ha_state()
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
@@ -95,7 +95,7 @@ class BMWLock(BMWBaseEntity, LockEntity):
self._attr_is_locked = None
self.async_write_ha_state()
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@@ -20,7 +20,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from . import DOMAIN, BMWConfigEntry
PARALLEL_UPDATES = 1
@@ -92,7 +92,7 @@ class BMWNotificationService(BaseNotificationService):
except (vol.Invalid, TypeError, ValueError) as ex:
raise ServiceValidationError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="invalid_poi",
translation_placeholders={
"poi_exception": str(ex),
@@ -107,7 +107,7 @@ class BMWNotificationService(BaseNotificationService):
await vehicle.remote_services.trigger_send_poi(poi)
except MyBMWAPIError as ex:
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from . import DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity
@@ -110,7 +110,7 @@ class BMWNumber(BMWBaseEntity, NumberEntity):
await self.entity_description.remote_service(self.vehicle, value)
except MyBMWAPIError as ex:
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from . import DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity
@@ -124,7 +124,7 @@ class BMWSelect(BMWBaseEntity, SelectEntity):
await self.entity_description.remote_service(self.vehicle, option)
except MyBMWAPIError as ex:
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@@ -69,7 +69,7 @@
"name": "Door lock state"
},
"condition_based_services": {
"name": "Condition based services"
"name": "Condition-based services"
},
"check_control_messages": {
"name": "Check control messages"
@@ -81,7 +81,7 @@
"name": "Connection status"
},
"is_pre_entry_climatization_enabled": {
"name": "Pre entry climatization"
"name": "Pre-entry climatization"
}
},
"button": {

View File

@@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from . import DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity
@@ -112,7 +112,7 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity):
await self.entity_description.remote_service_on(self.vehicle)
except MyBMWAPIError as ex:
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
@@ -124,7 +124,7 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity):
await self.entity_description.remote_service_off(self.vehicle)
except MyBMWAPIError as ex:
raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@@ -5,7 +5,7 @@ import logging
from typing import Any
from aiohttp import ClientError, ClientResponseError, ClientTimeout
from bond_async import Bond, BPUPSubscriptions, start_bpup
from bond_async import Bond, BPUPSubscriptions, RequestorUUID, start_bpup
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@@ -49,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BondConfigEntry) -> bool
token=token,
timeout=ClientTimeout(total=_API_TIMEOUT),
session=async_get_clientsession(hass),
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
)
hub = BondHub(bond, host)
try:

View File

@@ -8,7 +8,7 @@ import logging
from typing import Any
from aiohttp import ClientConnectionError, ClientResponseError
from bond_async import Bond
from bond_async import Bond, RequestorUUID
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult
@@ -34,7 +34,12 @@ TOKEN_SCHEMA = vol.Schema({})
async def async_get_token(hass: HomeAssistant, host: str) -> str | None:
"""Try to fetch the token from the bond device."""
bond = Bond(host, "", session=async_get_clientsession(hass))
bond = Bond(
host,
"",
session=async_get_clientsession(hass),
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
)
response: dict[str, str] = {}
with contextlib.suppress(ClientConnectionError):
response = await bond.token()
@@ -45,7 +50,10 @@ async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> tuple[st
"""Validate the user input allows us to connect."""
bond = Bond(
data[CONF_HOST], data[CONF_ACCESS_TOKEN], session=async_get_clientsession(hass)
data[CONF_HOST],
data[CONF_ACCESS_TOKEN],
session=async_get_clientsession(hass),
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
)
try:
hub = BondHub(bond, data[CONF_HOST])

View File

@@ -14,7 +14,11 @@ from homeassistant.helpers import device_registry as dr
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE, DOMAIN
PLATFORMS: list[Platform] = [Platform.ALARM_CONTROL_PANEL, Platform.SENSOR]
PLATFORMS: list[Platform] = [
Platform.ALARM_CONTROL_PANEL,
Platform.SENSOR,
Platform.SWITCH,
]
type BoschAlarmConfigEntry = ConfigEntry[Panel]

View File

@@ -86,3 +86,57 @@ class BoschAlarmAreaEntity(BoschAlarmEntity):
self._area.ready_observer.detach(self.schedule_update_ha_state)
if self._observe_status:
self._area.status_observer.detach(self.schedule_update_ha_state)
class BoschAlarmDoorEntity(BoschAlarmEntity):
"""A base entity for area related entities within a bosch alarm panel."""
def __init__(self, panel: Panel, door_id: int, unique_id: str) -> None:
"""Set up a area related entity for a bosch alarm panel."""
super().__init__(panel, unique_id)
self._door_id = door_id
self._door = panel.doors[door_id]
self._door_unique_id = f"{unique_id}_door_{door_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._door_unique_id)},
name=self._door.name,
manufacturer="Bosch Security Systems",
via_device=(DOMAIN, unique_id),
)
async def async_added_to_hass(self) -> None:
"""Observe state changes."""
await super().async_added_to_hass()
self._door.status_observer.attach(self.schedule_update_ha_state)
async def async_will_remove_from_hass(self) -> None:
"""Stop observing state changes."""
await super().async_added_to_hass()
self._door.status_observer.detach(self.schedule_update_ha_state)
class BoschAlarmOutputEntity(BoschAlarmEntity):
"""A base entity for area related entities within a bosch alarm panel."""
def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None:
"""Set up a output related entity for a bosch alarm panel."""
super().__init__(panel, unique_id)
self._output_id = output_id
self._output = panel.outputs[output_id]
self._output_unique_id = f"{unique_id}_output_{output_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._output_unique_id)},
name=self._output.name,
manufacturer="Bosch Security Systems",
via_device=(DOMAIN, unique_id),
)
async def async_added_to_hass(self) -> None:
"""Observe state changes."""
await super().async_added_to_hass()
self._output.status_observer.attach(self.schedule_update_ha_state)
async def async_will_remove_from_hass(self) -> None:
"""Stop observing state changes."""
await super().async_added_to_hass()
self._output.status_observer.detach(self.schedule_update_ha_state)

View File

@@ -2,7 +2,27 @@
"entity": {
"sensor": {
"faulting_points": {
"default": "mdi:alert-circle-outline"
"default": "mdi:alert-circle"
}
},
"switch": {
"locked": {
"default": "mdi:lock",
"state": {
"off": "mdi:lock-open"
}
},
"secured": {
"default": "mdi:lock",
"state": {
"off": "mdi:lock-open"
}
},
"cycling": {
"default": "mdi:lock",
"state": {
"on": "mdi:lock-open"
}
}
}
}

View File

@@ -54,9 +54,23 @@
},
"authentication_failed": {
"message": "Incorrect credentials for panel."
},
"incorrect_door_state": {
"message": "Door cannot be manipulated while it is being cycled."
}
},
"entity": {
"switch": {
"secured": {
"name": "Secured"
},
"cycling": {
"name": "Cycling"
},
"locked": {
"name": "Locked"
}
},
"sensor": {
"faulting_points": {
"name": "Faulting points",

View File

@@ -0,0 +1,150 @@
"""Support for Bosch Alarm Panel outputs and doors as switches."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from bosch_alarm_mode2 import Panel
from bosch_alarm_mode2.panel import Door
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BoschAlarmConfigEntry
from .const import DOMAIN
from .entity import BoschAlarmDoorEntity, BoschAlarmOutputEntity
@dataclass(kw_only=True, frozen=True)
class BoschAlarmSwitchEntityDescription(SwitchEntityDescription):
"""Describes Bosch Alarm door entity."""
value_fn: Callable[[Door], bool]
on_fn: Callable[[Panel, int], Coroutine[Any, Any, None]]
off_fn: Callable[[Panel, int], Coroutine[Any, Any, None]]
DOOR_SWITCH_TYPES: list[BoschAlarmSwitchEntityDescription] = [
BoschAlarmSwitchEntityDescription(
key="locked",
translation_key="locked",
value_fn=lambda door: door.is_locked(),
on_fn=lambda panel, door_id: panel.door_relock(door_id),
off_fn=lambda panel, door_id: panel.door_unlock(door_id),
),
BoschAlarmSwitchEntityDescription(
key="secured",
translation_key="secured",
value_fn=lambda door: door.is_secured(),
on_fn=lambda panel, door_id: panel.door_secure(door_id),
off_fn=lambda panel, door_id: panel.door_unsecure(door_id),
),
BoschAlarmSwitchEntityDescription(
key="cycling",
translation_key="cycling",
value_fn=lambda door: door.is_cycling(),
on_fn=lambda panel, door_id: panel.door_cycle(door_id),
off_fn=lambda panel, door_id: panel.door_relock(door_id),
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BoschAlarmConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switch entities for outputs."""
panel = config_entry.runtime_data
entities: list[SwitchEntity] = [
PanelOutputEntity(
panel, output_id, config_entry.unique_id or config_entry.entry_id
)
for output_id in panel.outputs
]
entities.extend(
PanelDoorEntity(
panel,
door_id,
config_entry.unique_id or config_entry.entry_id,
entity_description,
)
for door_id in panel.doors
for entity_description in DOOR_SWITCH_TYPES
)
async_add_entities(entities)
PARALLEL_UPDATES = 0
class PanelDoorEntity(BoschAlarmDoorEntity, SwitchEntity):
"""A switch entity for a door on a bosch alarm panel."""
entity_description: BoschAlarmSwitchEntityDescription
def __init__(
self,
panel: Panel,
door_id: int,
unique_id: str,
entity_description: BoschAlarmSwitchEntityDescription,
) -> None:
"""Set up a switch entity for a door on a bosch alarm panel."""
super().__init__(panel, door_id, unique_id)
self.entity_description = entity_description
self._attr_unique_id = f"{self._door_unique_id}_{entity_description.key}"
@property
def is_on(self) -> bool:
"""Return the value function."""
return self.entity_description.value_fn(self._door)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Run the on function."""
# If the door is currently cycling, we can't send it any other commands until it is done
if self._door.is_cycling():
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="incorrect_door_state"
)
await self.entity_description.on_fn(self.panel, self._door_id)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Run the off function."""
# If the door is currently cycling, we can't send it any other commands until it is done
if self._door.is_cycling():
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="incorrect_door_state"
)
await self.entity_description.off_fn(self.panel, self._door_id)
class PanelOutputEntity(BoschAlarmOutputEntity, SwitchEntity):
"""An output entity for a bosch alarm panel."""
_attr_name = None
def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None:
"""Set up an output entity for a bosch alarm panel."""
super().__init__(panel, output_id, unique_id)
self._attr_unique_id = self._output_unique_id
@property
def is_on(self) -> bool:
"""Check if this entity is on."""
return self._output.is_active()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on this output."""
await self.panel.set_output_active(self._output_id)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off this output."""
await self.panel.set_output_inactive(self._output_id)

View File

@@ -10,7 +10,12 @@ from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import BringConfigEntry, BringDataUpdateCoordinator
from .coordinator import (
BringActivityCoordinator,
BringConfigEntry,
BringCoordinators,
BringDataUpdateCoordinator,
)
PLATFORMS: list[Platform] = [Platform.EVENT, Platform.SENSOR, Platform.TODO]
@@ -26,7 +31,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: BringConfigEntry) -> boo
coordinator = BringDataUpdateCoordinator(hass, entry, bring)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
activity_coordinator = BringActivityCoordinator(hass, entry, coordinator)
await activity_coordinator.async_config_entry_first_refresh()
entry.runtime_data = BringCoordinators(coordinator, activity_coordinator)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@@ -30,7 +30,15 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
type BringConfigEntry = ConfigEntry[BringDataUpdateCoordinator]
type BringConfigEntry = ConfigEntry[BringCoordinators]
@dataclass
class BringCoordinators:
"""Data class holding coordinators."""
data: BringDataUpdateCoordinator
activity: BringActivityCoordinator
@dataclass(frozen=True)
@@ -39,17 +47,28 @@ class BringData(DataClassORJSONMixin):
lst: BringList
content: BringItemsResponse
@dataclass(frozen=True)
class BringActivityData(DataClassORJSONMixin):
"""Coordinator data class."""
activity: BringActivityResponse
users: BringUsersResponse
class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
"""A Bring Data Update Coordinator."""
class BringBaseCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
"""Bring base coordinator."""
config_entry: BringConfigEntry
user_settings: BringUserSettingsResponse
lists: list[BringList]
class BringDataUpdateCoordinator(BringBaseCoordinator[dict[str, BringData]]):
"""A Bring Data Update Coordinator."""
user_settings: BringUserSettingsResponse
def __init__(
self, hass: HomeAssistant, config_entry: BringConfigEntry, bring: Bring
) -> None:
@@ -90,16 +109,19 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
current_lists := {lst.listUuid for lst in self.lists}
):
self._purge_deleted_lists()
new_lists = current_lists - self.previous_lists
self.previous_lists = current_lists
list_dict: dict[str, BringData] = {}
for lst in self.lists:
if (ctx := set(self.async_contexts())) and lst.listUuid not in ctx:
if (
(ctx := set(self.async_contexts()))
and lst.listUuid not in ctx
and lst.listUuid not in new_lists
):
continue
try:
items = await self.bring.get_list(lst.listUuid)
activity = await self.bring.get_activity(lst.listUuid)
users = await self.bring.get_list_users(lst.listUuid)
except BringRequestException as e:
raise UpdateFailed(
translation_domain=DOMAIN,
@@ -111,7 +133,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
translation_key="setup_parse_exception",
) from e
else:
list_dict[lst.listUuid] = BringData(lst, items, activity, users)
list_dict[lst.listUuid] = BringData(lst, items)
return list_dict
@@ -156,3 +178,60 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
device_reg.async_update_device(
device.id, remove_config_entry_id=self.config_entry.entry_id
)
class BringActivityCoordinator(BringBaseCoordinator[dict[str, BringActivityData]]):
"""A Bring Activity Data Update Coordinator."""
user_settings: BringUserSettingsResponse
def __init__(
self,
hass: HomeAssistant,
config_entry: BringConfigEntry,
coordinator: BringDataUpdateCoordinator,
) -> None:
"""Initialize the Bring Activity data coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=timedelta(minutes=10),
)
self.coordinator = coordinator
self.lists = coordinator.lists
async def _async_update_data(self) -> dict[str, BringActivityData]:
"""Fetch activity data from bring."""
list_dict: dict[str, BringActivityData] = {}
for lst in self.lists:
if (
ctx := set(self.coordinator.async_contexts())
) and lst.listUuid not in ctx:
continue
try:
activity = await self.coordinator.bring.get_activity(lst.listUuid)
users = await self.coordinator.bring.get_list_users(lst.listUuid)
except BringAuthException as e:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_authentication_exception",
translation_placeholders={CONF_EMAIL: self.coordinator.bring.mail},
) from e
except BringRequestException as e:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="setup_request_exception",
) from e
except BringParseException as e:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="setup_parse_exception",
) from e
else:
list_dict[lst.listUuid] = BringActivityData(activity, users)
return list_dict

View File

@@ -20,9 +20,12 @@ async def async_get_config_entry_diagnostics(
return {
"data": {
k: async_redact_data(v.to_dict(), TO_REDACT)
for k, v in config_entry.runtime_data.data.items()
k: v.to_dict() for k, v in config_entry.runtime_data.data.data.items()
},
"lists": [lst.to_dict() for lst in config_entry.runtime_data.lists],
"user_settings": config_entry.runtime_data.user_settings.to_dict(),
"activity": {
k: async_redact_data(v.to_dict(), TO_REDACT)
for k, v in config_entry.runtime_data.activity.data.items()
},
"lists": [lst.to_dict() for lst in config_entry.runtime_data.data.lists],
"user_settings": config_entry.runtime_data.data.user_settings.to_dict(),
}

View File

@@ -8,17 +8,17 @@ from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import BringDataUpdateCoordinator
from .coordinator import BringBaseCoordinator
class BringBaseEntity(CoordinatorEntity[BringDataUpdateCoordinator]):
class BringBaseEntity(CoordinatorEntity[BringBaseCoordinator]):
"""Bring base entity."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: BringDataUpdateCoordinator,
coordinator: BringBaseCoordinator,
bring_list: BringList,
) -> None:
"""Initialize the entity."""
@@ -34,5 +34,7 @@ class BringBaseEntity(CoordinatorEntity[BringDataUpdateCoordinator]):
},
manufacturer="Bring! Labs AG",
model="Bring! Grocery Shopping List",
configuration_url=f"https://web.getbring.com/app/lists/{list(self.coordinator.lists).index(bring_list)}",
configuration_url=f"https://web.getbring.com/app/lists/{list(self.coordinator.lists).index(bring_list)}"
if bring_list in self.coordinator.lists
else None,
)

View File

@@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BringConfigEntry
from .coordinator import BringDataUpdateCoordinator
from .coordinator import BringActivityCoordinator
from .entity import BringBaseEntity
PARALLEL_UPDATES = 0
@@ -32,18 +32,18 @@ async def async_setup_entry(
"""Add event entities."""
nonlocal lists_added
if new_lists := {lst.listUuid for lst in coordinator.lists} - lists_added:
if new_lists := {lst.listUuid for lst in coordinator.data.lists} - lists_added:
async_add_entities(
BringEventEntity(
coordinator,
coordinator.activity,
bring_list,
)
for bring_list in coordinator.lists
for bring_list in coordinator.data.lists
if bring_list.listUuid in new_lists
)
lists_added |= new_lists
coordinator.async_add_listener(add_entities)
coordinator.activity.async_add_listener(add_entities)
add_entities()
@@ -51,10 +51,11 @@ class BringEventEntity(BringBaseEntity, EventEntity):
"""An event entity."""
_attr_translation_key = "activities"
coordinator: BringActivityCoordinator
def __init__(
self,
coordinator: BringDataUpdateCoordinator,
coordinator: BringActivityCoordinator,
bring_list: BringList,
) -> None:
"""Initialize the entity."""

View File

@@ -88,7 +88,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.data
lists_added: set[str] = set()
@callback
@@ -117,6 +117,7 @@ class BringSensorEntity(BringBaseEntity, SensorEntity):
"""A sensor entity."""
entity_description: BringSensorEntityDescription
coordinator: BringDataUpdateCoordinator
def __init__(
self,

View File

@@ -44,7 +44,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the sensor from a config entry created in the integrations UI."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.data
lists_added: set[str] = set()
@callback
@@ -88,6 +88,7 @@ class BringTodoListEntity(BringBaseEntity, TodoListEntity):
| TodoListEntityFeature.DELETE_TODO_ITEM
| TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
)
coordinator: BringDataUpdateCoordinator
def __init__(
self, coordinator: BringDataUpdateCoordinator, bring_list: BringList
@@ -107,7 +108,9 @@ class BringTodoListEntity(BringBaseEntity, TodoListEntity):
description=item.specification,
status=TodoItemStatus.NEEDS_ACTION,
)
for item in self.bring_list.content.items.purchase
for item in sorted(
self.bring_list.content.items.purchase, key=lambda i: i.itemId
)
),
*(
TodoItem(

View File

@@ -11,6 +11,13 @@
},
"audio_output": {
"default": "mdi:audio-input-stereo-minijack"
},
"control_bus_mode": {
"default": "mdi:audio-video-off",
"state": {
"amplifier": "mdi:speaker",
"receiver": "mdi:audio-video"
}
}
},
"switch": {

View File

@@ -11,6 +11,7 @@ from aiostreammagic import (
StreamMagicClient,
TransportControl,
)
from aiostreammagic.models import ControlBusMode
from homeassistant.components.media_player import (
BrowseMedia,
@@ -91,6 +92,8 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
features = BASE_FEATURES
if self.client.state.pre_amp_mode:
features |= PREAMP_FEATURES
if self.client.state.control_bus == ControlBusMode.AMPLIFIER:
features |= MediaPlayerEntityFeature.VOLUME_STEP
if TransportControl.PLAY_PAUSE in controls:
features |= MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE
for control in controls:

View File

@@ -4,7 +4,7 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from aiostreammagic import StreamMagicClient
from aiostreammagic.models import DisplayBrightness
from aiostreammagic.models import ControlBusMode, DisplayBrightness
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
@@ -76,6 +76,20 @@ CONTROL_ENTITIES: tuple[CambridgeAudioSelectEntityDescription, ...] = (
value_fn=_audio_output_value_fn,
set_value_fn=_audio_output_set_value_fn,
),
CambridgeAudioSelectEntityDescription(
key="control_bus_mode",
translation_key="control_bus_mode",
options=[
ControlBusMode.AMPLIFIER.value,
ControlBusMode.RECEIVER.value,
ControlBusMode.OFF.value,
],
entity_category=EntityCategory.CONFIG,
value_fn=lambda client: client.state.control_bus,
set_value_fn=lambda client, value: client.set_control_bus_mode(
ControlBusMode(value)
),
),
)

View File

@@ -46,6 +46,14 @@
},
"audio_output": {
"name": "Audio output"
},
"control_bus_mode": {
"name": "Control Bus mode",
"state": {
"amplifier": "Amplifier",
"receiver": "Receiver",
"off": "[%key:common::state::off%]"
}
}
},
"switch": {

View File

@@ -61,7 +61,6 @@ from homeassistant.helpers.deprecation import (
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.frame import ReportBehavior, report_usage
from homeassistant.helpers.network import get_url
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, VolDictType
@@ -86,7 +85,6 @@ from .img_util import scale_jpeg_camera_image
from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
from .webrtc import (
DATA_ICE_SERVERS,
CameraWebRTCLegacyProvider,
CameraWebRTCProvider,
WebRTCAnswer,
WebRTCCandidate, # noqa: F401
@@ -94,10 +92,8 @@ from .webrtc import (
WebRTCError,
WebRTCMessage, # noqa: F401
WebRTCSendMessage,
async_get_supported_legacy_provider,
async_get_supported_provider,
async_register_ice_servers,
async_register_rtsp_to_web_rtc_provider, # noqa: F401
async_register_webrtc_provider, # noqa: F401
async_register_ws,
)
@@ -436,7 +432,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
CACHED_PROPERTIES_WITH_ATTR_ = {
"brand",
"frame_interval",
"frontend_stream_type",
"is_on",
"is_recording",
"is_streaming",
@@ -456,8 +451,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
# Entity Properties
_attr_brand: str | None = None
_attr_frame_interval: float = MIN_STREAM_INTERVAL
# Deprecated in 2024.12. Remove in 2025.6
_attr_frontend_stream_type: StreamType | None
_attr_is_on: bool = True
_attr_is_recording: bool = False
_attr_is_streaming: bool = False
@@ -480,7 +473,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
self.async_update_token()
self._create_stream_lock: asyncio.Lock | None = None
self._webrtc_provider: CameraWebRTCProvider | None = None
self._legacy_webrtc_provider: CameraWebRTCLegacyProvider | None = None
self._supports_native_sync_webrtc = (
type(self).async_handle_web_rtc_offer != Camera.async_handle_web_rtc_offer
)
@@ -488,16 +480,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
type(self).async_handle_async_webrtc_offer
!= Camera.async_handle_async_webrtc_offer
)
self._deprecate_attr_frontend_stream_type_logged = False
if type(self).frontend_stream_type != Camera.frontend_stream_type:
report_usage(
(
f"is overwriting the 'frontend_stream_type' property in the {type(self).__name__} class,"
" which is deprecated and will be removed in Home Assistant 2025.6, "
),
core_integration_behavior=ReportBehavior.ERROR,
exclude_integrations={DOMAIN},
)
@cached_property
def entity_picture(self) -> str:
@@ -559,40 +541,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Return the interval between frames of the mjpeg stream."""
return self._attr_frame_interval
@property
def frontend_stream_type(self) -> StreamType | None:
"""Return the type of stream supported by this camera.
A camera may have a single stream type which is used to inform the
frontend which camera attributes and player to use. The default type
is to use HLS, and components can override to change the type.
"""
# Deprecated in 2024.12. Remove in 2025.6
# Use the camera_capabilities instead
if hasattr(self, "_attr_frontend_stream_type"):
if not self._deprecate_attr_frontend_stream_type_logged:
report_usage(
(
f"is setting the '_attr_frontend_stream_type' attribute in the {type(self).__name__} class,"
" which is deprecated and will be removed in Home Assistant 2025.6, "
),
core_integration_behavior=ReportBehavior.ERROR,
exclude_integrations={DOMAIN},
)
self._deprecate_attr_frontend_stream_type_logged = True
return self._attr_frontend_stream_type
if CameraEntityFeature.STREAM not in self.supported_features_compat:
return None
if (
self._webrtc_provider
or self._legacy_webrtc_provider
or self._supports_native_sync_webrtc
or self._supports_native_async_webrtc
):
return StreamType.WEB_RTC
return StreamType.HLS
@property
def available(self) -> bool:
"""Return True if entity is available."""
@@ -694,14 +642,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
)
return
if self._legacy_webrtc_provider and (
answer := await self._legacy_webrtc_provider.async_handle_web_rtc_offer(
self, offer_sdp
)
):
send_message(WebRTCAnswer(answer))
else:
raise HomeAssistantError("Camera does not support WebRTC")
raise HomeAssistantError("Camera does not support WebRTC")
def camera_image(
self, width: int | None = None, height: int | None = None
@@ -797,9 +738,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if motion_detection_enabled := self.motion_detection_enabled:
attrs["motion_detection"] = motion_detection_enabled
if frontend_stream_type := self.frontend_stream_type:
attrs["frontend_stream_type"] = frontend_stream_type
return attrs
@callback
@@ -823,9 +761,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
providers or inputs to the state attributes change.
"""
old_provider = self._webrtc_provider
old_legacy_provider = self._legacy_webrtc_provider
new_provider = None
new_legacy_provider = None
# Skip all providers if the camera has a native WebRTC implementation
if not (
@@ -836,15 +772,8 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
async_get_supported_provider
)
if new_provider is None:
# Only add the legacy provider if the new provider is not available
new_legacy_provider = await self._async_get_supported_webrtc_provider(
async_get_supported_legacy_provider
)
if old_provider != new_provider or old_legacy_provider != new_legacy_provider:
if old_provider != new_provider:
self._webrtc_provider = new_provider
self._legacy_webrtc_provider = new_legacy_provider
self._invalidate_camera_capabilities_cache()
if write_state:
self.async_write_ha_state()
@@ -879,10 +808,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
]
config.configuration.ice_servers.extend(ice_servers)
config.get_candidates_upfront = (
self._supports_native_sync_webrtc
or self._legacy_webrtc_provider is not None
)
config.get_candidates_upfront = self._supports_native_sync_webrtc
return config
@@ -918,7 +844,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
else:
frontend_stream_types.add(StreamType.HLS)
if self._webrtc_provider or self._legacy_webrtc_provider:
if self._webrtc_provider:
frontend_stream_types.add(StreamType.WEB_RTC)
return CameraCapabilities(frontend_stream_types)

View File

@@ -46,10 +46,6 @@
}
}
}
},
"legacy_webrtc_provider": {
"title": "Detected use of legacy WebRTC provider registered by {legacy_integration}",
"description": "The {legacy_integration} integration has registered a legacy WebRTC provider. Home Assistant prefers using the built-in modern WebRTC provider registered by the {builtin_integration} integration.\n\nBenefits of the built-in integration are:\n\n- The camera stream is started faster.\n- More camera devices are supported.\n\nTo fix this issue, you can either keep using the built-in modern WebRTC provider and remove the {legacy_integration} integration or remove the {builtin_integration} integration to use the legacy provider, and then restart Home Assistant."
}
},
"services": {

View File

@@ -8,7 +8,7 @@ from collections.abc import Awaitable, Callable, Iterable
from dataclasses import asdict, dataclass, field
from functools import cache, partial, wraps
import logging
from typing import TYPE_CHECKING, Any, Protocol
from typing import TYPE_CHECKING, Any
from mashumaro import MissingField
import voluptuous as vol
@@ -22,8 +22,7 @@ from webrtc_models import (
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.deprecation import deprecated_function
from homeassistant.helpers import config_validation as cv
from homeassistant.util.hass_dict import HassKey
from homeassistant.util.ulid import ulid
@@ -39,9 +38,6 @@ _LOGGER = logging.getLogger(__name__)
DATA_WEBRTC_PROVIDERS: HassKey[set[CameraWebRTCProvider]] = HassKey(
"camera_webrtc_providers"
)
DATA_WEBRTC_LEGACY_PROVIDERS: HassKey[dict[str, CameraWebRTCLegacyProvider]] = HassKey(
"camera_webrtc_legacy_providers"
)
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
"camera_webrtc_ice_servers"
)
@@ -163,18 +159,6 @@ class CameraWebRTCProvider(ABC):
return ## This is an optional method so we need a default here.
class CameraWebRTCLegacyProvider(Protocol):
"""WebRTC provider."""
async def async_is_supported(self, stream_source: str) -> bool:
"""Determine if the provider supports the stream source."""
async def async_handle_web_rtc_offer(
self, camera: Camera, offer_sdp: str
) -> str | None:
"""Handle the WebRTC offer and return an answer."""
@callback
def async_register_webrtc_provider(
hass: HomeAssistant,
@@ -204,8 +188,6 @@ def async_register_webrtc_provider(
async def _async_refresh_providers(hass: HomeAssistant) -> None:
"""Check all cameras for any state changes for registered providers."""
_async_check_conflicting_legacy_provider(hass)
component = hass.data[DATA_COMPONENT]
await asyncio.gather(
*(camera.async_refresh_providers() for camera in component.entities)
@@ -380,21 +362,6 @@ async def async_get_supported_provider(
return None
async def async_get_supported_legacy_provider(
hass: HomeAssistant, camera: Camera
) -> CameraWebRTCLegacyProvider | None:
"""Return the first supported provider for the camera."""
providers = hass.data.get(DATA_WEBRTC_LEGACY_PROVIDERS)
if not providers or not (stream_source := await camera.stream_source()):
return None
for provider in providers.values():
if await provider.async_is_supported(stream_source):
return provider
return None
@callback
def async_register_ice_servers(
hass: HomeAssistant,
@@ -411,94 +378,3 @@ def async_register_ice_servers(
servers.append(get_ice_server_fn)
return remove
# The following code is legacy code that was introduced with rtsp_to_webrtc and will be deprecated/removed in the future.
# Left it so custom integrations can still use it.
_RTSP_PREFIXES = {"rtsp://", "rtsps://", "rtmp://"}
# An RtspToWebRtcProvider accepts these inputs:
# stream_source: The RTSP url
# offer_sdp: The WebRTC SDP offer
# stream_id: A unique id for the stream, used to update an existing source
# The output is the SDP answer, or None if the source or offer is not eligible.
# The Callable may throw HomeAssistantError on failure.
type RtspToWebRtcProviderType = Callable[[str, str, str], Awaitable[str | None]]
class _CameraRtspToWebRTCProvider(CameraWebRTCLegacyProvider):
def __init__(self, fn: RtspToWebRtcProviderType) -> None:
"""Initialize the RTSP to WebRTC provider."""
self._fn = fn
async def async_is_supported(self, stream_source: str) -> bool:
"""Return if this provider is supports the Camera as source."""
return any(stream_source.startswith(prefix) for prefix in _RTSP_PREFIXES)
async def async_handle_web_rtc_offer(
self, camera: Camera, offer_sdp: str
) -> str | None:
"""Handle the WebRTC offer and return an answer."""
if not (stream_source := await camera.stream_source()):
return None
return await self._fn(stream_source, offer_sdp, camera.entity_id)
@deprecated_function("async_register_webrtc_provider", breaks_in_ha_version="2025.6")
def async_register_rtsp_to_web_rtc_provider(
hass: HomeAssistant,
domain: str,
provider: RtspToWebRtcProviderType,
) -> Callable[[], None]:
"""Register an RTSP to WebRTC provider.
The first provider to satisfy the offer will be used.
"""
if DOMAIN not in hass.data:
raise ValueError("Unexpected state, camera not loaded")
legacy_providers = hass.data.setdefault(DATA_WEBRTC_LEGACY_PROVIDERS, {})
if domain in legacy_providers:
raise ValueError("Provider already registered")
provider_instance = _CameraRtspToWebRTCProvider(provider)
@callback
def remove_provider() -> None:
legacy_providers.pop(domain)
hass.async_create_task(_async_refresh_providers(hass))
legacy_providers[domain] = provider_instance
hass.async_create_task(_async_refresh_providers(hass))
return remove_provider
@callback
def _async_check_conflicting_legacy_provider(hass: HomeAssistant) -> None:
"""Check if a legacy provider is registered together with the builtin provider."""
builtin_provider_domain = "go2rtc"
if (
(legacy_providers := hass.data.get(DATA_WEBRTC_LEGACY_PROVIDERS))
and (providers := hass.data.get(DATA_WEBRTC_PROVIDERS))
and any(provider.domain == builtin_provider_domain for provider in providers)
):
for domain in legacy_providers:
ir.async_create_issue(
hass,
DOMAIN,
f"legacy_webrtc_provider_{domain}",
is_fixable=False,
is_persistent=False,
issue_domain=domain,
learn_more_url="https://www.home-assistant.io/integrations/go2rtc/",
severity=ir.IssueSeverity.WARNING,
translation_key="legacy_webrtc_provider",
translation_placeholders={
"legacy_integration": domain,
"builtin_integration": builtin_provider_domain,
},
)

View File

@@ -10,12 +10,12 @@
"known_hosts": "Add known host"
},
"data_description": {
"known_hosts": "Hostnames or IP-addresses of cast devices, use if mDNS discovery is not working"
"known_hosts": "Hostnames or IP addresses of cast devices, use if mDNS discovery is not working"
}
}
},
"error": {
"invalid_known_hosts": "Known hosts must be a comma separated list of hosts."
"invalid_known_hosts": "Known hosts must be a comma-separated list of hosts."
}
},
"options": {

View File

@@ -61,7 +61,6 @@ from .const import (
CONF_RELAYER_SERVER,
CONF_REMOTESTATE_SERVER,
CONF_SERVICEHANDLERS_SERVER,
CONF_THINGTALK_SERVER,
CONF_USER_POOL_ID,
DATA_CLOUD,
DATA_CLOUD_LOG_HANDLER,
@@ -134,7 +133,6 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_CLOUDHOOK_SERVER): str,
vol.Optional(CONF_RELAYER_SERVER): str,
vol.Optional(CONF_REMOTESTATE_SERVER): str,
vol.Optional(CONF_THINGTALK_SERVER): str,
vol.Optional(CONF_SERVICEHANDLERS_SERVER): str,
}
)

View File

@@ -26,7 +26,11 @@ from homeassistant.core import Context, HassJob, HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from homeassistant.util.aiohttp import MockRequest, serialize_response
from . import alexa_config, google_config
@@ -36,6 +40,7 @@ from .prefs import CloudPreferences
_LOGGER = logging.getLogger(__name__)
VALID_REPAIR_TRANSLATION_KEYS = {
"no_subscription",
"warn_bad_custom_domain_configuration",
"reset_bad_custom_domain_configuration",
}
@@ -409,3 +414,7 @@ class CloudClient(Interface):
severity=IssueSeverity(severity),
is_fixable=False,
)
async def async_delete_repair_issue(self, identifier: str) -> None:
"""Delete a repair issue."""
async_delete_issue(hass=self._hass, domain=DOMAIN, issue_id=identifier)

View File

@@ -81,7 +81,6 @@ CONF_ACME_SERVER = "acme_server"
CONF_CLOUDHOOK_SERVER = "cloudhook_server"
CONF_RELAYER_SERVER = "relayer_server"
CONF_REMOTESTATE_SERVER = "remotestate_server"
CONF_THINGTALK_SERVER = "thingtalk_server"
CONF_SERVICEHANDLERS_SERVER = "servicehandlers_server"
MODE_DEV = "development"

View File

@@ -16,7 +16,7 @@ from typing import Any, Concatenate, cast
import aiohttp
from aiohttp import web
import attr
from hass_nabucasa import AlreadyConnectedError, Cloud, auth, thingtalk
from hass_nabucasa import AlreadyConnectedError, Cloud, auth
from hass_nabucasa.const import STATE_DISCONNECTED
from hass_nabucasa.voice_data import TTS_VOICES
import voluptuous as vol
@@ -104,7 +104,6 @@ def async_setup(hass: HomeAssistant) -> None:
websocket_api.async_register_command(hass, alexa_list)
websocket_api.async_register_command(hass, alexa_sync)
websocket_api.async_register_command(hass, thingtalk_convert)
websocket_api.async_register_command(hass, tts_info)
hass.http.register_view(GoogleActionsSyncView)
@@ -998,25 +997,6 @@ async def alexa_sync(
)
@websocket_api.websocket_command({"type": "cloud/thingtalk/convert", "query": str})
@websocket_api.async_response
async def thingtalk_convert(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Convert a query."""
cloud = hass.data[DATA_CLOUD]
async with asyncio.timeout(10):
try:
connection.send_result(
msg["id"], await thingtalk.async_convert(cloud, msg["query"])
)
except thingtalk.ThingTalkConversionError as err:
connection.send_error(msg["id"], websocket_api.ERR_UNKNOWN_ERROR, str(err))
@websocket_api.websocket_command({"type": "cloud/tts/info"})
def tts_info(
hass: HomeAssistant,

View File

@@ -13,6 +13,6 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==0.96.0"],
"requirements": ["hass-nabucasa==0.100.0"],
"single_config_entry": true
}

View File

@@ -62,6 +62,10 @@
}
}
},
"no_subscription": {
"title": "No subscription detected",
"description": "You do not have a Home Assistant Cloud subscription. Subscribe at {account_url}."
},
"warn_bad_custom_domain_configuration": {
"title": "Detected wrong custom domain configuration",
"description": "The DNS configuration for your custom domain ({custom_domains}) is not correct. Please check the DNS configuration of your domain and make sure it points to the correct CNAME."

View File

@@ -77,6 +77,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: ComelitConfigEntry) ->
coordinator = entry.runtime_data
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
await coordinator.api.logout()
await coordinator.api.close()
return unload_ok

View File

@@ -73,7 +73,6 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
) from err
finally:
await api.logout()
await api.close()
return {"title": data[CONF_HOST]}

View File

@@ -65,8 +65,8 @@ rules:
status: todo
comment: missing implementation
entity-category:
status: todo
comment: PR in progress
status: exempt
comment: no config or diagnostic entities
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done

View File

@@ -76,7 +76,7 @@
"cannot_authenticate": {
"message": "Error authenticating"
},
"updated_failed": {
"update_failed": {
"message": "Failed to update data: {error}"
}
}

View File

@@ -165,9 +165,7 @@ class ConfigManagerFlowIndexView(
"""Not implemented."""
raise aiohttp.web_exceptions.HTTPMethodNotAllowed("GET", ["POST"])
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission="add")
@RequestDataValidator(
vol.Schema(
{
@@ -218,16 +216,12 @@ class ConfigManagerFlowResourceView(
url = "/api/config/config_entries/flow/{flow_id}"
name = "api:config:config_entries:flow:resource"
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission="add")
async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
"""Get the current state of a data_entry_flow."""
return await super().get(request, flow_id)
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission="add")
async def post(self, request: web.Request, flow_id: str) -> web.Response:
"""Handle a POST request."""
return await super().post(request, flow_id)
@@ -262,9 +256,7 @@ class OptionManagerFlowIndexView(
url = "/api/config/config_entries/options/flow"
name = "api:config:config_entries:option:flow"
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
async def post(self, request: web.Request) -> web.Response:
"""Handle a POST request.
@@ -281,16 +273,12 @@ class OptionManagerFlowResourceView(
url = "/api/config/config_entries/options/flow/{flow_id}"
name = "api:config:config_entries:options:flow:resource"
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
"""Get the current state of a data_entry_flow."""
return await super().get(request, flow_id)
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
async def post(self, request: web.Request, flow_id: str) -> web.Response:
"""Handle a POST request."""
return await super().post(request, flow_id)
@@ -304,9 +292,7 @@ class SubentryManagerFlowIndexView(
url = "/api/config/config_entries/subentries/flow"
name = "api:config:config_entries:subentries:flow"
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
@RequestDataValidator(
vol.Schema(
{
@@ -341,16 +327,12 @@ class SubentryManagerFlowResourceView(
url = "/api/config/config_entries/subentries/flow/{flow_id}"
name = "api:config:config_entries:subentries:flow:resource"
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
"""Get the current state of a data_entry_flow."""
return await super().get(request, flow_id)
@require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
)
@require_admin(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
async def post(self, request: web.Request, flow_id: str) -> web.Response:
"""Handle a POST request."""
return await super().post(request, flow_id)

View File

@@ -9,12 +9,13 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api import ERR_NOT_FOUND, require_admin
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant, callback, split_entity_id
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.entity_component import async_get_entity_suggested_object_id
from homeassistant.helpers.json import json_dumps
@@ -22,6 +23,7 @@ from homeassistant.helpers.json import json_dumps
def async_setup(hass: HomeAssistant) -> bool:
"""Enable the Entity Registry views."""
websocket_api.async_register_command(hass, websocket_get_automatic_entity_ids)
websocket_api.async_register_command(hass, websocket_get_entities)
websocket_api.async_register_command(hass, websocket_get_entity)
websocket_api.async_register_command(hass, websocket_list_entities_for_display)
@@ -316,3 +318,43 @@ def websocket_remove_entity(
registry.async_remove(msg["entity_id"])
connection.send_message(websocket_api.result_message(msg["id"]))
@websocket_api.websocket_command(
{
vol.Required("type"): "config/entity_registry/get_automatic_entity_ids",
vol.Required("entity_ids"): cv.entity_ids,
}
)
@callback
def websocket_get_automatic_entity_ids(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Return the automatic entity IDs for the given entity IDs.
This is used to help user reset entity IDs which have been customized by the user.
"""
registry = er.async_get(hass)
entity_ids = msg["entity_ids"]
automatic_entity_ids: dict[str, str | None] = {}
for entity_id in entity_ids:
if not (entry := registry.entities.get(entity_id)):
automatic_entity_ids[entity_id] = None
continue
if (
suggested := async_get_entity_suggested_object_id(hass, entity_id)
) == split_entity_id(entry.entity_id)[1]:
# No need to generate a new entity ID
automatic_entity_ids[entity_id] = None
continue
automatic_entity_ids[entity_id] = registry.async_generate_entity_id(
entry.domain,
suggested or f"{entry.platform}_{entry.unique_id}",
)
connection.send_message(
websocket_api.result_message(msg["id"], automatic_entity_ids)
)

View File

@@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DANFOSS_AIR_DOMAIN
from . import DOMAIN
def setup_platform(
@@ -22,7 +22,7 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the available Danfoss Air sensors etc."""
data = hass.data[DANFOSS_AIR_DOMAIN]
data = hass.data[DOMAIN]
sensors = [
[

View File

@@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DANFOSS_AIR_DOMAIN
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -28,7 +28,7 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the available Danfoss Air sensors etc."""
data = hass.data[DANFOSS_AIR_DOMAIN]
data = hass.data[DOMAIN]
sensors = [
[

View File

@@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DANFOSS_AIR_DOMAIN
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -24,7 +24,7 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Danfoss Air HRV switch platform."""
data = hass.data[DANFOSS_AIR_DOMAIN]
data = hass.data[DOMAIN]
switches = [
[

View File

@@ -13,7 +13,7 @@ from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE, DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import DOMAIN as DECONZ_DOMAIN
from .const import DOMAIN
from .hub import DeconzHub
from .util import serial_from_unique_id
@@ -59,12 +59,12 @@ class DeconzBase[_DeviceT: _DeviceType]:
return DeviceInfo(
connections={(CONNECTION_ZIGBEE, self.serial)},
identifiers={(DECONZ_DOMAIN, self.serial)},
identifiers={(DOMAIN, self.serial)},
manufacturer=self._device.manufacturer,
model=self._device.model_id,
name=self._device.name,
sw_version=self._device.software_version,
via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
via_device=(DOMAIN, self.hub.api.config.bridge_id),
)
@@ -176,9 +176,9 @@ class DeconzSceneMixin(DeconzDevice[PydeconzScene]):
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
return DeviceInfo(
identifiers={(DECONZ_DOMAIN, self._group_identifier)},
identifiers={(DOMAIN, self._group_identifier)},
manufacturer="Dresden Elektronik",
model="deCONZ group",
name=self.group.name,
via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
via_device=(DOMAIN, self.hub.api.config.bridge_id),
)

View File

@@ -38,7 +38,7 @@ from homeassistant.util.color import (
)
from . import DeconzConfigEntry
from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS
from .const import DOMAIN, POWER_PLUGS
from .entity import DeconzDevice
from .hub import DeconzHub
@@ -395,11 +395,11 @@ class DeconzGroup(DeconzBaseLight[Group]):
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
return DeviceInfo(
identifiers={(DECONZ_DOMAIN, self.unique_id)},
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Dresden Elektronik",
model="deCONZ group",
name=self._device.name,
via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
via_device=(DOMAIN, self.hub.api.config.bridge_id),
)
@property

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/denonavr",
"iot_class": "local_push",
"loggers": ["denonavr"],
"requirements": ["denonavr==1.0.1"],
"requirements": ["denonavr==1.1.0"],
"ssdp": [
{
"manufacturer": "Denon",

View File

@@ -15,7 +15,7 @@
},
"data_description": {
"round": "Controls the number of decimal digits in the output.",
"time_window": "If set, the sensor's value is a time weighted moving average of derivatives within this window.",
"time_window": "If set, the sensor's value is a time-weighted moving average of derivatives within this window.",
"unit_prefix": "The output will be scaled according to the selected metric prefix and time unit of the derivative."
}
}

View File

@@ -15,7 +15,7 @@
"quality_scale": "internal",
"requirements": [
"aiodhcpwatcher==1.1.1",
"aiodiscover==2.6.1",
"aiodiscover==2.7.0",
"cached-ipaddress==0.10.0"
]
}

View File

@@ -68,7 +68,7 @@ async def async_validate_hostname(
result = False
with contextlib.suppress(DNSError):
result = bool(
await aiodns.DNSResolver(
await aiodns.DNSResolver( # type: ignore[call-overload]
nameservers=[resolver], udp_port=port, tcp_port=port
).query(hostname, qtype)
)

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dnsip",
"iot_class": "cloud_polling",
"requirements": ["aiodns==3.3.0"]
"requirements": ["aiodns==3.4.0"]
}

View File

@@ -106,7 +106,7 @@ class WanIpSensor(SensorEntity):
async def async_update(self) -> None:
"""Get the current DNS IP address for hostname."""
try:
response = await self.resolver.query(self.hostname, self.querytype)
response = await self.resolver.query(self.hostname, self.querytype) # type: ignore[call-overload]
except DNSError as err:
_LOGGER.warning("Exception while resolving host: %s", err)
response = None

View File

@@ -3,10 +3,10 @@
"step": {
"init": {
"data": {
"events": "Comma separated list of events."
"events": "Comma-separated list of events."
},
"data_description": {
"events": "Add a comma separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion"
"events": "Add a comma-separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion"
}
}
}

View File

@@ -8,7 +8,7 @@ from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DOVADO_DOMAIN
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -19,7 +19,7 @@ def get_service(
discovery_info: DiscoveryInfoType | None = None,
) -> DovadoSMSNotificationService:
"""Get the Dovado Router SMS notification service."""
return DovadoSMSNotificationService(hass.data[DOVADO_DOMAIN].client)
return DovadoSMSNotificationService(hass.data[DOMAIN].client)
class DovadoSMSNotificationService(BaseNotificationService):

View File

@@ -20,7 +20,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DOVADO_DOMAIN
from . import DOMAIN
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
@@ -90,7 +90,7 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Dovado sensor platform."""
dovado = hass.data[DOVADO_DOMAIN]
dovado = hass.data[DOMAIN]
sensors = config[CONF_SENSORS]
entities = [

View File

@@ -3,12 +3,12 @@
"step": {
"user": {
"data": {
"phone_number": "Phone Number"
"phone_number": "Phone number"
}
},
"one_time_password": {
"data": {
"one_time_password": "One Time Password"
"one_time_password": "One-time password"
}
}
},

View File

@@ -7,12 +7,12 @@
"step": {
"user": {
"data": {
"advertise_ip": "Advertise IP Address",
"advertise_port": "Advertise Port",
"host_ip": "Host IP Address",
"listen_port": "Listen Port",
"advertise_ip": "Advertise IP address",
"advertise_port": "Advertise port",
"host_ip": "Host IP address",
"listen_port": "Listen port",
"name": "[%key:common::config_flow::data::name%]",
"upnp_bind_multicast": "Bind multicast (True/False)"
"upnp_bind_multicast": "Bind multicast"
},
"title": "Define server configuration"
}

View File

@@ -52,6 +52,7 @@ VALID_ENERGY_UNITS_GAS = {
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS,
*VALID_ENERGY_UNITS,
}
VALID_VOLUME_UNITS_WATER: set[str] = {

View File

@@ -50,6 +50,7 @@ GAS_USAGE_UNITS: dict[str, tuple[UnitOfEnergy | UnitOfVolume, ...]] = {
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS,
),
}
GAS_PRICE_UNITS = tuple(

View File

@@ -64,7 +64,7 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
"/ivp/ensemble/generator",
"/ivp/meters",
"/ivp/meters/readings",
"/home,",
"/home",
]
for end_point in end_points:

View File

@@ -7,7 +7,7 @@
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"quality_scale": "platinum",
"requirements": ["pyenphase==1.26.0"],
"requirements": ["pyenphase==1.26.1"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."

View File

@@ -134,6 +134,22 @@ def esphome_state_property[_R, _EntityT: EsphomeEntity[Any, Any]](
return _wrapper
def async_esphome_state_property[_R, _EntityT: EsphomeEntity[Any, Any]](
func: Callable[[_EntityT], Awaitable[_R | None]],
) -> Callable[[_EntityT], Coroutine[Any, Any, _R | None]]:
"""Wrap a state property of an esphome entity.
This checks if the state object in the entity is set
and returns None if it is not set.
"""
@functools.wraps(func)
async def _wrapper(self: _EntityT) -> _R | None:
return await func(self) if self._has_state else None
return _wrapper
def esphome_float_state_property[_EntityT: EsphomeEntity[Any, Any]](
func: Callable[[_EntityT], float | None],
) -> Callable[[_EntityT], float | None]:

View File

@@ -8,6 +8,7 @@ from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
from functools import partial
import logging
from operator import delitem
from typing import TYPE_CHECKING, Any, Final, TypedDict, cast
from aioesphomeapi import (
@@ -183,18 +184,7 @@ class RuntimeEntryData:
"""Register to receive callbacks when static info changes for an EntityInfo type."""
callbacks = self.entity_info_callbacks.setdefault(entity_info_type, [])
callbacks.append(callback_)
return partial(
self._async_unsubscribe_register_static_info, callbacks, callback_
)
@callback
def _async_unsubscribe_register_static_info(
self,
callbacks: list[Callable[[list[EntityInfo]], None]],
callback_: Callable[[list[EntityInfo]], None],
) -> None:
"""Unsubscribe to when static info is registered."""
callbacks.remove(callback_)
return partial(callbacks.remove, callback_)
@callback
def async_register_key_static_info_updated_callback(
@@ -206,18 +196,7 @@ class RuntimeEntryData:
callback_key = (type(static_info), static_info.key)
callbacks = self.entity_info_key_updated_callbacks.setdefault(callback_key, [])
callbacks.append(callback_)
return partial(
self._async_unsubscribe_static_key_info_updated, callbacks, callback_
)
@callback
def _async_unsubscribe_static_key_info_updated(
self,
callbacks: list[Callable[[EntityInfo], None]],
callback_: Callable[[EntityInfo], None],
) -> None:
"""Unsubscribe to when static info is updated ."""
callbacks.remove(callback_)
return partial(callbacks.remove, callback_)
@callback
def async_set_assist_pipeline_state(self, state: bool) -> None:
@@ -232,14 +211,7 @@ class RuntimeEntryData:
) -> CALLBACK_TYPE:
"""Subscribe to assist pipeline updates."""
self.assist_pipeline_update_callbacks.append(update_callback)
return partial(self._async_unsubscribe_assist_pipeline_update, update_callback)
@callback
def _async_unsubscribe_assist_pipeline_update(
self, update_callback: CALLBACK_TYPE
) -> None:
"""Unsubscribe to assist pipeline updates."""
self.assist_pipeline_update_callbacks.remove(update_callback)
return partial(self.assist_pipeline_update_callbacks.remove, update_callback)
@callback
def async_remove_entities(
@@ -337,12 +309,7 @@ class RuntimeEntryData:
def async_subscribe_device_updated(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
"""Subscribe to state updates."""
self.device_update_subscriptions.add(callback_)
return partial(self._async_unsubscribe_device_update, callback_)
@callback
def _async_unsubscribe_device_update(self, callback_: CALLBACK_TYPE) -> None:
"""Unsubscribe to device updates."""
self.device_update_subscriptions.remove(callback_)
return partial(self.device_update_subscriptions.remove, callback_)
@callback
def async_subscribe_static_info_updated(
@@ -350,14 +317,7 @@ class RuntimeEntryData:
) -> CALLBACK_TYPE:
"""Subscribe to static info updates."""
self.static_info_update_subscriptions.add(callback_)
return partial(self._async_unsubscribe_static_info_updated, callback_)
@callback
def _async_unsubscribe_static_info_updated(
self, callback_: Callable[[list[EntityInfo]], None]
) -> None:
"""Unsubscribe to static info updates."""
self.static_info_update_subscriptions.remove(callback_)
return partial(self.static_info_update_subscriptions.remove, callback_)
@callback
def async_subscribe_state_update(
@@ -369,14 +329,7 @@ class RuntimeEntryData:
"""Subscribe to state updates."""
subscription_key = (state_type, state_key)
self.state_subscriptions[subscription_key] = entity_callback
return partial(self._async_unsubscribe_state_update, subscription_key)
@callback
def _async_unsubscribe_state_update(
self, subscription_key: tuple[type[EntityState], int]
) -> None:
"""Unsubscribe to state updates."""
self.state_subscriptions.pop(subscription_key)
return partial(delitem, self.state_subscriptions, subscription_key)
@callback
def async_update_state(self, state: EntityState) -> None:
@@ -523,7 +476,7 @@ class RuntimeEntryData:
) -> CALLBACK_TYPE:
"""Register to receive callbacks when the Assist satellite's configuration is updated."""
self.assist_satellite_config_update_callbacks.append(callback_)
return lambda: self.assist_satellite_config_update_callbacks.remove(callback_)
return partial(self.assist_satellite_config_update_callbacks.remove, callback_)
@callback
def async_assist_satellite_config_updated(
@@ -540,7 +493,7 @@ class RuntimeEntryData:
) -> CALLBACK_TYPE:
"""Register to receive callbacks when the Assist satellite's wake word is set."""
self.assist_satellite_set_wake_word_callbacks.append(callback_)
return lambda: self.assist_satellite_set_wake_word_callbacks.remove(callback_)
return partial(self.assist_satellite_set_wake_word_callbacks.remove, callback_)
@callback
def async_assist_satellite_set_wake_word(self, wake_word_id: str) -> None:

View File

@@ -17,7 +17,7 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==30.1.0",
"aioesphomeapi==30.2.0",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==2.15.1"
],

View File

@@ -195,7 +195,10 @@
"message": "Error compiling {configuration}; Try again in ESPHome dashboard for more information."
},
"error_uploading": {
"message": "Error during OTA of {configuration}; Try again in ESPHome dashboard for more information."
"message": "Error during OTA (Over-The-Air) of {configuration}; Try again in ESPHome dashboard for more information."
},
"ota_in_progress": {
"message": "An OTA (Over-The-Air) update is already in progress for {configuration}."
}
}
}

View File

@@ -31,6 +31,7 @@ from .coordinator import ESPHomeDashboardCoordinator
from .dashboard import async_get_dashboard
from .entity import (
EsphomeEntity,
async_esphome_state_property,
convert_api_error_ha_error,
esphome_state_property,
platform_async_setup_entry,
@@ -125,21 +126,17 @@ class ESPHomeDashboardUpdateEntity(
(dr.CONNECTION_NETWORK_MAC, entry_data.device_info.mac_address)
}
)
self._install_lock = asyncio.Lock()
self._available_future: asyncio.Future[None] | None = None
self._update_attrs()
@callback
def _update_attrs(self) -> None:
"""Update the supported features."""
# If the device has deep sleep, we can't assume we can install updates
# as the ESP will not be connectable (by design).
coordinator = self.coordinator
device_info = self._device_info
# Install support can change at run time
if (
coordinator.last_update_success
and coordinator.supports_update
and not device_info.has_deep_sleep
):
if coordinator.last_update_success and coordinator.supports_update:
self._attr_supported_features = UpdateEntityFeature.INSTALL
else:
self._attr_supported_features = NO_FEATURES
@@ -178,6 +175,13 @@ class ESPHomeDashboardUpdateEntity(
self, static_info: list[EntityInfo] | None = None
) -> None:
"""Handle updated data from the device."""
if (
self._entry_data.available
and self._available_future
and not self._available_future.done()
):
self._available_future.set_result(None)
self._available_future = None
self._update_attrs()
self.async_write_ha_state()
@@ -192,17 +196,46 @@ class ESPHomeDashboardUpdateEntity(
entry_data.async_subscribe_device_updated(self._handle_device_update)
)
async def async_will_remove_from_hass(self) -> None:
"""Handle entity about to be removed from Home Assistant."""
if self._available_future and not self._available_future.done():
self._available_future.cancel()
self._available_future = None
async def _async_wait_available(self) -> None:
"""Wait until the device is available."""
# If the device has deep sleep, we need to wait for it to wake up
# and connect to the network to be able to install the update.
if self._entry_data.available:
return
self._available_future = self.hass.loop.create_future()
try:
await self._available_future
finally:
self._available_future = None
async def async_install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
async with self.hass.data.setdefault(KEY_UPDATE_LOCK, asyncio.Lock()):
coordinator = self.coordinator
api = coordinator.api
device = coordinator.data.get(self._device_info.name)
assert device is not None
configuration = device["configuration"]
try:
if self._install_lock.locked():
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="ota_in_progress",
translation_placeholders={
"configuration": self._device_info.name,
},
)
# Ensure only one OTA per device at a time
async with self._install_lock:
# Ensure only one compile at a time for ALL devices
async with self.hass.data.setdefault(KEY_UPDATE_LOCK, asyncio.Lock()):
coordinator = self.coordinator
api = coordinator.api
device = coordinator.data.get(self._device_info.name)
assert device is not None
configuration = device["configuration"]
if not await api.compile(configuration):
raise HomeAssistantError(
translation_domain=DOMAIN,
@@ -211,14 +244,25 @@ class ESPHomeDashboardUpdateEntity(
"configuration": configuration,
},
)
if not await api.upload(configuration, "OTA"):
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="error_uploading",
translation_placeholders={
"configuration": configuration,
},
)
# If the device uses deep sleep, there's a small chance it goes
# to sleep right after the dashboard connects but before the OTA
# starts. In that case, the update won't go through, so we try
# again to catch it on its next wakeup.
attempts = 2 if self._device_info.has_deep_sleep else 1
try:
for attempt in range(1, attempts + 1):
await self._async_wait_available()
if await api.upload(configuration, "OTA"):
break
if attempt == attempts:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="error_uploading",
translation_placeholders={
"configuration": configuration,
},
)
finally:
await self.coordinator.async_request_refresh()
@@ -227,7 +271,9 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
"""A update implementation for esphome."""
_attr_supported_features = (
UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
UpdateEntityFeature.INSTALL
| UpdateEntityFeature.PROGRESS
| UpdateEntityFeature.RELEASE_NOTES
)
@callback
@@ -257,11 +303,12 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
"""Return the latest version."""
return self._state.latest_version
@property
@esphome_state_property
def release_summary(self) -> str:
"""Return the release summary."""
return self._state.release_summary
@async_esphome_state_property
async def async_release_notes(self) -> str | None:
"""Return the release notes."""
if self._state.release_summary:
return self._state.release_summary
return None
@property
@esphome_state_property

View File

@@ -2,8 +2,8 @@
import logging
from pyezviz.client import EzvizClient
from pyezviz.exceptions import (
from pyezvizapi.client import EzvizClient
from pyezvizapi.exceptions import (
EzvizAuthTokenExpired,
EzvizAuthVerificationCode,
HTTPError,

View File

@@ -6,8 +6,8 @@ from dataclasses import dataclass
from datetime import timedelta
import logging
from pyezviz import PyEzvizError
from pyezviz.constants import DefenseModeType
from pyezvizapi import PyEzvizError
from pyezvizapi.constants import DefenseModeType
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,

View File

@@ -6,9 +6,9 @@ from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from pyezviz import EzvizClient
from pyezviz.constants import SupportExt
from pyezviz.exceptions import HTTPError, PyEzvizError
from pyezvizapi import EzvizClient
from pyezvizapi.constants import SupportExt
from pyezvizapi.exceptions import HTTPError, PyEzvizError
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import logging
from pyezviz.exceptions import HTTPError, InvalidHost, PyEzvizError
from pyezvizapi.exceptions import HTTPError, InvalidHost, PyEzvizError
from homeassistant.components import ffmpeg
from homeassistant.components.camera import Camera, CameraEntityFeature

View File

@@ -6,15 +6,15 @@ from collections.abc import Mapping
import logging
from typing import TYPE_CHECKING, Any
from pyezviz.client import EzvizClient
from pyezviz.exceptions import (
from pyezvizapi.client import EzvizClient
from pyezvizapi.exceptions import (
AuthTestResultFailed,
EzvizAuthVerificationCode,
InvalidHost,
InvalidURL,
PyEzvizError,
)
from pyezviz.test_cam_rtsp import TestRTSPAuth
from pyezvizapi.test_cam_rtsp import TestRTSPAuth
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow

View File

@@ -4,8 +4,8 @@ import asyncio
from datetime import timedelta
import logging
from pyezviz.client import EzvizClient
from pyezviz.exceptions import (
from pyezvizapi.client import EzvizClient
from pyezvizapi.exceptions import (
EzvizAuthTokenExpired,
EzvizAuthVerificationCode,
HTTPError,

View File

@@ -5,8 +5,8 @@ from __future__ import annotations
import logging
from propcache.api import cached_property
from pyezviz.exceptions import PyEzvizError
from pyezviz.utils import decrypt_image
from pyezvizapi.exceptions import PyEzvizError
from pyezvizapi.utils import decrypt_image
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
from homeassistant.config_entries import SOURCE_IGNORE

View File

@@ -4,8 +4,8 @@ from __future__ import annotations
from typing import Any
from pyezviz.constants import DeviceCatagories, DeviceSwitchType, SupportExt
from pyezviz.exceptions import HTTPError, PyEzvizError
from pyezvizapi.constants import DeviceCatagories, DeviceSwitchType, SupportExt
from pyezvizapi.exceptions import HTTPError, PyEzvizError
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.core import HomeAssistant, callback

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