Compare commits

...

300 Commits

Author SHA1 Message Date
Paulus Schoutsen
cf259c2278 Expose nevermind intent to LLMs 2024-11-30 04:02:43 +00:00
Josef Zweck
e8ced4fa12 Bump aioacaia to 0.1.10 (#131906) 2024-11-29 22:32:20 -05:00
Josef Zweck
d9cef1e708 Guard against hostname change in lamarzocco discovery (#131873)
* Guard against hostname change in lamarzocco discovery

* switch to abort_entries_match
2024-11-29 22:31:56 -05:00
Marcel van der Veldt
a760786faf Fix media player join action for Music Assistant integration (#131910)
* Fix media player join action for Music Assistant integration

* Add tests for join/unjoin

* add one more test
2024-11-29 22:11:57 -05:00
J. Diego Rodríguez Royo
8c6a24c368 Use HomeAssistant error in the right cases (#131923)
* Use the correct exceptions

* Improved exception strings
2024-11-29 22:11:15 -05:00
Manu
24bd61be3b Add missing state_class in IronOS (#131928)
Add missing state class in IronOS
2024-11-29 22:10:12 -05:00
Matthias Alphart
1abd2209b3 Fix KNX IP Secure tunnelling endpoint selection with keyfile (#131941) 2024-11-30 01:13:52 +01:00
epenet
aa206c7608 Use typed ConfigEntry in discovergy (#131891) 2024-11-29 20:28:18 +01:00
Sid
87020e8945 Bump ruff to 0.8.1 (#131927) 2024-11-29 20:23:57 +01:00
Manu
dd62fb387e Bump pynecil to v1.0.1 (#131935) 2024-11-29 20:23:10 +01:00
Raphael Hehl
c19038ced6 Bump uiprotect to 6.6.4 (#131931) 2024-11-29 12:47:33 -06:00
Jc2k
6144cc26ba Bump aiohomekit to 3.2.7 (#131924) 2024-11-29 11:29:10 -06:00
Allen Porter
920c958ec7 Add runtime_data rule to quality_scale hassfest validation (#131857)
* Add quality scale check for runtime_data

* Linter fixes

* Add developer documentation link

* Update script/hassfest/quality_scale_validation/runtime_data.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update validation to check explicitly for ConfigEntry.runtime_data

* Update script/hassfest/quality_scale_validation/runtime_data.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Refine check for setting attributes

* Patch with changes from epenet

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-29 17:56:26 +01:00
epenet
0fc365a114 Add discovery rule to quality_scale hassfest validation (#131890) 2024-11-29 07:06:38 -08:00
David Knowles
954ac0d288 Ensure Schlage exceptions are translated (#131733) 2024-11-28 20:34:20 -08:00
epenet
28cfa37248 Add unique_config_entry rule to quality_scale hassfest validation (#131878)
* Add unique_config_entry rule to quality_scale hassfest validation

* Improve message
2024-11-28 20:08:43 -08:00
epenet
24f7bae5f2 Add documentation URL to quality_scale hassfest validation (#131879)
* Add documentation URL to quality_scale hassfest validation

* Adjust
2024-11-28 18:32:01 -08:00
Manu
8e12fbff88 Refactor calendars in Habitica (#131020)
* Refactor calendars

* changes
2024-11-28 18:31:38 -08:00
Robert Resch
5c8fb5ec2c Remove deprecated climate constants (#131798)
* Remove deprecated climate constants

* Fix

* Fix

* Fix

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-29 00:38:05 +01:00
Robert Resch
a68cf21179 Remove deprecated data entry flow constants (#131800)
* Remove deprecated data entry flow constants

* Fix

* Fix

* Fix

* Fix

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-29 00:37:26 +01:00
epenet
d596b4169d Add strict_typing rule to quality_scale hassfest validation (#131877)
* Add strict_typing rule to quality_scale hassfest validation

* Add acaia to .strict-typing
2024-11-28 22:05:34 +01:00
IceBotYT
8b467268df Add data descriptions to Nice G.O. config flow (#131865)
* Add data descriptions to Nice G.O. config flow

* Reference other strings instead
2024-11-28 12:09:01 -08:00
Richard Kroegel
6dd93253c6 Add captcha to BMW ConfigFlow (#131351)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-11-28 21:01:00 +01:00
Joost Lekkerkerker
9db6f0ffc4 Only download translation strings we have defined (#131864) 2024-11-28 20:52:51 +01:00
karwosts
889ac1552b Fix flaky test in history stats (#131869) 2024-11-28 20:51:23 +01:00
Bram Kragten
18db16b82c Update frontend to 20241127.1 (#131855) 2024-11-28 20:50:53 +01:00
Robert Resch
1f9ecfe839 Remove deprecated sensor constants (#131843) 2024-11-28 20:49:49 +01:00
Allen Porter
4d32fe97c3 Use ConfigEntry.runtime_data in Nest (#131871) 2024-11-28 20:45:27 +01:00
rd-blue
8feb6c7e06 Correction of prices update time in Tibber integration (with CLA now) (#131861)
correction of prices update time
2024-11-28 19:58:38 +01:00
Madhan
0b36a6d7f3 Bump PyMetEireann to 2024.11.0 (#131860)
Co-authored-by: Joostlek <joostlek@outlook.com>
2024-11-28 19:48:38 +01:00
epenet
837716b69e Add diagnostics rule to quality_scale hassfest validation (#131859) 2024-11-28 19:42:31 +01:00
Michael
1a9ab07742 Allow empty trigger sentence responses in conversations (#131849)
allow empty trigger sentence responses
2024-11-28 18:30:05 +01:00
Allen Porter
8862c5c4d8 Remove unnecessary hass.data defaults from Rainbird (#131858) 2024-11-28 09:16:58 -08:00
Joost Lekkerkerker
87320609dc Bump pyatv to 0.16.0 (#131852) 2024-11-28 11:04:00 -06:00
epenet
62e788c7da Add config flow rules to quality_scale hassfest validation (#131791)
* Add config flow rules to quality_scale hassfest validation

* Use integration.config_flow property
2024-11-28 17:58:56 +01:00
Erik Montnemery
bbce183faf Deprecate dt_util.utc_to_timestamp (#131787) 2024-11-28 17:00:20 +01:00
Robert Resch
0389800e2a Remove deprecated humidifier constants (#131844) 2024-11-28 16:59:11 +01:00
Robert Resch
0c5c09390c Remove deprecated fan constants (#131845) 2024-11-28 16:56:04 +01:00
Manu
57b099c2aa Add unit translations to Ista EcoTrend integration (#131768) 2024-11-28 16:55:07 +01:00
Robert Resch
ed408eb1a1 Remove deprecated device tracker constants (#131846) 2024-11-28 16:54:23 +01:00
Erik Montnemery
f7d2d06c9b Add comments in homeassistant/components/recorder/migration.py (#131820)
* Add comments in homeassistant/components/recorder/migration.py

* Update homeassistant/components/recorder/migration.py
2024-11-28 16:22:56 +01:00
Manu
3071aa2da1 Use common string for items unit in Bring (#131834) 2024-11-28 14:59:16 +01:00
Joost Lekkerkerker
474544abd8 Make wake word selection part of configuration (#131832) 2024-11-28 13:45:51 +01:00
Joost Lekkerkerker
dc064237ca Bump spotifyaio to 0.8.10 (#131827) 2024-11-28 13:45:10 +01:00
Robert Resch
a0584a0516 Remove deprecated switch constants (#131806)
* Remove deprecated switch constants

* Fix
2024-11-28 13:45:00 +01:00
Norbert Rittel
96dfa0e0cf Remove wrong plural "s" in 'todo.remove_item' action (#131814) 2024-11-28 13:44:40 +01:00
epenet
00d82363fe Delay "Split tests for full run" in CI (#131813)
Adjust split tests requirements in CI
2024-11-28 13:44:02 +01:00
epenet
c4e5b59326 Fix more flaky translation checks (#131824) 2024-11-28 13:41:30 +01:00
Erik Montnemery
d9832f8c3a Rename constant in tests/components/recorder/test_migration_from_schema_32.py (#131819) 2024-11-28 13:26:58 +01:00
epenet
f41bc98fe2 Cleanup deprecated exception in websocket tests (#131808) 2024-11-28 12:40:34 +01:00
Joost Lekkerkerker
3a76bfb857 Remove Spotify featured playlists and categories from media browser (#131758) 2024-11-28 12:34:06 +01:00
epenet
6ce5c89711 Fix group flaky test (#131815) 2024-11-28 12:29:38 +01:00
Franck Nijhof
9d387acb97 Ensure custom integrations are assigned the custom IQS scale (#131795) 2024-11-28 12:25:16 +01:00
Robert Resch
1d09a5bf89 Remove deprecated lock constants (#131812) 2024-11-28 12:21:13 +01:00
Robert Resch
a01e7cd6cf Remove deprecated number constants (#131810) 2024-11-28 12:20:43 +01:00
Robert Resch
3e0326dd66 Remove deprecated siren constants (#131807) 2024-11-28 12:14:43 +01:00
Robert Resch
4d27a32905 Remove deprecated cover constants (#131797) 2024-11-28 12:14:25 +01:00
Robert Resch
c5f68bcc58 Remove deprecated remote constants (#131809) 2024-11-28 12:14:06 +01:00
Robert Resch
3866176e1d Remove deprecated water heater constants (#131805) 2024-11-28 12:13:03 +01:00
Robert Resch
a67045ee6c Remove deprecated home assistant const constants (#131799) 2024-11-28 12:12:37 +01:00
Robert Resch
54ff6feadc Remove deprecated alarm control panel constants (#131790) 2024-11-28 12:11:08 +01:00
Robert Resch
fd14add67b Remove deprecated device registry constants (#131802) 2024-11-28 11:20:44 +01:00
Robert Resch
b28f352902 Remove deprecated binary sensor constants (#131793) 2024-11-28 11:08:18 +01:00
Robert Resch
fb152c7d22 Remove deprecated automation constants (#131792) 2024-11-28 11:07:00 +01:00
Robert Resch
be81fd86d3 Remvove deprecated core constants (#131803) 2024-11-28 11:06:04 +01:00
Robert Resch
28ec8272ee Remove deprecated camera constants (#131796) 2024-11-28 11:05:45 +01:00
Richard Kroegel
717f2ee206 Bump bimmer_connected to 0.17.0 (#131352) 2024-11-28 09:58:16 +01:00
epenet
5972da495a Bump samsungtvws to 2.7.1 (#131784) 2024-11-28 09:18:00 +01:00
Manu
2fcd9be3f2 Set parallel updates in IronOS integration (#131721) 2024-11-28 08:48:15 +01:00
David Knowles
a0ea9a1e83 Store Schlage runtime data in entry.runtime_data (#131731) 2024-11-28 08:29:29 +01:00
David Knowles
a831c37511 Enable strict typing for Schlage (#131734) 2024-11-28 08:29:15 +01:00
Jan Bouwhuis
d26c7a0536 Log warning if via_device reference not exists when creating or updating a device registry entry (#131746) 2024-11-28 08:27:24 +01:00
Manu
4257277086 Add units of measurement to Bring integration (#131763) 2024-11-28 08:13:15 +01:00
Manu
fe2bca51a4 Add translations for units of measurement to Habitica integration (#131761) 2024-11-28 08:12:52 +01:00
Manu
17236a5698 Remove unreachable code in Habitica (#131778) 2024-11-28 08:08:00 +01:00
Joost Lekkerkerker
39c2a529d1 Remove Spotify audio feature sensors (#131754) 2024-11-28 08:07:19 +01:00
TheJulianJES
0f5e0dd4bf Fix Home Connect microwave programs (#131782) 2024-11-28 08:06:31 +01:00
J. Nick Koston
eac6673c2b Bump orjson to 3.10.12 (#131752)
changelog: https://github.com/ijl/orjson/compare/3.10.11...3.10.12
2024-11-28 01:35:49 +01:00
Manu
bf4d6d2029 Fix rounding of attributes in Habitica integration (#131772) 2024-11-28 01:35:23 +01:00
puddly
f61a5b78cc Bump ZHA to 0.0.41 (#131776) 2024-11-28 01:34:57 +01:00
Marcel van der Veldt
cc9a97a5cf Bump music assistant client 1.0.8 (#131739) 2024-11-28 01:34:36 +01:00
J. Nick Koston
cf7acb5ae8 Bump aioesphomeapi to 27.0.3 (#131773) 2024-11-27 15:29:29 -08:00
J. Nick Koston
6edb2c0252 Bump uiprotect to 6.6.3 (#131764) 2024-11-27 15:55:51 -06:00
Josef Zweck
fb4d86196e Bump pylamarzocco to 1.2.12 (#131765) 2024-11-27 15:55:33 -06:00
Josef Zweck
44fc5c7871 Add missing data_description for lamarzocco OptionsFlow (#131708) 2024-11-27 22:37:15 +01:00
Allen Porter
c82e408138 Add a missing rainbird data description (#131740) 2024-11-27 22:36:17 +01:00
Marc Mueller
7110df04e6 Bump version to 2025.1.0dev0 (#131751) 2024-11-27 22:32:56 +01:00
J. Nick Koston
1635074aae Bump aiohttp to 3.11.8 (#131744) 2024-11-27 14:15:44 -06:00
Erik Montnemery
381d5453b1 Improve recorder history queries (#131702)
* Improve recorder history queries

* Remove some comments

* Update StatesManager._oldest_ts when adding pending state

* Update after review

* Improve tests

* Improve post-purge logic

* Avoid calling dt_util.utc_to_timestamp in new code

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2024-11-27 21:12:42 +01:00
epenet
e04b6f0cd8 Add quality scale hassfest check for config-entry-unload (#131720)
* Add dataclass to hassfest quality_scale

* Add basic check for config-entry-unloading

* Future-proof with a list of errors
2024-11-27 18:17:53 +01:00
Jan Bouwhuis
a6cb6fd239 Create MQTT device referenced by via device (#131588) 2024-11-27 18:12:46 +01:00
Paul Bottein
e8975cffe6 Update hash regex for frontend file in tests (#131742) 2024-11-27 18:07:26 +01:00
Paulus Schoutsen
ae34a6b375 Do not double expose scripts in LLM tools (#131726) 2024-11-27 18:04:08 +01:00
Raphael Hehl
1f1fdf80db Unifiprotect replace direct mocks with MockConfigEntry for test_async_ufp_instance_for_config_entry_ids (#131736)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-11-27 11:03:34 -06:00
Lutz
fda178da23 Add video event proxy endpoint for unifiprotect (#129980)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-11-27 11:03:21 -06:00
G Johansson
1450fe0880 Improve test quality in alarm_control_panel (#130541) 2024-11-27 17:49:02 +01:00
Abílio Costa
e4e9d76b45 Raise error if sensor has translated and hardcoded unit (#131657) 2024-11-27 17:45:53 +01:00
Marcel van der Veldt
3485ce9c71 Add actions to Music Assistant integration (#129515)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-11-27 17:43:48 +01:00
Michael Hansen
3eb483c1b0 Bump intents to 2024.11.27 (#131727) 2024-11-27 17:42:59 +01:00
J. Diego Rodríguez Royo
c2d6599736 Home connect program select entities (#126157)
* Home connect selector for programs

* Mark program switches as deprecated

* Simplified translation keys

* Improvements for program select entity

* Revert mark program switches as deprecated

* Return `None` if program is `None` or empty string

* Fix program format

* Use `is` instead of `==`

* Program selector entity selects program instead of start the selected program

* Fix typo

* Active and selected program

* Added ServiceValidationError

* Delete unnecessary `service` param at tests

* Use full program keys

* Fix again typos in programs states

* Use map for translations

* Add error handling for when the selected program is not registered on the program map

* Reverse map for programs and translation keys

* Remove stale string

* Log only once that the program is not part of the official Home Connect API specification

* pop programs

* Move `RE_CAMEL_CASE` to a better place

* Added warning if updated program is not valid

* Stale test function name

* Improve log about unknown program at update

* Add underscore before numbers in translation keys

* Added suggested changes

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

* Use target for adding an executor job

* Apply suggestions from code review

* Clean whitespace

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-11-27 16:34:41 +01:00
Bram Kragten
b2537a45e0 Update frontend to 20241127.0 (#131722) 2024-11-27 16:33:05 +01:00
epenet
e05401a922 Update snapshot to fix CI (#131725) 2024-11-27 16:28:36 +01:00
Raphael Hehl
f4b57617fb Unifiprotect fix missing domain check (#131724) 2024-11-27 16:23:59 +01:00
Manu
c21e221f65 Add data description to Iron OS integration (#131719) 2024-11-27 16:20:38 +01:00
epenet
d6f4a79b46 Remove workaround for flaky translation tests (#131628) 2024-11-27 08:37:36 -06:00
G Johansson
a7db35c76c Add horizontal swing support to ClimateEntity (#125578)
* Add horizontal swing support to ClimateEntity

* Fixes + tests

* Fixes
2024-11-27 15:06:46 +01:00
epenet
88feb8a7ad Fix ADS platform schema (#131701) 2024-11-27 14:47:17 +01:00
Abílio Costa
d8dd6a99b3 Use default translation on SensorEntity unit_of_measurement (#131633)
* Use translations on SensorEntity unit_of_measurement property

* Use default language for unit translation

* Update brother integration snapshot

* Update snapshots
2024-11-27 14:45:53 +01:00
epenet
137db5ac79 Bump samsungtvws to 2.7.0 (#131690) 2024-11-27 14:45:37 +01:00
Shay Levy
326f51a019 Bump aioshelly to 12.1.0 (#131714) 2024-11-27 15:20:47 +02:00
G Johansson
3464ffc53e Add open to Template lock (#129292)
* Add open to Template lock

* Update from review
2024-11-27 13:26:57 +01:00
Cyrill Raccaud
284fe17b1c Add time and offset config to Swiss public transport connections (#120357)
* add time and offset config for connections

* split the config flow

* fix arrival config

* add time_mode data description

* use delta as dict instead of string

* simplify the config_flow

* improve descriptions of config_flow

* improve config flow

* remove obsolete string

* switch priority of the config options

* improvements
2024-11-27 13:22:28 +01:00
Diogo Gomes
345c1fe0b2 Have Utility Meter monitor Timezone changes in configuration (#131112)
* listen to config changes for possible DST changes

* Add test

* check tz actually changed

* Update tests/components/utility_meter/test_sensor.py

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

* Update tests/components/utility_meter/test_sensor.py

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

* Clean up comment

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-11-27 12:12:45 +01:00
Franck Nijhof
56b4733e4a Clean up early assignment in script response (#131691) 2024-11-27 10:24:06 +01:00
Louis Christ
96eae1221c Fix bluesound_group attribute in bluesound integration (#130815)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-11-27 09:40:20 +01:00
Guido Schmitz
507bb4a685 Add data_description to devolo Home Network (#131511) 2024-11-27 09:26:19 +01:00
Petro31
33222436d2 Nested stop actions will now return response_variables (#126393)
fix-nested-stop-variable-response
2024-11-27 09:18:02 +01:00
G Johansson
1e05f98ddd Use report_usage for deprecation warning in alarm_control_panel (#130543)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-27 08:57:32 +01:00
epenet
2b939ce6ec Add translation checks for service exceptions (#131266)
* Add translation checks for service exceptions

* Adjust

* Remove invalid comment
2024-11-27 08:46:45 +01:00
Manu
00c4fa4146 Add missing section data_description to translation validator in hassfest (#131675)
Add missing data_description to translation validator in hassfest
2024-11-27 08:45:18 +01:00
TheJulianJES
605651f364 Bump ZHA to 0.0.40 (#131680) 2024-11-27 08:42:37 +01:00
Klaas Schoute
67ba44c3fa Use entity description class for Garages Amsterdam (#131672) 2024-11-27 08:42:19 +01:00
Bouwe Westerdijk
8bb0fab732 Bump plugwise to v1.6.0 and adapt (#131659) 2024-11-27 08:34:15 +01:00
dependabot[bot]
81d0bcde53 Bump docker/build-push-action from 6.9.0 to 6.10.0 (#131685)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-27 08:26:50 +01:00
J. Nick Koston
b8f81abbed Bump zeroconf to 0.136.2 (#131681) 2024-11-27 08:26:28 +01:00
Paulus Schoutsen
7e03100af2 Allow an LLM to see script response values (#131683) 2024-11-26 23:51:21 -06:00
Michael Hansen
46fe3dcbf1 Add wake word select for ESPHome Assist satellite (#131309)
* Add wake word select

* Fix linting

* Move to ESPHome

* Clean up and add more tests

* Update homeassistant/components/esphome/select.py

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-11-26 22:59:49 -05:00
Markus Jacobsen
a97eeaf189 Add Bang & Olufsen diagnostics (#131538)
* Add diagnostics

* Add tests for diagnostics

* Add media_player diagnostics

* Use media_player entity's state instead of registryentry

* Update tests

* Reorganize code
Remove context from media_player state

* Fix dict being read only
Simplify naming
Update test snapshot

* Update test snapshot
2024-11-26 20:56:36 -05:00
Jozef Kruszynski
40a4ff1c84 Adds media_browser functionality to the music assistant integration (#131577)
* Add test fixtures for all library loading

* Add media browser

* Add tests for media_browser
2024-11-26 20:52:08 -05:00
Markus Jacobsen
f04c50c59e Fix Bang & Olufsen WebSocket debug log and test (#131671)
* Fix test and debug message

* Reorder dict order
2024-11-26 20:48:46 -05:00
J. Nick Koston
dc62ef8bef Bump PySwitchbot to 0.54.0 (#131664) 2024-11-26 18:03:24 -06:00
Michael
70c8c57401 Dump ffmpeg stderr to ESPhome debug log (#130808)
* dump the stderr from ffmpeg to debug log

* add pid to indentify the ffmpeg process

* be more explosive :)

* move stderr task into _write_ffmpeg_data
2024-11-27 00:09:04 +01:00
J. Nick Koston
ce20670d84 Add a constraint for aiofiles to ensure it does not get downgraded (#131666) 2024-11-26 16:04:39 -06:00
Steven B.
4093a68cc0 Bump tplink python-kasa dependency to 0.8.0 (#131249) 2024-11-26 15:04:42 -06:00
prabhjotsbhatia-ca
1e6b96131a Bump androidtv to 0.0.75 (#131642) 2024-11-26 21:57:57 +01:00
Thomas55555
055c38a3c8 Don't enable number of collisions by default for Husqvarna Automower (#131665) 2024-11-26 21:38:46 +01:00
Duco Sebel
a0893bb9f7 Mark HomeWizard quality scale as platinum (#131663) 2024-11-26 21:33:45 +01:00
epenet
859daefeb8 Record current quality scale in renault (#131394) 2024-11-26 21:32:51 +01:00
dontinelli
06f9678414 Add quality scale for solarlog (#131440)
Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>
2024-11-26 21:24:57 +01:00
Josef Zweck
a7113cff68 Record current IQS state for acaia (#131086) 2024-11-26 21:14:52 +01:00
Thomas55555
7a107cac41 Add PARALLEL_UPDATES to Husqvarna Automower (#131662) 2024-11-26 21:09:45 +01:00
Steven B.
f3964596de tplink: forward compatible typing and test changes for kasa 0.8 (#131623) 2024-11-26 13:50:26 -06:00
Manu
2edcda47b0 Add diagnostics platform to Habitica (#131489) 2024-11-26 20:02:01 +01:00
Marco Aceti
6e8f3d9393 Add missing sensors to Tuya CO2 Detector (#131313) 2024-11-26 20:00:13 +01:00
Franck Nijhof
f095aea5c3 Record current IQS state for Stookwijzer (#131592)
* Record current IQS state for Stookwijzer

* Also mark test coverage

* Process review comment
2024-11-26 19:59:19 +01:00
blackovercoat
35f6ae0759 Add support for single phase power meter aqcz in Tuya (#126470) 2024-11-26 19:38:52 +01:00
Andrew Jackson
132a8cc31b Detect ingress host used when adding a Mealie integration (#130418)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-11-26 19:30:05 +01:00
Martin Hjelmare
ccbbcbb264 Make set value template number option required (#131625) 2024-11-26 19:27:59 +01:00
Jake Martin
dfa7ababfb Raise HomeAssistantError if update fails (#129727) 2024-11-26 19:27:17 +01:00
Alexandre CUER
f1655c5d1a Use SensorEntityDescription in emoncms (#130451) 2024-11-26 19:25:00 +01:00
Per Øyvind Øygard
7d5ba342c6 Add base entity class for Touchline zones (#131094)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-11-26 19:23:18 +01:00
Duco Sebel
a252faf9af Add reconfiguration flow in HomeWizard (#131535) 2024-11-26 19:20:50 +01:00
Norbert Rittel
7ba0f54412 Clarify 'item' and 'rename' descriptions of 'update_item' action (#131336) 2024-11-26 19:19:27 +01:00
Duco Sebel
a9cab28474 Add DHCP configuration update in HomeWizard (#131547) 2024-11-26 19:17:04 +01:00
Jan-Philipp Benecke
a5becfaff0 Add more supported lines to London Underground (#131650) 2024-11-26 19:03:50 +01:00
Jan-Philipp Benecke
e31d398811 Add binary sensor to SABnzbd (#131651) 2024-11-26 19:01:19 +01:00
Michael Hansen
192ffc09ee Add area slot to response for cancel all timers (#131638)
Add area slot to response
2024-11-26 10:58:39 -06:00
Alexey ALERT Rubashёff
15bf0c728c Sync overkiz Atlantic Water Heater datetime before switching the away mode on (#127408)
Set device datetime before turning on the away mode
2024-11-26 17:45:28 +01:00
Jan-Philipp Benecke
883c6121cf Prevent changing email address in inexogy reauth (#131632)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-26 17:17:10 +01:00
jesperraemaekers
bf9e7e4a0c Bump Weheat wh-python to 2024.11.26 (#131630) 2024-11-26 17:00:51 +01:00
Jan-Philipp Benecke
1a71fbe427 Add intent to cancel all timers (#130873)
* Add intent to cancel all timers

* Add intent to llm test
2024-11-26 09:59:41 -06:00
starkillerOG
a2ebfe6e83 Add Reolink binning mode select entity (#131570) 2024-11-26 16:19:41 +01:00
Steven B.
0e88e22fd2 Bump ring_doorbell to 0.9.13 (#131627) 2024-11-26 16:14:39 +01:00
Simon Lamon
ee74a35417 Support time entities in time conditions (#124575)
Co-authored-by: Mark Bergsma <mark@wikked.net>
2024-11-26 15:37:31 +01:00
Steven B.
147679f803 Add live view camera entity to ring integration (#127579) 2024-11-26 15:20:25 +01:00
Jan-Philipp Benecke
9510ef56f9 Add configuration url to SABnzbd device info (#131617) 2024-11-26 08:39:21 -05:00
dotvav
1fc3194613 Add diagnostics to Palazzetti (#131608) 2024-11-26 14:07:37 +01:00
Duco Sebel
1ddc8a35c2 Add test to validate HomeWizard updates discovery info (#131540) 2024-11-26 13:14:59 +01:00
Klaas Schoute
f5d323679f Fix bug on creating entities with unknown state - Garages Amsterdam (#131619) 2024-11-26 13:07:32 +01:00
Michael
b0b72326d8 Add Update syrupy snapshots VScode task (#131536)
* add Update syrupy snapshots task

* don't use xdist
2024-11-26 13:02:17 +01:00
Lenn
41c7cc6e81 Bump motionblindsble to 0.1.3 (#131613) 2024-11-26 12:54:50 +01:00
Franck Nijhof
551d778a31 Merge branch 'master' into dev 2024-11-26 12:48:56 +01:00
Jan-Philipp Benecke
3af751c129 Fix SABnzbd number icon (#131615) 2024-11-26 12:40:02 +01:00
Klaas Schoute
9a999e8742 Use ConfigEntry runtime_data in Garages Amsterdam (#131611) 2024-11-26 12:30:50 +01:00
Franck Nijhof
0644d782cd 2024.11.3 (#131248) 2024-11-22 11:55:45 +01:00
Franck Nijhof
4ef50ffd88 Bump version to 2024.11.3 2024-11-22 11:05:59 +01:00
starkillerOG
bfcd4194f3 Bump reolink_aio to 0.11.2 (#131237) 2024-11-22 11:05:37 +01:00
rappenze
2f05240e4c Fix fibaro cover state is not always correct (#131206) 2024-11-22 11:05:34 +01:00
starkillerOG
44ad8081a3 Reolink log fast poll errors once (#131203) 2024-11-22 11:05:30 +01:00
Jesse Hills
780eaa8379 Fix typo in ESPHome repair text (#131200) 2024-11-22 11:05:26 +01:00
Norbert Rittel
75dcdfb087 Fix cast translation string (#131156) 2024-11-22 11:05:23 +01:00
Norbert Rittel
c88ff2ca44 Fix typo in name of "Alarm arm home instant" action (#131151) 2024-11-22 11:05:19 +01:00
Norbert Rittel
402c668f05 Replace "service" with "action" in zha:reconfigure_device (#131111)
Replace "service" with "action" in one description

As services are now actions in HA this needs to be fixed.
2024-11-22 11:05:14 +01:00
Álvaro Fernández Rojas
93b4570c04 Update aioairzone to v0.9.7 (#131033) 2024-11-22 10:57:08 +01:00
Álvaro Fernández Rojas
50a610914b Bump aioairzone to 0.9.6 (#130559)
* Update aioairzone to v0.9.6

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

* Remove _async_migrator_mac_empty and improve tests

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

* Remove WebServer empty mac fixes as requested by @epenet

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

---------

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-11-22 10:57:04 +01:00
G Johansson
8db18181d0 Bump holidays to 0.61 (#130984) 2024-11-22 10:52:43 +01:00
G Johansson
335124acc6 Add missing catholic category in workday (#130983) 2024-11-22 10:52:40 +01:00
Norbert Rittel
24ccb9b894 Add more UI user-friendly description to six Supervisor actions (#130971) 2024-11-22 10:52:36 +01:00
Jan-Philipp Benecke
a75ce850b8 Strip whitespaces from host in ping config flow (#130970) 2024-11-22 10:52:33 +01:00
Renat Sibgatulin
4753510ace Bump aioairq to 0.4.3 (#130963) 2024-11-22 10:52:30 +01:00
ElmaxSrl
fc607ea7e5 Update elmax_api to v0.0.6.1 (#130917)
Co-authored-by: Alberto Geniola <albertogeniola@gmail.com>
2024-11-22 10:52:27 +01:00
Sergio Conde Gómez
477141c22a Unscape HTML Entities from RSS feeds (#130915)
* Unscape HTML Entities from RSS feeds

* Improve tests
2024-11-22 10:52:23 +01:00
Charles Yuan
aaa36adbcc Fixed Small Inaccuracy in Description String for myUplink (#130900) 2024-11-22 10:52:20 +01:00
J. Nick Koston
9447180c04 Bump bluetooth-adapters to 0.20.2 (#130877) 2024-11-22 10:52:17 +01:00
epenet
6853234f9d Pass config_entry explicitly in rachio (#130865) 2024-11-22 10:52:14 +01:00
G Johansson
6944ba0333 Use default device sensors also for AirQ devices in Sensibo (#130841) 2024-11-22 10:52:10 +01:00
starkillerOG
04bc041174 Reolink fix dev/entity id migration (#130836) 2024-11-22 10:52:07 +01:00
Glenn Waters
a024acf096 UPB integration: Change unique ID from int to string. (#130832) 2024-11-22 10:52:04 +01:00
hahn-th
5b1aca53ac Bump homematicip to 1.1.3 (#130824) 2024-11-22 10:52:00 +01:00
Michael
a588ced2e3 Fix unexpected stop of media playback via ffmpeg proxy for ESPhome devices (#130788)
disable writing progress stats to stderr in ffmpeg command
2024-11-22 10:51:57 +01:00
Franck Nijhof
876112ff54 Update twentemilieu to 2.1.0 (#130752) 2024-11-22 10:51:54 +01:00
Jan Bouwhuis
a48f88033d Fix file uploads in MQTT config flow not processed in executor (#130746)
Process file uploads in MQTT config flow in executor
2024-11-22 10:51:49 +01:00
Patrick
5deba1766e Fix and bump apsystems-ez1 to 2.4.0 (#130740) 2024-11-22 10:51:45 +01:00
Davin Kevin
4863243f5a Prevent endless loop in recorder when using a filter and there are no more states to purge (#126149)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-11-22 10:51:35 +01:00
Franck Nijhof
847afabed1 2024.11.2 (#130713) 2024-11-15 20:16:10 +01:00
Franck Nijhof
ac270e19be Bump version to 2024.11.2 2024-11-15 19:35:42 +01:00
Matt Zimmerman
ca40b96a89 Bump python-smarttub to 0.0.38 (#130679) 2024-11-15 19:35:14 +01:00
epenet
045e285bfe Fix missing translations in onewire (#130673) 2024-11-15 19:35:11 +01:00
epenet
8d6f2e78f5 Fix missing translations in generic (#130672) 2024-11-15 19:35:07 +01:00
epenet
9e4d26137e Fix missing translations in madvr (#130656) 2024-11-15 19:35:04 +01:00
epenet
f74bfdc974 Fix missing translations in toon (#130655) 2024-11-15 19:35:00 +01:00
epenet
1cabcdf257 Fix missing translations in tradfri (#130654)
* Fix missing translations in tradfri

* Simplify
2024-11-15 19:34:57 +01:00
epenet
c6931d656e Fix missing translations in utility_meter (#130652) 2024-11-15 19:34:54 +01:00
epenet
942830505a Fix missing translations in vilfo (#130650) 2024-11-15 19:34:51 +01:00
Jan-Philipp Benecke
880f28e28a Remove dumping config entry to log in setup of roborock (#130648) 2024-11-15 19:34:48 +01:00
Johan Nenzén
f406ffa75a Bump pyplaato to 0.0.19 (#130641)
Bumps version of pyplaato to 0.0.19
2024-11-15 19:34:44 +01:00
epenet
0d695c843f Add missing translation string to philips_js (#130637) 2024-11-15 19:34:41 +01:00
epenet
5f09eb97e1 Add missing translation string to lg_netcast (#130635) 2024-11-15 19:34:38 +01:00
epenet
6d561ca373 Add missing translation string to hvv_departures (#130634) 2024-11-15 19:34:34 +01:00
Alistair Galbraith
663ebe199d Fix scene loading issue (#130627) 2024-11-15 19:34:31 +01:00
Keilin Bickar
8b9c4db2b3 Bump sense-energy to 0.13.4 (#130625) 2024-11-15 19:34:27 +01:00
epenet
e478b9b599 Add missing translation string to smarty (#130624) 2024-11-15 19:34:23 +01:00
Robert Resch
5acdf58976 Fix hassfest by adding go2rtc reqs (#130602) 2024-11-15 19:33:09 +01:00
starkillerOG
6d861e7f47 Bump reolink-aio to 0.11.1 (#130600) 2024-11-15 19:32:30 +01:00
Johan Nenzén
281a8eda31 Fixes webhook schema for different temp and volume units (#130578) 2024-11-15 19:32:26 +01:00
Simone Chemelli
1bc005d0d4 Update uptime deviation for Vodafone Station (#130571)
Update sensor.py
2024-11-15 19:32:23 +01:00
puddly
95d60987ab Bump ZHA dependencies (#130563) 2024-11-15 19:32:19 +01:00
J. Nick Koston
53e38454b2 Fix non-thread-safe operation in powerview number (#130557) 2024-11-15 19:32:16 +01:00
Brig Lamoreaux
876b86cd3d fix translation in srp_energy (#130540) 2024-11-15 19:32:13 +01:00
Robert Resch
cb104935ea Add go2rtc recommended version (#130508) 2024-11-15 19:32:10 +01:00
Joost Lekkerkerker
4c24e26926 Bump aiowithings to 3.1.3 (#130504) 2024-11-15 19:32:06 +01:00
Robert Resch
4b13d8bc47 Bump go2rtc-client to 0.1.1 (#130498) 2024-11-15 19:30:50 +01:00
Tony
433e3718f8 Bump aioruckus to 0.42 (#130487) 2024-11-15 19:28:38 +01:00
Sheldon Ip
1e3c2c0631 Fix translations in subaru (#130486) 2024-11-15 19:28:34 +01:00
starkillerOG
3a2f996c13 Bump reolink_aio to 0.11.0 (#130481) 2024-11-15 19:28:30 +01:00
G Johansson
e4cb3c67d9 Fix legacy _attr_state handling in AlarmControlPanel (#130479) 2024-11-15 19:28:27 +01:00
puddly
8a22433168 Ensure ZHA setup works with container installs (#130470) 2024-11-15 19:28:23 +01:00
Joost Lekkerkerker
0976476d16 Bump aiowithings to 3.1.2 (#130469) 2024-11-15 19:28:19 +01:00
Kelvin Dekker
28f46a0f88 Fix typo in file strings (#130465) 2024-11-15 19:28:16 +01:00
G Johansson
8b173656e7 Fix translation in statistics (#130455)
* Fix translation in statistics

* Update homeassistant/components/statistics/strings.json
2024-11-15 19:28:12 +01:00
Joost Lekkerkerker
08f6f2759b Add title to water heater component (#130446) 2024-11-15 19:28:09 +01:00
Steven B.
f4798d27c7 Do not trigger events for updated ring events (#130430) 2024-11-15 19:28:05 +01:00
Steven B.
103a84b4bd Bump ring-doorbell to 0.9.12 (#130419) 2024-11-15 19:28:01 +01:00
Steven B.
4d3502e061 Bump ring library ring-doorbell to 0.9.9 (#129966) 2024-11-15 19:26:59 +01:00
J. Nick Koston
79329e16cf Fix missing title placeholders in powerwall reauth (#130389) 2024-11-15 19:24:37 +01:00
Daniel Hjelseth Høyer
929164251a Bump Tibber 0.30.8 (#130388) 2024-11-15 19:24:34 +01:00
Joost Lekkerkerker
300724443a Bump spotifyaio to 0.8.8 (#130372) 2024-11-15 19:24:30 +01:00
Robert Resch
70ef3a355c Go2rtc bump and set ffmpeg logs to debug (#130371) 2024-11-15 19:24:26 +01:00
Jan Bouwhuis
83162c1461 Fix typo in go2rtc (#130165)
Fix typo in original
2024-11-15 19:24:20 +01:00
Jan Bouwhuis
a12c76dbdd Use f-strings in go2rtc code and test and do not use abbreviation (#130158) 2024-11-15 19:22:09 +01:00
Noah Husby
9292b6da3d Disable brightness from devices with no display in Cambridge Audio (#130369) 2024-11-15 19:03:04 +01:00
Simon Lamon
8d05183de2 Add Spotify and Tidal to playingmode mapping (#130351) 2024-11-15 19:03:01 +01:00
Simon Lamon
a86ff41bbc Add seek support to LinkPlay (#130349) 2024-11-15 19:02:58 +01:00
Simon Lamon
ce92f3de44 Bump python-linkplay to 0.0.20 (#130348) 2024-11-15 19:02:54 +01:00
LG-ThinQ-Integration
465d8b2ee2 Fix fan's warning TURN_ON, TURN_OFF (#130327)
Co-authored-by: yunseon.park <yunseon.park@lge.com>
2024-11-15 19:02:51 +01:00
G Johansson
218eedfd93 Fix Homekit error handling alarm state unknown or unavailable (#130311) 2024-11-15 19:02:47 +01:00
Simone Chemelli
afec354b84 Avoid Shelly data update during shutdown (#130301) 2024-11-15 19:02:44 +01:00
Allen Porter
282f92e5f3 Ignore WebRTC candidates for nest cameras (#130294) 2024-11-15 19:02:41 +01:00
David Knowles
f6cd74e2d7 Make Hydrawise poll non-critical data less frequently (#130289) 2024-11-15 19:02:37 +01:00
Åke Strandberg
f821ddeab8 Add more f-series models to myuplink (#130283) 2024-11-15 19:02:34 +01:00
Allen Porter
d408b7ac62 Improve nest camera stream expiration to be defensive against errors (#130265) 2024-11-15 19:02:31 +01:00
Michael
83baa1a788 Fix translation key for done response in conversation (#130247) 2024-11-15 19:02:27 +01:00
Max Shcherbina
07a8cf14cd Update generic thermostat strings for clarity and accuracy (#130243) 2024-11-15 19:02:24 +01:00
Olivier Corradi
9f447af468 Rename "CO2 Signal" display name to Electricity Maps for consistency (#130242)
* Update strings.json for Electricity Maps

* Update strings.json

* Update config_flow.py

* Update test_config_flow.py

* Fix test
2024-11-15 19:02:20 +01:00
Allen Porter
c399d8f571 Bump google-nest-sdm to 6.1.5 (#130229) 2024-11-15 19:02:17 +01:00
jjlawren
4ea9574229 Bump SoCo to 0.30.6 (#130223) 2024-11-15 19:02:14 +01:00
Daniel Hjelseth Høyer
592b8ed0a0 Bump pyTibber (#130216) 2024-11-15 19:02:10 +01:00
Simone Chemelli
6b91c0810a Fix uptime sensor for Vodafone Station (#130215) 2024-11-15 19:02:06 +01:00
Max Shcherbina
9579e4a9c1 Fix wording in Google Calendar create_event strings for consistency (#130183) 2024-11-15 19:00:06 +01:00
IceBotYT
7f4f90f06d Bump nice-go to 0.3.10 (#130173)
Bump Nice G.O. to 0.3.10
2024-11-15 19:00:02 +01:00
Sheldon Ip
701a901fe4 Fix translations in ollama (#130164) 2024-11-15 18:59:59 +01:00
Simon Lamon
f914642e31 No longer thrown an error when device is offline in linkplay (#130161) 2024-11-15 18:59:55 +01:00
Simon Lamon
32dc9fc238 Allow dynamic max preset in linkplay play preset (#130160) 2024-11-15 18:59:52 +01:00
Simon Lamon
b27e0f9fe7 Bump python-linkplay to v0.0.18 (#130159) 2024-11-15 18:59:47 +01:00
Thomas55555
f040060b3c Fix RecursionError in Husqvarna Automower coordinator (#123085)
* reach maximum recursion depth exceeded in tests

* second background task

* Update homeassistant/components/husqvarna_automower/coordinator.py

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

* Update homeassistant/components/husqvarna_automower/coordinator.py

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

* test

* modify test

* tests

* use correct exception

* reset mock

* use recursion_limit

* remove unneeded ticks

* test TimeoutException

* set lower recursionlimit

* remove not that important comment and move the other

* test that we connect and listen successfully

* Simulate hass shutting down

* skip testing against the recursion limit

* Update homeassistant/components/husqvarna_automower/coordinator.py

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

* mock

* Remove comment

* Revert "mock"

This reverts commit e8ddaea3d7.

* Move patch to decorator

* Make execution of patched methods predictable

* Parametrize test, make mocked start_listening block

* Apply suggestions from code review

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Erik <erik@montnemery.com>
2024-11-15 18:47:59 +01:00
J. Nick Koston
cc45793896 Bump aiohttp to 3.10.11 (#130483)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-15 09:42:20 +01:00
Franck Nijhof
ab0556227c 2024.11.1 (#130156) 2024-11-08 19:42:10 +01:00
Franck Nijhof
c16fb9c93d Bump version to 2024.11.1 2024-11-08 18:58:21 +01:00
Jan Bouwhuis
da8fc7a2fc Refrase imap fetch service description string (#130152) 2024-11-08 18:58:07 +01:00
Allen Porter
864b4d86f2 Fix bugs in nest stream expiration handling (#130150) 2024-11-08 18:58:04 +01:00
Louis Christ
1bb0ced7c0 Fix volume_up not working in some cases in bluesound integration (#130146) 2024-11-08 18:58:00 +01:00
Martin Hjelmare
2fe4fc908b Bump ha-ffmpeg to 3.2.2 (#130142) 2024-11-08 18:57:25 +01:00
Joost Lekkerkerker
aa2c3b046f Bump spotifyaio to 0.8.7 (#130140) 2024-11-08 18:56:15 +01:00
Robert Resch
22822cb8aa Add go2rtc workaround for HA managed one until upstream fixes it (#130139) 2024-11-08 18:56:12 +01:00
Shai Ungar
b71383c997 Fix issue when timestamp is None (#130133)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-08 18:56:09 +01:00
Bram Kragten
b0b163df48 Update frontend to 20241106.2 (#130128) 2024-11-08 18:56:06 +01:00
Luke Lashley
35539dbf60 Bump python-roborock to 2.7.2 (#130100) 2024-11-08 18:56:02 +01:00
Bram Kragten
09d03e8edf Update frontend to 20241106.1 (#130086) 2024-11-08 18:55:59 +01:00
Kelvin Dekker
46e37f3bdd Fix typo in insteon strings (#130085) 2024-11-08 18:55:55 +01:00
Klaas Schoute
0206c149cf Force int value on port in P1Monitor (#130084) 2024-11-08 18:55:52 +01:00
Josef Zweck
29620ef977 Add missing string to tedee plus test (#130081) 2024-11-08 18:55:49 +01:00
Erik Montnemery
9012b113ad Don't create repairs asking user to remove duplicate flipr config entries (#130058)
* Don't create repairs asking user to remove duplicate flipr config entries

* Improve comments
2024-11-08 18:55:46 +01:00
Allen Porter
5f5f6cc3d5 Fix KeyError in nest integration when the old key format does not exist (#130057)
* Fix bug in nest setup when the old key format does not exist

* Further simplify the entry.data check

* Update homeassistant/components/nest/api.py

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

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-11-08 18:55:42 +01:00
Erik Montnemery
7ff501f3ec Don't create repairs asking user to remove duplicate ignored config entries (#130056) 2024-11-08 18:55:39 +01:00
sean t
b0f110b9ab Bump agent-py to 0.0.24 (#130018)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-08 18:55:36 +01:00
epenet
2692bc23a5 Add missing placeholder description to twitch (#130013) 2024-11-08 18:55:33 +01:00
Allen Porter
1beac5f0f8 Bump google-nest-sdm to 6.1.4 (#130005)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-08 18:55:29 +01:00
Keilin Bickar
ec7ba1b7fd Update sense energy library to 0.13.3 (#129998) 2024-11-08 18:55:25 +01:00
Brett Adams
5bd1b0dd9c Fix Trunks in Teslemetry and Tesla Fleet (#129986) 2024-11-08 18:55:21 +01:00
Michael Hansen
a2ad4c9cfd Bump intents to 2024.11.6 (#129982) 2024-11-08 18:52:43 +01:00
433 changed files with 12657 additions and 5743 deletions

View File

@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -522,7 +522,7 @@ jobs:
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.12"
HA_SHORT_VERSION: "2025.1"
DEFAULT_PYTHON: "3.12"
ALL_PYTHON_VERSIONS: "['3.12', '3.13']"
# 10.3 is the oldest supported version
@@ -819,6 +819,12 @@ jobs:
needs:
- info
- base
- gen-requirements-all
- hassfest
- lint-other
- lint-ruff
- lint-ruff-format
- mypy
name: Split tests for full run
steps:
- name: Install additional OS dependencies

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
rev: v0.8.1
hooks:
- id: ruff
args:

View File

@@ -41,6 +41,7 @@ homeassistant.util.unit_system
# --- Add components below this line ---
homeassistant.components
homeassistant.components.abode.*
homeassistant.components.acaia.*
homeassistant.components.accuweather.*
homeassistant.components.acer_projector.*
homeassistant.components.acmeda.*
@@ -405,6 +406,7 @@ homeassistant.components.ruuvitag_ble.*
homeassistant.components.samsungtv.*
homeassistant.components.scene.*
homeassistant.components.schedule.*
homeassistant.components.schlage.*
homeassistant.components.scrape.*
homeassistant.components.script.*
homeassistant.components.search.*

16
.vscode/tasks.json vendored
View File

@@ -87,6 +87,22 @@
},
"problemMatcher": []
},
{
"label": "Update syrupy snapshots",
"detail": "Update syrupy snapshots for a given integration.",
"type": "shell",
"command": "python3 -m pytest ./tests/components/${input:integrationName} --snapshot-update",
"dependsOn": ["Compile English translations"],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Generate Requirements",
"type": "shell",

View File

@@ -25,5 +25,5 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["aioacaia"],
"requirements": ["aioacaia==0.1.9"]
"requirements": ["aioacaia==0.1.10"]
}

View File

@@ -0,0 +1,106 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
No custom actions are defined.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
No custom actions are defined.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: todo
entity-event-setup:
status: exempt
comment: |
No explicit event subscriptions.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup:
status: exempt
comment: |
Device is expected to be offline most of the time, but needs to connect quickly once available.
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
No custom actions are defined.
config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable:
status: done
comment: |
Handled by coordinator.
parallel-updates: done
reauthentication-flow:
status: exempt
comment: |
No authentication required.
test-coverage: done
# Gold
devices: done
diagnostics: done
discovery-update-info:
status: exempt
comment: |
No IP discovery.
discovery:
status: done
comment: |
Bluetooth discovery.
docs-data-update: done
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices:
status: exempt
comment: |
Device type integration.
entity-category: done
entity-device-class: done
entity-disabled-by-default:
status: exempt
comment: |
No noisy/non-essential entities.
entity-translations: done
exception-translations:
status: exempt
comment: |
No custom exceptions.
icon-translations: done
reconfiguration-flow:
status: exempt
comment: |
Only parameter that could be changed (MAC = unique_id) would force a new config entry.
repair-issues:
status: exempt
comment: |
No repairs/issues.
stale-devices:
status: exempt
comment: |
Device type integration.
# Platinum
async-dependency: done
inject-websession:
status: exempt
comment: |
Bluetooth connection.
strict-typing: done

View File

@@ -37,7 +37,7 @@ STATE_KEY_POSITION = "position"
PLATFORM_SCHEMA = COVER_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_ADS_VAR): cv.string,
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_ADS_VAR_POSITION): cv.string,
vol.Optional(CONF_ADS_VAR_SET_POS): cv.string,
vol.Optional(CONF_ADS_VAR_CLOSE): cv.string,

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio
from datetime import timedelta
from functools import partial
import logging
from typing import TYPE_CHECKING, Any, Final, final
@@ -27,26 +26,14 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import make_entity_service_schema
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.frame import ReportBehavior, report_usage
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .const import ( # noqa: F401
_DEPRECATED_FORMAT_NUMBER,
_DEPRECATED_FORMAT_TEXT,
_DEPRECATED_SUPPORT_ALARM_ARM_AWAY,
_DEPRECATED_SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
_DEPRECATED_SUPPORT_ALARM_ARM_HOME,
_DEPRECATED_SUPPORT_ALARM_ARM_NIGHT,
_DEPRECATED_SUPPORT_ALARM_ARM_VACATION,
_DEPRECATED_SUPPORT_ALARM_TRIGGER,
from .const import (
ATTR_CHANGED_BY,
ATTR_CODE_ARM_REQUIRED,
DOMAIN,
@@ -163,7 +150,6 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
_alarm_control_panel_option_default_code: str | None = None
__alarm_legacy_state: bool = False
__alarm_legacy_state_reported: bool = False
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Post initialisation processing."""
@@ -180,9 +166,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
unless already reported.
"""
if name == "_attr_state":
if self.__alarm_legacy_state_reported is not True:
self._report_deprecated_alarm_state_handling()
self.__alarm_legacy_state_reported = True
self._report_deprecated_alarm_state_handling()
return super().__setattr__(name, value)
@callback
@@ -194,7 +178,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
) -> None:
"""Start adding an entity to a platform."""
super().add_to_platform_start(hass, platform, parallel_updates)
if self.__alarm_legacy_state and not self.__alarm_legacy_state_reported:
if self.__alarm_legacy_state:
self._report_deprecated_alarm_state_handling()
@callback
@@ -203,19 +187,16 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
Integrations should implement alarm_state instead of using state directly.
"""
self.__alarm_legacy_state_reported = True
if "custom_components" in type(self).__module__:
# Do not report on core integrations as they have been fixed.
report_issue = "report it to the custom integration author."
_LOGGER.warning(
"Entity %s (%s) is setting state directly"
" which will stop working in HA Core 2025.11."
" Entities should implement the 'alarm_state' property and"
" return its state using the AlarmControlPanelState enum, please %s",
self.entity_id,
type(self),
report_issue,
)
report_usage(
"is setting state directly."
f" Entity {self.entity_id} ({type(self)}) should implement the 'alarm_state'"
" property and return its state using the AlarmControlPanelState enum",
core_integration_behavior=ReportBehavior.ERROR,
custom_integration_behavior=ReportBehavior.LOG,
breaks_in_ha_version="2025.11",
integration_domain=self.platform.platform_name if self.platform else None,
exclude_integrations={DOMAIN},
)
@final
@property
@@ -417,13 +398,3 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
self._alarm_control_panel_option_default_code = default_code
return
self._alarm_control_panel_option_default_code = None
# As we import constants of the const module here, we need to add the following
# functions to check for deprecated constants again
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -1,16 +1,8 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from functools import partial
from typing import Final
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
DOMAIN: Final = "alarm_control_panel"
ATTR_CHANGED_BY: Final = "changed_by"
@@ -39,12 +31,6 @@ class CodeFormat(StrEnum):
NUMBER = "number"
# These constants are deprecated as of Home Assistant 2022.5, can be removed in 2025.1
# Please use the CodeFormat enum instead.
_DEPRECATED_FORMAT_TEXT: Final = DeprecatedConstantEnum(CodeFormat.TEXT, "2025.1")
_DEPRECATED_FORMAT_NUMBER: Final = DeprecatedConstantEnum(CodeFormat.NUMBER, "2025.1")
class AlarmControlPanelEntityFeature(IntFlag):
"""Supported features of the alarm control panel entity."""
@@ -56,27 +42,6 @@ class AlarmControlPanelEntityFeature(IntFlag):
ARM_VACATION = 32
# These constants are deprecated as of Home Assistant 2022.5
# Please use the AlarmControlPanelEntityFeature enum instead.
_DEPRECATED_SUPPORT_ALARM_ARM_HOME: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_HOME, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_AWAY: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_AWAY, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_NIGHT: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_NIGHT, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_TRIGGER: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.TRIGGER, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_CUSTOM_BYPASS: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_VACATION: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_VACATION, "2025.1"
)
CONDITION_TRIGGERED: Final = "is_triggered"
CONDITION_DISARMED: Final = "is_disarmed"
CONDITION_ARMED_HOME: Final = "is_armed_home"
@@ -84,10 +49,3 @@ CONDITION_ARMED_AWAY: Final = "is_armed_away"
CONDITION_ARMED_NIGHT: Final = "is_armed_night"
CONDITION_ARMED_VACATION: Final = "is_armed_vacation"
CONDITION_ARMED_CUSTOM_BYPASS: Final = "is_armed_custom_bypass"
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -9,7 +9,7 @@
"loggers": ["adb_shell", "androidtv", "pure_python_adb"],
"requirements": [
"adb-shell[async]==0.4.4",
"androidtv[async]==0.0.73",
"androidtv[async]==0.0.75",
"pure-python-adb[async]==0.3.0.dev0"
]
}

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"iot_class": "local_push",
"loggers": ["pyatv", "srptools"],
"requirements": ["pyatv==0.15.1"],
"requirements": ["pyatv==0.16.0"],
"zeroconf": [
"_mediaremotetv._tcp.local.",
"_companion-link._tcp.local.",

View File

@@ -1040,7 +1040,7 @@ class PipelineRun:
:= await conversation.async_handle_sentence_triggers(
self.hass, user_input
)
):
) is not None:
# Sentence trigger matched
trigger_response = intent.IntentResponse(
self.pipeline.conversation_language

View File

@@ -6,7 +6,6 @@ from abc import ABC, abstractmethod
import asyncio
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from functools import partial
import logging
from typing import Any, Protocol, cast
@@ -51,12 +50,6 @@ from homeassistant.core import (
from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError
from homeassistant.helpers import condition
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstant,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.issue_registry import (
@@ -86,12 +79,7 @@ from homeassistant.helpers.trace import (
trace_get,
trace_path,
)
from homeassistant.helpers.trigger import (
TriggerActionType,
TriggerData,
TriggerInfo,
async_initialize_triggers,
)
from homeassistant.helpers.trigger import async_initialize_triggers
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
from homeassistant.util.dt import parse_datetime
@@ -137,20 +125,6 @@ class IfAction(Protocol):
"""AND all conditions."""
# AutomationActionType, AutomationTriggerData,
# and AutomationTriggerInfo are deprecated as of 2022.9.
# Can be removed in 2025.1
_DEPRECATED_AutomationActionType = DeprecatedConstant(
TriggerActionType, "TriggerActionType", "2025.1"
)
_DEPRECATED_AutomationTriggerData = DeprecatedConstant(
TriggerData, "TriggerData", "2025.1"
)
_DEPRECATED_AutomationTriggerInfo = DeprecatedConstant(
TriggerInfo, "TriggerInfo", "2025.1"
)
@bind_hass
def is_on(hass: HomeAssistant, entity_id: str) -> bool:
"""Return true if specified automation entity_id is on.
@@ -477,6 +451,7 @@ class UnavailableAutomationEntity(BaseAutomationEntity):
)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
await super().async_will_remove_from_hass()
async_delete_issue(
self.hass, DOMAIN, f"{self.entity_id}_validation_{self._validation_status}"
@@ -1219,11 +1194,3 @@ def websocket_config(
"config": automation.raw_config,
},
)
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -0,0 +1,40 @@
"""Support for Bang & Olufsen diagnostics."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
from homeassistant.core import HomeAssistant
import homeassistant.helpers.entity_registry as er
from . import BangOlufsenConfigEntry
from .const import DOMAIN
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: BangOlufsenConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
data: dict = {
"config_entry": config_entry.as_dict(),
"websocket_connected": config_entry.runtime_data.client.websocket_connected,
}
if TYPE_CHECKING:
assert config_entry.unique_id
# Add media_player entity's state
entity_registry = er.async_get(hass)
if entity_id := entity_registry.async_get_entity_id(
MEDIA_PLAYER_DOMAIN, DOMAIN, config_entry.unique_id
):
if state := hass.states.get(entity_id):
state_dict = dict(state.as_dict())
# Remove context as it is not relevant
state_dict.pop("context")
data["media_player"] = state_dict
return data

View File

@@ -204,13 +204,11 @@ class BangOlufsenWebsocket(BangOlufsenBase):
def on_all_notifications_raw(self, notification: BaseWebSocketResponse) -> None:
"""Receive all notifications."""
debug_notification = {
"device_id": self._device.id,
"serial_number": int(self._unique_id),
**notification,
}
_LOGGER.debug("%s", notification)
self.hass.bus.async_fire(
BANG_OLUFSEN_WEBSOCKET_EVENT,
{
"device_id": self._device.id,
"serial_number": int(self._unique_id),
**notification,
},
)
_LOGGER.debug("%s", debug_notification)
self.hass.bus.async_fire(BANG_OLUFSEN_WEBSOCKET_EVENT, debug_notification)

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from datetime import timedelta
from enum import StrEnum
from functools import partial
import logging
from typing import Literal, final
@@ -16,12 +15,6 @@ from homeassistant.const import STATE_OFF, STATE_ON, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -126,94 +119,7 @@ class BinarySensorDeviceClass(StrEnum):
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(BinarySensorDeviceClass))
# DEVICE_CLASS* below are deprecated as of 2021.12
# use the BinarySensorDeviceClass enum instead.
DEVICE_CLASSES = [cls.value for cls in BinarySensorDeviceClass]
_DEPRECATED_DEVICE_CLASS_BATTERY = DeprecatedConstantEnum(
BinarySensorDeviceClass.BATTERY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_BATTERY_CHARGING = DeprecatedConstantEnum(
BinarySensorDeviceClass.BATTERY_CHARGING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_CO = DeprecatedConstantEnum(
BinarySensorDeviceClass.CO, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_COLD = DeprecatedConstantEnum(
BinarySensorDeviceClass.COLD, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_CONNECTIVITY = DeprecatedConstantEnum(
BinarySensorDeviceClass.CONNECTIVITY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DOOR = DeprecatedConstantEnum(
BinarySensorDeviceClass.DOOR, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_GARAGE_DOOR = DeprecatedConstantEnum(
BinarySensorDeviceClass.GARAGE_DOOR, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_GAS = DeprecatedConstantEnum(
BinarySensorDeviceClass.GAS, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_HEAT = DeprecatedConstantEnum(
BinarySensorDeviceClass.HEAT, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_LIGHT = DeprecatedConstantEnum(
BinarySensorDeviceClass.LIGHT, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_LOCK = DeprecatedConstantEnum(
BinarySensorDeviceClass.LOCK, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_MOISTURE = DeprecatedConstantEnum(
BinarySensorDeviceClass.MOISTURE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_MOTION = DeprecatedConstantEnum(
BinarySensorDeviceClass.MOTION, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_MOVING = DeprecatedConstantEnum(
BinarySensorDeviceClass.MOVING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_OCCUPANCY = DeprecatedConstantEnum(
BinarySensorDeviceClass.OCCUPANCY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_OPENING = DeprecatedConstantEnum(
BinarySensorDeviceClass.OPENING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_PLUG = DeprecatedConstantEnum(
BinarySensorDeviceClass.PLUG, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_POWER = DeprecatedConstantEnum(
BinarySensorDeviceClass.POWER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_PRESENCE = DeprecatedConstantEnum(
BinarySensorDeviceClass.PRESENCE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_PROBLEM = DeprecatedConstantEnum(
BinarySensorDeviceClass.PROBLEM, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_RUNNING = DeprecatedConstantEnum(
BinarySensorDeviceClass.RUNNING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SAFETY = DeprecatedConstantEnum(
BinarySensorDeviceClass.SAFETY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SMOKE = DeprecatedConstantEnum(
BinarySensorDeviceClass.SMOKE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SOUND = DeprecatedConstantEnum(
BinarySensorDeviceClass.SOUND, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_TAMPER = DeprecatedConstantEnum(
BinarySensorDeviceClass.TAMPER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_UPDATE = DeprecatedConstantEnum(
BinarySensorDeviceClass.UPDATE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_VIBRATION = DeprecatedConstantEnum(
BinarySensorDeviceClass.VIBRATION, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
BinarySensorDeviceClass.WINDOW, "2025.1"
)
# mypy: disallow-any-generics
@@ -294,11 +200,3 @@ class BinarySensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
if (is_on := self.is_on) is None:
return None
return STATE_ON if is_on else STATE_OFF
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -292,14 +292,6 @@ class BluesoundPlayer(MediaPlayerEntity):
self._last_status_update = dt_util.utcnow()
self._status = status
group_name = status.group_name
if group_name != self._group_name:
_LOGGER.debug("Group name change detected on device: %s", self.id)
self._group_name = group_name
# rebuild ordered list of entity_ids that are in the group, master is first
self._group_list = self.rebuild_bluesound_group()
self.async_write_ha_state()
except PlayerUnreachableError:
self._attr_available = False
@@ -323,6 +315,8 @@ class BluesoundPlayer(MediaPlayerEntity):
self._sync_status = sync_status
self._group_list = self.rebuild_bluesound_group()
if sync_status.master is not None:
self._is_master = False
master_id = f"{sync_status.master.ip}:{sync_status.master.port}"
@@ -619,21 +613,32 @@ class BluesoundPlayer(MediaPlayerEntity):
def rebuild_bluesound_group(self) -> list[str]:
"""Rebuild the list of entities in speaker group."""
if self._group_name is None:
if self.sync_status.master is None and self.sync_status.slaves is None:
return []
device_group = self._group_name.split("+")
player_entities: list[BluesoundPlayer] = self.hass.data[DATA_BLUESOUND]
sorted_entities: list[BluesoundPlayer] = sorted(
self.hass.data[DATA_BLUESOUND],
key=lambda entity: entity.is_master,
reverse=True,
)
return [
entity.sync_status.name
for entity in sorted_entities
if entity.bluesound_device_name in device_group
leader_sync_status: SyncStatus | None = None
if self.sync_status.master is None:
leader_sync_status = self.sync_status
else:
required_id = f"{self.sync_status.master.ip}:{self.sync_status.master.port}"
for x in player_entities:
if x.sync_status.id == required_id:
leader_sync_status = x.sync_status
break
if leader_sync_status is None or leader_sync_status.slaves is None:
return []
follower_ids = [f"{x.ip}:{x.port}" for x in leader_sync_status.slaves]
follower_names = [
x.sync_status.name
for x in player_entities
if x.sync_status.id in follower_ids
]
follower_names.insert(0, leader_sync_status.name)
return follower_names
async def async_unjoin(self) -> None:
"""Unjoin the player from a group."""

View File

@@ -27,9 +27,18 @@ from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_SOURCE, CONF_US
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
from homeassistant.util.ssl import get_default_context
from . import DOMAIN
from .const import CONF_ALLOWED_REGIONS, CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN
from .const import (
CONF_ALLOWED_REGIONS,
CONF_CAPTCHA_REGIONS,
CONF_CAPTCHA_TOKEN,
CONF_CAPTCHA_URL,
CONF_GCID,
CONF_READ_ONLY,
CONF_REFRESH_TOKEN,
)
DATA_SCHEMA = vol.Schema(
{
@@ -41,7 +50,14 @@ DATA_SCHEMA = vol.Schema(
translation_key="regions",
)
),
}
},
extra=vol.REMOVE_EXTRA,
)
CAPTCHA_SCHEMA = vol.Schema(
{
vol.Required(CONF_CAPTCHA_TOKEN): str,
},
extra=vol.REMOVE_EXTRA,
)
@@ -54,6 +70,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
data[CONF_USERNAME],
data[CONF_PASSWORD],
get_region_from_name(data[CONF_REGION]),
hcaptcha_token=data.get(CONF_CAPTCHA_TOKEN),
verify=get_default_context(),
)
try:
@@ -79,15 +97,17 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
data: dict[str, Any] = {}
_existing_entry_data: Mapping[str, Any] | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
errors: dict[str, str] = self.data.pop("errors", {})
if user_input is not None:
if user_input is not None and not errors:
unique_id = f"{user_input[CONF_REGION]}-{user_input[CONF_USERNAME]}"
await self.async_set_unique_id(unique_id)
@@ -96,22 +116,35 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
else:
self._abort_if_unique_id_configured()
# Store user input for later use
self.data.update(user_input)
# North America and Rest of World require captcha token
if (
self.data.get(CONF_REGION) in CONF_CAPTCHA_REGIONS
and CONF_CAPTCHA_TOKEN not in self.data
):
return await self.async_step_captcha()
info = None
try:
info = await validate_input(self.hass, user_input)
entry_data = {
**user_input,
CONF_REFRESH_TOKEN: info.get(CONF_REFRESH_TOKEN),
CONF_GCID: info.get(CONF_GCID),
}
info = await validate_input(self.hass, self.data)
except MissingCaptcha:
errors["base"] = "missing_captcha"
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
finally:
self.data.pop(CONF_CAPTCHA_TOKEN, None)
if info:
entry_data = {
**self.data,
CONF_REFRESH_TOKEN: info.get(CONF_REFRESH_TOKEN),
CONF_GCID: info.get(CONF_GCID),
}
if self.source == SOURCE_REAUTH:
return self.async_update_reload_and_abort(
self._get_reauth_entry(), data=entry_data
@@ -128,7 +161,7 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
schema = self.add_suggested_values_to_schema(
DATA_SCHEMA,
self._existing_entry_data,
self._existing_entry_data or self.data,
)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
@@ -147,6 +180,22 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
self._existing_entry_data = self._get_reconfigure_entry().data
return await self.async_step_user()
async def async_step_captcha(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Show captcha form."""
if user_input and user_input.get(CONF_CAPTCHA_TOKEN):
self.data[CONF_CAPTCHA_TOKEN] = user_input[CONF_CAPTCHA_TOKEN].strip()
return await self.async_step_user(self.data)
return self.async_show_form(
step_id="captcha",
data_schema=CAPTCHA_SCHEMA,
description_placeholders={
"captcha_url": CONF_CAPTCHA_URL.format(region=self.data[CONF_REGION])
},
)
@staticmethod
@callback
def async_get_options_flow(

View File

@@ -8,10 +8,15 @@ ATTR_DIRECTION = "direction"
ATTR_VIN = "vin"
CONF_ALLOWED_REGIONS = ["china", "north_america", "rest_of_world"]
CONF_CAPTCHA_REGIONS = ["north_america", "rest_of_world"]
CONF_READ_ONLY = "read_only"
CONF_ACCOUNT = "account"
CONF_REFRESH_TOKEN = "refresh_token"
CONF_GCID = "gcid"
CONF_CAPTCHA_TOKEN = "captcha_token"
CONF_CAPTCHA_URL = (
"https://bimmer-connected.readthedocs.io/en/stable/captcha/{region}.html"
)
DATA_HASS_CONFIG = "hass_config"

View File

@@ -84,11 +84,6 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
if self.account.refresh_token != old_refresh_token:
self._update_config_entry_refresh_token(self.account.refresh_token)
_LOGGER.debug(
"bimmer_connected: refresh token %s > %s",
old_refresh_token,
self.account.refresh_token,
)
def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None:
"""Update or delete the refresh_token in the Config Entry."""

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"requirements": ["bimmer-connected[china]==0.16.4"]
"requirements": ["bimmer-connected[china]==0.17.0"]
}

View File

@@ -7,6 +7,16 @@
"password": "[%key:common::config_flow::data::password%]",
"region": "ConnectedDrive Region"
}
},
"captcha": {
"title": "Are you a robot?",
"description": "A captcha is required for BMW login. Visit the external website to complete the challenge and submit the form. Copy the resulting token into the field below.\n\n{captcha_url}\n\nNo data will be exposed outside of your Home Assistant instance.",
"data": {
"captcha_token": "Captcha token"
},
"data_description": {
"captcha_token": "One-time token retrieved from the captcha challenge."
}
}
},
"error": {

View File

@@ -9,4 +9,3 @@ ATTR_ITEM_NAME: Final = "item"
ATTR_NOTIFICATION_TYPE: Final = "message"
SERVICE_PUSH_NOTIFICATION = "send_message"
UNIT_ITEMS = "items"

View File

@@ -20,7 +20,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import BringConfigEntry
from .const import UNIT_ITEMS
from .coordinator import BringData, BringDataUpdateCoordinator
from .entity import BringBaseEntity
from .util import list_language, sum_attributes
@@ -48,19 +47,16 @@ SENSOR_DESCRIPTIONS: tuple[BringSensorEntityDescription, ...] = (
key=BringSensor.URGENT,
translation_key=BringSensor.URGENT,
value_fn=lambda lst, _: sum_attributes(lst, "urgent"),
native_unit_of_measurement=UNIT_ITEMS,
),
BringSensorEntityDescription(
key=BringSensor.CONVENIENT,
translation_key=BringSensor.CONVENIENT,
value_fn=lambda lst, _: sum_attributes(lst, "convenient"),
native_unit_of_measurement=UNIT_ITEMS,
),
BringSensorEntityDescription(
key=BringSensor.DISCOUNTED,
translation_key=BringSensor.DISCOUNTED,
value_fn=lambda lst, _: sum_attributes(lst, "discounted"),
native_unit_of_measurement=UNIT_ITEMS,
),
BringSensorEntityDescription(
key=BringSensor.LIST_LANGUAGE,

View File

@@ -1,4 +1,7 @@
{
"common": {
"shopping_list_items": "items"
},
"config": {
"step": {
"user": {
@@ -29,13 +32,16 @@
"entity": {
"sensor": {
"urgent": {
"name": "Urgent"
"name": "Urgent",
"unit_of_measurement": "[%key:component::bring::common::shopping_list_items%]"
},
"convenient": {
"name": "On occasion"
"name": "On occasion",
"unit_of_measurement": "[%key:component::bring::common::shopping_list_items%]"
},
"discounted": {
"name": "Discount only"
"name": "Discount only",
"unit_of_measurement": "[%key:component::bring::common::shopping_list_items%]"
},
"list_language": {
"name": "Region & language",

View File

@@ -67,9 +67,7 @@ from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, VolDictType
from homeassistant.loader import bind_hass
from .const import ( # noqa: F401
_DEPRECATED_STREAM_TYPE_HLS,
_DEPRECATED_STREAM_TYPE_WEB_RTC,
from .const import (
CAMERA_IMAGE_TIMEOUT,
CAMERA_STREAM_SOURCE_TIMEOUT,
CONF_DURATION,
@@ -135,16 +133,6 @@ class CameraEntityFeature(IntFlag):
STREAM = 2
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Pleease use the CameraEntityFeature enum instead.
_DEPRECATED_SUPPORT_ON_OFF: Final = DeprecatedConstantEnum(
CameraEntityFeature.ON_OFF, "2025.1"
)
_DEPRECATED_SUPPORT_STREAM: Final = DeprecatedConstantEnum(
CameraEntityFeature.STREAM, "2025.1"
)
DEFAULT_CONTENT_TYPE: Final = "image/jpeg"
ENTITY_IMAGE_URL: Final = "/api/camera_proxy/{0}?token={1}"

View File

@@ -3,15 +3,8 @@
from __future__ import annotations
from enum import StrEnum
from functools import partial
from typing import TYPE_CHECKING, Final
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.util.hass_dict import HassKey
if TYPE_CHECKING:
@@ -58,17 +51,3 @@ class StreamType(StrEnum):
HLS = "hls"
WEB_RTC = "web_rtc"
# These constants are deprecated as of Home Assistant 2022.5
# Please use the StreamType enum instead.
_DEPRECATED_STREAM_TYPE_HLS = DeprecatedConstantEnum(StreamType.HLS, "2025.1")
_DEPRECATED_STREAM_TYPE_WEB_RTC = DeprecatedConstantEnum(StreamType.WEB_RTC, "2025.1")
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -26,11 +26,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
@@ -41,20 +36,6 @@ from homeassistant.util.hass_dict import HassKey
from homeassistant.util.unit_conversion import TemperatureConverter
from .const import ( # noqa: F401
_DEPRECATED_HVAC_MODE_AUTO,
_DEPRECATED_HVAC_MODE_COOL,
_DEPRECATED_HVAC_MODE_DRY,
_DEPRECATED_HVAC_MODE_FAN_ONLY,
_DEPRECATED_HVAC_MODE_HEAT,
_DEPRECATED_HVAC_MODE_HEAT_COOL,
_DEPRECATED_HVAC_MODE_OFF,
_DEPRECATED_SUPPORT_AUX_HEAT,
_DEPRECATED_SUPPORT_FAN_MODE,
_DEPRECATED_SUPPORT_PRESET_MODE,
_DEPRECATED_SUPPORT_SWING_MODE,
_DEPRECATED_SUPPORT_TARGET_HUMIDITY,
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE,
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE_RANGE,
ATTR_AUX_HEAT,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
@@ -70,6 +51,8 @@ from .const import ( # noqa: F401
ATTR_MIN_TEMP,
ATTR_PRESET_MODE,
ATTR_PRESET_MODES,
ATTR_SWING_HORIZONTAL_MODE,
ATTR_SWING_HORIZONTAL_MODES,
ATTR_SWING_MODE,
ATTR_SWING_MODES,
ATTR_TARGET_TEMP_HIGH,
@@ -101,6 +84,7 @@ from .const import ( # noqa: F401
SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_HORIZONTAL_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
SWING_BOTH,
@@ -219,6 +203,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"async_handle_set_swing_mode_service",
[ClimateEntityFeature.SWING_MODE],
)
component.async_register_entity_service(
SERVICE_SET_SWING_HORIZONTAL_MODE,
{vol.Required(ATTR_SWING_HORIZONTAL_MODE): cv.string},
"async_handle_set_swing_horizontal_mode_service",
[ClimateEntityFeature.SWING_HORIZONTAL_MODE],
)
return True
@@ -256,6 +246,8 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
"fan_modes",
"swing_mode",
"swing_modes",
"swing_horizontal_mode",
"swing_horizontal_modes",
"supported_features",
"min_temp",
"max_temp",
@@ -300,6 +292,8 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_attr_supported_features: ClimateEntityFeature = ClimateEntityFeature(0)
_attr_swing_mode: str | None
_attr_swing_modes: list[str] | None
_attr_swing_horizontal_mode: str | None
_attr_swing_horizontal_modes: list[str] | None
_attr_target_humidity: float | None = None
_attr_target_temperature_high: float | None
_attr_target_temperature_low: float | None
@@ -513,6 +507,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if ClimateEntityFeature.SWING_MODE in supported_features:
data[ATTR_SWING_MODES] = self.swing_modes
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
data[ATTR_SWING_HORIZONTAL_MODES] = self.swing_horizontal_modes
return data
@final
@@ -564,6 +561,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if ClimateEntityFeature.SWING_MODE in supported_features:
data[ATTR_SWING_MODE] = self.swing_mode
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode
if ClimateEntityFeature.AUX_HEAT in supported_features:
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
if (
@@ -691,11 +691,27 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""
return self._attr_swing_modes
@cached_property
def swing_horizontal_mode(self) -> str | None:
"""Return the horizontal swing setting.
Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE.
"""
return self._attr_swing_horizontal_mode
@cached_property
def swing_horizontal_modes(self) -> list[str] | None:
"""Return the list of available horizontal swing modes.
Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE.
"""
return self._attr_swing_horizontal_modes
@final
@callback
def _valid_mode_or_raise(
self,
mode_type: Literal["preset", "swing", "fan", "hvac"],
mode_type: Literal["preset", "horizontal_swing", "swing", "fan", "hvac"],
mode: str | HVACMode,
modes: list[str] | list[HVACMode] | None,
) -> None:
@@ -793,6 +809,26 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Set new target swing operation."""
await self.hass.async_add_executor_job(self.set_swing_mode, swing_mode)
@final
async def async_handle_set_swing_horizontal_mode_service(
self, swing_horizontal_mode: str
) -> None:
"""Validate and set new horizontal swing mode."""
self._valid_mode_or_raise(
"horizontal_swing", swing_horizontal_mode, self.swing_horizontal_modes
)
await self.async_set_swing_horizontal_mode(swing_horizontal_mode)
def set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new target horizontal swing operation."""
raise NotImplementedError
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new target horizontal swing operation."""
await self.hass.async_add_executor_job(
self.set_swing_horizontal_mode, swing_horizontal_mode
)
@final
async def async_handle_set_preset_mode_service(self, preset_mode: str) -> None:
"""Validate and set new preset mode."""
@@ -1027,13 +1063,3 @@ async def async_service_temperature_set(
kwargs[value] = temp
await entity.async_set_temperature(**kwargs)
# As we import deprecated constants from the const module, we need to add these two functions
# otherwise this module will be logged for using deprecated constants and not the custom component
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = ft.partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -1,14 +1,6 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
class HVACMode(StrEnum):
@@ -37,15 +29,6 @@ class HVACMode(StrEnum):
FAN_ONLY = "fan_only"
# These HVAC_MODE_* constants are deprecated as of Home Assistant 2022.5.
# Please use the HVACMode enum instead.
_DEPRECATED_HVAC_MODE_OFF = DeprecatedConstantEnum(HVACMode.OFF, "2025.1")
_DEPRECATED_HVAC_MODE_HEAT = DeprecatedConstantEnum(HVACMode.HEAT, "2025.1")
_DEPRECATED_HVAC_MODE_COOL = DeprecatedConstantEnum(HVACMode.COOL, "2025.1")
_DEPRECATED_HVAC_MODE_HEAT_COOL = DeprecatedConstantEnum(HVACMode.HEAT_COOL, "2025.1")
_DEPRECATED_HVAC_MODE_AUTO = DeprecatedConstantEnum(HVACMode.AUTO, "2025.1")
_DEPRECATED_HVAC_MODE_DRY = DeprecatedConstantEnum(HVACMode.DRY, "2025.1")
_DEPRECATED_HVAC_MODE_FAN_ONLY = DeprecatedConstantEnum(HVACMode.FAN_ONLY, "2025.1")
HVAC_MODES = [cls.value for cls in HVACMode]
# No preset is active
@@ -92,6 +75,10 @@ SWING_BOTH = "both"
SWING_VERTICAL = "vertical"
SWING_HORIZONTAL = "horizontal"
# Possible horizontal swing state
SWING_HORIZONTAL_ON = "on"
SWING_HORIZONTAL_OFF = "off"
class HVACAction(StrEnum):
"""HVAC action for climate devices."""
@@ -106,14 +93,6 @@ class HVACAction(StrEnum):
PREHEATING = "preheating"
# These CURRENT_HVAC_* constants are deprecated as of Home Assistant 2022.5.
# Please use the HVACAction enum instead.
_DEPRECATED_CURRENT_HVAC_OFF = DeprecatedConstantEnum(HVACAction.OFF, "2025.1")
_DEPRECATED_CURRENT_HVAC_HEAT = DeprecatedConstantEnum(HVACAction.HEATING, "2025.1")
_DEPRECATED_CURRENT_HVAC_COOL = DeprecatedConstantEnum(HVACAction.COOLING, "2025.1")
_DEPRECATED_CURRENT_HVAC_DRY = DeprecatedConstantEnum(HVACAction.DRYING, "2025.1")
_DEPRECATED_CURRENT_HVAC_IDLE = DeprecatedConstantEnum(HVACAction.IDLE, "2025.1")
_DEPRECATED_CURRENT_HVAC_FAN = DeprecatedConstantEnum(HVACAction.FAN, "2025.1")
CURRENT_HVAC_ACTIONS = [cls.value for cls in HVACAction]
@@ -134,6 +113,8 @@ ATTR_HVAC_MODES = "hvac_modes"
ATTR_HVAC_MODE = "hvac_mode"
ATTR_SWING_MODES = "swing_modes"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_HORIZONTAL_MODE = "swing_horizontal_mode"
ATTR_SWING_HORIZONTAL_MODES = "swing_horizontal_modes"
ATTR_TARGET_TEMP_HIGH = "target_temp_high"
ATTR_TARGET_TEMP_LOW = "target_temp_low"
ATTR_TARGET_TEMP_STEP = "target_temp_step"
@@ -153,6 +134,7 @@ SERVICE_SET_PRESET_MODE = "set_preset_mode"
SERVICE_SET_HUMIDITY = "set_humidity"
SERVICE_SET_HVAC_MODE = "set_hvac_mode"
SERVICE_SET_SWING_MODE = "set_swing_mode"
SERVICE_SET_SWING_HORIZONTAL_MODE = "set_swing_horizontal_mode"
SERVICE_SET_TEMPERATURE = "set_temperature"
@@ -168,35 +150,4 @@ class ClimateEntityFeature(IntFlag):
AUX_HEAT = 64
TURN_OFF = 128
TURN_ON = 256
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the ClimateEntityFeature enum instead.
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE = DeprecatedConstantEnum(
ClimateEntityFeature.TARGET_TEMPERATURE, "2025.1"
)
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE_RANGE = DeprecatedConstantEnum(
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, "2025.1"
)
_DEPRECATED_SUPPORT_TARGET_HUMIDITY = DeprecatedConstantEnum(
ClimateEntityFeature.TARGET_HUMIDITY, "2025.1"
)
_DEPRECATED_SUPPORT_FAN_MODE = DeprecatedConstantEnum(
ClimateEntityFeature.FAN_MODE, "2025.1"
)
_DEPRECATED_SUPPORT_PRESET_MODE = DeprecatedConstantEnum(
ClimateEntityFeature.PRESET_MODE, "2025.1"
)
_DEPRECATED_SUPPORT_SWING_MODE = DeprecatedConstantEnum(
ClimateEntityFeature.SWING_MODE, "2025.1"
)
_DEPRECATED_SUPPORT_AUX_HEAT = DeprecatedConstantEnum(
ClimateEntityFeature.AUX_HEAT, "2025.1"
)
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())
SWING_HORIZONTAL_MODE = 512

View File

@@ -51,6 +51,13 @@
"on": "mdi:arrow-oscillating",
"vertical": "mdi:arrow-up-down"
}
},
"swing_horizontal_mode": {
"default": "mdi:circle-medium",
"state": {
"off": "mdi:arrow-oscillating-off",
"on": "mdi:arrow-expand-horizontal"
}
}
}
}
@@ -65,6 +72,9 @@
"set_swing_mode": {
"service": "mdi:arrow-oscillating"
},
"set_swing_horizontal_mode": {
"service": "mdi:arrow-expand-horizontal"
},
"set_temperature": {
"service": "mdi:thermometer"
},

View File

@@ -14,6 +14,7 @@ from .const import (
ATTR_HUMIDITY,
ATTR_HVAC_MODE,
ATTR_PRESET_MODE,
ATTR_SWING_HORIZONTAL_MODE,
ATTR_SWING_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
@@ -23,6 +24,7 @@ from .const import (
SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_HORIZONTAL_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
)
@@ -76,6 +78,14 @@ async def _async_reproduce_states(
):
await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE])
if (
ATTR_SWING_HORIZONTAL_MODE in state.attributes
and state.attributes[ATTR_SWING_HORIZONTAL_MODE] is not None
):
await call_service(
SERVICE_SET_SWING_HORIZONTAL_MODE, [ATTR_SWING_HORIZONTAL_MODE]
)
if (
ATTR_FAN_MODE in state.attributes
and state.attributes[ATTR_FAN_MODE] is not None

View File

@@ -131,7 +131,20 @@ set_swing_mode:
fields:
swing_mode:
required: true
example: "horizontal"
example: "on"
selector:
text:
set_swing_horizontal_mode:
target:
entity:
domain: climate
supported_features:
- climate.ClimateEntityFeature.SWING_HORIZONTAL_MODE
fields:
swing_horizontal_mode:
required: true
example: "on"
selector:
text:

View File

@@ -19,6 +19,7 @@ from . import (
ATTR_HUMIDITY,
ATTR_HVAC_ACTION,
ATTR_PRESET_MODE,
ATTR_SWING_HORIZONTAL_MODE,
ATTR_SWING_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
@@ -34,6 +35,7 @@ SIGNIFICANT_ATTRIBUTES: set[str] = {
ATTR_HVAC_ACTION,
ATTR_PRESET_MODE,
ATTR_SWING_MODE,
ATTR_SWING_HORIZONTAL_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE,
@@ -70,6 +72,7 @@ def async_check_significant_change(
ATTR_HVAC_ACTION,
ATTR_PRESET_MODE,
ATTR_SWING_MODE,
ATTR_SWING_HORIZONTAL_MODE,
]:
return True

View File

@@ -123,6 +123,16 @@
"swing_modes": {
"name": "Swing modes"
},
"swing_horizontal_mode": {
"name": "Horizontal swing mode",
"state": {
"off": "[%key:common::state::off%]",
"on": "[%key:common::state::on%]"
}
},
"swing_horizontal_modes": {
"name": "Horizontal swing modes"
},
"target_temp_high": {
"name": "Upper target temperature"
},
@@ -221,6 +231,16 @@
}
}
},
"set_swing_horizontal_mode": {
"name": "Set horizontal swing mode",
"description": "Sets horizontal swing operation mode.",
"fields": {
"swing_horizontal_mode": {
"name": "Horizontal swing mode",
"description": "Horizontal swing operation mode."
}
}
},
"turn_on": {
"name": "[%key:common::action::turn_on%]",
"description": "Turns climate device on."
@@ -264,6 +284,9 @@
"not_valid_swing_mode": {
"message": "Swing mode {mode} is not valid. Valid swing modes are: {modes}."
},
"not_valid_horizontal_swing_mode": {
"message": "Horizontal swing mode {mode} is not valid. Valid horizontal swing modes are: {modes}."
},
"not_valid_fan_mode": {
"message": "Fan mode {mode} is not valid. Valid fan modes are: {modes}."
},

View File

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

View File

@@ -89,36 +89,8 @@ class CoverDeviceClass(StrEnum):
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(CoverDeviceClass))
# DEVICE_CLASS* below are deprecated as of 2021.12
# use the CoverDeviceClass enum instead.
DEVICE_CLASSES = [cls.value for cls in CoverDeviceClass]
_DEPRECATED_DEVICE_CLASS_AWNING = DeprecatedConstantEnum(
CoverDeviceClass.AWNING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_BLIND = DeprecatedConstantEnum(
CoverDeviceClass.BLIND, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_CURTAIN = DeprecatedConstantEnum(
CoverDeviceClass.CURTAIN, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DAMPER = DeprecatedConstantEnum(
CoverDeviceClass.DAMPER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DOOR = DeprecatedConstantEnum(CoverDeviceClass.DOOR, "2025.1")
_DEPRECATED_DEVICE_CLASS_GARAGE = DeprecatedConstantEnum(
CoverDeviceClass.GARAGE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_GATE = DeprecatedConstantEnum(CoverDeviceClass.GATE, "2025.1")
_DEPRECATED_DEVICE_CLASS_SHADE = DeprecatedConstantEnum(
CoverDeviceClass.SHADE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SHUTTER = DeprecatedConstantEnum(
CoverDeviceClass.SHUTTER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
CoverDeviceClass.WINDOW, "2025.1"
)
# mypy: disallow-any-generics
@@ -136,27 +108,6 @@ class CoverEntityFeature(IntFlag):
SET_TILT_POSITION = 128
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the CoverEntityFeature enum instead.
_DEPRECATED_SUPPORT_OPEN = DeprecatedConstantEnum(CoverEntityFeature.OPEN, "2025.1")
_DEPRECATED_SUPPORT_CLOSE = DeprecatedConstantEnum(CoverEntityFeature.CLOSE, "2025.1")
_DEPRECATED_SUPPORT_SET_POSITION = DeprecatedConstantEnum(
CoverEntityFeature.SET_POSITION, "2025.1"
)
_DEPRECATED_SUPPORT_STOP = DeprecatedConstantEnum(CoverEntityFeature.STOP, "2025.1")
_DEPRECATED_SUPPORT_OPEN_TILT = DeprecatedConstantEnum(
CoverEntityFeature.OPEN_TILT, "2025.1"
)
_DEPRECATED_SUPPORT_CLOSE_TILT = DeprecatedConstantEnum(
CoverEntityFeature.CLOSE_TILT, "2025.1"
)
_DEPRECATED_SUPPORT_STOP_TILT = DeprecatedConstantEnum(
CoverEntityFeature.STOP_TILT, "2025.1"
)
_DEPRECATED_SUPPORT_SET_TILT_POSITION = DeprecatedConstantEnum(
CoverEntityFeature.SET_TILT_POSITION, "2025.1"
)
ATTR_CURRENT_POSITION = "current_position"
ATTR_CURRENT_TILT_POSITION = "current_tilt_position"
ATTR_POSITION = "position"

View File

@@ -43,6 +43,7 @@ async def async_setup_entry(
target_humidity=None,
current_humidity=None,
swing_mode=None,
swing_horizontal_mode=None,
hvac_mode=HVACMode.HEAT,
hvac_action=HVACAction.HEATING,
target_temp_high=None,
@@ -60,6 +61,7 @@ async def async_setup_entry(
target_humidity=67.4,
current_humidity=54.2,
swing_mode="off",
swing_horizontal_mode="auto",
hvac_mode=HVACMode.COOL,
hvac_action=HVACAction.COOLING,
target_temp_high=None,
@@ -78,6 +80,7 @@ async def async_setup_entry(
target_humidity=None,
current_humidity=None,
swing_mode="auto",
swing_horizontal_mode=None,
hvac_mode=HVACMode.HEAT_COOL,
hvac_action=None,
target_temp_high=24,
@@ -109,6 +112,7 @@ class DemoClimate(ClimateEntity):
target_humidity: float | None,
current_humidity: float | None,
swing_mode: str | None,
swing_horizontal_mode: str | None,
hvac_mode: HVACMode,
hvac_action: HVACAction | None,
target_temp_high: float | None,
@@ -129,6 +133,8 @@ class DemoClimate(ClimateEntity):
self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY
if swing_mode is not None:
self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
if swing_horizontal_mode is not None:
self._attr_supported_features |= ClimateEntityFeature.SWING_HORIZONTAL_MODE
if HVACMode.HEAT_COOL in hvac_modes or HVACMode.AUTO in hvac_modes:
self._attr_supported_features |= (
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
@@ -147,9 +153,11 @@ class DemoClimate(ClimateEntity):
self._hvac_action = hvac_action
self._hvac_mode = hvac_mode
self._current_swing_mode = swing_mode
self._current_swing_horizontal_mode = swing_horizontal_mode
self._fan_modes = ["on_low", "on_high", "auto_low", "auto_high", "off"]
self._hvac_modes = hvac_modes
self._swing_modes = ["auto", "1", "2", "3", "off"]
self._swing_horizontal_modes = ["auto", "rangefull", "off"]
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
self._attr_device_info = DeviceInfo(
@@ -242,6 +250,16 @@ class DemoClimate(ClimateEntity):
"""List of available swing modes."""
return self._swing_modes
@property
def swing_horizontal_mode(self) -> str | None:
"""Return the swing setting."""
return self._current_swing_horizontal_mode
@property
def swing_horizontal_modes(self) -> list[str]:
"""List of available swing modes."""
return self._swing_horizontal_modes
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
@@ -266,6 +284,11 @@ class DemoClimate(ClimateEntity):
self._current_swing_mode = swing_mode
self.async_write_ha_state()
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new swing mode."""
self._current_swing_horizontal_mode = swing_horizontal_mode
self.async_write_ha_state()
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new fan mode."""
self._current_fan_mode = fan_mode

View File

@@ -19,6 +19,13 @@
"auto": "mdi:arrow-oscillating",
"off": "mdi:arrow-oscillating-off"
}
},
"swing_horizontal_mode": {
"state": {
"rangefull": "mdi:pan-horizontal",
"auto": "mdi:compare-horizontal",
"off": "mdi:arrow-oscillating-off"
}
}
}
}

View File

@@ -42,6 +42,13 @@
"auto": "Auto",
"off": "[%key:common::state::off%]"
}
},
"swing_horizontal_mode": {
"state": {
"rangefull": "Full range",
"auto": "Auto",
"off": "[%key:common::state::off%]"
}
}
}
}

View File

@@ -2,15 +2,8 @@
from __future__ import annotations
from functools import partial
from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME # noqa: F401
from homeassistant.core import HomeAssistant
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
@@ -23,10 +16,6 @@ from .config_entry import ( # noqa: F401
async_unload_entry,
)
from .const import ( # noqa: F401
_DEPRECATED_SOURCE_TYPE_BLUETOOTH,
_DEPRECATED_SOURCE_TYPE_BLUETOOTH_LE,
_DEPRECATED_SOURCE_TYPE_GPS,
_DEPRECATED_SOURCE_TYPE_ROUTER,
ATTR_ATTRIBUTES,
ATTR_BATTERY,
ATTR_DEV_ID,
@@ -72,13 +61,3 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the device tracker."""
async_setup_legacy_integration(hass, config)
return True
# As we import deprecated constants from the const module, we need to add these two functions
# otherwise this module will be logged for using deprecated constants and not the custom component
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -4,16 +4,9 @@ from __future__ import annotations
from datetime import timedelta
from enum import StrEnum
from functools import partial
import logging
from typing import Final
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.util.signal_type import SignalType
LOGGER: Final = logging.getLogger(__package__)
@@ -34,19 +27,6 @@ class SourceType(StrEnum):
BLUETOOTH_LE = "bluetooth_le"
# SOURCE_TYPE_* below are deprecated as of 2022.9
# use the SourceType enum instead.
_DEPRECATED_SOURCE_TYPE_GPS: Final = DeprecatedConstantEnum(SourceType.GPS, "2025.1")
_DEPRECATED_SOURCE_TYPE_ROUTER: Final = DeprecatedConstantEnum(
SourceType.ROUTER, "2025.1"
)
_DEPRECATED_SOURCE_TYPE_BLUETOOTH: Final = DeprecatedConstantEnum(
SourceType.BLUETOOTH, "2025.1"
)
_DEPRECATED_SOURCE_TYPE_BLUETOOTH_LE: Final = DeprecatedConstantEnum(
SourceType.BLUETOOTH_LE, "2025.1"
)
CONF_SCAN_INTERVAL: Final = "interval_seconds"
SCAN_INTERVAL: Final = timedelta(seconds=12)
@@ -72,10 +52,3 @@ ATTR_IP: Final = "ip"
CONNECTED_DEVICE_REGISTERED = SignalType[dict[str, str | None]](
"device_tracker_connected_device_registered"
)
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -6,11 +6,17 @@
"description": "[%key:common::config_flow::description::confirm_setup%]",
"data": {
"ip_address": "[%key:common::config_flow::data::ip%]"
},
"data_description": {
"ip_address": "IP address of your devolo Home Network device. This can be found in the devolo Home Network App on the device dashboard."
}
},
"reauth_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"password": "Password you protected the device with."
}
},
"zeroconf_confirm": {

View File

@@ -60,11 +60,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_reload_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -> None:
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -11,12 +11,7 @@ from pydiscovergy.authentication import BasicAuth
import pydiscovergy.error as discovergyError
import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.selector import (
@@ -57,35 +52,14 @@ class DiscovergyConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_existing_entry: ConfigEntry
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user",
data_schema=CONFIG_SCHEMA,
)
return await self._validate_and_save(user_input)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle the initial step."""
self._existing_entry = self._get_reauth_entry()
return await self.async_step_reauth_confirm()
return await self.async_step_user()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the reauth step."""
return await self._validate_and_save(user_input, step_id="reauth_confirm")
async def _validate_and_save(
self, user_input: Mapping[str, Any] | None = None, step_id: str = "user"
async def async_step_user(
self, user_input: Mapping[str, Any] | None = None
) -> ConfigFlowResult:
"""Validate user input and create config entry."""
errors = {}
@@ -106,17 +80,17 @@ class DiscovergyConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Unexpected error occurred while getting meters")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(user_input[CONF_EMAIL].lower())
if self.source == SOURCE_REAUTH:
self._abort_if_unique_id_mismatch(reason="account_mismatch")
return self.async_update_reload_and_abort(
entry=self._existing_entry,
data={
CONF_EMAIL: user_input[CONF_EMAIL],
entry=self._get_reauth_entry(),
data_updates={
CONF_PASSWORD: user_input[CONF_PASSWORD],
},
)
# set unique id to title which is the account email
await self.async_set_unique_id(user_input[CONF_EMAIL].lower())
self._abort_if_unique_id_configured()
return self.async_create_entry(
@@ -124,10 +98,10 @@ class DiscovergyConfigFlow(ConfigFlow, domain=DOMAIN):
)
return self.async_show_form(
step_id=step_id,
step_id="user",
data_schema=self.add_suggested_values_to_schema(
CONFIG_SCHEMA,
self._existing_entry.data
self._get_reauth_entry().data
if self.source == SOURCE_REAUTH
else user_input,
),

View File

@@ -6,12 +6,6 @@
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
},
"reauth_confirm": {
"data": {
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
@@ -21,6 +15,7 @@
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"account_mismatch": "The inexogy account authenticated with, does not match the account needed re-authentication.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},

View File

@@ -10,16 +10,31 @@ from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
CONF_API_KEY,
CONF_ID,
CONF_UNIT_OF_MEASUREMENT,
CONF_URL,
CONF_VALUE_TEMPLATE,
PERCENTAGE,
UnitOfApparentPower,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
UnitOfPressure,
UnitOfSoundPressure,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumeFlowRate,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultType
@@ -41,6 +56,146 @@ from .const import (
)
from .coordinator import EmoncmsCoordinator
SENSORS: dict[str | None, SensorEntityDescription] = {
"kWh": SensorEntityDescription(
key="energy|kWh",
translation_key="energy",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"Wh": SensorEntityDescription(
key="energy|Wh",
translation_key="energy",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"kW": SensorEntityDescription(
key="power|kW",
translation_key="power",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.KILO_WATT,
state_class=SensorStateClass.MEASUREMENT,
),
"W": SensorEntityDescription(
key="power|W",
translation_key="power",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
),
"V": SensorEntityDescription(
key="voltage",
translation_key="voltage",
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
),
"A": SensorEntityDescription(
key="current",
translation_key="current",
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
"VA": SensorEntityDescription(
key="apparent_power",
translation_key="apparent_power",
device_class=SensorDeviceClass.APPARENT_POWER,
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
"°C": SensorEntityDescription(
key="temperature|celsius",
translation_key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
"°F": SensorEntityDescription(
key="temperature|fahrenheit",
translation_key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
state_class=SensorStateClass.MEASUREMENT,
),
"K": SensorEntityDescription(
key="temperature|kelvin",
translation_key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.KELVIN,
state_class=SensorStateClass.MEASUREMENT,
),
"Hz": SensorEntityDescription(
key="frequency",
translation_key="frequency",
device_class=SensorDeviceClass.FREQUENCY,
native_unit_of_measurement=UnitOfFrequency.HERTZ,
state_class=SensorStateClass.MEASUREMENT,
),
"hPa": SensorEntityDescription(
key="pressure",
translation_key="pressure",
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT,
),
"dB": SensorEntityDescription(
key="decibel",
translation_key="decibel",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
state_class=SensorStateClass.MEASUREMENT,
),
"": SensorEntityDescription(
key="volume|cubic_meter",
translation_key="volume",
device_class=SensorDeviceClass.VOLUME_STORAGE,
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
state_class=SensorStateClass.MEASUREMENT,
),
"m³/h": SensorEntityDescription(
key="flow|cubic_meters_per_hour",
translation_key="flow",
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
"l/m": SensorEntityDescription(
key="flow|liters_per_minute",
translation_key="flow",
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
native_unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_MINUTE,
state_class=SensorStateClass.MEASUREMENT,
),
"m/s": SensorEntityDescription(
key="speed|meters_per_second",
translation_key="speed",
device_class=SensorDeviceClass.SPEED,
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
),
"µg/m³": SensorEntityDescription(
key="concentration|microgram_per_cubic_meter",
translation_key="concentration",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
"ppm": SensorEntityDescription(
key="concentration|microgram_parts_per_million",
translation_key="concentration",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"%": SensorEntityDescription(
key="percent",
translation_key="percent",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
}
ATTR_FEEDID = "FeedId"
ATTR_FEEDNAME = "FeedName"
ATTR_LASTUPDATETIME = "LastUpdated"
@@ -173,6 +328,8 @@ async def async_setup_entry(
class EmonCmsSensor(CoordinatorEntity[EmoncmsCoordinator], SensorEntity):
"""Implementation of an Emoncms sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: EmoncmsCoordinator,
@@ -187,33 +344,15 @@ class EmonCmsSensor(CoordinatorEntity[EmoncmsCoordinator], SensorEntity):
elem = {}
if self.coordinator.data:
elem = self.coordinator.data[self.idx]
self._attr_name = f"{name} {elem[FEED_NAME]}"
self._attr_native_unit_of_measurement = unit_of_measurement
self._attr_translation_placeholders = {
"emoncms_details": f"{elem[FEED_TAG]} {elem[FEED_NAME]}",
}
self._attr_unique_id = f"{unique_id}-{elem[FEED_ID]}"
if unit_of_measurement in ("kWh", "Wh"):
self._attr_device_class = SensorDeviceClass.ENERGY
self._attr_state_class = SensorStateClass.TOTAL_INCREASING
elif unit_of_measurement == "W":
self._attr_device_class = SensorDeviceClass.POWER
self._attr_state_class = SensorStateClass.MEASUREMENT
elif unit_of_measurement == "V":
self._attr_device_class = SensorDeviceClass.VOLTAGE
self._attr_state_class = SensorStateClass.MEASUREMENT
elif unit_of_measurement == "A":
self._attr_device_class = SensorDeviceClass.CURRENT
self._attr_state_class = SensorStateClass.MEASUREMENT
elif unit_of_measurement == "VA":
self._attr_device_class = SensorDeviceClass.APPARENT_POWER
self._attr_state_class = SensorStateClass.MEASUREMENT
elif unit_of_measurement in ("°C", "°F", "K"):
self._attr_device_class = SensorDeviceClass.TEMPERATURE
self._attr_state_class = SensorStateClass.MEASUREMENT
elif unit_of_measurement == "Hz":
self._attr_device_class = SensorDeviceClass.FREQUENCY
self._attr_state_class = SensorStateClass.MEASUREMENT
elif unit_of_measurement == "hPa":
self._attr_device_class = SensorDeviceClass.PRESSURE
self._attr_state_class = SensorStateClass.MEASUREMENT
description = SENSORS.get(unit_of_measurement)
if description is not None:
self.entity_description = description
else:
self._attr_native_unit_of_measurement = unit_of_measurement
self._update_attributes(elem)
def _update_attributes(self, elem: dict[str, Any]) -> None:

View File

@@ -24,6 +24,52 @@
"already_configured": "This server is already configured"
}
},
"entity": {
"sensor": {
"energy": {
"name": "Energy {emoncms_details}"
},
"power": {
"name": "Power {emoncms_details}"
},
"percent": {
"name": "Percentage {emoncms_details}"
},
"voltage": {
"name": "Voltage {emoncms_details}"
},
"current": {
"name": "Current {emoncms_details}"
},
"apparent_power": {
"name": "Apparent power {emoncms_details}"
},
"temperature": {
"name": "Temperature {emoncms_details}"
},
"frequency": {
"name": "Frequency {emoncms_details}"
},
"pressure": {
"name": "Pressure {emoncms_details}"
},
"decibel": {
"name": "Decibel {emoncms_details}"
},
"volume": {
"name": "Volume {emoncms_details}"
},
"flow": {
"name": "Flow rate {emoncms_details}"
},
"speed": {
"name": "Speed {emoncms_details}"
},
"concentration": {
"name": "Concentration {emoncms_details}"
}
}
},
"options": {
"error": {
"api_error": "[%key:component::emoncms::config::error::api_error%]"

View File

@@ -95,11 +95,7 @@ async def async_setup_entry(
if entry_data.device_info.voice_assistant_feature_flags_compat(
entry_data.api_version
):
async_add_entities(
[
EsphomeAssistSatellite(entry, entry_data),
]
)
async_add_entities([EsphomeAssistSatellite(entry, entry_data)])
class EsphomeAssistSatellite(
@@ -198,6 +194,9 @@ class EsphomeAssistSatellite(
self._satellite_config.max_active_wake_words = config.max_active_wake_words
_LOGGER.debug("Received satellite configuration: %s", self._satellite_config)
# Inform listeners that config has been updated
self.entry_data.async_assist_satellite_config_updated(self._satellite_config)
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
@@ -254,6 +253,13 @@ class EsphomeAssistSatellite(
# Will use media player for TTS/announcements
self._update_tts_format()
# Update wake word select when config is updated
self.async_on_remove(
self.entry_data.async_register_assist_satellite_set_wake_word_callback(
self.async_set_wake_word
)
)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
await super().async_will_remove_from_hass()
@@ -478,6 +484,17 @@ class EsphomeAssistSatellite(
"""Handle announcement finished message (also sent for TTS)."""
self.tts_response_finished()
@callback
def async_set_wake_word(self, wake_word_id: str) -> None:
"""Set active wake word and update config on satellite."""
self._satellite_config.active_wake_words = [wake_word_id]
self.config_entry.async_create_background_task(
self.hass,
self.async_set_configuration(self._satellite_config),
"esphome_voice_assistant_set_config",
)
_LOGGER.debug("Setting active wake word: %s", wake_word_id)
def _update_tts_format(self) -> None:
"""Update the TTS format from the first media player."""
for supported_format in chain(*self.entry_data.media_player_formats.values()):

View File

@@ -48,6 +48,7 @@ from aioesphomeapi import (
from aioesphomeapi.model import ButtonInfo
from bleak_esphome.backend.device import ESPHomeBluetoothDevice
from homeassistant.components.assist_satellite import AssistSatelliteConfiguration
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
@@ -152,6 +153,12 @@ class RuntimeEntryData:
media_player_formats: dict[str, list[MediaPlayerSupportedFormat]] = field(
default_factory=lambda: defaultdict(list)
)
assist_satellite_config_update_callbacks: list[
Callable[[AssistSatelliteConfiguration], None]
] = field(default_factory=list)
assist_satellite_set_wake_word_callbacks: list[Callable[[str], None]] = field(
default_factory=list
)
@property
def name(self) -> str:
@@ -504,3 +511,35 @@ class RuntimeEntryData:
# We use this to determine if a deep sleep device should
# be marked as unavailable or not.
self.expected_disconnect = True
@callback
def async_register_assist_satellite_config_updated_callback(
self,
callback_: Callable[[AssistSatelliteConfiguration], None],
) -> CALLBACK_TYPE:
"""Register to receive callbacks when the Assist satellite's configuration is updated."""
self.assist_satellite_config_update_callbacks.append(callback_)
return lambda: self.assist_satellite_config_update_callbacks.remove(callback_)
@callback
def async_assist_satellite_config_updated(
self, config: AssistSatelliteConfiguration
) -> None:
"""Notify listeners that the Assist satellite configuration has been updated."""
for callback_ in self.assist_satellite_config_update_callbacks.copy():
callback_(config)
@callback
def async_register_assist_satellite_set_wake_word_callback(
self,
callback_: Callable[[str], None],
) -> CALLBACK_TYPE:
"""Register to receive callbacks when the Assist satellite's wake word is set."""
self.assist_satellite_set_wake_word_callbacks.append(callback_)
return lambda: self.assist_satellite_set_wake_word_callbacks.remove(callback_)
@callback
def async_assist_satellite_set_wake_word(self, wake_word_id: str) -> None:
"""Notify listeners that the Assist satellite wake word has been set."""
for callback_ in self.assist_satellite_set_wake_word_callbacks.copy():
callback_(wake_word_id)

View File

@@ -212,6 +212,10 @@ class FFmpegConvertResponse(web.StreamResponse):
assert proc.stdout is not None
assert proc.stderr is not None
stderr_task = self.hass.async_create_background_task(
self._dump_ffmpeg_stderr(proc), "ESPHome media proxy dump stderr"
)
try:
# Pull audio chunks from ffmpeg and pass them to the HTTP client
while (
@@ -230,18 +234,14 @@ class FFmpegConvertResponse(web.StreamResponse):
raise # don't log error
except:
_LOGGER.exception("Unexpected error during ffmpeg conversion")
# Process did not exit successfully
stderr_text = ""
while line := await proc.stderr.readline():
stderr_text += line.decode()
_LOGGER.error("FFmpeg output: %s", stderr_text)
raise
finally:
# Allow conversion info to be removed
self.convert_info.is_finished = True
# stop dumping ffmpeg stderr task
stderr_task.cancel()
# Terminate hangs, so kill is used
if proc.returncode is None:
proc.kill()
@@ -250,6 +250,16 @@ class FFmpegConvertResponse(web.StreamResponse):
if request.transport and not request.transport.is_closing():
await writer.write_eof()
async def _dump_ffmpeg_stderr(
self,
proc: asyncio.subprocess.Process,
) -> None:
assert proc.stdout is not None
assert proc.stderr is not None
while self.hass.is_running and (chunk := await proc.stderr.readline()):
_LOGGER.debug("ffmpeg[%s] output: %s", proc.pid, chunk.decode().rstrip())
class FFmpegProxyView(HomeAssistantView):
"""FFmpeg web view to convert audio and stream back to client."""

View File

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

View File

@@ -8,8 +8,11 @@ from homeassistant.components.assist_pipeline.select import (
AssistPipelineSelect,
VadSensitivitySelect,
)
from homeassistant.components.select import SelectEntity
from homeassistant.components.assist_satellite import AssistSatelliteConfiguration
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import restore_state
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
@@ -47,6 +50,7 @@ async def async_setup_entry(
[
EsphomeAssistPipelineSelect(hass, entry_data),
EsphomeVadSensitivitySelect(hass, entry_data),
EsphomeAssistSatelliteWakeWordSelect(hass, entry_data),
]
)
@@ -89,3 +93,77 @@ class EsphomeVadSensitivitySelect(EsphomeAssistEntity, VadSensitivitySelect):
"""Initialize a VAD sensitivity selector."""
EsphomeAssistEntity.__init__(self, entry_data)
VadSensitivitySelect.__init__(self, hass, self._device_info.mac_address)
class EsphomeAssistSatelliteWakeWordSelect(
EsphomeAssistEntity, SelectEntity, restore_state.RestoreEntity
):
"""Wake word selector for esphome devices."""
entity_description = SelectEntityDescription(
key="wake_word",
translation_key="wake_word",
entity_category=EntityCategory.CONFIG,
)
_attr_should_poll = False
_attr_current_option: str | None = None
_attr_options: list[str] = []
def __init__(self, hass: HomeAssistant, entry_data: RuntimeEntryData) -> None:
"""Initialize a wake word selector."""
EsphomeAssistEntity.__init__(self, entry_data)
unique_id_prefix = self._device_info.mac_address
self._attr_unique_id = f"{unique_id_prefix}-wake_word"
# name -> id
self._wake_words: dict[str, str] = {}
@property
def available(self) -> bool:
"""Return if entity is available."""
return bool(self._attr_options)
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
# Update options when config is updated
self.async_on_remove(
self._entry_data.async_register_assist_satellite_config_updated_callback(
self.async_satellite_config_updated
)
)
async def async_select_option(self, option: str) -> None:
"""Select an option."""
if wake_word_id := self._wake_words.get(option):
# _attr_current_option will be updated on
# async_satellite_config_updated after the device sets the wake
# word.
self._entry_data.async_assist_satellite_set_wake_word(wake_word_id)
def async_satellite_config_updated(
self, config: AssistSatelliteConfiguration
) -> None:
"""Update options with available wake words."""
if (not config.available_wake_words) or (config.max_active_wake_words < 1):
self._attr_current_option = None
self._wake_words.clear()
self.async_write_ha_state()
return
self._wake_words = {w.wake_word: w.id for w in config.available_wake_words}
self._attr_options = sorted(self._wake_words)
if config.active_wake_words:
# Select first active wake word
wake_word_id = config.active_wake_words[0]
for wake_word in config.available_wake_words:
if wake_word.id == wake_word_id:
self._attr_current_option = wake_word.wake_word
else:
# Select first available wake word
self._attr_current_option = config.available_wake_words[0].wake_word
self.async_write_ha_state()

View File

@@ -84,6 +84,12 @@
"aggressive": "[%key:component::assist_pipeline::entity::select::vad_sensitivity::state::aggressive%]",
"relaxed": "[%key:component::assist_pipeline::entity::select::vad_sensitivity::state::relaxed%]"
}
},
"wake_word": {
"name": "Wake word",
"state": {
"okay_nabu": "Okay Nabu"
}
}
},
"climate": {

View File

@@ -23,12 +23,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
@@ -61,21 +55,6 @@ class FanEntityFeature(IntFlag):
TURN_ON = 32
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the FanEntityFeature enum instead.
_DEPRECATED_SUPPORT_SET_SPEED = DeprecatedConstantEnum(
FanEntityFeature.SET_SPEED, "2025.1"
)
_DEPRECATED_SUPPORT_OSCILLATE = DeprecatedConstantEnum(
FanEntityFeature.OSCILLATE, "2025.1"
)
_DEPRECATED_SUPPORT_DIRECTION = DeprecatedConstantEnum(
FanEntityFeature.DIRECTION, "2025.1"
)
_DEPRECATED_SUPPORT_PRESET_MODE = DeprecatedConstantEnum(
FanEntityFeature.PRESET_MODE, "2025.1"
)
SERVICE_INCREASE_SPEED = "increase_speed"
SERVICE_DECREASE_SPEED = "decrease_speed"
SERVICE_OSCILLATE = "oscillate"
@@ -543,11 +522,3 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
Requires FanEntityFeature.SET_SPEED.
"""
return self._attr_preset_modes
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = ft.partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

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

View File

@@ -9,7 +9,6 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
@@ -17,24 +16,23 @@ PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
type GaragesAmsterdamConfigEntry = ConfigEntry[GaragesAmsterdamDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: GaragesAmsterdamConfigEntry
) -> bool:
"""Set up Garages Amsterdam from a config entry."""
client = ODPAmsterdam(session=async_get_clientsession(hass))
coordinator = GaragesAmsterdamDataUpdateCoordinator(hass, client)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: GaragesAmsterdamConfigEntry
) -> bool:
"""Unload Garages Amsterdam config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if len(hass.config_entries.async_entries(DOMAIN)) == 1:
hass.data.pop(DOMAIN)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -2,48 +2,77 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from odp_amsterdam import Garage
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from . import GaragesAmsterdamConfigEntry
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
from .entity import GaragesAmsterdamEntity
BINARY_SENSORS = {
"state",
}
@dataclass(frozen=True, kw_only=True)
class GaragesAmsterdamBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Class describing Garages Amsterdam binary sensor entity."""
is_on: Callable[[Garage], bool]
BINARY_SENSORS: tuple[GaragesAmsterdamBinarySensorEntityDescription, ...] = (
GaragesAmsterdamBinarySensorEntityDescription(
key="state",
translation_key="state",
device_class=BinarySensorDeviceClass.PROBLEM,
is_on=lambda garage: garage.state != "ok",
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: GaragesAmsterdamConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Defer sensor setup to the shared sensor module."""
coordinator: GaragesAmsterdamDataUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
]
coordinator = entry.runtime_data
async_add_entities(
GaragesAmsterdamBinarySensor(coordinator, entry.data["garage_name"], info_type)
for info_type in BINARY_SENSORS
GaragesAmsterdamBinarySensor(
coordinator=coordinator,
garage_name=entry.data["garage_name"],
description=description,
)
for description in BINARY_SENSORS
)
class GaragesAmsterdamBinarySensor(GaragesAmsterdamEntity, BinarySensorEntity):
"""Binary Sensor representing garages amsterdam data."""
_attr_device_class = BinarySensorDeviceClass.PROBLEM
_attr_name = None
entity_description: GaragesAmsterdamBinarySensorEntityDescription
def __init__(
self,
*,
coordinator: GaragesAmsterdamDataUpdateCoordinator,
garage_name: str,
description: GaragesAmsterdamBinarySensorEntityDescription,
) -> None:
"""Initialize garages amsterdam binary sensor."""
super().__init__(coordinator, garage_name)
self.entity_description = description
self._attr_unique_id = f"{garage_name}-{description.key}"
@property
def is_on(self) -> bool:
"""If the binary sensor is currently on or off."""
return (
getattr(self.coordinator.data[self._garage_name], self._info_type) != "ok"
)
return self.entity_description.is_on(self.coordinator.data[self._garage_name])

View File

@@ -7,7 +7,7 @@ import logging
from typing import Final
DOMAIN: Final = "garages_amsterdam"
ATTRIBUTION = f'{"Data provided by municipality of Amsterdam"}'
ATTRIBUTION = "Data provided by municipality of Amsterdam"
LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(minutes=10)

View File

@@ -19,13 +19,10 @@ class GaragesAmsterdamEntity(CoordinatorEntity[GaragesAmsterdamDataUpdateCoordin
self,
coordinator: GaragesAmsterdamDataUpdateCoordinator,
garage_name: str,
info_type: str,
) -> None:
"""Initialize garages amsterdam entity."""
super().__init__(coordinator)
self._attr_unique_id = f"{garage_name}-{info_type}"
self._garage_name = garage_name
self._info_type = info_type
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, garage_name)},
name=garage_name,

View File

@@ -2,54 +2,93 @@
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from collections.abc import Callable
from dataclasses import dataclass
from odp_amsterdam import Garage
from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from . import GaragesAmsterdamConfigEntry
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
from .entity import GaragesAmsterdamEntity
SENSORS = {
"free_space_short",
"free_space_long",
"short_capacity",
"long_capacity",
}
@dataclass(frozen=True, kw_only=True)
class GaragesAmsterdamSensorEntityDescription(SensorEntityDescription):
"""Class describing Garages Amsterdam sensor entity."""
value_fn: Callable[[Garage], StateType]
SENSORS: tuple[GaragesAmsterdamSensorEntityDescription, ...] = (
GaragesAmsterdamSensorEntityDescription(
key="free_space_short",
translation_key="free_space_short",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda garage: garage.free_space_short,
),
GaragesAmsterdamSensorEntityDescription(
key="free_space_long",
translation_key="free_space_long",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda garage: garage.free_space_long,
),
GaragesAmsterdamSensorEntityDescription(
key="short_capacity",
translation_key="short_capacity",
value_fn=lambda garage: garage.short_capacity,
),
GaragesAmsterdamSensorEntityDescription(
key="long_capacity",
translation_key="long_capacity",
value_fn=lambda garage: garage.long_capacity,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: GaragesAmsterdamConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Defer sensor setup to the shared sensor module."""
coordinator: GaragesAmsterdamDataUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
]
coordinator = entry.runtime_data
async_add_entities(
GaragesAmsterdamSensor(coordinator, entry.data["garage_name"], info_type)
for info_type in SENSORS
if getattr(coordinator.data[entry.data["garage_name"]], info_type) != ""
GaragesAmsterdamSensor(
coordinator=coordinator,
garage_name=entry.data["garage_name"],
description=description,
)
for description in SENSORS
if description.value_fn(coordinator.data[entry.data["garage_name"]]) is not None
)
class GaragesAmsterdamSensor(GaragesAmsterdamEntity, SensorEntity):
"""Sensor representing garages amsterdam data."""
_attr_native_unit_of_measurement = "cars"
entity_description: GaragesAmsterdamSensorEntityDescription
def __init__(
self,
*,
coordinator: GaragesAmsterdamDataUpdateCoordinator,
garage_name: str,
info_type: str,
description: GaragesAmsterdamSensorEntityDescription,
) -> None:
"""Initialize garages amsterdam sensor."""
super().__init__(coordinator, garage_name, info_type)
self._attr_translation_key = info_type
super().__init__(coordinator, garage_name)
self.entity_description = description
self._attr_unique_id = f"{garage_name}-{description.key}"
@property
def available(self) -> bool:
@@ -59,6 +98,8 @@ class GaragesAmsterdamSensor(GaragesAmsterdamEntity, SensorEntity):
)
@property
def native_value(self) -> str:
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return getattr(self.coordinator.data[self._garage_name], self._info_type)
return self.entity_description.value_fn(
self.coordinator.data[self._garage_name]
)

View File

@@ -3,8 +3,13 @@
"config": {
"step": {
"user": {
"title": "Pick a garage to monitor",
"data": { "garage_name": "Garage name" }
"description": "Select a garage from the list",
"data": {
"garage_name": "Garage name"
},
"data_description": {
"garage_name": "The name of the garage you want to monitor."
}
}
},
"abort": {
@@ -16,16 +21,25 @@
"entity": {
"sensor": {
"free_space_short": {
"name": "Short parking free space"
"name": "Short parking free space",
"unit_of_measurement": "cars"
},
"free_space_long": {
"name": "Long parking free space"
"name": "Long parking free space",
"unit_of_measurement": "cars"
},
"short_capacity": {
"name": "Short parking capacity"
"name": "Short parking capacity",
"unit_of_measurement": "cars"
},
"long_capacity": {
"name": "Long parking capacity"
"name": "Long parking capacity",
"unit_of_measurement": "cars"
}
},
"binary_sensor": {
"state": {
"name": "State"
}
}
}

View File

@@ -33,7 +33,7 @@ class HabiticaButtonEntityDescription(ButtonEntityDescription):
"""Describes Habitica button entity."""
press_fn: Callable[[HabiticaDataUpdateCoordinator], Any]
available_fn: Callable[[HabiticaData], bool] | None = None
available_fn: Callable[[HabiticaData], bool]
class_needed: str | None = None
entity_picture: str | None = None
@@ -343,11 +343,10 @@ class HabiticaButton(HabiticaBase, ButtonEntity):
@property
def available(self) -> bool:
"""Is entity available."""
if not super().available:
return False
if self.entity_description.available_fn:
return self.entity_description.available_fn(self.coordinator.data)
return True
return super().available and self.entity_description.available_fn(
self.coordinator.data
)
@property
def entity_picture(self) -> str | None:

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from abc import abstractmethod
from datetime import date, datetime, timedelta
from enum import StrEnum
@@ -60,6 +61,43 @@ class HabiticaCalendarEntity(HabiticaBase, CalendarEntity):
"""Initialize calendar entity."""
super().__init__(coordinator, self.entity_description)
@abstractmethod
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Return events."""
@property
def event(self) -> CalendarEvent | None:
"""Return the current or next upcoming event."""
return next(iter(self.get_events(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.get_events(start_date, end_date)
@property
def start_of_today(self) -> datetime:
"""Habitica daystart."""
return dt_util.start_of_local_day(
datetime.fromisoformat(self.coordinator.data.user["lastCron"])
)
def get_recurrence_dates(
self, recurrences: rrule, start_date: datetime, end_date: datetime | None = None
) -> list[datetime]:
"""Calculate recurrence dates based on start_date and end_date."""
if end_date:
return recurrences.between(
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(start_date, inc=True)]
class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
"""Habitica todos calendar entity."""
@@ -69,7 +107,7 @@ class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
translation_key=HabiticaCalendar.TODOS,
)
def dated_todos(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Get all dated todos."""
@@ -112,18 +150,6 @@ class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
),
)
@property
def event(self) -> CalendarEvent | None:
"""Return the current or next upcoming event."""
return next(iter(self.dated_todos(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.dated_todos(start_date, end_date)
class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
"""Habitica dailies calendar entity."""
@@ -133,13 +159,6 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
translation_key=HabiticaCalendar.DAILIES,
)
@property
def today(self) -> datetime:
"""Habitica daystart."""
return dt_util.start_of_local_day(
datetime.fromisoformat(self.coordinator.data.user["lastCron"])
)
def end_date(self, recurrence: datetime, end: datetime | None = None) -> date:
"""Calculate the end date for a yesterdaily.
@@ -152,29 +171,20 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
if end:
return recurrence.date() + timedelta(days=1)
return (
dt_util.start_of_local_day() if recurrence == self.today else recurrence
dt_util.start_of_local_day()
if recurrence == self.start_of_today
else recurrence
).date() + timedelta(days=1)
def get_recurrence_dates(
self, recurrences: rrule, start_date: datetime, end_date: datetime | None = None
) -> list[datetime]:
"""Calculate recurrence dates based on start_date and end_date."""
if end_date:
return recurrences.between(
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(self.today, inc=True)]
def due_dailies(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Get dailies and recurrences for a given period or the next upcoming."""
# we only have dailies for today and future recurrences
if end_date and end_date < self.today:
if end_date and end_date < self.start_of_today:
return []
start_date = max(start_date, self.today)
start_date = max(start_date, self.start_of_today)
events = []
for task in self.coordinator.data.tasks:
@@ -187,10 +197,12 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
recurrences, start_date, end_date
)
for recurrence in recurrence_dates:
is_future_event = recurrence > self.today
is_current_event = recurrence <= self.today and not task["completed"]
is_future_event = recurrence > self.start_of_today
is_current_event = (
recurrence <= self.start_of_today and not task["completed"]
)
if not (is_future_event or is_current_event):
if not is_future_event and not is_current_event:
continue
events.append(
@@ -214,20 +226,15 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return next(iter(self.due_dailies(self.today)), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.due_dailies(start_date, end_date)
return next(iter(self.get_events(self.start_of_today)), None)
@property
def extra_state_attributes(self) -> dict[str, bool | None] | None:
"""Return entity specific state attributes."""
return {
"yesterdaily": self.event.start < self.today.date() if self.event else None
"yesterdaily": self.event.start < self.start_of_today.date()
if self.event
else None
}
@@ -239,7 +246,7 @@ class HabiticaTodoRemindersCalendarEntity(HabiticaCalendarEntity):
translation_key=HabiticaCalendar.TODO_REMINDERS,
)
def reminders(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Reminders for todos."""
@@ -282,18 +289,6 @@ class HabiticaTodoRemindersCalendarEntity(HabiticaCalendarEntity):
key=lambda event: event.start,
)
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return next(iter(self.reminders(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.reminders(start_date, end_date)
class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
"""Habitica daily reminders calendar entity."""
@@ -321,47 +316,31 @@ class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
tzinfo=dt_util.DEFAULT_TIME_ZONE,
)
@property
def today(self) -> datetime:
"""Habitica daystart."""
return dt_util.start_of_local_day(
datetime.fromisoformat(self.coordinator.data.user["lastCron"])
)
def get_recurrence_dates(
self, recurrences: rrule, start_date: datetime, end_date: datetime | None = None
) -> list[datetime]:
"""Calculate recurrence dates based on start_date and end_date."""
if end_date:
return recurrences.between(
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(self.today, inc=True)]
def reminders(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Reminders for dailies."""
events = []
if end_date and end_date < self.today:
if end_date and end_date < self.start_of_today:
return []
start_date = max(start_date, self.today)
start_date = max(start_date, self.start_of_today)
for task in self.coordinator.data.tasks:
if not (task["type"] == HabiticaTaskType.DAILY and task["everyX"]):
continue
recurrences = build_rrule(task)
recurrences_start = self.today
recurrences_start = self.start_of_today
recurrence_dates = self.get_recurrence_dates(
recurrences, recurrences_start, end_date
)
for recurrence in recurrence_dates:
is_future_event = recurrence > self.today
is_current_event = recurrence <= self.today and not task["completed"]
is_future_event = recurrence > self.start_of_today
is_current_event = (
recurrence <= self.start_of_today and not task["completed"]
)
if not is_future_event and not is_current_event:
continue
@@ -374,9 +353,6 @@ class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
# Event ends before date range
continue
if end_date and start > end_date:
# Event starts after date range
continue
events.append(
CalendarEvent(
start=start,
@@ -391,15 +367,3 @@ class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
events,
key=lambda event: event.start,
)
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return next(iter(self.reminders(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.reminders(start_date, end_date)

View File

@@ -25,8 +25,6 @@ ATTR_DATA = "data"
MANUFACTURER = "HabitRPG, Inc."
NAME = "Habitica"
UNIT_TASKS = "tasks"
ATTR_CONFIG_ENTRY = "config_entry"
ATTR_SKILL = "skill"
ATTR_TASK = "task"

View File

@@ -0,0 +1,27 @@
"""Diagnostics platform for Habitica integration."""
from __future__ import annotations
from typing import Any
from homeassistant.const import CONF_URL
from homeassistant.core import HomeAssistant
from .const import CONF_API_USER
from .types import HabiticaConfigEntry
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: HabiticaConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
habitica_data = await config_entry.runtime_data.api.user.anonymized.get()
return {
"config_entry_data": {
CONF_URL: config_entry.data[CONF_URL],
CONF_API_USER: config_entry.data[CONF_API_USER],
},
"habitica_data": habitica_data,
}

View File

@@ -24,7 +24,7 @@ from homeassistant.helpers.issue_registry import (
)
from homeassistant.helpers.typing import StateType
from .const import ASSETS_URL, DOMAIN, UNIT_TASKS
from .const import ASSETS_URL, DOMAIN
from .entity import HabiticaBase
from .types import HabiticaConfigEntry
from .util import entity_used_in, get_attribute_points, get_attributes_total
@@ -84,40 +84,34 @@ SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
HabitipySensorEntityDescription(
key=HabitipySensorEntity.HEALTH,
translation_key=HabitipySensorEntity.HEALTH,
native_unit_of_measurement="HP",
suggested_display_precision=0,
value_fn=lambda user, _: user.get("stats", {}).get("hp"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.HEALTH_MAX,
translation_key=HabitipySensorEntity.HEALTH_MAX,
native_unit_of_measurement="HP",
entity_registry_enabled_default=False,
value_fn=lambda user, _: user.get("stats", {}).get("maxHealth"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.MANA,
translation_key=HabitipySensorEntity.MANA,
native_unit_of_measurement="MP",
suggested_display_precision=0,
value_fn=lambda user, _: user.get("stats", {}).get("mp"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.MANA_MAX,
translation_key=HabitipySensorEntity.MANA_MAX,
native_unit_of_measurement="MP",
value_fn=lambda user, _: user.get("stats", {}).get("maxMP"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.EXPERIENCE,
translation_key=HabitipySensorEntity.EXPERIENCE,
native_unit_of_measurement="XP",
value_fn=lambda user, _: user.get("stats", {}).get("exp"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.EXPERIENCE_MAX,
translation_key=HabitipySensorEntity.EXPERIENCE_MAX,
native_unit_of_measurement="XP",
value_fn=lambda user, _: user.get("stats", {}).get("toNextLevel"),
),
HabitipySensorEntityDescription(
@@ -128,7 +122,6 @@ SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
HabitipySensorEntityDescription(
key=HabitipySensorEntity.GOLD,
translation_key=HabitipySensorEntity.GOLD,
native_unit_of_measurement="GP",
suggested_display_precision=2,
value_fn=lambda user, _: user.get("stats", {}).get("gp"),
),
@@ -144,7 +137,6 @@ SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
translation_key=HabitipySensorEntity.GEMS,
value_fn=lambda user, _: user.get("balance", 0) * 4,
suggested_display_precision=0,
native_unit_of_measurement="gems",
entity_picture="shop_gem.png",
),
HabitipySensorEntityDescription(
@@ -229,20 +221,17 @@ TASK_SENSOR_DESCRIPTION: tuple[HabitipyTaskSensorEntityDescription, ...] = (
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.HABITS,
translation_key=HabitipySensorEntity.HABITS,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "habit"],
),
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.DAILIES,
translation_key=HabitipySensorEntity.DAILIES,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "daily"],
entity_registry_enabled_default=False,
),
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.TODOS,
translation_key=HabitipySensorEntity.TODOS,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [
r for r in tasks if r.get("type") == "todo" and not r.get("completed")
],
@@ -251,7 +240,6 @@ TASK_SENSOR_DESCRIPTION: tuple[HabitipyTaskSensorEntityDescription, ...] = (
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.REWARDS,
translation_key=HabitipySensorEntity.REWARDS,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "reward"],
),
)

View File

@@ -2,7 +2,11 @@
"common": {
"todos": "To-Do's",
"dailies": "Dailies",
"config_entry_name": "Select character"
"config_entry_name": "Select character",
"unit_tasks": "tasks",
"unit_health_points": "HP",
"unit_mana_points": "MP",
"unit_experience_points": "XP"
},
"config": {
"abort": {
@@ -135,31 +139,39 @@
"name": "Display name"
},
"health": {
"name": "Health"
"name": "Health",
"unit_of_measurement": "[%key:component::habitica::common::unit_health_points%]"
},
"health_max": {
"name": "Max. health"
"name": "Max. health",
"unit_of_measurement": "[%key:component::habitica::common::unit_health_points%]"
},
"mana": {
"name": "Mana"
"name": "Mana",
"unit_of_measurement": "[%key:component::habitica::common::unit_mana_points%]"
},
"mana_max": {
"name": "Max. mana"
"name": "Max. mana",
"unit_of_measurement": "[%key:component::habitica::common::unit_mana_points%]"
},
"experience": {
"name": "Experience"
"name": "Experience",
"unit_of_measurement": "[%key:component::habitica::common::unit_experience_points%]"
},
"experience_max": {
"name": "Next level"
"name": "Next level",
"unit_of_measurement": "[%key:component::habitica::common::unit_experience_points%]"
},
"level": {
"name": "Level"
},
"gold": {
"name": "Gold"
"name": "Gold",
"unit_of_measurement": "GP"
},
"gems": {
"name": "Gems"
"name": "Gems",
"unit_of_measurement": "gems"
},
"trinkets": {
"name": "Mystic hourglasses"
@@ -174,16 +186,20 @@
}
},
"todos": {
"name": "[%key:component::habitica::common::todos%]"
"name": "[%key:component::habitica::common::todos%]",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"dailys": {
"name": "[%key:component::habitica::common::dailies%]"
"name": "[%key:component::habitica::common::dailies%]",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"habits": {
"name": "Habits"
"name": "Habits",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"rewards": {
"name": "Rewards"
"name": "Rewards",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"strength": {
"name": "Strength",

View File

@@ -174,7 +174,7 @@ def get_attribute_points(
)
return {
"level": min(round(user["stats"]["lvl"] / 2), 50),
"level": min(floor(user["stats"]["lvl"] / 2), 50),
"equipment": equipment,
"class": class_bonus,
"allocated": user["stats"][attribute],

View File

@@ -22,7 +22,7 @@ import homeassistant.util.dt as dt_util
from . import websocket_api
from .const import DOMAIN
from .helpers import entities_may_have_state_changes_after, has_recorder_run_after
from .helpers import entities_may_have_state_changes_after, has_states_before
CONF_ORDER = "use_include_order"
@@ -107,7 +107,10 @@ class HistoryPeriodView(HomeAssistantView):
no_attributes = "no_attributes" in request.query
if (
(end_time and not has_recorder_run_after(hass, end_time))
# has_states_before will return True if there are states older than
# end_time. If it's false, we know there are no states in the
# database up until end_time.
(end_time and not has_states_before(hass, end_time))
or not include_start_time_state
and entity_ids
and not entities_may_have_state_changes_after(

View File

@@ -6,7 +6,6 @@ from collections.abc import Iterable
from datetime import datetime as dt
from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.models import process_timestamp
from homeassistant.core import HomeAssistant
@@ -26,8 +25,10 @@ def entities_may_have_state_changes_after(
return False
def has_recorder_run_after(hass: HomeAssistant, run_time: dt) -> bool:
"""Check if the recorder has any runs after a specific time."""
return run_time >= process_timestamp(
get_instance(hass).recorder_runs_manager.first.start
)
def has_states_before(hass: HomeAssistant, run_time: dt) -> bool:
"""Check if the recorder has states as old or older than run_time.
Returns True if there may be such states.
"""
oldest_ts = get_instance(hass).states_manager.oldest_ts
return oldest_ts is not None and run_time.timestamp() >= oldest_ts

View File

@@ -39,7 +39,7 @@ from homeassistant.util.async_ import create_eager_task
import homeassistant.util.dt as dt_util
from .const import EVENT_COALESCE_TIME, MAX_PENDING_HISTORY_STATES
from .helpers import entities_may_have_state_changes_after, has_recorder_run_after
from .helpers import entities_may_have_state_changes_after, has_states_before
_LOGGER = logging.getLogger(__name__)
@@ -142,7 +142,10 @@ async def ws_get_history_during_period(
no_attributes = msg["no_attributes"]
if (
(end_time and not has_recorder_run_after(hass, end_time))
# has_states_before will return True if there are states older than
# end_time. If it's false, we know there are no states in the
# database up until end_time.
(end_time and not has_states_before(hass, end_time))
or not include_start_time_state
and entity_ids
and not entities_may_have_state_changes_after(

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
import re
from typing import Any, cast
from requests import HTTPError
@@ -44,6 +45,8 @@ type HomeConnectConfigEntry = ConfigEntry[api.ConfigEntryAuth]
_LOGGER = logging.getLogger(__name__)
RE_CAMEL_CASE = re.compile(r"(?<!^)(?=[A-Z])|(?=\d)(?<=\D)")
SCAN_INTERVAL = timedelta(minutes=1)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -85,6 +88,7 @@ PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.LIGHT,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.TIME,
@@ -336,3 +340,14 @@ def get_dict_from_home_connect_error(err: api.HomeConnectError) -> dict[str, Any
if len(err.args) > 0 and isinstance(err.args[0], str)
else "?",
}
def bsh_key_to_translation_key(bsh_key: str) -> str:
"""Convert a BSH key to a translation key format.
This function takes a BSH key, such as `Dishcare.Dishwasher.Program.Eco50`,
and converts it to a translation key format, such as `dishcare_dishwasher_bsh_key_eco50`.
"""
return "_".join(
RE_CAMEL_CASE.sub("_", split) for split in bsh_key.split(".")
).lower()

View File

@@ -5,10 +5,23 @@ DOMAIN = "home_connect"
OAUTH2_AUTHORIZE = "https://api.home-connect.com/security/oauth/authorize"
OAUTH2_TOKEN = "https://api.home-connect.com/security/oauth/token"
APPLIANCES_WITH_PROGRAMS = (
"CleaningRobot",
"CoffeeMaker",
"Dishwasher",
"Dryer",
"Hood",
"Oven",
"WarmingDrawer",
"Washer",
"WasherDryer",
)
BSH_POWER_STATE = "BSH.Common.Setting.PowerState"
BSH_POWER_ON = "BSH.Common.EnumType.PowerState.On"
BSH_POWER_OFF = "BSH.Common.EnumType.PowerState.Off"
BSH_POWER_STANDBY = "BSH.Common.EnumType.PowerState.Standby"
BSH_SELECTED_PROGRAM = "BSH.Common.Root.SelectedProgram"
BSH_ACTIVE_PROGRAM = "BSH.Common.Root.ActiveProgram"
BSH_REMOTE_CONTROL_ACTIVATION_STATE = "BSH.Common.Status.RemoteControlActive"
BSH_REMOTE_START_ALLOWANCE_STATE = "BSH.Common.Status.RemoteControlStartAllowed"

View File

@@ -16,7 +16,7 @@ from homeassistant.components.light import (
LightEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.util.color as color_util
@@ -150,7 +150,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self.device.appliance.set_setting, self.bsh_key, True
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_light",
translation_placeholders={
@@ -169,7 +169,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self._enable_custom_color_value_key,
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="select_light_custom_color",
translation_placeholders={
@@ -187,7 +187,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
f"#{hex_val}",
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_color",
translation_placeholders={
@@ -219,7 +219,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
f"#{hex_val}",
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_color",
translation_placeholders={
@@ -244,7 +244,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self.device.appliance.set_setting, self._brightness_key, brightness
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_brightness",
translation_placeholders={
@@ -263,7 +263,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self.device.appliance.set_setting, self.bsh_key, False
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off_light",
translation_placeholders={

View File

@@ -12,7 +12,7 @@ from homeassistant.components.number import (
NumberEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
@@ -117,7 +117,7 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
value,
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting",
translation_placeholders={

View File

@@ -0,0 +1,300 @@
"""Provides a select platform for Home Connect."""
import contextlib
import logging
from homeconnect.api import HomeConnectError
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import (
HomeConnectConfigEntry,
bsh_key_to_translation_key,
get_dict_from_home_connect_error,
)
from .api import HomeConnectDevice
from .const import (
APPLIANCES_WITH_PROGRAMS,
ATTR_VALUE,
BSH_ACTIVE_PROGRAM,
BSH_SELECTED_PROGRAM,
DOMAIN,
)
from .entity import HomeConnectEntity
_LOGGER = logging.getLogger(__name__)
TRANSLATION_KEYS_PROGRAMS_MAP = {
bsh_key_to_translation_key(program): program
for program in (
"ConsumerProducts.CleaningRobot.Program.Cleaning.CleanAll",
"ConsumerProducts.CleaningRobot.Program.Cleaning.CleanMap",
"ConsumerProducts.CleaningRobot.Program.Basic.GoHome",
"ConsumerProducts.CoffeeMaker.Program.Beverage.Ristretto",
"ConsumerProducts.CoffeeMaker.Program.Beverage.Espresso",
"ConsumerProducts.CoffeeMaker.Program.Beverage.EspressoDoppio",
"ConsumerProducts.CoffeeMaker.Program.Beverage.Coffee",
"ConsumerProducts.CoffeeMaker.Program.Beverage.XLCoffee",
"ConsumerProducts.CoffeeMaker.Program.Beverage.CaffeGrande",
"ConsumerProducts.CoffeeMaker.Program.Beverage.EspressoMacchiato",
"ConsumerProducts.CoffeeMaker.Program.Beverage.Cappuccino",
"ConsumerProducts.CoffeeMaker.Program.Beverage.LatteMacchiato",
"ConsumerProducts.CoffeeMaker.Program.Beverage.CaffeLatte",
"ConsumerProducts.CoffeeMaker.Program.Beverage.MilkFroth",
"ConsumerProducts.CoffeeMaker.Program.Beverage.WarmMilk",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.KleinerBrauner",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.GrosserBrauner",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Verlaengerter",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.VerlaengerterBraun",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.WienerMelange",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.FlatWhite",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Cortado",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.CafeCortado",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.CafeConLeche",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.CafeAuLait",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Doppio",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Kaapi",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.KoffieVerkeerd",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Galao",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Garoto",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.Americano",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.RedEye",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.BlackEye",
"ConsumerProducts.CoffeeMaker.Program.CoffeeWorld.DeadEye",
"ConsumerProducts.CoffeeMaker.Program.Beverage.HotWater",
"Dishcare.Dishwasher.Program.PreRinse",
"Dishcare.Dishwasher.Program.Auto1",
"Dishcare.Dishwasher.Program.Auto2",
"Dishcare.Dishwasher.Program.Auto3",
"Dishcare.Dishwasher.Program.Eco50",
"Dishcare.Dishwasher.Program.Quick45",
"Dishcare.Dishwasher.Program.Intensiv70",
"Dishcare.Dishwasher.Program.Normal65",
"Dishcare.Dishwasher.Program.Glas40",
"Dishcare.Dishwasher.Program.GlassCare",
"Dishcare.Dishwasher.Program.NightWash",
"Dishcare.Dishwasher.Program.Quick65",
"Dishcare.Dishwasher.Program.Normal45",
"Dishcare.Dishwasher.Program.Intensiv45",
"Dishcare.Dishwasher.Program.AutoHalfLoad",
"Dishcare.Dishwasher.Program.IntensivPower",
"Dishcare.Dishwasher.Program.MagicDaily",
"Dishcare.Dishwasher.Program.Super60",
"Dishcare.Dishwasher.Program.Kurz60",
"Dishcare.Dishwasher.Program.ExpressSparkle65",
"Dishcare.Dishwasher.Program.MachineCare",
"Dishcare.Dishwasher.Program.SteamFresh",
"Dishcare.Dishwasher.Program.MaximumCleaning",
"Dishcare.Dishwasher.Program.MixedLoad",
"LaundryCare.Dryer.Program.Cotton",
"LaundryCare.Dryer.Program.Synthetic",
"LaundryCare.Dryer.Program.Mix",
"LaundryCare.Dryer.Program.Blankets",
"LaundryCare.Dryer.Program.BusinessShirts",
"LaundryCare.Dryer.Program.DownFeathers",
"LaundryCare.Dryer.Program.Hygiene",
"LaundryCare.Dryer.Program.Jeans",
"LaundryCare.Dryer.Program.Outdoor",
"LaundryCare.Dryer.Program.SyntheticRefresh",
"LaundryCare.Dryer.Program.Towels",
"LaundryCare.Dryer.Program.Delicates",
"LaundryCare.Dryer.Program.Super40",
"LaundryCare.Dryer.Program.Shirts15",
"LaundryCare.Dryer.Program.Pillow",
"LaundryCare.Dryer.Program.AntiShrink",
"LaundryCare.Dryer.Program.MyTime.MyDryingTime",
"LaundryCare.Dryer.Program.TimeCold",
"LaundryCare.Dryer.Program.TimeWarm",
"LaundryCare.Dryer.Program.InBasket",
"LaundryCare.Dryer.Program.TimeColdFix.TimeCold20",
"LaundryCare.Dryer.Program.TimeColdFix.TimeCold30",
"LaundryCare.Dryer.Program.TimeColdFix.TimeCold60",
"LaundryCare.Dryer.Program.TimeWarmFix.TimeWarm30",
"LaundryCare.Dryer.Program.TimeWarmFix.TimeWarm40",
"LaundryCare.Dryer.Program.TimeWarmFix.TimeWarm60",
"LaundryCare.Dryer.Program.Dessous",
"Cooking.Common.Program.Hood.Automatic",
"Cooking.Common.Program.Hood.Venting",
"Cooking.Common.Program.Hood.DelayedShutOff",
"Cooking.Oven.Program.HeatingMode.PreHeating",
"Cooking.Oven.Program.HeatingMode.HotAir",
"Cooking.Oven.Program.HeatingMode.HotAirEco",
"Cooking.Oven.Program.HeatingMode.HotAirGrilling",
"Cooking.Oven.Program.HeatingMode.TopBottomHeating",
"Cooking.Oven.Program.HeatingMode.TopBottomHeatingEco",
"Cooking.Oven.Program.HeatingMode.BottomHeating",
"Cooking.Oven.Program.HeatingMode.PizzaSetting",
"Cooking.Oven.Program.HeatingMode.SlowCook",
"Cooking.Oven.Program.HeatingMode.IntensiveHeat",
"Cooking.Oven.Program.HeatingMode.KeepWarm",
"Cooking.Oven.Program.HeatingMode.PreheatOvenware",
"Cooking.Oven.Program.HeatingMode.FrozenHeatupSpecial",
"Cooking.Oven.Program.HeatingMode.Desiccation",
"Cooking.Oven.Program.HeatingMode.Defrost",
"Cooking.Oven.Program.HeatingMode.Proof",
"Cooking.Oven.Program.HeatingMode.HotAir30Steam",
"Cooking.Oven.Program.HeatingMode.HotAir60Steam",
"Cooking.Oven.Program.HeatingMode.HotAir80Steam",
"Cooking.Oven.Program.HeatingMode.HotAir100Steam",
"Cooking.Oven.Program.HeatingMode.SabbathProgramme",
"Cooking.Oven.Program.Microwave.90Watt",
"Cooking.Oven.Program.Microwave.180Watt",
"Cooking.Oven.Program.Microwave.360Watt",
"Cooking.Oven.Program.Microwave.600Watt",
"Cooking.Oven.Program.Microwave.900Watt",
"Cooking.Oven.Program.Microwave.1000Watt",
"Cooking.Oven.Program.Microwave.Max",
"Cooking.Oven.Program.HeatingMode.WarmingDrawer",
"LaundryCare.Washer.Program.Cotton",
"LaundryCare.Washer.Program.Cotton.CottonEco",
"LaundryCare.Washer.Program.Cotton.Eco4060",
"LaundryCare.Washer.Program.Cotton.Colour",
"LaundryCare.Washer.Program.EasyCare",
"LaundryCare.Washer.Program.Mix",
"LaundryCare.Washer.Program.Mix.NightWash",
"LaundryCare.Washer.Program.DelicatesSilk",
"LaundryCare.Washer.Program.Wool",
"LaundryCare.Washer.Program.Sensitive",
"LaundryCare.Washer.Program.Auto30",
"LaundryCare.Washer.Program.Auto40",
"LaundryCare.Washer.Program.Auto60",
"LaundryCare.Washer.Program.Chiffon",
"LaundryCare.Washer.Program.Curtains",
"LaundryCare.Washer.Program.DarkWash",
"LaundryCare.Washer.Program.Dessous",
"LaundryCare.Washer.Program.Monsoon",
"LaundryCare.Washer.Program.Outdoor",
"LaundryCare.Washer.Program.PlushToy",
"LaundryCare.Washer.Program.ShirtsBlouses",
"LaundryCare.Washer.Program.SportFitness",
"LaundryCare.Washer.Program.Towels",
"LaundryCare.Washer.Program.WaterProof",
"LaundryCare.Washer.Program.PowerSpeed59",
"LaundryCare.Washer.Program.Super153045.Super15",
"LaundryCare.Washer.Program.Super153045.Super1530",
"LaundryCare.Washer.Program.DownDuvet.Duvet",
"LaundryCare.Washer.Program.Rinse.RinseSpinDrain",
"LaundryCare.Washer.Program.DrumClean",
"LaundryCare.WasherDryer.Program.Cotton",
"LaundryCare.WasherDryer.Program.Cotton.Eco4060",
"LaundryCare.WasherDryer.Program.Mix",
"LaundryCare.WasherDryer.Program.EasyCare",
"LaundryCare.WasherDryer.Program.WashAndDry60",
"LaundryCare.WasherDryer.Program.WashAndDry90",
)
}
PROGRAMS_TRANSLATION_KEYS_MAP = {
value: key for key, value in TRANSLATION_KEYS_PROGRAMS_MAP.items()
}
PROGRAM_SELECT_ENTITY_DESCRIPTIONS = (
SelectEntityDescription(
key=BSH_ACTIVE_PROGRAM,
translation_key="active_program",
),
SelectEntityDescription(
key=BSH_SELECTED_PROGRAM,
translation_key="selected_program",
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: HomeConnectConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Home Connect select entities."""
def get_entities() -> list[HomeConnectProgramSelectEntity]:
"""Get a list of entities."""
entities: list[HomeConnectProgramSelectEntity] = []
programs_not_found = set()
for device in entry.runtime_data.devices:
if device.appliance.type in APPLIANCES_WITH_PROGRAMS:
with contextlib.suppress(HomeConnectError):
programs = device.appliance.get_programs_available()
if programs:
for program in programs:
if program not in PROGRAMS_TRANSLATION_KEYS_MAP:
programs.remove(program)
if program not in programs_not_found:
_LOGGER.info(
'The program "%s" is not part of the official Home Connect API specification',
program,
)
programs_not_found.add(program)
entities.extend(
HomeConnectProgramSelectEntity(device, programs, desc)
for desc in PROGRAM_SELECT_ENTITY_DESCRIPTIONS
)
return entities
async_add_entities(await hass.async_add_executor_job(get_entities), True)
class HomeConnectProgramSelectEntity(HomeConnectEntity, SelectEntity):
"""Select class for Home Connect programs."""
def __init__(
self,
device: HomeConnectDevice,
programs: list[str],
desc: SelectEntityDescription,
) -> None:
"""Initialize the entity."""
super().__init__(
device,
desc,
)
self._attr_options = [
PROGRAMS_TRANSLATION_KEYS_MAP[program] for program in programs
]
self.start_on_select = desc.key == BSH_ACTIVE_PROGRAM
async def async_update(self) -> None:
"""Update the program selection status."""
program = self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE)
if not program:
program_translation_key = None
elif not (
program_translation_key := PROGRAMS_TRANSLATION_KEYS_MAP.get(program)
):
_LOGGER.debug(
'The program "%s" is not part of the official Home Connect API specification',
program,
)
self._attr_current_option = program_translation_key
_LOGGER.debug("Updated, new program: %s", self._attr_current_option)
async def async_select_option(self, option: str) -> None:
"""Select new program."""
bsh_key = TRANSLATION_KEYS_PROGRAMS_MAP[option]
_LOGGER.debug(
"Starting program: %s" if self.start_on_select else "Selecting program: %s",
bsh_key,
)
if self.start_on_select:
target = self.device.appliance.start_program
else:
target = self.device.appliance.select_program
try:
await self.hass.async_add_executor_job(target, bsh_key)
except HomeConnectError as err:
if self.start_on_select:
translation_key = "start_program"
else:
translation_key = "select_program"
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key=translation_key,
translation_placeholders={
**get_dict_from_home_connect_error(err),
"program": bsh_key,
},
) from err
self.async_entity_update()

View File

@@ -23,40 +23,43 @@
},
"exceptions": {
"turn_on_light": {
"message": "Error while trying to turn on {entity_id}: {description}"
"message": "Error turning on {entity_id}: {description}"
},
"turn_off_light": {
"message": "Error while trying to turn off {entity_id}: {description}"
"message": "Error turning off {entity_id}: {description}"
},
"set_light_brightness": {
"message": "Error while trying to set brightness of {entity_id}: {description}"
"message": "Error setting brightness of {entity_id}: {description}"
},
"select_light_custom_color": {
"message": "Error while trying to select custom color of {entity_id}: {description}"
"message": "Error selecting custom color of {entity_id}: {description}"
},
"set_light_color": {
"message": "Error while trying to set color of {entity_id}: {description}"
"message": "Error setting color of {entity_id}: {description}"
},
"set_setting": {
"message": "Error while trying to assign the value \"{value}\" to the setting \"{setting_key}\" for {entity_id}: {description}"
"message": "Error assigning the value \"{value}\" to the setting \"{setting_key}\" for {entity_id}: {description}"
},
"turn_on": {
"message": "Error while trying to turn on {entity_id} ({setting_key}): {description}"
"message": "Error turning on {entity_id} ({setting_key}): {description}"
},
"turn_off": {
"message": "Error while trying to turn off {entity_id} ({setting_key}): {description}"
"message": "Error turning off {entity_id} ({setting_key}): {description}"
},
"select_program": {
"message": "Error selecting program {program}: {description}"
},
"start_program": {
"message": "Error while trying to start program {program}: {description}"
"message": "Error starting program {program}: {description}"
},
"stop_program": {
"message": "Error while trying to stop program {program}: {description}"
"message": "Error stopping program {program}: {description}"
},
"power_on": {
"message": "Error while trying to turn on {appliance_name}: {description}"
"message": "Error turning on {appliance_name}: {description}"
},
"power_off": {
"message": "Error while trying to turn off {appliance_name} with value \"{value}\": {description}"
"message": "Error turning off {appliance_name} with value \"{value}\": {description}"
},
"turn_off_not_supported": {
"message": "{appliance_name} does not support turning off or entering standby mode."
@@ -267,6 +270,326 @@
"name": "Wine compartment 3 temperature"
}
},
"select": {
"selected_program": {
"name": "Selected program",
"state": {
"consumer_products_cleaning_robot_program_cleaning_clean_all": "Clean all",
"consumer_products_cleaning_robot_program_cleaning_clean_map": "Clean map",
"consumer_products_cleaning_robot_program_basic_go_home": "Go home",
"consumer_products_coffee_maker_program_beverage_ristretto": "Ristretto",
"consumer_products_coffee_maker_program_beverage_espresso": "Espresso",
"consumer_products_coffee_maker_program_beverage_espresso_doppio": "Espresso doppio",
"consumer_products_coffee_maker_program_beverage_coffee": "Coffee",
"consumer_products_coffee_maker_program_beverage_x_l_coffee": "XL coffee",
"consumer_products_coffee_maker_program_beverage_caffe_grande": "Caffe grande",
"consumer_products_coffee_maker_program_beverage_espresso_macchiato": "Espresso macchiato",
"consumer_products_coffee_maker_program_beverage_cappuccino": "Cappuccino",
"consumer_products_coffee_maker_program_beverage_latte_macchiato": "Latte macchiato",
"consumer_products_coffee_maker_program_beverage_caffe_latte": "Caffe latte",
"consumer_products_coffee_maker_program_beverage_milk_froth": "Milk froth",
"consumer_products_coffee_maker_program_beverage_warm_milk": "Warm milk",
"consumer_products_coffee_maker_program_coffee_world_kleiner_brauner": "Kleiner brauner",
"consumer_products_coffee_maker_program_coffee_world_grosser_brauner": "Grosser brauner",
"consumer_products_coffee_maker_program_coffee_world_verlaengerter": "Verlaengerter",
"consumer_products_coffee_maker_program_coffee_world_verlaengerter_braun": "Verlaengerter braun",
"consumer_products_coffee_maker_program_coffee_world_wiener_melange": "Wiener melange",
"consumer_products_coffee_maker_program_coffee_world_flat_white": "Flat white",
"consumer_products_coffee_maker_program_coffee_world_cortado": "Cortado",
"consumer_products_coffee_maker_program_coffee_world_cafe_cortado": "Cafe cortado",
"consumer_products_coffee_maker_program_coffee_world_cafe_con_leche": "Cafe con leche",
"consumer_products_coffee_maker_program_coffee_world_cafe_au_lait": "Cafe au lait",
"consumer_products_coffee_maker_program_coffee_world_doppio": "Doppio",
"consumer_products_coffee_maker_program_coffee_world_kaapi": "Kaapi",
"consumer_products_coffee_maker_program_coffee_world_koffie_verkeerd": "Koffie verkeerd",
"consumer_products_coffee_maker_program_coffee_world_galao": "Galao",
"consumer_products_coffee_maker_program_coffee_world_garoto": "Garoto",
"consumer_products_coffee_maker_program_coffee_world_americano": "Americano",
"consumer_products_coffee_maker_program_coffee_world_red_eye": "Red eye",
"consumer_products_coffee_maker_program_coffee_world_black_eye": "Black eye",
"consumer_products_coffee_maker_program_coffee_world_dead_eye": "Dead eye",
"consumer_products_coffee_maker_program_beverage_hot_water": "Hot water",
"dishcare_dishwasher_program_pre_rinse": "Pre_rinse",
"dishcare_dishwasher_program_auto_1": "Auto 1",
"dishcare_dishwasher_program_auto_2": "Auto 2",
"dishcare_dishwasher_program_auto_3": "Auto 3",
"dishcare_dishwasher_program_eco_50": "Eco 50ºC",
"dishcare_dishwasher_program_quick_45": "Quick 45ºC",
"dishcare_dishwasher_program_intensiv_70": "Intensive 70ºC",
"dishcare_dishwasher_program_normal_65": "Normal 65ºC",
"dishcare_dishwasher_program_glas_40": "Glass 40ºC",
"dishcare_dishwasher_program_glass_care": "Glass care",
"dishcare_dishwasher_program_night_wash": "Night wash",
"dishcare_dishwasher_program_quick_65": "Quick 65ºC",
"dishcare_dishwasher_program_normal_45": "Normal 45ºC",
"dishcare_dishwasher_program_intensiv_45": "Intensive 45ºC",
"dishcare_dishwasher_program_auto_half_load": "Auto half load",
"dishcare_dishwasher_program_intensiv_power": "Intensive power",
"dishcare_dishwasher_program_magic_daily": "Magic daily",
"dishcare_dishwasher_program_super_60": "Super 60ºC",
"dishcare_dishwasher_program_kurz_60": "Kurz 60ºC",
"dishcare_dishwasher_program_express_sparkle_65": "Express sparkle 65ºC",
"dishcare_dishwasher_program_machine_care": "Machine care",
"dishcare_dishwasher_program_steam_fresh": "Steam fresh",
"dishcare_dishwasher_program_maximum_cleaning": "Maximum cleaning",
"dishcare_dishwasher_program_mixed_load": "Mixed load",
"laundry_care_dryer_program_cotton": "Cotton",
"laundry_care_dryer_program_synthetic": "Synthetic",
"laundry_care_dryer_program_mix": "Mix",
"laundry_care_dryer_program_blankets": "Blankets",
"laundry_care_dryer_program_business_shirts": "Business shirts",
"laundry_care_dryer_program_down_feathers": "Down feathers",
"laundry_care_dryer_program_hygiene": "Hygiene",
"laundry_care_dryer_program_jeans": "Jeans",
"laundry_care_dryer_program_outdoor": "Outdoor",
"laundry_care_dryer_program_synthetic_refresh": "Synthetic refresh",
"laundry_care_dryer_program_towels": "Towels",
"laundry_care_dryer_program_delicates": "Delicates",
"laundry_care_dryer_program_super_40": "Super 40ºC",
"laundry_care_dryer_program_shirts_15": "Shirts 15ºC",
"laundry_care_dryer_program_pillow": "Pillow",
"laundry_care_dryer_program_anti_shrink": "Anti shrink",
"laundry_care_dryer_program_my_time_my_drying_time": "My drying time",
"laundry_care_dryer_program_time_cold": "Cold (variable time)",
"laundry_care_dryer_program_time_warm": "Warm (variable time)",
"laundry_care_dryer_program_in_basket": "In basket",
"laundry_care_dryer_program_time_cold_fix_time_cold_20": "Cold (20 min)",
"laundry_care_dryer_program_time_cold_fix_time_cold_30": "Cold (30 min)",
"laundry_care_dryer_program_time_cold_fix_time_cold_60": "Cold (60 min)",
"laundry_care_dryer_program_time_warm_fix_time_warm_30": "Warm (30 min)",
"laundry_care_dryer_program_time_warm_fix_time_warm_40": "Warm (40 min)",
"laundry_care_dryer_program_time_warm_fix_time_warm_60": "Warm (60 min)",
"laundry_care_dryer_program_dessous": "Dessous",
"cooking_common_program_hood_automatic": "Automatic",
"cooking_common_program_hood_venting": "Venting",
"cooking_common_program_hood_delayed_shut_off": "Delayed shut off",
"cooking_oven_program_heating_mode_pre_heating": "Pre-heating",
"cooking_oven_program_heating_mode_hot_air": "Hot air",
"cooking_oven_program_heating_mode_hot_air_eco": "Hot air eco",
"cooking_oven_program_heating_mode_hot_air_grilling": "Hot air grilling",
"cooking_oven_program_heating_mode_top_bottom_heating": "Top bottom heating",
"cooking_oven_program_heating_mode_top_bottom_heating_eco": "Top bottom heating eco",
"cooking_oven_program_heating_mode_bottom_heating": "Bottom heating",
"cooking_oven_program_heating_mode_pizza_setting": "Pizza setting",
"cooking_oven_program_heating_mode_slow_cook": "Slow cook",
"cooking_oven_program_heating_mode_intensive_heat": "Intensive heat",
"cooking_oven_program_heating_mode_keep_warm": "Keep warm",
"cooking_oven_program_heating_mode_preheat_ovenware": "Preheat ovenware",
"cooking_oven_program_heating_mode_frozen_heatup_special": "Special Heat-Up for frozen products",
"cooking_oven_program_heating_mode_desiccation": "Desiccation",
"cooking_oven_program_heating_mode_defrost": "Defrost",
"cooking_oven_program_heating_mode_proof": "Proof",
"cooking_oven_program_heating_mode_hot_air_30_steam": "Hot air + 30 RH",
"cooking_oven_program_heating_mode_hot_air_60_steam": "Hot air + 60 RH",
"cooking_oven_program_heating_mode_hot_air_80_steam": "Hot air + 80 RH",
"cooking_oven_program_heating_mode_hot_air_100_steam": "Hot air + 100 RH",
"cooking_oven_program_heating_mode_sabbath_programme": "Sabbath programme",
"cooking_oven_program_microwave_90_watt": "90 Watt",
"cooking_oven_program_microwave_180_watt": "180 Watt",
"cooking_oven_program_microwave_360_watt": "360 Watt",
"cooking_oven_program_microwave_600_watt": "600 Watt",
"cooking_oven_program_microwave_900_watt": "900 Watt",
"cooking_oven_program_microwave_1000_watt": "1000 Watt",
"cooking_oven_program_microwave_max": "Max",
"cooking_oven_program_heating_mode_warming_drawer": "Warming drawer",
"laundry_care_washer_program_cotton": "Cotton",
"laundry_care_washer_program_cotton_cotton_eco": "Cotton eco",
"laundry_care_washer_program_cotton_eco_4060": "Cotton eco 40/60ºC",
"laundry_care_washer_program_cotton_colour": "Cotton color",
"laundry_care_washer_program_easy_care": "Easy care",
"laundry_care_washer_program_mix": "Mix",
"laundry_care_washer_program_mix_night_wash": "Mix night wash",
"laundry_care_washer_program_delicates_silk": "Delicates silk",
"laundry_care_washer_program_wool": "Wool",
"laundry_care_washer_program_sensitive": "Sensitive",
"laundry_care_washer_program_auto_30": "Auto 30ºC",
"laundry_care_washer_program_auto_40": "Auto 40ºC",
"laundry_care_washer_program_auto_60": "Auto 60ºC",
"laundry_care_washer_program_chiffon": "Chiffon",
"laundry_care_washer_program_curtains": "Curtains",
"laundry_care_washer_program_dark_wash": "Dark wash",
"laundry_care_washer_program_dessous": "Dessous",
"laundry_care_washer_program_monsoon": "Monsoon",
"laundry_care_washer_program_outdoor": "Outdoor",
"laundry_care_washer_program_plush_toy": "Plush toy",
"laundry_care_washer_program_shirts_blouses": "Shirts blouses",
"laundry_care_washer_program_sport_fitness": "Sport fitness",
"laundry_care_washer_program_towels": "Towels",
"laundry_care_washer_program_water_proof": "Water proof",
"laundry_care_washer_program_power_speed_59": "Power speed <60 min",
"laundry_care_washer_program_super_153045_super_15": "Super 15 min",
"laundry_care_washer_program_super_153045_super_1530": "Super 15/30 min",
"laundry_care_washer_program_down_duvet_duvet": "Down duvet",
"laundry_care_washer_program_rinse_rinse_spin_drain": "Rinse spin drain",
"laundry_care_washer_program_drum_clean": "Drum clean",
"laundry_care_washer_dryer_program_cotton": "Cotton",
"laundry_care_washer_dryer_program_cotton_eco_4060": "Cotton eco 40/60 ºC",
"laundry_care_washer_dryer_program_mix": "Mix",
"laundry_care_washer_dryer_program_easy_care": "Easy care",
"laundry_care_washer_dryer_program_wash_and_dry_60": "Wash and dry (60 min)",
"laundry_care_washer_dryer_program_wash_and_dry_90": "Wash and dry (90 min)"
}
},
"active_program": {
"name": "Active program",
"state": {
"consumer_products_cleaning_robot_program_cleaning_clean_all": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_cleaning_robot_program_cleaning_clean_all%]",
"consumer_products_cleaning_robot_program_cleaning_clean_map": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_cleaning_robot_program_cleaning_clean_map%]",
"consumer_products_cleaning_robot_program_basic_go_home": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_cleaning_robot_program_basic_go_home%]",
"consumer_products_coffee_maker_program_beverage_ristretto": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_ristretto%]",
"consumer_products_coffee_maker_program_beverage_espresso": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_espresso%]",
"consumer_products_coffee_maker_program_beverage_espresso_doppio": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_espresso_doppio%]",
"consumer_products_coffee_maker_program_beverage_coffee": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_coffee%]",
"consumer_products_coffee_maker_program_beverage_x_l_coffee": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_x_l_coffee%]",
"consumer_products_coffee_maker_program_beverage_caffe_grande": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_caffe_grande%]",
"consumer_products_coffee_maker_program_beverage_espresso_macchiato": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_espresso_macchiato%]",
"consumer_products_coffee_maker_program_beverage_cappuccino": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_cappuccino%]",
"consumer_products_coffee_maker_program_beverage_latte_macchiato": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_latte_macchiato%]",
"consumer_products_coffee_maker_program_beverage_caffe_latte": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_caffe_latte%]",
"consumer_products_coffee_maker_program_beverage_milk_froth": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_milk_froth%]",
"consumer_products_coffee_maker_program_beverage_warm_milk": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_warm_milk%]",
"consumer_products_coffee_maker_program_coffee_world_kleiner_brauner": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_kleiner_brauner%]",
"consumer_products_coffee_maker_program_coffee_world_grosser_brauner": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_grosser_brauner%]",
"consumer_products_coffee_maker_program_coffee_world_verlaengerter": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_verlaengerter%]",
"consumer_products_coffee_maker_program_coffee_world_verlaengerter_braun": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_verlaengerter_braun%]",
"consumer_products_coffee_maker_program_coffee_world_wiener_melange": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_wiener_melange%]",
"consumer_products_coffee_maker_program_coffee_world_flat_white": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_flat_white%]",
"consumer_products_coffee_maker_program_coffee_world_cortado": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_cortado%]",
"consumer_products_coffee_maker_program_coffee_world_cafe_cortado": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_cafe_cortado%]",
"consumer_products_coffee_maker_program_coffee_world_cafe_con_leche": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_cafe_con_leche%]",
"consumer_products_coffee_maker_program_coffee_world_cafe_au_lait": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_cafe_au_lait%]",
"consumer_products_coffee_maker_program_coffee_world_doppio": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_doppio%]",
"consumer_products_coffee_maker_program_coffee_world_kaapi": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_kaapi%]",
"consumer_products_coffee_maker_program_coffee_world_koffie_verkeerd": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_koffie_verkeerd%]",
"consumer_products_coffee_maker_program_coffee_world_galao": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_galao%]",
"consumer_products_coffee_maker_program_coffee_world_garoto": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_garoto%]",
"consumer_products_coffee_maker_program_coffee_world_americano": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_americano%]",
"consumer_products_coffee_maker_program_coffee_world_red_eye": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_red_eye%]",
"consumer_products_coffee_maker_program_coffee_world_black_eye": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_black_eye%]",
"consumer_products_coffee_maker_program_coffee_world_dead_eye": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_coffee_world_dead_eye%]",
"consumer_products_coffee_maker_program_beverage_hot_water": "[%key:component::home_connect::entity::select::selected_program::state::consumer_products_coffee_maker_program_beverage_hot_water%]",
"dishcare_dishwasher_program_pre_rinse": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_pre_rinse%]",
"dishcare_dishwasher_program_auto_1": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_auto_1%]",
"dishcare_dishwasher_program_auto_2": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_auto_2%]",
"dishcare_dishwasher_program_auto_3": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_auto_3%]",
"dishcare_dishwasher_program_eco_50": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_eco_50%]",
"dishcare_dishwasher_program_quick_45": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_quick_45%]",
"dishcare_dishwasher_program_intensiv_70": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_intensiv_70%]",
"dishcare_dishwasher_program_normal_65": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_normal_65%]",
"dishcare_dishwasher_program_glas_40": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_glas_40%]",
"dishcare_dishwasher_program_glass_care": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_glass_care%]",
"dishcare_dishwasher_program_night_wash": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_night_wash%]",
"dishcare_dishwasher_program_quick_65": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_quick_65%]",
"dishcare_dishwasher_program_normal_45": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_normal_45%]",
"dishcare_dishwasher_program_intensiv_45": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_intensiv_45%]",
"dishcare_dishwasher_program_auto_half_load": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_auto_half_load%]",
"dishcare_dishwasher_program_intensiv_power": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_intensiv_power%]",
"dishcare_dishwasher_program_magic_daily": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_magic_daily%]",
"dishcare_dishwasher_program_super_60": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_super_60%]",
"dishcare_dishwasher_program_kurz_60": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_kurz_60%]",
"dishcare_dishwasher_program_express_sparkle_65": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_express_sparkle_65%]",
"dishcare_dishwasher_program_machine_care": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_machine_care%]",
"dishcare_dishwasher_program_steam_fresh": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_steam_fresh%]",
"dishcare_dishwasher_program_maximum_cleaning": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_maximum_cleaning%]",
"dishcare_dishwasher_program_mixed_load": "[%key:component::home_connect::entity::select::selected_program::state::dishcare_dishwasher_program_mixed_load%]",
"laundry_care_dryer_program_cotton": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_cotton%]",
"laundry_care_dryer_program_synthetic": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_synthetic%]",
"laundry_care_dryer_program_mix": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_mix%]",
"laundry_care_dryer_program_blankets": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_blankets%]",
"laundry_care_dryer_program_business_shirts": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_business_shirts%]",
"laundry_care_dryer_program_down_feathers": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_down_feathers%]",
"laundry_care_dryer_program_hygiene": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_hygiene%]",
"laundry_care_dryer_program_jeans": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_jeans%]",
"laundry_care_dryer_program_outdoor": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_outdoor%]",
"laundry_care_dryer_program_synthetic_refresh": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_synthetic_refresh%]",
"laundry_care_dryer_program_towels": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_towels%]",
"laundry_care_dryer_program_delicates": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_delicates%]",
"laundry_care_dryer_program_super_40": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_super_40%]",
"laundry_care_dryer_program_shirts_15": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_shirts_15%]",
"laundry_care_dryer_program_pillow": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_pillow%]",
"laundry_care_dryer_program_anti_shrink": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_anti_shrink%]",
"laundry_care_dryer_program_my_time_my_drying_time": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_my_time_my_drying_time%]",
"laundry_care_dryer_program_time_cold": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_cold%]",
"laundry_care_dryer_program_time_warm": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_warm%]",
"laundry_care_dryer_program_in_basket": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_in_basket%]",
"laundry_care_dryer_program_time_cold_fix_time_cold_20": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_cold_fix_time_cold_20%]",
"laundry_care_dryer_program_time_cold_fix_time_cold_30": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_cold_fix_time_cold_30%]",
"laundry_care_dryer_program_time_cold_fix_time_cold_60": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_cold_fix_time_cold_60%]",
"laundry_care_dryer_program_time_warm_fix_time_warm_30": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_warm_fix_time_warm_30%]",
"laundry_care_dryer_program_time_warm_fix_time_warm_40": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_warm_fix_time_warm_40%]",
"laundry_care_dryer_program_time_warm_fix_time_warm_60": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_time_warm_fix_time_warm_60%]",
"laundry_care_dryer_program_dessous": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_dryer_program_dessous%]",
"cooking_common_program_hood_automatic": "[%key:component::home_connect::entity::select::selected_program::state::cooking_common_program_hood_automatic%]",
"cooking_common_program_hood_venting": "[%key:component::home_connect::entity::select::selected_program::state::cooking_common_program_hood_venting%]",
"cooking_common_program_hood_delayed_shut_off": "[%key:component::home_connect::entity::select::selected_program::state::cooking_common_program_hood_delayed_shut_off%]",
"cooking_oven_program_heating_mode_pre_heating": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_pre_heating%]",
"cooking_oven_program_heating_mode_hot_air": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air%]",
"cooking_oven_program_heating_mode_hot_air_eco": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air_eco%]",
"cooking_oven_program_heating_mode_hot_air_grilling": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air_grilling%]",
"cooking_oven_program_heating_mode_top_bottom_heating": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_top_bottom_heating%]",
"cooking_oven_program_heating_mode_top_bottom_heating_eco": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_top_bottom_heating_eco%]",
"cooking_oven_program_heating_mode_bottom_heating": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_bottom_heating%]",
"cooking_oven_program_heating_mode_pizza_setting": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_pizza_setting%]",
"cooking_oven_program_heating_mode_slow_cook": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_slow_cook%]",
"cooking_oven_program_heating_mode_intensive_heat": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_intensive_heat%]",
"cooking_oven_program_heating_mode_keep_warm": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_keep_warm%]",
"cooking_oven_program_heating_mode_preheat_ovenware": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_preheat_ovenware%]",
"cooking_oven_program_heating_mode_frozen_heatup_special": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_frozen_heatup_special%]",
"cooking_oven_program_heating_mode_desiccation": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_desiccation%]",
"cooking_oven_program_heating_mode_defrost": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_defrost%]",
"cooking_oven_program_heating_mode_proof": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_proof%]",
"cooking_oven_program_heating_mode_hot_air_30_steam": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air_30_steam%]",
"cooking_oven_program_heating_mode_hot_air_60_steam": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air_60_steam%]",
"cooking_oven_program_heating_mode_hot_air_80_steam": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air_80_steam%]",
"cooking_oven_program_heating_mode_hot_air_100_steam": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_hot_air_100_steam%]",
"cooking_oven_program_heating_mode_sabbath_programme": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_sabbath_programme%]",
"cooking_oven_program_microwave_90_watt": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_90_watt%]",
"cooking_oven_program_microwave_180_watt": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_180_watt%]",
"cooking_oven_program_microwave_360_watt": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_360_watt%]",
"cooking_oven_program_microwave_600_watt": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_600_watt%]",
"cooking_oven_program_microwave_900_watt": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_900_watt%]",
"cooking_oven_program_microwave_1000_watt": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_1000_watt%]",
"cooking_oven_program_microwave_max": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_microwave_max%]",
"cooking_oven_program_heating_mode_warming_drawer": "[%key:component::home_connect::entity::select::selected_program::state::cooking_oven_program_heating_mode_warming_drawer%]",
"laundry_care_washer_program_cotton": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_cotton%]",
"laundry_care_washer_program_cotton_cotton_eco": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_cotton_cotton_eco%]",
"laundry_care_washer_program_cotton_eco_4060": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_cotton_eco_4060%]",
"laundry_care_washer_program_cotton_colour": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_cotton_colour%]",
"laundry_care_washer_program_easy_care": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_easy_care%]",
"laundry_care_washer_program_mix": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_mix%]",
"laundry_care_washer_program_mix_night_wash": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_mix_night_wash%]",
"laundry_care_washer_program_delicates_silk": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_delicates_silk%]",
"laundry_care_washer_program_wool": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_wool%]",
"laundry_care_washer_program_sensitive": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_sensitive%]",
"laundry_care_washer_program_auto_30": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_auto_30%]",
"laundry_care_washer_program_auto_40": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_auto_40%]",
"laundry_care_washer_program_auto_60": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_auto_60%]",
"laundry_care_washer_program_chiffon": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_chiffon%]",
"laundry_care_washer_program_curtains": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_curtains%]",
"laundry_care_washer_program_dark_wash": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_dark_wash%]",
"laundry_care_washer_program_dessous": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_dessous%]",
"laundry_care_washer_program_monsoon": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_monsoon%]",
"laundry_care_washer_program_outdoor": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_outdoor%]",
"laundry_care_washer_program_plush_toy": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_plush_toy%]",
"laundry_care_washer_program_shirts_blouses": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_shirts_blouses%]",
"laundry_care_washer_program_sport_fitness": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_sport_fitness%]",
"laundry_care_washer_program_towels": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_towels%]",
"laundry_care_washer_program_water_proof": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_water_proof%]",
"laundry_care_washer_program_power_speed_59": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_power_speed_59%]",
"laundry_care_washer_program_super_153045_super_15": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_super_153045_super_15%]",
"laundry_care_washer_program_super_153045_super_1530": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_super_153045_super_1530%]",
"laundry_care_washer_program_down_duvet_duvet": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_down_duvet_duvet%]",
"laundry_care_washer_program_rinse_rinse_spin_drain": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_rinse_rinse_spin_drain%]",
"laundry_care_washer_program_drum_clean": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_program_drum_clean%]",
"laundry_care_washer_dryer_program_cotton": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_dryer_program_cotton%]",
"laundry_care_washer_dryer_program_cotton_eco_4060": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_dryer_program_cotton_eco_4060%]",
"laundry_care_washer_dryer_program_mix": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_dryer_program_mix%]",
"laundry_care_washer_dryer_program_easy_care": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_dryer_program_easy_care%]",
"laundry_care_washer_dryer_program_wash_and_dry_60": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_dryer_program_wash_and_dry_60%]",
"laundry_care_washer_dryer_program_wash_and_dry_90": "[%key:component::home_connect::entity::select::selected_program::state::laundry_care_washer_dryer_program_wash_and_dry_90%]"
}
}
},
"sensor": {
"program_progress": {
"name": "Program progress"

View File

@@ -8,11 +8,12 @@ from homeconnect.api import HomeConnectError
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
from .const import (
APPLIANCES_WITH_PROGRAMS,
ATTR_ALLOWED_VALUES,
ATTR_CONSTRAINTS,
ATTR_VALUE,
@@ -36,18 +37,6 @@ from .entity import HomeConnectDevice, HomeConnectEntity
_LOGGER = logging.getLogger(__name__)
APPLIANCES_WITH_PROGRAMS = (
"CleaningRobot",
"CoffeeMaker",
"Dishwasher",
"Dryer",
"Hood",
"Oven",
"WarmingDrawer",
"Washer",
"WasherDryer",
)
SWITCHES = (
SwitchEntityDescription(
@@ -145,7 +134,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_available = False
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on",
translation_placeholders={
@@ -169,7 +158,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn off: %s", err)
self._attr_available = False
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off",
translation_placeholders={
@@ -220,7 +209,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
self.device.appliance.start_program, self.program_name
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="start_program",
translation_placeholders={
@@ -236,7 +225,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
try:
await self.hass.async_add_executor_job(self.device.appliance.stop_program)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="stop_program",
translation_placeholders={
@@ -289,7 +278,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_is_on = False
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_on",
translation_placeholders={
@@ -302,7 +291,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Switch the device off."""
if not hasattr(self, "power_off_state"):
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unable_to_retrieve_turn_off",
translation_placeholders={
@@ -311,7 +300,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
if self.power_off_state is None:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off_not_supported",
translation_placeholders={
@@ -327,7 +316,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_is_on = True
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_off",
translation_placeholders={

View File

@@ -7,7 +7,7 @@ from homeconnect.api import HomeConnectError
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
@@ -80,7 +80,7 @@ class HomeConnectTimeEntity(HomeConnectEntity, TimeEntity):
time_to_seconds(value),
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting",
translation_placeholders={

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.6"],
"requirements": ["aiohomekit==3.2.7"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
}

View File

@@ -9,13 +9,15 @@ from typing import Any, NamedTuple
from homewizard_energy import HomeWizardEnergyV1
from homewizard_energy.errors import DisabledError, RequestError, UnsupportedError
from homewizard_energy.v1.models import Device
from voluptuous import Required, Schema
import voluptuous as vol
from homeassistant.components import onboarding, zeroconf
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_IP_ADDRESS, CONF_PATH
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.selector import TextSelector
from .const import (
CONF_API_ENABLED,
@@ -68,11 +70,11 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
user_input = user_input or {}
return self.async_show_form(
step_id="user",
data_schema=Schema(
data_schema=vol.Schema(
{
Required(
vol.Required(
CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS)
): str,
): TextSelector(),
}
),
errors=errors,
@@ -110,6 +112,32 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_discovery_confirm()
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle dhcp discovery to update existing entries.
This flow is triggered only by DHCP discovery of known devices.
"""
try:
device = await self._async_try_connect(discovery_info.ip)
except RecoverableError as ex:
_LOGGER.error(ex)
return self.async_abort(reason="unknown")
await self.async_set_unique_id(
f"{device.product_type}_{discovery_info.macaddress}"
)
self._abort_if_unique_id_configured(
updates={CONF_IP_ADDRESS: discovery_info.ip}
)
# This situation should never happen, as Home Assistant will only
# send updates for existing entries. In case it does, we'll just
# abort the flow with an unknown error.
return self.async_abort(reason="unknown")
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -170,6 +198,43 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_show_form(step_id="reauth_confirm", errors=errors)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}
if user_input:
try:
device_info = await self._async_try_connect(user_input[CONF_IP_ADDRESS])
except RecoverableError as ex:
_LOGGER.error(ex)
errors = {"base": ex.error_code}
else:
await self.async_set_unique_id(
f"{device_info.product_type}_{device_info.serial}"
)
self._abort_if_unique_id_mismatch(reason="wrong_device")
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates=user_input,
)
reconfigure_entry = self._get_reconfigure_entry()
return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema(
{
vol.Required(
CONF_IP_ADDRESS,
default=reconfigure_entry.data.get(CONF_IP_ADDRESS),
): TextSelector(),
}
),
description_placeholders={
"title": reconfigure_entry.title,
},
errors=errors,
)
@staticmethod
async def _async_try_connect(ip_address: str) -> Device:
"""Try to connect.

View File

@@ -3,9 +3,15 @@
"name": "HomeWizard Energy",
"codeowners": ["@DCSBL"],
"config_flow": true,
"dhcp": [
{
"registered_devices": true
}
],
"documentation": "https://www.home-assistant.io/integrations/homewizard",
"iot_class": "local_polling",
"loggers": ["homewizard_energy"],
"quality_scale": "platinum",
"requirements": ["python-homewizard-energy==v7.0.0"],
"zeroconf": ["_hwenergy._tcp.local."]
}

View File

@@ -46,11 +46,7 @@ rules:
# Gold
devices: done
diagnostics: done
discovery-update-info:
status: todo
comment: |
The integration doesn't update the device info based on DHCP discovery
of known existing devices.
discovery-update-info: done
discovery: done
docs-data-update: done
docs-examples: done
@@ -69,7 +65,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: |

View File

@@ -17,6 +17,15 @@
},
"reauth_confirm": {
"description": "The local API is disabled. Go to the HomeWizard Energy app and enable the API in the device settings."
},
"reconfigure": {
"description": "Update configuration for {title}.",
"data": {
"ip_address": "[%key:common::config_flow::data::ip%]"
},
"data_description": {
"ip_address": "[%key:component::homewizard::config::step::user::data_description::ip_address%]"
}
}
},
"error": {
@@ -29,7 +38,9 @@
"device_not_supported": "This device is not supported",
"unknown_error": "[%key:common::config_flow::error::unknown%]",
"unsupported_api_version": "Detected unsupported API version",
"reauth_successful": "Enabling API was successful"
"reauth_successful": "Enabling API was successful",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"wrong_device": "The configured device is not the same found on this IP address."
}
},
"entity": {

View File

@@ -326,7 +326,8 @@ class HomeAssistantApplication(web.Application):
protocol,
writer,
task,
loop=self._loop,
# loop will never be None when called from aiohttp
loop=self._loop, # type: ignore[arg-type]
client_max_size=self._client_max_size,
)

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from datetime import timedelta
from enum import StrEnum
from functools import partial
import logging
from typing import Any, final
@@ -22,11 +21,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -34,9 +28,6 @@ from homeassistant.loader import bind_hass
from homeassistant.util.hass_dict import HassKey
from .const import ( # noqa: F401
_DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER,
_DEPRECATED_DEVICE_CLASS_HUMIDIFIER,
_DEPRECATED_SUPPORT_MODES,
ATTR_ACTION,
ATTR_AVAILABLE_MODES,
ATTR_CURRENT_HUMIDITY,
@@ -314,13 +305,3 @@ async def async_service_humidity_set(
)
await entity.async_set_humidity(humidity)
# As we import deprecated constants from the const module, we need to add these two functions
# otherwise this module will be logged for using deprecated constants and not the custom component
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -1,15 +1,6 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedConstant,
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
MODE_NORMAL = "normal"
MODE_ECO = "eco"
@@ -43,15 +34,6 @@ DEFAULT_MAX_HUMIDITY = 100
DOMAIN = "humidifier"
# DEVICE_CLASS_* below are deprecated as of 2021.12
# use the HumidifierDeviceClass enum instead.
_DEPRECATED_DEVICE_CLASS_HUMIDIFIER = DeprecatedConstant(
"humidifier", "HumidifierDeviceClass.HUMIDIFIER", "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER = DeprecatedConstant(
"dehumidifier", "HumidifierDeviceClass.DEHUMIDIFIER", "2025.1"
)
SERVICE_SET_MODE = "set_mode"
SERVICE_SET_HUMIDITY = "set_humidity"
@@ -60,17 +42,3 @@ class HumidifierEntityFeature(IntFlag):
"""Supported features of the humidifier entity."""
MODES = 1
# The SUPPORT_MODES constant is deprecated as of Home Assistant 2022.5.
# Please use the HumidifierEntityFeature enum instead.
_DEPRECATED_SUPPORT_MODES = DeprecatedConstantEnum(
HumidifierEntityFeature.MODES, "2025.1"
)
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -22,6 +22,8 @@ from .entity import (
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class AutomowerButtonEntityDescription(ButtonEntityDescription):

View File

@@ -22,6 +22,10 @@ from .const import DOMAIN
from .coordinator import AutomowerDataUpdateCoordinator
from .entity import AutomowerAvailableEntity, handle_sending_exception
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
DOCKED_ACTIVITIES = (MowerActivities.PARKED_IN_CS, MowerActivities.CHARGING)
MOWING_ACTIVITIES = (
MowerActivities.MOWING,
@@ -42,9 +46,6 @@ PARK = "park"
OVERRIDE_MODES = [MOW, PARK]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: AutomowerConfigEntry,

View File

@@ -24,6 +24,8 @@ from .entity import (
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
@callback
def _async_get_cutting_height(data: MowerAttributes) -> int:

View File

@@ -16,6 +16,7 @@ from .entity import AutomowerControlEntity, handle_sending_exception
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
HEADLIGHT_MODES: list = [
HeadlightModes.ALWAYS_OFF.lower(),

View File

@@ -349,6 +349,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
key="number_of_collisions",
translation_key="number_of_collisions",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL,
exists_fn=lambda data: data.statistics.number_of_collisions is not None,
value_fn=attrgetter("statistics.number_of_collisions"),

View File

@@ -19,6 +19,8 @@ from .entity import (
handle_sending_exception,
)
PARALLEL_UPDATES = 1
_LOGGER = logging.getLogger(__name__)

View File

@@ -45,6 +45,7 @@ from homeassistant.util import dt as dt_util
from .const import DOMAIN, TIMER_DATA
from .timers import (
CancelAllTimersIntentHandler,
CancelTimerIntentHandler,
DecreaseTimerIntentHandler,
IncreaseTimerIntentHandler,
@@ -130,6 +131,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
intent.async_register(hass, SetPositionIntentHandler())
intent.async_register(hass, StartTimerIntentHandler())
intent.async_register(hass, CancelTimerIntentHandler())
intent.async_register(hass, CancelAllTimersIntentHandler())
intent.async_register(hass, IncreaseTimerIntentHandler())
intent.async_register(hass, DecreaseTimerIntentHandler())
intent.async_register(hass, PauseTimerIntentHandler())
@@ -362,7 +364,7 @@ class NevermindIntentHandler(intent.IntentHandler):
"""Takes no action."""
intent_type = intent.INTENT_NEVERMIND
description = "Cancels the current request and does nothing"
description = "Cancel the current conversation if it was started by mistake or the user wants it to stop."
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Do nothing and produces an empty response."""

View File

@@ -887,6 +887,36 @@ class CancelTimerIntentHandler(intent.IntentHandler):
return intent_obj.create_response()
class CancelAllTimersIntentHandler(intent.IntentHandler):
"""Intent handler for cancelling all timers."""
intent_type = intent.INTENT_CANCEL_ALL_TIMERS
description = "Cancels all timers"
slot_schema = {
vol.Optional("area"): cv.string,
}
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent."""
hass = intent_obj.hass
timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots)
canceled = 0
for timer in _find_timers(hass, intent_obj.device_id, slots):
timer_manager.cancel_timer(timer.id)
canceled += 1
response = intent_obj.create_response()
speech_slots = {"canceled": canceled}
if "area" in slots:
speech_slots["area"] = slots["area"]["value"]
response.async_set_speech_slots(speech_slots)
return response
class IncreaseTimerIntentHandler(intent.IntentHandler):
"""Intent handler for increasing the time of a timer."""

View File

@@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/iron_os",
"iot_class": "local_polling",
"loggers": ["pynecil", "aiogithubapi"],
"requirements": ["pynecil==0.2.1", "aiogithubapi==24.6.0"]
"requirements": ["pynecil==1.0.1", "aiogithubapi==24.6.0"]
}

View File

@@ -23,6 +23,8 @@ from . import IronOSConfigEntry
from .const import DOMAIN, MAX_TEMP, MIN_TEMP
from .entity import IronOSBaseEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class IronOSNumberEntityDescription(NumberEntityDescription):

View File

@@ -107,6 +107,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=OHM,
value_fn=lambda data: data.tip_resistance,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
),
IronOSSensorEntityDescription(
key=PinecilSensor.UPTIME,
@@ -137,7 +138,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
IronOSSensorEntityDescription(
key=PinecilSensor.TIP_VOLTAGE,
translation_key=PinecilSensor.TIP_VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
native_unit_of_measurement=UnitOfElectricPotential.MICROVOLT,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=3,

View File

@@ -5,10 +5,13 @@
"description": "[%key:component::bluetooth::config::step::user::description%]",
"data": {
"address": "[%key:common::config_flow::data::device%]"
},
"data_description": {
"address": "Ensure your device is powered on and within Bluetooth range before continuing"
}
},
"bluetooth_confirm": {
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
"description": "Do you want to set up {name}?\n\n*Ensure your device is powered on and within Bluetooth range before continuing*"
}
},
"abort": {

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