Compare commits

..

233 Commits

Author SHA1 Message Date
Erik
6d082a87a4 Include pending_components in WS command get_config 2025-04-01 07:50:27 +02:00
pglab-electronics
c0e8f14745 Update support to external library pypglab to version 0.0.5 (#141876)
update support to external library pypglab to version 0.0.5
2025-03-31 10:25:48 +02:00
elmurato
0488012c77 Add sensor platform to Pterodactyl (#141428)
* Add sensor platform

* Correct CPU Limit state attribute translation

* Remove calculated util entitites, add usage and limit entities

* Use suggested_unit_of_measurement instead of converters

* Start only first word of sensor names in upper case, improve suggested units of sensors

* Simplify update of native_value, set uptime as timestamp

* Add paranthesis around multi-line lambda
2025-03-31 10:23:40 +02:00
J. Nick Koston
f247183e11 Bump SQLAlchemy to 2.0.40 (#141898)
changelog: https://docs.sqlalchemy.org/en/20/changelog/changelog_20.html#change-2.0.40
2025-03-31 10:11:13 +02:00
Norbert Rittel
c662b94d06 Replace "Away" in climate with common state string, matching "Home" (#141897)
* Replace "Away" in `climate` with common state string

Also reordered the states a bit to group the two presence-based options at the top and order the rest alphabetically.

* Prettier
2025-03-31 09:56:10 +02:00
Norbert Rittel
ee4bf165b5 Use common state for "Away" in nobo_hub (#141895) 2025-03-31 08:45:19 +02:00
Norbert Rittel
92ac396d19 Use common state for "Away" in honeywell (#141894) 2025-03-31 08:44:42 +02:00
Norbert Rittel
03366038ce Define "Away" state in plugwise using common string (#141875) 2025-03-31 07:35:03 +02:00
Noah Husby
0b91aa9202 Bump aiorussound to 4.5.0 (#141892) 2025-03-31 07:32:14 +02:00
Norbert Rittel
ffc4fa1c2a Replace "Away" in humidifier with common string (#141872) 2025-03-31 07:29:17 +02:00
Norbert Rittel
15e03957a9 Replace "Away" in generic_thermostat with common string (#141880) 2025-03-31 07:25:19 +02:00
Marc Mueller
0be881bca6 Fix test RuntimeWarnings for homeassistant_hardware (#141884) 2025-03-31 07:24:02 +02:00
Paulus Schoutsen
e88b321741 Ensure user always has first turn for Google Gen AI (#141893) 2025-03-30 23:31:45 -04:00
Allen Porter
0c4cb27fe9 Add OAuth support for Model Context Protocol (mcp) integration (#141874)
* Add authentication support for Model Context Protocol (mcp) integration

* Update homeassistant/components/mcp/application_credentials.py

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Handle MCP servers with ports

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2025-03-30 23:14:52 -04:00
J. Nick Koston
1639163c2e Handle encryption being disabled on an ESPHome device (#141887)
fixes #121442
2025-03-30 21:25:24 -04:00
J. Nick Koston
f043404cd9 Fix duplicate call to async_write_ha_state when adding elkm1 entities (#141890)
When an entity is added state is always written in
add_to_platform_finish:

7336178e03/homeassistant/helpers/entity.py (L1384)

We should not do it in async_added_to_hass as well
2025-03-30 21:23:54 -04:00
J. Nick Koston
018651ff1d Improve handling of empty iterable in async_add_entities (#141889)
* Improve handling of empty iterable in async_add_entities

We had two checks here because we were doing an empty
iterable check. If its a list we can check it directly
but if its not we need to convert it to a list to know
if its empty.

* tweaks

* tasks never used
2025-03-30 21:22:47 -04:00
J. Nick Koston
704d7a037c Bump aioesphomeapi to 29.8.0 (#141888)
changelog: https://github.com/esphome/aioesphomeapi/compare/v29.7.0...v29.8.0
2025-03-30 21:14:17 -04:00
Marc Mueller
7336178e03 Fix test RuntimeWarnings for hassio (#141883) 2025-03-30 12:00:48 -10:00
tdfountain
1c16fb8e42 Set and check unique id of config in NUT (#141783)
* Set and check unique id in config

* Update homeassistant/components/nut/config_flow.py

Set unique ID and abort only if value is defined

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

* Add duplicate ID test case for multiple devices

* Add unique ID check to config flow step for UPS

* Update homeassistant/components/nut/__init__.py

Fix to only set config_entries unique ID if not None

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

* Remove duplicate config flow call

---------

Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-03-30 11:41:56 -10:00
tdfountain
3ab2cd3fb7 Set device connection MAC address for networked devices in NUT (#141856)
* Set device connection MAC address for networked devices

* Change variable name for consistency
2025-03-30 11:21:11 -10:00
Norbert Rittel
5057343b6a Replace "Home" and "Away" in vallox with common strings (#141870) 2025-03-30 22:49:52 +02:00
Norbert Rittel
6c3e85fd5e Replace "Home" and "Away" in reolink with common strings (#141869) 2025-03-30 22:44:48 +02:00
Norbert Rittel
f046456445 Replace "Home" and "Away" in opentherm_gw with common strings (#141867) 2025-03-30 22:36:46 +02:00
tdfountain
e81a08916a Remove scan interval option from NUT (#141845)
Remove scan interval option and test case, migrate config and add migration test case
2025-03-30 22:34:45 +02:00
John Karabudak
85d2e3d006 Fix LLM to speed up prefill (#141156)
* fix: two minor LLM changes to speed up prefill

- moved the current date/time to the end of the prompt
- started sorting all entities by last_changed

* addressed PR comments

* fixed tests

* reduced scope of try/catch in LLM prompt

* addressed more PR comments

* fixed Anthropic test

* addressed another PR comment

* fixed remainder of tests
2025-03-30 13:30:40 -07:00
Norbert Rittel
936b0b32ed Replace "Home" and "Away" in drop_connect with common strings (#141864) 2025-03-30 22:30:08 +02:00
J. Nick Koston
0d511c697c Improve performance of as_compressed_state (#141800)
We have to build all of these at startup.

Its a lot faster to compare floats instead
of datetime objects. Since we already have to
fetch last_changed_timestamp, use it to compare
with last_updated_timestamp since we already know
we will have last_updated_timestamp
2025-03-30 22:20:24 +02:00
Norbert Rittel
5bfe034b4d Replace "Country" with common and pollutant labels with sensor strings (#141863)
* Replace "Country" with common and pollutant labels with `sensor` strings

* Fix copy & paste error for "ozone"
2025-03-30 22:17:51 +02:00
J. Nick Koston
cf786b3b04 Bump google_cloud deps (#141861)
speech: https://github.com/googleapis/google-cloud-python/compare/google-cloud-speech-v2.27.0...google-cloud-speech-v2.31.1
texttospeech: https://github.com/googleapis/google-cloud-python/compare/google-cloud-texttospeech-v2.17.2...google-cloud-texttospeech-v2.25.1
2025-03-30 22:15:19 +02:00
Joost Lekkerkerker
0f9f090db2 Bump pySmartThings to 3.0.1 (#141722) 2025-03-30 21:34:49 +02:00
J. Nick Koston
302eea7418 Bump PyISY to 3.4.0 (#141851)
* Bump PyISY to 3.3.0

changelog: https://github.com/automicus/PyISY/compare/v3.2.0...v3.3.0

* Apply suggestions from code review

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-03-30 22:29:51 +03:00
J. Nick Koston
b5e1f7e03e Cleanup some typing in isy994 (#141859)
Now that pyisy is mostly typed there were some obvious
issues. We are still a long way away from being able
to add py.typed to pyisy, but we can now see some
obvious things in an IDE
2025-03-30 09:18:30 -10:00
Norbert Rittel
02397a8d2d Replace "Off" state in selectors of home_connect with common state (#141857)
* Replace "Off" state in selectors of `home_connect` with common state

* Replace internal with common references
2025-03-30 21:03:46 +02:00
Norbert Rittel
ea9437eab2 Use common state for "Off" in climate selector (#141850)
* Use common states for "Away" and "Off" in `climate`

* Revert common state for "Away"

Four other integrations are referencing this instead of the common state. Needs to be addressed first.
2025-03-30 21:02:54 +02:00
Norbert Rittel
aaea30bee0 Replace "Off" in selector of media_player with common state (#141853) 2025-03-30 21:01:03 +02:00
Joost Lekkerkerker
9c869fa701 Add a coordinator to Point (#126775)
* Add a coordinator to Point

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix
2025-03-30 20:58:40 +02:00
Eli Sand
5106548f2c Fix generic_thermostat so it doesn't turn on when current temp is within target temp range (#138209)
* Don't turn on thermostat if temp is equal to target temp.

* Update strings to reflect logic change.

* Fix logic and add zero tolerance tests.

* Include tests for cool mode

* Removed unnecessary async_block_till_done calls
2025-03-30 19:43:13 +01:00
Franck Nijhof
506d485c0d Ensure EcoNet operation modes are unique (#141689) 2025-03-30 20:31:08 +02:00
Bouwe Westerdijk
da190ec96f Bump plugwise to v1.7.3 (#141843) 2025-03-30 20:24:13 +02:00
Franck Nijhof
9567929484 Update pvo to v2.2.1 (#141847) 2025-03-30 21:12:42 +03:00
Norbert Rittel
dc16494332 Replace "Disabled" with common state in schlage, fix sentence-case (#141849)
Replace "Disabled" with common state in `lamarzocco`, fix sentence-case

- replace "Disabled" with with common state reference
- fix sentence-casing of "Auto-lock"
2025-03-30 21:12:15 +03:00
Norbert Rittel
933f422588 Replace "Disabled" with common state in lamarzocco (#141848) 2025-03-30 20:00:18 +02:00
Michael
663d0691a7 Move setup messages from info to debug level (#141834)
move info to debug level
2025-03-30 19:49:41 +02:00
Norbert Rittel
97a0b9272e Resolve state mismatches in wolflink (#141846) 2025-03-30 19:42:39 +02:00
Lucas Mindêllo de Andrade
3d49000c75 Remove sunweg integration (#124230)
* chore(sunweg): remove sunweg integration

* Update homeassistant/components/sunweg/strings.json

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/sunweg/manifest.json

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* feat: added async remove entry

* Clean setup_entry; add tests

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: abmantis <amfcalt@gmail.com>
2025-03-30 18:11:09 +01:00
Norbert Rittel
68d1a3c0a2 Replace "Off" and references with common state in tesla_fleet (#141840) 2025-03-30 19:06:28 +02:00
Norbert Rittel
b06de7a687 Replace "Off" and references with common state in teslemetry (#141841) 2025-03-30 18:50:58 +02:00
Michael
963ea6141c Fix the entity category for max throughput sensors in AVM Fritz!Box Tools (#141838)
correct the entity category for max throughput sensors
2025-03-30 17:46:03 +02:00
Franck Nijhof
7232d36494 Fix hardcoded UoM for total power sensor for Tuya zndb devices (#141822) 2025-03-30 17:44:48 +02:00
Norbert Rittel
12eb071e8a Replace "Off" with common state in plugwise (#141828) 2025-03-30 17:31:12 +02:00
Paulus Schoutsen
86be626c69 Migrate ESPHome to use token instead of media source ID for legacy Assist Pipelines (#139665)
Migrate legacy ESPHome devices to use TTS token

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-30 16:53:49 +02:00
Mauricio Bonani
ec20e41836 Improve the readability of status messages in NUT (#141335)
Improve the readability of status messages
2025-03-30 16:26:44 +02:00
Michal Schwarz
b3564b6cff Fix order of palettes, presets and playlists in WLED integration (#132207)
* Fix order of palettes, presets and playlists in WLED integration

* fix tests: update palette items order

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-30 16:14:56 +02:00
Marlon
acbee815be Update apsystems library to support battery inverter (#140086)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-30 16:11:22 +02:00
Norbert Rittel
5a1aeff85c Replace "On" and "Off" with common states in rfxtrx (#141835)
Also fix sentence-casing on other "… on" and "… off" states.
2025-03-30 16:10:17 +02:00
Allen Porter
4463e4c42b Move roborock unique id to be based on roborock userid instead of email (#141337)
* Move roborock unique id to be based on roborock userid instead of email

* Remove unnecessary data update

* Update tests

* Add tests coverage for removal of config entry

* Use config entry migration

* Remove unused fixtues

* Remove unnecessary logging
2025-03-30 16:04:28 +02:00
Norbert Rittel
4103ef71c9 Replace "Off" with common state in wyoming (#141832) 2025-03-30 16:02:09 +02:00
Norbert Rittel
dccaa2dd2d Replace "Off" with common state in sleepiq (#141831) 2025-03-30 16:01:15 +02:00
Norbert Rittel
5ac6096e08 Replace "Off" with common state in osoenergy (#141830) 2025-03-30 16:00:50 +02:00
Norbert Rittel
4734a82f99 Replace "Off" with common state in airgradient (#141829)
* Replace "Off" with common state in `airgradient`

Also reference the name for CO2 from the `sensor` integration.

* Replace indirect with direct references
2025-03-30 16:00:11 +02:00
Norbert Rittel
c6c2309dee Replace "Idle" with common state in zha (#141825) 2025-03-30 15:21:13 +02:00
Simone Chemelli
5e1bbd8bff Add full test coverage for Comelit climate platform (#140460)
* Add climate tests for Comelit

* fix climate and humidifier

* fix code and tests

* fix humidifier

* apply review comment

* align post merge

* add more tests

* typo

* apply review comment

* ruff
2025-03-30 15:15:26 +02:00
Michael
4761207097 Add boost preset to AVM Fritz!SmartHome climate entities (#141802)
* add boost preset to climate entities

* add set boost preset test
2025-03-30 15:03:26 +02:00
Simone Chemelli
b4a6ca63b3 Add full test coverage for Comelit sensor platform (#141813) 2025-03-30 15:02:15 +02:00
Jan Bouwhuis
a5b320180a Correct spelling for 'availability` in MQTT translation strings (#141818) 2025-03-30 15:01:06 +02:00
Aidan Timson
bcc767136c Add System Bridge suggested sensor precisions (#141815) 2025-03-30 15:00:38 +02:00
Simone Chemelli
d3257d96d0 Add full test coverage for Comelit light platform (#141736)
* Add full test coverage for Comelit light platform

* cleanup
2025-03-30 14:59:56 +02:00
Martin Hjelmare
89df6a82b0 Bump pydroid-ipcam to 3.0.0 (#141739) 2025-03-30 14:59:13 +02:00
Thomas55555
e725ba403b Bump ical to 9.0.3 (#141805) 2025-03-30 14:58:47 +02:00
Aidan Timson
578fece13e Fix System Bridge wait timeout wait condition (#141811)
* Fix System Bridge wait timeout wait condition

* Add DataMissingException as a timeout condition

* Add tests
2025-03-30 14:57:53 +02:00
Norbert Rittel
0eeb6b5fd5 Replace "Idle" with common state in backup, fix sentence-case (#141814)
* Replace "Idle" with common state in `backup`, fix sentence-case

* Update test_sensors.ambr
2025-03-30 14:56:36 +02:00
Norbert Rittel
ad3f7f041f Replace "Idle" with common state in homekit_controller (#141820) 2025-03-30 15:34:36 +03:00
Norbert Rittel
dce9bfd359 Replace "Idle" with common state in venstar, fix sentence-case (#141819) 2025-03-30 15:34:23 +03:00
Norbert Rittel
8f96ccc835 Fix sentence-casing in a few strings of bmw_connected_drive (#141816)
* Fix sentence-casing in a few strings of `bmw_connected_drive`

Also replace "Standby" state with common string reference.

* Update test_select.ambr
2025-03-30 15:13:35 +03:00
Andrew Sayre
a48dd05035 Refactor registration of HEOS media player entity services (#141666)
Refactor entity service registration
2025-03-30 15:10:05 +03:00
Norbert Rittel
eb90958341 Replace "Stand-by" and "Off" with common states in palazzetti (#141809)
Also fixes the  wrong spelling of "Stand-by" by using "Standby" from the common string.
2025-03-30 15:08:03 +03:00
Norbert Rittel
31ed6a48cb Replace "Standby" with common state in roborock (#141810) 2025-03-30 15:07:43 +03:00
Norbert Rittel
dfa80f0787 Replace "Standby" with common state in knx (#141817)
Also reordered the states alphabetically to improve code readability.
2025-03-30 15:07:08 +03:00
Norbert Rittel
73acfa6a8e Replace "Stand-by" with common state in incomfort (#141807)
Also fixes the  wrong spelling of "Stand-by" by using "Standby" from the common string.
2025-03-30 13:55:06 +02:00
Norbert Rittel
efad20cdff Replace "Standby" and "Idle" with common states in fronius (#141812) 2025-03-30 13:43:33 +02:00
Norbert Rittel
11d68cef54 Replace "Standby" with common state in blue_current (#141806) 2025-03-30 14:14:17 +03:00
J. Nick Koston
9c28e60475 Bump pyisy to 3.2.0 (#141798)
changelog: https://github.com/automicus/PyISY/compare/v3.1.15...v3.2.0

Fixes some tasks missing a strong reference
https://github.com/automicus/PyISY/pull/425

There is a bit of refactoring so I did not tag it for
beta.
2025-03-30 14:06:07 +03:00
Simone Chemelli
9ee79b87ee Add full test coverage for Comelit switch platform (#141738)
* Add full test coverage for Comelit switch platform

* cleanup
2025-03-30 12:10:41 +02:00
Simone Chemelli
600aedc9a1 Add tests for Comelit cover platform (#141740)
* Add  tests for Comelit cover platform

* cleanup up
2025-03-30 12:04:00 +02:00
Norbert Rittel
5b5efb5aaa Replace "Stopped" with common state in smartthings (#141789)
* Replace "Stopped" with common state in `smartthings`

* Replace internal references with common ones
2025-03-30 11:58:17 +02:00
Norbert Rittel
391b3ed1e7 Replace "Stopped" with common state in snoo (#141788)
* Replace "Stopped" with common state in `snoo`

* Replace internal reference with common one
2025-03-30 11:57:15 +02:00
Norbert Rittel
24277259ad Use more common states for ESS and PV in vicare (#141792) 2025-03-30 11:56:50 +02:00
J. Nick Koston
65261de7cc Migrate emulated_roku to use runtime_data to fix flakey tests (#141795) 2025-03-30 11:55:58 +02:00
Norbert Rittel
beb92a7f9c Replace "Charging" state for binary_sensor with common string (#141796) 2025-03-30 11:41:01 +02:00
J. Nick Koston
f1b059c75d Bump PyISY to 3.1.15 (#141778)
changelog: https://github.com/automicus/PyISY/compare/v3.1.14...v3.1.15

fixes #141517
fixes #132279
2025-03-30 11:40:25 +02:00
J. Nick Koston
d4970f81aa Cleanup ESPHome update tests to avoid accessing integration internals (#141786)
We should not access DomainData directly in the test
2025-03-29 22:30:06 -10:00
Norbert Rittel
29219afb7f Replace "Charging" state in renault with common string (#141787) 2025-03-30 10:16:42 +02:00
tdfountain
7fbf15edc9 Add ambient state translations in NUT (#141772)
Add ambient state translations
2025-03-29 21:00:53 -10:00
J. Nick Koston
c8d3fa6768 Small cleanups to the device registry (#141773)
Remove some calls to internal functions that
are now available directly on the devices
and deleted_devices objects

Remove internal functions that are no
longer used
2025-03-29 21:00:13 -10:00
J. Nick Koston
ea5cf3d854 Bump aiohomekit to 3.2.13 (#141764)
changelog: https://github.com/Jc2k/aiohomekit/compare/3.2.8...3.2.13
2025-03-29 20:59:56 -10:00
J. Nick Koston
4a833fb489 Fix blocking late import of httpcore from httpx (#141771)
There is a late import that blocks the event loop
in newer version
9e8ab40369/httpx/_transports/default.py (L75)
2025-03-29 20:59:40 -10:00
Norbert Rittel
02aa823d25 Replace "Stopped" with common state in matter (#141768) 2025-03-30 09:42:48 +03:00
Norbert Rittel
92034aeecc Replace "Opening" / "Closing" with common states in homee (#141766) 2025-03-30 09:42:28 +03:00
Florent Thoumie
9f2232fad1 Bump iaqualink to 0.5.3 (#141709)
* Update to iaqualink 0.5.3 and silence warning

* Update to iaqualink 0.5.3 and silence warning

* Re-add via_device line
2025-03-30 06:49:18 +02:00
tdfountain
2be2d54a5c Replace hard coded attributes with constants for test cases in NUT (#141774)
Replace hard coded attributes with constants
2025-03-29 15:19:41 -10:00
J. Nick Koston
ed99686cc1 Bump propcache to 0.3.1 (#141770)
* Bump propcache to 0.3.1

changelog: https://github.com/aio-libs/propcache/compare/v0.3.0...v0.3.1

* revert
2025-03-29 23:10:08 +00:00
Norbert Rittel
a6c1f1e485 Replace "Opening" / "Closing" with common states in shelly (#141767) 2025-03-30 00:48:28 +03:00
puddly
a219445751 Add helper methods to simplify USB integration testing (#141733)
* Add some helper methods to simplify USB integration testing

* Re-export `usb_device_from_port`
2025-03-29 11:26:37 -10:00
Norbert Rittel
b65b5aacb6 Add common state references to cover, valve and lock (#141754)
* Add common states to `cover`

* @NoRi2909 Add common states to `valve`

* Add common states to `lock`
2025-03-29 17:06:15 -04:00
Norbert Rittel
bcead72265 Replace "Stopped" with common state in traccar_server (#141751) 2025-03-29 17:05:34 -04:00
Benjamin Bender
35b9564ed4 Show external cover art in music-assistant-integration (#141716)
* fix: handling of external album-art in music-assistant-integration

* chore: refinements

* make the image-logic more readable

* fix code comment to be accurate
2025-03-29 17:04:57 -04:00
Florent Thoumie
aba01d4361 Remove iaqualink warning caused by via_device (#141761)
Remove warning caused by via_device
2025-03-29 17:03:35 -04:00
Franck Nijhof
4398af51c8 Fix spamming log message in QNAP (#141752) 2025-03-29 16:57:43 -04:00
Norbert Rittel
83f4f4cc96 Replace "Stopped" with common state in ipp (#141750) 2025-03-29 19:19:56 +01:00
Norbert Rittel
1800e6fb8e Add common states for "Opening" and "Closing" (#141747) 2025-03-29 19:19:30 +01:00
Simone Chemelli
43b83c855f Align code styling in Vodafone Station tests (#141745) 2025-03-29 18:42:12 +01:00
Marcel van der Veldt
20e2de200f Always set pause feature on Music Assistant mediaplayers (#141686) 2025-03-29 18:39:59 +01:00
Josef Zweck
ed4ebe1222 Add unkown to uncalibrated state for tedee (#141262) 2025-03-29 18:38:19 +01:00
Simone Chemelli
4e4446cef4 Fix immediate state update for Comelit (#141735) 2025-03-29 18:22:03 +01:00
Norbert Rittel
e2ff0b265d Replace "Stopped" with common state in prusalink (#141743)
* Replace "Stopped" with common state in `prusalink`

* Sentence-case "Nozzle diameter"
2025-03-29 18:07:38 +01:00
J. Nick Koston
6d48fc183a Fix ESPHome update entities being loaded before device_info is available (#141704)
* Fix ESPHome update entities being loaded before device_info is available

Since we load platforms when restoring config, the update
platform could be loaded before the connection to the
device was finished which meant device_info could still
be empty. Wait until device_info is available to
load the update platform.

fixes #135906

* Apply suggestions from code review

* move comment

* Update entry_data.py

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>

---------

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
2025-03-29 17:53:01 +01:00
J. Nick Koston
ea8392a4a1 Fix ESPHome entities not being removed when the ESPHome config removes an entire platform (#141708)
* Fix old ESPHome entities not being removed when configuration changes

fixes #140756

* make sure all callbacks fire

* make sure all callbacks fire

* make sure all callbacks fire

* make sure all callbacks fire

* revert

* cover
2025-03-29 17:48:51 +01:00
Simone Chemelli
bcd296822d Add full test coverage for Comelit alarm control panel (#141371)
* Add full test coverage for Comelit alarm control panel

* fix methods description

* revert unwanted change

* apply review comment
2025-03-29 17:29:37 +01:00
Jan Bouwhuis
6ee97f341d Improve MQTT translation strings (#141691)
* Improve MQTT options translation string

* more improvements
2025-03-29 17:29:04 +01:00
Norbert Rittel
aa2ab74ee9 Replace "On" and "Off" in airzone_cloud with common states (#141711)
* Replace "On", "Off" and "Stop(ped)" in `airzone_cloud` with common strings

* Revert to "Stop" as mode name by manufacturer

Co-authored-by: acidcoke <acidcoke@acidic.codes>

---------

Co-authored-by: acidcoke <acidcoke@acidic.codes>
2025-03-29 18:05:41 +03:00
Norbert Rittel
49b2ab9889 Replace "Stopped" etc. with common state in teslemetry/tessie/tesla_fleet (#141714)
* Replace "Stopped" with common state in `teslemetry`

* Replace "Disconnected" with common state in `teslemetry`

* Replace "Stopped"/"Disconnected" with common state in `tessie`

* Replace "Stopped", "Connected", "Disconnected" with common state in `tesla_fleet`
2025-03-29 18:03:48 +03:00
Martin Hjelmare
2549e2cc0f Patch Z-Wave platforms in humidifier tests (#141732) 2025-03-29 17:59:13 +03:00
J. Diego Rodríguez Royo
b15fa81a44 Set Home Connect program action field as not required (#141729)
* Set Home Connect program action field as not required

* Remove required field

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-29 15:02:54 +01:00
J. Diego Rodríguez Royo
09f6246d1b Dynamically add Home Connect event sensors (#141198)
* Dynamically add Home Connect event sensors to HA

* Add and remove listeners on paired and depaired events

* Apply suggestion

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

* Update test

* Adjust English

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-29 12:53:34 +01:00
Norbert Rittel
96ff389fd1 Sentence-case "Medium type" in mopeka (#141718) 2025-03-29 10:19:25 +01:00
Joost Lekkerkerker
b55f1df297 Only link the parent device if known in SmartThings (#141719)
Only link the parent device if we know the parent device
2025-03-29 10:18:27 +01:00
Joost Lekkerkerker
d88f7b8600 Only trigger events on button updates in SmartThings (#141720)
Only trigger events on button updates
2025-03-29 10:17:38 +01:00
Norbert Rittel
df2a94bb5b Replace "country" with common string in lg_thinq (#141690) 2025-03-29 00:27:10 +00:00
Norbert Rittel
c4ac492c6e Add common state "Stopped" (#141701) 2025-03-29 00:25:22 +00:00
Franck Nijhof
fcd4d3e2df Add ability to subscribe to own YouTube channels (#141693) 2025-03-28 19:59:24 -04:00
Franck Nijhof
42d6bd3839 Handle invalid JSON errors in AirNow (#141695) 2025-03-28 19:58:41 -04:00
J. Nick Koston
d6b48003b6 Improve performance of websocket_api _state_diff_event (#141696)
We can use last_updated_timestamp for the compare since its always
calculated when the state is created and comparing floats is
much faster than datetime objects
2025-03-28 19:58:12 -04:00
Franck Nijhof
ba8f69d956 Fix Tuya tdq category to pick up temp & humid (#141698) 2025-03-28 19:57:56 -04:00
Norbert Rittel
f22bb72d18 Replace 4 occurrences of "Enable" in teslemetry with common string (#141699) 2025-03-28 23:47:44 +01:00
Marcel van der Veldt
f7a0a9fa41 Bump music assistant client to 1.2.0 (#141668)
* Bump music assistant client to 1.2.0

* Update test fixtures
2025-03-28 22:43:31 +01:00
Norbert Rittel
3795d653c5 Replace "country" with common string in holiday (#141687) 2025-03-28 22:43:00 +01:00
Norbert Rittel
8ee014b855 Fix grammar / sentence-casing in workday (#141682)
* Fix grammar / sentence-casing in `workday`

Also replace "country" with common string.

* Add two more references

* Fix second data description reference

* Add "given" to action description for better translations
2025-03-28 21:22:53 +01:00
Erwin Douna
1ab5bdf85f Tado add proper off state (#135480)
* Add proper off state

* Remove current temp

* Add default frost temp
2025-03-28 20:54:36 +01:00
puddly
5283e1a39f Handle all firmware types for ZBT-1 and Yellow update entities (#141674)
Handle other firmware types
2025-03-28 20:38:16 +01:00
Franck Nijhof
17c56208ee Fix camera proxy with sole image quality settings (#141676) 2025-03-28 20:36:15 +01:00
Norbert Rittel
8474d9fefe Replace "country" with common string in ecovacs (#141677) 2025-03-28 20:32:32 +01:00
J. Nick Koston
fd9f002e9f Increase websocket_api allowed peak time to 10s (#141680)
* Increase websocket_api allowed peak time to 10s

fixes #141624

During integration reload or startup, we can end up sending a message for
each entity being created for integrations that create them from an external
source (ie MQTT) because the messages come in one at a time. This can overload
the loop and/or client for more than 5s. While we have done significant work
to optimize for this path, we are at the limit at what we can expect clients
to be able to process in the time window, so increase the time window.

* adjust test
2025-03-28 20:32:00 +01:00
Norbert Rittel
26268357a0 Replace "country" with common string in prosegur (#141678) 2025-03-28 20:19:20 +01:00
Dan Raper
82b463b22f Get Ohme to gold quality (#140617)
* Add reconfigure step, diagnostics and default disabled entities to Ohme

* Formatting

* Update tests

* Bugfixes and add tests for diagnostics and reconfigure

* Remove diagnostics changes

* Remove reconfigure changes

* Pull upstream strings.json
2025-03-28 18:41:00 +00:00
Jason Hunter
7ae397a211 Update Duke Energy package to fix integration (#141669)
* Update Duke Energy package to fix integration

* fix tests
2025-03-28 18:33:59 +00:00
Norbert Rittel
ea4ad681e4 Replace "country" with common string in cookidoo (#141670) 2025-03-28 18:29:31 +00:00
Michael Hansen
a150f9d5ad Bump intents and always prefer more literal text (#141663) 2025-03-28 13:03:42 -04:00
puddly
afb7fe0d40 Include ZBT-1 and Yellow in device registry (#141623)
* Add the Yellow and ZBT-1 to the device registry

* Unload platforms

* Fix unit tests

* Rename the Yellow update entity to `Radio firmware`

* Rename `EmberZNet` to `EmberZNet Zigbee`

* Prefix the `sw_version` with the firmware type and clean up

* Fix unit tests

* Remove unnecessary `always_update=False` from data update coordinator
2025-03-28 18:00:05 +01:00
Norbert Rittel
8bf42b9d3e Replace "language" and "country" with common strings in epic_games_store (#141665) 2025-03-28 17:50:36 +01:00
Andrew Sayre
ba00707d89 Add HEOS entity service to remove queue items (#141495)
* Add remove queue items service

* Tests

* Correct casing of ID

* Match docs
2025-03-28 11:09:01 -05:00
tdfountain
2121b943a3 Add exception translation to NUT (#141629)
* Add exception translation and test cases

* Capitalize ID in error string

* Test translation placeholders, simplify test cases
2025-03-28 16:43:16 +01:00
Bram Kragten
ef06d2c06e Update frontend to 20250328.0 (#141659) 2025-03-28 16:08:14 +01:00
Norbert Rittel
cc1fac5776 Add a common string for "country" (#141653) 2025-03-28 15:52:44 +01:00
Norbert Rittel
6cb3430c60 Fix sentence-casing of "sea level" in matter (#141655)
* Fix sentence-casing of "sea level" in `matter`

* Update test_number.ambr
2025-03-28 15:30:21 +01:00
Paulus Schoutsen
4cea90f773 Enable the message box on default for satelitte announcement actions (#141654) 2025-03-28 15:07:09 +01:00
Paulus Schoutsen
4da5f6188d Ensure connection test sound has no preannouncement (#141647) 2025-03-28 14:01:12 +01:00
Norbert Rittel
e7f8b9ad92 Fix typo and sentence-casing in jewish_calendar (#141651)
Also replace "Language" with common string.
2025-03-28 15:55:52 +03:00
Jan Bouwhuis
473a28c5f2 Fix duplicate 'device' term in MQTT translation strings (#141646)
* Fix duplicate 'device' from MQTT translation strings

* Update homeassistant/components/mqtt/strings.json
2025-03-28 13:55:36 +01:00
Franck Nijhof
d765936be3 Fix ESPHome event entity staying unavailable (#141650) 2025-03-28 13:55:11 +01:00
Norbert Rittel
0db643d9d1 Replace "connect" / "disconnect" with common strings in idasen_desk (#141649) 2025-03-28 08:46:13 -04:00
Norbert Rittel
af29159e2f Remove "meter" from entity names of rainforest_raven sensors (#141487)
* Fix misleading friendly names of `rainforest_raven` sensors

The three sensors
- power_demand
- total_energy_delivered
- total_energy_received

currently add "meter" in their friendly names.

This does not provide any useful information and is rather irritating instead – it sounds like these are the power demands or consumption of the meter itself. But they are the measured values.

This commit removes "meter" from the names making them simpler and more precise, too.

In addition the sentence-casing of "MAC addresses" is fixed.

* Update test_sensor.ambr

* Update test_sensor.ambr (2)

* Also remove "meter" from Signal strength

* Update test_sensor.ambr (3)

* Change `meter_price` to `energy_price` in strings.json

* Change `meter_price` to `energy_price` in test_sensor.ambr

* Change `meter_price` to `energy_price` in sensor.py
2025-03-28 11:26:51 +00:00
Tsvi Mostovicz
65c38d8e31 Jewish calendar match omer service variables requirement to documentation (#141620)
The documentation and the omer schema require a Nusach to be specified, but the YAML misses that requirement
2025-03-28 13:59:04 +03:00
LG-ThinQ-Integration
adb7aa237b Add number for ventilator's sleepTimer (#140972)
Add sleepTimer for ventilator

Co-authored-by: yunseon.park <yunseon.park@lge.com>
2025-03-28 11:54:18 +01:00
Norbert Rittel
577f86b83a Remove "meter" from entity names of rainforest_eagle sensors (#141641)
* Remove "meter" from entity names in strings.json

* Replace `meter_price`with `energy_price`in sensor.py

* Update test_sensor.py
2025-03-28 13:52:15 +03:00
J. Diego Rodríguez Royo
01169e9184 Add Pitsos virtual integration (#141610) 2025-03-28 13:50:48 +03:00
J. Diego Rodríguez Royo
dde037291a Add Neff virtual integration (#141609) 2025-03-28 13:50:29 +03:00
J. Diego Rodríguez Royo
6971a189f9 Add Gaggenau virtual integration (#141608) 2025-03-28 13:50:12 +03:00
J. Diego Rodríguez Royo
c860686138 Add Constructa virtual integration (#141607) 2025-03-28 13:49:52 +03:00
J. Diego Rodríguez Royo
2eb507863f Add Balay virtual integration (#141606) 2025-03-28 13:49:38 +03:00
Joost Lekkerkerker
54ee5c6998 Add default string and icon for light effect off (#141567) 2025-03-28 11:48:34 +01:00
alorente
63df2474a9 Fix missing response for queued mode scripts (#141460) 2025-03-28 10:47:41 +00:00
Robert Resch
93f12fb7c6 Reverts #141363 "Deprecate SmartThings machine state sensors" (#141573)
Reverts #141363
2025-03-28 11:40:24 +01:00
Nick Pesce
b7a995ac53 Use correct default value for multi press buttons in the Matter integration (#141630)
* Respect the min 2 constraint for the switch MultiPressMax attribute

* Update test_event.py

* Update generic_switch_multi.json

* Fix issue and update tests
2025-03-28 11:11:18 +01:00
Solmath
267a80e70c Show internet radio station if no artist is available in Cambridge Audio (#140716)
* Add media_channel property to cambridge audio

* Return channel instead of artist when playing internet radio to mimick behaviour of CXN100 and StreamMagic app

* Add test for media_artist attribute

* Add test that media_artist is not set in certain cases

* Update homeassistant/components/cambridge_audio/media_player.py

Co-authored-by: Noah Husby <32528627+noahhusby@users.noreply.github.com>

---------

Co-authored-by: Noah Husby <32528627+noahhusby@users.noreply.github.com>
2025-03-28 09:49:20 +01:00
Norbert Rittel
078be3b8df Replace already_configured in teslemetry with common string (#141637) 2025-03-28 09:44:34 +01:00
J. Diego Rodríguez Royo
7b6c967c3a Add Profilo virtual integration (#141611) 2025-03-28 09:42:51 +01:00
J. Diego Rodríguez Royo
a405ccd044 Add Siemens virtual integration (#141612) 2025-03-28 09:37:27 +01:00
J. Diego Rodríguez Royo
f6c55ebf05 Add Thermador virtual integration (#141613) 2025-03-28 09:35:05 +01:00
Shay Levy
6b3b4cce4b Record Shelly quality scale (#141062)
* Record Shelly quality scale

* Update

* change stale-devices status to todo

* Update homeassistant/components/shelly/quality_scale.yaml

Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>

---------

Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>
2025-03-28 09:30:29 +01:00
Jan Bouwhuis
8887c979b4 Fix volatile_organic_compounds_parts translation string to be referenced for MQTT subentries device class selector (#141618)
* Fix ` volatile_organic_compounds_parts` translation string to be referenced for MQTT subentries device class selector

* Fix tests
2025-03-28 08:48:23 +01:00
Norbert Rittel
665541409a Fix sentence-casing in airvisual user strings (#141632) 2025-03-28 10:37:55 +03:00
Denis Shulyaka
195919b5fb Add PDF support for openai_conversation.generate_content service (#141588)
Add PDF support for openai_conversation.generate_content service
2025-03-27 20:05:54 -04:00
Petro31
31479056ed Fix an issue with the switch preview in beta (#141617)
Fix an issue with the switch preview
2025-03-28 00:43:17 +02:00
Jan-Philipp Benecke
9f0976d94a Bump aiowebdav2 to 0.4.4 (#141615) 2025-03-27 23:19:04 +01:00
Norbert Rittel
a049d2b7db Make names of switch entities in gree consistent with docs (#141580) 2025-03-27 21:51:11 +00:00
Norbert Rittel
6959017d55 Use official camel-cased spelling "FullTopic" in tasmota (#141604)
* Use camel-cased spelling "FullTopic" in `tasmota`

This  should ensure that this fixed term is kept in translations.

In addition an excessive space character is removed.

* Fix wrong plural in second sentence
2025-03-27 23:12:42 +02:00
Luke Lashley
4ff5a04a72 Bump Python-Snoo to 0.6.5 (#141599)
* Bump Python-Snoo to 0.6.5

* add to event_types
2025-03-27 21:56:11 +01:00
Norbert Rittel
ea0c4a7263 Fix misleading friendly names of pvoutput sensors (#141312)
* Fix misleading friendly names of `pvoutput` sensors

* Update test_sensor.py

* Update test_sensor.py - prettier
2025-03-27 21:49:16 +01:00
J. Nick Koston
9633f03ddc Fix zeroconf logging level not being respected (#141601)
Removes an old logging workaround that is no longer needed

fixes #141558
2025-03-27 21:45:48 +01:00
Artur Pragacz
4c0d8ce87c Remove deprecated YAML import in Onkyo (#141600) 2025-03-27 21:13:23 +01:00
Joost Lekkerkerker
d92728e533 Add brand for Bosch (#141561) 2025-03-27 21:01:52 +01:00
Paul Bottein
799962ef0e Update frontend to 20250327.1 (#141596) 2025-03-27 20:58:59 +01:00
Stephan Traub
631f817f11 Wiz - update dependency to support new light features and bugfixes (#141529)
* Bump pywizlight and fix deprecation issue

* Removed workaround for color_mode; update pywizlight
2025-03-27 21:51:42 +02:00
Martin Hjelmare
52f7bdeb5d Patch Z-Wave platforms in fan tests (#141591) 2025-03-27 21:40:39 +02:00
Martin Hjelmare
51db140aed Clean up Z-Wave config flow (#141595) 2025-03-27 21:30:16 +02:00
Jan-Philipp Benecke
1ad12d5945 Bump aiowebdav2 to 0.4.3 (#141586) 2025-03-27 19:44:33 +02:00
Simon Lamon
9f5d94046d Fix typing error in NMBS (#141589)
Fix typing error
2025-03-27 19:39:33 +02:00
Erik Montnemery
de1e06c39b Revert "Promote after dependencies in bootstrap" (#141584)
Revert "Promote after dependencies in bootstrap (#140352)"

This reverts commit 3766040960.
2025-03-27 12:57:58 -04:00
Bram Kragten
abbabc11d2 Update frontend to 20250327.0 (#141585) 2025-03-27 17:51:52 +01:00
Joost Lekkerkerker
62be82fd3c Also migrate completion time entities in SmartThings (#141572) 2025-03-27 16:36:45 +01:00
Luke Lashley
f0fd5a639a Better handle Roborock discovery (#141575) 2025-03-27 11:17:56 -04:00
Andrii Mitnovych
dea00fac3f Get area and floor by alias (#126150)
* Add possibility to get area by alias

* Add ability to get floor by alias

* Moved alias lookup to separate function, adjusted templates.

* Changed registry to return all areas/floors with given alias

* Use normalize_name from normalized_name_base_registry
2025-03-27 16:02:47 +01:00
Erwin Douna
c30f17f592 Tado fix HomeKit flow (#141525)
* Initial commit

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-03-27 16:01:54 +01:00
Martin Hjelmare
e9e95f45d8 Handle cloud subscription expired for backup upload (#141564)
Handle cloud backup subscription expired for upload
2025-03-27 15:29:11 +01:00
Joost Lekkerkerker
e8aa3e6d34 Add icons to hue effects (#141559) 2025-03-27 12:05:45 +01:00
Norbert Rittel
3646884d79 Replace "controller_id" with friendly name in homeworks error message (#141550) 2025-03-27 12:29:53 +02:00
Norbert Rittel
5747c6b1a8 Fix sentence-casing in konnected strings, replace "override" with "custom" (#141553)
Fix sentence-casing in `konnected`strings, replace "Override" with "Custom"

Make string consistent with HA standards.

As "Override" can be misunderstood as the verb, replace it with "Custom".
2025-03-27 10:59:19 +01:00
Thomas55555
43a5c7ddc8 Handle webcal prefix in remote calendar (#141541)
Handel webcal prefix in remote calendar
2025-03-27 10:22:25 +01:00
J. Diego Rodríguez Royo
d9d74107fe Improve some Home Connect deprecations (#141508) 2025-03-27 10:18:30 +01:00
Erik Montnemery
373cca9857 Remove unused mypy ignore from google_generative_ai_conversation (#141549) 2025-03-27 10:03:07 +01:00
Martin Hjelmare
284b3f444d Remove leftover cloudflare persistent notification dismiss (#141548) 2025-03-27 09:53:47 +01:00
Simon Lamon
dfb088e524 Bump linkplay to v0.2.2 (#141542)
Bump linkplay
2025-03-27 08:51:12 +01:00
Manu
5546f1d73d Support for upcoming pyLoad-ng release in pyLoad integration (#141297)
Fix extra key `proxy` in pyLoad
2025-03-27 07:46:58 +01:00
Jan Bouwhuis
13fc871806 Use kwargs only for MQTT subentry PlatformField helper (#141498) 2025-03-27 07:46:08 +01:00
J. Nick Koston
0f9fd78656 Bump pyserial-asyncio-fast to 0.16 (#141540)
changelog: https://github.com/home-assistant-libs/pyserial-asyncio-fast/compare/0.15...0.16
2025-03-27 07:32:59 +01:00
Ivan Lopez Hernandez
4f318c0be3 Initialize google.genai.Client in the executor (#141432)
* Intialize the client on an executor thread

* Fix MyPy error

* MyPy error

* Exception error

* Fix ruff

* Update __init__.py

---------

Co-authored-by: tronikos <tronikos@users.noreply.github.com>
2025-03-26 22:05:22 -07:00
Michael Hansen
5eb1d0a28e Add default preannounce sound to Assist satellites (#141522)
* Add default preannounce sound

* Allow None to disable sound

* Register static path instead of HTTP view

* Fix path

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-03-26 22:45:28 -05:00
Brett Adams
66c03713b7 Fix Auto Seat Heater in Tesla Fleet (#141539)
Fix Auto Seat Heater
2025-03-26 20:55:34 -04:00
Marc Mueller
d51070c99b Update boto3 to 1.37.1 and aiobotocore to 2.21.1 (#141499) 2025-03-27 01:38:34 +01:00
Marc Mueller
50d050e63e Update pyserial-asyncio-fast to 0.15 (#141537) 2025-03-27 01:33:01 +01:00
Norbert Rittel
89bf426163 Fix wrong friendly name for storage_power in solaredge (#141269)
* Fix wrong friendly name for `storage_power` in `solaredge`

"Stored power" is a contradiction in itself.
You can only store energy.

* Two additional spelling fixes

* Sentence-case "site"
2025-03-27 00:24:14 +01:00
Jan Bouwhuis
377548e3a1 Fix QoS schema issue in MQTT subentries (#141531) 2025-03-26 23:35:28 +01:00
Simone Chemelli
543c6929e6 Fix refresh state for Comelit alarm (#141370) 2025-03-26 22:34:53 +00:00
Jan Bouwhuis
42ae572948 Fix MQTT options flow QoS selector can not serialize (#141528) 2025-03-26 22:56:57 +01:00
Thomas55555
c3f8b7e200 Fix work area sensor for Husqvarna Automower (#141527)
* Fix work area sensor for Husqvarna Automower

* simplify
2025-03-26 23:16:26 +02:00
Robert Resch
3a207e2571 Show box for Smartthings rise number entity (#141526) 2025-03-26 22:03:24 +01:00
Andrew Sayre
6bfd39f094 Add play queue item to HEOS (#141480)
Add ability to play specific queue item
2025-03-26 15:47:10 -05:00
Robert Resch
002ca9611d Add test for invalid mean type in StatisticsMeta (#141475) 2025-03-26 21:40:02 +01:00
Joost Lekkerkerker
46ee3d2b26 Sort SmartThings devices to be created by parent device id (#141515) 2025-03-26 20:52:39 +01:00
Franck Nijhof
eb901bcf3a Bump version to 2025.5.0dev0 (#141507) 2025-03-26 20:30:03 +01:00
Norbert Rittel
930b4a2c81 Capitalize "Ethernet" in roku sensor name (#141509)
* Capitalize "Ethernet" in `roku` sensor name

* Update test_binary_sensor.py
2025-03-26 21:18:52 +02:00
Robert Resch
22d1b8e1cd Bump deebot-client to 12.4.0 (#141501) 2025-03-26 19:36:04 +01:00
374 changed files with 7477 additions and 4804 deletions

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 12
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2025.4"
HA_SHORT_VERSION: "2025.5"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version

2
CODEOWNERS generated
View File

@@ -1480,8 +1480,6 @@ build.json @home-assistant/supervisor
/tests/components/suez_water/ @ooii @jb101010-2
/homeassistant/components/sun/ @Swamp-Ig
/tests/components/sun/ @Swamp-Ig
/homeassistant/components/sunweg/ @rokam
/tests/components/sunweg/ @rokam
/homeassistant/components/supla/ @mwegrzynek
/homeassistant/components/surepetcare/ @benleb @danielhiversen
/tests/components/surepetcare/ @benleb @danielhiversen

View File

@@ -68,8 +68,8 @@
"led_bar_mode": {
"name": "LED bar mode",
"state": {
"off": "Off",
"co2": "Carbon dioxide",
"off": "[%key:common::state::off%]",
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
"pm": "Particulate matter"
}
},
@@ -143,8 +143,8 @@
"led_bar_mode": {
"name": "[%key:component::airgradient::entity::select::led_bar_mode::name%]",
"state": {
"off": "[%key:component::airgradient::entity::select::led_bar_mode::state::off%]",
"co2": "[%key:component::airgradient::entity::select::led_bar_mode::state::co2%]",
"off": "[%key:common::state::off%]",
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
"pm": "[%key:component::airgradient::entity::select::led_bar_mode::state::pm%]"
}
},

View File

@@ -8,7 +8,7 @@ from aiohttp import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
from pyairnow import WebServiceAPI
from pyairnow.conv import aqi_to_concentration
from pyairnow.errors import AirNowError
from pyairnow.errors import AirNowError, InvalidJsonError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -79,7 +79,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
distance=self.distance,
)
except (AirNowError, ClientConnectorError) as error:
except (AirNowError, ClientConnectorError, InvalidJsonError) as error:
raise UpdateFailed(error) from error
if not obs:

View File

@@ -2,7 +2,7 @@
"config": {
"step": {
"geography_by_coords": {
"title": "Configure a Geography",
"title": "Configure a geography",
"description": "Use the AirVisual cloud API to monitor a latitude/longitude.",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
@@ -16,8 +16,8 @@
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"city": "City",
"country": "Country",
"state": "State"
"state": "State",
"country": "[%key:common::config_flow::data::country%]"
}
},
"reauth_confirm": {
@@ -56,12 +56,12 @@
"sensor": {
"pollutant_label": {
"state": {
"co": "Carbon Monoxide",
"n2": "Nitrogen Dioxide",
"o3": "Ozone",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Sulfur Dioxide"
"co": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
"n2": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]",
"o3": "[%key:component::sensor::entity_component::ozone::name%]",
"p1": "[%key:component::sensor::entity_component::pm10::name%]",
"p2": "[%key:component::sensor::entity_component::pm25::name%]",
"s2": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]"
}
},
"pollutant_level": {

View File

@@ -32,8 +32,8 @@
"air_quality": {
"name": "Air Quality mode",
"state": {
"off": "Off",
"on": "On",
"off": "[%key:common::state::off%]",
"on": "[%key:common::state::on%]",
"auto": "Auto"
}
},

View File

@@ -6,5 +6,5 @@
"iot_class": "cloud_push",
"loggers": ["boto3", "botocore", "s3transfer"],
"quality_scale": "legacy",
"requirements": ["boto3==1.34.131"]
"requirements": ["boto3==1.37.1"]
}

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/android_ip_webcam",
"iot_class": "local_polling",
"requirements": ["pydroid-ipcam==2.0.0"]
"requirements": ["pydroid-ipcam==3.0.0"]
}

View File

@@ -43,6 +43,7 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
config_entry: ApSystemsConfigEntry
device_version: str
battery_system: bool
def __init__(
self,
@@ -68,6 +69,7 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
self.api.max_power = device_info.maxPower
self.api.min_power = device_info.minPower
self.device_version = device_info.devVer
self.battery_system = device_info.isBatterySystem
async def _async_update_data(self) -> ApSystemsSensorData:
try:

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/apsystems",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["apsystems-ez1==2.4.0"]
"requirements": ["apsystems-ez1==2.5.0"]
}

View File

@@ -36,6 +36,8 @@ class ApSystemsInverterSwitch(ApSystemsEntity, SwitchEntity):
super().__init__(data)
self._api = data.coordinator.api
self._attr_unique_id = f"{data.device_id}_inverter_status"
if data.coordinator.battery_system:
self._attr_available = False
async def async_update(self) -> None:
"""Update switch status and availability."""

View File

@@ -8,6 +8,7 @@ announce:
message:
required: false
example: "Time to wake up!"
default: ""
selector:
text:
media_id:
@@ -28,6 +29,7 @@ start_conversation:
start_message:
required: false
example: "You left the lights on in the living room. Turn them off?"
default: ""
selector:
text:
start_media_id:

View File

@@ -198,7 +198,8 @@ async def websocket_test_connection(
hass.async_create_background_task(
satellite.async_internal_announce(
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}"
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}",
preannounce_media_id=None,
),
f"assist_satellite_connection_test_{msg['entity_id']}",
)

View File

@@ -6,5 +6,5 @@
"iot_class": "cloud_push",
"loggers": ["aiobotocore", "botocore"],
"quality_scale": "legacy",
"requirements": ["aiobotocore==2.13.1", "botocore==1.34.131"]
"requirements": ["aiobotocore==2.21.1", "botocore==1.37.1"]
}

View File

@@ -26,9 +26,9 @@
"entity": {
"sensor": {
"backup_manager_state": {
"name": "Backup Manager State",
"name": "Backup Manager state",
"state": {
"idle": "Idle",
"idle": "[%key:common::state::idle%]",
"create_backup": "Creating a backup",
"receive_backup": "Receiving a backup",
"restore_backup": "Restoring a backup"

View File

@@ -0,0 +1 @@
"""Balay virtual integration."""

View File

@@ -0,0 +1,6 @@
{
"domain": "balay",
"name": "Balay",
"integration_type": "virtual",
"supported_by": "home_connect"
}

View File

@@ -132,7 +132,7 @@
"name": "Charging",
"state": {
"off": "Not charging",
"on": "Charging"
"on": "[%key:common::state::charging%]"
}
},
"carbon_monoxide": {

View File

@@ -37,7 +37,7 @@
"vehicle_status": {
"name": "Vehicle status",
"state": {
"standby": "Standby",
"standby": "[%key:common::state::standby%]",
"vehicle_detected": "Detected",
"ready": "Ready",
"no_power": "No power",

View File

@@ -6,7 +6,7 @@
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"region": "ConnectedDrive Region"
"region": "ConnectedDrive region"
},
"data_description": {
"username": "The email address of your MyBMW/MINI Connected account.",
@@ -113,10 +113,10 @@
},
"select": {
"ac_limit": {
"name": "AC Charging Limit"
"name": "AC charging limit"
},
"charging_mode": {
"name": "Charging Mode",
"name": "Charging mode",
"state": {
"immediate_charging": "Immediate charging",
"delayed_charging": "Delayed charging",
@@ -181,7 +181,7 @@
"cooling": "Cooling",
"heating": "Heating",
"inactive": "Inactive",
"standby": "Standby",
"standby": "[%key:common::state::standby%]",
"ventilation": "Ventilation"
}
},

View File

@@ -142,6 +142,12 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
@property
def media_artist(self) -> str | None:
"""Artist of current playing media, music track only."""
if (
not self.client.play_state.metadata.artist
and self.client.state.source == "IR"
):
# Return channel instead of artist when playing internet radio
return self.client.play_state.metadata.station
return self.client.play_state.metadata.artist
@property
@@ -169,6 +175,11 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
"""Last time the media position was updated."""
return self.client.position_last_updated
@property
def media_channel(self) -> str | None:
"""Channel currently playing."""
return self.client.play_state.metadata.station
@property
def is_volume_muted(self) -> bool | None:
"""Volume mute status."""

View File

@@ -98,13 +98,13 @@
"name": "Preset",
"state": {
"none": "None",
"eco": "Eco",
"away": "Away",
"home": "[%key:common::state::home%]",
"away": "[%key:common::state::not_home%]",
"activity": "Activity",
"boost": "Boost",
"comfort": "Comfort",
"home": "[%key:common::state::home%]",
"sleep": "Sleep",
"activity": "Activity"
"eco": "Eco",
"sleep": "Sleep"
}
},
"preset_modes": {
@@ -257,7 +257,7 @@
"selector": {
"hvac_mode": {
"options": {
"off": "Off",
"off": "[%key:common::state::off%]",
"auto": "Auto",
"cool": "Cool",
"dry": "Dry",

View File

@@ -9,7 +9,6 @@ from typing import Any
import pycfdns
import voluptuous as vol
from homeassistant.components import persistent_notification
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_TOKEN, CONF_ZONE
from homeassistant.core import HomeAssistant
@@ -118,8 +117,6 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by the user."""
persistent_notification.async_dismiss(self.hass, "cloudflare_setup")
errors: dict[str, str] = {}
if user_input is not None:

View File

@@ -8,7 +8,7 @@ from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import COVER, STATE_COVER, STATE_OFF, STATE_ON
from homeassistant.components.cover import CoverDeviceClass, CoverEntity, CoverState
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -98,13 +98,20 @@ class ComelitCoverEntity(
"""Return if the cover is opening."""
return self._current_action("opening")
async def _cover_set_state(self, action: int, state: int) -> None:
"""Set desired cover state."""
self._last_state = self.state
await self._api.set_device_status(COVER, self._device.index, action)
self.coordinator.data[COVER][self._device.index].status = state
self.async_write_ha_state()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover."""
await self._api.set_device_status(COVER, self._device.index, STATE_OFF)
await self._cover_set_state(STATE_OFF, 2)
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open cover."""
await self._api.set_device_status(COVER, self._device.index, STATE_ON)
await self._cover_set_state(STATE_ON, 1)
async def async_stop_cover(self, **_kwargs: Any) -> None:
"""Stop the cover."""
@@ -112,13 +119,7 @@ class ComelitCoverEntity(
return
action = STATE_ON if self.is_closing else STATE_OFF
await self._api.set_device_status(COVER, self._device.index, action)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle device update."""
self._last_state = self.state
self.async_write_ha_state()
await self._cover_set_state(action, 0)
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""

View File

@@ -59,7 +59,8 @@ class ComelitLightEntity(CoordinatorEntity[ComelitSerialBridge], LightEntity):
async def _light_set_state(self, state: int) -> None:
"""Set desired light state."""
await self.coordinator.api.set_device_status(LIGHT, self._device.index, state)
await self.coordinator.async_request_refresh()
self.coordinator.data[LIGHT][self._device.index].status = state
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""

View File

@@ -67,7 +67,8 @@ class ComelitSwitchEntity(CoordinatorEntity[ComelitSerialBridge], SwitchEntity):
await self.coordinator.api.set_device_status(
self._device.type, self._device.index, state
)
await self.coordinator.async_request_refresh()
self.coordinator.data[self._device.type][self._device.index].status = state
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""

View File

@@ -0,0 +1 @@
"""Constructa virtual integration."""

View File

@@ -0,0 +1,6 @@
{
"domain": "constructa",
"name": "Constructa",
"integration_type": "virtual",
"supported_by": "home_connect"
}

View File

@@ -354,6 +354,35 @@ class ChatLog:
if self.delta_listener:
self.delta_listener(self, asdict(tool_result))
async def _async_expand_prompt_template(
self,
llm_context: llm.LLMContext,
prompt: str,
language: str,
user_name: str | None = None,
) -> str:
try:
return template.Template(prompt, self.hass).async_render(
{
"ha_name": self.hass.config.location_name,
"user_name": user_name,
"llm_context": llm_context,
},
parse_result=False,
)
except TemplateError as err:
LOGGER.error("Error rendering prompt: %s", err)
intent_response = intent.IntentResponse(language=language)
intent_response.async_set_error(
intent.IntentResponseErrorCode.UNKNOWN,
"Sorry, I had a problem with my template",
)
raise ConverseError(
"Error rendering prompt",
conversation_id=self.conversation_id,
response=intent_response,
) from err
async def async_update_llm_data(
self,
conversing_domain: str,
@@ -409,38 +438,28 @@ class ChatLog:
):
user_name = user.name
try:
prompt_parts = [
template.Template(
llm.BASE_PROMPT
+ (user_llm_prompt or llm.DEFAULT_INSTRUCTIONS_PROMPT),
self.hass,
).async_render(
{
"ha_name": self.hass.config.location_name,
"user_name": user_name,
"llm_context": llm_context,
},
parse_result=False,
)
]
except TemplateError as err:
LOGGER.error("Error rendering prompt: %s", err)
intent_response = intent.IntentResponse(language=user_input.language)
intent_response.async_set_error(
intent.IntentResponseErrorCode.UNKNOWN,
"Sorry, I had a problem with my template",
prompt_parts = []
prompt_parts.append(
await self._async_expand_prompt_template(
llm_context,
(user_llm_prompt or llm.DEFAULT_INSTRUCTIONS_PROMPT),
user_input.language,
user_name,
)
raise ConverseError(
"Error rendering prompt",
conversation_id=self.conversation_id,
response=intent_response,
) from err
)
if llm_api:
prompt_parts.append(llm_api.api_prompt)
prompt_parts.append(
await self._async_expand_prompt_template(
llm_context,
llm.BASE_PROMPT,
user_input.language,
user_name,
)
)
if extra_system_prompt := (
# Take new system prompt if one was given
user_input.extra_system_prompt or self.extra_system_prompt

View File

@@ -650,7 +650,14 @@ class DefaultAgent(ConversationEntity):
if (
(maybe_result is None) # first result
or (num_matched_entities > best_num_matched_entities)
or (
# More literal text matched
result.text_chunks_matched > maybe_result.text_chunks_matched
)
or (
# More entities matched
num_matched_entities > best_num_matched_entities
)
or (
# Fewer unmatched entities
(num_matched_entities == best_num_matched_entities)
@@ -662,16 +669,6 @@ class DefaultAgent(ConversationEntity):
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges > best_num_unmatched_ranges)
)
or (
# More literal text matched
(num_matched_entities == best_num_matched_entities)
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges == best_num_unmatched_ranges)
and (
result.text_chunks_matched
> maybe_result.text_chunks_matched
)
)
or (
# Prefer match failures with entities
(result.text_chunks_matched == maybe_result.text_chunks_matched)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.3.24"]
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.3.28"]
}

View File

@@ -6,7 +6,7 @@
"data": {
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]",
"country": "Country"
"country": "[%key:common::config_flow::data::country%]"
},
"data_description": {
"email": "Email used to access your {cookidoo} account.",

View File

@@ -38,10 +38,10 @@
"name": "[%key:component::cover::title%]",
"state": {
"open": "[%key:common::state::open%]",
"opening": "Opening",
"opening": "[%key:common::state::opening%]",
"closed": "[%key:common::state::closed%]",
"closing": "Closing",
"stopped": "Stopped"
"closing": "[%key:common::state::closing%]",
"stopped": "[%key:common::state::stopped%]"
},
"state_attributes": {
"current_position": {

View File

@@ -38,8 +38,8 @@
"protect_mode": {
"name": "Protect mode",
"state": {
"away": "Away",
"home": "Home",
"away": "[%key:common::state::not_home%]",
"home": "[%key:common::state::home%]",
"schedule": "Schedule"
}
}

View File

@@ -50,10 +50,10 @@ class DukeEnergyConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
username = auth["cdp_internal_user_id"].lower()
username = auth["internalUserID"].lower()
await self.async_set_unique_id(username)
self._abort_if_unique_id_configured()
email = auth["email"].lower()
email = auth["loginEmailAddress"].lower()
data = {
CONF_EMAIL: email,
CONF_USERNAME: username,

View File

@@ -6,5 +6,5 @@
"dependencies": ["recorder"],
"documentation": "https://www.home-assistant.io/integrations/duke_energy",
"iot_class": "cloud_polling",
"requirements": ["aiodukeenergy==0.2.2"]
"requirements": ["aiodukeenergy==0.3.0"]
}

View File

@@ -91,15 +91,15 @@ class EcoNetWaterHeater(EcoNetEntity[WaterHeater], WaterHeaterEntity):
def operation_list(self) -> list[str]:
"""List of available operation modes."""
econet_modes = self.water_heater.modes
op_list = []
operation_modes = set()
for mode in econet_modes:
if (
mode is not WaterHeaterOperationMode.UNKNOWN
and mode is not WaterHeaterOperationMode.VACATION
):
ha_mode = ECONET_STATE_TO_HA[mode]
op_list.append(ha_mode)
return op_list
operation_modes.add(ha_mode)
return list(operation_modes)
@property
def supported_features(self) -> WaterHeaterEntityFeature:

View File

@@ -14,7 +14,7 @@
"step": {
"auth": {
"data": {
"country": "Country",
"country": "[%key:common::config_flow::data::country%]",
"override_rest_url": "REST URL",
"override_mqtt_url": "MQTT URL",
"password": "[%key:common::config_flow::data::password%]",

View File

@@ -100,7 +100,11 @@ class ElkEntity(Entity):
return {"index": self._element.index + 1}
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
pass
"""Handle changes to the element.
This method is called when the element changes. It should be
overridden by subclasses to handle the changes.
"""
@callback
def _element_callback(self, element: Element, changeset: dict[str, Any]) -> None:
@@ -111,7 +115,7 @@ class ElkEntity(Entity):
async def async_added_to_hass(self) -> None:
"""Register callback for ElkM1 changes and update entity state."""
self._element.add_callback(self._element_callback)
self._element_callback(self._element, {})
self._element_changed(self._element, {})
@property
def device_info(self) -> DeviceInfo:

View File

@@ -46,6 +46,8 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
type EmulatedRokuConfigEntry = ConfigEntry[EmulatedRoku]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the emulated roku component."""
@@ -65,22 +67,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: EmulatedRokuConfigEntry
) -> bool:
"""Set up an emulated roku server from a config entry."""
config = config_entry.data
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
name = config[CONF_NAME]
listen_port = config[CONF_LISTEN_PORT]
host_ip = config.get(CONF_HOST_IP) or await async_get_source_ip(hass)
advertise_ip = config.get(CONF_ADVERTISE_IP)
advertise_port = config.get(CONF_ADVERTISE_PORT)
upnp_bind_multicast = config.get(CONF_UPNP_BIND_MULTICAST)
config = entry.data
name: str = config[CONF_NAME]
listen_port: int = config[CONF_LISTEN_PORT]
host_ip: str = config.get(CONF_HOST_IP) or await async_get_source_ip(hass)
advertise_ip: str | None = config.get(CONF_ADVERTISE_IP)
advertise_port: int | None = config.get(CONF_ADVERTISE_PORT)
upnp_bind_multicast: bool | None = config.get(CONF_UPNP_BIND_MULTICAST)
server = EmulatedRoku(
hass,
entry.entry_id,
name,
host_ip,
listen_port,
@@ -88,14 +89,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
advertise_port,
upnp_bind_multicast,
)
hass.data[DOMAIN][name] = server
entry.runtime_data = server
return await server.setup()
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: EmulatedRokuConfigEntry
) -> bool:
"""Unload a config entry."""
name = entry.data[CONF_NAME]
server = hass.data[DOMAIN].pop(name)
return await server.unload()
return await entry.runtime_data.unload()

View File

@@ -5,7 +5,13 @@ import logging
from emulated_roku import EmulatedRokuCommandHandler, EmulatedRokuServer
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CoreState, EventOrigin
from homeassistant.core import (
CALLBACK_TYPE,
CoreState,
Event,
EventOrigin,
HomeAssistant,
)
LOGGER = logging.getLogger(__package__)
@@ -27,16 +33,18 @@ class EmulatedRoku:
def __init__(
self,
hass,
name,
host_ip,
listen_port,
advertise_ip,
advertise_port,
upnp_bind_multicast,
):
hass: HomeAssistant,
entry_id: str,
name: str,
host_ip: str,
listen_port: int,
advertise_ip: str | None,
advertise_port: int | None,
upnp_bind_multicast: bool | None,
) -> None:
"""Initialize the properties."""
self.hass = hass
self.entry_id = entry_id
self.roku_usn = name
self.host_ip = host_ip
@@ -47,21 +55,21 @@ class EmulatedRoku:
self.bind_multicast = upnp_bind_multicast
self._api_server = None
self._api_server: EmulatedRokuServer | None = None
self._unsub_start_listener = None
self._unsub_stop_listener = None
self._unsub_start_listener: CALLBACK_TYPE | None = None
self._unsub_stop_listener: CALLBACK_TYPE | None = None
async def setup(self):
async def setup(self) -> bool:
"""Start the emulated_roku server."""
class EventCommandHandler(EmulatedRokuCommandHandler):
"""emulated_roku command handler to turn commands into events."""
def __init__(self, hass):
def __init__(self, hass: HomeAssistant) -> None:
self.hass = hass
def on_keydown(self, roku_usn, key):
def on_keydown(self, roku_usn: str, key: str) -> None:
"""Handle keydown event."""
self.hass.bus.async_fire(
EVENT_ROKU_COMMAND,
@@ -73,7 +81,7 @@ class EmulatedRoku:
EventOrigin.local,
)
def on_keyup(self, roku_usn, key):
def on_keyup(self, roku_usn: str, key: str) -> None:
"""Handle keyup event."""
self.hass.bus.async_fire(
EVENT_ROKU_COMMAND,
@@ -85,7 +93,7 @@ class EmulatedRoku:
EventOrigin.local,
)
def on_keypress(self, roku_usn, key):
def on_keypress(self, roku_usn: str, key: str) -> None:
"""Handle keypress event."""
self.hass.bus.async_fire(
EVENT_ROKU_COMMAND,
@@ -97,7 +105,7 @@ class EmulatedRoku:
EventOrigin.local,
)
def launch(self, roku_usn, app_id):
def launch(self, roku_usn: str, app_id: str) -> None:
"""Handle launch event."""
self.hass.bus.async_fire(
EVENT_ROKU_COMMAND,
@@ -129,17 +137,19 @@ class EmulatedRoku:
bind_multicast=self.bind_multicast,
)
async def emulated_roku_stop(event):
async def emulated_roku_stop(event: Event | None) -> None:
"""Wrap the call to emulated_roku.close."""
LOGGER.debug("Stopping emulated_roku %s", self.roku_usn)
self._unsub_stop_listener = None
assert self._api_server is not None
await self._api_server.close()
async def emulated_roku_start(event):
async def emulated_roku_start(event: Event | None) -> None:
"""Wrap the call to emulated_roku.start."""
try:
LOGGER.debug("Starting emulated_roku %s", self.roku_usn)
self._unsub_start_listener = None
assert self._api_server is not None
await self._api_server.start()
except OSError:
LOGGER.exception(
@@ -165,7 +175,7 @@ class EmulatedRoku:
return True
async def unload(self):
async def unload(self) -> bool:
"""Unload the emulated_roku server."""
LOGGER.debug("Unloading emulated_roku %s", self.roku_usn)
@@ -177,6 +187,7 @@ class EmulatedRoku:
self._unsub_stop_listener()
self._unsub_stop_listener = None
assert self._api_server is not None
await self._api_server.close()
return True

View File

@@ -3,8 +3,8 @@
"step": {
"user": {
"data": {
"language": "Language",
"country": "Country"
"language": "[%key:common::config_flow::data::language%]",
"country": "[%key:common::config_flow::data::country%]"
}
}
},

View File

@@ -310,12 +310,13 @@ class EsphomeAssistSatellite(
self.entry_data.api_version
)
)
if feature_flags & VoiceAssistantFeature.SPEAKER:
media_id = tts_output["media_id"]
if feature_flags & VoiceAssistantFeature.SPEAKER and (
stream := tts.async_get_stream(self.hass, tts_output["token"])
):
self._tts_streaming_task = (
self.config_entry.async_create_background_task(
self.hass,
self._stream_tts_audio(media_id),
self._stream_tts_audio(stream),
"esphome_voice_assistant_tts",
)
)
@@ -564,7 +565,7 @@ class EsphomeAssistSatellite(
async def _stream_tts_audio(
self,
media_id: str,
tts_result: tts.ResultStream,
sample_rate: int = 16000,
sample_width: int = 2,
sample_channels: int = 1,
@@ -579,15 +580,14 @@ class EsphomeAssistSatellite(
if not self._is_running:
return
extension, data = await tts.async_get_media_source_audio(
self.hass,
media_id,
)
if extension != "wav":
_LOGGER.error("Only WAV audio can be streamed, got %s", extension)
if tts_result.extension != "wav":
_LOGGER.error(
"Only WAV audio can be streamed, got %s", tts_result.extension
)
return
data = b"".join([chunk async for chunk in tts_result.async_stream_result()])
with io.BytesIO(data) as wav_io, wave.open(wav_io, "rb") as wav_file:
if (
(wav_file.getframerate() != sample_rate)

View File

@@ -128,8 +128,23 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
self._password = ""
return await self._async_authenticate_or_add()
if error is None and entry_data.get(CONF_NOISE_PSK):
return await self.async_step_reauth_encryption_removed_confirm()
return await self.async_step_reauth_confirm()
async def async_step_reauth_encryption_removed_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauthorization flow when encryption was removed."""
if user_input is not None:
self._noise_psk = None
return self._async_get_entry()
return self.async_show_form(
step_id="reauth_encryption_removed_confirm",
description_placeholders={"name": self._name},
)
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@@ -282,15 +282,18 @@ class RuntimeEntryData:
) -> None:
"""Distribute an update of static infos to all platforms."""
# First, load all platforms
needed_platforms = set()
if async_get_dashboard(hass):
needed_platforms.add(Platform.UPDATE)
needed_platforms: set[Platform] = set()
if self.device_info and self.device_info.voice_assistant_feature_flags_compat(
self.api_version
):
needed_platforms.add(Platform.BINARY_SENSOR)
needed_platforms.add(Platform.SELECT)
if self.device_info:
if async_get_dashboard(hass):
# Only load the update platform if the device_info is set
# When we restore the entry, the device_info may not be set yet
# and we don't want to load the update platform since it needs
# a complete device_info.
needed_platforms.add(Platform.UPDATE)
if self.device_info.voice_assistant_feature_flags_compat(self.api_version):
needed_platforms.add(Platform.BINARY_SENSOR)
needed_platforms.add(Platform.SELECT)
ent_reg = er.async_get(hass)
registry_get_entity = ent_reg.async_get_entity_id
@@ -312,18 +315,19 @@ class RuntimeEntryData:
# Make a dict of the EntityInfo by type and send
# them to the listeners for each specific EntityInfo type
infos_by_type: dict[type[EntityInfo], list[EntityInfo]] = {}
infos_by_type: defaultdict[type[EntityInfo], list[EntityInfo]] = defaultdict(
list
)
for info in infos:
info_type = type(info)
if info_type not in infos_by_type:
infos_by_type[info_type] = []
infos_by_type[info_type].append(info)
infos_by_type[type(info)].append(info)
callbacks_by_type = self.entity_info_callbacks
for type_, entity_infos in infos_by_type.items():
if callbacks_ := callbacks_by_type.get(type_):
for callback_ in callbacks_:
callback_(entity_infos)
for type_, callbacks in self.entity_info_callbacks.items():
# If all entities for a type are removed, we
# still need to call the callbacks with an empty list
# to make sure the entities are removed.
entity_infos = infos_by_type.get(type_, [])
for callback_ in callbacks:
callback_(entity_infos)
# Finally update static info subscriptions
for callback_ in self.static_info_update_subscriptions:

View File

@@ -33,6 +33,16 @@ class EsphomeEvent(EsphomeEntity[EventInfo, Event], EventEntity):
self._trigger_event(self._state.event_type)
self.async_write_ha_state()
@callback
def _on_device_update(self) -> None:
"""Call when device updates or entry data changes."""
super()._on_device_update()
if self._entry_data.available:
# Event entities should go available directly
# when the device comes online and not wait
# for the next data push.
self.async_write_ha_state()
async_setup_entry = partial(
platform_async_setup_entry,

View File

@@ -13,6 +13,7 @@ from aioesphomeapi import (
APIConnectionError,
APIVersion,
DeviceInfo as EsphomeDeviceInfo,
EncryptionHelloAPIError,
EntityInfo,
HomeassistantServiceCall,
InvalidAuthAPIError,
@@ -570,6 +571,7 @@ class ESPHomeManager:
if isinstance(
err,
(
EncryptionHelloAPIError,
RequiresEncryptionAPIError,
InvalidEncryptionKeyAPIError,
InvalidAuthAPIError,

View File

@@ -16,7 +16,7 @@
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
"mqtt": ["esphome/discover/#"],
"requirements": [
"aioesphomeapi==29.7.0",
"aioesphomeapi==29.8.0",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==2.12.0"
],

View File

@@ -43,6 +43,9 @@
},
"description": "The ESPHome device {name} enabled transport encryption or changed the encryption key. Please enter the updated key. You can find it in the ESPHome Dashboard or in your device configuration."
},
"reauth_encryption_removed_confirm": {
"description": "The ESPHome device {name} disabled transport encryption. Please confirm that you want to remove the encryption key and allow unencrypted connections."
},
"discovery_confirm": {
"description": "Do you want to add the ESPHome node `{name}` to Home Assistant?",
"title": "Discovered ESPHome node"

View File

@@ -193,7 +193,6 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="max_kb_s_sent",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_max_kb_s_sent_state,
),
FritzSensorEntityDescription(
@@ -201,7 +200,6 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="max_kb_s_received",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_max_kb_s_received_state,
),
FritzSensorEntityDescription(
@@ -225,6 +223,7 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="link_kb_s_sent",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_link_kb_s_sent_state,
),
FritzSensorEntityDescription(
@@ -232,6 +231,7 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="link_kb_s_received",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_link_kb_s_received_state,
),
FritzSensorEntityDescription(

View File

@@ -6,6 +6,7 @@ from typing import Any
from homeassistant.components.climate import (
ATTR_HVAC_MODE,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
ClimateEntity,
@@ -38,7 +39,7 @@ from .sensor import value_scheduled_preset
HVAC_MODES = [HVACMode.HEAT, HVACMode.OFF]
PRESET_HOLIDAY = "holiday"
PRESET_SUMMER = "summer"
PRESET_MODES = [PRESET_ECO, PRESET_COMFORT]
PRESET_MODES = [PRESET_ECO, PRESET_COMFORT, PRESET_BOOST]
SUPPORTED_FEATURES = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.PRESET_MODE
@@ -194,6 +195,8 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
return PRESET_HOLIDAY
if self.data.summer_active:
return PRESET_SUMMER
if self.data.target_temperature == ON_API_TEMPERATURE:
return PRESET_BOOST
if self.data.target_temperature == self.data.comfort_temperature:
return PRESET_COMFORT
if self.data.target_temperature == self.data.eco_temperature:
@@ -211,6 +214,8 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
await self.async_set_temperature(temperature=self.data.comfort_temperature)
elif preset_mode == PRESET_ECO:
await self.async_set_temperature(temperature=self.data.eco_temperature)
elif preset_mode == PRESET_BOOST:
await self.async_set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
@property
def extra_state_attributes(self) -> ClimateExtraAttributes:

View File

@@ -182,10 +182,10 @@
"state": {
"startup": "Startup",
"running": "Running",
"standby": "Standby",
"standby": "[%key:common::state::standby%]",
"bootloading": "Bootloading",
"error": "Error",
"idle": "Idle",
"idle": "[%key:common::state::idle%]",
"ready": "Ready",
"sleeping": "Sleeping"
}

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250327.1"]
"requirements": ["home-assistant-frontend==20250328.0"]
}

View File

@@ -0,0 +1 @@
"""Gaggenau virtual integration."""

View File

@@ -0,0 +1,6 @@
{
"domain": "gaggenau",
"name": "Gaggenau",
"integration_type": "virtual",
"supported_by": "home_connect"
}

View File

@@ -539,10 +539,14 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
return
assert self._cur_temp is not None and self._target_temp is not None
too_cold = self._target_temp >= self._cur_temp + self._cold_tolerance
too_hot = self._cur_temp >= self._target_temp + self._hot_tolerance
min_temp = self._target_temp - self._cold_tolerance
max_temp = self._target_temp + self._hot_tolerance
if self._is_device_active:
if (self.ac_mode and too_cold) or (not self.ac_mode and too_hot):
if (self.ac_mode and self._cur_temp <= min_temp) or (
not self.ac_mode and self._cur_temp >= max_temp
):
_LOGGER.debug("Turning off heater %s", self.heater_entity_id)
await self._async_heater_turn_off()
elif time is not None:
@@ -552,7 +556,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
self.heater_entity_id,
)
await self._async_heater_turn_on()
elif (self.ac_mode and too_hot) or (not self.ac_mode and too_cold):
elif (self.ac_mode and self._cur_temp > max_temp) or (
not self.ac_mode and self._cur_temp < min_temp
):
_LOGGER.debug("Turning on heater %s", self.heater_entity_id)
await self._async_heater_turn_on()
elif time is not None:

View File

@@ -21,17 +21,17 @@
"heater": "Switch entity used to cool or heat depending on A/C mode.",
"target_sensor": "Temperature sensor that reflects the current temperature.",
"min_cycle_duration": "Set a minimum amount of time that the switch specified must be in its current state prior to being switched either off or on.",
"cold_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched on. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will start when the sensor equals or goes below 24.5.",
"cold_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched on. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will start when the sensor goes below 24.5.",
"hot_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched off. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will stop when the sensor equals or goes above 25.5."
}
},
"presets": {
"title": "Temperature presets",
"data": {
"away_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::away%]",
"home_temp": "[%key:common::state::home%]",
"away_temp": "[%key:common::state::not_home%]",
"comfort_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]",
"eco_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::eco%]",
"home_temp": "[%key:common::state::home%]",
"sleep_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::sleep%]",
"activity_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::activity%]"
}
@@ -63,10 +63,10 @@
"presets": {
"title": "[%key:component::generic_thermostat::config::step::presets::title%]",
"data": {
"away_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::away%]",
"home_temp": "[%key:common::state::home%]",
"away_temp": "[%key:common::state::not_home%]",
"comfort_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]",
"eco_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::eco%]",
"home_temp": "[%key:common::state::home%]",
"sleep_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::sleep%]",
"activity_temp": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::activity%]"
}

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.0.1"]
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.0.3"]
}

View File

@@ -8,7 +8,7 @@
"integration_type": "service",
"iot_class": "cloud_push",
"requirements": [
"google-cloud-texttospeech==2.17.2",
"google-cloud-speech==2.27.0"
"google-cloud-texttospeech==2.25.1",
"google-cloud-speech==2.31.1"
]
}

View File

@@ -7,7 +7,7 @@ import logging
from types import MappingProxyType
from typing import Any
from google import genai # type: ignore[attr-defined]
from google import genai
from google.genai.errors import APIError, ClientError
from requests.exceptions import Timeout
import voluptuous as vol

View File

@@ -356,6 +356,15 @@ class GoogleGenerativeAIConversationEntity(
messages.append(_convert_content(chat_content))
# The SDK requires the first message to be a user message
# This is not the case if user used `start_conversation`
# Workaround from https://github.com/googleapis/python-genai/issues/529#issuecomment-2740964537
if messages and messages[0].role != "user":
messages.insert(
0,
Content(role="user", parts=[Part.from_text(text=" ")]),
)
if tool_results:
messages.append(_create_google_tool_response_content(tool_results))
generateContentConfig = GenerateContentConfig(

View File

@@ -16,13 +16,13 @@
"name": "Panel light"
},
"quiet": {
"name": "Quiet"
"name": "Quiet mode"
},
"fresh_air": {
"name": "Fresh air"
},
"xfan": {
"name": "XFan"
"name": "Xtra fan"
},
"health_mode": {
"name": "Health mode"

View File

@@ -2,11 +2,13 @@
ATTR_PASSWORD = "password"
ATTR_USERNAME = "username"
ATTR_QUEUE_IDS = "queue_ids"
DOMAIN = "heos"
ENTRY_TITLE = "HEOS System"
SERVICE_GET_QUEUE = "get_queue"
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
SERVICE_GROUP_VOLUME_UP = "group_volume_up"
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
SERVICE_SIGN_IN = "sign_in"
SERVICE_SIGN_OUT = "sign_out"

View File

@@ -3,6 +3,9 @@
"get_queue": {
"service": "mdi:playlist-music"
},
"remove_from_queue": {
"service": "mdi:playlist-remove"
},
"group_volume_set": {
"service": "mdi:volume-medium"
},

View File

@@ -24,12 +24,10 @@ from pyheos import (
const as heos_const,
)
from pyheos.util import mediauri as heos_source
import voluptuous as vol
from homeassistant.components import media_source
from homeassistant.components.media_player import (
ATTR_MEDIA_ENQUEUE,
ATTR_MEDIA_VOLUME_LEVEL,
BrowseError,
BrowseMedia,
MediaClass,
@@ -43,30 +41,16 @@ from homeassistant.components.media_player import (
)
from homeassistant.components.media_source import BrowseMediaSource
from homeassistant.const import Platform
from homeassistant.core import (
HomeAssistant,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.core import HomeAssistant, ServiceResponse, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import (
config_validation as cv,
entity_platform,
entity_registry as er,
)
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utcnow
from .const import (
DOMAIN as HEOS_DOMAIN,
SERVICE_GET_QUEUE,
SERVICE_GROUP_VOLUME_DOWN,
SERVICE_GROUP_VOLUME_SET,
SERVICE_GROUP_VOLUME_UP,
)
from . import services
from .const import DOMAIN as HEOS_DOMAIN
from .coordinator import HeosConfigEntry, HeosCoordinator
PARALLEL_UPDATES = 0
@@ -137,25 +121,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add media players for a config entry."""
# Register custom entity services
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_GET_QUEUE,
None,
"async_get_queue",
supports_response=SupportsResponse.ONLY,
)
platform.async_register_entity_service(
SERVICE_GROUP_VOLUME_SET,
{vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float},
"async_set_group_volume_level",
)
platform.async_register_entity_service(
SERVICE_GROUP_VOLUME_DOWN, None, "async_group_volume_down"
)
platform.async_register_entity_service(
SERVICE_GROUP_VOLUME_UP, None, "async_group_volume_up"
)
services.register_media_player_services()
def add_entities_callback(players: Sequence[HeosPlayer]) -> None:
"""Add entities for each player."""
@@ -387,6 +353,15 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity):
await self._player.play_preset_station(index)
return
if media_type == "queue":
# media_id must be an int
try:
queue_id = int(media_id)
except ValueError:
raise ValueError(f"Invalid queue id '{media_id}'") from None
await self._player.play_queue(queue_id)
return
raise ValueError(f"Unsupported media type '{media_type}'")
@catch_action_error("select source")
@@ -500,6 +475,10 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity):
await self.coordinator.heos.set_group(new_members)
return
async def async_remove_from_queue(self, queue_ids: list[int]) -> None:
"""Remove items from the queue."""
await self._player.remove_from_queue(queue_ids)
@property
def available(self) -> bool:
"""Return True if the device is available."""

View File

@@ -1,19 +1,33 @@
"""Services for the HEOS integration."""
from dataclasses import dataclass
import logging
from typing import Final
from pyheos import CommandAuthenticationError, Heos, HeosError
import voluptuous as vol
from homeassistant.components.media_player import ATTR_MEDIA_VOLUME_LEVEL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers import (
config_validation as cv,
entity_platform,
issue_registry as ir,
)
from homeassistant.helpers.typing import VolDictType, VolSchemaType
from .const import (
ATTR_PASSWORD,
ATTR_QUEUE_IDS,
ATTR_USERNAME,
DOMAIN,
SERVICE_GET_QUEUE,
SERVICE_GROUP_VOLUME_DOWN,
SERVICE_GROUP_VOLUME_SET,
SERVICE_GROUP_VOLUME_UP,
SERVICE_REMOVE_FROM_QUEUE,
SERVICE_SIGN_IN,
SERVICE_SIGN_OUT,
)
@@ -44,6 +58,62 @@ def register(hass: HomeAssistant) -> None:
)
@dataclass(frozen=True)
class EntityServiceDescription:
"""Describe an entity service."""
name: str
method_name: str
schema: VolDictType | VolSchemaType | None = None
supports_response: SupportsResponse = SupportsResponse.NONE
def async_register(self, platform: entity_platform.EntityPlatform) -> None:
"""Register the service with the platform."""
platform.async_register_entity_service(
self.name,
self.schema,
self.method_name,
supports_response=self.supports_response,
)
REMOVE_FROM_QUEUE_SCHEMA: Final[VolDictType] = {
vol.Required(ATTR_QUEUE_IDS): vol.All(
cv.ensure_list,
[vol.All(cv.positive_int, vol.Range(min=1))],
vol.Unique(),
)
}
GROUP_VOLUME_SET_SCHEMA: Final[VolDictType] = {
vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float
}
MEDIA_PLAYER_ENTITY_SERVICES: Final = (
# Player queue services
EntityServiceDescription(
SERVICE_GET_QUEUE, "async_get_queue", supports_response=SupportsResponse.ONLY
),
EntityServiceDescription(
SERVICE_REMOVE_FROM_QUEUE, "async_remove_from_queue", REMOVE_FROM_QUEUE_SCHEMA
),
# Group volume services
EntityServiceDescription(
SERVICE_GROUP_VOLUME_SET,
"async_set_group_volume_level",
GROUP_VOLUME_SET_SCHEMA,
),
EntityServiceDescription(SERVICE_GROUP_VOLUME_DOWN, "async_group_volume_down"),
EntityServiceDescription(SERVICE_GROUP_VOLUME_UP, "async_group_volume_up"),
)
def register_media_player_services() -> None:
"""Register media_player entity services."""
platform = entity_platform.async_get_current_platform()
for service in MEDIA_PLAYER_ENTITY_SERVICES:
service.async_register(platform)
def _get_controller(hass: HomeAssistant) -> Heos:
"""Get the HEOS controller instance."""
_LOGGER.warning(

View File

@@ -4,6 +4,19 @@ get_queue:
integration: heos
domain: media_player
remove_from_queue:
target:
entity:
integration: heos
domain: media_player
fields:
queue_ids:
required: true
selector:
text:
multiple: true
type: number
group_volume_set:
target:
entity:

View File

@@ -90,6 +90,16 @@
"name": "Get queue",
"description": "Retrieves the queue of the media player."
},
"remove_from_queue": {
"name": "Remove from queue",
"description": "Removes items from the play queue.",
"fields": {
"queue_ids": {
"name": "Queue IDs",
"description": "The IDs (indexes) of the items in the queue to remove."
}
}
},
"group_volume_down": {
"name": "Turn down group volume",
"description": "Turns down the group volume."

View File

@@ -8,7 +8,7 @@
"step": {
"user": {
"data": {
"country": "Country"
"country": "[%key:common::config_flow::data::country%]"
}
},
"options": {

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
from asyncio import sleep as asyncio_sleep
from collections import defaultdict
from collections.abc import Callable
from contextlib import suppress
from dataclasses import dataclass
import logging
from typing import Any, cast
@@ -119,8 +120,11 @@ class HomeConnectCoordinator(
self.__dict__.pop("context_listeners", None)
def remove_listener_and_invalidate_context_listeners() -> None:
remove_listener()
self.__dict__.pop("context_listeners", None)
# There are cases where the remove_listener will be called
# although it has been already removed somewhere else
with suppress(KeyError):
remove_listener()
self.__dict__.pop("context_listeners", None)
return remove_listener_and_invalidate_context_listeners

View File

@@ -1,7 +1,10 @@
"""Provides a sensor for Home Connect."""
from collections import defaultdict
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta
from functools import partial
import logging
from typing import cast
@@ -14,7 +17,7 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfVolume
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util, slugify
@@ -42,7 +45,6 @@ class HomeConnectSensorEntityDescription(
):
"""Entity Description class for sensors."""
default_value: str | None = None
appliance_types: tuple[str, ...] | None = None
fetch_unit: bool = False
@@ -198,7 +200,6 @@ EVENT_SENSORS = (
key=EventKey.BSH_COMMON_EVENT_PROGRAM_ABORTED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="program_aborted",
appliance_types=("Dishwasher", "CleaningRobot", "CookProcessor"),
),
@@ -206,7 +207,6 @@ EVENT_SENSORS = (
key=EventKey.BSH_COMMON_EVENT_PROGRAM_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="program_finished",
appliance_types=(
"Oven",
@@ -222,7 +222,6 @@ EVENT_SENSORS = (
key=EventKey.BSH_COMMON_EVENT_ALARM_CLOCK_ELAPSED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="alarm_clock_elapsed",
appliance_types=("Oven", "Cooktop"),
),
@@ -230,7 +229,6 @@ EVENT_SENSORS = (
key=EventKey.COOKING_OVEN_EVENT_PREHEAT_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="preheat_finished",
appliance_types=("Oven", "Cooktop"),
),
@@ -238,7 +236,6 @@ EVENT_SENSORS = (
key=EventKey.COOKING_OVEN_EVENT_REGULAR_PREHEAT_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="regular_preheat_finished",
appliance_types=("Oven",),
),
@@ -246,7 +243,6 @@ EVENT_SENSORS = (
key=EventKey.LAUNDRY_CARE_DRYER_EVENT_DRYING_PROCESS_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="drying_process_finished",
appliance_types=("Dryer",),
),
@@ -254,7 +250,6 @@ EVENT_SENSORS = (
key=EventKey.DISHCARE_DISHWASHER_EVENT_SALT_NEARLY_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="salt_nearly_empty",
appliance_types=("Dishwasher",),
),
@@ -262,7 +257,6 @@ EVENT_SENSORS = (
key=EventKey.DISHCARE_DISHWASHER_EVENT_RINSE_AID_NEARLY_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="rinse_aid_nearly_empty",
appliance_types=("Dishwasher",),
),
@@ -270,7 +264,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="bean_container_empty",
appliance_types=("CoffeeMaker",),
),
@@ -278,7 +271,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_WATER_TANK_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="water_tank_empty",
appliance_types=("CoffeeMaker",),
),
@@ -286,7 +278,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DRIP_TRAY_FULL,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="drip_tray_full",
appliance_types=("CoffeeMaker",),
),
@@ -294,7 +285,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_KEEP_MILK_TANK_COOL,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="keep_milk_tank_cool",
appliance_types=("CoffeeMaker",),
),
@@ -302,7 +292,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_20_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="descaling_in_20_cups",
appliance_types=("CoffeeMaker",),
),
@@ -310,7 +299,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_15_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="descaling_in_15_cups",
appliance_types=("CoffeeMaker",),
),
@@ -318,7 +306,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_10_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="descaling_in_10_cups",
appliance_types=("CoffeeMaker",),
),
@@ -326,7 +313,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_5_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="descaling_in_5_cups",
appliance_types=("CoffeeMaker",),
),
@@ -334,7 +320,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_DESCALED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_should_be_descaled",
appliance_types=("CoffeeMaker",),
),
@@ -342,7 +327,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_DESCALING_OVERDUE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_descaling_overdue",
appliance_types=("CoffeeMaker",),
),
@@ -350,7 +334,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_DESCALING_BLOCKAGE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_descaling_blockage",
appliance_types=("CoffeeMaker",),
),
@@ -358,7 +341,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_CLEANED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_should_be_cleaned",
appliance_types=("CoffeeMaker",),
),
@@ -366,7 +348,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CLEANING_OVERDUE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_cleaning_overdue",
appliance_types=("CoffeeMaker",),
),
@@ -374,7 +355,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN20CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="calc_n_clean_in20cups",
appliance_types=("CoffeeMaker",),
),
@@ -382,7 +362,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN15CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="calc_n_clean_in15cups",
appliance_types=("CoffeeMaker",),
),
@@ -390,7 +369,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN10CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="calc_n_clean_in10cups",
appliance_types=("CoffeeMaker",),
),
@@ -398,7 +376,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN5CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="calc_n_clean_in5cups",
appliance_types=("CoffeeMaker",),
),
@@ -406,7 +383,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_CALC_N_CLEANED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_should_be_calc_n_cleaned",
appliance_types=("CoffeeMaker",),
),
@@ -414,7 +390,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CALC_N_CLEAN_OVERDUE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_calc_n_clean_overdue",
appliance_types=("CoffeeMaker",),
),
@@ -422,7 +397,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CALC_N_CLEAN_BLOCKAGE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="device_calc_n_clean_blockage",
appliance_types=("CoffeeMaker",),
),
@@ -430,7 +404,6 @@ EVENT_SENSORS = (
key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="freezer_door_alarm",
appliance_types=("FridgeFreezer", "Freezer"),
),
@@ -438,7 +411,6 @@ EVENT_SENSORS = (
key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_REFRIGERATOR,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="refrigerator_door_alarm",
appliance_types=("FridgeFreezer", "Refrigerator"),
),
@@ -446,7 +418,6 @@ EVENT_SENSORS = (
key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_TEMPERATURE_ALARM_FREEZER,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="freezer_temperature_alarm",
appliance_types=("FridgeFreezer", "Freezer"),
),
@@ -454,7 +425,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_EMPTY_DUST_BOX_AND_CLEAN_FILTER,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="empty_dust_box_and_clean_filter",
appliance_types=("CleaningRobot",),
),
@@ -462,7 +432,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_ROBOT_IS_STUCK,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="robot_is_stuck",
appliance_types=("CleaningRobot",),
),
@@ -470,7 +439,6 @@ EVENT_SENSORS = (
key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_DOCKING_STATION_NOT_FOUND,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="docking_station_not_found",
appliance_types=("CleaningRobot",),
),
@@ -478,7 +446,6 @@ EVENT_SENSORS = (
key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_1_FILL_LEVEL_POOR,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="poor_i_dos_1_fill_level",
appliance_types=("Washer", "WasherDryer"),
),
@@ -486,7 +453,6 @@ EVENT_SENSORS = (
key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_2_FILL_LEVEL_POOR,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="poor_i_dos_2_fill_level",
appliance_types=("Washer", "WasherDryer"),
),
@@ -494,7 +460,6 @@ EVENT_SENSORS = (
key=EventKey.COOKING_COMMON_EVENT_HOOD_GREASE_FILTER_MAX_SATURATION_NEARLY_REACHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="grease_filter_max_saturation_nearly_reached",
appliance_types=("Hood",),
),
@@ -502,7 +467,6 @@ EVENT_SENSORS = (
key=EventKey.COOKING_COMMON_EVENT_HOOD_GREASE_FILTER_MAX_SATURATION_REACHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
default_value="off",
translation_key="grease_filter_max_saturation_reached",
appliance_types=("Hood",),
),
@@ -515,12 +479,6 @@ def _get_entities_for_appliance(
) -> list[HomeConnectEntity]:
"""Get a list of entities."""
return [
*[
HomeConnectEventSensor(entry.runtime_data, appliance, description)
for description in EVENT_SENSORS
if description.appliance_types
and appliance.info.type in description.appliance_types
],
*[
HomeConnectProgramSensor(entry.runtime_data, appliance, desc)
for desc in BSH_PROGRAM_SENSORS
@@ -534,6 +492,72 @@ def _get_entities_for_appliance(
]
def _add_event_sensor_entity(
entry: HomeConnectConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
appliance: HomeConnectApplianceData,
description: HomeConnectSensorEntityDescription,
remove_event_sensor_listener_list: list[Callable[[], None]],
) -> None:
"""Add an event sensor entity."""
if (
(appliance_data := entry.runtime_data.data.get(appliance.info.ha_id)) is None
) or description.key not in appliance_data.events:
return
for remove_listener in remove_event_sensor_listener_list:
remove_listener()
async_add_entities(
[
HomeConnectEventSensor(entry.runtime_data, appliance, description),
]
)
def _add_event_sensor_listeners(
entry: HomeConnectConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
remove_event_sensor_listener_dict: dict[str, list[CALLBACK_TYPE]],
) -> None:
for appliance in entry.runtime_data.data.values():
if appliance.info.ha_id in remove_event_sensor_listener_dict:
continue
for event_sensor_description in EVENT_SENSORS:
if appliance.info.type not in cast(
tuple[str, ...], event_sensor_description.appliance_types
):
continue
# We use a list as a kind of lazy initializer, as we can use the
# remove_listener while we are initializing it.
remove_event_sensor_listener_list = remove_event_sensor_listener_dict[
appliance.info.ha_id
]
remove_listener = entry.runtime_data.async_add_listener(
partial(
_add_event_sensor_entity,
entry,
async_add_entities,
appliance,
event_sensor_description,
remove_event_sensor_listener_list,
),
(appliance.info.ha_id, event_sensor_description.key),
)
remove_event_sensor_listener_list.append(remove_listener)
entry.async_on_unload(remove_listener)
def _remove_event_sensor_listeners_on_depaired(
entry: HomeConnectConfigEntry,
remove_event_sensor_listener_dict: dict[str, list[CALLBACK_TYPE]],
) -> None:
registered_listeners_ha_id = set(remove_event_sensor_listener_dict)
actual_appliances = set(entry.runtime_data.data)
for appliance_ha_id in registered_listeners_ha_id - actual_appliances:
for listener in remove_event_sensor_listener_dict.pop(appliance_ha_id):
listener()
async def async_setup_entry(
hass: HomeAssistant,
entry: HomeConnectConfigEntry,
@@ -546,6 +570,32 @@ async def async_setup_entry(
async_add_entities,
)
remove_event_sensor_listener_dict: dict[str, list[CALLBACK_TYPE]] = defaultdict(
list
)
entry.async_on_unload(
entry.runtime_data.async_add_special_listener(
partial(
_add_event_sensor_listeners,
entry,
async_add_entities,
remove_event_sensor_listener_dict,
),
(EventKey.BSH_COMMON_APPLIANCE_PAIRED,),
)
)
entry.async_on_unload(
entry.runtime_data.async_add_special_listener(
partial(
_remove_event_sensor_listeners_on_depaired,
entry,
remove_event_sensor_listener_dict,
),
(EventKey.BSH_COMMON_APPLIANCE_DEPAIRED,),
)
)
class HomeConnectSensor(HomeConnectEntity, SensorEntity):
"""Sensor class for Home Connect."""
@@ -650,8 +700,5 @@ class HomeConnectEventSensor(HomeConnectSensor):
def update_native_value(self) -> None:
"""Update the sensor's status."""
event = self.appliance.events.get(cast(EventKey, self.bsh_key))
if event:
self._update_native_value(event.value)
elif not self._attr_native_value:
self._attr_native_value = self.entity_description.default_value
event = self.appliance.events[cast(EventKey, self.bsh_key)]
self._update_native_value(event.value)

View File

@@ -64,7 +64,6 @@ set_program_and_options:
- selected_program
program:
example: dishcare_dishwasher_program_auto2
required: true
selector:
select:
mode: dropdown

View File

@@ -511,7 +511,7 @@
},
"spin_speed": {
"options": {
"laundry_care_washer_enum_type_spin_speed_off": "Off",
"laundry_care_washer_enum_type_spin_speed_off": "[%key:common::state::off%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_400": "400 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_600": "600 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_700": "700 rpm",
@@ -521,7 +521,7 @@
"laundry_care_washer_enum_type_spin_speed_r_p_m_1200": "1200 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "1400 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "1600 rpm",
"laundry_care_washer_enum_type_spin_speed_ul_off": "Off",
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:common::state::off%]",
"laundry_care_washer_enum_type_spin_speed_ul_low": "Low",
"laundry_care_washer_enum_type_spin_speed_ul_medium": "Medium",
"laundry_care_washer_enum_type_spin_speed_ul_high": "High"
@@ -529,7 +529,7 @@
},
"vario_perfect": {
"options": {
"laundry_care_common_enum_type_vario_perfect_off": "Off",
"laundry_care_common_enum_type_vario_perfect_off": "[%key:common::state::off%]",
"laundry_care_common_enum_type_vario_perfect_eco_perfect": "Eco perfect",
"laundry_care_common_enum_type_vario_perfect_speed_perfect": "Speed perfect"
}
@@ -1494,7 +1494,7 @@
"spin_speed": {
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_spin_speed::name%]",
"state": {
"laundry_care_washer_enum_type_spin_speed_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_off%]",
"laundry_care_washer_enum_type_spin_speed_off": "[%key:common::state::off%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_400%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_600%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_700": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_700%]",
@@ -1504,7 +1504,7 @@
"laundry_care_washer_enum_type_spin_speed_r_p_m_1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1200%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1400%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1600%]",
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_off%]",
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:common::state::off%]",
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_low%]",
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_medium%]",
"laundry_care_washer_enum_type_spin_speed_ul_high": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_high%]"
@@ -1513,7 +1513,7 @@
"vario_perfect": {
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_vario_perfect::name%]",
"state": {
"laundry_care_common_enum_type_vario_perfect_off": "[%key:component::home_connect::selector::vario_perfect::options::laundry_care_common_enum_type_vario_perfect_off%]",
"laundry_care_common_enum_type_vario_perfect_off": "[%key:common::state::off%]",
"laundry_care_common_enum_type_vario_perfect_eco_perfect": "[%key:component::home_connect::selector::vario_perfect::options::laundry_care_common_enum_type_vario_perfect_eco_perfect%]",
"laundry_care_common_enum_type_vario_perfect_speed_perfect": "[%key:component::home_connect::selector::vario_perfect::options::laundry_care_common_enum_type_vario_perfect_speed_perfect%]"
}

View File

@@ -31,7 +31,6 @@ class FirmwareUpdateCoordinator(DataUpdateCoordinator[FirmwareManifest]):
_LOGGER,
name="firmware update coordinator",
update_interval=FIRMWARE_REFRESH_INTERVAL,
always_update=False,
)
self.hass = hass
self.session = session

View File

@@ -199,7 +199,7 @@ class BaseFirmwareUpdateEntity(
# This entity is not currently associated with a device so we must manually
# give it a name
self._attr_name = f"{self._config_entry.title} Update"
self._attr_title = self.entity_description.firmware_name or "unknown"
self._attr_title = self.entity_description.firmware_name or "Unknown"
if (
self._current_firmware_info is None

View File

@@ -15,14 +15,13 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a Home Assistant SkyConnect config entry."""
await hass.config_entries.async_forward_entry_setups(entry, ["update"])
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
await hass.config_entries.async_unload_platforms(entry, ["update"])
return True

View File

@@ -21,11 +21,20 @@ from homeassistant.components.update import UpdateDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import FIRMWARE, FIRMWARE_VERSION, NABU_CASA_FIRMWARE_RELEASES_URL
from .const import (
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
NABU_CASA_FIRMWARE_RELEASES_URL,
PRODUCT,
SERIAL_NUMBER,
HardwareVariant,
)
_LOGGER = logging.getLogger(__name__)
@@ -42,7 +51,7 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
fw_type="skyconnect_zigbee_ncp",
version_key="ezsp_version",
expected_firmware_type=ApplicationType.EZSP,
firmware_name="EmberZNet",
firmware_name="EmberZNet Zigbee",
),
ApplicationType.SPINEL: FirmwareUpdateEntityDescription(
key="firmware",
@@ -55,6 +64,28 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
expected_firmware_type=ApplicationType.SPINEL,
firmware_name="OpenThread RCP",
),
ApplicationType.CPC: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type="skyconnect_multipan",
version_key="cpc_version",
expected_firmware_type=ApplicationType.CPC,
firmware_name="Multiprotocol",
),
ApplicationType.GECKO_BOOTLOADER: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None, # We don't want to update the bootloader
version_key="gecko_bootloader_version",
expected_firmware_type=ApplicationType.GECKO_BOOTLOADER,
firmware_name="Gecko Bootloader",
),
None: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
@@ -77,9 +108,16 @@ def _async_create_update_entity(
) -> FirmwareUpdateEntity:
"""Create an update entity that handles firmware type changes."""
firmware_type = config_entry.data[FIRMWARE]
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type) if firmware_type is not None else None
]
try:
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type)
]
except (KeyError, ValueError):
_LOGGER.debug(
"Unknown firmware type %r, using default entity description", firmware_type
)
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[None]
entity = FirmwareUpdateEntity(
device=config_entry.data["device"],
@@ -130,6 +168,7 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
"""SkyConnect firmware update entity."""
bootloader_reset_type = None
_attr_has_entity_name = True
def __init__(
self,
@@ -141,8 +180,18 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
"""Initialize the SkyConnect firmware update entity."""
super().__init__(device, config_entry, update_coordinator, entity_description)
self._attr_unique_id = (
f"{self._config_entry.data['serial_number']}_{self.entity_description.key}"
variant = HardwareVariant.from_usb_product_name(
self._config_entry.data[PRODUCT]
)
serial_number = self._config_entry.data[SERIAL_NUMBER]
self._attr_unique_id = f"{serial_number}_{self.entity_description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, serial_number)},
name=f"{variant.full_name} ({serial_number[:8]})",
model=variant.full_name,
manufacturer="Nabu Casa",
serial_number=serial_number,
)
# Use the cached firmware info if it exists
@@ -155,6 +204,17 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
source="homeassistant_sky_connect",
)
def _update_attributes(self) -> None:
"""Recompute the attributes of the entity."""
super()._update_attributes()
assert self.device_entry is not None
device_registry = dr.async_get(self.hass)
device_registry.async_update_device(
device_id=self.device_entry.id,
sw_version=f"{self.entity_description.firmware_name} {self._attr_installed_version}",
)
@callback
def _firmware_info_callback(self, firmware_info: FirmwareInfo) -> None:
"""Handle updated firmware info being pushed by an integration."""

View File

@@ -62,6 +62,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
await hass.config_entries.async_unload_platforms(entry, ["update"])
return True

View File

@@ -2,8 +2,9 @@
DOMAIN = "homeassistant_yellow"
RADIO_MODEL = "Home Assistant Yellow"
RADIO_MANUFACTURER = "Nabu Casa"
MODEL = "Home Assistant Yellow"
MANUFACTURER = "Nabu Casa"
RADIO_DEVICE = "/dev/ttyAMA1"
ZHA_HW_DISCOVERY_DATA = {

View File

@@ -149,5 +149,12 @@
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
}
},
"entity": {
"update": {
"firmware": {
"name": "Radio firmware"
}
}
}
}

View File

@@ -21,13 +21,17 @@ from homeassistant.components.update import UpdateDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
MANUFACTURER,
MODEL,
NABU_CASA_FIRMWARE_RELEASES_URL,
RADIO_DEVICE,
)
@@ -39,7 +43,7 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
ApplicationType | None, FirmwareUpdateEntityDescription
] = {
ApplicationType.EZSP: FirmwareUpdateEntityDescription(
key="firmware",
key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
@@ -47,10 +51,10 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
fw_type="yellow_zigbee_ncp",
version_key="ezsp_version",
expected_firmware_type=ApplicationType.EZSP,
firmware_name="EmberZNet",
firmware_name="EmberZNet Zigbee",
),
ApplicationType.SPINEL: FirmwareUpdateEntityDescription(
key="firmware",
key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
@@ -60,12 +64,34 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
expected_firmware_type=ApplicationType.SPINEL,
firmware_name="OpenThread RCP",
),
None: FirmwareUpdateEntityDescription(
ApplicationType.CPC: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type="yellow_multipan",
version_key="cpc_version",
expected_firmware_type=ApplicationType.CPC,
firmware_name="Multiprotocol",
),
ApplicationType.GECKO_BOOTLOADER: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None, # We don't want to update the bootloader
version_key="gecko_bootloader_version",
expected_firmware_type=ApplicationType.GECKO_BOOTLOADER,
firmware_name="Gecko Bootloader",
),
None: FirmwareUpdateEntityDescription(
key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None,
version_key=None,
expected_firmware_type=None,
@@ -82,9 +108,16 @@ def _async_create_update_entity(
) -> FirmwareUpdateEntity:
"""Create an update entity that handles firmware type changes."""
firmware_type = config_entry.data[FIRMWARE]
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type) if firmware_type is not None else None
]
try:
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type)
]
except (KeyError, ValueError):
_LOGGER.debug(
"Unknown firmware type %r, using default entity description", firmware_type
)
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[None]
entity = FirmwareUpdateEntity(
device=RADIO_DEVICE,
@@ -135,6 +168,7 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
"""Yellow firmware update entity."""
bootloader_reset_type = "yellow" # Triggers a GPIO reset
_attr_has_entity_name = True
def __init__(
self,
@@ -145,8 +179,13 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
) -> None:
"""Initialize the Yellow firmware update entity."""
super().__init__(device, config_entry, update_coordinator, entity_description)
self._attr_unique_id = self.entity_description.key
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, "yellow")},
name=MODEL,
model=MODEL,
manufacturer=MANUFACTURER,
)
# Use the cached firmware info if it exists
if self._config_entry.data[FIRMWARE] is not None:
@@ -158,6 +197,17 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
source="homeassistant_yellow",
)
def _update_attributes(self) -> None:
"""Recompute the attributes of the entity."""
super()._update_attributes()
assert self.device_entry is not None
device_registry = dr.async_get(self.hass)
device_registry.async_update_device(
device_id=self.device_entry.id,
sw_version=f"{self.entity_description.firmware_name} {self._attr_installed_version}",
)
@callback
def _firmware_info_callback(self, firmware_info: FirmwareInfo) -> None:
"""Handle updated firmware info being pushed by an integration."""

View File

@@ -297,8 +297,8 @@
"open": "[%key:common::state::open%]",
"closed": "[%key:common::state::closed%]",
"partial": "Partially open",
"opening": "Opening",
"closing": "Closing"
"opening": "[%key:common::state::opening%]",
"closing": "[%key:common::state::closing%]"
}
},
"uv": {

View File

@@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"iot_class": "local_push",
"loggers": ["aiohomekit", "commentjson"],
"requirements": ["aiohomekit==3.2.8"],
"requirements": ["aiohomekit==3.2.13"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
}

View File

@@ -141,7 +141,7 @@
"air_purifier_state_current": {
"state": {
"inactive": "Inactive",
"idle": "Idle",
"idle": "[%key:common::state::idle%]",
"purifying": "Purifying"
}
}

View File

@@ -57,7 +57,7 @@
},
"exceptions": {
"invalid_controller_id": {
"message": "Invalid controller_id \"{controller_id}\", expected one of \"{controller_ids}\""
"message": "Invalid controller ID \"{controller_id}\", expected one of \"{controller_ids}\""
}
},
"options": {

View File

@@ -55,7 +55,7 @@
"preset_mode": {
"state": {
"hold": "Hold",
"away": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::away%]",
"away": "[%key:common::state::not_home%]",
"none": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::none%]"
}
}

View File

@@ -63,14 +63,14 @@
"name": "Mode",
"state": {
"normal": "Normal",
"eco": "Eco",
"away": "Away",
"home": "[%key:common::state::home%]",
"away": "[%key:common::state::not_home%]",
"auto": "Auto",
"baby": "Baby",
"boost": "Boost",
"comfort": "Comfort",
"home": "[%key:common::state::home%]",
"sleep": "Sleep",
"auto": "Auto",
"baby": "Baby"
"eco": "Eco",
"sleep": "Sleep"
}
}
}

View File

@@ -32,7 +32,6 @@ class AqualinkEntity(Entity):
manufacturer=dev.manufacturer,
model=dev.model,
name=dev.label,
via_device=(DOMAIN, dev.system.serial),
)
async def async_added_to_hass(self) -> None:

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/iaqualink",
"iot_class": "cloud_polling",
"loggers": ["iaqualink"],
"requirements": ["iaqualink==0.5.0", "h2==4.1.0"],
"requirements": ["iaqualink==0.5.3", "h2==4.1.0"],
"single_config_entry": true
}

View File

@@ -26,10 +26,10 @@
"entity": {
"button": {
"connect": {
"name": "Connect"
"name": "[%key:common::action::connect%]"
},
"disconnect": {
"name": "Disconnect"
"name": "[%key:common::action::disconnect%]"
}
},
"sensor": {

View File

@@ -118,7 +118,7 @@
"tapwater_int": "Tap water internal",
"sensor_test": "Sensor test",
"central_heating": "Central heating",
"standby": "Stand-by",
"standby": "[%key:common::state::standby%]",
"postrun_boyler": "Post run boiler",
"service": "Service",
"tapwater": "Tap water",

View File

@@ -38,7 +38,7 @@
"state": {
"printing": "Printing",
"idle": "[%key:common::state::idle%]",
"stopped": "Stopped"
"stopped": "[%key:common::state::stopped%]"
}
},
"uptime": {

View File

@@ -138,7 +138,7 @@ async def async_setup_entry(
for vtype, _, vid in isy.variables.children:
numbers.append(isy.variables[vtype][vid])
if (
isy.conf[CONFIG_NETWORKING] or isy.conf[CONFIG_PORTAL]
isy.conf[CONFIG_NETWORKING] or isy.conf.get(CONFIG_PORTAL)
) and isy.networking.nobjs:
isy_data.devices[CONF_NETWORK] = _create_service_device_info(
isy, name=CONFIG_NETWORKING, unique_id=CONF_NETWORK
@@ -227,9 +227,9 @@ async def async_unload_entry(
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
isy_data = hass.data[DOMAIN][entry.entry_id]
isy_data: IsyData = hass.data[DOMAIN][entry.entry_id]
isy: ISY = isy_data.root
isy = isy_data.root
_LOGGER.debug("ISY Stopping Event Stream and automatic updates")
isy.websocket.stop()

View File

@@ -181,6 +181,7 @@ class ISYProgramEntity(ISYEntity):
_actions: Program
_status: Program
_node: Program
def __init__(self, name: str, status: Program, actions: Program = None) -> None:
"""Initialize the ISY program-based entity."""

View File

@@ -24,7 +24,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["pyisy"],
"requirements": ["pyisy==3.1.14"],
"requirements": ["pyisy==3.4.0"],
"ssdp": [
{
"manufacturer": "Universal Devices Inc.",

View File

@@ -21,6 +21,7 @@ from homeassistant.helpers.service import entity_service_call
from homeassistant.helpers.typing import VolDictType
from .const import _LOGGER, DOMAIN
from .models import IsyData
# Common Services for All Platforms:
SERVICE_SEND_PROGRAM_COMMAND = "send_program_command"
@@ -149,7 +150,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
isy_name = service.data.get(CONF_ISY)
for config_entry_id in hass.data[DOMAIN]:
isy_data = hass.data[DOMAIN][config_entry_id]
isy_data: IsyData = hass.data[DOMAIN][config_entry_id]
isy = isy_data.root
if isy_name and isy_name != isy.conf["name"]:
continue

View File

@@ -157,7 +157,7 @@ class ISYEnableSwitchEntity(ISYAuxControlEntity, SwitchEntity):
device_info=device_info,
)
self._attr_name = description.name # Override super
self._change_handler: EventListener = None
self._change_handler: EventListener | None = None
# pylint: disable-next=hass-missing-super-call
async def async_added_to_hass(self) -> None:

View File

@@ -6,6 +6,7 @@ count_omer:
selector:
date:
nusach:
required: true
example: "sfarad"
default: "sfarad"
selector:

View File

@@ -3,9 +3,9 @@
"sensor": {
"hebrew_date": {
"state_attributes": {
"hebrew_year": { "name": "Hebrew Year" },
"hebrew_month_name": { "name": "Hebrew Month Name" },
"hebrew_day": { "name": "Hebrew Day" }
"hebrew_year": { "name": "Hebrew year" },
"hebrew_month_name": { "name": "Hebrew month name" },
"hebrew_day": { "name": "Hebrew day" }
}
}
}
@@ -16,10 +16,10 @@
"data": {
"name": "[%key:common::config_flow::data::name%]",
"diaspora": "Outside of Israel?",
"language": "Language for Holidays and Dates",
"language": "Language for holidays and dates",
"location": "[%key:common::config_flow::data::location%]",
"elevation": "[%key:common::config_flow::data::elevation%]",
"time_zone": "Time Zone"
"time_zone": "Time zone"
},
"data_description": {
"time_zone": "If you specify a location, make sure to specify the time zone for correct calendar times calculations"
@@ -36,7 +36,7 @@
"init": {
"title": "Configure options for Jewish Calendar",
"data": {
"candle_lighting_minutes_before_sunset": "Minutes before sunset for candle lighthing",
"candle_lighting_minutes_before_sunset": "Minutes before sunset for candle lighting",
"havdalah_minutes_after_sunset": "Minutes after sunset for Havdalah"
},
"data_description": {
@@ -70,7 +70,7 @@
"description": "Nusach to count the Omer in."
},
"language": {
"name": "Language",
"name": "[%key:common::config_flow::data::language%]",
"description": "Language to count the Omer in."
}
}

View File

@@ -316,10 +316,10 @@
"name": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::name%]",
"state": {
"auto": "Auto",
"building_protection": "Building protection",
"comfort": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]",
"standby": "Standby",
"economy": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::eco%]",
"building_protection": "Building protection"
"standby": "[%key:common::state::standby%]"
}
}
}

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