Compare commits

...

524 Commits

Author SHA1 Message Date
Ville Skyttä 6f3a1344b3 Raise errors on interactive entity actions when suspended 2025-05-04 15:12:27 +03:00
Ville Skyttä 28f9d46f2e Mark interactive entities unavailable when suspended 2025-05-04 15:12:27 +03:00
Ville Skyttä 5156dd9c89 Normalize action parameter URL
For consistency with integration setup.
2025-05-04 15:12:26 +03:00
Ville Skyttä a159f357ae Raise errors on action call problems 2025-05-04 15:12:26 +03:00
Brett Adams 8046684179 Update models const in Teslemetry (#144175) 2025-05-04 13:44:56 +02:00
Brett Adams 5a475ec7ea Improve typing of binary sensors in Teslemetry (#144169) 2025-05-04 13:42:25 +02:00
Brett Adams 8c6edd8b81 Add better typing to Teslemetry switch platform (#144168) 2025-05-04 13:41:45 +02:00
Brett Adams de496c693e Add hazard lights binary sensor to Teslemetry (#144166) 2025-05-04 13:36:13 +02:00
Norbert Rittel cb37d4d36a Fix spelling of "comma-separated (list / event name)" in doorbird (#144190) 2025-05-04 13:09:31 +03:00
Norbert Rittel 2aa82da615 Fix spelling of "comma-separated (list)" in huawei_lte (#144189) 2025-05-04 13:09:09 +03:00
Maciej Bieniek 04982f5e12 Add missing pollen category to AccuWeather (#144185)
* Add extreme level to pollen map

* Sort

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

* Bump pymiele to 0.4.3

---------

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

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

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

* fix doc strings

* Apply suggestions from code review

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

---------

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

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

* feat: update translation

* feat: add separate translations for HVAC and temperature

* fix: test cases

* fix: test cases for test_set_preset_mode_boost

* rev: code review

* rev: exception string

* feat: updated  error message and added helper function

* Update homeassistant/components/fritzbox/strings.json

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

* fix: translation key

* remove check_active_or_lock_mode from async_set_preset_mode

---------

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

* Added tests

* Use enums

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

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

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

* Improve naming of miele freezers and fridges

* Address review

* Address review comment

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

* Update homeassistant/components/knocki/quality_scale.yaml

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

---------

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

* Improve handling of unknown program codes

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

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

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

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

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

* Add snapshots

* Format fixture

* Set CurrentPhase attribute

* Set OperationalState attribute

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

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

* Add test variants for WMS device types and their diagnostics

* Add test variants for cover movement of WMS device types

* Move device entry tests to test_init and avoid snapshot list

Suggested-by: joostlek
2025-04-30 20:51:10 +02:00
Norbert Rittel 621cf6ce58 Fix broken references in teslemetry (#143981)
* Fix broken references in `teslemetry`

* Fix full strings

* Add just "name:" to references

* Add missing colons

* Fix

* Add "entity::" to all strings
2025-04-30 20:48:14 +02:00
Guido Schmitz 83e0ed7b05 Improve config flow of devolo Home Network (#131911)
* Improve config flow of devolo Home Network

* Apply feedback

* Use references
2025-04-30 20:47:26 +02:00
tdfountain 0752807aaf Improve device action config entry lookup in NUT (#142133)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-04-30 20:46:02 +02:00
Timothy 53df69ee6e Encourage to use UID instead of name for update and delete todos (#143556) 2025-04-30 20:45:26 +02:00
Sid dbc38cdc6b Add switch platform to eheimdigital (#142412)
* Add switch platform to eheimdigital

* docstring fixes

* Review

* Review

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-30 20:42:00 +02:00
yohaybn 102d55ec57 Jewish Calendar - support omer count after sunset (#143332)
Co-authored-by: Tsvi Mostovicz <ttmost@gmail.com>
2025-04-30 20:41:03 +02:00
Norbert Rittel 3f7cae8583 Spelling fixes to user-facing strings of tplink (#143649)
* Fixes to user-facing strings of `tplink`

- add missing hyphen to "auto-off" and "auto-update"
- sentence-case one overlooked word

* Update test_sensor.ambr

* Update test_switch.ambr
2025-04-30 20:39:40 +02:00
Peter Åslund a3a1d424c6 Implement data coordinator for Adax-integration (#139514)
* Implemented coordinator (for Cloud integration)

* Optimized coordinator updates

* Finalizing

* Running ruff and ruff format

* Raise error if trying to instantiate coordinator for a AdaxLocal config

* Re-added data-handler for AdaxLocal integrations

* Added a coordinator for Local integrations

* mypy warnings

* Update homeassistant/components/adax/manifest.json

Co-authored-by: Daniel Hjelseth Høyer <mail@dahoiv.net>

* Resolve mypy issues

* PR comments

- Explicit passing of config_entry to Coordinator base type
- Avoid duplicate storing of Coordinator data. Instead use self.data
- Remove try-catch and wrapping to UpdateFailed in _async_update_data
- Custom ConfigEntry type for passing coordinator via entry.runtime_data

* When changing HVAC_MODE update data via Coordinator to optimize

* Apply already loaded data for Climate entity directly in __init__

* Moved SCAN_INTERVAL into const.py

* Removed logging statements

* Remove unnecessary get_rooms() / get_status() functions

* Resolvning mypy issues

* Adding tests for coordinators

* Resolving review comments by joostlek

* Setup of Cloud devices with device_id

* Implement Climate tests for Adax

* Implementing assertions of UNAVAILABLE state

* Removed no longer needed method

* Apply suggestions from code review

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

* Mock Adax class instead of individual methods

* Mock config entry via fixture

* Load config entry data from .json fixture

* Hard code config_entry_data instead of .json file

* Removed obsolete .json-files

* Fix

* Fix

---------

Co-authored-by: Daniel Hjelseth Høyer <mail@dahoiv.net>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-04-30 20:33:46 +02:00
Shay Levy 2cede8fec6 Record Switcher quality scale (#141065)
* Record Switcher quality scale

* Update entity-device-class status to todo
2025-04-30 20:33:12 +02:00
Arjan 5816d495e3 Linkplay: add entity_picture attribute (media image url) for media player, works for WiiM (#143328)
Add media image url to show as entity_picture
2025-04-30 19:59:25 +02:00
Wilbert 24803b1e75 Add SmartThings water consumption sensor (#142765)
* Add SmartThings water consumption sensor

* Update water consumption sensor

* Partly revert changes UnitOfVolume

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-30 19:58:39 +02:00
Retha Runolfsson 9732b8c0dd Add switchbot circulator fan support (#142980)
* add support for circulator fan

* add fan unin tests

* optimize unit tests

* add fan mode translation and icon

* optimize fan unit test
2025-04-30 19:58:33 +02:00
hahn-th 1626b3b7c9 Add absolute humidity sensor to homematicip_cloud (#143709)
* add absolute humidity sensor

* drop unused tests; rename test

* Fix docstrings
2025-04-30 19:51:43 +02:00
puddly 8760a82dfa Bump ZHA to 0.0.57 (#143963)
* Use new (internal) cluster handler IDs in unit tests

* Always add a profile_id to created endpoints

* Use new library decimal formatting

* Implement the ENUM device class for sensors

* Use the suggested display precision hint

* Revert "Implement the ENUM device class for sensors"

This reverts commit d11ab268121b7ffe67c81e45fdc46004fb57a22a.

* Bump ZHA to 0.0.57

* Add strings for v2 quirk entities

* Use ZHA library diagnostics

* Update snapshot

* Revert ZHA change that reports a cover state of `open` if either lift or tilt axes are `open`

This is an interim change to address issues with some cover 'relay' type devices which falsely report support for both lift and tilt. In reality these only support one axes or the other, with users using yaml overrides to restrict functionality in HA.

Devices that genuinely support both movement axes will behave the same as they did prior to https://github.com/zigpy/zha/pull/376
https://github.com/home-assistant/core/pull/141447

A subsequent PR will be made to allow users to override the covering type in a way that allows the entity handler to be aware of the configuration, calculating the state accordingly.

* Spelling mistake

---------

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
Co-authored-by: Jack <46714706+jeverley@users.noreply.github.com>
2025-04-30 19:43:38 +02:00
Parker Brown 0f5d5ab0a2 Add return energy and compensation to Opower (#135258)
* Add return energy statistics to Opower coordinator

* Add consumption and return cost statistics to Opower coordinator

* Rename return cost to compensation for clarity and consistency

* Use original cost stats for consumption cost

* Rename return cost to compensation

* Remove comment

* Raise issue for negative consumption/cost values in Opower statistics

* Migrate existing statistics and raise an issue

* Update strings.json

---------

Co-authored-by: tronikos <tronikos@users.noreply.github.com>
2025-04-30 19:42:36 +02:00
Justin Bull e05f7a9633 Expose LitterHopper status for LR4 (#143684)
* Expose LitterHopper status for LR4

* Proper naming and icons

* Add simple tests

* fix test: lowercase enabled

* over-torque, not OT

* Don't use icon_fn for simple state map

* short not Short

* Better state names
2025-04-30 19:41:05 +02:00
Jozef Kruszynski 30656a4e72 Add mediabrowser search to music assistant (#143851)
* add search to music assistant

* fix: copy paste error

* refactor: remove unnecessary hasattr condition checks

* refactor: clean up type hinting for mypy
2025-04-30 19:40:41 +02:00
Manuel Rüger 5ccb9486e0 switchbot_cloud: Add battery sensor for Bot and Smart Locks (#143689) 2025-04-30 19:32:02 +02:00
Brett Adams a6d5891e8a Add more sensors to Teslemetry (#143386)
* Add more sensors

* first batch

* first batch finished

* Sensors

* Clean up

* Remove comment

* Updates

* Fix translation

* Other small fixes

* [%key:common::state::enabled%]

* enabled

* More translation improvements

* Small tweaks

* reconnect

* Add Icons

* fault

* faults

* Fix bad merge

* review feedback

* uom fixes

* Translate units
2025-04-30 19:19:34 +02:00
Abílio Costa fc440f310b Add door binary sensor to Whirlpool (#143947) 2025-04-30 19:15:19 +02:00
Joost Lekkerkerker 4d9ab42ab5 Add detergent select entities to smartthings (#143666)
* Add detergent select entities to smartthings

* Add detergent select entities to smartthings

* Add detergent select entities to smartthings

* Update homeassistant/components/smartthings/select.py

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

* Fix

* Fix

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-04-30 19:14:48 +02:00
Michael e53f380710 Migrate climate attributes to own entities in AVM Fritz!SmartHome (#143394)
* migrate climate attributes to own entities

* add a comment to make it searchable

* Apply suggestions from code review

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

* Apply suggestions from code review

* update snapshots

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-04-30 18:37:58 +02:00
Paul Bottein 6a514ac2de Update frontend to 20250430.2 (#143974)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-04-30 18:24:15 +02:00
Kevin Stillhammer 02bd8d67c8 Use google-maps-routing in google_travel_time (#140691)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-30 18:22:15 +02:00
andreimoraru 2c118d4850 Bump yt-dlp to 2025.03.31 (#143733)
* Update manifest.json: bump yt-dlp to 2025.03.31

* Update requirements_all.txt: bump yt-dlp to 2025.03.31

* Update requirements_test_all.txt: bump yt-dlp to 2025.03.31

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-04-30 18:15:46 +02:00
Denis Shulyaka 949225ffeb Bump openai to 1.76.2 (#143902)
* Bump openai to 1.76.1

* Fix mypy

* Fix coverage

* 1.76.2

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-04-30 12:07:55 -04:00
tronikos 1ef04a8dde Add National Grid US virtual integration (#143756) 2025-04-30 18:06:54 +02:00
tronikos 5c58f97e57 Add Google Gemini virtual integration (#143753) 2025-04-30 18:04:25 +02:00
Stefan Agner f1b8c8855e Push country config to Supervisor (#143871) 2025-04-30 18:02:18 +02:00
Jan Bouwhuis daf143f66e Fix broken URL in MQTT translation strings (#143973) 2025-04-30 18:01:51 +02:00
Michael Hansen 6c0e46f050 Bump intents to 2025.4.30 (#143969)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-04-30 18:01:17 +02:00
epenet 70133da025 Use freezer.tick once more in SamsungTV (#143970) 2025-04-30 17:50:13 +02:00
Paul Bottein 837592381a Update frontend to 20250430.1 (#143965) 2025-04-30 18:22:05 +03:00
Timothy 101b073793 Use Lokalise references to remove duplicates in todo component (#143967) 2025-04-30 15:57:26 +01:00
epenet df5f150531 Cleanup samsungtv coordinator (#143949) 2025-04-30 16:25:45 +02:00
J. Diego Rodríguez Royo 4061314cd2 Allow multiple config entries in Home Connect (#143935)
* Allow multiple config entries in Home Connect

* Config entry migration

* Create new entry if reauth flow is completed with other account

* Abort if different account on reauth
2025-04-30 16:22:18 +02:00
Robert Resch 819be719ef Bump uv to 0.7.1 (#143957)
* Bump uv to 0.6.17

* Bump uv to 0.7.1
2025-04-30 16:16:55 +02:00
Martin Hjelmare 80e4f19172 Fix Z-Wave USB flow test warning (#143956) 2025-04-30 16:14:44 +02:00
Jan Bouwhuis 5b0ea21607 Add light as entity platform on MQTT subentries (#141345)
* Add light as entity platform on MQTT subentries

* Improve translation strings

* Rename to separate brightness

* Remove option to use mireds for color temperature

* Fix tests

* Add translation reference

* Correct reference

* Add flash and transition feature switches

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-04-30 15:57:51 +02:00
Norbert Rittel 84634ce288 Improve Error message states in fronius (#143958) 2025-04-30 15:56:22 +02:00
Maciej Bieniek fa1dc75517 Add repair flow for Shelly BLE scanner with unsupported firmware (#143850) 2025-04-30 15:43:07 +02:00
J. Diego Rodríguez Royo d8122d149b Add zeroconf to Home Connect (#143952) 2025-04-30 15:42:06 +02:00
Joost Lekkerkerker 923300f4e7 Add Sabbath mode to SmartThings (#141072) 2025-04-30 15:39:23 +02:00
epenet af66d0b647 Delay register callback in SamsungTV (#143950) 2025-04-30 15:38:40 +02:00
epenet 8b9c4dadd0 Use freezer.tick in SamsungTV tests (#143954) 2025-04-30 15:38:00 +02:00
Sid 857db679ae Add time platform to eheimdigital (#143168) 2025-04-30 15:34:28 +02:00
J. Nick Koston 03ecd7f06c Remove icon from rehlko power_source (#143955) 2025-04-30 15:33:14 +02:00
epenet c6bdee8dd8 Various minor tweaks in samsungtv tests (#143951) 2025-04-30 15:26:39 +02:00
epenet 800f403643 Adjust unique_id in SamsungTV tests (#143959) 2025-04-30 15:25:50 +02:00
Robert Resch 9b1c6b07f5 Bump deebot-client to 13.0.0 (#143823) 2025-04-30 15:24:54 +02:00
Brian Choromanski f7c1a0c5e6 Add tests for parse_time_expression (#143912) 2025-04-30 13:58:17 +01:00
Pete Sage 57a7c26c64 Add generator status sensors for Rehlko (#143948) 2025-04-30 14:55:12 +02:00
Norbert Rittel d606e86b47 Fix spelling of "Overtorque fault" in litterrobot (#143953) 2025-04-30 14:53:03 +02:00
Erik Montnemery f7a9319122 Don't attempt to garbage collect objects leaked by previous modules (#143944) 2025-04-30 14:52:50 +02:00
Allen Porter b16151ac6d Add an LLM tool for fetching todo list items (#143777)
* Add a tool for fetching todo list items

* Simplify the todo list interface by adding an "all" status

* Update prompt to improve performance on smaller models
2025-04-30 08:49:33 -04:00
Manuel Rüger bdd9099294 switchbot_cloud: Add firmware information (#143693) 2025-04-30 14:48:18 +02:00
Allen Porter d924f0b1d6 Improve the live context tool prompt with additional instructions (#143746)
* Improve the live context tool prompt with additional instructions

* Fix vertical whitespace
2025-04-30 08:47:54 -04:00
Paulus Schoutsen 5dab9ba01b Allow streaming text into TTS ResultStream objects (#143745)
Allow streaming messages into TTS ResultStream
2025-04-30 08:21:19 -04:00
J. Nick Koston ae118da5a1 Bump orjson to 3.10.18 (#143943) 2025-04-30 14:03:38 +02:00
epenet e24082be9a Fix incorrect return types in samsungtv tests (#143937) 2025-04-30 13:31:21 +02:00
Manu 8fafbfaf82 Change function alias to proxy in ista EcoTrend (#143911)
Change function alias
2025-04-30 13:07:51 +02:00
Joost Lekkerkerker 6168fe006e Remove Oncue integration (#143945) 2025-04-30 12:50:28 +02:00
Pete Sage 6c633668f6 Add Rehlko (formerly Kohler Energy Management) Integration (#143602)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-04-30 12:44:16 +02:00
epenet 73a1dbffeb Fix invalid-else in samsungtv (#143942) 2025-04-30 12:34:36 +02:00
Erik Montnemery 40217e764d Allow overriding blueprinted templates (#143874)
* Allow overriding blueprinted templates

* Remove duplicated line
2025-04-30 12:14:28 +02:00
Petar Petrov a7af0eaccd Add retry restore step to ZWave-JS migration (#143934)
* Add retry restore step to ZWave-JS migration

* improve test
2025-04-30 11:54:50 +02:00
epenet 4ac29c6aef Remove redundant turn_on/turn_off methods in samsungtv (#143939) 2025-04-30 11:47:39 +02:00
epenet ef023f084b Ensure port is stored and used in SamsungTV legacy bridge (#143940)
* Ensure port is stored and used in SamsungTV legacy bridge

* Tweak
2025-04-30 11:47:28 +02:00
Simone Chemelli 441bca5bda Use CONF_PIN in SamsungTv config flow (#143621)
* Use CONF_PIN in SamsunTv config flow

* Adjust tests

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-04-30 11:26:20 +02:00
Robert Resch a8bee20aa3 Add Nuki brand with Matter support (#143904) 2025-04-30 11:14:19 +02:00
Martin Hjelmare 04bea9c732 Handle Z-Wave migration low SDK version (#143936) 2025-04-30 11:43:05 +03:00
epenet 98cbc2a182 Add extra logging in samsungtv (#143933)
* Cache and reuse REST client in samsungtv

* Add logging
2025-04-30 10:22:36 +02:00
epenet 09518b1a71 Remove redundant Renault test fixtures (#143929)
Remove redundant Renault fixtures
2025-04-30 10:05:29 +02:00
epenet 42d22bb1a3 Use unique registration number in renault tests (#143926) 2025-04-30 10:05:00 +02:00
tmenguy 69c387a360 Improve Renault plug status binary sensor (#143931)
improve binary plug sensor
2025-04-30 10:03:01 +02:00
J. Nick Koston 4b6fa12925 Make name a top-level key for SSDP discovery WebSocket API (#143923) 2025-04-30 09:40:15 +02:00
epenet c562cba030 Use unique VIN in renault tests (#143925) 2025-04-30 08:48:49 +02:00
epenet 40764b6995 Cleanup renault test constants (#143924)
* More

* tweak

* Adjust

* docstring

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-04-30 08:32:07 +02:00
J. Nick Koston 4ee3290929 Improve ESPHome dashboard diagnostics (#143914) 2025-04-30 08:19:16 +02:00
epenet dc02c37413 Use snapshot_platform in renault tests (#143864)
* Use snapshot_platform in renault tests

* More

* tweak

* Improve
2025-04-30 08:08:24 +02:00
Manu 34becb541a add verify_ssl config flow option to ntfy integration (#143731)
* add verfy_ssl option

* changes
2025-04-30 08:07:58 +02:00
Martin Hjelmare eabf88e3c9 Fix Z-Wave USB discovery already configured (#143907)
Fix zwave usb discovery already configured
2025-04-30 07:40:18 +02:00
J. Nick Koston c3dac50f21 Bump bluemaestro-ble to 0.4.0 (#143922) 2025-04-30 07:39:04 +02:00
J. Nick Koston f7240b52c5 Bump leaone-ble to 0.3.0 (#143921) 2025-04-30 07:38:51 +02:00
J. Nick Koston 2112b5a763 Bump thermopro-ble to 0.13.0 (#143920) 2025-04-30 07:38:34 +02:00
J. Nick Koston 03b10b45c4 Bump sensorpro-ble to 0.7.0 (#143919) 2025-04-30 07:38:13 +02:00
J. Nick Koston 62361230f3 Bump thermobeacon-ble to 0.10.0 (#143918) 2025-04-30 07:37:50 +02:00
J. Nick Koston 653306eb91 Bump sensorpush-ble to 1.9.0 (#143917) 2025-04-30 07:37:37 +02:00
J. Nick Koston 07e2cfb736 Bump inkbird-ble to 0.15.0 (#143916) 2025-04-30 07:36:48 +02:00
Paulus Schoutsen f980434046 Clean up Text-to-Speech (#143744) 2025-04-30 04:29:35 +02:00
Marc Mueller 97084e9382 Remove redundant typing cast in miele (#143913) 2025-04-30 03:13:23 +02:00
J. Nick Koston 9db34fe232 Bump habluetooth to 3.45.0 (#143909) 2025-04-30 00:05:33 +02:00
Åke Strandberg c4f0b4ab23 Bump pymiele to 0.4.1 (#143903) 2025-04-30 00:55:23 +03:00
Brian Choromanski 1647afc58a Improve parse_time_expression list comprehension to get interval values (#143488) 2025-04-29 22:20:05 +01:00
Martin Hjelmare 53ea8422f8 Improve Z-Wave hassio confirm form text (#143908) 2025-04-29 22:57:30 +02:00
Jan Bouwhuis 0b988b3fac Bump incomfort-client to v0.6.8 (#143895)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-04-29 22:05:52 +02:00
Thomas55555 5a4abe3ec1 Bump apsystems-ez1 to 2.6.0 (#143897)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-04-29 21:36:13 +02:00
J. Nick Koston 89abc5ac69 Add WebSocket API to ssdp to observe discovery (#143862) 2025-04-29 21:03:53 +02:00
Norbert Rittel 08fe6653bb Add missing hyphen to "self-test" in weheat (#143899) 2025-04-29 22:02:24 +03:00
Norbert Rittel 9aa18c7157 Add missing hyphen to "self-check" in incomfort (#143900) 2025-04-29 20:59:15 +02:00
Retha Runolfsson cc7929f8fb Add log when device is online and unavailable (#143648) 2025-04-29 20:52:12 +02:00
Josef Zweck d657298791 Add statistic entities to lamarzocco (#143415)
* Bump pylamarzocco to 2.0.0b2

* Add statistic entities to lamarzocco

* add icons

* Update coordinator.py

* update uom

* Update homeassistant/components/lamarzocco/sensor.py

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* revert cups

* remove unnecessary call (for now)

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2025-04-29 20:49:26 +02:00
Jan Bouwhuis 05f393560f Fix mcp_server CI test (#143898) 2025-04-29 20:40:50 +02:00
Abílio Costa 92da640d4c Rename const maps in Whirlpool (#143409) 2025-04-29 20:39:29 +02:00
Manu ad3fd151aa Add reconfiguration flow to ista EcoTrend integration (#143457) 2025-04-29 20:37:04 +02:00
Simon Lamon cd104dc08c LinkPlay group members should return the entity ids (#141791) 2025-04-29 20:28:08 +02:00
Norbert Rittel d3745d2519 Add missing hyphens to "self-…" in imeon_inverter (#143888)
* Add missing hyphens to "self-…" in `imeon_inverter`

* Update test_sensor.ambr
2025-04-29 21:01:49 +03:00
Norbert Rittel 931f3fa41a Fix spelling of "self-consumption" in tessie/tesla_fleet/teslemetry (#143890)
* Fix spelling of "self-consumption" in `tessie`

* Fix spelling of "self-consumption" in `tesla_fleet`

* Fix spelling of "self-consumption" in `teslemetry`
2025-04-29 21:01:41 +03:00
Norbert Rittel 87b5a91212 Add missing hyphen to "self-clean" in roborock (#143893)
Fix four states that contain "self-clean" by adding the missing hyphen.
2025-04-29 21:01:35 +03:00
Norbert Rittel 3b8da62d84 Make spelling of "self-consumption" consistent in growatt_server (#143886)
Also fix one overlooked sentence-casing.
2025-04-29 21:01:08 +03:00
Norbert Rittel 86a48294f4 Change all imap action descriptions to match HA style (#143894)
Change all `imap` action description to match HA style

Change all four descriptions to use third-person singular to ensure proper (machine) translations.
2025-04-29 20:59:06 +03:00
Ville Skyttä a03884981f Prefer huawei_lte SSDP model name over friendly name (#143725) 2025-04-29 19:25:32 +02:00
Ville Skyttä ab695f90c7 Upgrade url-normalize to 2.2.1 (#143751) 2025-04-29 19:10:57 +02:00
Allen Porter efcf8f9555 Improve TurnOn/Off LLM tool descriptions (#143768) 2025-04-29 19:09:05 +02:00
epenet f71903a563 Simplify device registry checks in renault tests (#143863) 2025-04-29 19:03:14 +02:00
Petro31 95552e9a5b Add trigger based template lights (#140631)
* Add abstract template light class in preparation for trigger based template lights

* add base for trigger entity

* Update more tests

* revert trigger template entity changes and light trigger tests.

* fix merge conflicts

* address comments

* change function name

* nitpick

* fix merge conflict issue

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-04-29 18:02:44 +02:00
Åke Strandberg 5da57271b2 Add 3 duration sensors to miele (#143160)
* Add 3 duration sensors

* Update snapshot

* Address review comments

* Cleanup

* Adjust type hint
2025-04-29 17:53:24 +02:00
Norbert Rittel 62a7139f4d Fix hyphens on "self-consumption"/"serial number" in enphase_envoy (#143887) 2025-04-29 17:29:48 +02:00
Franck Nijhof a7be26cd95 Merge branch 'master' into dev 2025-04-29 15:17:37 +00:00
J. Nick Koston 9c3b0952e0 Turn off autospec for zeroconf mocks (#143879) 2025-04-29 16:45:58 +02:00
J. Nick Koston c771f446b4 Bump aioesphomeapi to 30.1.0 (#143881) 2025-04-29 16:13:30 +02:00
Petro31 9a25561017 Fix duplicate code from merge conflict (#143880)
fix conflict
2025-04-29 16:09:08 +02:00
Manuel Stahl bd870f0537 Remove dependency on modbus for stiebel_eltron (#136482)
* Remove dependency on modbus for stiebel_eltron

The modbus integration changed its setup, so it is
not possible anymore to have an empty hub.

* Add config flow

* Update pystiebeleltron to v0.1.0

* Fix

* Fix

* Add test for non existing modbus hub

* Fix tests

* Add more tests

* Add missing translation string

* Add test for import failure

* Fix issues from review comments

* Fix issues from review comments

* Mock stiebel eltron client instead of setup_entry

* Update homeassistant/components/stiebel_eltron/__init__.py

* Update homeassistant/components/stiebel_eltron/__init__.py

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-29 15:34:16 +02:00
Manuel Stahl d7f43bddfa Remove dependency on modbus for stiebel_eltron (#136482)
* Remove dependency on modbus for stiebel_eltron

The modbus integration changed its setup, so it is
not possible anymore to have an empty hub.

* Add config flow

* Update pystiebeleltron to v0.1.0

* Fix

* Fix

* Add test for non existing modbus hub

* Fix tests

* Add more tests

* Add missing translation string

* Add test for import failure

* Fix issues from review comments

* Fix issues from review comments

* Mock stiebel eltron client instead of setup_entry

* Update homeassistant/components/stiebel_eltron/__init__.py

* Update homeassistant/components/stiebel_eltron/__init__.py

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-29 14:57:01 +02:00
Åke Strandberg 87107c5a59 Add log of missing codes to miele diagnostics (#143877)
Add missing code log to diagnostics
2025-04-29 14:56:45 +02:00
Patrick 9ce920b35a Add support for external USB drives to Synology DSM (#138661)
* Add external usb drives

* Add partition percentage used

* Move icons to icons.json

* Add external usb to diagnostics

* Add assert for external usb entity

* Fix reset external_usb

* Update homeassistant/components/synology_dsm/diagnostics.py

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

* Update homeassistant/components/synology_dsm/diagnostics.py

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

* Fix diagnostics

* Make each partition a device

* Add usb sensor tests

* Add diagnostics tests

* It is possible that api.external_usb is None

* Merge upstream into syno_external_usb

* add manufacturer and model to partition

* fix tests

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
Co-authored-by: mib1185 <mail@mib85.de>
2025-04-29 13:32:21 +02:00
J. Diego Rodríguez Royo 15aff9662c Refresh Home Connect program entities possible options when an appliance gets connected (#143213)
Refresh options when an appliance gets connected
2025-04-29 13:12:21 +02:00
Åke Strandberg da6fb91886 Add some more sensors to miele integration (#142979)
* Add some more sensors

* Add some debug logging and correct spelling

* Address review comments

* Split out duration sensors to separate PR

* Update strings

* Filter program phases by device type

* Update tests

* Fix auto link

* Address som of the comments

* Lint

* Lint

* Remove duplicates from enum sensor options

* Update snapshot

* Sort options in enum sensors
2025-04-29 13:07:55 +02:00
Alex Fuchs 1e880f7406 Bump apsystems-ez1 to 2.5.1 (#143739)
Bump apsystems-ez1 to 2.5.1 to fix debounce problem
2025-04-29 13:04:57 +02:00
Matrix 81153042d3 Bump YoLink Lib to v0.5.2 (#143873)
Bump YoLink API to v0.5.2
2025-04-29 12:57:23 +02:00
Michael 493ca261dc Add strict type checking to SMTP integration (#143698) 2025-04-29 12:56:29 +02:00
Ville Skyttä 7493b340ca Add more huawei_lte sensor descriptions (#143707) 2025-04-29 12:54:36 +02:00
Norbert Rittel e85e60ed6a Use common state "Fault" in wolflink (#143688) 2025-04-29 12:53:09 +02:00
chammp 8ff4d5dcbf Adapt template sensors to use the same plural trigger/condition/action definitions as automations (#127875)
* Add plurals to template entities

* Ruff

* Ruffy ruff

* Fix linters

* Fix bug introduced after merging dev

* Fix merge mistake

* Revert adding automation helper

* Revert "Fix bug introduced after merging dev"

This reverts commit 098d478f150a06546fb9ec3668865fa5d763c6b2.

* Fix blueprint validation

* Apply suggestions from code review

---------

Co-authored-by: Erik <erik@montnemery.com>
2025-04-29 11:52:58 +02:00
Norbert Rittel f2838e493b Use common state for "Fault" in peblar (#143708) 2025-04-29 11:39:21 +02:00
Åke Strandberg a71edcf1a1 Add fan platform to miele integration (#143772)
* Add fan platform

* Fix after review comment

* Address review comments

* Remove commented code

* Update tests

* Use constant
2025-04-29 10:48:56 +02:00
Arie Catsman 47bef74e7c apply for platinum quality scale for enphase_envoy (#143846) 2025-04-29 10:41:22 +02:00
Erwin Douna b757a7e3fe Replace pymelcloud with python-melcloud (#142120) 2025-04-29 10:38:00 +02:00
dependabot[bot] 362ff5724d Bump actions/attest-build-provenance from 2.2.3 to 2.3.0 (#143865) 2025-04-29 10:31:16 +02:00
Joost Lekkerkerker 4f8363a5c2 Add availability to SmartThings devices (#143836)
* Bump pySmartThings to 3.1.0

* Bump pySmartThings to 3.2.0

* Add availability to SmartThings devices

* Add availability to SmartThings devices

* Add availability to SmartThings devices
2025-04-29 10:29:07 +02:00
Maciej Bieniek ae3925118c Do not allow to enable BT scanner for Shelly Gen4 device with Zigbee enabled (#143824)
* Bluetooth is not supported when Zigbee is enabled

* Update tests

* Format
2025-04-29 11:12:34 +03:00
Petro31 b2fcab20a6 Add trigger based entities to template switch (#141763)
* Add trigger based entities to template switch platform

* add suggestions
2025-04-29 09:40:16 +02:00
Brett Adams 6423957d29 Add common translations to Sentry in Teslemetry (#143868)
missing translation keys
2025-04-29 09:26:19 +02:00
Brett Adams 835cdad0a9 Add sentry mode sensor to Teslemetry (#143855)
* Add sentry mode sensor

* Fix state handler
2025-04-29 08:37:10 +02:00
Klaas Schoute d8d6decb38 Bump odp-amsterdam to v6.1.1 (#143854) 2025-04-29 08:35:56 +02:00
Erik Montnemery 16b42cc109 Add cv.renamed (#143834) 2025-04-29 07:36:37 +02:00
tmenguy a47f27821f Add some tests with an invalid plugStatus and renault twingo iii. (#143838) 2025-04-28 22:31:27 +02:00
Daniel Hjelseth Høyer c797e7a973 Mill, add statistics (#130406)
* Mill, new features

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* typo

* tests

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mill

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* Update const.py

* Update sensor.py

* Update sensor.py

* Add test

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* Add test

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mill

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mock_setup_entry

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* after_depencies

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* Mill

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mill stats

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mill stats

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* format

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mill

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* Add test

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* tests

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* mill

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

---------

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2025-04-28 21:59:42 +02:00
Norbert Rittel 245eb64405 Fix spelling of "self-test" in apcupsd (#143843) 2025-04-28 22:35:16 +03:00
Martin Hjelmare a895fcf057 Bump zwave-js-server-python to 0.63.0 (#143844) 2025-04-28 21:34:47 +02:00
Norbert Rittel 5706fb26b8 Make spelling of "self-test" consistent in zha (#143842)
While the "self-test" button already contains the recommended hyphen it's missing in the switch and sensor entity names.
2025-04-28 21:07:50 +03:00
Åke Strandberg 3f82120cdc Add miele core temp sensors (#143785)
Add core temp sensors
2025-04-28 18:47:42 +02:00
Brett Adams 20df183470 Improve energy entities in Teslemetry (#143641)
* Energy fixes

* improvements

* Add more icons
2025-04-28 18:47:12 +02:00
dependabot[bot] 980216795f Bump docker/build-push-action from 6.15.0 to 6.16.0 (#143651)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/471d1dc4e07e5cdedd4c2171150001c434f0b7a4...14487ce63c7a62a4a324b0bfb37086795e31c6c1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 16:50:07 +02:00
Joost Lekkerkerker fdfcd841ba Bump pySmartThings to 3.2.0 (#143833)
* Bump pySmartThings to 3.1.0

* Bump pySmartThings to 3.2.0
2025-04-28 16:23:06 +02:00
Josef Zweck 28a09794e9 Bump pylamarzocco to 2.0.0b6 (#143778)
* Bump pylamarzocco to 2.0.0b5

* bump to 6
2025-04-28 16:22:46 +02:00
David Knowles a0c9217375 Schlage: Source valid auto lock times from pyschlage (#143382)
* Source auto lock times from pyschlage

* Update auto lock strings

* Test all options are translated
2025-04-28 14:45:38 +02:00
Petro31 469176c59b Fix trigger template entity issue when coordinator data is None (#143830)
Fix issue when coordinator data is None
2025-04-28 14:32:16 +02:00
Erik Montnemery 3ece672890 Update rainforest_raven test snapshots (#143829) 2025-04-28 14:04:10 +03:00
Brett Adams c6ebba8843 Add streaming connectivity binary sensors to Teslemetry (#143443)
* Add connectivity entities

* Add connectivity entities

* Fix Wi-Fi spelling in Teslemetry component
2025-04-28 12:58:40 +02:00
Erik Montnemery 1f047807a4 Update netatmo test snapshots (#143828) 2025-04-28 13:48:50 +03:00
Erik Montnemery f1b724c49a Update samsungtv test snapshots (#143826) 2025-04-28 13:48:39 +03:00
J. Nick Koston 5ebed2046c Bump bluetooth-data-tools to 1.28.1 (#143817) 2025-04-28 12:05:07 +02:00
Arie Catsman d1236a53b8 add enphase_envoy interface mac to device registry (#143758)
* add enphase_envoy interface mac to device registry

* Test for capitalized error log entry.

* increase mac collection delay from 17 to 34 sec
2025-04-28 11:20:11 +02:00
Tsvi Mostovicz 84f07ee992 Bump hdate to 1.1.0 (#143759) 2025-04-28 10:38:49 +02:00
Franck Nijhof 360bffa3a9 2025.4.4 (#143653) 2025-04-25 09:47:05 +02:00
Franck Nijhof 2214d9b330 Bump version to 2025.4.4 2025-04-25 06:54:02 +00:00
Joost Lekkerkerker 6a2d733d85 Bump pysmartthings to 3.0.5 (#143586) 2025-04-25 06:53:49 +00:00
cnico 7392d5a30a Bump dio-chacon-api to v1.2.2 (#143489)
Bump dio-chacon-api to v1.2.2 to solve https://github.com/home-assistant/core/issues/142808
2025-04-25 06:53:48 +00:00
J. Nick Koston b3deeca939 Bump aiohomekit to 3.2.14 (#143440) 2025-04-25 06:53:47 +00:00
Simone Chemelli c38a3a239c Fix Vodafone Station config entry unload (#143371) 2025-04-25 06:53:45 +00:00
Simon Lamon afa6ed09ef Sync random sensor device classes (#143368) 2025-04-25 06:53:44 +00:00
Simon Lamon deb966128f Add scan interval and parallel updates to LinkPlay media player (#143324) 2025-04-25 06:53:43 +00:00
Marc Mueller 73707fa231 Fix licenses check for setuptools (#143292) 2025-04-25 06:53:41 +00:00
Marc Mueller 10ac39f6b2 Update setuptools to 78.1.1 (#143275) 2025-04-25 06:53:40 +00:00
Arjan 2e05dc8618 Météo-France: Additional states and change weather condition for "Ciel clair" (#143198)
* Additional new states and change for ciel-clair

* Adding new previously unmapped state

* Adding new forecast state

Adding Brouillard dense, reported after the review
2025-04-25 06:53:39 +00:00
J. Diego Rodríguez Royo d8233b4de5 Create Home Connect active and selected program entities only when there are programs (#143185)
* Create active and selected program entities only when there are programs

* Test improvements
2025-04-25 06:53:37 +00:00
Arjan 7cbc3ea65f Meteofrance: adding new states provided by MF API since mid April (#143137) 2025-04-25 06:53:36 +00:00
Franck Nijhof 6f0a9910ea 2025.4.3 (#143253) 2025-04-19 12:22:36 +02:00
Franck Nijhof b8793760a1 Bump version to 2025.4.3 2025-04-19 09:17:04 +00:00
Joost Lekkerkerker 6264f9c67b Fix missing binary sensor for CoolSelect+ in SmartThings (#143216) 2025-04-19 09:16:46 +00:00
Joost Lekkerkerker 2a74deb84e Fix SmartThings soundbar without media playback (#143170) 2025-04-19 09:16:45 +00:00
puddly 9d1ff37a79 Bump ZHA to 0.0.56 (#143165)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-04-19 09:16:43 +00:00
Franck Nijhof 2f99164781 Reduce jumping Starlink uptime sensor (#143076) 2025-04-19 09:16:42 +00:00
Marc Mueller 80ef32f09d Add Python-2.0 to list of approved licenses (#143052) 2025-04-19 09:16:40 +00:00
G Johansson 63be0e2e1a Bump pysmhi to 1.0.2 (#143007)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-04-19 09:16:39 +00:00
Simone Chemelli 74c4553bb0 Increase uptime deviation for Shelly (#142996)
* Increase uptime deviation for Shelly

* fix test

* make troubleshooting easy

* change deviation interval

* increase deviation to 1m
2025-04-19 09:16:38 +00:00
starkillerOG e240707b32 Bump reolink-aio to 0.13.2 (#142985) 2025-04-19 09:16:36 +00:00
Simone Chemelli 7c867852a9 Fix switch state for Comelit (#142978) 2025-04-19 09:16:35 +00:00
G Johansson 2d149dc746 Bump holidays to 0.70 (#142954) 2025-04-19 09:16:33 +00:00
Alex L 7edcddd3e4 Update UK Transport Integration URL (#142949) 2025-04-19 09:16:32 +00:00
Tsvi Mostovicz 71f658b560 Don't do I/O while getting Jewish calendar data schema (#142919) 2025-04-19 09:16:31 +00:00
Guido Schmitz 9886db5d6d Bump devolo_plc_api to 1.5.1 (#142908) 2025-04-19 09:16:29 +00:00
Glenn Waters c236cd070c Bump Environment Canada library to 0.10.1 (#142882) 2025-04-19 09:16:28 +00:00
Kevin Stillhammer 9f1a830d32 Only get tracked pairs for kraken (#142877)
Only get tracked pairs

Getting all available pairs leads to a too long request URL
2025-04-19 09:16:26 +00:00
Allen Porter 1e69ce9111 Fix quality loss for LLM conversation agent question answering (#142873)
* Fix a bug parsing a streaming response with no json

* Remove debug lines

* Fix  quality loss for LLM conversation agent question answering

* Update tests
2025-04-19 09:15:41 +00:00
starkillerOG 389297155d Fix Reolink Home Hub Pro playback (#142871)
Fix Home Hub Pro playback
2025-04-19 09:12:22 +00:00
starkillerOG c341b86520 Select correct Reolink device uid (#142864)
* Select correct device_uid

* Fix styling

* restructure

* Add test

* Update test_util.py

* Add explanation string
2025-04-19 09:12:21 +00:00
Eric Park 88eef379b2 Keep track of last play status update time in Apple TV (#142838) 2025-04-19 09:12:20 +00:00
peteS-UK 34767d4058 Force Squeezebox item id to string (#142793)
force item_id to string
2025-04-19 09:12:18 +00:00
Dionisis Toulatos 12c3d54a63 Fix MQTT device discovery when using node_id (#142784)
* Fix device discovery when using node_id

* tests

---------

Co-authored-by: jbouwh <jan@jbsoft.nl>
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-04-19 09:12:16 +00:00
Manu 33a185dade Fix error in recurrence calculation of Habitica integration (#142759)
Fix error in rrule calculation of Habitica integration
2025-04-19 09:12:15 +00:00
Erik Montnemery c1c5776d85 Correct enum member check in home_connect (#142666)
* Correct enum member check in home_connect

* Update homeassistant/components/home_connect/coordinator.py

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

* Add mypy override

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-04-19 09:12:14 +00:00
Brett Adams eda642554d Check Energy Live API works before creating the coordinator in Tessie (#142510)
* Check live API works before creating the coordinator

* Fix diag

* Fix mypy on entity

* is not None
2025-04-19 09:12:11 +00:00
Chase Mamatey 51f5ce013f Fix duke_energy data retrieval to adhere to service start date (#136054) 2025-04-19 09:12:10 +00:00
Franck Nijhof f7794ea6b5 2025.4.2 (#142755) 2025-04-12 11:43:50 +02:00
Sanjay Govind 7a1bea7ff5 Add jaraco.itertools license exception as the classifier was removed but no SPDX expression was added (#142439) 2025-04-11 21:35:35 +00:00
Thomas55555 c7c645776d Bump ical to 9.1.0 (#142197) 2025-04-11 21:35:28 +00:00
Franck Nijhof 667cb772e9 Bump version to 2025.4.2 2025-04-11 16:12:45 +00:00
Jeff Rescignano 933d008e52 Upgrade sharkiq depedency to 1.1.0 (#142746) 2025-04-11 16:12:06 +00:00
Allen Porter d868f39aea Fix Anthropic bug parsing a streaming response with no json (#142745) 2025-04-11 15:42:08 +00:00
Joost Lekkerkerker 28d776a0b0 Fix SmartThings gas meter (#142741) 2025-04-11 15:42:04 +00:00
Joost Lekkerkerker b5d541b596 Bump pySmartThings to 3.0.4 (#142739) 2025-04-11 15:41:59 +00:00
Bram Kragten 4948499889 Update frontend to 20250411.0 (#142736) 2025-04-11 15:41:55 +00:00
starkillerOG 7696b101f6 Reolink migrate unique ID debugging (#142723)
* Filter out unexpected unique_ids

* correct

* Add test

* fix styling
2025-04-11 15:41:51 +00:00
starkillerOG fd2987a9fd Bump reolink-aio 0.13.1 (#142719) 2025-04-11 15:41:47 +00:00
Christopher Fenner 4c1d32020a Bump PyViCare to 2.44.0 (#142701)
bump vicare to v2.44.0
2025-04-11 15:41:42 +00:00
Jan Bouwhuis b40bdab0ae Fix EC certificate key not allowed in MQTT client setup (#142698) 2025-04-11 15:41:38 +00:00
Simone Chemelli d192aecd3b Comelit config flow timeout error (#142667) 2025-04-11 15:41:34 +00:00
Thomas55555 d1781f5766 Bump livisi to 0.0.25 (#142638) 2025-04-11 15:41:29 +00:00
henryptung 2c4461457a Bump led_ble to 1.1.7 (#142629)
changelog: https://github.com/Bluetooth-Devices/led-ble/compare/v1.1.6...v1.1.7
2025-04-11 15:40:35 +00:00
J. Nick Koston 82959081de Pin multidict to >= 6.4.2 to resolve memory leaks (#142614)
* Pin multidict to >= 6.4.1 to resolve memory leaks

https://github.com/aio-libs/multidict/issues/1134
https://github.com/aio-libs/multidict/issues/1131
https://github.com/aio-libs/multidict/releases/tag/v6.4.1
https://github.com/aio-libs/multidict/releases/tag/v6.4.0

* Apply suggestions from code review
2025-04-11 15:39:38 +00:00
Thimo Seitz acdac6d5e8 Update growatt server dependency to 1.6.0 (#142606)
* Update GrowattServer Dependency

* Update requirements_test_all.txt
2025-04-11 15:39:34 +00:00
Fredrik Erlandsson d3d7889883 Fix ssl_cert load from config_flow (#142570)
fix ssl_cert load from config_flow
2025-04-11 15:39:29 +00:00
puddly 60ece3e1c9 Fix Core deadlock by ensuring only one ZHA log queue handler thread is running at a time (#142568)
Ensure only one log queue handler is running at a time
2025-04-11 15:39:25 +00:00
Christopher Fenner a9f8529460 Fix Quickmode handling in ViCare integration (#142561)
* only check quickmode if supported

* update snapshot

* revert
2025-04-11 15:39:20 +00:00
Andrew Sayre ec53b61f9e Bump pyheos to v1.0.5 (#142554)
Update pyheos
2025-04-11 15:39:16 +00:00
Thomas55555 e9f02edd8b Fix adding devices in Husqvarna Automower (#142549) 2025-04-11 15:39:12 +00:00
Marcel van der Veldt d1b7898219 Fix small typo in Music Assistant integration causing unavailable players (#142535)
Fix small typo in Music Assistant integration causing issues with adding players
2025-04-11 15:39:08 +00:00
Jan Bouwhuis 8dc21ef619 Allow max to be equal with min for mqtt number config validation (#142522) 2025-04-11 15:39:03 +00:00
tronikos d9f91598a5 Fix range of Google Generative AI temperature (#142513) 2025-04-11 15:34:29 +00:00
Ivan Lopez Hernandez c540acf2bd Handle None on the response candidates in Google Generative AI (#142497)
* Added type checking on the candidates list

* Made error message a constant
2025-04-11 15:34:24 +00:00
Maciej Bieniek f702f3efcd Fix Shelly initialization if device runs large script (#142487)
* Don't check the whole script to see if it generates events

* Fix tests

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-04-11 15:34:20 +00:00
Wilfred Ketelaar 9410061405 Fixed Renault charge state icon (#142478)
Fixed charge state icon (duplicate mdi prefix)
2025-04-11 15:34:15 +00:00
Maciej Bieniek 485b28d9ea Bump aioshelly to version 13.4.1 (#142477)
* Bymp aioshelly to 13.4.1

* Catch InvalidHostError

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-04-11 15:34:10 +00:00
epenet d59200a9f5 Fix kelvin parameter in light action specifications (#142456) 2025-04-11 15:34:05 +00:00
starkillerOG 44a92ca81c Fix Reolink smart AI sensors (#142454) 2025-04-11 15:34:01 +00:00
J. Nick Koston d39fa39a03 Fix HKC showing hvac_action as idle when fan is active and heat cool target is off (#142443)
* Fix HKC showing hvac_action as idle when fan is active and heat cool target is off

fixes #142442

* comment relocation
2025-04-11 15:33:56 +00:00
Michael 36ec857523 Fix reload of AVM FRITZ!Tools when new connected device is detected (#142430) 2025-04-11 15:33:51 +00:00
Simone Chemelli fcb8cdc146 Add missing strings to Fritz (#142413)
* Add missing strings to Fritz

* update quality scale

* add common section

this avoids later re-structuring and re-translating

* fix strings

* fix strings

* apply review comment

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2025-04-11 15:33:46 +00:00
Simone Chemelli 2322b0b65f Add exceptions translation to SamsungTV (#142406)
* Add exceptions translation to SmasungTV

* Update strings.json

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2025-04-11 15:33:42 +00:00
tronikos 87baaf4255 Bump opower to 0.11.1 (#142395)
* Bump opower to 0.10.1

* opower==0.11.0

* opower==0.11.1

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-04-11 15:33:37 +00:00
J. Nick Koston b7f0e877f0 Bump aioesphomeapi to 29.9.0 (#142393)
changelog: https://github.com/esphome/aioesphomeapi/compare/v29.8.0...v29.9.0

fixes #142381
2025-04-11 15:33:33 +00:00
Jan-Philipp Benecke 5d92a04732 Only load files ending .metadata.json in WebDAV (#142388) 2025-04-11 15:33:29 +00:00
Álvaro Fernández Rojas 8ff879df22 Update aioairzone to v1.0.0 (#142385)
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2025-04-11 15:33:24 +00:00
J. Nick Koston 9fb7ee676e Bump flux_led to 1.2.0 (#142362)
changelog: https://github.com/lightinglibs/flux_led/compare/1.1.3...1.2.0
2025-04-11 15:33:20 +00:00
Jan Bouwhuis 2c855a3986 Limit mqtt info logging for discovery of new components (#142344)
* Limit mqtt info logging for discovery of new component

* Keep in bail out, when debug logging is not enabled
2025-04-11 15:33:14 +00:00
Luke Lashley cdd4894e30 Check that the current roboorck map exists before updating it. (#142341)
* Check that the current map exists

* Add a few extra checks

* Update coordinator.py

Co-authored-by: Allen Porter <allen.porter@gmail.com>

* fixlint

---------

Co-authored-by: Allen Porter <allen.porter@gmail.com>
2025-04-11 15:33:09 +00:00
tronikos 5f26226712 Add a description for the enable_google_search_tool option in Google AI (#142322)
* Add a description for the enable_google_search_tool option in Google AI

* Use quotes
2025-04-11 15:33:05 +00:00
tronikos 8baf61031d Bump opower to 0.10.0 (#142321) 2025-04-11 15:33:00 +00:00
Andre Lengwenus e90ba40553 Add SensorDeviceClass and unit for LCN CO2 sensor. (#142320)
Add SesnorDeviceClass and unit for LCN CO2 sensor.
2025-04-11 15:32:54 +00:00
Luke Lashley b38016425f Update Roborock map more consistently on state change (#142228)
* update map more consistently on state change

* Makecoordinator keep track of last_updated_state
2025-04-11 15:32:49 +00:00
Thomas55555 ee5e3f7691 Add error details in remote calendar flow (#141753)
* Add error details in remote calendar flow

* no args

* adjust

* json

* Apply suggestions

* remove description placeholder
2025-04-11 15:32:41 +00:00
Franck Nijhof 7af6a4f493 2025.4.1 (#142299)
* Fix blocking event loop - daikin (#141442)

* fix blocking event loop

* create ssl_context directly

* update manifest

* update manifest.json

* Made Google Search enable dependent on Assist availability (#141712)

* Made Google Search enable dependent on Assist availability

* Show error instead of rendering again

* Cleanup test code

* Fix humidifier platform for Comelit (#141854)

* Fix humidifier platform for Comelit

* apply review comment

* Bump evohome-async to 1.0.5 (#141871)

bump client to 1.0.5

* Replace "to log into" with "to log in to" in `incomfort` (#142060)

* Replace "to log into" with "to log in to" in `incomfort`

Also fix one missing sentence-casing of "gateway".

* Replace duplicate "data_description" strings with references

* Avoid unnecessary reload in apple_tv reauth flow (#142079)

* Add translation for hassio update entity name (#142090)

* Bump pyenphase to 1.25.5 (#142107)

* Hide broken ZBT-1 config entries on the hardware page (#142110)

* Hide bad ZBT-1 config entries on the hardware page

* Set up the bad config entry in the unit test

* Roll into a list comprehension

* Remove constant changes

* Fix condition in unit test

* Bump pysmhi to 1.0.1 (#142111)

* Avoid logging a warning when replacing an ignored config entry (#142114)

Replacing an ignored config entry with one from the user
flow should not generate a warning. We should only warn
if we are replacing a usable config entry.

Followup to adjust the warning added in #130567
cc @epenet

* Slow down polling in Tesla Fleet (#142130)

* Slow down polling

* Fix tests

* Bump tesla-fleet-api to v1.0.17 (#142131)

bump

* Tado bump to 0.18.11 (#142175)

* Bump to version 0.18.11

* Adding hassfest files

* Add preset mode to SmartThings climate (#142180)

* Add preset mode to SmartThings climate

* Add preset mode to SmartThings climate

* Do not create a HA mediaplayer for the builtin Music Assistant player (#142192)

Do not create a HA mediaplayer for the builtin Music player

* Do not fetch disconnected Home Connect appliances (#142200)

* Do not fetch disconnected Home Connect appliances

* Apply suggestions

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

* Update docstring

---------

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

* Fix fibaro setup (#142201)

* Fix circular mean by always storing and using the weighted one (#142208)

* Fix circular mean by always storing and using the weighted one

* fix

* Fix test

* Bump pySmartThings to 3.0.2 (#142257)

Co-authored-by: Robert Resch <robert@resch.dev>

* Update frontend to 20250404.0 (#142274)

* Bump forecast-solar lib to v4.1.0 (#142280)

Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>

* Bump version to 2025.4.1

* Fix skyconnect tests (#142262)

fix tests

* Fix empty actions (#142292)

* Apply fix

* Add tests for alarm button cover lock

* update light

* add number tests

* test select

* add switch tests

* test vacuum

* update lock test

---------

Co-authored-by: Fredrik Erlandsson <fredrik.e@gmail.com>
Co-authored-by: Ivan Lopez Hernandez <ivan.lh.94@outlook.com>
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
Co-authored-by: David Bonnes <zxdavb@bonnes.me>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
Co-authored-by: Arie Catsman <120491684+catsmanac@users.noreply.github.com>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Brett Adams <Bre77@users.noreply.github.com>
Co-authored-by: Erwin Douna <e.douna@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>
Co-authored-by: J. Diego Rodríguez Royo <jdrr1998@hotmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: rappenze <rappenze@yahoo.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Klaas Schoute <klaas_schoute@hotmail.com>
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Petro31 <35082313+Petro31@users.noreply.github.com>
2025-04-04 22:59:10 +02:00
Petro31 c25f26a290 Fix empty actions (#142292)
* Apply fix

* Add tests for alarm button cover lock

* update light

* add number tests

* test select

* add switch tests

* test vacuum

* update lock test
2025-04-04 20:18:31 +00:00
Josef Zweck 8d62cb60a6 Fix skyconnect tests (#142262)
fix tests
2025-04-04 20:18:27 +00:00
Franck Nijhof 4f799069ea Bump version to 2025.4.1 2025-04-04 19:24:45 +00:00
Klaas Schoute af708b78e0 Bump forecast-solar lib to v4.1.0 (#142280)
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
2025-04-04 19:24:30 +00:00
Bram Kragten f46e659740 Update frontend to 20250404.0 (#142274) 2025-04-04 19:24:27 +00:00
Joost Lekkerkerker 7bd517e6ff Bump pySmartThings to 3.0.2 (#142257)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-04-04 19:24:23 +00:00
Robert Resch e9abdab1f5 Fix circular mean by always storing and using the weighted one (#142208)
* Fix circular mean by always storing and using the weighted one

* fix

* Fix test
2025-04-04 19:24:20 +00:00
rappenze 86eee4f041 Fix fibaro setup (#142201) 2025-04-04 19:24:17 +00:00
J. Diego Rodríguez Royo 9db60c830c Do not fetch disconnected Home Connect appliances (#142200)
* Do not fetch disconnected Home Connect appliances

* Apply suggestions

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

* Update docstring

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-04-04 19:24:13 +00:00
Marcel van der Veldt c43a4682b9 Do not create a HA mediaplayer for the builtin Music Assistant player (#142192)
Do not create a HA mediaplayer for the builtin Music player
2025-04-04 19:24:10 +00:00
Joost Lekkerkerker 2a4996055a Add preset mode to SmartThings climate (#142180)
* Add preset mode to SmartThings climate

* Add preset mode to SmartThings climate
2025-04-04 19:24:07 +00:00
Erwin Douna 4643fc2c14 Tado bump to 0.18.11 (#142175)
* Bump to version 0.18.11

* Adding hassfest files
2025-04-04 19:24:04 +00:00
Brett Adams 6410b90d82 Bump tesla-fleet-api to v1.0.17 (#142131)
bump
2025-04-04 19:24:00 +00:00
Brett Adams e5c00eceae Slow down polling in Tesla Fleet (#142130)
* Slow down polling

* Fix tests
2025-04-04 19:23:55 +00:00
J. Nick Koston fe65579df8 Avoid logging a warning when replacing an ignored config entry (#142114)
Replacing an ignored config entry with one from the user
flow should not generate a warning. We should only warn
if we are replacing a usable config entry.

Followup to adjust the warning added in #130567
cc @epenet
2025-04-04 19:23:52 +00:00
G Johansson 281beecb05 Bump pysmhi to 1.0.1 (#142111) 2025-04-04 19:23:48 +00:00
puddly 7546b5d269 Hide broken ZBT-1 config entries on the hardware page (#142110)
* Hide bad ZBT-1 config entries on the hardware page

* Set up the bad config entry in the unit test

* Roll into a list comprehension

* Remove constant changes

* Fix condition in unit test
2025-04-04 19:23:45 +00:00
Arie Catsman 490e3201b9 Bump pyenphase to 1.25.5 (#142107) 2025-04-04 19:23:42 +00:00
Paul Bottein 04be575139 Add translation for hassio update entity name (#142090) 2025-04-04 19:23:39 +00:00
Erik Montnemery 854cae7f12 Avoid unnecessary reload in apple_tv reauth flow (#142079) 2025-04-04 19:23:35 +00:00
Norbert Rittel 109d20978f Replace "to log into" with "to log in to" in incomfort (#142060)
* Replace "to log into" with "to log in to" in `incomfort`

Also fix one missing sentence-casing of "gateway".

* Replace duplicate "data_description" strings with references
2025-04-04 19:23:32 +00:00
David Bonnes f8d284ec4b Bump evohome-async to 1.0.5 (#141871)
bump client to 1.0.5
2025-04-04 19:23:28 +00:00
Simone Chemelli 06ebe0810f Fix humidifier platform for Comelit (#141854)
* Fix humidifier platform for Comelit

* apply review comment
2025-04-04 19:23:25 +00:00
Ivan Lopez Hernandez 802ad2ff51 Made Google Search enable dependent on Assist availability (#141712)
* Made Google Search enable dependent on Assist availability

* Show error instead of rendering again

* Cleanup test code
2025-04-04 19:23:22 +00:00
Fredrik Erlandsson 9070a8d579 Fix blocking event loop - daikin (#141442)
* fix blocking event loop

* create ssl_context directly

* update manifest

* update manifest.json
2025-04-04 19:23:18 +00:00
Franck Nijhof e8b2a3de8b 2025.4.0 (#141505) 2025-04-02 18:47:40 +02:00
Joost Lekkerkerker 39549d5dd4 Fix switch name Unknown in SmartThings (#142081)
Fix switch name Unknown
2025-04-02 15:16:50 +00:00
Franck Nijhof 0c19e47bd4 Bump version to 2025.4.0 2025-04-02 15:02:28 +00:00
Michael 05507d77e3 Fix state class for battery sensors in AVM Fritz!SmartHome (#142078)
* set proper state class for battery sensor

* fix tests
2025-04-02 15:02:04 +00:00
Franck Nijhof 94558e2d40 Bump version to 2025.4.0b15 2025-04-02 14:19:49 +00:00
puddly 4f22fe8f7f Translation key for ZBT-1 integration failing due to disconnection (#142077)
Translation key for device disconnected
2025-04-02 14:19:41 +00:00
Marcel van der Veldt 9e7dfbb857 Deprecate None effect instead of breaking it for Hue (#142073)
* Deprecate effect none instead of breaking it for Hue

* add guard for unknown effect value

* revert guard

* Fix

* Add test

* Add test

* Add test

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-02 14:19:38 +00:00
Joost Lekkerkerker 02d182239a Improve SmartThings switch deprecation (#142072) 2025-04-02 14:19:35 +00:00
Joost Lekkerkerker 4e0f581747 Improve SmartThings sensor deprecation (#142070)
* Improve SmartThings sensor deprecation

* Improve SmartThings sensor deprecation

* Improve SmartThings sensor deprecation
2025-04-02 14:19:32 +00:00
Joost Lekkerkerker 42d97d348c Add Eve brand (#142067) 2025-04-02 14:19:29 +00:00
Robert Resch 69380c85ca Bump deebot-client to 12.5.0 (#142046) 2025-04-02 14:19:25 +00:00
Abílio Costa b38c647830 Allow excluding modules from noisy logs check (#142020)
* Allow excluding modules from noisy logs check

* Cache non-excluded modules; hardcode self module name; optimize call

* Address review comments
2025-04-02 14:19:22 +00:00
Petro31 2396fd1090 Fix weather templates using new style configuration (#136677) 2025-04-02 14:19:19 +00:00
Franck Nijhof aa4eb89eee Bump version to 2025.4.0b14 2025-04-02 09:44:23 +00:00
J. Nick Koston 1b1bc6af95 Bump bluetooth-data-tools to 1.26.5 (#142045)
changelog: https://github.com/Bluetooth-Devices/bluetooth-data-tools/compare/v1.26.1...v1.26.5
2025-04-02 09:36:51 +00:00
J. Nick Koston f17003a79c Bump aiohttp to 3.11.16 (#142034)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.15...v3.11.16
2025-04-02 09:34:14 +00:00
TheJulianJES ec70e8b0cd Bump ZHA to 0.0.55 (#142031) 2025-04-02 08:29:26 +00:00
puddly d888c70ff0 Fix entity names for HA hardware firmware update entities (#142029)
* Fix entity names for HA hardware firmware update entities

* Fix unit tests
2025-04-02 08:29:23 +00:00
puddly f29444002e Skip firmware config flow confirmation if the hardware is in use (#142017)
* Auto-confirm the discovery if we detect that the device is already in use

* Add a unit test
2025-04-02 08:29:20 +00:00
Tomek Wasilczyk fc66997a36 Fix warning about unfinished oauth tasks on shutdown (#141969)
* Don't wait for OAuth token task on shutdown

To reproduce the warning:
1. Start authentication with integration using OAuth (e.g. SmartThings)
2. When redirected to external login site, just close the page
3. Settings -> Restart Home Assistant

* Clarify comment

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-04-02 08:29:16 +00:00
Erik Montnemery 35513ae072 Remove unused mypy ignore from google_generative_ai_conversation (#141549) 2025-04-02 08:29:13 +00:00
Franck Nijhof cd363d48c3 Bump version to 2025.4.0b13 2025-04-01 19:12:16 +00:00
G Johansson d47ef835d7 Fix train to for multiple stations in Trafikverket Train (#142016) 2025-04-01 19:11:51 +00:00
Bram Kragten 00177c699e Update frontend to 20250401.0 (#142010) 2025-04-01 19:11:48 +00:00
Joost Lekkerkerker 11b0086a01 Add LG ThinQ event bus listener to lifecycle hooks (#142006) 2025-04-01 19:11:44 +00:00
J. Nick Koston ceb177f80e Bump aiohttp to 3.11.15 (#141967)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.14...v3.11.15

fixes #141855
fixes #141146
2025-04-01 19:10:28 +00:00
Jan Bouwhuis fa3832fbd7 Improve error handling and logging on MQTT update entity state updates when template rederings fails (#141960) 2025-04-01 19:07:10 +00:00
puddly 2b9c903429 Fix data in old SkyConnect integration config entries or delete them (#141959)
* Delete old SkyConnect integration config entries

* Try migrating, if possible

* Do not delete config entries, log a failure
2025-04-01 19:07:07 +00:00
puddly a7c43f9b49 Reload the ZBT-1 integration on USB state changes (#141287)
* Reload the config entry when the ZBT-1 is unplugged

* Register the USB event handler globally to react better to re-plugs

* Fix existing unit tests

* Add an empty `CONFIG_SCHEMA`

* Add a unit test

* Fix unit tests

* Fix unit tests for Linux

* Address most review comments

* Address remaining review comments
2025-04-01 19:07:03 +00:00
Joost Lekkerkerker b428196149 Improve SmartThings deprecation (#141939)
* Improve SmartThings deprecation

* Improve SmartThings deprecation
2025-04-01 19:01:43 +00:00
Erik Montnemery e23da1a90f Fix import issues related to onboarding views (#141919)
* Fix import issues related to onboarding views

* Add ha-intents and numpy to pyproject.toml

* Add more requirements to pyproject.toml

* Add more requirements to pyproject.toml
2025-04-01 19:00:24 +00:00
Ben Jones 3951c2ea66 Handle empty or missing state values for MQTT light entities using 'template' schema (#141177)
* check for empty or missing values when processing state messages for MQTT light entities using 'template' schema

* normalise warning logs

* add tests (one is still failing and I can't work out why)

* fix test

* improve test coverage after PR review

* improve test coverage after PR review
2025-04-01 18:32:50 +00:00
Louis Christ fee152654d Use saved volume when selecting preset in bluesound integration (#141079)
* Use load_preset to select preset as source

* Add tests

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-01 18:32:47 +00:00
Mikko Koo 51073c948c Fix nordpool Not to return Unknown if price is exactly 0 (#140647)
* now the price will return even if it is exactly 0

* now the price will return even if it is exactly 0

* now the price will return even if it is exactly 0

* clean code

* clean code

* update testing code coverage

* change zero testing to SE4

* remove row duplicate

* fix date comments

* improve testing

* simplify if-return-0

* remove unnecessary tests

* order testing rows

* restore test_sensor_no_next_price

* remove_average_price_test

* fix test name
2025-04-01 18:32:44 +00:00
aaronburt 91438088a0 Correct unit conversion for OneDrive quota display (#140337)
* Correct unit conversion for OneDrive quota display

* Convert OneDrive quota values from bytes to GiB in coordinator and update strings
2025-04-01 18:32:39 +00:00
Franck Nijhof 427e1abdae Bump version to 2025.4.0b12 2025-03-31 20:12:58 +00:00
Steven Looman 6e7ac45ac0 Bump async-upnp-client to 0.44.0 (#141946) 2025-03-31 20:12:48 +00:00
Bram Kragten 4b3b9ebc29 Update frontend to 20250331.0 (#141943) 2025-03-31 20:12:43 +00:00
Franck Nijhof 649d8638ed Bump version to 2025.4.0b11 2025-03-31 18:34:34 +00:00
Jan-Philipp Benecke 12c4152dbe Bump aiowebdav2 to 0.4.5 (#141934) 2025-03-31 18:34:25 +00:00
Michael Hansen 8f9572bb05 Add preannounce boolean for announce/start conversation (#141930)
* Add preannounce boolean

* Fix disabling preannounce in wizard

* Fix casing

* Fix type of preannounce_media_id

* Adjust description of preannounce_media_id
2025-03-31 18:34:22 +00:00
Erik Montnemery 6d022ff4e0 Revert PR 136314 (Cleanup map references in lovelace) (#141928)
* Revert PR 136314 (Cleanup map references in lovelace)

* Update homeassistant/components/lovelace/__init__.py

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

* Fix dashboard creation

* Update homeassistant/components/lovelace/__init__.py

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-31 18:34:19 +00:00
Josef Zweck c0c2edb90a Add None check to azure_storage (#141922) 2025-03-31 18:34:16 +00:00
Michael b014219fdd Correct further sensor categorizations in AVM Fritz!Box tools (#141911)
mark margin and attenuation as diagnostic and disable them by default
2025-03-31 18:34:13 +00:00
Joost Lekkerkerker 216b8ef400 Don't create SmartThings entities for disabled components (#141909) 2025-03-31 18:34:10 +00:00
Joost Lekkerkerker f2ccd46267 Fix SmartThings being able to understand incomplete DRLC (#141907) 2025-03-31 18:34:06 +00:00
Dan Raper e16ba27ce8 Bump ohmepy to 1.5.1 (#141879)
* Bump ohmepy to 1.5.1

* Fix types for ohmepy version change
2025-03-31 18:34:03 +00:00
Thomas55555 506526a6a2 Handle 403 error in remote calendar (#141839)
* Handle 403 error in remote calendar

* tests
2025-03-31 18:34:00 +00:00
Franck Nijhof a88678cf42 Fix SmartThings climate entity missing off HAVC mode (#141700)
* Fix smartthing climate entity missing off HAVC mode:

* Fix tests

* Fix test

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-03-31 18:33:57 +00:00
Retha Runolfsson d0b61af7ec Add switchbot cover unit tests (#140265)
* add cover unit tests

* Add unit test for SwitchBot cover

* fix: use mock_restore_cache to mock the last state

* modify unit tests

* modify scripts as suggest

* improve readability

* adjust patch target per review comments

* adjust patch target per review comments

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-03-31 18:33:53 +00:00
Franck Nijhof 04f5315ab2 Bump version to 2025.4.0b10 2025-03-31 08:09:39 +00:00
Paulus Schoutsen 7f9e4ba39e Ensure user always has first turn for Google Gen AI (#141893) 2025-03-31 08:09:10 +00:00
J. Nick Koston 06aaf188ea Fix duplicate call to async_write_ha_state when adding elkm1 entities (#141890)
When an entity is added state is always written in
add_to_platform_finish:

https://github.com/home-assistant/core/blob/7336178e03a80be11f54eadd6833b9a2a40bae30/homeassistant/helpers/entity.py#L1384

We should not do it in async_added_to_hass as well
2025-03-31 08:09:06 +00:00
J. Nick Koston 627f994872 Bump aioesphomeapi to 29.8.0 (#141888)
changelog: https://github.com/esphome/aioesphomeapi/compare/v29.7.0...v29.8.0
2025-03-31 08:09:03 +00:00
J. Nick Koston 9e81ec5aae Handle encryption being disabled on an ESPHome device (#141887)
fixes #121442
2025-03-31 08:09:00 +00:00
Franck Nijhof 69753fca1d Update pvo to v2.2.1 (#141847) 2025-03-31 08:08:57 +00:00
Michael 7773cc121e Fix the entity category for max throughput sensors in AVM Fritz!Box Tools (#141838)
correct the entity category for max throughput sensors
2025-03-31 08:08:54 +00:00
Michael 3aa56936ad Move setup messages from info to debug level (#141834)
move info to debug level
2025-03-31 08:08:51 +00:00
Franck Nijhof e66416c23d Fix hardcoded UoM for total power sensor for Tuya zndb devices (#141822) 2025-03-31 08:08:48 +00:00
Jan Bouwhuis a592feae3d Correct spelling for 'availability` in MQTT translation strings (#141818) 2025-03-31 08:08:45 +00:00
Aidan Timson fc0d71e891 Fix System Bridge wait timeout wait condition (#141811)
* Fix System Bridge wait timeout wait condition

* Add DataMissingException as a timeout condition

* Add tests
2025-03-31 08:08:42 +00:00
Thomas55555 d4640f1d24 Bump ical to 9.0.3 (#141805) 2025-03-31 08:08:39 +00:00
Michael 6fe158836e Add boost preset to AVM Fritz!SmartHome climate entities (#141802)
* add boost preset to climate entities

* add set boost preset test
2025-03-31 08:08:36 +00:00
J. Nick Koston 629c0087f4 Bump PyISY to 3.1.15 (#141778)
changelog: https://github.com/automicus/PyISY/compare/v3.1.14...v3.1.15

fixes #141517
fixes #132279
2025-03-31 08:08:33 +00:00
J. Nick Koston 363bd75129 Fix blocking late import of httpcore from httpx (#141771)
There is a late import that blocks the event loop
in newer version
https://github.com/encode/httpx/blob/9e8ab40369bd3ec2cc8bff37ab79bf5769c8b00f/httpx/_transports/default.py#L75
2025-03-31 08:08:30 +00:00
J. Nick Koston 7592d350a8 Bump aiohomekit to 3.2.13 (#141764)
changelog: https://github.com/Jc2k/aiohomekit/compare/3.2.8...3.2.13
2025-03-31 08:08:27 +00:00
puddly 8ac8401b4e Add helper methods to simplify USB integration testing (#141733)
* Add some helper methods to simplify USB integration testing

* Re-export `usb_device_from_port`
2025-03-31 08:08:24 +00:00
Joost Lekkerkerker eed075dbfa Bump pySmartThings to 3.0.1 (#141722) 2025-03-31 08:08:21 +00:00
Florent Thoumie 23dbdedfb6 Bump iaqualink to 0.5.3 (#141709)
* Update to iaqualink 0.5.3 and silence warning

* Update to iaqualink 0.5.3 and silence warning

* Re-add via_device line
2025-03-31 08:08:18 +00:00
Franck Nijhof 85ad29e28e Ensure EcoNet operation modes are unique (#141689) 2025-03-31 08:08:15 +00:00
Michal Schwarz 35fc81b038 Fix order of palettes, presets and playlists in WLED integration (#132207)
* Fix order of palettes, presets and playlists in WLED integration

* fix tests: update palette items order

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-31 08:08:11 +00:00
Lucas Mindêllo de Andrade 5d45b84cd2 Remove sunweg integration (#124230)
* chore(sunweg): remove sunweg integration

* Update homeassistant/components/sunweg/strings.json

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

* Update homeassistant/components/sunweg/manifest.json

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

* feat: added async remove entry

* Clean setup_entry; add tests

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: abmantis <amfcalt@gmail.com>
2025-03-31 08:06:54 +00:00
Franck Nijhof 7766649304 Bump version to 2025.4.0b9 2025-03-29 17:50:46 +00:00
Simone Chemelli 07e9020dfa Fix immediate state update for Comelit (#141735) 2025-03-29 17:50:36 +00:00
J. Diego Rodríguez Royo f504a759e0 Set Home Connect program action field as not required (#141729)
* Set Home Connect program action field as not required

* Remove required field

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-29 17:50:32 +00:00
Joost Lekkerkerker 9927de4801 Only trigger events on button updates in SmartThings (#141720)
Only trigger events on button updates
2025-03-29 17:50:29 +00:00
Joost Lekkerkerker 1244fc4682 Only link the parent device if known in SmartThings (#141719)
Only link the parent device if we know the parent device
2025-03-29 17:50:26 +00:00
Norbert Rittel e77a1b12f7 Sentence-case "Medium type" in mopeka (#141718) 2025-03-29 17:50:22 +00:00
J. Nick Koston 5459daaa10 Fix ESPHome entities not being removed when the ESPHome config removes an entire platform (#141708)
* Fix old ESPHome entities not being removed when configuration changes

fixes #140756

* make sure all callbacks fire

* make sure all callbacks fire

* make sure all callbacks fire

* make sure all callbacks fire

* revert

* cover
2025-03-29 17:50:18 +00:00
J. Nick Koston 400131df78 Fix ESPHome update entities being loaded before device_info is available (#141704)
* Fix ESPHome update entities being loaded before device_info is available

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

fixes #135906

* Apply suggestions from code review

* move comment

* Update entry_data.py

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

---------

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
2025-03-29 17:50:15 +00:00
Franck Nijhof 28e1843ff9 Fix Tuya tdq category to pick up temp & humid (#141698) 2025-03-29 17:50:12 +00:00
Franck Nijhof df777318d1 Handle invalid JSON errors in AirNow (#141695) 2025-03-29 17:50:08 +00:00
Jan Bouwhuis 6ad5e9e89c Improve MQTT translation strings (#141691)
* Improve MQTT options translation string

* more improvements
2025-03-29 17:50:05 +00:00
Norbert Rittel a0bd8deee9 Replace "country" with common string in holiday (#141687) 2025-03-29 17:50:01 +00:00
Marcel van der Veldt 405cbd6a00 Always set pause feature on Music Assistant mediaplayers (#141686) 2025-03-29 17:49:58 +00:00
Marcel van der Veldt 3e0eb5ab2c Bump music assistant client to 1.2.0 (#141668)
* Bump music assistant client to 1.2.0

* Update test fixtures
2025-03-29 17:49:55 +00:00
Norbert Rittel fad75a70b6 Add a common string for "country" (#141653) 2025-03-29 17:49:52 +00:00
Josef Zweck d9720283df Add unkown to uncalibrated state for tedee (#141262) 2025-03-29 17:49:46 +00:00
Franck Nijhof 14eed1778b Bump version to 2025.4.0b8 2025-03-28 20:46:26 +00:00
Norbert Rittel 049aaa7e8b Fix grammar / sentence-casing in workday (#141682)
* Fix grammar / sentence-casing in `workday`

Also replace "country" with common string.

* Add two more references

* Fix second data description reference

* Add "given" to action description for better translations
2025-03-28 20:46:17 +00:00
J. Nick Koston 35717e8216 Increase websocket_api allowed peak time to 10s (#141680)
* Increase websocket_api allowed peak time to 10s

fixes #141624

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

* adjust test
2025-03-28 20:46:13 +00:00
Franck Nijhof 2a081abc18 Fix camera proxy with sole image quality settings (#141676) 2025-03-28 20:46:10 +00:00
puddly b7f29c7358 Handle all firmware types for ZBT-1 and Yellow update entities (#141674)
Handle other firmware types
2025-03-28 20:46:06 +00:00
Jason Hunter 3bb6373df5 Update Duke Energy package to fix integration (#141669)
* Update Duke Energy package to fix integration

* fix tests
2025-03-28 20:46:03 +00:00
Michael Hansen e1b4edec50 Bump intents and always prefer more literal text (#141663) 2025-03-28 20:46:00 +00:00
puddly 147bee57e1 Include ZBT-1 and Yellow in device registry (#141623)
* Add the Yellow and ZBT-1 to the device registry

* Unload platforms

* Fix unit tests

* Rename the Yellow update entity to `Radio firmware`

* Rename `EmberZNet` to `EmberZNet Zigbee`

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

* Fix unit tests

* Remove unnecessary `always_update=False` from data update coordinator
2025-03-28 20:45:56 +00:00
Erwin Douna fcdaea64da Tado add proper off state (#135480)
* Add proper off state

* Remove current temp

* Add default frost temp
2025-03-28 20:45:53 +00:00
Franck Nijhof d1512d46be Bump version to 2025.4.0b7 2025-03-28 16:00:45 +00:00
Bram Kragten 0be7db6270 Update frontend to 20250328.0 (#141659) 2025-03-28 15:09:56 +00:00
Paulus Schoutsen 2af0282725 Enable the message box on default for satelitte announcement actions (#141654) 2025-03-28 15:09:51 +00:00
Franck Nijhof ff458c8417 Bump version to 2025.4.0b6 2025-03-28 15:04:34 +00:00
Franck Nijhof cc93152ff0 Fix ESPHome event entity staying unavailable (#141650) 2025-03-28 14:05:40 +00:00
Paulus Schoutsen 9965f01609 Ensure connection test sound has no preannouncement (#141647) 2025-03-28 14:05:37 +00:00
Jan Bouwhuis e9c76ce694 Fix duplicate 'device' term in MQTT translation strings (#141646)
* Fix duplicate 'device' from MQTT translation strings

* Update homeassistant/components/mqtt/strings.json
2025-03-28 14:05:34 +00:00
Norbert Rittel 58ab7d350d Fix sentence-casing in airvisual user strings (#141632) 2025-03-28 14:05:30 +00:00
Nick Pesce e4d6e20ebd Use correct default value for multi press buttons in the Matter integration (#141630)
* Respect the min 2 constraint for the switch MultiPressMax attribute

* Update test_event.py

* Update generic_switch_multi.json

* Fix issue and update tests
2025-03-28 14:05:27 +00:00
Tsvi Mostovicz 45e273897a Jewish calendar match omer service variables requirement to documentation (#141620)
The documentation and the omer schema require a Nusach to be specified, but the YAML misses that requirement
2025-03-28 14:05:23 +00:00
Jan Bouwhuis d9ec7142d7 Fix volatile_organic_compounds_parts translation string to be referenced for MQTT subentries device class selector (#141618)
* Fix ` volatile_organic_compounds_parts` translation string to be referenced for MQTT subentries device class selector

* Fix tests
2025-03-28 14:05:20 +00:00
Petro31 e162499267 Fix an issue with the switch preview in beta (#141617)
Fix an issue with the switch preview
2025-03-28 14:05:16 +00:00
Jan-Philipp Benecke 67f21429e3 Bump aiowebdav2 to 0.4.4 (#141615) 2025-03-28 14:05:12 +00:00
J. Nick Koston a0563f06c9 Fix zeroconf logging level not being respected (#141601)
Removes an old logging workaround that is no longer needed

fixes #141558
2025-03-28 14:05:05 +00:00
Luke Lashley e7c4fdc8bb Bump Python-Snoo to 0.6.5 (#141599)
* Bump Python-Snoo to 0.6.5

* add to event_types
2025-03-28 14:05:00 +00:00
Norbert Rittel c490e350bc Make names of switch entities in gree consistent with docs (#141580) 2025-03-28 14:04:56 +00:00
Robert Resch e11409ef99 Reverts #141363 "Deprecate SmartThings machine state sensors" (#141573)
Reverts #141363
2025-03-28 14:04:52 +00:00
Joost Lekkerkerker 5c8e415a76 Add default string and icon for light effect off (#141567) 2025-03-28 14:04:49 +00:00
alorente e795fb9497 Fix missing response for queued mode scripts (#141460) 2025-03-28 14:04:45 +00:00
Norbert Rittel d0afabb85c Fix misleading friendly names of pvoutput sensors (#141312)
* Fix misleading friendly names of `pvoutput` sensors

* Update test_sensor.py

* Update test_sensor.py - prettier
2025-03-28 14:04:41 +00:00
Franck Nijhof 4f3e8e9b94 Bump version to 2025.4.0b5 2025-03-27 20:03:14 +00:00
Paul Bottein 46c1cbbc9c Update frontend to 20250327.1 (#141596) 2025-03-27 20:03:01 +00:00
Simon Lamon 8d9a4ea278 Fix typing error in NMBS (#141589)
Fix typing error
2025-03-27 20:02:58 +00:00
Jan-Philipp Benecke 22c83e2393 Bump aiowebdav2 to 0.4.3 (#141586) 2025-03-27 20:02:55 +00:00
Joost Lekkerkerker c83a75f6f9 Add brand for Bosch (#141561) 2025-03-27 20:02:51 +00:00
Franck Nijhof 841c727112 Bump version to 2025.4.0b4 2025-03-27 16:59:36 +00:00
Bram Kragten d8c9655bfd Update frontend to 20250327.0 (#141585) 2025-03-27 16:59:29 +00:00
Erik Montnemery 942ed89cc4 Revert "Promote after dependencies in bootstrap" (#141584)
Revert "Promote after dependencies in bootstrap (#140352)"

This reverts commit 3766040960.
2025-03-27 16:59:25 +00:00
Franck Nijhof a1fe6b9cf3 Bump version to 2025.4.0b3 2025-03-27 15:38:31 +00:00
Luke Lashley 2567181cc2 Better handle Roborock discovery (#141575) 2025-03-27 15:38:24 +00:00
Joost Lekkerkerker 028e4f6029 Also migrate completion time entities in SmartThings (#141572) 2025-03-27 15:38:21 +00:00
Martin Hjelmare b82e1a9bef Handle cloud subscription expired for backup upload (#141564)
Handle cloud backup subscription expired for upload
2025-03-27 15:38:18 +00:00
Joost Lekkerkerker 438f226c31 Add icons to hue effects (#141559) 2025-03-27 15:38:15 +00:00
Erwin Douna 2f139e3cb1 Tado fix HomeKit flow (#141525)
* Initial commit

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-03-27 15:38:07 +00:00
Franck Nijhof 5d75e96fbf Bump version to 2025.4.0b2 2025-03-27 10:19:35 +00:00
Norbert Rittel dcf2ec5c37 Fix sentence-casing in konnected strings, replace "override" with "custom" (#141553)
Fix sentence-casing in `konnected`strings, replace "Override" with "Custom"

Make string consistent with HA standards.

As "Override" can be misunderstood as the verb, replace it with "Custom".
2025-03-27 10:19:22 +00:00
Simon Lamon 2431e1ba98 Bump linkplay to v0.2.2 (#141542)
Bump linkplay
2025-03-27 10:19:18 +00:00
Thomas55555 4ead108c15 Handle webcal prefix in remote calendar (#141541)
Handel webcal prefix in remote calendar
2025-03-27 10:19:14 +00:00
Michael Hansen ec8363fa49 Add default preannounce sound to Assist satellites (#141522)
* Add default preannounce sound

* Allow None to disable sound

* Register static path instead of HTTP view

* Fix path

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-03-27 10:19:09 +00:00
J. Diego Rodríguez Royo e7ff0a3f8b Improve some Home Connect deprecations (#141508) 2025-03-27 10:19:06 +00:00
Ivan Lopez Hernandez f4c0eb4189 Initialize google.genai.Client in the executor (#141432)
* Intialize the client on an executor thread

* Fix MyPy error

* MyPy error

* Exception error

* Fix ruff

* Update __init__.py

---------

Co-authored-by: tronikos <tronikos@users.noreply.github.com>
2025-03-27 10:19:02 +00:00
Manu b1ee5a76e1 Support for upcoming pyLoad-ng release in pyLoad integration (#141297)
Fix extra key `proxy` in pyLoad
2025-03-27 10:18:58 +00:00
Norbert Rittel 6b9e8c301b Fix wrong friendly name for storage_power in solaredge (#141269)
* Fix wrong friendly name for `storage_power` in `solaredge`

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

* Two additional spelling fixes

* Sentence-case "site"
2025-03-27 10:18:53 +00:00
Franck Nijhof 89c3266c7e Bump version to 2025.4.0b1 2025-03-26 23:21:26 +00:00
Jan Bouwhuis cff0a632e8 Fix QoS schema issue in MQTT subentries (#141531) 2025-03-26 23:21:17 +00:00
Jan Bouwhuis e04d8557ae Fix MQTT options flow QoS selector can not serialize (#141528) 2025-03-26 23:21:14 +00:00
Thomas55555 ca6286f241 Fix work area sensor for Husqvarna Automower (#141527)
* Fix work area sensor for Husqvarna Automower

* simplify
2025-03-26 23:21:10 +00:00
Robert Resch 35bcc9d5af Show box for Smartthings rise number entity (#141526) 2025-03-26 23:21:07 +00:00
Joost Lekkerkerker 25b45ce867 Sort SmartThings devices to be created by parent device id (#141515) 2025-03-26 23:21:03 +00:00
Robert Resch d568209bd5 Bump deebot-client to 12.4.0 (#141501) 2025-03-26 23:21:00 +00:00
Simone Chemelli 8a43e8af9e Fix refresh state for Comelit alarm (#141370) 2025-03-26 23:20:56 +00:00
Franck Nijhof 785e5b2c16 Bump version to 2025.4.0b0 2025-03-26 17:41:03 +00:00
619 changed files with 40710 additions and 19183 deletions
+3 -3
View File
@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.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@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -531,7 +531,7 @@ jobs:
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
+1 -1
View File
@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 12
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2025.5"
HA_SHORT_VERSION: "2025.6"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version
+2
View File
@@ -332,6 +332,7 @@ homeassistant.components.media_player.*
homeassistant.components.media_source.*
homeassistant.components.met_eireann.*
homeassistant.components.metoffice.*
homeassistant.components.miele.*
homeassistant.components.mikrotik.*
homeassistant.components.min_max.*
homeassistant.components.minecraft_server.*
@@ -463,6 +464,7 @@ homeassistant.components.slack.*
homeassistant.components.sleepiq.*
homeassistant.components.smhi.*
homeassistant.components.smlight.*
homeassistant.components.smtp.*
homeassistant.components.snooz.*
homeassistant.components.solarlog.*
homeassistant.components.sonarr.*
Generated
+4 -3
View File
@@ -1081,8 +1081,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/ombi/ @larssont
/homeassistant/components/onboarding/ @home-assistant/core
/tests/components/onboarding/ @home-assistant/core
/homeassistant/components/oncue/ @bdraco @peterager
/tests/components/oncue/ @bdraco @peterager
/homeassistant/components/ondilo_ico/ @JeromeHXP
/tests/components/ondilo_ico/ @JeromeHXP
/homeassistant/components/onedrive/ @zweckj
@@ -1260,6 +1258,8 @@ build.json @home-assistant/supervisor
/tests/components/recovery_mode/ @home-assistant/core
/homeassistant/components/refoss/ @ashionky
/tests/components/refoss/ @ashionky
/homeassistant/components/rehlko/ @bdraco @peterager
/tests/components/rehlko/ @bdraco @peterager
/homeassistant/components/remote/ @home-assistant/core
/tests/components/remote/ @home-assistant/core
/homeassistant/components/remote_calendar/ @Thomas55555
@@ -1474,7 +1474,8 @@ build.json @home-assistant/supervisor
/tests/components/steam_online/ @tkdrob
/homeassistant/components/steamist/ @bdraco
/tests/components/steamist/ @bdraco
/homeassistant/components/stiebel_eltron/ @fucm
/homeassistant/components/stiebel_eltron/ @fucm @ThyMYthOS
/tests/components/stiebel_eltron/ @fucm @ThyMYthOS
/homeassistant/components/stookwijzer/ @fwestenberg
/tests/components/stookwijzer/ @fwestenberg
/homeassistant/components/stream/ @hunterjm @uvjustin @allenporter
Generated
+1 -1
View File
@@ -31,7 +31,7 @@ RUN \
&& go2rtc --version
# Install uv
RUN pip3 install uv==0.6.10
RUN pip3 install uv==0.7.1
WORKDIR /usr/src
+1
View File
@@ -6,6 +6,7 @@
"google_assistant_sdk",
"google_cloud",
"google_drive",
"google_gemini",
"google_generative_ai_conversation",
"google_mail",
"google_maps",
+6
View File
@@ -0,0 +1,6 @@
{
"domain": "nuki",
"name": "Nuki",
"integrations": ["nuki"],
"iot_standards": ["matter"]
}
@@ -67,6 +67,7 @@ POLLEN_CATEGORY_MAP = {
2: "moderate",
3: "high",
4: "very_high",
5: "extreme",
}
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
@@ -72,6 +72,7 @@
"level": {
"name": "Level",
"state": {
"extreme": "Extreme",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "Moderate",
@@ -89,6 +90,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -123,6 +125,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -167,6 +170,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -181,6 +185,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -195,6 +200,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
+17 -4
View File
@@ -2,25 +2,38 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import CONNECTION_TYPE, LOCAL
from .coordinator import AdaxCloudCoordinator, AdaxConfigEntry, AdaxLocalCoordinator
PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AdaxConfigEntry) -> bool:
"""Set up Adax from a config entry."""
if entry.data.get(CONNECTION_TYPE) == LOCAL:
local_coordinator = AdaxLocalCoordinator(hass, entry)
entry.runtime_data = local_coordinator
else:
cloud_coordinator = AdaxCloudCoordinator(hass, entry)
entry.runtime_data = cloud_coordinator
await entry.runtime_data.async_config_entry_first_refresh()
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: AdaxConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_migrate_entry(
hass: HomeAssistant, config_entry: AdaxConfigEntry
) -> bool:
"""Migrate old entry."""
# convert title and unique_id to string
if config_entry.version == 1:
+80 -69
View File
@@ -12,57 +12,42 @@ from homeassistant.components.climate import (
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_TEMPERATURE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_TOKEN,
CONF_UNIQUE_ID,
PRECISION_WHOLE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
from . import AdaxConfigEntry
from .const import CONNECTION_TYPE, DOMAIN, LOCAL
from .coordinator import AdaxCloudCoordinator, AdaxLocalCoordinator
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: AdaxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Adax thermostat with config flow."""
if entry.data.get(CONNECTION_TYPE) == LOCAL:
adax_data_handler = AdaxLocal(
entry.data[CONF_IP_ADDRESS],
entry.data[CONF_TOKEN],
websession=async_get_clientsession(hass, verify_ssl=False),
)
local_coordinator = cast(AdaxLocalCoordinator, entry.runtime_data)
async_add_entities(
[LocalAdaxDevice(adax_data_handler, entry.data[CONF_UNIQUE_ID])], True
[LocalAdaxDevice(local_coordinator, entry.data[CONF_UNIQUE_ID])],
)
else:
cloud_coordinator = cast(AdaxCloudCoordinator, entry.runtime_data)
async_add_entities(
AdaxDevice(cloud_coordinator, device_id)
for device_id in cloud_coordinator.data
)
return
adax_data_handler = Adax(
entry.data[ACCOUNT_ID],
entry.data[CONF_PASSWORD],
websession=async_get_clientsession(hass),
)
async_add_entities(
(
AdaxDevice(room, adax_data_handler)
for room in await adax_data_handler.get_rooms()
),
True,
)
class AdaxDevice(ClimateEntity):
class AdaxDevice(CoordinatorEntity[AdaxCloudCoordinator], ClimateEntity):
"""Representation of a heater."""
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
@@ -76,20 +61,37 @@ class AdaxDevice(ClimateEntity):
_attr_target_temperature_step = PRECISION_WHOLE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
def __init__(
self,
coordinator: AdaxCloudCoordinator,
device_id: str,
) -> None:
"""Initialize the heater."""
self._device_id = heater_data["id"]
self._adax_data_handler = adax_data_handler
super().__init__(coordinator)
self._adax_data_handler: Adax = coordinator.adax_data_handler
self._device_id = device_id
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
self._attr_name = self.room["name"]
self._attr_unique_id = f"{self.room['homeId']}_{self._device_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, heater_data["id"])},
identifiers={(DOMAIN, device_id)},
# Instead of setting the device name to the entity name, adax
# should be updated to set has_entity_name = True, and set the entity
# name to None
name=cast(str | None, self.name),
manufacturer="Adax",
)
self._apply_data(self.room)
@property
def available(self) -> bool:
"""Whether the entity is available or not."""
return super().available and self._device_id in self.coordinator.data
@property
def room(self) -> dict[str, Any]:
"""Gets the data for this particular device."""
return self.coordinator.data[self._device_id]
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode."""
@@ -104,7 +106,9 @@ class AdaxDevice(ClimateEntity):
)
else:
return
await self._adax_data_handler.update()
# Request data refresh from source to verify that update was successful
await self.coordinator.async_request_refresh()
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
@@ -114,28 +118,31 @@ class AdaxDevice(ClimateEntity):
self._device_id, temperature, True
)
async def async_update(self) -> None:
"""Get the latest data."""
for room in await self._adax_data_handler.get_rooms():
if room["id"] != self._device_id:
continue
self._attr_name = room["name"]
self._attr_current_temperature = room.get("temperature")
self._attr_target_temperature = room.get("targetTemperature")
if room["heatingEnabled"]:
self._attr_hvac_mode = HVACMode.HEAT
self._attr_icon = "mdi:radiator"
else:
self._attr_hvac_mode = HVACMode.OFF
self._attr_icon = "mdi:radiator-off"
return
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if room := self.room:
self._apply_data(room)
super()._handle_coordinator_update()
def _apply_data(self, room: dict[str, Any]) -> None:
"""Update the appropriate attributues based on received data."""
self._attr_current_temperature = room.get("temperature")
self._attr_target_temperature = room.get("targetTemperature")
if room["heatingEnabled"]:
self._attr_hvac_mode = HVACMode.HEAT
self._attr_icon = "mdi:radiator"
else:
self._attr_hvac_mode = HVACMode.OFF
self._attr_icon = "mdi:radiator-off"
class LocalAdaxDevice(ClimateEntity):
class LocalAdaxDevice(CoordinatorEntity[AdaxLocalCoordinator], ClimateEntity):
"""Representation of a heater."""
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
_attr_hvac_mode = HVACMode.HEAT
_attr_hvac_mode = HVACMode.OFF
_attr_icon = "mdi:radiator-off"
_attr_max_temp = 35
_attr_min_temp = 5
_attr_supported_features = (
@@ -146,9 +153,10 @@ class LocalAdaxDevice(ClimateEntity):
_attr_target_temperature_step = PRECISION_WHOLE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
def __init__(self, adax_data_handler: AdaxLocal, unique_id: str) -> None:
def __init__(self, coordinator: AdaxLocalCoordinator, unique_id: str) -> None:
"""Initialize the heater."""
self._adax_data_handler = adax_data_handler
super().__init__(coordinator)
self._adax_data_handler: AdaxLocal = coordinator.adax_data_handler
self._attr_unique_id = unique_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
@@ -169,17 +177,20 @@ class LocalAdaxDevice(ClimateEntity):
return
await self._adax_data_handler.set_target_temperature(temperature)
async def async_update(self) -> None:
"""Get the latest data."""
data = await self._adax_data_handler.get_status()
self._attr_current_temperature = data["current_temperature"]
self._attr_available = self._attr_current_temperature is not None
if (target_temp := data["target_temperature"]) == 0:
self._attr_hvac_mode = HVACMode.OFF
self._attr_icon = "mdi:radiator-off"
if target_temp == 0:
self._attr_target_temperature = self._attr_min_temp
else:
self._attr_hvac_mode = HVACMode.HEAT
self._attr_icon = "mdi:radiator"
self._attr_target_temperature = target_temp
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if data := self.coordinator.data:
self._attr_current_temperature = data["current_temperature"]
self._attr_available = self._attr_current_temperature is not None
if (target_temp := data["target_temperature"]) == 0:
self._attr_hvac_mode = HVACMode.OFF
self._attr_icon = "mdi:radiator-off"
if target_temp == 0:
self._attr_target_temperature = self._attr_min_temp
else:
self._attr_hvac_mode = HVACMode.HEAT
self._attr_icon = "mdi:radiator"
self._attr_target_temperature = target_temp
super()._handle_coordinator_update()
+3
View File
@@ -1,5 +1,6 @@
"""Constants for the Adax integration."""
import datetime
from typing import Final
ACCOUNT_ID: Final = "account_id"
@@ -9,3 +10,5 @@ DOMAIN: Final = "adax"
LOCAL = "Local"
WIFI_SSID = "wifi_ssid"
WIFI_PSWD = "wifi_pswd"
SCAN_INTERVAL = datetime.timedelta(seconds=60)
@@ -0,0 +1,71 @@
"""DataUpdateCoordinator for the Adax component."""
import logging
from typing import Any, cast
from adax import Adax
from adax_local import Adax as AdaxLocal
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ACCOUNT_ID, SCAN_INTERVAL
_LOGGER = logging.getLogger(__name__)
type AdaxConfigEntry = ConfigEntry[AdaxCloudCoordinator | AdaxLocalCoordinator]
class AdaxCloudCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Coordinator for updating data to and from Adax (cloud)."""
def __init__(self, hass: HomeAssistant, entry: AdaxConfigEntry) -> None:
"""Initialize the Adax coordinator used for Cloud mode."""
super().__init__(
hass,
config_entry=entry,
logger=_LOGGER,
name="AdaxCloud",
update_interval=SCAN_INTERVAL,
)
self.adax_data_handler = Adax(
entry.data[ACCOUNT_ID],
entry.data[CONF_PASSWORD],
websession=async_get_clientsession(hass),
)
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
"""Fetch data from the Adax."""
rooms = await self.adax_data_handler.get_rooms() or []
return {r["id"]: r for r in rooms}
class AdaxLocalCoordinator(DataUpdateCoordinator[dict[str, Any] | None]):
"""Coordinator for updating data to and from Adax (local)."""
def __init__(self, hass: HomeAssistant, entry: AdaxConfigEntry) -> None:
"""Initialize the Adax coordinator used for Local mode."""
super().__init__(
hass,
config_entry=entry,
logger=_LOGGER,
name="AdaxLocal",
update_interval=SCAN_INTERVAL,
)
self.adax_data_handler = AdaxLocal(
entry.data[CONF_IP_ADDRESS],
entry.data[CONF_TOKEN],
websession=async_get_clientsession(hass, verify_ssl=False),
)
async def _async_update_data(self) -> dict[str, Any]:
"""Fetch data from the Adax."""
if result := await self.adax_data_handler.get_status():
return cast(dict[str, Any], result)
raise UpdateFailed("Got invalid status from device")
@@ -93,7 +93,7 @@
"name": "Internal temperature"
},
"last_self_test": {
"name": "Last self test"
"name": "Last self-test"
},
"last_transfer": {
"name": "Last transfer"
@@ -177,7 +177,7 @@
"name": "Restore requirement"
},
"self_test_result": {
"name": "Self test result"
"name": "Self-test result"
},
"sensitivity": {
"name": "Sensitivity"
@@ -195,7 +195,7 @@
"name": "Status"
},
"self_test_interval": {
"name": "Self test interval"
"name": "Self-test interval"
},
"time_left": {
"name": "Time left"
@@ -6,5 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/apsystems",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["apsystems-ez1==2.5.0"]
"loggers": ["APsystemsEZ1"],
"requirements": ["apsystems-ez1==2.6.0"]
}
@@ -18,6 +18,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
ATTR_NAME,
CONF_ACTIONS,
CONF_ALIAS,
CONF_CONDITIONS,
CONF_DEVICE_ID,
@@ -27,6 +28,7 @@ from homeassistant.const import (
CONF_MODE,
CONF_PATH,
CONF_PLATFORM,
CONF_TRIGGERS,
CONF_VARIABLES,
CONF_ZONE,
EVENT_HOMEASSISTANT_STARTED,
@@ -86,11 +88,9 @@ from homeassistant.util.hass_dict import HassKey
from .config import AutomationConfig, ValidationStatus
from .const import (
CONF_ACTIONS,
CONF_INITIAL_STATE,
CONF_TRACE,
CONF_TRIGGER_VARIABLES,
CONF_TRIGGERS,
DEFAULT_INITIAL_STATE,
DOMAIN,
LOGGER,
+7 -32
View File
@@ -14,11 +14,15 @@ from homeassistant.components import blueprint
from homeassistant.components.trace import TRACE_CONFIG_SCHEMA
from homeassistant.config import config_per_platform, config_without_domain
from homeassistant.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_ALIAS,
CONF_CONDITION,
CONF_CONDITIONS,
CONF_DESCRIPTION,
CONF_ID,
CONF_TRIGGER,
CONF_TRIGGERS,
CONF_VARIABLES,
)
from homeassistant.core import HomeAssistant
@@ -30,14 +34,10 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.util.yaml.input import UndefinedSubstitution
from .const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_HIDE_ENTITY,
CONF_INITIAL_STATE,
CONF_TRACE,
CONF_TRIGGER,
CONF_TRIGGER_VARIABLES,
CONF_TRIGGERS,
DOMAIN,
LOGGER,
)
@@ -58,34 +58,9 @@ _MINIMAL_PLATFORM_SCHEMA = vol.Schema(
def _backward_compat_schema(value: Any | None) -> Any:
"""Backward compatibility for automations."""
if not isinstance(value, dict):
return value
# `trigger` has been renamed to `triggers`
if CONF_TRIGGER in value:
if CONF_TRIGGERS in value:
raise vol.Invalid(
"Cannot specify both 'trigger' and 'triggers'. Please use 'triggers' only."
)
value[CONF_TRIGGERS] = value.pop(CONF_TRIGGER)
# `condition` has been renamed to `conditions`
if CONF_CONDITION in value:
if CONF_CONDITIONS in value:
raise vol.Invalid(
"Cannot specify both 'condition' and 'conditions'. Please use 'conditions' only."
)
value[CONF_CONDITIONS] = value.pop(CONF_CONDITION)
# `action` has been renamed to `actions`
if CONF_ACTION in value:
if CONF_ACTIONS in value:
raise vol.Invalid(
"Cannot specify both 'action' and 'actions'. Please use 'actions' only."
)
value[CONF_ACTIONS] = value.pop(CONF_ACTION)
return value
value = cv.renamed(CONF_TRIGGER, CONF_TRIGGERS)(value)
value = cv.renamed(CONF_ACTION, CONF_ACTIONS)(value)
return cv.renamed(CONF_CONDITION, CONF_CONDITIONS)(value)
PLATFORM_SCHEMA = vol.All(
@@ -2,10 +2,6 @@
import logging
CONF_ACTION = "action"
CONF_ACTIONS = "actions"
CONF_TRIGGER = "trigger"
CONF_TRIGGERS = "triggers"
CONF_TRIGGER_VARIABLES = "trigger_variables"
DOMAIN = "automation"
+1 -1
View File
@@ -202,7 +202,7 @@ class BackupConfig:
if agent_id not in self.data.agents:
old_agent_retention = None
self.data.agents[agent_id] = AgentConfig(
protected=agent_config.get("protected", False),
protected=agent_config.get("protected", True),
retention=new_agent_retention,
)
else:
@@ -12,5 +12,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bluemaestro",
"iot_class": "local_push",
"requirements": ["bluemaestro-ble==0.3.0"]
"requirements": ["bluemaestro-ble==0.4.0"]
}
@@ -18,9 +18,9 @@
"bleak==0.22.3",
"bleak-retry-connector==3.9.0",
"bluetooth-adapters==0.21.4",
"bluetooth-auto-recovery==1.4.5",
"bluetooth-data-tools==1.28.0",
"bluetooth-auto-recovery==1.5.1",
"bluetooth-data-tools==1.28.1",
"dbus-fast==2.43.0",
"habluetooth==3.44.0"
"habluetooth==3.48.2"
]
}
+2 -1
View File
@@ -5,7 +5,7 @@ import logging
from typing import Any
from aiohttp import ClientError, ClientResponseError, ClientTimeout
from bond_async import Bond, BPUPSubscriptions, start_bpup
from bond_async import Bond, BPUPSubscriptions, RequestorUUID, start_bpup
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@@ -49,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BondConfigEntry) -> bool
token=token,
timeout=ClientTimeout(total=_API_TIMEOUT),
session=async_get_clientsession(hass),
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
)
hub = BondHub(bond, host)
try:
+11 -3
View File
@@ -8,7 +8,7 @@ import logging
from typing import Any
from aiohttp import ClientConnectionError, ClientResponseError
from bond_async import Bond
from bond_async import Bond, RequestorUUID
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult
@@ -34,7 +34,12 @@ TOKEN_SCHEMA = vol.Schema({})
async def async_get_token(hass: HomeAssistant, host: str) -> str | None:
"""Try to fetch the token from the bond device."""
bond = Bond(host, "", session=async_get_clientsession(hass))
bond = Bond(
host,
"",
session=async_get_clientsession(hass),
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
)
response: dict[str, str] = {}
with contextlib.suppress(ClientConnectionError):
response = await bond.token()
@@ -45,7 +50,10 @@ async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> tuple[st
"""Validate the user input allows us to connect."""
bond = Bond(
data[CONF_HOST], data[CONF_ACCESS_TOKEN], session=async_get_clientsession(hass)
data[CONF_HOST],
data[CONF_ACCESS_TOKEN],
session=async_get_clientsession(hass),
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
)
try:
hub = BondHub(bond, data[CONF_HOST])
+2 -2
View File
@@ -10,12 +10,12 @@
"known_hosts": "Add known host"
},
"data_description": {
"known_hosts": "Hostnames or IP-addresses of cast devices, use if mDNS discovery is not working"
"known_hosts": "Hostnames or IP addresses of cast devices, use if mDNS discovery is not working"
}
}
},
"error": {
"invalid_known_hosts": "Known hosts must be a comma separated list of hosts."
"invalid_known_hosts": "Known hosts must be a comma-separated list of hosts."
}
},
"options": {
+7 -3
View File
@@ -418,9 +418,11 @@ class CloudTTSEntity(TextToSpeechEntity):
language=language,
voice=options.get(
ATTR_VOICE,
self._voice
if language == self._language
else DEFAULT_VOICES[language],
(
self._voice
if language == self._language
else DEFAULT_VOICES[language]
),
),
gender=options.get(ATTR_GENDER),
),
@@ -435,6 +437,8 @@ class CloudTTSEntity(TextToSpeechEntity):
class CloudProvider(Provider):
"""Home Assistant Cloud speech API provider."""
has_entity = True
def __init__(self, cloud: Cloud[CloudClient]) -> None:
"""Initialize cloud provider."""
self.cloud = cloud
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.3.28"]
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.4.30"]
}
@@ -7,7 +7,7 @@ import logging
from typing import Any
from devolo_plc_api.device import Device
from devolo_plc_api.exceptions.device import DeviceNotFound
from devolo_plc_api.exceptions.device import DeviceNotFound, DevicePasswordProtected
import voluptuous as vol
from homeassistant.components import zeroconf
@@ -22,7 +22,9 @@ from .const import DOMAIN, PRODUCT, SERIAL_NUMBER, TITLE
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_IP_ADDRESS): str})
STEP_USER_DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_IP_ADDRESS): str, vol.Optional(CONF_PASSWORD): str}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Optional(CONF_PASSWORD): str})
@@ -36,7 +38,16 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
device = Device(data[CONF_IP_ADDRESS], zeroconf_instance=zeroconf_instance)
device.password = data[CONF_PASSWORD]
await device.async_connect(session_instance=async_client)
# Try a password protected, non-writing device API call that raises, if the password is wrong.
# If only the plcnet API is available, we can continue without trying a password as the plcnet
# API does not require a password.
if device.device:
await device.device.async_uptime()
await device.async_disconnect()
return {
@@ -59,23 +70,22 @@ class DevoloHomeNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the initial step."""
errors: dict = {}
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
try:
info = await validate_input(self.hass, user_input)
except DeviceNotFound:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(info[SERIAL_NUMBER], raise_on_progress=False)
self._abort_if_unique_id_configured()
user_input[CONF_PASSWORD] = ""
return self.async_create_entry(title=info[TITLE], data=user_input)
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except DeviceNotFound:
errors["base"] = "cannot_connect"
except DevicePasswordProtected:
errors["base"] = "invalid_auth"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(
info[SERIAL_NUMBER], raise_on_progress=False
)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=info[TITLE], data=user_input)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
@@ -106,15 +116,27 @@ class DevoloHomeNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle a flow initiated by zeroconf."""
title = self.context["title_placeholders"][CONF_NAME]
errors: dict = {}
data_schema: vol.Schema | None = None
if user_input is not None:
data = {
CONF_IP_ADDRESS: self.host,
CONF_PASSWORD: "",
CONF_PASSWORD: user_input.get(CONF_PASSWORD, ""),
}
return self.async_create_entry(title=title, data=data)
try:
await validate_input(self.hass, data)
except DevicePasswordProtected:
errors = {"base": "invalid_auth"}
data_schema = STEP_REAUTH_DATA_SCHEMA
else:
return self.async_create_entry(title=title, data=data)
return self.async_show_form(
step_id="zeroconf_confirm",
data_schema=data_schema,
description_placeholders={"host_name": title},
errors=errors,
)
async def async_step_reauth(
@@ -134,14 +156,21 @@ class DevoloHomeNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by reauthentication."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=STEP_REAUTH_DATA_SCHEMA,
)
errors: dict = {}
if user_input is not None:
data = {
CONF_IP_ADDRESS: self.host,
CONF_PASSWORD: user_input[CONF_PASSWORD],
}
try:
await validate_input(self.hass, data)
except DevicePasswordProtected:
errors = {"base": "invalid_auth"}
else:
return self.async_update_reload_and_abort(self._reauth_entry, data=data)
data = {
CONF_IP_ADDRESS: self.host,
CONF_PASSWORD: user_input[CONF_PASSWORD],
}
return self.async_update_reload_and_abort(self._reauth_entry, data=data)
return self.async_show_form(
step_id="reauth_confirm",
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
)
@@ -5,10 +5,12 @@
"user": {
"description": "[%key:common::config_flow::description::confirm_setup%]",
"data": {
"ip_address": "[%key:common::config_flow::data::ip%]"
"ip_address": "[%key:common::config_flow::data::ip%]",
"password": "[%key:common::config_flow::data::password%]"
},
"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."
"ip_address": "IP address of your devolo Home Network device. This can be found in the devolo Home Network App on the device dashboard.",
"password": "Password you protected the device with."
}
},
"reauth_confirm": {
@@ -16,16 +18,23 @@
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"password": "Password you protected the device with."
"password": "[%key:component::devolo_home_network::config::step::user::data_description::password%]"
}
},
"zeroconf_confirm": {
"description": "Do you want to add the devolo home network device with the hostname `{host_name}` to Home Assistant?",
"title": "Discovered devolo home network device"
"title": "Discovered devolo home network device",
"data": {
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"password": "[%key:component::devolo_home_network::config::step::user::data_description::password%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
+1 -1
View File
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dnsip",
"iot_class": "cloud_polling",
"requirements": ["aiodns==3.2.0"]
"requirements": ["aiodns==3.3.0"]
}
@@ -3,10 +3,10 @@
"step": {
"init": {
"data": {
"events": "Comma separated list of events."
"events": "Comma-separated list of events."
},
"data_description": {
"events": "Add a comma separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion"
"events": "Add a comma-separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion"
}
}
}
@@ -5,7 +5,7 @@ from dataclasses import dataclass
from typing import Generic
from deebot_client.capabilities import CapabilityEvent
from deebot_client.events.water_info import WaterInfoEvent
from deebot_client.events.water_info import MopAttachedEvent
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
@@ -32,9 +32,9 @@ class EcovacsBinarySensorEntityDescription(
ENTITY_DESCRIPTIONS: tuple[EcovacsBinarySensorEntityDescription, ...] = (
EcovacsBinarySensorEntityDescription[WaterInfoEvent](
capability_fn=lambda caps: caps.water,
value_fn=lambda e: e.mop_attached,
EcovacsBinarySensorEntityDescription[MopAttachedEvent](
capability_fn=lambda caps: caps.water.mop_attached if caps.water else None,
value_fn=lambda e: e.value,
key="water_mop_attached",
translation_key="water_mop_attached",
entity_category=EntityCategory.DIAGNOSTIC,
+6 -2
View File
@@ -1,8 +1,11 @@
"""Ecovacs image entities."""
from typing import cast
from deebot_client.capabilities import CapabilityMap
from deebot_client.device import Device
from deebot_client.events.map import CachedMapInfoEvent, MapChangedEvent
from deebot_client.map import Map
from homeassistant.components.image import ImageEntity
from homeassistant.core import HomeAssistant
@@ -47,6 +50,7 @@ class EcovacsMap(
"""Initialize entity."""
super().__init__(device, capability, hass=hass)
self._attr_extra_state_attributes = {}
self._map = cast(Map, self._device.map)
entity_description = EntityDescription(
key="map",
@@ -55,7 +59,7 @@ class EcovacsMap(
def image(self) -> bytes | None:
"""Return bytes of image or None."""
if svg := self._device.map.get_svg_map():
if svg := self._map.get_svg_map():
return svg.encode()
return None
@@ -80,4 +84,4 @@ class EcovacsMap(
Only used by the generic entity update service.
"""
await super().async_update()
self._device.map.refresh()
self._map.refresh()
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.10", "deebot-client==12.5.0"]
"requirements": ["py-sucks==0.9.10", "deebot-client==13.0.1"]
}
+5 -4
View File
@@ -6,7 +6,8 @@ from typing import Any, Generic
from deebot_client.capabilities import CapabilitySetTypes
from deebot_client.device import Device
from deebot_client.events import WaterInfoEvent, WorkModeEvent
from deebot_client.events import WorkModeEvent
from deebot_client.events.water_info import WaterAmountEvent
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
@@ -31,9 +32,9 @@ class EcovacsSelectEntityDescription(
ENTITY_DESCRIPTIONS: tuple[EcovacsSelectEntityDescription, ...] = (
EcovacsSelectEntityDescription[WaterInfoEvent](
capability_fn=lambda caps: caps.water,
current_option_fn=lambda e: get_name_key(e.amount),
EcovacsSelectEntityDescription[WaterAmountEvent](
capability_fn=lambda caps: caps.water.amount if caps.water else None,
current_option_fn=lambda e: get_name_key(e.value),
options_fn=lambda water: [get_name_key(amount) for amount in water.types],
key="water_amount",
translation_key="water_amount",
@@ -9,7 +9,14 @@ from homeassistant.helpers.device_registry import DeviceEntry
from .const import DOMAIN
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
PLATFORMS = [Platform.CLIMATE, Platform.LIGHT, Platform.NUMBER, Platform.SENSOR]
PLATFORMS = [
Platform.CLIMATE,
Platform.LIGHT,
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
Platform.TIME,
]
async def async_setup_entry(
@@ -30,6 +30,22 @@
"no_error": "mdi:check-circle"
}
}
},
"switch": {
"filter_active": {
"default": "mdi:pump",
"state": {
"off": "mdi:pump-off"
}
}
},
"time": {
"day_start_time": {
"default": "mdi:weather-sunny"
},
"night_start_time": {
"default": "mdi:moon-waning-crescent"
}
}
}
}
@@ -79,6 +79,14 @@
"air_in_filter": "Air in filter"
}
}
},
"time": {
"day_start_time": {
"name": "Day start time"
},
"night_start_time": {
"name": "Night start time"
}
}
}
}
@@ -0,0 +1,70 @@
"""EHEIM Digital switches."""
from typing import Any, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: EheimDigitalConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the callbacks for the coordinator so switches can be added as devices are found."""
coordinator = entry.runtime_data
def async_setup_device_entities(
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the switch entities for one or multiple devices."""
entities: list[SwitchEntity] = []
for device in device_address.values():
if isinstance(device, EheimDigitalClassicVario):
entities.append(EheimDigitalClassicVarioSwitch(coordinator, device)) # noqa: PERF401
async_add_entities(entities)
coordinator.add_platform_callback(async_setup_device_entities)
async_setup_device_entities(coordinator.hub.devices)
class EheimDigitalClassicVarioSwitch(
EheimDigitalEntity[EheimDigitalClassicVario], SwitchEntity
):
"""Represent an EHEIM Digital classicVARIO switch entity."""
_attr_translation_key = "filter_active"
_attr_name = None
def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
device: EheimDigitalClassicVario,
) -> None:
"""Initialize an EHEIM Digital classicVARIO switch entity."""
super().__init__(coordinator, device)
self._attr_unique_id = device.mac_address
self._async_update_attrs()
@override
async def async_turn_off(self, **kwargs: Any) -> None:
await self._device.set_active(active=False)
@override
async def async_turn_on(self, **kwargs: Any) -> None:
await self._device.set_active(active=True)
@override
def _async_update_attrs(self) -> None:
self._attr_is_on = self._device.is_active
@@ -0,0 +1,132 @@
"""EHEIM Digital time entities."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import time
from typing import Generic, TypeVar, final, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.heater import EheimDigitalHeater
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
PARALLEL_UPDATES = 0
_DeviceT_co = TypeVar("_DeviceT_co", bound=EheimDigitalDevice, covariant=True)
@dataclass(frozen=True, kw_only=True)
class EheimDigitalTimeDescription(TimeEntityDescription, Generic[_DeviceT_co]):
"""Class describing EHEIM Digital time entities."""
value_fn: Callable[[_DeviceT_co], time | None]
set_value_fn: Callable[[_DeviceT_co, time], Awaitable[None]]
CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalTimeDescription[EheimDigitalClassicVario], ...
] = (
EheimDigitalTimeDescription[EheimDigitalClassicVario](
key="day_start_time",
translation_key="day_start_time",
entity_category=EntityCategory.CONFIG,
value_fn=lambda device: device.day_start_time,
set_value_fn=lambda device, value: device.set_day_start_time(value),
),
EheimDigitalTimeDescription[EheimDigitalClassicVario](
key="night_start_time",
translation_key="night_start_time",
entity_category=EntityCategory.CONFIG,
value_fn=lambda device: device.night_start_time,
set_value_fn=lambda device, value: device.set_night_start_time(value),
),
)
HEATER_DESCRIPTIONS: tuple[EheimDigitalTimeDescription[EheimDigitalHeater], ...] = (
EheimDigitalTimeDescription[EheimDigitalHeater](
key="day_start_time",
translation_key="day_start_time",
entity_category=EntityCategory.CONFIG,
value_fn=lambda device: device.day_start_time,
set_value_fn=lambda device, value: device.set_day_start_time(value),
),
EheimDigitalTimeDescription[EheimDigitalHeater](
key="night_start_time",
translation_key="night_start_time",
entity_category=EntityCategory.CONFIG,
value_fn=lambda device: device.night_start_time,
set_value_fn=lambda device, value: device.set_night_start_time(value),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: EheimDigitalConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the callbacks for the coordinator so times can be added as devices are found."""
coordinator = entry.runtime_data
def async_setup_device_entities(
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the time entities for one or multiple devices."""
entities: list[EheimDigitalTime[EheimDigitalDevice]] = []
for device in device_address.values():
if isinstance(device, EheimDigitalClassicVario):
entities.extend(
EheimDigitalTime[EheimDigitalClassicVario](
coordinator, device, description
)
for description in CLASSICVARIO_DESCRIPTIONS
)
if isinstance(device, EheimDigitalHeater):
entities.extend(
EheimDigitalTime[EheimDigitalHeater](
coordinator, device, description
)
for description in HEATER_DESCRIPTIONS
)
async_add_entities(entities)
coordinator.add_platform_callback(async_setup_device_entities)
async_setup_device_entities(coordinator.hub.devices)
@final
class EheimDigitalTime(
EheimDigitalEntity[_DeviceT_co], TimeEntity, Generic[_DeviceT_co]
):
"""Represent an EHEIM Digital time entity."""
entity_description: EheimDigitalTimeDescription[_DeviceT_co]
def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
device: _DeviceT_co,
description: EheimDigitalTimeDescription[_DeviceT_co],
) -> None:
"""Initialize an EHEIM Digital time entity."""
super().__init__(coordinator, device)
self.entity_description = description
self._attr_unique_id = f"{device.mac_address}_{description.key}"
@override
async def async_set_value(self, value: time) -> None:
"""Change the time."""
return await self.entity_description.set_value_fn(self._device, value)
@override
def _async_update_attrs(self) -> None:
"""Update the entity attributes."""
self._attr_native_value = self.entity_description.value_fn(self._device)
@@ -9,12 +9,14 @@ import logging
from typing import Any
from pyenphase import Envoy, EnvoyError, EnvoyTokenAuth
from pyenphase.models.home import EnvoyInterfaceInformation
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
@@ -26,7 +28,7 @@ TOKEN_REFRESH_CHECK_INTERVAL = timedelta(days=1)
STALE_TOKEN_THRESHOLD = timedelta(days=30).total_seconds()
NOTIFICATION_ID = "enphase_envoy_notification"
FIRMWARE_REFRESH_INTERVAL = timedelta(hours=4)
MAC_VERIFICATION_DELAY = timedelta(seconds=34)
_LOGGER = logging.getLogger(__name__)
@@ -39,6 +41,7 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
envoy_serial_number: str
envoy_firmware: str
config_entry: EnphaseConfigEntry
interface: EnvoyInterfaceInformation | None
def __init__(
self, hass: HomeAssistant, envoy: Envoy, entry: EnphaseConfigEntry
@@ -50,8 +53,10 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self.password = entry_data[CONF_PASSWORD]
self._setup_complete = False
self.envoy_firmware = ""
self.interface = None
self._cancel_token_refresh: CALLBACK_TYPE | None = None
self._cancel_firmware_refresh: CALLBACK_TYPE | None = None
self._cancel_mac_verification: CALLBACK_TYPE | None = None
super().__init__(
hass,
_LOGGER,
@@ -121,6 +126,66 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self.hass.config_entries.async_reload(self.config_entry.entry_id)
)
def _schedule_mac_verification(
self, delay: timedelta = MAC_VERIFICATION_DELAY
) -> None:
"""Schedule one time job to verify envoy mac address."""
self.async_cancel_mac_verification()
self._cancel_mac_verification = async_call_later(
self.hass,
delay,
self._async_verify_mac,
)
@callback
def _async_verify_mac(self, now: datetime.datetime) -> None:
"""Verify Envoy active interface mac address in background."""
self.hass.async_create_background_task(
self._async_fetch_and_compare_mac(), "{name} verify envoy mac address"
)
async def _async_fetch_and_compare_mac(self) -> None:
"""Get Envoy interface information and update mac in device connections."""
interface: (
EnvoyInterfaceInformation | None
) = await self.envoy.interface_settings()
if interface is None:
_LOGGER.debug("%s: interface information returned None", self.name)
return
# remember interface information so diagnostics can include in report
self.interface = interface
# Add to or update device registry connections as needed
device_registry = dr.async_get(self.hass)
envoy_device = device_registry.async_get_device(
identifiers={
(
DOMAIN,
self.envoy_serial_number,
)
}
)
if envoy_device is None:
_LOGGER.error(
"No envoy device found in device registry: %s %s",
DOMAIN,
self.envoy_serial_number,
)
return
connection = (dr.CONNECTION_NETWORK_MAC, interface.mac)
if connection in envoy_device.connections:
_LOGGER.debug(
"connection verified as existing: %s in %s", connection, self.name
)
return
device_registry.async_update_device(
device_id=envoy_device.id,
new_connections={connection},
)
_LOGGER.debug("added connection: %s to %s", connection, self.name)
@callback
def _async_mark_setup_complete(self) -> None:
"""Mark setup as complete and setup firmware checks and token refresh if needed."""
@@ -132,6 +197,7 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
FIRMWARE_REFRESH_INTERVAL,
cancel_on_shutdown=True,
)
self._schedule_mac_verification()
self.async_cancel_token_refresh()
if not isinstance(self.envoy.auth, EnvoyTokenAuth):
return
@@ -252,3 +318,10 @@ class EnphaseUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
if self._cancel_firmware_refresh:
self._cancel_firmware_refresh()
self._cancel_firmware_refresh = None
@callback
def async_cancel_mac_verification(self) -> None:
"""Cancel mac verification."""
if self._cancel_mac_verification:
self._cancel_mac_verification()
self._cancel_mac_verification = None
@@ -3,6 +3,7 @@
from __future__ import annotations
import copy
from datetime import datetime
from typing import TYPE_CHECKING, Any
from attr import asdict
@@ -63,6 +64,7 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
"/ivp/ensemble/generator",
"/ivp/meters",
"/ivp/meters/readings",
"/home,",
]
for end_point in end_points:
@@ -146,11 +148,25 @@ async def async_get_config_entry_diagnostics(
"inverters": envoy_data.inverters,
"tariff": envoy_data.tariff,
}
# Add Envoy active interface information to report
active_interface: dict[str, Any] = {}
if coordinator.interface:
active_interface = {
"name": (interface := coordinator.interface).primary_interface,
"interface type": interface.interface_type,
"mac": interface.mac,
"uses dhcp": interface.dhcp,
"firmware build date": datetime.fromtimestamp(
interface.software_build_epoch
).strftime("%Y-%m-%d %H:%M:%S"),
"envoy timezone": interface.timezone,
}
envoy_properties: dict[str, Any] = {
"envoy_firmware": envoy.firmware,
"part_number": envoy.part_number,
"envoy_model": envoy.envoy_model,
"active interface": active_interface,
"supported_features": [feature.name for feature in envoy.supported_features],
"phase_mode": envoy.phase_mode,
"phase_count": envoy.phase_count,
@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"quality_scale": "bronze",
"quality_scale": "platinum",
"requirements": ["pyenphase==1.26.0"],
"zeroconf": [
{
@@ -128,7 +128,7 @@
"storage_mode": {
"name": "Storage mode",
"state": {
"self_consumption": "Self consumption",
"self_consumption": "Self-consumption",
"backup": "Full backup",
"savings": "Savings mode"
}
@@ -393,7 +393,7 @@
},
"exceptions": {
"unexpected_device": {
"message": "Unexpected Envoy serial-number found at {host}; expected {expected_serial}, found {actual_serial}"
"message": "Unexpected Envoy serial number found at {host}; expected {expected_serial}, found {actual_serial}"
},
"authentication_error": {
"message": "Envoy authentication failure on {host}: {args}"
@@ -22,5 +22,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["eq3btsmart"],
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.14.0"]
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.15.1"]
}
@@ -22,6 +22,7 @@ import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_REAUTH,
SOURCE_RECONFIGURE,
ConfigEntry,
@@ -31,6 +32,7 @@ from homeassistant.config_entries import (
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
@@ -302,7 +304,15 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
)
):
return
if entry.source == SOURCE_IGNORE:
# Don't call _fetch_device_info() for ignored entries
raise AbortFlow("already_configured")
configured_host: str | None = entry.data.get(CONF_HOST)
configured_port: int | None = entry.data.get(CONF_PORT)
if configured_host == host and configured_port == port:
# Don't probe to verify the mac is correct since
# the host and port matches.
raise AbortFlow("already_configured")
configured_psk: str | None = entry.data.get(CONF_NOISE_PSK)
await self._fetch_device_info(host, port or configured_port, configured_psk)
updates: dict[str, Any] = {}
@@ -10,10 +10,18 @@ from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from . import CONF_NOISE_PSK
from .const import CONF_DEVICE_NAME
from .dashboard import async_get_dashboard
from .entry_data import ESPHomeConfigEntry
REDACT_KEYS = {CONF_NOISE_PSK, CONF_PASSWORD, "mac_address", "bluetooth_mac_address"}
CONFIGURED_DEVICE_KEYS = (
"configuration",
"current_version",
"deployed_version",
"loaded_integrations",
"target_platform",
)
async def async_get_config_entry_diagnostics(
@@ -26,6 +34,9 @@ async def async_get_config_entry_diagnostics(
entry_data = config_entry.runtime_data
device_info = entry_data.device_info
device_name: str | None = (
device_info.name if device_info else config_entry.data.get(CONF_DEVICE_NAME)
)
if (storage_data := await entry_data.store.async_load()) is not None:
diag["storage_data"] = storage_data
@@ -45,7 +56,19 @@ async def async_get_config_entry_diagnostics(
"scanner": await scanner.async_diagnostics(),
}
diag_dashboard: dict[str, Any] = {"configured": False}
diag["dashboard"] = diag_dashboard
if dashboard := async_get_dashboard(hass):
diag["dashboard"] = dashboard.addon_slug
diag_dashboard["configured"] = True
diag_dashboard["supports_update"] = dashboard.supports_update
diag_dashboard["last_update_success"] = dashboard.last_update_success
diag_dashboard["last_exception"] = dashboard.last_exception
diag_dashboard["addon"] = dashboard.addon_slug
if device_name and dashboard.data:
diag_dashboard["has_matching_name"] = device_name in dashboard.data
if data := dashboard.data.get(device_name):
diag_dashboard["device"] = {
key: data.get(key) for key in CONFIGURED_DEVICE_KEYS
}
return async_redact_data(diag, REDACT_KEYS)
@@ -17,9 +17,9 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==30.0.1",
"aioesphomeapi==30.1.0",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==2.14.0"
"bleak-esphome==2.15.1"
],
"zeroconf": ["_esphomelib._tcp.local."]
}
@@ -195,7 +195,10 @@
"message": "Error compiling {configuration}; Try again in ESPHome dashboard for more information."
},
"error_uploading": {
"message": "Error during OTA of {configuration}; Try again in ESPHome dashboard for more information."
"message": "Error during OTA (Over-The-Air) of {configuration}; Try again in ESPHome dashboard for more information."
},
"ota_in_progress": {
"message": "An OTA (Over-The-Air) update is already in progress for {configuration}."
}
}
}
+66 -24
View File
@@ -29,7 +29,6 @@ from homeassistant.util.enum import try_parse_enum
from .const import DOMAIN
from .coordinator import ESPHomeDashboardCoordinator
from .dashboard import async_get_dashboard
from .domain_data import DomainData
from .entity import (
EsphomeEntity,
convert_api_error_ha_error,
@@ -62,7 +61,7 @@ async def async_setup_entry(
if (dashboard := async_get_dashboard(hass)) is None:
return
entry_data = DomainData.get(hass).get_entry_data(entry)
entry_data = entry.runtime_data
assert entry_data.device_info is not None
device_name = entry_data.device_info.name
unsubs: list[CALLBACK_TYPE] = []
@@ -126,21 +125,17 @@ class ESPHomeDashboardUpdateEntity(
(dr.CONNECTION_NETWORK_MAC, entry_data.device_info.mac_address)
}
)
self._install_lock = asyncio.Lock()
self._available_future: asyncio.Future[None] | None = None
self._update_attrs()
@callback
def _update_attrs(self) -> None:
"""Update the supported features."""
# If the device has deep sleep, we can't assume we can install updates
# as the ESP will not be connectable (by design).
coordinator = self.coordinator
device_info = self._device_info
# Install support can change at run time
if (
coordinator.last_update_success
and coordinator.supports_update
and not device_info.has_deep_sleep
):
if coordinator.last_update_success and coordinator.supports_update:
self._attr_supported_features = UpdateEntityFeature.INSTALL
else:
self._attr_supported_features = NO_FEATURES
@@ -179,6 +174,13 @@ class ESPHomeDashboardUpdateEntity(
self, static_info: list[EntityInfo] | None = None
) -> None:
"""Handle updated data from the device."""
if (
self._entry_data.available
and self._available_future
and not self._available_future.done()
):
self._available_future.set_result(None)
self._available_future = None
self._update_attrs()
self.async_write_ha_state()
@@ -193,17 +195,46 @@ class ESPHomeDashboardUpdateEntity(
entry_data.async_subscribe_device_updated(self._handle_device_update)
)
async def async_will_remove_from_hass(self) -> None:
"""Handle entity about to be removed from Home Assistant."""
if self._available_future and not self._available_future.done():
self._available_future.cancel()
self._available_future = None
async def _async_wait_available(self) -> None:
"""Wait until the device is available."""
# If the device has deep sleep, we need to wait for it to wake up
# and connect to the network to be able to install the update.
if self._entry_data.available:
return
self._available_future = self.hass.loop.create_future()
try:
await self._available_future
finally:
self._available_future = None
async def async_install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
async with self.hass.data.setdefault(KEY_UPDATE_LOCK, asyncio.Lock()):
coordinator = self.coordinator
api = coordinator.api
device = coordinator.data.get(self._device_info.name)
assert device is not None
configuration = device["configuration"]
try:
if self._install_lock.locked():
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="ota_in_progress",
translation_placeholders={
"configuration": self._device_info.name,
},
)
# Ensure only one OTA per device at a time
async with self._install_lock:
# Ensure only one compile at a time for ALL devices
async with self.hass.data.setdefault(KEY_UPDATE_LOCK, asyncio.Lock()):
coordinator = self.coordinator
api = coordinator.api
device = coordinator.data.get(self._device_info.name)
assert device is not None
configuration = device["configuration"]
if not await api.compile(configuration):
raise HomeAssistantError(
translation_domain=DOMAIN,
@@ -212,14 +243,25 @@ class ESPHomeDashboardUpdateEntity(
"configuration": configuration,
},
)
if not await api.upload(configuration, "OTA"):
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="error_uploading",
translation_placeholders={
"configuration": configuration,
},
)
# If the device uses deep sleep, there's a small chance it goes
# to sleep right after the dashboard connects but before the OTA
# starts. In that case, the update won't go through, so we try
# again to catch it on its next wakeup.
attempts = 2 if self._device_info.has_deep_sleep else 1
try:
for attempt in range(1, attempts + 1):
await self._async_wait_available()
if await api.upload(configuration, "OTA"):
break
if attempt == attempts:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="error_uploading",
translation_placeholders={
"configuration": configuration,
},
)
finally:
await self.coordinator.async_request_refresh()
@@ -15,6 +15,8 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_FEATURE_DEVICE_TRACKING,
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
DEFAULT_SSL,
DOMAIN,
FRITZ_AUTH_EXCEPTIONS,
@@ -38,6 +40,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> bool:
"""Set up fritzboxtools from config entry."""
_LOGGER.debug("Setting up FRITZ!Box Tools component")
avm_wrapper = AvmWrapper(
hass=hass,
config_entry=entry,
@@ -46,6 +49,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
use_tls=entry.data.get(CONF_SSL, DEFAULT_SSL),
device_discovery_enabled=entry.options.get(
CONF_FEATURE_DEVICE_TRACKING, DEFAULT_CONF_FEATURE_DEVICE_TRACKING
),
)
try:
@@ -62,6 +68,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
raise ConfigEntryAuthFailed("Missing UPnP configuration")
await avm_wrapper.async_config_entry_first_refresh()
await avm_wrapper.async_trigger_cleanup()
entry.runtime_data = avm_wrapper
+22 -2
View File
@@ -35,7 +35,9 @@ from homeassistant.helpers.service_info.ssdp import (
from homeassistant.helpers.typing import VolDictType
from .const import (
CONF_FEATURE_DEVICE_TRACKING,
CONF_OLD_DISCOVERY,
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
DEFAULT_CONF_OLD_DISCOVERY,
DEFAULT_HOST,
DEFAULT_HTTP_PORT,
@@ -72,7 +74,8 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
"""Initialize FRITZ!Box Tools flow."""
self._name: str = ""
self._password: str = ""
self._use_tls: bool = False
self._use_tls: bool = DEFAULT_SSL
self._feature_device_discovery: bool = DEFAULT_CONF_FEATURE_DEVICE_TRACKING
self._port: int | None = None
self._username: str = ""
self._model: str = ""
@@ -141,6 +144,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
options={
CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds(),
CONF_OLD_DISCOVERY: DEFAULT_CONF_OLD_DISCOVERY,
CONF_FEATURE_DEVICE_TRACKING: self._feature_device_discovery,
},
)
@@ -204,6 +208,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
self._username = user_input[CONF_USERNAME]
self._password = user_input[CONF_PASSWORD]
self._use_tls = user_input[CONF_SSL]
self._feature_device_discovery = user_input[CONF_FEATURE_DEVICE_TRACKING]
self._port = self._determine_port(user_input)
error = await self.async_fritz_tools_init()
@@ -234,6 +239,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool,
vol.Required(
CONF_FEATURE_DEVICE_TRACKING,
default=DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
): bool,
}
),
errors=errors or {},
@@ -250,6 +259,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool,
vol.Required(
CONF_FEATURE_DEVICE_TRACKING,
default=DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
): bool,
}
),
description_placeholders={"name": self._name},
@@ -405,7 +418,7 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow):
"""Handle options flow."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_create_entry(data=user_input)
options = self.config_entry.options
data_schema = vol.Schema(
@@ -420,6 +433,13 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow):
CONF_OLD_DISCOVERY,
default=options.get(CONF_OLD_DISCOVERY, DEFAULT_CONF_OLD_DISCOVERY),
): bool,
vol.Optional(
CONF_FEATURE_DEVICE_TRACKING,
default=options.get(
CONF_FEATURE_DEVICE_TRACKING,
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
),
): bool,
}
)
return self.async_show_form(step_id="init", data_schema=data_schema)
+3
View File
@@ -40,6 +40,9 @@ PLATFORMS = [
CONF_OLD_DISCOVERY = "old_discovery"
DEFAULT_CONF_OLD_DISCOVERY = False
CONF_FEATURE_DEVICE_TRACKING = "feature_device_tracking"
DEFAULT_CONF_FEATURE_DEVICE_TRACKING = True
DSL_CONNECTION: Literal["dsl"] = "dsl"
DEFAULT_DEVICE_NAME = "Unknown device"
+23 -9
View File
@@ -39,6 +39,7 @@ from homeassistant.util.hass_dict import HassKey
from .const import (
CONF_OLD_DISCOVERY,
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
DEFAULT_CONF_OLD_DISCOVERY,
DEFAULT_HOST,
DEFAULT_SSL,
@@ -175,6 +176,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
username: str = DEFAULT_USERNAME,
host: str = DEFAULT_HOST,
use_tls: bool = DEFAULT_SSL,
device_discovery_enabled: bool = DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
) -> None:
"""Initialize FritzboxTools class."""
super().__init__(
@@ -202,6 +204,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
self.port = port
self.username = username
self.use_tls = use_tls
self.device_discovery_enabled = device_discovery_enabled
self.has_call_deflections: bool = False
self._model: str | None = None
self._current_firmware: str | None = None
@@ -332,10 +335,15 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
"entity_states": {},
}
try:
await self.async_scan_devices()
await self.async_update_device_info()
if self.device_discovery_enabled:
await self.async_scan_devices()
entity_data["entity_states"] = await self.hass.async_add_executor_job(
self._entity_states_update
)
if self.has_call_deflections:
entity_data[
"call_deflections"
@@ -521,7 +529,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
return {}
def manage_device_info(
self, dev_info: Device, dev_mac: str, consider_home: bool
self, dev_info: Device, dev_mac: str, consider_home: float
) -> bool:
"""Update device lists and return if device is new."""
_LOGGER.debug("Client dev_info: %s", dev_info)
@@ -551,12 +559,8 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
if new_device:
async_dispatcher_send(self.hass, self.signal_device_new)
async def async_scan_devices(self, now: datetime | None = None) -> None:
"""Scan for new devices and return a list of found device ids."""
if self.hass.is_stopping:
_ha_is_stopping("scan devices")
return
async def async_update_device_info(self, now: datetime | None = None) -> None:
"""Update own device information."""
_LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host)
(
@@ -565,6 +569,13 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
self._release_url,
) = await self._async_update_device_info()
async def async_scan_devices(self, now: datetime | None = None) -> None:
"""Scan for new network devices."""
if self.hass.is_stopping:
_ha_is_stopping("scan devices")
return
_LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host)
_default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds()
if self._options:
@@ -683,7 +694,10 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
async def async_trigger_cleanup(self) -> None:
"""Trigger device trackers cleanup."""
device_hosts = await self._async_update_hosts_info()
_LOGGER.debug("Device tracker cleanup triggered")
device_hosts = {self.mac: Device(True, "", "", "", "", None)}
if self.device_discovery_enabled:
device_hosts = await self._async_update_hosts_info()
entity_reg: er.EntityRegistry = er.async_get(self.hass)
config_entry = self.config_entry
+15 -7
View File
@@ -4,7 +4,9 @@
"data_description_port": "Leave empty to use the default port.",
"data_description_username": "Username for the FRITZ!Box.",
"data_description_password": "Password for the FRITZ!Box.",
"data_description_ssl": "Use SSL to connect to the FRITZ!Box."
"data_description_ssl": "Use SSL to connect to the FRITZ!Box.",
"data_description_feature_device_tracking": "Enable or disable the network device tracking feature.",
"data_feature_device_tracking": "Enable network device tracking"
},
"config": {
"flow_title": "{name}",
@@ -15,12 +17,14 @@
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"ssl": "[%key:common::config_flow::data::ssl%]"
"ssl": "[%key:common::config_flow::data::ssl%]",
"feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]"
},
"data_description": {
"username": "[%key:component::fritz::common::data_description_username%]",
"password": "[%key:component::fritz::common::data_description_password%]",
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
"ssl": "[%key:component::fritz::common::data_description_ssl%]",
"feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]"
}
},
"reauth_confirm": {
@@ -57,14 +61,16 @@
"port": "[%key:common::config_flow::data::port%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"ssl": "[%key:common::config_flow::data::ssl%]"
"ssl": "[%key:common::config_flow::data::ssl%]",
"feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]"
},
"data_description": {
"host": "[%key:component::fritz::common::data_description_host%]",
"port": "[%key:component::fritz::common::data_description_port%]",
"username": "[%key:component::fritz::common::data_description_username%]",
"password": "[%key:component::fritz::common::data_description_password%]",
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
"ssl": "[%key:component::fritz::common::data_description_ssl%]",
"feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]"
}
}
},
@@ -89,11 +95,13 @@
"init": {
"data": {
"consider_home": "Seconds to consider a device at 'home'",
"old_discovery": "Enable old discovery method"
"old_discovery": "Enable old discovery method",
"feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]"
},
"data_description": {
"consider_home": "Time in seconds to consider a device at home. Default is 180 seconds.",
"old_discovery": "Enable old discovery method. This is needed for some scenarios."
"old_discovery": "Enable old discovery method. This is needed for some scenarios.",
"feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]"
}
}
}
@@ -55,6 +55,32 @@ BINARY_SENSOR_TYPES: Final[tuple[FritzBinarySensorEntityDescription, ...]] = (
suitable=lambda device: device.device_lock is not None,
is_on=lambda device: not device.device_lock,
),
FritzBinarySensorEntityDescription(
key="battery_low",
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
suitable=lambda device: device.battery_low is not None,
is_on=lambda device: device.battery_low,
entity_registry_enabled_default=False,
),
FritzBinarySensorEntityDescription(
key="holiday_active",
translation_key="holiday_active",
suitable=lambda device: device.holiday_active is not None,
is_on=lambda device: device.holiday_active,
),
FritzBinarySensorEntityDescription(
key="summer_active",
translation_key="summer_active",
suitable=lambda device: device.summer_active is not None,
is_on=lambda device: device.summer_active,
),
FritzBinarySensorEntityDescription(
key="window_open",
translation_key="window_open",
suitable=lambda device: device.window_open is not None,
is_on=lambda device: device.window_open,
),
)
+18 -10
View File
@@ -144,6 +144,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
self.check_active_or_lock_mode()
if kwargs.get(ATTR_HVAC_MODE) is HVACMode.OFF:
await self.async_set_hkr_state("off")
elif (target_temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
@@ -168,11 +169,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new operation mode."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_hvac_while_active_mode",
)
self.check_active_or_lock_mode()
if self.hvac_mode is hvac_mode:
LOGGER.debug(
"%s is already in requested hvac mode %s", self.name, hvac_mode
@@ -204,16 +201,13 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set preset mode."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_preset_while_active_mode",
)
self.check_active_or_lock_mode()
await self.async_set_hkr_state(PRESET_API_HKR_STATE_MAPPING[preset_mode])
@property
def extra_state_attributes(self) -> ClimateExtraAttributes:
"""Return the device specific state attributes."""
# deprecated with #143394, can be removed in 2025.11
attrs: ClimateExtraAttributes = {
ATTR_STATE_BATTERY_LOW: self.data.battery_low,
}
@@ -229,3 +223,17 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
attrs[ATTR_STATE_WINDOW_OPEN] = self.data.window_open
return attrs
def check_active_or_lock_mode(self) -> None:
"""Check if in summer/vacation mode or lock enabled."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_settings_while_active_mode",
)
if self.data.lock:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_settings_while_lock_enabled",
)
@@ -1,5 +1,28 @@
{
"entity": {
"binary_sensor": {
"holiday_active": {
"default": "mdi:bag-suitcase-outline",
"state": {
"on": "mdi:bag-suitcase-outline",
"off": "mdi:bag-suitcase-off-outline"
}
},
"summer_active": {
"default": "mdi:radiator-off",
"state": {
"on": "mdi:radiator-off",
"off": "mdi:radiator"
}
},
"window_open": {
"default": "mdi:window-open",
"state": {
"on": "mdi:window-open",
"off": "mdi:window-closed"
}
}
},
"climate": {
"thermostat": {
"state_attributes": {
@@ -55,7 +55,10 @@
"binary_sensor": {
"alarm": { "name": "Alarm" },
"device_lock": { "name": "Button lock via UI" },
"lock": { "name": "Button lock on device" }
"holiday_active": { "name": "Holiday mode" },
"lock": { "name": "Button lock on device" },
"summer_active": { "name": "Summer mode" },
"window_open": { "name": "Open window detected" }
},
"climate": {
"thermostat": {
@@ -85,11 +88,11 @@
"manual_switching_disabled": {
"message": "Can't toggle switch while manual switching is disabled for the device."
},
"change_preset_while_active_mode": {
"message": "Can't change preset while holiday or summer mode is active on the device."
"change_settings_while_lock_enabled": {
"message": "Can't change settings while manual access for telephone, app, or user interface is disabled on the device"
},
"change_hvac_while_active_mode": {
"message": "Can't change HVAC mode while holiday or summer mode is active on the device."
"change_settings_while_active_mode": {
"message": "Can't change settings while holiday or summer mode is active on the device."
}
}
}
@@ -39,9 +39,9 @@
"options": {
"step": {
"init": {
"title": "Configure Prefixes",
"title": "Configure prefixes",
"data": {
"prefixes": "Prefixes (comma separated list)"
"prefixes": "Prefixes (comma-separated list)"
}
}
},
@@ -82,13 +82,13 @@
"ac_frequency_too_high": "AC frequency too high",
"ac_frequency_too_low": "AC frequency too low",
"ac_grid_outside_permissible_limits": "AC grid outside the permissible limits",
"stand_alone_operation_detected": "Stand alone operation detected",
"stand_alone_operation_detected": "Stand-alone operation detected",
"rcmu_error": "RCMU error",
"arc_detection_triggered": "Arc detection triggered",
"overcurrent_ac": "Overcurrent (AC)",
"overcurrent_dc": "Overcurrent (DC)",
"dc_module_over_temperature": "DC module over temperature",
"ac_module_over_temperature": "AC module over temperature",
"dc_module_over_temperature": "DC module overtemperature",
"ac_module_over_temperature": "AC module overtemperature",
"no_power_fed_in_despite_closed_relay": "No power being fed in, despite closed relay",
"pv_output_too_low_for_feeding_energy_into_the_grid": "PV output too low for feeding energy into the grid",
"low_pv_voltage_dc_input_voltage_too_low": "Low PV voltage - DC input voltage too low for feeding energy into the grid",
@@ -133,16 +133,16 @@
"no_energy_fed_by_mppt1_past_24_hours": "No energy fed into the grid by MPPT1 in the past 24 hours",
"dc_low_string_1": "DC low string 1",
"dc_low_string_2": "DC low string 2",
"derating_caused_by_over_frequency": "Derating caused by over-frequency",
"derating_caused_by_over_frequency": "Derating caused by overfrequency",
"arc_detector_switched_off": "Arc detector switched off (e.g. during external arc monitoring)",
"grid_voltage_dependent_power_reduction_active": "Grid Voltage Dependent Power Reduction is active",
"grid_voltage_dependent_power_reduction_active": "Grid voltage-dependent power reduction (GVDPR) is active",
"can_bus_full": "CAN bus is full",
"ac_module_temperature_sensor_faulty_l3": "AC module temperature sensor faulty (L3)",
"dc_module_temperature_sensor_faulty": "DC module temperature sensor faulty",
"internal_processor_status": "Warning about the internal processor status. See status code for more information",
"eeprom_reinitialised": "EEPROM has been re-initialised",
"initialisation_error_usb_flash_drive_not_supported": "Initialisation error USB flash drive is not supported",
"initialisation_error_usb_stick_over_current": "Initialisation error Over current on USB stick",
"initialisation_error_usb_stick_over_current": "Initialisation error Overcurrent on USB stick",
"no_usb_flash_drive_connected": "No USB flash drive connected",
"update_file_not_recognised_or_missing": "Update file not recognised or not present",
"update_file_does_not_match_device": "Update file does not match the device, update file too old",
@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250411.0"]
"requirements": ["home-assistant-frontend==20250502.0"]
}
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/garages_amsterdam",
"iot_class": "cloud_polling",
"requirements": ["odp-amsterdam==6.0.2"]
"requirements": ["odp-amsterdam==6.1.1"]
}
@@ -0,0 +1 @@
"""Virtual integration: Google Gemini."""
@@ -0,0 +1,6 @@
{
"domain": "google_gemini",
"name": "Google Gemini",
"integration_type": "virtual",
"supported_by": "google_generative_ai_conversation"
}
@@ -1,11 +1,18 @@
"""The google_travel_time component."""
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from .const import CONF_TIME
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Google Maps Travel Time from a config entry."""
@@ -16,3 +23,37 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate an old config entry."""
if config_entry.version == 1:
_LOGGER.debug(
"Migrating from version %s.%s",
config_entry.version,
config_entry.minor_version,
)
options = dict(config_entry.options)
if options.get(CONF_TIME) == "now":
options[CONF_TIME] = None
elif options.get(CONF_TIME) is not None:
if dt_util.parse_time(options[CONF_TIME]) is None:
try:
from_timestamp = dt_util.utc_from_timestamp(int(options[CONF_TIME]))
options[CONF_TIME] = (
f"{from_timestamp.time().hour:02}:{from_timestamp.time().minute:02}"
)
except ValueError:
_LOGGER.error(
"Invalid time format found while migrating: %s. The old config never worked. Reset to default (empty)",
options[CONF_TIME],
)
options[CONF_TIME] = None
hass.config_entries.async_update_entry(config_entry, options=options, version=2)
_LOGGER.debug(
"Migration to version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)
return True
@@ -19,6 +19,7 @@ from homeassistant.helpers.selector import (
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
TimeSelector,
)
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
@@ -106,7 +107,7 @@ OPTIONS_SCHEMA = vol.Schema(
translation_key=CONF_TIME_TYPE,
)
),
vol.Optional(CONF_TIME, default=""): cv.string,
vol.Optional(CONF_TIME): TimeSelector(),
vol.Optional(CONF_TRAFFIC_MODEL): SelectSelector(
SelectSelectorConfig(
options=TRAFFIC_MODELS,
@@ -181,8 +182,7 @@ async def validate_input(
) -> dict[str, str] | None:
"""Validate the user input allows us to connect."""
try:
await hass.async_add_executor_job(
validate_config_entry,
await validate_config_entry(
hass,
user_input[CONF_API_KEY],
user_input[CONF_ORIGIN],
@@ -201,7 +201,7 @@ async def validate_input(
class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Google Maps Travel Time."""
VERSION = 1
VERSION = 2
@staticmethod
@callback
@@ -1,5 +1,12 @@
"""Constants for Google Travel Time."""
from google.maps.routing_v2 import (
RouteTravelMode,
TrafficModel,
TransitPreferences,
Units,
)
DOMAIN = "google_travel_time"
ATTRIBUTION = "Powered by Google"
@@ -7,7 +14,6 @@ ATTRIBUTION = "Powered by Google"
CONF_DESTINATION = "destination"
CONF_OPTIONS = "options"
CONF_ORIGIN = "origin"
CONF_TRAVEL_MODE = "travel_mode"
CONF_AVOID = "avoid"
CONF_UNITS = "units"
CONF_ARRIVAL_TIME = "arrival_time"
@@ -79,11 +85,37 @@ ALL_LANGUAGES = [
AVOID_OPTIONS = ["tolls", "highways", "ferries", "indoor"]
TRANSIT_PREFS = ["less_walking", "fewer_transfers"]
TRANSIT_PREFS_TO_GOOGLE_SDK_ENUM = {
"less_walking": TransitPreferences.TransitRoutingPreference.LESS_WALKING,
"fewer_transfers": TransitPreferences.TransitRoutingPreference.FEWER_TRANSFERS,
}
TRANSPORT_TYPES = ["bus", "subway", "train", "tram", "rail"]
TRANSPORT_TYPES_TO_GOOGLE_SDK_ENUM = {
"bus": TransitPreferences.TransitTravelMode.BUS,
"subway": TransitPreferences.TransitTravelMode.SUBWAY,
"train": TransitPreferences.TransitTravelMode.TRAIN,
"tram": TransitPreferences.TransitTravelMode.LIGHT_RAIL,
"rail": TransitPreferences.TransitTravelMode.RAIL,
}
TRAVEL_MODES = ["driving", "walking", "bicycling", "transit"]
TRAVEL_MODES_TO_GOOGLE_SDK_ENUM = {
"driving": RouteTravelMode.DRIVE,
"walking": RouteTravelMode.WALK,
"bicycling": RouteTravelMode.BICYCLE,
"transit": RouteTravelMode.TRANSIT,
}
TRAFFIC_MODELS = ["best_guess", "pessimistic", "optimistic"]
TRAFFIC_MODELS_TO_GOOGLE_SDK_ENUM = {
"best_guess": TrafficModel.BEST_GUESS,
"pessimistic": TrafficModel.PESSIMISTIC,
"optimistic": TrafficModel.OPTIMISTIC,
}
# googlemaps library uses "metric" or "imperial" terminology in distance_matrix
UNITS_METRIC = "metric"
UNITS_IMPERIAL = "imperial"
UNITS = [UNITS_METRIC, UNITS_IMPERIAL]
UNITS_TO_GOOGLE_SDK_ENUM = {
UNITS_METRIC: Units.METRIC,
UNITS_IMPERIAL: Units.IMPERIAL,
}
@@ -2,41 +2,80 @@
import logging
from googlemaps import Client
from googlemaps.distance_matrix import distance_matrix
from googlemaps.exceptions import ApiError, Timeout, TransportError
from google.api_core.client_options import ClientOptions
from google.api_core.exceptions import (
Forbidden,
GatewayTimeout,
GoogleAPIError,
Unauthorized,
)
from google.maps.routing_v2 import (
ComputeRoutesRequest,
Location,
RoutesAsyncClient,
RouteTravelMode,
Waypoint,
)
from google.type import latlng_pb2
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.location import find_coordinates
_LOGGER = logging.getLogger(__name__)
def validate_config_entry(
def convert_to_waypoint(hass: HomeAssistant, location: str) -> Waypoint | None:
"""Convert a location to a Waypoint.
Will either use coordinates or if none are found, use the location as an address.
"""
coordinates = find_coordinates(hass, location)
if coordinates is None:
return None
try:
formatted_coordinates = coordinates.split(",")
vol.Schema(cv.gps(formatted_coordinates))
except (AttributeError, vol.ExactSequenceInvalid):
return Waypoint(address=location)
return Waypoint(
location=Location(
lat_lng=latlng_pb2.LatLng(
latitude=float(formatted_coordinates[0]),
longitude=float(formatted_coordinates[1]),
)
)
)
async def validate_config_entry(
hass: HomeAssistant, api_key: str, origin: str, destination: str
) -> None:
"""Return whether the config entry data is valid."""
resolved_origin = find_coordinates(hass, origin)
resolved_destination = find_coordinates(hass, destination)
resolved_origin = convert_to_waypoint(hass, origin)
resolved_destination = convert_to_waypoint(hass, destination)
client_options = ClientOptions(api_key=api_key)
client = RoutesAsyncClient(client_options=client_options)
field_mask = "routes.duration"
request = ComputeRoutesRequest(
origin=resolved_origin,
destination=resolved_destination,
travel_mode=RouteTravelMode.DRIVE,
)
try:
client = Client(api_key, timeout=10)
except ValueError as value_error:
_LOGGER.error("Malformed API key")
raise InvalidApiKeyException from value_error
try:
distance_matrix(client, resolved_origin, resolved_destination, mode="driving")
except ApiError as api_error:
if api_error.status == "REQUEST_DENIED":
_LOGGER.error("Request denied: %s", api_error.message)
raise InvalidApiKeyException from api_error
_LOGGER.error("Unknown error: %s", api_error.message)
raise UnknownException from api_error
except TransportError as transport_error:
_LOGGER.error("Unknown error: %s", transport_error)
raise UnknownException from transport_error
except Timeout as timeout_error:
await client.compute_routes(
request, metadata=[("x-goog-fieldmask", field_mask)]
)
except (Unauthorized, Forbidden) as unauthorized_error:
_LOGGER.error("Request denied: %s", unauthorized_error.message)
raise InvalidApiKeyException from unauthorized_error
except GatewayTimeout as timeout_error:
_LOGGER.error("Timeout error")
raise TimeoutError from timeout_error
except GoogleAPIError as unknown_error:
_LOGGER.error("Unknown error: %s", unknown_error)
raise UnknownException from unknown_error
class InvalidApiKeyException(Exception):
@@ -5,6 +5,6 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/google_travel_time",
"iot_class": "cloud_polling",
"loggers": ["googlemaps", "homeassistant.helpers.location"],
"requirements": ["googlemaps==2.5.1"]
"loggers": ["google", "homeassistant.helpers.location"],
"requirements": ["google-maps-routing==0.6.14"]
}
@@ -2,12 +2,22 @@
from __future__ import annotations
from datetime import datetime, timedelta
import datetime
import logging
from typing import TYPE_CHECKING, Any
from googlemaps import Client
from googlemaps.distance_matrix import distance_matrix
from googlemaps.exceptions import ApiError, Timeout, TransportError
from google.api_core.client_options import ClientOptions
from google.api_core.exceptions import GoogleAPIError
from google.maps.routing_v2 import (
ComputeRoutesRequest,
Route,
RouteModifiers,
RoutesAsyncClient,
RouteTravelMode,
RoutingPreference,
TransitPreferences,
)
from google.protobuf import timestamp_pb2
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -17,6 +27,8 @@ from homeassistant.components.sensor import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_LANGUAGE,
CONF_MODE,
CONF_NAME,
EVENT_HOMEASSISTANT_STARTED,
UnitOfTime,
@@ -30,26 +42,49 @@ from homeassistant.util import dt as dt_util
from .const import (
ATTRIBUTION,
CONF_ARRIVAL_TIME,
CONF_AVOID,
CONF_DEPARTURE_TIME,
CONF_DESTINATION,
CONF_ORIGIN,
CONF_TRAFFIC_MODEL,
CONF_TRANSIT_MODE,
CONF_TRANSIT_ROUTING_PREFERENCE,
CONF_UNITS,
DEFAULT_NAME,
DOMAIN,
TRAFFIC_MODELS_TO_GOOGLE_SDK_ENUM,
TRANSIT_PREFS_TO_GOOGLE_SDK_ENUM,
TRANSPORT_TYPES_TO_GOOGLE_SDK_ENUM,
TRAVEL_MODES_TO_GOOGLE_SDK_ENUM,
UNITS_TO_GOOGLE_SDK_ENUM,
)
from .helpers import convert_to_waypoint
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=5)
SCAN_INTERVAL = datetime.timedelta(minutes=10)
FIELD_MASK = "routes.duration,routes.localized_values"
def convert_time_to_utc(timestr):
"""Take a string like 08:00:00 and convert it to a unix timestamp."""
combined = datetime.combine(
dt_util.start_of_local_day(), dt_util.parse_time(timestr)
def convert_time(time_str: str) -> timestamp_pb2.Timestamp | None:
"""Convert a string like '08:00' to a google pb2 Timestamp.
If the time is in the past, it will be shifted to the next day.
"""
parsed_time = dt_util.parse_time(time_str)
if TYPE_CHECKING:
assert parsed_time is not None
start_of_day = dt_util.start_of_local_day()
combined = datetime.datetime.combine(
start_of_day,
parsed_time,
start_of_day.tzinfo,
)
if combined < datetime.now():
combined = combined + timedelta(days=1)
return dt_util.as_timestamp(combined)
if combined < dt_util.now():
combined = combined + datetime.timedelta(days=1)
timestamp = timestamp_pb2.Timestamp()
timestamp.FromDatetime(dt=combined)
return timestamp
async def async_setup_entry(
@@ -63,7 +98,8 @@ async def async_setup_entry(
destination = config_entry.data[CONF_DESTINATION]
name = config_entry.data.get(CONF_NAME, DEFAULT_NAME)
client = Client(api_key, timeout=10)
client_options = ClientOptions(api_key=api_key)
client = RoutesAsyncClient(client_options=client_options)
sensor = GoogleTravelTimeSensor(
config_entry, name, api_key, origin, destination, client
@@ -80,7 +116,15 @@ class GoogleTravelTimeSensor(SensorEntity):
_attr_device_class = SensorDeviceClass.DURATION
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, config_entry, name, api_key, origin, destination, client):
def __init__(
self,
config_entry: ConfigEntry,
name: str,
api_key: str,
origin: str,
destination: str,
client: RoutesAsyncClient,
) -> None:
"""Initialize the sensor."""
self._attr_name = name
self._attr_unique_id = config_entry.entry_id
@@ -91,13 +135,12 @@ class GoogleTravelTimeSensor(SensorEntity):
)
self._config_entry = config_entry
self._matrix = None
self._api_key = api_key
self._route: Route | None = None
self._client = client
self._origin = origin
self._destination = destination
self._resolved_origin = None
self._resolved_destination = None
self._resolved_origin: str | None = None
self._resolved_destination: str | None = None
async def async_added_to_hass(self) -> None:
"""Handle when entity is added."""
@@ -109,77 +152,127 @@ class GoogleTravelTimeSensor(SensorEntity):
await self.first_update()
@property
def native_value(self):
def native_value(self) -> float | None:
"""Return the state of the sensor."""
if self._matrix is None:
if self._route is None:
return None
_data = self._matrix["rows"][0]["elements"][0]
if "duration_in_traffic" in _data:
return round(_data["duration_in_traffic"]["value"] / 60)
if "duration" in _data:
return round(_data["duration"]["value"] / 60)
return None
return round(self._route.duration.seconds / 60)
@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes."""
if self._matrix is None:
if self._route is None:
return None
res = self._matrix.copy()
options = self._config_entry.options.copy()
res.update(options)
del res["rows"]
_data = self._matrix["rows"][0]["elements"][0]
if "duration_in_traffic" in _data:
res["duration_in_traffic"] = _data["duration_in_traffic"]["text"]
if "duration" in _data:
res["duration"] = _data["duration"]["text"]
if "distance" in _data:
res["distance"] = _data["distance"]["text"]
res["origin"] = self._resolved_origin
res["destination"] = self._resolved_destination
return res
result = self._config_entry.options.copy()
result["duration_in_traffic"] = self._route.localized_values.duration.text
result["duration"] = self._route.localized_values.static_duration.text
result["distance"] = self._route.localized_values.distance.text
async def first_update(self, _=None):
result["origin"] = self._resolved_origin
result["destination"] = self._resolved_destination
return result
async def first_update(self, _=None) -> None:
"""Run the first update and write the state."""
await self.hass.async_add_executor_job(self.update)
await self.async_update()
self.async_write_ha_state()
def update(self) -> None:
async def async_update(self) -> None:
"""Get the latest data from Google."""
options_copy = self._config_entry.options.copy()
dtime = options_copy.get(CONF_DEPARTURE_TIME)
atime = options_copy.get(CONF_ARRIVAL_TIME)
if dtime is not None and ":" in dtime:
options_copy[CONF_DEPARTURE_TIME] = convert_time_to_utc(dtime)
elif dtime is not None:
options_copy[CONF_DEPARTURE_TIME] = dtime
elif atime is None:
options_copy[CONF_DEPARTURE_TIME] = "now"
travel_mode = TRAVEL_MODES_TO_GOOGLE_SDK_ENUM[
self._config_entry.options[CONF_MODE]
]
if atime is not None and ":" in atime:
options_copy[CONF_ARRIVAL_TIME] = convert_time_to_utc(atime)
elif atime is not None:
options_copy[CONF_ARRIVAL_TIME] = atime
if (
departure_time := self._config_entry.options.get(CONF_DEPARTURE_TIME)
) is not None:
departure_time = convert_time(departure_time)
if (
arrival_time := self._config_entry.options.get(CONF_ARRIVAL_TIME)
) is not None:
arrival_time = convert_time(arrival_time)
if travel_mode != RouteTravelMode.TRANSIT:
arrival_time = None
traffic_model = None
routing_preference = None
route_modifiers = None
if travel_mode == RouteTravelMode.DRIVE:
if (
options_traffic_model := self._config_entry.options.get(
CONF_TRAFFIC_MODEL
)
) is not None:
traffic_model = TRAFFIC_MODELS_TO_GOOGLE_SDK_ENUM[options_traffic_model]
routing_preference = RoutingPreference.TRAFFIC_AWARE_OPTIMAL
route_modifiers = RouteModifiers(
avoid_tolls=self._config_entry.options.get(CONF_AVOID) == "tolls",
avoid_ferries=self._config_entry.options.get(CONF_AVOID) == "ferries",
avoid_highways=self._config_entry.options.get(CONF_AVOID) == "highways",
avoid_indoor=self._config_entry.options.get(CONF_AVOID) == "indoor",
)
transit_preferences = None
if travel_mode == RouteTravelMode.TRANSIT:
transit_routing_preference = None
transit_travel_mode = (
TransitPreferences.TransitTravelMode.TRANSIT_TRAVEL_MODE_UNSPECIFIED
)
if (
option_transit_preferences := self._config_entry.options.get(
CONF_TRANSIT_ROUTING_PREFERENCE
)
) is not None:
transit_routing_preference = TRANSIT_PREFS_TO_GOOGLE_SDK_ENUM[
option_transit_preferences
]
if (
option_transit_mode := self._config_entry.options.get(CONF_TRANSIT_MODE)
) is not None:
transit_travel_mode = TRANSPORT_TYPES_TO_GOOGLE_SDK_ENUM[
option_transit_mode
]
transit_preferences = TransitPreferences(
routing_preference=transit_routing_preference,
allowed_travel_modes=[transit_travel_mode],
)
language = None
if (
options_language := self._config_entry.options.get(CONF_LANGUAGE)
) is not None:
language = options_language
self._resolved_origin = find_coordinates(self.hass, self._origin)
self._resolved_destination = find_coordinates(self.hass, self._destination)
_LOGGER.debug(
"Getting update for origin: %s destination: %s",
self._resolved_origin,
self._resolved_destination,
)
if self._resolved_destination is not None and self._resolved_origin is not None:
request = ComputeRoutesRequest(
origin=convert_to_waypoint(self.hass, self._resolved_origin),
destination=convert_to_waypoint(self.hass, self._resolved_destination),
travel_mode=travel_mode,
routing_preference=routing_preference,
departure_time=departure_time,
arrival_time=arrival_time,
route_modifiers=route_modifiers,
language_code=language,
units=UNITS_TO_GOOGLE_SDK_ENUM[self._config_entry.options[CONF_UNITS]],
traffic_model=traffic_model,
transit_preferences=transit_preferences,
)
try:
self._matrix = distance_matrix(
self._client,
self._resolved_origin,
self._resolved_destination,
**options_copy,
response = await self._client.compute_routes(
request, metadata=[("x-goog-fieldmask", FIELD_MASK)]
)
except (ApiError, TransportError, Timeout) as ex:
if response is not None and len(response.routes) > 0:
self._route = response.routes[0]
except GoogleAPIError as ex:
_LOGGER.error("Error getting travel time: %s", ex)
self._matrix = None
self._route = None
@@ -3,7 +3,7 @@
"config": {
"step": {
"user": {
"description": "You can specify the origin and destination in the form of an address, latitude/longitude coordinates, or a Google place ID. When specifying the location using a Google place ID, the ID must be prefixed with `place_id:`.",
"description": "You can specify the origin and destination in the form of an address, latitude/longitude coordinates or an entity ID that provides this information in its state, an entity ID with latitude and longitude attributes, or zone friendly name (case sensitive)",
"data": {
"name": "[%key:common::config_flow::data::name%]",
"api_key": "[%key:common::config_flow::data::api_key%]",
@@ -33,16 +33,16 @@
"options": {
"step": {
"init": {
"description": "You can optionally specify either a Departure Time or Arrival Time. If specifying a departure time, you can enter `now`, a Unix timestamp, or a 24 hour time string like `08:00:00`. If specifying an arrival time, you can use a Unix timestamp or a 24 hour time string like `08:00:00`",
"description": "You can optionally specify either a departure time or arrival time in the form of a 24 hour time string like `08:00:00`",
"data": {
"mode": "Travel Mode",
"mode": "Travel mode",
"language": "[%key:common::config_flow::data::language%]",
"time_type": "Time Type",
"time_type": "Time type",
"time": "Time",
"avoid": "Avoid",
"traffic_model": "Traffic Model",
"transit_mode": "Transit Mode",
"transit_routing_preference": "Transit Routing Preference",
"traffic_model": "Traffic model",
"transit_mode": "Transit mode",
"transit_routing_preference": "Transit routing preference",
"units": "Units"
}
}
@@ -68,19 +68,19 @@
},
"units": {
"options": {
"metric": "Metric System",
"imperial": "Imperial System"
"metric": "Metric system",
"imperial": "Imperial system"
}
},
"time_type": {
"options": {
"arrival_time": "Arrival Time",
"departure_time": "Departure Time"
"arrival_time": "Arrival time",
"departure_time": "Departure time"
}
},
"traffic_model": {
"options": {
"best_guess": "Best Guess",
"best_guess": "Best guess",
"pessimistic": "Pessimistic",
"optimistic": "Optimistic"
}
@@ -96,8 +96,8 @@
},
"transit_routing_preference": {
"options": {
"less_walking": "Less Walking",
"fewer_transfers": "Fewer Transfers"
"less_walking": "Less walking",
"fewer_transfers": "Fewer transfers"
}
}
}
@@ -164,7 +164,7 @@
"name": "Load consumption today (solar)"
},
"mix_self_consumption_today": {
"name": "Self consumption today (solar + battery)"
"name": "Self-consumption today (solar + battery)"
},
"mix_load_consumption_battery_today": {
"name": "Load consumption today (battery)"
@@ -173,7 +173,7 @@
"name": "Import from grid today (load)"
},
"mix_last_update": {
"name": "Last Data Update"
"name": "Last data update"
},
"mix_import_from_grid_today_combined": {
"name": "Import from grid today (load + charging)"
+1 -10
View File
@@ -1,6 +1,6 @@
"""Constants for the habitica integration."""
from homeassistant.const import APPLICATION_NAME, CONF_PATH, __version__
from homeassistant.const import APPLICATION_NAME, __version__
CONF_API_USER = "api_user"
@@ -13,15 +13,6 @@ HABITICANS_URL = "https://habitica.com/static/img/home-main@3x.ffc32b12.png"
DOMAIN = "habitica"
# service constants
SERVICE_API_CALL = "api_call"
ATTR_PATH = CONF_PATH
ATTR_ARGS = "args"
# event constants
EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success"
ATTR_DATA = "data"
MANUFACTURER = "HabitRPG, Inc."
NAME = "Habitica"
+1 -61
View File
@@ -29,7 +29,7 @@ import voluptuous as vol
from homeassistant.components.todo import ATTR_RENAME
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DATE, ATTR_NAME, CONF_NAME
from homeassistant.const import ATTR_DATE, ATTR_NAME
from homeassistant.core import (
HomeAssistant,
ServiceCall,
@@ -38,28 +38,24 @@ from homeassistant.core import (
)
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.selector import ConfigEntrySelector
from homeassistant.util import dt as dt_util
from .const import (
ATTR_ADD_CHECKLIST_ITEM,
ATTR_ALIAS,
ATTR_ARGS,
ATTR_CLEAR_DATE,
ATTR_CLEAR_REMINDER,
ATTR_CONFIG_ENTRY,
ATTR_COST,
ATTR_COUNTER_DOWN,
ATTR_COUNTER_UP,
ATTR_DATA,
ATTR_DIRECTION,
ATTR_FREQUENCY,
ATTR_INTERVAL,
ATTR_ITEM,
ATTR_KEYWORD,
ATTR_NOTES,
ATTR_PATH,
ATTR_PRIORITY,
ATTR_REMINDER,
ATTR_REMOVE_CHECKLIST_ITEM,
@@ -78,10 +74,8 @@ from .const import (
ATTR_UNSCORE_CHECKLIST_ITEM,
ATTR_UP_DOWN,
DOMAIN,
EVENT_API_CALL_SUCCESS,
SERVICE_ABORT_QUEST,
SERVICE_ACCEPT_QUEST,
SERVICE_API_CALL,
SERVICE_CANCEL_QUEST,
SERVICE_CAST_SKILL,
SERVICE_CREATE_DAILY,
@@ -106,14 +100,6 @@ from .coordinator import HabiticaConfigEntry
_LOGGER = logging.getLogger(__name__)
SERVICE_API_CALL_SCHEMA = vol.Schema(
{
vol.Required(ATTR_NAME): str,
vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]),
vol.Optional(ATTR_ARGS): dict,
}
)
SERVICE_CAST_SKILL_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector({"integration": DOMAIN}),
@@ -266,46 +252,6 @@ def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
"""Set up services for Habitica integration."""
async def handle_api_call(call: ServiceCall) -> None:
async_create_issue(
hass,
DOMAIN,
"deprecated_api_call",
breaks_in_ha_version="2025.6.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_api_call",
)
_LOGGER.warning(
"Deprecated action called: 'habitica.api_call' is deprecated and will be removed in Home Assistant version 2025.6.0"
)
name = call.data[ATTR_NAME]
path = call.data[ATTR_PATH]
entries: list[HabiticaConfigEntry] = hass.config_entries.async_entries(DOMAIN)
api = None
for entry in entries:
if entry.data[CONF_NAME] == name:
api = await entry.runtime_data.habitica.habitipy()
break
if api is None:
_LOGGER.error("API_CALL: User '%s' not configured", name)
return
try:
for element in path:
api = api[element]
except KeyError:
_LOGGER.error(
"API_CALL: Path %s is invalid for API on '{%s}' element", path, element
)
return
kwargs = call.data.get(ATTR_ARGS, {})
data = await api(**kwargs)
hass.bus.async_fire(
EVENT_API_CALL_SUCCESS, {ATTR_NAME: name, ATTR_PATH: path, ATTR_DATA: data}
)
async def cast_skill(call: ServiceCall) -> ServiceResponse:
"""Skill action."""
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
@@ -928,12 +874,6 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
schema=SERVICE_CREATE_TASK_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
SERVICE_API_CALL,
handle_api_call,
schema=SERVICE_API_CALL_SCHEMA,
)
hass.services.async_register(
DOMAIN,
@@ -1,20 +1,4 @@
# Describes the format for Habitica service
api_call:
fields:
name:
required: true
example: "xxxNotAValidNickxxx"
selector:
text:
path:
required: true
example: '["tasks", "user", "post"]'
selector:
object:
args:
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
selector:
object:
cast_skill:
fields:
config_entry: &config_entry
@@ -526,31 +526,9 @@
"deprecated_entity": {
"title": "The Habitica {name} entity is deprecated",
"description": "The Habitica entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your automations and scripts, disable `{entity}` and reload the integration/restart Home Assistant to fix this issue."
},
"deprecated_api_call": {
"title": "The Habitica action habitica.api_call is deprecated",
"description": "The Habitica action `habitica.api_call` is deprecated and will be removed in Home Assistant 2025.5.0.\n\nPlease update your automations and scripts to use other Habitica actions and entities."
}
},
"services": {
"api_call": {
"name": "API name",
"description": "Calls Habitica API.",
"fields": {
"name": {
"name": "[%key:common::config_flow::data::name%]",
"description": "Habitica's username to call for."
},
"path": {
"name": "[%key:common::config_flow::data::path%]",
"description": "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks."
},
"args": {
"name": "Args",
"description": "Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint."
}
}
},
"cast_skill": {
"name": "Cast a skill",
"description": "Uses a skill or spell from your Habitica character on a specific task to affect its progress or status.",
+7 -5
View File
@@ -385,18 +385,20 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
)
last_timezone = None
last_country = None
async def push_config(_: Event | None) -> None:
"""Push core config to Hass.io."""
nonlocal last_timezone
nonlocal last_country
new_timezone = str(hass.config.time_zone)
new_country = str(hass.config.country)
if new_timezone == last_timezone:
return
last_timezone = new_timezone
await hassio.update_hass_timezone(new_timezone)
if new_timezone != last_timezone or new_country != last_country:
last_timezone = new_timezone
last_country = new_country
await hassio.update_hass_config(new_timezone, new_country)
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, push_config)
+4 -2
View File
@@ -248,12 +248,14 @@ class HassIO:
return await self.send_command("/homeassistant/options", payload=options)
@_api_bool
def update_hass_timezone(self, timezone: str) -> Coroutine:
def update_hass_config(self, timezone: str, country: str | None) -> Coroutine:
"""Update Home-Assistant timezone data on Hass.io.
This method returns a coroutine.
"""
return self.send_command("/supervisor/options", payload={"timezone": timezone})
return self.send_command(
"/supervisor/options", payload={"timezone": timezone, "country": country}
)
@_api_bool
def update_diagnostics(self, diagnostics: bool) -> Coroutine:
@@ -7,6 +7,7 @@ from typing import Any
from aiohomeconnect.client import Client as HomeConnectClient
import aiohttp
import jwt
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
@@ -110,25 +111,39 @@ async def async_migrate_entry(
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1 and entry.minor_version == 1:
if entry.version == 1:
match entry.minor_version:
case 1:
@callback
def update_unique_id(
entity_entry: RegistryEntry,
) -> dict[str, Any] | None:
"""Update unique ID of entity entry."""
for old_id_suffix, new_id_suffix in OLD_NEW_UNIQUE_ID_SUFFIX_MAP.items():
if entity_entry.unique_id.endswith(f"-{old_id_suffix}"):
return {
"new_unique_id": entity_entry.unique_id.replace(
old_id_suffix, new_id_suffix
)
}
return None
@callback
def update_unique_id(
entity_entry: RegistryEntry,
) -> dict[str, Any] | None:
"""Update unique ID of entity entry."""
for (
old_id_suffix,
new_id_suffix,
) in OLD_NEW_UNIQUE_ID_SUFFIX_MAP.items():
if entity_entry.unique_id.endswith(f"-{old_id_suffix}"):
return {
"new_unique_id": entity_entry.unique_id.replace(
old_id_suffix, new_id_suffix
)
}
return None
await async_migrate_entries(hass, entry.entry_id, update_unique_id)
await async_migrate_entries(hass, entry.entry_id, update_unique_id)
hass.config_entries.async_update_entry(entry, minor_version=2)
hass.config_entries.async_update_entry(entry, minor_version=2)
case 2:
hass.config_entries.async_update_entry(
entry,
minor_version=3,
unique_id=jwt.decode(
entry.data["token"]["access_token"],
options={"verify_signature": False},
)["sub"],
)
_LOGGER.debug("Migration to version %s successful", entry.version)
return True
@@ -4,6 +4,7 @@ from collections.abc import Mapping
import logging
from typing import Any
import jwt
import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
@@ -19,7 +20,7 @@ class OAuth2FlowHandler(
DOMAIN = DOMAIN
MINOR_VERSION = 2
MINOR_VERSION = 3
@property
def logger(self) -> logging.Logger:
@@ -45,9 +46,15 @@ class OAuth2FlowHandler(
async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult:
"""Create an oauth config entry or update existing entry for reauth."""
await self.async_set_unique_id(
jwt.decode(
data["token"]["access_token"], options={"verify_signature": False}
)["sub"]
)
if self.source == SOURCE_REAUTH:
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(
self._get_reauth_entry(),
data_updates=data,
self._get_reauth_entry(), data_updates=data
)
self._abort_if_unique_id_configured()
return await super().async_oauth_create_entry(data)
@@ -4,8 +4,6 @@ from __future__ import annotations
from typing import Any
from aiohomeconnect.client import Client as HomeConnectClient
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntry
@@ -14,7 +12,7 @@ from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
async def _generate_appliance_diagnostics(
client: HomeConnectClient, appliance: HomeConnectApplianceData
appliance: HomeConnectApplianceData,
) -> dict[str, Any]:
return {
**appliance.info.to_dict(),
@@ -31,9 +29,7 @@ async def async_get_config_entry_diagnostics(
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
appliance.info.ha_id: await _generate_appliance_diagnostics(
entry.runtime_data.client, appliance
)
appliance.info.ha_id: await _generate_appliance_diagnostics(appliance)
for appliance in entry.runtime_data.data.values()
}
@@ -45,6 +41,4 @@ async def async_get_device_diagnostics(
ha_id = next(
(identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN),
)
return await _generate_appliance_diagnostics(
entry.runtime_data.client, entry.runtime_data.data[ha_id]
)
return await _generate_appliance_diagnostics(entry.runtime_data.data[ha_id])
@@ -32,7 +32,6 @@ _LOGGER = logging.getLogger(__name__)
class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
"""Generic Home Connect entity (base class)."""
_attr_should_poll = False
_attr_has_entity_name = True
def __init__(
@@ -39,11 +39,11 @@ PARALLEL_UPDATES = 1
class HomeConnectLightEntityDescription(LightEntityDescription):
"""Light entity description."""
brightness_key: SettingKey | None = None
brightness_key: SettingKey
brightness_scale: tuple[float, float]
color_key: SettingKey | None = None
enable_custom_color_value_key: str | None = None
custom_color_key: SettingKey | None = None
brightness_scale: tuple[float, float] = (0.0, 100.0)
LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = (
@@ -4,9 +4,23 @@
"codeowners": ["@DavidMStraub", "@Diegorro98", "@MartinHjelmare"],
"config_flow": true,
"dependencies": ["application_credentials", "repairs"],
"dhcp": [
{
"hostname": "balay-*",
"macaddress": "C8D778*"
},
{
"hostname": "(bosch|siemens)-*",
"macaddress": "68A40E*"
},
{
"hostname": "siemens-*",
"macaddress": "38B4D3*"
}
],
"documentation": "https://www.home-assistant.io/integrations/home_connect",
"iot_class": "cloud_push",
"loggers": ["aiohomeconnect"],
"requirements": ["aiohomeconnect==0.17.0"],
"single_config_entry": true
"zeroconf": ["_homeconnect._tcp.local."]
}
@@ -11,6 +11,7 @@ from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
)
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -79,7 +80,7 @@ NUMBERS = (
NumberEntityDescription(
key=SettingKey.COOKING_HOOD_COLOR_TEMPERATURE_PERCENT,
translation_key="color_temperature_percent",
native_unit_of_measurement="%",
native_unit_of_measurement=PERCENTAGE,
),
NumberEntityDescription(
key=SettingKey.LAUNDRY_CARE_WASHER_I_DOS_1_BASE_LEVEL,
@@ -11,7 +11,7 @@ from aiohomeconnect.model.error import HomeConnectError
from aiohomeconnect.model.program import Execution
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -366,16 +366,37 @@ class HomeConnectProgramSelectEntity(HomeConnectEntity, SelectEntity):
appliance,
desc,
)
self.set_options()
def set_options(self) -> None:
"""Set the options for the entity."""
self._attr_options = [
PROGRAMS_TRANSLATION_KEYS_MAP[program.key]
for program in appliance.programs
for program in self.appliance.programs
if program.key != ProgramKey.UNKNOWN
and (
program.constraints is None
or program.constraints.execution in desc.allowed_executions
or program.constraints.execution
in self.entity_description.allowed_executions
)
]
@callback
def refresh_options(self) -> None:
"""Refresh the options for the entity."""
self.set_options()
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.async_add_listener(
self.refresh_options,
(self.appliance.info.ha_id, EventKey.BSH_COMMON_APPLIANCE_CONNECTED),
)
)
def update_native_value(self) -> None:
"""Set the program value."""
event = self.appliance.events.get(cast(EventKey, self.bsh_key))
@@ -159,7 +159,6 @@ SENSORS = (
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_BATTERY_LEVEL,
device_class=SensorDeviceClass.BATTERY,
translation_key="battery_level",
),
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_VIDEO_CAMERA_STATE,
@@ -14,13 +14,15 @@
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"wrong_account": "Please ensure you reconfigure against the same account."
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
@@ -1549,34 +1551,39 @@
}
},
"coffee_counter": {
"name": "Coffees"
"name": "Coffees",
"unit_of_measurement": "coffees"
},
"powder_coffee_counter": {
"name": "Powder coffees"
"name": "Powder coffees",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::coffee_counter::unit_of_measurement%]"
},
"hot_water_counter": {
"name": "Hot water"
},
"hot_water_cups_counter": {
"name": "Hot water cups"
"name": "Hot water cups",
"unit_of_measurement": "cups"
},
"hot_milk_counter": {
"name": "Hot milk cups"
"name": "Hot milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"frothy_milk_counter": {
"name": "Frothy milk cups"
"name": "Frothy milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"milk_counter": {
"name": "Milk cups"
"name": "Milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"coffee_and_milk_counter": {
"name": "Coffee and milk cups"
"name": "Coffee and milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"ristretto_espresso_counter": {
"name": "Ristretto espresso cups"
},
"battery_level": {
"name": "Battery level"
"name": "Ristretto espresso cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"camera_state": {
"name": "Camera state",
@@ -79,7 +79,7 @@ class HomeConnectTimeEntity(HomeConnectEntity, TimeEntity):
async def async_added_to_hass(self) -> None:
"""Call when entity is added to hass."""
await super().async_added_to_hass()
if self.bsh_key == SettingKey.BSH_COMMON_ALARM_CLOCK:
if self.bsh_key is SettingKey.BSH_COMMON_ALARM_CLOCK:
automations = automations_with_entity(self.hass, self.entity_id)
scripts = scripts_with_entity(self.hass, self.entity_id)
items = automations + scripts
@@ -123,7 +123,7 @@ class HomeConnectTimeEntity(HomeConnectEntity, TimeEntity):
async def async_will_remove_from_hass(self) -> None:
"""Call when entity will be removed from hass."""
if self.bsh_key == SettingKey.BSH_COMMON_ALARM_CLOCK:
if self.bsh_key is SettingKey.BSH_COMMON_ALARM_CLOCK:
async_delete_issue(
self.hass,
DOMAIN,
@@ -9,10 +9,10 @@ from typing import Any
from homematicip.async_home import AsyncHome
from homematicip.auth import Auth
from homematicip.base.base_connection import HmipConnectionError
from homematicip.base.enums import EventType
from homematicip.connection.connection_context import ConnectionContextBuilder
from homematicip.connection.rest_connection import RestConnection
from homematicip.exceptions.connection_exceptions import HmipConnectionError
import homeassistant
from homeassistant.config_entries import ConfigEntry
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/homematicip_cloud",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.0.1"]
"requirements": ["homematicip==2.0.1.1"]
}
@@ -46,6 +46,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
LIGHT_LUX,
PERCENTAGE,
UnitOfEnergy,
@@ -127,6 +128,7 @@ async def async_setup_entry(
):
entities.append(HomematicipTemperatureSensor(hap, device))
entities.append(HomematicipHumiditySensor(hap, device))
entities.append(HomematicipAbsoluteHumiditySensor(hap, device))
elif isinstance(device, (RoomControlDeviceAnalog,)):
entities.append(HomematicipTemperatureSensor(hap, device))
if isinstance(
@@ -348,6 +350,35 @@ class HomematicipTemperatureSensor(HomematicipGenericEntity, SensorEntity):
return state_attr
class HomematicipAbsoluteHumiditySensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP absolute humidity sensor."""
_attr_native_unit_of_measurement = CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the thermometer device."""
super().__init__(hap, device, post="Absolute Humidity")
@property
def native_value(self) -> int | None:
"""Return the state."""
if self.functional_channel is None:
return None
value = self.functional_channel.vaporAmount
# Handle case where value might be None
if (
self.functional_channel.vaporAmount is None
or self.functional_channel.vaporAmount == ""
):
return None
# Convert from g/m³ to mg/m³
return int(float(value) * 1000)
class HomematicipIlluminanceSensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP Illuminance sensor."""
+17 -13
View File
@@ -21,6 +21,7 @@ from huawei_lte_api.exceptions import (
ResponseErrorNotSupportedException,
)
from requests.exceptions import Timeout
from url_normalize import url_normalize
import voluptuous as vol
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
@@ -40,7 +41,11 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
ServiceValidationError,
)
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
@@ -121,7 +126,7 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.url})
SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.string})
PLATFORMS = [
Platform.BINARY_SENSOR,
@@ -507,26 +512,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
because the latter is not available anywhere in the UI.
"""
routers = hass.data[DOMAIN].routers
if url := service.data.get(CONF_URL):
if url := url_normalize(service.data.get(CONF_URL), default_scheme="http"):
router = next(
(router for router in routers.values() if router.url == url), None
)
elif not routers:
_LOGGER.error("%s: no routers configured", service.service)
return
raise ServiceValidationError("No routers configured")
elif len(routers) == 1:
router = next(iter(routers.values()))
else:
_LOGGER.error(
"%s: more than one router configured, must specify one of URLs %s",
service.service,
sorted(router.url for router in routers.values()),
raise ServiceValidationError(
f"More than one router configured, must specify one of URLs {sorted(router.url for router in routers.values())}"
)
return
if not router:
_LOGGER.error("%s: router %s unavailable", service.service, url)
return
raise ServiceValidationError(f"Router {url} not available")
was_suspended = router.suspended
if service.service == SERVICE_RESUME_INTEGRATION:
# Login will be handled automatically on demand
router.suspended = False
@@ -536,7 +537,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
router.suspended = True
_LOGGER.debug("%s: %s", service.service, "done")
else:
_LOGGER.error("%s: unsupported service", service.service)
raise ServiceValidationError(f"Unsupported service {service.service}")
if was_suspended != router.suspended:
# Make interactive entities' availability update
dispatcher_send(hass, UPDATE_SIGNAL, router.config_entry.unique_id)
for service in ADMIN_SERVICES:
async_register_admin_service(
@@ -14,10 +14,11 @@ from homeassistant.components.button import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_platform
from .const import DOMAIN
from .entity import HuaweiLteBaseEntityWithDevice
from .entity import HuaweiLteBaseInteractiveEntity
_LOGGER = logging.getLogger(__name__)
@@ -36,7 +37,7 @@ async def async_setup_entry(
async_add_entities(buttons)
class BaseButton(HuaweiLteBaseEntityWithDevice, ButtonEntity):
class BaseButton(HuaweiLteBaseInteractiveEntity, ButtonEntity):
"""Huawei LTE button base class."""
@property
@@ -50,10 +51,7 @@ class BaseButton(HuaweiLteBaseEntityWithDevice, ButtonEntity):
def press(self) -> None:
"""Press button."""
if self.router.suspended:
_LOGGER.debug(
"%s: ignored, integration suspended", self.entity_description.key
)
return
raise ServiceValidationError("Integration is suspended")
result = self._press()
_LOGGER.debug("%s: %s", self.entity_description.key, result)
@@ -40,6 +40,7 @@ from homeassistant.core import callback
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_PRESENTATION_URL,
ATTR_UPNP_SERIAL,
ATTR_UPNP_UDN,
@@ -276,11 +277,12 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
if TYPE_CHECKING:
assert discovery_info.ssdp_location
url = url_normalize(
discovery_info.upnp.get(
ATTR_UPNP_PRESENTATION_URL,
f"http://{urlparse(discovery_info.ssdp_location).hostname}/",
)
discovery_info.upnp.get(ATTR_UPNP_PRESENTATION_URL)
or f"http://{urlparse(discovery_info.ssdp_location).hostname}/"
)
if TYPE_CHECKING:
# url_normalize only returns None if passed None, and we don't do that
assert url is not None
unique_id = discovery_info.upnp.get(
ATTR_UPNP_SERIAL, discovery_info.upnp[ATTR_UPNP_UDN]
@@ -308,8 +310,11 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
self.context.update(
{
"title_placeholders": {
CONF_NAME: discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME)
or "Huawei LTE"
CONF_NAME: (
discovery_info.upnp.get(ATTR_UPNP_MODEL_NAME)
or discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME)
or "Huawei LTE"
)
}
}
)
@@ -66,3 +66,12 @@ class HuaweiLteBaseEntityWithDevice(HuaweiLteBaseEntity):
connections=self.router.device_connections,
identifiers=self.router.device_identifiers,
)
class HuaweiLteBaseInteractiveEntity(HuaweiLteBaseEntityWithDevice):
"""Base interactive entity."""
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and not self.router.suspended
@@ -37,6 +37,137 @@
"default": "mdi:antenna"
}
},
"sensor": {
"uptime": {
"default": "mdi:timer-outline"
},
"wan_ip_address": {
"default": "mdi:ip"
},
"wan_ipv6_address": {
"default": "mdi:ip"
},
"cell_id": {
"default": "mdi:antenna"
},
"cqi0": {
"default": "mdi:speedometer"
},
"cqi1": {
"default": "mdi:speedometer"
},
"enodeb_id": {
"default": "mdi:antenna"
},
"lac": {
"default": "mdi:map-marker"
},
"nei_cellid": {
"default": "mdi:antenna"
},
"nrcqi0": {
"default": "mdi:speedometer"
},
"nrcqi1": {
"default": "mdi:speedometer"
},
"pci": {
"default": "mdi:antenna"
},
"rac": {
"default": "mdi:map-marker"
},
"tac": {
"default": "mdi:map-marker"
},
"sms_unread": {
"default": "mdi:email-arrow-left"
},
"current_day_transfer": {
"default": "mdi:arrow-up-down-bold"
},
"current_month_download": {
"default": "mdi:download"
},
"current_month_upload": {
"default": "mdi:upload"
},
"wifi_clients_connected": {
"default": "mdi:wifi"
},
"primary_dns_server": {
"default": "mdi:ip"
},
"primary_ipv6_dns_server": {
"default": "mdi:ip"
},
"secondary_dns_server": {
"default": "mdi:ip"
},
"secondary_ipv6_dns_server": {
"default": "mdi:ip"
},
"current_connection_duration": {
"default": "mdi:timer-outline"
},
"current_connection_download": {
"default": "mdi:download"
},
"current_download_rate": {
"default": "mdi:download"
},
"current_connection_upload": {
"default": "mdi:upload"
},
"current_upload_rate": {
"default": "mdi:upload"
},
"total_connected_duration": {
"default": "mdi:timer-outline"
},
"total_download": {
"default": "mdi:download"
},
"total_upload": {
"default": "mdi:upload"
},
"sms_deleted_device": {
"default": "mdi:email-minus"
},
"sms_drafts_device": {
"default": "mdi:email-arrow-right-outline"
},
"sms_inbox_device": {
"default": "mdi:email"
},
"sms_capacity_device": {
"default": "mdi:email"
},
"sms_outbox_device": {
"default": "mdi:email-arrow-right"
},
"sms_unread_device": {
"default": "mdi:email-arrow-left"
},
"sms_drafts_sim": {
"default": "mdi:email-arrow-right-outline"
},
"sms_inbox_sim": {
"default": "mdi:email"
},
"sms_capacity_sim": {
"default": "mdi:email"
},
"sms_outbox_sim": {
"default": "mdi:email-arrow-right"
},
"sms_unread_sim": {
"default": "mdi:email-arrow-left"
},
"sms_messages_sim": {
"default": "mdi:email-arrow-left"
}
},
"switch": {
"mobile_data": {
"default": "mdi:signal-off",
@@ -9,7 +9,7 @@
"requirements": [
"huawei-lte-api==1.11.0",
"stringcase==1.2.0",
"url-normalize==2.2.0"
"url-normalize==2.2.1"
],
"ssdp": [
{
@@ -17,13 +17,14 @@ from homeassistant.components.select import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED
from . import Router
from .const import DOMAIN, KEY_NET_NET_MODE
from .entity import HuaweiLteBaseEntityWithDevice
from .entity import HuaweiLteBaseInteractiveEntity
_LOGGER = logging.getLogger(__name__)
@@ -76,7 +77,7 @@ async def async_setup_entry(
async_add_entities(selects, True)
class HuaweiLteSelectEntity(HuaweiLteBaseEntityWithDevice, SelectEntity):
class HuaweiLteSelectEntity(HuaweiLteBaseInteractiveEntity, SelectEntity):
"""Huawei LTE select entity."""
entity_description: HuaweiSelectEntityDescription
@@ -102,6 +103,8 @@ class HuaweiLteSelectEntity(HuaweiLteBaseEntityWithDevice, SelectEntity):
def select_option(self, option: str) -> None:
"""Change the selected option."""
if self.router.suspended:
raise ServiceValidationError("Integration is suspended")
self.entity_description.setter_fn(option)
@property
+28 -43
View File
@@ -138,7 +138,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"uptime": HuaweiSensorEntityDescription(
key="uptime",
translation_key="uptime",
icon="mdi:timer-outline",
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -146,14 +145,12 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"WanIPAddress": HuaweiSensorEntityDescription(
key="WanIPAddress",
translation_key="wan_ip_address",
icon="mdi:ip",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=True,
),
"WanIPv6Address": HuaweiSensorEntityDescription(
key="WanIPv6Address",
translation_key="wan_ipv6_address",
icon="mdi:ip",
entity_category=EntityCategory.DIAGNOSTIC,
),
},
@@ -181,19 +178,16 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"cell_id": HuaweiSensorEntityDescription(
key="cell_id",
translation_key="cell_id",
icon="mdi:antenna",
entity_category=EntityCategory.DIAGNOSTIC,
),
"cqi0": HuaweiSensorEntityDescription(
key="cqi0",
translation_key="cqi0",
icon="mdi:speedometer",
entity_category=EntityCategory.DIAGNOSTIC,
),
"cqi1": HuaweiSensorEntityDescription(
key="cqi1",
translation_key="cqi1",
icon="mdi:speedometer",
entity_category=EntityCategory.DIAGNOSTIC,
),
"dl_mcs": HuaweiSensorEntityDescription(
@@ -230,13 +224,16 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"enodeb_id": HuaweiSensorEntityDescription(
key="enodeb_id",
translation_key="enodeb_id",
icon="mdi:antenna",
entity_category=EntityCategory.DIAGNOSTIC,
),
"ims": HuaweiSensorEntityDescription(
key="ims",
translation_key="ims",
entity_category=EntityCategory.DIAGNOSTIC,
),
"lac": HuaweiSensorEntityDescription(
key="lac",
translation_key="lac",
icon="mdi:map-marker",
entity_category=EntityCategory.DIAGNOSTIC,
),
"ltedlfreq": HuaweiSensorEntityDescription(
@@ -271,6 +268,11 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
),
entity_category=EntityCategory.DIAGNOSTIC,
),
"nei_cellid": HuaweiSensorEntityDescription(
key="nei_cellid",
translation_key="nei_cellid",
entity_category=EntityCategory.DIAGNOSTIC,
),
"nrbler": HuaweiSensorEntityDescription(
key="nrbler",
translation_key="nrbler",
@@ -279,13 +281,11 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"nrcqi0": HuaweiSensorEntityDescription(
key="nrcqi0",
translation_key="nrcqi0",
icon="mdi:speedometer",
entity_category=EntityCategory.DIAGNOSTIC,
),
"nrcqi1": HuaweiSensorEntityDescription(
key="nrcqi1",
translation_key="nrcqi1",
icon="mdi:speedometer",
entity_category=EntityCategory.DIAGNOSTIC,
),
"nrdlbandwidth": HuaweiSensorEntityDescription(
@@ -365,7 +365,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"pci": HuaweiSensorEntityDescription(
key="pci",
translation_key="pci",
icon="mdi:antenna",
entity_category=EntityCategory.DIAGNOSTIC,
),
"plmn": HuaweiSensorEntityDescription(
@@ -376,7 +375,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"rac": HuaweiSensorEntityDescription(
key="rac",
translation_key="rac",
icon="mdi:map-marker",
entity_category=EntityCategory.DIAGNOSTIC,
),
"rrc_status": HuaweiSensorEntityDescription(
@@ -423,6 +421,17 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=True,
),
"rxlev": HuaweiSensorEntityDescription(
key="rxlev",
translation_key="rxlev",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
"sc": HuaweiSensorEntityDescription(
key="sc",
translation_key="sc",
entity_category=EntityCategory.DIAGNOSTIC,
),
"sinr": HuaweiSensorEntityDescription(
key="sinr",
translation_key="sinr",
@@ -436,7 +445,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"tac": HuaweiSensorEntityDescription(
key="tac",
translation_key="tac",
icon="mdi:map-marker",
entity_category=EntityCategory.DIAGNOSTIC,
),
"tdd": HuaweiSensorEntityDescription(
@@ -480,6 +488,12 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
device_class=SensorDeviceClass.FREQUENCY,
entity_category=EntityCategory.DIAGNOSTIC,
),
"wdlfreq": HuaweiSensorEntityDescription(
key="wdlfreq",
translation_key="wdlfreq",
device_class=SensorDeviceClass.FREQUENCY,
entity_category=EntityCategory.DIAGNOSTIC,
),
}
),
#
@@ -494,7 +508,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"UnreadMessage": HuaweiSensorEntityDescription(
key="UnreadMessage",
translation_key="sms_unread",
icon="mdi:email-arrow-left",
),
},
),
@@ -508,7 +521,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_day_transfer",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:arrow-up-down-bold",
state_class=SensorStateClass.TOTAL,
last_reset_item="CurrentDayDuration",
last_reset_format_fn=format_last_reset_elapsed_seconds,
@@ -518,7 +530,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_month_download",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:download",
state_class=SensorStateClass.TOTAL,
last_reset_item="MonthDuration",
last_reset_format_fn=format_last_reset_elapsed_seconds,
@@ -528,7 +539,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_month_upload",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:upload",
state_class=SensorStateClass.TOTAL,
last_reset_item="MonthDuration",
last_reset_format_fn=format_last_reset_elapsed_seconds,
@@ -552,32 +562,27 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"CurrentWifiUser": HuaweiSensorEntityDescription(
key="CurrentWifiUser",
translation_key="wifi_clients_connected",
icon="mdi:wifi",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
"PrimaryDns": HuaweiSensorEntityDescription(
key="PrimaryDns",
translation_key="primary_dns_server",
icon="mdi:ip",
entity_category=EntityCategory.DIAGNOSTIC,
),
"PrimaryIPv6Dns": HuaweiSensorEntityDescription(
key="PrimaryIPv6Dns",
translation_key="primary_ipv6_dns_server",
icon="mdi:ip",
entity_category=EntityCategory.DIAGNOSTIC,
),
"SecondaryDns": HuaweiSensorEntityDescription(
key="SecondaryDns",
translation_key="secondary_dns_server",
icon="mdi:ip",
entity_category=EntityCategory.DIAGNOSTIC,
),
"SecondaryIPv6Dns": HuaweiSensorEntityDescription(
key="SecondaryIPv6Dns",
translation_key="secondary_ipv6_dns_server",
icon="mdi:ip",
entity_category=EntityCategory.DIAGNOSTIC,
),
},
@@ -590,14 +595,12 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_connection_duration",
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
icon="mdi:timer-outline",
),
"CurrentDownload": HuaweiSensorEntityDescription(
key="CurrentDownload",
translation_key="current_connection_download",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:download",
state_class=SensorStateClass.TOTAL_INCREASING,
),
"CurrentDownloadRate": HuaweiSensorEntityDescription(
@@ -605,7 +608,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_download_rate",
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
icon="mdi:download",
state_class=SensorStateClass.MEASUREMENT,
),
"CurrentUpload": HuaweiSensorEntityDescription(
@@ -613,7 +615,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_connection_upload",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:upload",
state_class=SensorStateClass.TOTAL_INCREASING,
),
"CurrentUploadRate": HuaweiSensorEntityDescription(
@@ -621,7 +622,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="current_upload_rate",
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
icon="mdi:upload",
state_class=SensorStateClass.MEASUREMENT,
),
"TotalConnectTime": HuaweiSensorEntityDescription(
@@ -629,7 +629,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="total_connected_duration",
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
icon="mdi:timer-outline",
state_class=SensorStateClass.TOTAL_INCREASING,
),
"TotalDownload": HuaweiSensorEntityDescription(
@@ -637,7 +636,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="total_download",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:download",
state_class=SensorStateClass.TOTAL_INCREASING,
),
"TotalUpload": HuaweiSensorEntityDescription(
@@ -645,7 +643,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
translation_key="total_upload",
native_unit_of_measurement=UnitOfInformation.BYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:upload",
state_class=SensorStateClass.TOTAL_INCREASING,
),
},
@@ -691,62 +688,50 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
"LocalDeleted": HuaweiSensorEntityDescription(
key="LocalDeleted",
translation_key="sms_deleted_device",
icon="mdi:email-minus",
),
"LocalDraft": HuaweiSensorEntityDescription(
key="LocalDraft",
translation_key="sms_drafts_device",
icon="mdi:email-arrow-right-outline",
),
"LocalInbox": HuaweiSensorEntityDescription(
key="LocalInbox",
translation_key="sms_inbox_device",
icon="mdi:email",
),
"LocalMax": HuaweiSensorEntityDescription(
key="LocalMax",
translation_key="sms_capacity_device",
icon="mdi:email",
),
"LocalOutbox": HuaweiSensorEntityDescription(
key="LocalOutbox",
translation_key="sms_outbox_device",
icon="mdi:email-arrow-right",
),
"LocalUnread": HuaweiSensorEntityDescription(
key="LocalUnread",
translation_key="sms_unread_device",
icon="mdi:email-arrow-left",
),
"SimDraft": HuaweiSensorEntityDescription(
key="SimDraft",
translation_key="sms_drafts_sim",
icon="mdi:email-arrow-right-outline",
),
"SimInbox": HuaweiSensorEntityDescription(
key="SimInbox",
translation_key="sms_inbox_sim",
icon="mdi:email",
),
"SimMax": HuaweiSensorEntityDescription(
key="SimMax",
translation_key="sms_capacity_sim",
icon="mdi:email",
),
"SimOutbox": HuaweiSensorEntityDescription(
key="SimOutbox",
translation_key="sms_outbox_sim",
icon="mdi:email-arrow-right",
),
"SimUnread": HuaweiSensorEntityDescription(
key="SimUnread",
translation_key="sms_unread_sim",
icon="mdi:email-arrow-left",
),
"SimUsed": HuaweiSensorEntityDescription(
key="SimUsed",
translation_key="sms_messages_sim",
icon="mdi:email-arrow-left",
),
},
),
@@ -842,7 +827,7 @@ class HuaweiLteSensor(HuaweiLteBaseEntityWithDevice, SensorEntity):
"""Return icon for sensor."""
if self.entity_description.icon_fn:
return self.entity_description.icon_fn(self.state)
return self.entity_description.icon
return super().icon
@property
def device_class(self) -> SensorDeviceClass | None:

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