Compare commits

..

294 Commits

Author SHA1 Message Date
abmantis 69a252092c Restore state in Energy cost sensors 2025-02-20 23:38:15 +00:00
abmantis ec257a54f3 Simplify Energy cost sensor update method 2025-02-20 22:44:26 +00:00
Josh Gustafson 97b853e2ea Bump arcam-fmj to 1.8.1 (#138959) 2025-02-21 00:16:25 +02:00
epenet 9d241a77b7 Adjust DSL line status options in SFR Box integration (#136425) 2025-02-20 23:14:17 +01:00
cro 1cae504cfe Fix bug in set_preset_mode_with_end_datetime (wrong typo of frost_guard) (#138402) 2025-02-20 22:52:03 +01:00
Petr V 509add8e5c Adjust Tuya Water Detector to support 1 as an alarm state (#135933) 2025-02-20 22:51:49 +01:00
J. Nick Koston 97bf557b32 Restore PaddleSwitchPico (Pico Paddle Remote) device trigger to Lutron Caseta (#137689) 2025-02-20 22:49:26 +01:00
Norbert Rittel aec7fc1835 Use capitalized "Modbus" as name, replace "slave" with "server" (#138945) 2025-02-20 22:42:29 +01:00
J. Nick Koston ab299d2bf7 Bump propcache to 0.3.0 (#138949) 2025-02-20 22:39:33 +01:00
Michael 490e012e54 Fix handling of min/max temperature presets in AVM Fritz!SmartHome (#138954) 2025-02-20 22:38:43 +01:00
Arie Catsman e8ff31b792 Add error handling to enphase_envoy number platform action (#136812) 2025-02-20 22:23:59 +01:00
Franck Nijhof 5f98d5a65a Revert Python 3.13.2 requirement for now (#138948) 2025-02-20 19:42:11 +01:00
Markus Adrario 5d1eb69281 Add light platform to Homee (#138776) 2025-02-20 19:31:31 +01:00
Norbert Rittel ec7ec993b0 Improve names and descriptions of media_player.xxx_set actions (#138773)
Co-authored-by: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com>
2025-02-20 18:26:14 +01:00
Erik Montnemery ff4f4111d0 Minor adjustment of recorder helper (#138941) 2025-02-20 16:28:39 +00:00
Jonas Fors Lellky 66f293c8f3 Add climate entity tests for flexit_bacnet and mark test coverage done (99%) (#138887) 2025-02-20 16:30:50 +01:00
Joost Lekkerkerker 8826714704 Bump ruff to 0.9.7 (#138939) 2025-02-20 16:23:21 +01:00
Erik Montnemery f828b4e0b9 Adjust config entry state check in vizio (#138905) 2025-02-20 16:18:57 +01:00
Steven Stallion 73442e8443 Add SensorPush Cloud integration (#134223) 2025-02-20 16:15:47 +01:00
Erik Montnemery 0d8c449ff4 Validate hassio backup settings (#138880)
* Validate hassio backup settings

* Add snapshots

* Don't reset addon and folder settings

* Adapt to changes in BackupConfig.update
2025-02-20 16:06:33 +01:00
Erik Montnemery fb57284561 Remove helper.recorder.async_wait_recorder (#138935) 2025-02-20 15:02:22 +00:00
Dmitry Kuzmenko b856de225d Catch zeep fault as well on GetSystemDateAndTime call. (#138916) 2025-02-20 15:18:19 +01:00
Josef Zweck 9f7c4648a2 Allow files to be directly deleted in onedrive (#138908)
* Allow files to be directly deleted in onedrive

* let options flow reload

* update description
2025-02-20 13:35:29 +01:00
Andrew Sayre 2d0967994e Fix ability to set HEOS options (#138235) 2025-02-20 13:14:57 +01:00
J. Nick Koston d2bd45099b Bump habluetooth to 3.22.1 and bleak-retry-connector to 3.9.0 (#138898) 2025-02-20 13:11:14 +01:00
Erik Montnemery 6d6dfce7d1 Adjust cleanup of removed integration spider (#138932) 2025-02-20 12:19:00 +01:00
Erik Montnemery d9a18c2994 Adjust cleanup of removed integration myq (#138931) 2025-02-20 12:18:40 +01:00
Erik Montnemery affec21a6a Adjust cleanup of removed integration mazda (#138930) 2025-02-20 12:17:58 +01:00
Erik Montnemery 94869f3210 Adjust cleanup of removed integration linear_garage_door (#138929) 2025-02-20 12:17:10 +01:00
Erik Montnemery e53617a788 Adjust cleanup of removed integration life360 (#138928) 2025-02-20 12:16:39 +01:00
Erik Montnemery e916b57714 Adjust cleanup of removed integration eight_sleep (#138926) 2025-02-20 12:16:23 +01:00
Martin Hjelmare 119b296c26 Make backup config update a callback (#138925) 2025-02-20 11:11:34 +00:00
Markus Adrario 20f273f06a Add button platform to Homee (#138923) 2025-02-20 12:07:12 +01:00
Jan-Philipp Benecke 6aae319b1a Allow use of insecure ciphers in rest_command (#138886) 2025-02-20 10:48:45 +01:00
J. Nick Koston b3e245687c Bump bluetooth-auto-recovery to 1.4.4 (#138895) 2025-02-20 10:48:01 +01:00
starkillerOG 1a56dcfdaf Fix Reolink callback id collision (#138918) 2025-02-20 10:46:24 +01:00
Norbert Rittel 66af5ca1e9 Improve action descriptions of ness_alarm integration (#138921)
- for the panic action change the description to "Triggers a panic _alarm_" as we don't want to trigger a panic ;-)
- for the aux action replace "Trigger …" with "Changes the state of an aux output" as it can turn this off as well
- clarify the description of the state field, dropping "true" for a UI-friendly wording
2025-02-20 10:04:05 +01:00
Erik Montnemery d24a14442f Adjust cleanup of removed integration aladdin_connect (#138917) 2025-02-20 09:38:15 +01:00
Erik Montnemery c7169a4ed7 Adjust config entry state checks in nest (#138912) 2025-02-20 09:14:45 +01:00
Erik Montnemery 08358514b4 Adjust config entry state checks in mcp_server (#138913) 2025-02-20 09:14:17 +01:00
Erik Montnemery 1392bab4d5 Adjust config entry state checks in renault (#138910) 2025-02-20 09:11:15 +01:00
Erik Montnemery e79a1a52c3 Adjust config entry state checks in esphome (#138914) 2025-02-20 09:08:46 +01:00
dependabot[bot] 872cca9935 Bump actions/cache from 4.2.0 to 4.2.1 (#138901) 2025-02-20 09:03:54 +01:00
Erik Montnemery 1bf7e5d749 Adjust config entry state check in yolink (#138904) 2025-02-20 09:01:15 +01:00
Erik Montnemery 2f7a8b4d9d Adjust config entry state checks in reolink (#138909) 2025-02-20 08:58:37 +01:00
Erik Montnemery 0949f7d0ba Adjust config entry state checks in qbus (#138911) 2025-02-20 08:57:55 +01:00
dependabot[bot] a2ceeb19dc Bump docker/build-push-action from 6.13.0 to 6.14.0 (#138902) 2025-02-20 08:47:37 +01:00
Erik Montnemery 1c3d6b5641 Minor readability improvement of Spotify browse media (#138907) 2025-02-20 08:45:36 +01:00
Saswat Padhi 14375e76a3 Opower: Fix unavailable "start date" and "end date" sensors (#138694)
avoid passing string into date device class
2025-02-19 23:42:09 -08:00
Manu e5c0183e0f Set parallel_updates in pyLoad integration (#138897)
Set parallel_updates
2025-02-20 08:15:14 +01:00
Manu 5c8fa717bf Move test before setup coordinator _async_setup in pyLoad integration (#138893)
Move setup test to `async_setup` in the coordinator
2025-02-20 08:14:08 +01:00
Thomas D 5d851b6a56 Add light platform to qbus (#136168)
* Add light platform

* Add on/off for light

* Renamed add_entities to async_add_entities

* Revert qbusmqttapi bump

* Align dependency version

* Use AddConfigEntryEntitiesCallback

* Use AddConfigEntryEntitiesCallback
2025-02-20 06:13:13 +01:00
Manu 5dfd358fc9 Bump pyloadapi to 1.4.1 (#138894) 2025-02-19 20:51:13 -06:00
Simone Chemelli 901011de7b Use xmod model info for Shelly XMOD devices (#137013) 2025-02-19 22:47:23 +01:00
Erik Montnemery ad7780291e Correct backup date when reading a backup created by supervisor (#138860) 2025-02-19 22:40:03 +01:00
Simone Chemelli eb6993f0a8 Switch cleanup for Shelly (part 1) (#138791) 2025-02-19 22:39:17 +01:00
Glenn Waters 406f894dc1 Environment Canada: Add a detailed forecast action (#138806)
* Add forecast service.

* Add detailed Environment Canada forecast data.

* Add icon and translations.

* Fix missing commas

* Add const.

* Add test.
2025-02-19 15:07:53 -06:00
Franck Nijhof 0a0a96fb3b Add initial basic GitHub Copilot instructions (#137754)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-02-19 21:52:20 +01:00
Erik Montnemery 354855ff5f Remove some dead code from the conversation integration (#138878) 2025-02-19 21:51:45 +01:00
Abílio Costa 8e6f2e6ff2 Add LINAK virtual integration supported by Idasen Desk (#138749) 2025-02-19 21:48:27 +01:00
Paulus Schoutsen 0b6f49fec2 Filter out certain intents from being matched in local fallback (#137763)
* Filter out certain intents from being matched in local fallback

* Only filter if LLM agent can control HA
2025-02-19 14:27:42 -06:00
Michael b2e2ef3119 Bump pyfritzhome to 0.6.15 (#138879) 2025-02-19 21:24:35 +01:00
Norbert Rittel e360348525 Make description of input_select.select_next action consistent (#138877) 2025-02-19 20:28:09 +01:00
Steven Hartland 4ed4c2cc5c Fix scaffolding generations (#138820) 2025-02-19 20:23:29 +01:00
Norbert Rittel bc5146db3c Make field description of snips.say_action UI-friendly (#138276) 2025-02-19 20:21:30 +01:00
Maghiel Dijksman f98e83514d Tuya camera rm duplication (#138794) 2025-02-19 20:03:32 +01:00
Norbert Rittel e847a8d6a5 Capitalize all occurrences of "Bond" brand name (#138876)
Also makes older action descriptions consistent.
2025-02-19 20:49:30 +02:00
Artur Pragacz 7117708937 Improve reading clarity of steps code in scripts helper (#134395)
* Reorganize steps code in scripts helper

* Address feedback

* Revert to getattr
2025-02-19 19:37:36 +01:00
Josef Zweck d2ce89882b Bump onedrive-personal-sdk to 0.0.11 (#138861) 2025-02-19 11:52:38 -06:00
Andrew Sayre 1d3fcc67b8 Select preferred discovered HEOS host (#138779)
* Select preffered host from discovery

* Remove invalid test comment
2025-02-19 11:51:47 -06:00
Jonas Fors Lellky 32b854515b Add exception translation for async_set_temperature in integration flexit_bacnet (#138870) 2025-02-19 18:23:58 +01:00
Artur Pragacz 6c3a9cb1a8 Improve reading clarity of steps code in scripts helper part 1 (#138628) 2025-02-19 11:18:28 -06:00
Erik Montnemery 81c909e8ce Revert "Add assistant filter to expose entities list command" (#138867)
Revert "Add assistant filter to expose entities list command (#138817)"

This reverts commit a6bb5dbe2a.
2025-02-19 18:13:36 +01:00
Pete Sage 85f44fa008 Update play_media parameter description in Media Player (#138855) 2025-02-19 16:43:13 +00:00
Markus Adrario fb3b23aef3 Homee switch platform (#137457) 2025-02-19 15:55:16 +00:00
Erik Montnemery b70c5710a9 Correct invalid automatic backup settings when loading from store (#138716)
* Correct invalid automatic backup settings when loading from store

* Improve docstring

* Improve tests
2025-02-19 16:24:30 +01:00
Sid 600bfed704 Refactor eheimdigital setup_device_entities (#138837) 2025-02-19 07:54:25 -06:00
Erik Montnemery af0a862aab Clean up translations for mocked integrations inbetween tests (#138732)
* Clean up translations for mocked integrations inbetween tests

* Adjust code, add test

* Fix docstring

* Improve cleanup, add test

* Fix test
2025-02-19 13:49:31 +01:00
starkillerOG 1733f5d3fb Fix playback for encrypted Reolink files (#138852) 2025-02-19 13:42:53 +01:00
Robert Resch 97c558b694 Add WIND_DIRECTION to SensorDeviceClass and NumberDeviceClass (#138714)
* Add WIND_DIRECTION to SensorDeviceClass

* Add WIND_DIRECTION to NumberDeviceClass

* Fix tests
2025-02-19 12:24:22 +01:00
proohit d655c51ef9 Adds Tado Child Lock support (#135837) 2025-02-19 11:24:04 +00:00
Joakim Sørensen 618bdba4d3 Add check_connection parameter to cloud login methods and handle AlreadyConnectedError (#138699) 2025-02-19 11:19:03 +01:00
LG-ThinQ-Integration 38efe94def Modify string water_heater's off state (#137627)
* Modify string water_heater's off state

* Modify washer's delay name

---------

Co-authored-by: yunseon.park <yunseon.park@lge.com>
2025-02-19 11:00:25 +01:00
Matthias Alphart 0c28b69269 Update xknx to 3.6.0 (#138838) 2025-02-19 10:38:52 +01:00
Jonas Fors Lellky 36c7546e26 Remove unused code in the climate entity of the flexit_bacnet integration (#138840)
Removes unused code in the climate entity

This was unintentionally left in the code when adding a coordinator
2025-02-19 10:26:16 +01:00
Michael Arthur 8d39f298c0 Electric Kiwi: Parallel updates (#138839)
* parallel updates

* Update homeassistant/components/electric_kiwi/select.py
2025-02-19 10:16:06 +01:00
Christopher Fenner 68085ed4f9 Add sensors for pellets boiler in ViCare integration (#138563)
* add buffer sensors

* remove duplicate sensor

* add labels

* Bump PyViCare to 2.43.0

* add fuel need sensor
2025-02-19 09:44:12 +01:00
J. Nick Koston d97194303a Improve performance of calculating state (#138832)
```
print(timeit.timeit("x.update(y)", setup=x={a:b}
2025-02-19 09:43:41 +01:00
Jonas Fors Lellky b6cb2bfe5b Add test for flexit_bacnet hvac mode (#138748)
Add test for hvac mode
2025-02-19 09:15:07 +01:00
Michael Arthur c5222708ed add icon to select (#138834) 2025-02-19 09:05:29 +01:00
Michael Arthur 6cf31e0807 Electric Kiwi: Add quality scale (#138680)
* add quality scale file

* Apply suggestions from code review

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

* add suggestions and add extra missing icon

* update a few based on documentation

* exempt installation parameters

* set a few more documentation items to done

* Update homeassistant/components/electric_kiwi/quality_scale.yaml

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

* update reason for no installation parameters

* set docs installation parameters to done

* revert back to exempt

* add bronze scale

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-19 08:43:45 +01:00
HA-Roberto ff83a14570 Add button for bond light temp toggle feature (#135379)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-02-19 00:48:29 -06:00
J. Nick Koston 46599a4ac4 Bump habluetooth to 3.22.0 (#138812) 2025-02-18 23:50:11 -06:00
Jan Bouwhuis 689421eddf Move blocking code to executor job in MQTT CI test helper (#138815) 2025-02-19 06:14:07 +01:00
J. Nick Koston ee5e25aca6 Bump aioesphomeapi to 29.1.1 (#138827) 2025-02-18 21:14:38 -06:00
Michael Hansen a6bb5dbe2a Add assistant filter to expose entities list command (#138817) 2025-02-18 20:39:44 -05:00
skobow f8ffbf0506 Set clean_start=True on connect to MQTT broker (#136026)
* Addresses #135443: Set  on connect.

* Make clean start implementation compatible with v2 API

* Add tests

* Do not pass default value for `clean_start` on_connect

* Revert "Do not pass default value for `clean_start` on_connect"

This reverts commit 75806736cf.

* Use partial top pass kwargs to mqtt client connect

---------

Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Co-authored-by: jbouwh <jan@jbsoft.nl>
2025-02-18 23:11:21 +01:00
Andrew Sayre 6613b46071 Add HEOS group volume down/up actions (#138801)
Add group volume down/up actions
2025-02-18 15:53:59 -06:00
Christopher Fenner 1579e90d58 Fix typos in strings.json files (#138601)
* fix codespell issues

* update nextcloud snapshots

* update weheat snapshots

* update waqi snapshots
2025-02-18 22:36:28 +01:00
Franck Nijhof b71d5737a5 Update Home Assistant base image to 2025.02.1 (#138746)
* Update Home Assistant base image to 2025.02.1

* Require Python 3.13.2 now
2025-02-18 22:34:08 +01:00
J. Diego Rodríguez Royo 8e887f550e Add connectivity binary sensor to Home Connect (#138795)
Add connectivity binary sensor
2025-02-18 22:08:40 +01:00
J. Diego Rodríguez Royo 1af8b69dd6 Set Home Connect beverages counters as diagnostics (#138798)
Set beverages counters as diagnostics
2025-02-18 22:03:35 +01:00
J. Diego Rodríguez Royo 6ef401251c Add Home Connect entities that weren't added before (#138796)
Added entities that weren't added before
2025-02-18 22:01:13 +01:00
J. Diego Rodríguez Royo 141bcae793 Add Home Connect to .strict-typing (#138799)
* Add Home Connect to .strict-typing

* Fix mypy errors
2025-02-18 21:50:19 +01:00
J. Nick Koston 8ae52cdc4c Fix shelly not being able to be setup from user flow when already discovered (#138807)
raise_on_progress=False was missing in the user flow which
made it impossible to configure a shelly by IP when there
was an active discovery because the flow would abort
2025-02-18 22:05:05 +02:00
Robert Resch 13fe2a9929 Reorder Dockerfile to improve caching (#138789) 2025-02-18 20:31:41 +01:00
Robert Resch df50863872 Bump uv to 0.6.1 (#138790) 2025-02-18 20:28:41 +01:00
SLaks 82ac3e3fdf Ecobee: Report Humidifier Action (#138756)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-18 20:11:37 +01:00
Maciej Bieniek c48797804d Add _shelly._tcp to Shelly zeroconf configuration (#138782)
Add _shelly._tcp to zeroconf
2025-02-18 19:57:10 +01:00
Matrix e6217efcd6 Add switch flex button support. (#137524) 2025-02-18 19:23:27 +01:00
Parker Brown 8dd1e9d101 Add threshold sensor to Aranet (#137291)
* Add threshold level sensor description to Aranet component

* Use Color enum for status options

* Add threshold level sensor tests for Aranet components

* Rename `threshold_level` key to `status`

* Update test to expect 7 sensors instead of 6

* Map sensor status to more human-friendly strings

* Rename `threshold_level` key to `concentration_status`

* Update docstring for  function

* Simplify `get_friendly_status()`

* Rename `concentration_status` to `concentration_level`

* Rename `concentration_status` to `concentration_level` in sensor tests

* Refactor concentration level handling and tests

* Normalize concentration level status values to lowercase

* Add error to translations

* Don't scale status string

* Apply suggestions from code review

Co-authored-by: Shay Levy <levyshay1@gmail.com>

* Rename `concentration_level` to `threshold_indication`

* Update threshold indication translations

* `threshold_indication` → `threshold`

* Capitalize sensor name

Co-Authored-By: Shay Levy <levyshay1@gmail.com>

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-02-18 20:16:50 +02:00
Renat Sibgatulin 096468baa4 airq: add more verbose debug logging (#138192) 2025-02-18 19:03:47 +01:00
Andrew Sayre 3659fa4c4e Add HEOS entity service to set group volume level (#136885) 2025-02-18 11:56:50 -06:00
peteS-UK d1f0e0a70f Add support for announce to Squeezebox media player (#129460)
* initial

* Add support for announce: true to media player

* Move play_announcement to _player

* update snapshot

* conftest update

* remove conftest update

* Update conftest.py

* Test Updates

* Updates post moving functions to library

* test fixes

* Review updates

* Snapshot update

* rebase updates

* Merge updates

* Review updates

* Review updates
2025-02-18 11:22:19 -06:00
Norbert Rittel a45fb57595 Fix grammar in evohome.reset_system action, consistently add "mode" (#138777)
* Fix grammar in evohome.reset_system action, consistently add "mode"

- fix the grammar with "Sets … and resets …"
- add "mode" to all mode names for consistency

* Revert, removing one excessive "mode"
2025-02-18 08:43:51 -06:00
Pete Sage e9fcef1b57 Fix TV input source option for Sonos Arc Ultra (#138778)
initial commit
2025-02-18 08:43:00 -06:00
Petar Petrov a003f89a5e Fix Z-WaveJS inclusion in the background (#138717)
* Fix Z-WaveJS inclusion in the background

* improve async handling

* just return the `requested_grant` to the driver

* handle controller busy state
2025-02-18 15:17:13 +01:00
Erik Montnemery 22c634e626 Don't allow setting backup retention to 0 days or copies (#138771)
* Don't allow setting backup retention to 0 days or copies

* Add tests
2025-02-18 15:16:44 +01:00
Niv Steingarten 46c604fcbe Bump pyrympro from 0.0.8 to 0.0.9 (#138753) 2025-02-18 14:23:25 +01:00
Norbert Rittel 94d3b3919d Make spelling of "BSB-Lan" consistent (#138766) 2025-02-18 13:58:29 +02:00
Norbert Rittel 350b935fa7 Fixing casing mistakes in user-facing strings of renault (#138729)
- use sentence-casing for strings
- use uppercase for "ID"
2025-02-18 12:06:10 +01:00
J. Nick Koston e660096801 Bump zeroconf to 0.145.1 (#138763) 2025-02-18 10:38:48 +00:00
starkillerOG f5e1fa6a21 Allow playback of h265 encoded Reolink video (#138667) 2025-02-18 11:17:13 +01:00
Brett Adams 800cdee409 Update Diagnostics in Teslemetry (#138759)
* Testing

* Diag
2025-02-18 09:44:29 +01:00
Jan Bouwhuis 33df208296 Fix temp files of mqtt CI tests not cleaned up properly (#138741)
* Fix temp files of mqtt CI tests not cleaned up properly

* Do not cleanup tempfiles, patch gettempdir only
2025-02-18 08:38:43 +01:00
J. Nick Koston 0dc1151a25 Bump aioesphomeapi to 29.1.0 (#138742) 2025-02-17 17:08:38 -06:00
Christopher Fenner 25865b4849 Bump PyViCare to 2.43.1 (#138737)
bump PyViCare to 2.43.1
2025-02-18 00:28:49 +02:00
Norbert Rittel 5658f9ca40 Fix wrong description of teslemetry.set_scheduled_charging action (#138723)
The action allows the user to set a time at which to start charging, but the action's description uses the wrong word "completed".
2025-02-17 23:28:45 +02:00
Jonas Fors Lellky f9047d0223 Mark action-exceptions as exempt for flexit_bacnet (#138739)
* Mark action-exceptions as exempt for flexit_bacnet

* Update homeassistant/components/flexit_bacnet/quality_scale.yaml

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-17 21:15:37 +01:00
Jonas Fors Lellky bbfb9fbdae Mark reauthentication-flow as exempt for flexit_bacnet (#138740) 2025-02-17 21:10:18 +01:00
Norbert Rittel 772e7147bd Fix user-facing strings of the NWS integration (#138727)
- fix sentence-casing of "API key" to match common string
- remove excessive trailing period from action name
- reword action description to match HA style
- make "Forecast type" description UI-friendly (a selector is available)
2025-02-17 21:51:30 +02:00
Xitee 9ac60f1c7f Fix small typo in qbittorrent strings.json (#138734) 2025-02-17 20:37:33 +01:00
Sid 3b6e3fe457 Fix race condition on eheimdigital coordinator setup (#138580) 2025-02-17 20:10:56 +01:00
Andrew Sayre da9fbf21df Update HEOS repair issues quality scale item (#138724) 2025-02-17 20:04:39 +01:00
Norbert Rittel d7e796e9f9 Fix typos in qBittorrent exceptions strings (#138728) 2025-02-17 17:37:46 +00:00
Erik Montnemery e0795e6d07 Improve config entry state transitions when unloading and removing entries (#138522)
* Improve config entry state transitions when unloading and removing entries

* Update integrations which check for a single loaded entry

* Update tests checking state after unload fails

* Update homeassistant/config_entries.py

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-02-17 18:16:57 +01:00
Joost Lekkerkerker ff16e587e8 Bump airgradient to 0.9.2 (#138725)
* Bump airgradient to 0.9.2

* Bump airgradient to 0.9.2
2025-02-17 18:45:26 +02:00
LG-ThinQ-Integration 04b826daa1 Add sensors for washer and system boiler in LG ThinQ (#137514)
Co-authored-by: yunseon.park <yunseon.park@lge.com>
2025-02-17 16:30:41 +00:00
Abílio Costa 25296e1b8f Move ZHA debug logs handling out of event loop (#138568) 2025-02-17 11:12:55 -05:00
Daniel O'Connor 67fcbc4c28 Add LV-RH131S-WM Air Purifier (#138626)
* Add LV-RH131S-WM Air Purifier

Fix 138486

* Update homeassistant/components/vesync/const.py
2025-02-17 16:29:28 +01:00
Andrew Sayre 34a33e0465 Create HEOS devices after integration setup (#138721)
* Create entities for new players

* Fix docstring typo
2025-02-17 09:28:55 -06:00
Jonas Fors Lellky 82f2e72327 Add translations for exceptions (#138669)
* Add translations for exceptions

* Review comment

* Add translation for exception in the coordinator

* Use same translation string for switch exceptions
2025-02-17 16:18:46 +01:00
Abílio Costa 9422c4de65 Fix snapshots timezone in Cloud tests (#138393)
* Fix snapshots timezone in Cloud tests

* Add explanation comment
2025-02-17 16:01:03 +01:00
Erik Montnemery 4cdc3de94a Correct backup filename on delete or download of cloud backup (#138704)
* Correct backup filename on delete or download of cloud backup

* Improve tests

* Address review comments
2025-02-17 08:38:28 -06:00
Marc Mueller 51aea58c7a Update mypy-dev to 1.16.0a3 (#138655) 2025-02-17 14:46:33 +01:00
epenet 7e388f69b0 Add common entity module to pylint plugin (#138706)
* Add common entity module to pylint plugin

* Fix pylint errors
2025-02-17 14:45:32 +01:00
epenet d8d054e7dd Improve type hints in base entities (#138708) 2025-02-17 14:45:00 +01:00
Norbert Rittel 4a385ed26c Use correct camel-case for OpenThread, reword error message (#138651)
* Use correct camel-case for OpenThread, reword error message

* Treat "Border Agent ID" as a name by capitalizing it
2025-02-17 13:38:42 +01:00
Michael df6cb0b824 Add repair-issue that backup location setup is missing in Synology DSM (#138233)
* add missing backup location setup repair-issue

* add tests

* tweak translation strings

* add test for other fixable issues

* remove senseless abort reason no_file_station
2025-02-17 13:03:31 +01:00
ashionky a7f63e3847 Optimize Refoss state_class of Sensor (#138266)
TOTAL_INCREASING
2025-02-17 13:02:52 +01:00
Robert Resch b4fac38d8a Bump uv to 0.6.0 (#138707) 2025-02-17 12:42:02 +01:00
Matrix 168e45b0f9 Bump yolink api 0.4.8 (#138703) 2025-02-17 12:24:56 +01:00
Norbert Rittel 1fe644d056 Fix casing in Sensibo action descriptions (#138701)
- treat "Pure Boost" as a feature name
- fix sentence-casing
- capitalize first word
2025-02-17 11:05:39 +01:00
Alberto Geniola cd13eff8ae Elmax - fix issue 136877 (#138419)
* Fix IPv6 zero-conf discovery not handling hostname correctly.

* Aligned tests.

* Remove redundant !s notation.

* Add IPv6 discovery tests

* Parametrize input_uri to avoid duplicated code

* Update tests/components/elmax/conftest.py

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-17 10:01:27 +01:00
Norbert Rittel e77193fa2e Improve 17track action descriptions by using those from the online docs (#138698)
* Improve 17Track action descriptions using those from the online docs

Also change them to third-person singular to match the descriptive style that Home Assistant prefers.

* Add missing period on 2nd description
2025-02-17 09:08:40 +01:00
Dan Raper 66d16336ea Add preconditioning number entity to Ohme (#138346)
* Add preconditioning number entity

* Updated test snapshots for ohme

* Update test snapshots
2025-02-17 09:07:18 +01:00
Norbert Rittel ed3ca76696 Update foscam action descriptions to match HA style (#138664)
Update foscam action description to match HA style
2025-02-17 09:03:28 +01:00
Jonas Fors Lellky f2126a357a Comply with parallel updates quality rule (#138672) 2025-02-17 08:58:21 +01:00
Andrew Sayre 89956adf2e Allow removal of stale HEOS devices (#138677)
Allow device removal
2025-02-17 08:47:11 +01:00
Paulus Schoutsen c357b3ae65 Move some setups during onboarding to background (#138558)
* Move some setups during onboarding to background

* Update homeassistant/components/onboarding/views.py

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-02-16 23:06:28 -05:00
cdnninja 6b90e7b2c2 Bump pyvesync for vesync (#138681)
* bump pyvesync

* fix tests

* Test fix
2025-02-16 21:33:48 -06:00
fwestenberg 56b51227bb Bump stookwijzer==1.5.4 (#138678) 2025-02-16 19:19:03 -06:00
Markus Lanthaler 93f1597e6d Add latest Nighthawk WiFi 7 routers to V2 models (#138675) 2025-02-16 22:03:57 +01:00
peteS-UK bdeb24cb61 Add OptionsFlow to Squeezebox to allow setting Browse Limit and Volume Step (#129578)
* Initial

* prettier strings

* Updates

* remove error strings

* prettier again

* Update strings.json

vscode prettier fails check

* update test to remove invalid value

* Remove config_entry __init__

* remove param

* Review updates

* ruff fixes

* Review changes

* Shorten options flow ui string

* Review changes

* Remove errant mock attib

---------

Co-authored-by: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com>
2025-02-16 15:02:29 -06:00
Shai Ungar 09df6c8706 Rename "returned" state to "alert" (#138676)
Rename "returned" state to "alert" in icons, services, and strings files
2025-02-16 22:33:32 +02:00
Martin Hjelmare 0b7ec96448 Improve remember the milk storage (#138618) 2025-02-16 21:17:26 +01:00
Michael ccd0e27e84 Allow renaming of backup files in Synology DSM (#138652)
* get backup base file name from meta file

* use BackupNotFound
2025-02-16 20:00:17 +01:00
Keilin Bickar e0b50ee1e2 Bump sense_energy to 0.13.5 (#138659) 2025-02-16 10:04:45 -08:00
Jonas Fors Lellky 7063636db6 Add quality scale bronze for flexit_bacnet (#138309)
* Add quality scale bronze for flexit_bacnet

* Add new line at end of file

* Remove flexit_bacnet from list of integrations without quality scale

* Add missing translation strings

* Fix review comments

* Remove flexit_bacnet from list of integrations without quality scale

* Review comment

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

* Review comment

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

* Add the complete list of quality scale rules

* Fix lint error

* Use correct formatting for todos

* Fix lint error

* Set all rules above bronze to todo

* Update status for rules that are done

* Update homeassistant/components/flexit_bacnet/quality_scale.yaml

* Update homeassistant/components/flexit_bacnet/quality_scale.yaml

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-16 17:06:09 +01:00
Michael f67fb9985e Allow wifi switches for mesh repeaters in AVM Fritz!Box Tools (#135456)
* create wifi switches for mesh slaves, but disable them by default

* check if mesh isbased on wifi uplink

* fix
2025-02-16 15:12:16 +01:00
Jonas Fors Lellky 2d5e920de0 Flexit bacnet/quality preparations (#138514)
Add data_description for config flow
2025-02-16 14:55:05 +01:00
Norbert Rittel 9e15a33c42 Fix sentence-casing and capitalization of "Zigbee" in smlight (#138647) 2025-02-16 15:46:08 +02:00
Norbert Rittel e767863ea4 Replace opentherm_gw action key name with friendly name for UI (#138634) 2025-02-16 13:17:47 +01:00
Luca Bensi 7f3270e982 Bump pysmarty2 to 0.10.2 (#138625) 2025-02-16 14:09:15 +02:00
Josef Zweck 95b1cf465b Use gibibytes for onedrive (#138637)
* Use gibibytes for onedrive

* also to strings
2025-02-16 14:08:01 +02:00
Norbert Rittel 3ce8e1683a Fix sentence-casing in ZHA integration, capitalize names (#138636)
* Fix sentence-casing in ZHA integration, capitalize names

* Reorder title and description keys

* Remove wrong trailing commas

* Restore accidental deletion

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

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2025-02-16 12:17:21 +01:00
Teynar 21032ea7cd Add missing unit for Withings snore sensor (#138517) 2025-02-16 10:21:34 +01:00
J. Diego Rodríguez Royo c75707ec79 Use correct inputs for relative time and duration options (#138619) 2025-02-16 00:29:38 +01:00
Norbert Rittel 9573f7828b Update action description in ecovacs integration to match HA style (#138548) 2025-02-15 21:52:41 +01:00
IceBotYT d435f7be09 Update integrations screenshot in README (#138555) 2025-02-15 21:50:52 +01:00
Norbert Rittel a3eb73cfcc Replace alarm action descriptions with wording from online docs (#138608) 2025-02-15 21:46:00 +01:00
Josef Zweck fdaa640c8e Add issues for data cap to onedrive (#138411)
* Add issues for data cap to onedrive

* brackets

* Fix double space

Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>

---------

Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
2025-02-15 21:44:59 +01:00
Bouwe Westerdijk 6059446ae3 Bump plugwise to v1.7.2 (#138613) 2025-02-15 21:39:06 +01:00
CodingSquirrel 827865a1b9 Bump pyeconet to 0.1.28 (#138610) 2025-02-15 21:36:54 +01:00
Nathan Spencer 0a78f2725d Add switch to toggle filter cycle 2 on balboa spas (#138605) 2025-02-15 20:20:33 +01:00
Erik Montnemery 78337a6846 Disable zwave_js testing resetting the controller (#138595)
* Improve zwave_js test of resetting the controller

* Disable the test
2025-02-15 20:16:07 +01:00
J. Diego Rodríguez Royo 78c4d815ce Fix home connect coffe-milk ratio option (#138593)
* Fix home connect milk ratio option

* Use enumeration instead of number selector for coffee-milk ratio
2025-02-15 20:10:27 +01:00
Duco Sebel 482df7408a Provide part of uuid when requesting token for HomeWizard v2 API (#138586) 2025-02-15 16:29:09 +01:00
Markus Jacobsen 05696b5528 Add Event entity states to diagnostics for Bang & Olufsen (#135859)
Add diagnostics for event buttons
2025-02-15 09:28:10 -06:00
Brett Adams c89d8edb3c Remove dynamic rate limits from Tesla Fleet (#138576)
* remove

* TEsts
2025-02-15 16:27:29 +01:00
Christopher Fenner 08f6e9cd12 Bump PyViCare to 2.43.0 (#138564)
* Bump PyViCare to 2.42.1

* Bump PyViCare to 2.43.0
2025-02-15 16:24:43 +01:00
Nathan Spencer cbb0dee911 Bump pybalboa to 1.1.3 (#138557) 2025-02-15 16:22:04 +01:00
Brett Adams 798d2326ed Bump tesla-fleet-api to v0.9.10 (#138575)
bump
2025-02-15 16:20:51 +01:00
Khole 91ba9b2239 Bump pyhive-integration to 1.0.2 (#138569) 2025-02-15 14:13:16 +01:00
J. Diego Rodríguez Royo 7a23348b1d Fix and improve Home Connect strings (#138583)
* Fix `hot_water_temperature` strings for tea options

* Improve `deprecated_program_switch` issue description

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Improve option descriptions strings

Co-authored-by: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com>

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com>
2025-02-15 11:29:40 +01:00
Michael Hansen 30a6a6ad4b Use language util to match intent language (#138560) 2025-02-14 20:51:53 -05:00
J. Nick Koston 4a4c2ff552 Bump zeroconf to 0.144.3 (#138553) 2025-02-14 16:17:35 -08:00
Michael Hansen e16343ed72 Prevent voice wizard from crashing for wyoming/voip (#138547)
* Prevent voice wizard from crashing for wyoming/voip

* Use stub configuration in websocket API
2025-02-14 15:41:45 -06:00
J. Nick Koston 28dd44504e Bump aioesphomeapi to 29.0.2 (#138549)
changelog: https://github.com/esphome/aioesphomeapi/compare/v29.0.0...v29.0.2
2025-02-14 22:42:36 +02:00
Nathan Spencer b916fbe1fc Add time entity to balboa (#138248) 2025-02-14 20:50:51 +01:00
Xitee 58797a14e7 Add 6 new sensors to qBittorrent integration (#138446)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-14 20:48:19 +01:00
Joris Pelgröm c090fbfbad Add binary sensor platform to LetPot integration (#138554) 2025-02-14 20:21:30 +01:00
J. Diego Rodríguez Royo 2bfe96dded Add Home Connect action with recognized programs and options (#130662)
* Added recognized options to Home Connect actions

* Fix ruff

* Fix strings.json

* Fix dishwasher typo

* Improved test_bsh_key_transformations

* Add missing return types

* Added descriptions

* Remove custom options

* Fixes

* Merge the 4 services (select, start, set options for active or selected program)

And deprecate the original ones

* Delete stale snapshots

* Clean up logic after service validation

* Make deprecated actions issues fixable

And delete issue on entry unload

* Fixes and improvements

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

* Improvements

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

* Fix name and descriptions

* Add `affects_to` to strings and service.yaml

* Add missing periods at strings

* Fix

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Add tests to check if the flow removes the deprecated action issue

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-02-14 20:21:01 +01:00
IceBotYT d99044572a Improved auth failure handling in Nice G.O. (#136607) 2025-02-14 20:03:21 +01:00
Manu 11aa08cf74 Set quality scale to platinum 🏆️ for Habitica integration (#136076) 2025-02-14 19:56:32 +01:00
Norbert Rittel 5dc1689e7c Update action descriptions of weather integration (#138540) 2025-02-14 17:06:17 +00:00
J. Nick Koston 28ea55aac0 Bump aiohttp-asyncmdnsresolver to 0.1.1 (#138534) 2025-02-14 15:27:16 +00:00
Manu 7bd2c1d710 Refactor and add tests to image platform of Habitica (#135897) 2025-02-14 15:20:19 +00:00
Josh Gustafson 23d43b23ee Bump arcam-fmj to 1.8.0 (#138422)
* arcam_fmj: bump arcam-fmj to 1.8.0

* Revert castings

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-02-14 16:03:47 +01:00
Bram Kragten 7dd678ccdf Update frontend to 20250214.0 (#138521) 2025-02-14 14:12:49 +01:00
Joris Pelgröm 371490a470 Add sensor platform to LetPot integration (#138491)
* Add sensor platform to LetPot integration

* Handle support in description supported_fn, use common string

* Update homeassistant/components/letpot/switch.py

* Update homeassistant/components/letpot/sensor.py

* Update homeassistant/components/letpot/sensor.py

* Update homeassistant/components/letpot/strings.json

* Fix translation key in snapshot

* snapshot no quotes

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-02-14 13:57:27 +01:00
Norbert Rittel 48f58c7d49 Fix action descriptions in Xiaomi Miio integration (#138476)
* Fix action description in Xiaomi Miio integration

Correct several missing descriptions, wrong references to completely different actions, resulting duplicates and copy & paste errors.

Make the grammar more consistent across all strings.

Make one occurrence of "xiaomi miio" consistent by capitalizing.

* Apply suggestions from @CFenner review

Co-authored-by: Christopher Fenner <9592452+CFenner@users.noreply.github.com>

* Change "on a light" to "of a light", remove wrong comma

* Change "turn off" to "turning off" according to OED

---------

Co-authored-by: Christopher Fenner <9592452+CFenner@users.noreply.github.com>
2025-02-14 13:52:22 +01:00
RJPoelstra fae68c8ad5 Add icon translation to MotionMount integration (#138520)
* Add icon translation for error sensor

* Mark icon-translations as done
2025-02-14 13:47:36 +01:00
Michael fa4ebeb680 Bump py-synologydsm-api to 2.6.3 (#138516)
bump py-synologydsm-api to 2.6.3
2025-02-14 13:11:32 +01:00
Erik Montnemery efd7ddeb89 Improve tests of removing and unloading config entries (#138432)
* Improve tests of removing and unloading config entries

* Fix unnecessary coroutine
2025-02-14 13:06:07 +01:00
RJPoelstra f407dbd35c Disable less used entities by default in MotionMount integration (#138509)
* Mark sensors as disabled by default as most users won't need them

* Mark entity-disabled-by-default as done

* Enable disabled entities during tests
2025-02-14 12:46:41 +01:00
Manu 4d3a4015ed Update quality scale to platinum 🏆️ for Bring! integration (#138202)
* Update documentation status in bring quality_scale.yaml

* Update quality scale

* options flow exempt
2025-02-14 11:39:04 +01:00
RJPoelstra 9f9aeb4cce Add entity category to non primary entities for motionmount integration (#138436)
Add entity category to non primary entities
2025-02-14 11:10:08 +01:00
Norbert Rittel b9148d6368 Improve descriptions of snooz.transition_xx actions (#138403)
The current action descriptions of the snooz integration are easy to misunderstand and result in wrong translations.

This commit replaces them with the wording from the online docs, slightly adapted for the UI that already displays the units and ranges.
2025-02-14 10:37:56 +01:00
Shai Ungar d82dd9e7e6 Bump pyseventeentrack to 1.0.2 (#138506)
Bump pyseventeentrack version
2025-02-14 10:25:04 +01:00
Erik Montnemery 51beb21fe4 Bump hass-nabucasa from 0.91.0 to 0.92.0 (#138510) 2025-02-14 10:19:00 +01:00
Phill (pssc) 83f8a4454d squeezebox bump pysqueezebox to 0.12.0 (#138205)
* bump pysqueezebox to 0.12.0

* python3 -m script.gen_requirements_all
2025-02-14 10:14:44 +01:00
Christopher Fenner 6a4f5188b1 Bump PyViCare to 2.42.1 (#138494) 2025-02-14 02:30:53 +02:00
TheJulianJES 099adebcb6 Bump ZHA to 0.0.49 to fix Tuya TRV issues (#138492)
Bump ZHA to 0.0.49
2025-02-14 02:04:39 +02:00
Shay Levy 00e98954e4 Bump aiowebostv to 0.6.2 (#138488) 2025-02-14 01:52:33 +02:00
Michael 621bcccef7 Remove scan interval option from Synology DSM (#138490)
remove scan interval option
2025-02-13 22:51:14 +01:00
Jan Bouwhuis d6b7762dd6 Upgrade paho-mqtt API to v2 (#137613)
* Upgrade paho-mqtt API to v2

* Refactor on_connect callback

* Add tests

* Fix Tasmota tests
2025-02-13 22:13:19 +01:00
J. Nick Koston bbbad90ca2 Fix race configuring zeroconf (#138425) 2025-02-13 14:17:06 -06:00
tronikos ab2e075b41 Bump opower to 0.9.0 (#138433)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-02-13 21:35:58 +02:00
Norbert Rittel 2ea648f8ae Replace config.yaml with correct configuration.yaml in folder_watcher (#138434) 2025-02-13 20:55:04 +02:00
Pete Sage bf27eeb861 Add sonos_websocket to Sonos loggers (#138470) 2025-02-13 18:46:50 +00:00
Maghiel Dijksman d4c5479e50 Fix Tuya unsupported cameras (#136960) 2025-02-13 16:14:56 +00:00
RJPoelstra a03c588002 Mark entity-device-class as done for motionmount integration (#138459)
All entities where a device class is available have a device class
2025-02-13 16:54:29 +01:00
David Rapan 82074a8940 Starlink migration to StarlinkConfigEntry (#137896)
* refactor: Utilize custom StarlinkConfigEntry

* fix: ruff-format

* fix: Init tests

* fix: StarlinkConfigEntry in coordinator after recent PRs

* fix: CONF_IP_ADDRESS constant

* fix: After merge clean up

* fix: Naming conventions

* feat: Add runtime_data into init test

* refactor: Remove runtime_data assert in unload entry test
2025-02-13 16:36:07 +01:00
Artur Pragacz 7021175e0d Simplify stage 1 in bootstrap (#137668)
* Simplify stage 1 in bootstrap

* Add timeouts to STAGE 0

* Fix test

* Clarify pre import language

* Remove timeout for frontend and recorder

* Address review

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-02-13 13:07:24 +01:00
Norbert Rittel e9138a427d Replace wrong description reference of isy994.send_node_command (#138385) 2025-02-13 06:00:38 -06:00
Indu Prakash 6a26d59142 Add night light brightness level setting to VeSync (#137544) 2025-02-13 11:45:09 +00:00
Joakim Sørensen a8f4ab73ae Bump hass-nabucasa from 0.90.0 to 0.91.0 (#138441) 2025-02-13 11:40:55 +00:00
Norbert Rittel 0a9d134f49 Make descriptions of data fields in notify actions UI-friendly (#138431)
Also fixes a duplicated period at the end of the second string.
2025-02-13 10:28:55 +01:00
Arie Catsman 07c304125a Add error handling to enphase_envoy select platform action (#136698)
* Add error handling to enphase_envoy select platform action

* Add translation key parameter to exception_handler decorator
2025-02-13 09:37:52 +01:00
IceBotYT 6bc4f04a07 Handle no_readings in La Crosse View (#138354)
* Handle no_readings in La Crosse View

* Fixes
2025-02-13 09:24:28 +01:00
Joris Pelgröm 737baaef2b Improve test coverage for letpot (#138420) 2025-02-13 09:22:05 +01:00
IceBotYT 1ac16f6dbf Set suggested display precision in La Crosse View (#138355)
* Set suggested display precision in La Crosse View

* Switch to entity descriptions
2025-02-13 08:37:46 +01:00
puddly 81cac25bd0 OTBR firmware API for Home Assistant Hardware (#138330)
* Implement `async_register_firmware_info_provider` for OTBR

* Keep track of the current device for OTBR

Keep track of the current device, part 2

* Fix unit tests

* Revert keeping track of the current device

* Fix existing unit tests

* Increase test coverage

* Remove unused code from tests

* Reload OTBR when the addon reloads

* Only reload if the current entry is running

* Runtime test

* Add a unit test for the reloading

* Clarify the purpose of `ConfigEntryState.SETUP_IN_PROGRESS`

* Simplify typing
2025-02-12 15:48:09 -05:00
Steven B. c0068e0891 Bump python-kasa to 0.10.2 (#138381) 2025-02-12 21:42:07 +01:00
Josef Zweck 641b487196 Improve test coverage for onedrive (#138410)
* Improve test coverage for onedrive

* set done in quality scale
2025-02-12 20:44:39 +01:00
Erik Montnemery 03b3097c34 Update cloud backup agent to use calculate_b64md5 from lib (#138391)
* Update cloud backup agent to use calculate_b64md5 from lib

* Catch error, add test

* Address review comments

* Update tests/components/cloud/test_backup.py

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

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-02-12 19:11:20 +01:00
jdanders 400dbc8d1b Add missing thermostat state EMERGENCY_HEAT to econet (#137623)
* Add missing thermostat state EMERGENCY_HEAT to econet

* econet: fix overloaded reverse dictionary

* Update homeassistant/components/econet/climate.py

---------

Co-authored-by: Robert Resch <robert@resch.dev>
2025-02-12 18:56:42 +01:00
Steven B. d9108cc003 Fix tplink iot strip sensor refresh (#138375) 2025-02-12 11:46:11 -06:00
Josef Zweck ff5ddce7b0 Add sensor platform to OneDrive for drive usage (#138232) 2025-02-12 18:37:30 +01:00
Andre W. 620141cfb1 Fix version extraction for APsystems (#138023)
Co-authored-by: Marlon <mawol@protonmail.com>
2025-02-12 17:24:39 +00:00
J. Nick Koston 8bf870f296 Bump zeroconf to 0.144.1 (#138353)
* Bump zeroconf to 0.143.1

changelog: https://github.com/python-zeroconf/python-zeroconf/compare/0.143.0...0.143.1

fixes #138324
fixes https://github.com/home-assistant/core/issues/137731
fixes https://github.com/home-assistant/core/issues/138298

* one more
2025-02-12 15:57:26 +01:00
Erik Montnemery 281c2bfb7b Bump hass-nabucasa from 0.89.0 to 0.90.0 (#138387)
* Bump hass-nabucasa from 0.89.0 to 0.90.0

* Use new shiny enum
2025-02-12 15:29:42 +01:00
Dan Raper 910711ecba Bump ohmepy to 1.3.0 (#138380)
* Bump ohmepy to 1.3.0

* CI fix for enum change
2025-02-12 14:54:21 +01:00
epenet 4807682fc5 Remove unused arguments in forked_daapd initialisation (#138289) 2025-02-12 14:18:10 +01:00
fwestenberg 327bb34be1 Bump stookwijzer to 1.5.2 (#138384)
Bump stookwijzer==1.5.2
2025-02-12 15:15:32 +02:00
Robert Resch 6084bee2d5 Bump deebot-client to 12.1.0 (#138382) 2025-02-12 15:14:52 +02:00
Abílio Costa bc11444fb2 Add missing loggers to Cloud (#138374) 2025-02-12 13:14:31 +01:00
epenet e12b100a37 Use runtime_data in fireservicerota (#138361) 2025-02-12 12:49:26 +01:00
Robert Resch ef9d5dd568 Bump cryptography to 44.0.1 (#138371) 2025-02-12 12:46:53 +01:00
epenet 2bb582f8e6 Use runtime_data in geo_json_events (#138366)
* Use runtime_data in geo_json_events

* Update __init__.py
2025-02-12 12:42:22 +01:00
jdanders f1471f143c Fix broken issue creation in econet (#137773)
* econet: Fix broken issue creation

* econet: fix broken issue creation with create_issue
2025-02-12 12:41:52 +01:00
Norbert Rittel 64fa9b78f8 Fix typos in user-facing strings of Bayesian integration (#138364) 2025-02-12 12:39:43 +01:00
Markus Adrario 88b444fa5b Add Homee sensor tests (#137200) 2025-02-12 11:35:36 +00:00
Norbert Rittel 487a4ac5c4 Improve field names and descriptions of easyEnergy actions (#138319)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-02-12 11:28:15 +00:00
TimL a3cde3d8ab Fix authentication error when adding new devices to SMLIGHT (#138373)
* Fix authentication issue

Fixes #138216

* Fix incorrect mocks in unsupported device tests

* set _device_name in auth flow also

* Update get_info Mock to handle authentication

* Update tests
2025-02-12 12:22:58 +01:00
Erik Montnemery 6ef1178a35 Use setup_backup_integration test helper in backup tests (#138362) 2025-02-12 09:49:01 +01:00
epenet 2033dbdd90 Use entry.async_on_unload in fireservicerota (#138360) 2025-02-12 09:22:35 +01:00
Erik Montnemery a6c51440e5 Use test helper for creating a mocked backup agent in backup tests (#138312)
* Use test helper for creating a mocked backup agent in backup tests

* Adjust according to discussion
2025-02-12 08:55:16 +01:00
Pete Sage 1393f417ed Expose media_player async_browse_media as service (#116452)
* initial commit

* make fields optional

* x

* ruff issues

* ruff issues

* ruff issues

* ruff issues

* update example

* update description

* use constants

* Update homeassistant/components/media_player/strings.json

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

* update service call metadata

* update description

* patch the demo

* Update homeassistant/components/media_player/strings.json

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

* revert unrelated change

* update test metadata

* update test metadata

* change patch target to be more specific

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-02-12 01:06:13 +01:00
Allen Porter da1e3c29ed Update anthropic to use the streaming API (#138256) 2025-02-12 01:05:23 +01:00
Robert Resch 117a71cb67 Bump sentry-sdk to 1.45.1 (#138349) 2025-02-12 01:04:05 +01:00
Abílio Costa 48b8ec01e3 Add logs to Cloud component support package (#138230)
* Add logs to Cloud component support package

* Add section for logs

* Replace list with deque

* Copy the deque to avoid mutation during iteration
2025-02-11 23:05:19 +01:00
rrooggiieerr 0ffbe076be Fix timer.cancel action fires timer.cancelled event even on canceled timers (#134507)
* Fixes https://github.com/home-assistant/core/issues/116105

* Fixes unit test in accordance to documentation

Timer needs to be active before it can be canceled

* Allow canceling of paused timers

* Add test for canceling/finishing already canceled/finished timers

* Add test for finishing a paused timer, this should not be possible

* Revert finish related tests

* Merge branch 'timer.cancelled_fix' of
git@github.com:rrooggiieerr/homeassistant-core.git into
timer.cancelled_fix

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-02-11 22:08:18 +01:00
Allen Porter 8d5f927b42 Fix next authentication token error handling (#138299) 2025-02-11 21:47:36 +01:00
Erik Montnemery 6abf7b525a Improve test coverage of config subentries and fix related issues (#138321)
Improve test coverage of config subentries
2025-02-11 21:46:56 +01:00
Arie Catsman 6115def083 Bump pyenphase to 1.25.1 (#138327)
* Bump pyenphase to 1.25.1

* Add new opt_schedules to nephase_envoy test fixtures
2025-02-11 21:35:03 +01:00
rrooggiieerr 17089e822e Allow timer.finish on paused timers (#134552)
* Add test for finishing already finished timer

* Add test for finishing a paused timer

* Allow canceled timer to be finished
2025-02-11 21:26:13 +01:00
Norbert Rittel 7a556ac3ec Remove "true" / "false" and key name from yeelight.set_music_mode action (#138334) 2025-02-11 20:48:50 +01:00
Norbert Rittel 857e35b7fd Remove remaining occurrences of "true" / "false" in telegram_bot (#138329)
Make the field description UI-friendly.
2025-02-11 20:22:22 +01:00
epenet 2cea2258a2 Improve type hints in forked_daapd coordinator (#138287) 2025-02-11 18:27:27 +00:00
Norbert Rittel 444b9a9579 Improve user-facing strings of denonavr for better translations (#138322)
- fix sentence-casing for "network receiver" as this should be translated
- change "Ethernet" to upper-case
- replace "True/false for enable/disable" with UI-friendly description
2025-02-11 19:26:59 +01:00
Erik Montnemery fe3d6f93d7 Fix data_entry_flow.UnknownStep error message (#138288) 2025-02-11 18:56:18 +01:00
Joost Lekkerkerker 3489b20e86 Refactor SmartThings sensor platform (#138313) 2025-02-11 18:14:13 +01:00
epenet 8e7f35aa7d Use runtime_data in flo (#138307) 2025-02-11 17:42:58 +01:00
epenet 14e1b55b5a Do not test internals in flo tests (#138306)
* Do not test internals in flo tests

* fix
2025-02-11 17:26:58 +01:00
balazs92117 a85bb98743 Dsmr eon hungary (#138162)
Add EON hungary
2025-02-11 17:25:57 +01:00
RJPoelstra ab1e1c06b6 Set PARALLEL_UPDATES for MotionMount integration (#138264)
Set PARALLEL_UPDATES
2025-02-11 17:22:51 +01:00
Michael Hansen 6226542e4d Keep responding state on wake word start (#138244)
* Keep responding state on wake word start

* Add comment
2025-02-11 10:21:41 -06:00
epenet 7f376ff004 Use runtime_data in flux_led (#138279) 2025-02-11 17:09:06 +01:00
epenet 62b563eb60 Use runtime_data in flexit_bacnet (#138280) 2025-02-11 17:08:39 +01:00
epenet df4c718bac Use runtime_data in fjaraskupan (#138281) 2025-02-11 17:00:44 +01:00
Steven Hartland 1a4738b1d4 Fix scaffolding integration generation (#138247)
* fix(scaffold): integration generation

Fix script.scaffold integration generation which was failing due to
hassfest quality check.

Add the required `quality_scale` to the generated integration
manifest.json.

Use the new `--skip-plugins` flag to skip the hassfest quality check
when generating integrations, as the quality scale rules are marked as
todo, and only run against the generated integration.

Correct typo in help for hassfest command `--plugins` flag.

Update Integration.core method to use absolute path to ensure it returns
the true if the integration is a core integration, which was causing
other checks to fail, as the integration was not being marked as core.

Always output subprocess output as it contains the error message when a
command fails, without this the user would not know why the command
failed.

Fixes: #128639

* Adjust comment language

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-02-11 16:24:04 +01:00
Erik Montnemery 8b3421deb7 Add test helper for creating a mocked backup agent (#138294)
* Add test helper for creating a mocked backup agent

* Address review comments
2025-02-11 16:21:24 +01:00
710 changed files with 25601 additions and 4701 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 99 KiB

+100
View File
@@ -0,0 +1,100 @@
# Instructions for GitHub Copilot
This repository holds the core of Home Assistant, a Python 3 based home
automation application.
- Python code must be compatible with Python 3.13
- Use the newest Python language features if possible:
- Pattern matching
- Type hints
- f-strings for string formatting over `%` or `.format()`
- Dataclasses
- Walrus operator
- Code quality tools:
- Formatting: Ruff
- Linting: PyLint and Ruff
- Type checking: MyPy
- Testing: pytest with plain functions and fixtures
- Inline code documentation:
- File headers should be short and concise:
```python
"""Integration for Peblar EV chargers."""
```
- Every method and function needs a docstring:
```python
async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bool:
"""Set up Peblar from a config entry."""
...
```
- All code and comments and other text are written in American English
- Follow existing code style patterns as much as possible
- Core locations:
- Shared constants: `homeassistant/const.py`, use them instead of hardcoding
strings or creating duplicate integration constants.
- Integration files:
- Constants: `homeassistant/components/{domain}/const.py`
- Models: `homeassistant/components/{domain}/models.py`
- Coordinator: `homeassistant/components/{domain}/coordinator.py`
- Config flow: `homeassistant/components/{domain}/config_flow.py`
- Platform code: `homeassistant/components/{domain}/{platform}.py`
- All external I/O operations must be async
- Async patterns:
- Avoid sleeping in loops
- Avoid awaiting in loops, gather instead
- No blocking calls
- Polling:
- Follow update coordinator pattern, when possible
- Polling interval may not be configurable by the user
- For local network polling, the minimum interval is 5 seconds
- For cloud polling, the minimum interval is 60 seconds
- Error handling:
- Use specific exceptions from `homeassistant.exceptions`
- Setup failures:
- Temporary: Raise `ConfigEntryNotReady`
- Permanent: Use `ConfigEntryError`
- Logging:
- Message format:
- No periods at end
- No integration names or domains (added automatically)
- No sensitive data (keys, tokens, passwords), even when those are incorrect.
- Be very restrictive on the use of logging info messages, use debug for
anything which is not targeting the user.
- Use lazy logging (no f-strings):
```python
_LOGGER.debug("This is a log message with %s", variable)
```
- Entities:
- Ensure unique IDs for state persistence:
- Unique IDs should not contain values that are subject to user or network change.
- An ID needs to be unique per platform, not per integration.
- The ID does not have to contain the integration domain or platform.
- Acceptable examples:
- Serial number of a device
- MAC address of a device formatted using `homeassistant.helpers.device_registry.format_mac`
Do not obtain the MAC address through arp cache of local network access,
only use the MAC address provided by discovery or the device itself.
- Unique identifier that is physically printed on the device or burned into an EEPROM
- Not acceptable examples:
- IP Address
- Device name
- Hostname
- URL
- Email address
- Username
- For entities that are setup by a config entry, the config entry ID
can be used as a last resort if no other Unique ID is available.
For example: `f"{entry.entry_id}-battery"`
- If the state value is unknown, use `None`
- Do not use the `unavailable` string as a state value,
implement the `available()` property method instead
- Do not use the `unknown` string as a state value, use `None` instead
- Extra entity state attributes:
- The keys of all state attributes should always be present
- If the value is unknown, use `None`
- Provide descriptive state attributes
- Testing:
- Test location: `tests/components/{domain}/`
- Use pytest fixtures from `tests.common`
- Mock external dependencies
- Use snapshots for complex data
- Follow existing test patterns
+2 -2
View File
@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.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@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
+22 -22
View File
@@ -240,7 +240,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.0
uses: actions/cache@v4.2.1
with:
path: venv
key: >-
@@ -256,7 +256,7 @@ jobs:
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v4.2.0
uses: actions/cache@v4.2.1
with:
path: ${{ env.PRE_COMMIT_CACHE }}
lookup-only: true
@@ -286,7 +286,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -295,7 +295,7 @@ jobs:
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
@@ -326,7 +326,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -335,7 +335,7 @@ jobs:
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
@@ -366,7 +366,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -375,7 +375,7 @@ jobs:
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
@@ -482,7 +482,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.0
uses: actions/cache@v4.2.1
with:
path: venv
key: >-
@@ -490,7 +490,7 @@ jobs:
needs.info.outputs.python_cache_key }}
- name: Restore uv wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v4.2.0
uses: actions/cache@v4.2.1
with:
path: ${{ env.UV_CACHE_DIR }}
key: >-
@@ -578,7 +578,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -611,7 +611,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -649,7 +649,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -692,7 +692,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -739,7 +739,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -791,7 +791,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -799,7 +799,7 @@ jobs:
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore mypy cache
uses: actions/cache@v4.2.0
uses: actions/cache@v4.2.1
with:
path: .mypy_cache
key: >-
@@ -865,7 +865,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -929,7 +929,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -1051,7 +1051,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -1181,7 +1181,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
@@ -1328,7 +1328,7 @@ jobs:
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.0
uses: actions/cache/restore@v4.2.1
with:
path: venv
fail-on-cache-miss: true
+1 -1
View File
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.1
rev: v0.9.7
hooks:
- id: ruff
args:
+2
View File
@@ -234,6 +234,7 @@ homeassistant.components.here_travel_time.*
homeassistant.components.history.*
homeassistant.components.history_stats.*
homeassistant.components.holiday.*
homeassistant.components.home_connect.*
homeassistant.components.homeassistant.*
homeassistant.components.homeassistant_alerts.*
homeassistant.components.homeassistant_green.*
@@ -437,6 +438,7 @@ homeassistant.components.select.*
homeassistant.components.sensibo.*
homeassistant.components.sensirion_ble.*
homeassistant.components.sensor.*
homeassistant.components.sensorpush_cloud.*
homeassistant.components.sensoterra.*
homeassistant.components.senz.*
homeassistant.components.sfr_box.*
Generated
+2
View File
@@ -1342,6 +1342,8 @@ build.json @home-assistant/supervisor
/tests/components/sensorpro/ @bdraco
/homeassistant/components/sensorpush/ @bdraco
/tests/components/sensorpush/ @bdraco
/homeassistant/components/sensorpush_cloud/ @sstallion
/tests/components/sensorpush_cloud/ @sstallion
/homeassistant/components/sensoterra/ @markruys
/tests/components/sensoterra/ @markruys
/homeassistant/components/sentry/ @dcramer @frenck
Generated
+19 -19
View File
@@ -12,8 +12,26 @@ ENV \
ARG QEMU_CPU
# Home Assistant S6-Overlay
COPY rootfs /
# Needs to be redefined inside the FROM statement to be set for RUN commands
ARG BUILD_ARCH
# Get go2rtc binary
RUN \
case "${BUILD_ARCH}" in \
"aarch64") go2rtc_suffix='arm64' ;; \
"armhf") go2rtc_suffix='armv6' ;; \
"armv7") go2rtc_suffix='arm' ;; \
*) go2rtc_suffix=${BUILD_ARCH} ;; \
esac \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.8/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& chmod +x /bin/go2rtc \
# Verify go2rtc can be executed
&& go2rtc --version
# Install uv
RUN pip3 install uv==0.5.27
RUN pip3 install uv==0.6.1
WORKDIR /usr/src
@@ -42,22 +60,4 @@ RUN \
&& python3 -m compileall \
homeassistant/homeassistant
# Home Assistant S6-Overlay
COPY rootfs /
# Needs to be redefined inside the FROM statement to be set for RUN commands
ARG BUILD_ARCH
# Get go2rtc binary
RUN \
case "${BUILD_ARCH}" in \
"aarch64") go2rtc_suffix='arm64' ;; \
"armhf") go2rtc_suffix='armv6' ;; \
"armv7") go2rtc_suffix='arm' ;; \
*) go2rtc_suffix=${BUILD_ARCH} ;; \
esac \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.8/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& chmod +x /bin/go2rtc \
# Verify go2rtc can be executed
&& go2rtc --version
WORKDIR /config
+5 -5
View File
@@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-homeassistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.12.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.12.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.12.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.12.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.12.0
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.02.1
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.02.1
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.02.1
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.02.1
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.02.1
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io
+60 -76
View File
@@ -134,14 +134,12 @@ DATA_REGISTRIES_LOADED: HassKey[None] = HassKey("bootstrap_registries_loaded")
LOG_SLOW_STARTUP_INTERVAL = 60
SLOW_STARTUP_CHECK_INTERVAL = 1
STAGE_0_SUBSTAGE_TIMEOUT = 60
STAGE_1_TIMEOUT = 120
STAGE_2_TIMEOUT = 300
WRAP_UP_TIMEOUT = 300
COOLDOWN_TIME = 60
DEBUGGER_INTEGRATIONS = {"debugpy"}
# Core integrations are unconditionally loaded
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
@@ -152,6 +150,10 @@ LOGGING_AND_HTTP_DEPS_INTEGRATIONS = {
"isal",
# Set log levels
"logger",
# Ensure network config is available
# before hassio or any other integration is
# loaded that might create an aiohttp client session
"network",
# Error logging
"system_log",
"sentry",
@@ -172,12 +174,27 @@ FRONTEND_INTEGRATIONS = {
# add it here.
"backup",
}
RECORDER_INTEGRATIONS = {
# Setup after frontend
# To record data
"recorder",
}
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
# Stage 0 is divided into substages. Each substage has a name, a set of integrations and a timeout.
# The substage containing recorder should have no timeout, as it could cancel a database migration.
# Recorder freezes "recorder" timeout during a migration, but it does not freeze other timeouts.
# The substages preceding it should also have no timeout, until we ensure that the recorder
# is not accidentally promoted as a dependency of any of the integrations in them.
# If we add timeouts to the frontend substages, we should make sure they don't apply in recovery mode.
STAGE_0_INTEGRATIONS = (
# Load logging and http deps as soon as possible
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS, None),
# Setup frontend
("frontend", FRONTEND_INTEGRATIONS, None),
# Setup recorder
("recorder", {"recorder"}, None),
# Start up debuggers. Start these first in case they want to wait.
("debugger", {"debugpy"}, STAGE_0_SUBSTAGE_TIMEOUT),
# Zeroconf is used for mdns resolution in aiohttp client helper.
("zeroconf", {"zeroconf"}, STAGE_0_SUBSTAGE_TIMEOUT),
)
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb")
# Stage 1 integrations are not to be preimported in bootstrap.
STAGE_1_INTEGRATIONS = {
# We need to make sure discovery integrations
# update their deps before stage 2 integrations
@@ -189,9 +206,8 @@ STAGE_1_INTEGRATIONS = {
"mqtt_eventstream",
# To provide account link implementations
"cloud",
# Ensure supervisor is available
"hassio",
}
DEFAULT_INTEGRATIONS = {
# These integrations are set up unless recovery mode is activated.
#
@@ -232,22 +248,12 @@ DEFAULT_INTEGRATIONS_SUPERVISOR = {
# These integrations are set up if using the Supervisor
"hassio",
}
CRITICAL_INTEGRATIONS = {
# Recovery mode is activated if these integrations fail to set up
"frontend",
}
SETUP_ORDER = (
# Load logging and http deps as soon as possible
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS),
# Setup frontend
("frontend", FRONTEND_INTEGRATIONS),
# Setup recorder
("recorder", RECORDER_INTEGRATIONS),
# Start up debuggers. Start these first in case they want to wait.
("debugger", DEBUGGER_INTEGRATIONS),
)
#
# Storage keys we are likely to load during startup
# in order of when we expect to load them.
@@ -694,7 +700,6 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
return deps_dir
@core.callback
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
"""Get domains of components to set up."""
# Filter out the repeating and common config section [homeassistant]
@@ -890,69 +895,48 @@ async def _async_set_up_integrations(
domains_to_setup, integration_cache = await _async_resolve_domains_to_setup(
hass, config
)
stage_2_domains = domains_to_setup.copy()
# Initialize recorder
if "recorder" in domains_to_setup:
recorder.async_initialize_recorder(hass)
pre_stage_domains = [
(name, domains_to_setup & domain_group) for name, domain_group in SETUP_ORDER
stage_0_and_1_domains: list[tuple[str, set[str], int | None]] = [
*(
(name, domain_group & domains_to_setup, timeout)
for name, domain_group, timeout in STAGE_0_INTEGRATIONS
),
("stage 1", STAGE_1_INTEGRATIONS & domains_to_setup, STAGE_1_TIMEOUT),
]
# calculate what components to setup in what stage
stage_1_domains: set[str] = set()
_LOGGER.info("Setting up stage 0 and 1")
for name, domain_group, timeout in stage_0_and_1_domains:
if not domain_group:
continue
# Find all dependencies of any dependency of any stage 1 integration that
# we plan on loading and promote them to stage 1. This is done only to not
# get misleading log messages
deps_promotion: set[str] = STAGE_1_INTEGRATIONS
while deps_promotion:
old_deps_promotion = deps_promotion
deps_promotion = set()
_LOGGER.info("Setting up %s: %s", name, domain_group)
to_be_loaded = domain_group.copy()
to_be_loaded.update(
dep
for domain in domain_group
if (integration := integration_cache.get(domain)) is not None
for dep in integration.all_dependencies
)
async_set_domains_to_be_loaded(hass, to_be_loaded)
stage_2_domains -= to_be_loaded
for domain in old_deps_promotion:
if domain not in domains_to_setup or domain in stage_1_domains:
continue
stage_1_domains.add(domain)
if (dep_itg := integration_cache.get(domain)) is None:
continue
deps_promotion.update(dep_itg.all_dependencies)
stage_2_domains = domains_to_setup - stage_1_domains
for name, domain_group in pre_stage_domains:
if domain_group:
stage_2_domains -= domain_group
_LOGGER.info("Setting up %s: %s", name, domain_group)
to_be_loaded = domain_group.copy()
to_be_loaded.update(
dep
for domain in domain_group
if (integration := integration_cache.get(domain)) is not None
for dep in integration.all_dependencies
)
async_set_domains_to_be_loaded(hass, to_be_loaded)
if timeout is None:
await _async_setup_multi_components(hass, domain_group, config)
# Enables after dependencies when setting up stage 1 domains
async_set_domains_to_be_loaded(hass, stage_1_domains)
# Start setup
if stage_1_domains:
_LOGGER.info("Setting up stage 1: %s", stage_1_domains)
try:
async with hass.timeout.async_timeout(
STAGE_1_TIMEOUT, cool_down=COOLDOWN_TIME
):
await _async_setup_multi_components(hass, stage_1_domains, config)
except TimeoutError:
_LOGGER.warning(
"Setup timed out for stage 1 waiting on %s - moving forward",
hass._active_tasks, # noqa: SLF001
)
else:
try:
async with hass.timeout.async_timeout(timeout, cool_down=COOLDOWN_TIME):
await _async_setup_multi_components(hass, domain_group, config)
except TimeoutError:
_LOGGER.warning(
"Setup timed out for %s waiting on %s - moving forward",
name,
hass._active_tasks, # noqa: SLF001
)
# Add after dependencies when setting up stage 2 domains
async_set_domains_to_be_loaded(hass, stage_2_domains)
+5
View File
@@ -0,0 +1,5 @@
{
"domain": "sensorpush",
"name": "SensorPush",
"integrations": ["sensorpush", "sensorpush_cloud"]
}
+2 -7
View File
@@ -7,7 +7,7 @@ from dataclasses import dataclass
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
@@ -123,12 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> bool:
"""Unload AdGuard Home config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
if not hass.config_entries.async_loaded_entries(DOMAIN):
# This is the last loaded instance of AdGuard, deregister any services
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.9.1"],
"requirements": ["airgradient==0.9.2"],
"zeroconf": ["_airgradient._tcp.local."]
}
@@ -83,6 +83,7 @@ class AirQConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(device_info["id"])
self._abort_if_unique_id_configured()
_LOGGER.debug("Creating an entry for %s", device_info["name"])
return self.async_create_entry(title=device_info["name"], data=user_input)
return self.async_show_form(
+14 -2
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
from aioairq import AirQ
from aioairq.core import AirQ, identify_warming_up_sensors
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
@@ -55,6 +55,9 @@ class AirQCoordinator(DataUpdateCoordinator):
async def _async_update_data(self) -> dict:
"""Fetch the data from the device."""
if "name" not in self.device_info:
_LOGGER.debug(
"'name' not found in AirQCoordinator.device_info, fetching from the device"
)
info = await self.airq.fetch_device_info()
self.device_info.update(
DeviceInfo(
@@ -64,7 +67,16 @@ class AirQCoordinator(DataUpdateCoordinator):
hw_version=info["hw_version"],
)
)
return await self.airq.get_latest_data( # type: ignore[no-any-return]
_LOGGER.debug(
"Updated AirQCoordinator.device_info for 'name' %s",
self.device_info.get("name"),
)
data: dict = await self.airq.get_latest_data(
return_average=self.return_average,
clip_negative_values=self.clip_negative,
)
if warming_up_sensors := identify_warming_up_sensors(data):
_LOGGER.debug(
"Following sensors are still warming up: %s", warming_up_sensors
)
return data
@@ -2,7 +2,7 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
@@ -28,11 +28,13 @@ async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if all(
config_entry.state is ConfigEntryState.NOT_LOADED
for config_entry in hass.config_entries.async_entries(DOMAIN)
if config_entry.entry_id != entry.entry_id
):
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
return True
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Remove a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
# Remove any remaining disabled or ignored entries
for _entry in hass.config_entries.async_entries(DOMAIN):
hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id))
@@ -90,7 +90,7 @@
},
"alarm_arm_home": {
"name": "Arm home",
"description": "Sets the alarm to: _armed, but someone is home_.",
"description": "Arms the alarm in the home mode.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
@@ -100,7 +100,7 @@
},
"alarm_arm_away": {
"name": "Arm away",
"description": "Sets the alarm to: _armed, no one home_.",
"description": "Arms the alarm in the away mode.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
@@ -110,7 +110,7 @@
},
"alarm_arm_night": {
"name": "Arm night",
"description": "Sets the alarm to: _armed for the night_.",
"description": "Arms the alarm in the night mode.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
@@ -120,7 +120,7 @@
},
"alarm_arm_vacation": {
"name": "Arm vacation",
"description": "Sets the alarm to: _armed for vacation_.",
"description": "Arms the alarm in the vacation mode.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
@@ -130,7 +130,7 @@
},
"alarm_trigger": {
"name": "Trigger",
"description": "Trigger the alarm manually.",
"description": "Triggers the alarm manually.",
"fields": {
"code": {
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
@@ -239,6 +239,7 @@ SENSOR_DESCRIPTIONS = (
native_unit_of_measurement=DEGREE,
suggested_display_precision=0,
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key=TYPE_WINDGUSTMPH,
@@ -608,21 +608,25 @@ SENSOR_DESCRIPTIONS = (
key=TYPE_WINDDIR,
translation_key="wind_direction",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key=TYPE_WINDDIR_AVG10M,
translation_key="wind_direction_average_10m",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key=TYPE_WINDDIR_AVG2M,
translation_key="wind_direction_average_2m",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key=TYPE_WINDGUSTDIR,
translation_key="wind_gust_direction",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key=TYPE_WINDGUSTMPH,
@@ -10,7 +10,7 @@
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"cannot_receive_deviceinfo": "Failed to retreive MAC Address. Make sure the device is turned on"
"cannot_receive_deviceinfo": "Failed to retrieve MAC Address. Make sure the device is turned on"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
@@ -1,16 +1,23 @@
"""Conversation support for Anthropic."""
from collections.abc import Callable
from collections.abc import AsyncGenerator, Callable
import json
from typing import Any, Literal, cast
from typing import Any, Literal
import anthropic
from anthropic import AsyncStream
from anthropic._types import NOT_GIVEN
from anthropic.types import (
InputJSONDelta,
Message,
MessageParam,
MessageStreamEvent,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawContentBlockStopEvent,
TextBlock,
TextBlockParam,
TextDelta,
ToolParam,
ToolResultBlockParam,
ToolUseBlock,
@@ -109,7 +116,7 @@ def _convert_content(chat_content: conversation.Content) -> MessageParam:
type="tool_use",
id=tool_call.id,
name=tool_call.tool_name,
input=json.dumps(tool_call.tool_args),
input=tool_call.tool_args,
)
for tool_call in chat_content.tool_calls or ()
],
@@ -124,6 +131,66 @@ def _convert_content(chat_content: conversation.Content) -> MessageParam:
raise ValueError(f"Unexpected content type: {type(chat_content)}")
async def _transform_stream(
result: AsyncStream[MessageStreamEvent],
) -> AsyncGenerator[conversation.AssistantContentDeltaDict]:
"""Transform the response stream into HA format.
A typical stream of responses might look something like the following:
- RawMessageStartEvent with no content
- RawContentBlockStartEvent with an empty TextBlock
- RawContentBlockDeltaEvent with a TextDelta
- RawContentBlockDeltaEvent with a TextDelta
- RawContentBlockDeltaEvent with a TextDelta
- ...
- RawContentBlockStopEvent
- RawContentBlockStartEvent with ToolUseBlock specifying the function name
- RawContentBlockDeltaEvent with a InputJSONDelta
- RawContentBlockDeltaEvent with a InputJSONDelta
- ...
- RawContentBlockStopEvent
- RawMessageDeltaEvent with a stop_reason='tool_use'
- RawMessageStopEvent(type='message_stop')
"""
if result is None:
raise TypeError("Expected a stream of messages")
current_tool_call: dict | None = None
async for response in result:
LOGGER.debug("Received response: %s", response)
if isinstance(response, RawContentBlockStartEvent):
if isinstance(response.content_block, ToolUseBlock):
current_tool_call = {
"id": response.content_block.id,
"name": response.content_block.name,
"input": "",
}
elif isinstance(response.content_block, TextBlock):
yield {"role": "assistant"}
elif isinstance(response, RawContentBlockDeltaEvent):
if isinstance(response.delta, InputJSONDelta):
if current_tool_call is None:
raise ValueError("Unexpected delta without a tool call")
current_tool_call["input"] += response.delta.partial_json
elif isinstance(response.delta, TextDelta):
LOGGER.debug("yielding delta: %s", response.delta.text)
yield {"content": response.delta.text}
elif isinstance(response, RawContentBlockStopEvent):
if current_tool_call:
yield {
"tool_calls": [
llm.ToolInput(
id=current_tool_call["id"],
tool_name=current_tool_call["name"],
tool_args=json.loads(current_tool_call["input"]),
)
]
}
current_tool_call = None
class AnthropicConversationEntity(
conversation.ConversationEntity, conversation.AbstractConversationAgent
):
@@ -206,58 +273,30 @@ class AnthropicConversationEntity(
# To prevent infinite loops, we limit the number of iterations
for _iteration in range(MAX_TOOL_ITERATIONS):
try:
response = await client.messages.create(
stream = await client.messages.create(
model=options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL),
messages=messages,
tools=tools or NOT_GIVEN,
max_tokens=options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
system=system.content,
temperature=options.get(CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE),
stream=True,
)
except anthropic.AnthropicError as err:
raise HomeAssistantError(
f"Sorry, I had a problem talking to Anthropic: {err}"
) from err
LOGGER.debug("Response %s", response)
messages.append(_message_convert(response))
text = "".join(
messages.extend(
[
content.text
for content in response.content
if isinstance(content, TextBlock)
_convert_content(content)
async for content in chat_log.async_add_delta_content_stream(
user_input.agent_id, _transform_stream(stream)
)
]
)
tool_inputs = [
llm.ToolInput(
id=tool_call.id,
tool_name=tool_call.name,
tool_args=cast(dict[str, Any], tool_call.input),
)
for tool_call in response.content
if isinstance(tool_call, ToolUseBlock)
]
tool_results = [
ToolResultBlockParam(
type="tool_result",
tool_use_id=tool_response.tool_call_id,
content=json.dumps(tool_response.tool_result),
)
async for tool_response in chat_log.async_add_assistant_content(
conversation.AssistantContent(
agent_id=user_input.agent_id,
content=text,
tool_calls=tool_inputs or None,
)
)
]
if tool_results:
messages.append(MessageParam(role="user", content=tool_results))
if not tool_inputs:
if not chat_log.unresponded_tool_results:
break
response_content = chat_log.content[-1]
+11 -1
View File
@@ -19,10 +19,20 @@ class ApSystemsEntity(Entity):
data: ApSystemsData,
) -> None:
"""Initialize the APsystems entity."""
# Handle device version safely
sw_version = None
if data.coordinator.device_version:
version_parts = data.coordinator.device_version.split(" ")
if len(version_parts) > 1:
sw_version = version_parts[1]
else:
sw_version = version_parts[0]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, data.device_id)},
manufacturer="APsystems",
model="EZ1-M",
serial_number=data.device_id,
sw_version=data.coordinator.device_version.split(" ")[1],
sw_version=sw_version,
)
+12 -2
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from aranet4.client import Aranet4Advertisement
from aranet4.client import Aranet4Advertisement, Color
from bleak.backends.device import BLEDevice
from homeassistant.components.bluetooth.passive_update_processor import (
@@ -74,6 +74,13 @@ SENSOR_DESCRIPTIONS = {
native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT,
),
"status": AranetSensorEntityDescription(
key="threshold",
translation_key="threshold",
name="Threshold",
device_class=SensorDeviceClass.ENUM,
options=[status.name.lower() for status in Color],
),
"co2": AranetSensorEntityDescription(
key="co2",
name="Carbon Dioxide",
@@ -161,7 +168,10 @@ def sensor_update_to_bluetooth_data_update(
val = getattr(adv.readings, key)
if val == -1:
continue
val *= desc.scale
if key == "status":
val = val.name.lower()
else:
val *= desc.scale
data[tag] = val
names[tag] = desc.name
descs[tag] = desc
@@ -21,5 +21,17 @@
"no_devices_found": "No unconfigured Aranet devices found.",
"outdated_version": "This device is using outdated firmware. Please update it to at least v1.2.0 and try again."
}
},
"entity": {
"sensor": {
"threshold": {
"state": {
"error": "Error",
"green": "Green",
"yellow": "Yellow",
"red": "Red"
}
}
}
}
}
@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
"iot_class": "local_polling",
"loggers": ["arcam"],
"requirements": ["arcam-fmj==1.5.2"],
"requirements": ["arcam-fmj==1.8.1"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
+6 -1
View File
@@ -92,7 +92,12 @@ def discover_sensors(topic: str, payload: dict[str, Any]) -> list[ArwnSensor] |
device_class=SensorDeviceClass.WIND_SPEED,
),
ArwnSensor(
topic + "/dir", "Wind Direction", "direction", DEGREE, "mdi:compass"
topic + "/dir",
"Wind Direction",
"direction",
DEGREE,
"mdi:compass",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
]
return None
@@ -13,7 +13,7 @@ from pathlib import Path
from queue import Empty, Queue
from threading import Thread
import time
from typing import Any, Literal, cast
from typing import TYPE_CHECKING, Any, Literal, cast
import wave
import hass_nabucasa
@@ -30,7 +30,7 @@ from homeassistant.components import (
from homeassistant.components.tts import (
generate_media_source_id as tts_generate_media_source_id,
)
from homeassistant.const import MATCH_ALL
from homeassistant.const import ATTR_SUPPORTED_FEATURES, MATCH_ALL
from homeassistant.core import Context, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import chat_session, intent
@@ -81,6 +81,9 @@ from .error import (
)
from .vad import AudioBuffer, VoiceActivityTimeout, VoiceCommandSegmenter, chunk_samples
if TYPE_CHECKING:
from hassil.recognize import RecognizeResult
_LOGGER = logging.getLogger(__name__)
STORAGE_KEY = f"{DOMAIN}.pipelines"
@@ -123,6 +126,12 @@ STORED_PIPELINE_RUNS = 10
SAVE_DELAY = 10
@callback
def _async_local_fallback_intent_filter(result: RecognizeResult) -> bool:
"""Filter out intents that are not local fallback."""
return result.intent.name in (intent.INTENT_GET_STATE, intent.INTENT_NEVERMIND)
@callback
def _async_resolve_default_pipeline_settings(
hass: HomeAssistant,
@@ -1084,10 +1093,22 @@ class PipelineRun:
)
intent_response.async_set_speech(trigger_response_text)
intent_filter: Callable[[RecognizeResult], bool] | None = None
# If the LLM has API access, we filter out some sentences that are
# interfering with LLM operation.
if (
intent_agent_state := self.hass.states.get(self.intent_agent)
) and intent_agent_state.attributes.get(
ATTR_SUPPORTED_FEATURES, 0
) & conversation.ConversationEntityFeature.CONTROL:
intent_filter = _async_local_fallback_intent_filter
# Try local intents first, if preferred.
elif self.pipeline.prefer_local_intents and (
intent_response := await conversation.async_handle_intents(
self.hass, user_input
self.hass,
user_input,
intent_filter=intent_filter,
)
):
# Local intent matched
@@ -405,7 +405,10 @@ class AssistSatelliteEntity(entity.Entity):
def _internal_on_pipeline_event(self, event: PipelineEvent) -> None:
"""Set state based on pipeline stage."""
if event.type is PipelineEventType.WAKE_WORD_START:
self._set_state(AssistSatelliteState.IDLE)
# Only return to idle if we're not currently responding.
# The state will return to idle in tts_response_finished.
if self.state != AssistSatelliteState.RESPONDING:
self._set_state(AssistSatelliteState.IDLE)
elif event.type is PipelineEventType.STT_START:
self._set_state(AssistSatelliteState.LISTENING)
elif event.type is PipelineEventType.INTENT_START:
@@ -19,6 +19,7 @@ from .const import (
DOMAIN,
AssistSatelliteEntityFeature,
)
from .entity import AssistSatelliteConfiguration
CONNECTION_TEST_TIMEOUT = 30
@@ -91,7 +92,16 @@ def websocket_get_configuration(
)
return
config_dict = asdict(satellite.async_get_configuration())
try:
config_dict = asdict(satellite.async_get_configuration())
except NotImplementedError:
# Stub configuration
config_dict = asdict(
AssistSatelliteConfiguration(
available_wake_words=[], active_wake_words=[], max_active_wake_words=1
)
)
config_dict["pipeline_entity_id"] = satellite.pipeline_entity_id
config_dict["vad_entity_id"] = satellite.vad_sensitivity_entity_id
@@ -11,7 +11,7 @@
},
"error": {
"cannot_connect": "Unable to connect, please check serial port, address, electrical connection and that inverter is on (in daylight)",
"invalid_serial_port": "Serial port is not a valid device or could not be openned",
"invalid_serial_port": "Serial port is not a valid device or could not be opened",
"cannot_open_serial_port": "Cannot open serial port, please check and try again"
},
"abort": {
@@ -16,6 +16,7 @@ from .agent import (
BackupAgentPlatformProtocol,
LocalBackupAgent,
)
from .config import BackupConfig, CreateBackupParametersDict
from .const import DATA_MANAGER, DOMAIN
from .http import async_register_http_views
from .manager import (
@@ -47,12 +48,14 @@ __all__ = [
"BackupAgent",
"BackupAgentError",
"BackupAgentPlatformProtocol",
"BackupConfig",
"BackupManagerError",
"BackupNotFound",
"BackupPlatformProtocol",
"BackupReaderWriter",
"BackupReaderWriterError",
"CreateBackupEvent",
"CreateBackupParametersDict",
"CreateBackupStage",
"CreateBackupState",
"Folder",
+2 -1
View File
@@ -154,7 +154,8 @@ class BackupConfig:
self.data.retention.apply(self._manager)
self.data.schedule.apply(self._manager)
async def update(
@callback
def update(
self,
*,
agents: dict[str, AgentParametersDict] | UndefinedType = UNDEFINED,
+48 -1
View File
@@ -43,7 +43,11 @@ from .agent import (
BackupAgentPlatformProtocol,
LocalBackupAgent,
)
from .config import BackupConfig, delete_backups_exceeding_configured_count
from .config import (
BackupConfig,
CreateBackupParametersDict,
delete_backups_exceeding_configured_count,
)
from .const import (
BUF_SIZE,
DATA_MANAGER,
@@ -282,6 +286,10 @@ class BackupReaderWriter(abc.ABC):
) -> None:
"""Get restore events after core restart."""
@abc.abstractmethod
async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config."""
class IncorrectPasswordError(BackupReaderWriterError):
"""Raised when the password is incorrect."""
@@ -333,6 +341,7 @@ class BackupManager:
self.config.load(stored["config"])
self.known_backups.load(stored["backups"])
await self._reader_writer.async_validate_config(config=self.config)
await self._reader_writer.async_resume_restore_progress_after_restart(
on_progress=self.async_on_backup_event
)
@@ -1832,6 +1841,44 @@ class CoreBackupReaderWriter(BackupReaderWriter):
)
on_progress(IdleEvent())
async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config.
Update automatic backup settings to not include addons or folders and remove
hassio agents in case a backup created by supervisor was restored.
"""
create_backup = config.data.create_backup
if (
not create_backup.include_addons
and not create_backup.include_all_addons
and not create_backup.include_folders
and not any(a_id.startswith("hassio.") for a_id in create_backup.agent_ids)
):
LOGGER.debug("Backup settings don't need to be adjusted")
return
LOGGER.info(
"Adjusting backup settings to not include addons, folders or supervisor locations"
)
automatic_agents = [
agent_id
for agent_id in create_backup.agent_ids
if not agent_id.startswith("hassio.")
]
if (
self._local_agent_id not in automatic_agents
and "hassio.local" in create_backup.agent_ids
):
automatic_agents = [self._local_agent_id, *automatic_agents]
config.update(
create_backup=CreateBackupParametersDict(
agent_ids=automatic_agents,
include_addons=None,
include_all_addons=False,
include_folders=None,
)
)
def _generate_backup_id(date: str, name: str) -> str:
"""Generate a backup ID."""
+8 -1
View File
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
STORE_DELAY_SAVE = 30
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
STORAGE_VERSION_MINOR = 3
STORAGE_VERSION_MINOR = 4
class StoredBackupData(TypedDict):
@@ -60,6 +60,13 @@ class _BackupStore(Store[StoredBackupData]):
else:
data["config"]["schedule"]["days"] = [state]
data["config"]["schedule"]["recurrence"] = "custom_days"
if old_minor_version < 4:
# Workaround for a bug in frontend which incorrectly set days to 0
# instead of to None for unlimited retention.
if data["config"]["retention"]["copies"] == 0:
data["config"]["retention"]["copies"] = None
if data["config"]["retention"]["days"] == 0:
data["config"]["retention"]["days"] = None
# Note: We allow reading data with major version 2.
# Reject if major version is higher than 2.
+5 -2
View File
@@ -104,12 +104,15 @@ def read_backup(backup_path: Path) -> AgentBackup:
bool, homeassistant.get("exclude_database", False)
)
extra_metadata = cast(dict[str, bool | str], data.get("extra", {}))
date = extra_metadata.get("supervisor.backup_request_date", data["date"])
return AgentBackup(
addons=addons,
backup_id=cast(str, data["slug"]),
database_included=database_included,
date=cast(str, data["date"]),
extra_metadata=cast(dict[str, bool | str], data.get("extra", {})),
date=cast(str, date),
extra_metadata=extra_metadata,
folders=folders,
homeassistant_included=homeassistant_included,
homeassistant_version=homeassistant_version,
+7 -5
View File
@@ -346,6 +346,7 @@ async def handle_config_info(
)
@callback
@websocket_api.require_admin
@websocket_api.websocket_command(
{
@@ -368,8 +369,10 @@ async def handle_config_info(
),
vol.Optional("retention"): vol.Schema(
{
vol.Optional("copies"): vol.Any(int, None),
vol.Optional("days"): vol.Any(int, None),
# Note: We can't use cv.positive_int because it allows 0 even
# though 0 is not positive.
vol.Optional("copies"): vol.Any(vol.All(int, vol.Range(min=1)), None),
vol.Optional("days"): vol.Any(vol.All(int, vol.Range(min=1)), None),
},
),
vol.Optional("schedule"): vol.Schema(
@@ -385,8 +388,7 @@ async def handle_config_info(
),
}
)
@websocket_api.async_response
async def handle_config_update(
def handle_config_update(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
@@ -396,7 +398,7 @@ async def handle_config_update(
changes = dict(msg)
changes.pop("id")
changes.pop("type")
await manager.config.update(**changes)
manager.config.update(**changes)
connection.send_result(msg["id"])
@@ -24,6 +24,8 @@ PLATFORMS = [
Platform.FAN,
Platform.LIGHT,
Platform.SELECT,
Platform.SWITCH,
Platform.TIME,
]
@@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/balboa",
"iot_class": "local_push",
"loggers": ["pybalboa"],
"requirements": ["pybalboa==1.1.2"]
"requirements": ["pybalboa==1.1.3"]
}
@@ -78,6 +78,19 @@
"high": "High"
}
}
},
"switch": {
"filter_cycle_2_enabled": {
"name": "Filter cycle 2 enabled"
}
},
"time": {
"filter_cycle_start": {
"name": "Filter cycle {index} start"
},
"filter_cycle_end": {
"name": "Filter cycle {index} end"
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
"""Support for Balboa switches."""
from __future__ import annotations
from typing import Any
from pybalboa import SpaClient
from homeassistant.components.switch import SwitchEntity
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BalboaConfigEntry
from .entity import BalboaEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: BalboaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the spa's switches."""
spa = entry.runtime_data
async_add_entities([BalboaSwitchEntity(spa)])
class BalboaSwitchEntity(BalboaEntity, SwitchEntity):
"""Representation of a Balboa switch entity."""
def __init__(self, spa: SpaClient) -> None:
"""Initialize a Balboa switch entity."""
super().__init__(spa, "filter_cycle_2_enabled")
self._attr_entity_category = EntityCategory.CONFIG
self._attr_translation_key = "filter_cycle_2_enabled"
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._client.filter_cycle_2_enabled
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
await self._client.configure_filter_cycle(2, enabled=True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
await self._client.configure_filter_cycle(2, enabled=False)
+56
View File
@@ -0,0 +1,56 @@
"""Support for Balboa times."""
from __future__ import annotations
from datetime import time
import itertools
from typing import Any
from pybalboa import SpaClient
from homeassistant.components.time import TimeEntity
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BalboaConfigEntry
from .entity import BalboaEntity
FILTER_CYCLE = "filter_cycle_"
async def async_setup_entry(
hass: HomeAssistant,
entry: BalboaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the spa's times."""
spa = entry.runtime_data
async_add_entities(
BalboaTimeEntity(spa, index, period)
for index, period in itertools.product((1, 2), ("start", "end"))
)
class BalboaTimeEntity(BalboaEntity, TimeEntity):
"""Representation of a Balboa time entity."""
entity_category = EntityCategory.CONFIG
def __init__(self, spa: SpaClient, index: int, period: str) -> None:
"""Initialize a Balboa time entity."""
super().__init__(spa, f"{FILTER_CYCLE}{index}_{period}")
self.index = index
self.period = period
self._attr_translation_key = f"{FILTER_CYCLE}{period}"
self._attr_translation_placeholders = {"index": str(index)}
@property
def native_value(self) -> time | None:
"""Return the value reported by the time."""
return getattr(self._client, f"{FILTER_CYCLE}{self.index}_{self.period}")
async def async_set_value(self, value: time) -> None:
"""Change the time."""
args: dict[str, Any] = {self.period: value}
await self._client.configure_filter_cycle(self.index, **args)
@@ -4,12 +4,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any
from homeassistant.components.event import DOMAIN as EVENT_DOMAIN
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import BangOlufsenConfigEntry
from .const import DOMAIN
from .const import DEVICE_BUTTONS, DOMAIN
async def async_get_config_entry_diagnostics(
@@ -25,8 +26,9 @@ async def async_get_config_entry_diagnostics(
if TYPE_CHECKING:
assert config_entry.unique_id
# Add media_player entity's state
entity_registry = er.async_get(hass)
# Add media_player entity's state
if entity_id := entity_registry.async_get_entity_id(
MEDIA_PLAYER_DOMAIN, DOMAIN, config_entry.unique_id
):
@@ -37,4 +39,16 @@ async def async_get_config_entry_diagnostics(
state_dict.pop("context")
data["media_player"] = state_dict
# Add button Event entity states (if enabled)
for device_button in DEVICE_BUTTONS:
if entity_id := entity_registry.async_get_entity_id(
EVENT_DOMAIN, DOMAIN, f"{config_entry.unique_id}_{device_button}"
):
if state := hass.states.get(entity_id):
state_dict = dict(state.as_dict())
# Remove context as it is not relevant
state_dict.pop("context")
data[f"{device_button}_event"] = state_dict
return data
@@ -5,14 +5,14 @@
"title": "Manual YAML fix required for Bayesian"
},
"no_prob_given_false": {
"description": "In the Bayesian integration `prob_given_false` is now a required configuration variable as there was no mathematical rationale for the previous default value. Please add this to your `configuration.yml` for `bayesian/{entity}`. These observations will be ignored until you do.",
"description": "In the Bayesian integration `prob_given_false` is now a required configuration variable as there was no mathematical rationale for the previous default value. Please add this to your `configuration.yaml` for `bayesian/{entity}`. These observations will be ignored until you do.",
"title": "Manual YAML addition required for Bayesian"
}
},
"services": {
"reload": {
"name": "[%key:common::action::reload%]",
"description": "Reloads bayesian sensors from the YAML-configuration."
"description": "Reloads Bayesian sensors from the YAML-configuration."
}
}
}
@@ -16,11 +16,11 @@
"quality_scale": "internal",
"requirements": [
"bleak==0.22.3",
"bleak-retry-connector==3.8.1",
"bleak-retry-connector==3.9.0",
"bluetooth-adapters==0.21.4",
"bluetooth-auto-recovery==1.4.2",
"bluetooth-auto-recovery==1.4.4",
"bluetooth-data-tools==1.23.4",
"dbus-fast==2.33.0",
"habluetooth==3.21.1"
"habluetooth==3.22.1"
]
}
+7
View File
@@ -91,6 +91,13 @@ BUTTONS: tuple[BondButtonEntityDescription, ...] = (
mutually_exclusive=Action.SET_BRIGHTNESS,
argument=None,
),
BondButtonEntityDescription(
key=Action.TOGGLE_LIGHT_TEMP,
name="Toggle Light Temperature",
translation_key="toggle_light_temp",
mutually_exclusive=None, # No mutually exclusive action
argument=None,
),
BondButtonEntityDescription(
key=Action.START_UP_LIGHT_DIMMER,
name="Start Up Light Dimmer",
+7 -7
View File
@@ -31,7 +31,7 @@
"services": {
"set_fan_speed_tracked_state": {
"name": "Set fan speed tracked state",
"description": "Sets the tracked fan speed for a bond fan.",
"description": "Sets the tracked fan speed for a Bond fan.",
"fields": {
"entity_id": {
"name": "Entity",
@@ -45,7 +45,7 @@
},
"set_switch_power_tracked_state": {
"name": "Set switch power tracked state",
"description": "Sets the tracked power state of a bond switch.",
"description": "Sets the tracked power state of a Bond switch.",
"fields": {
"entity_id": {
"name": "Entity",
@@ -59,7 +59,7 @@
},
"set_light_power_tracked_state": {
"name": "Set light power tracked state",
"description": "Sets the tracked power state of a bond light.",
"description": "Sets the tracked power state of a Bond light.",
"fields": {
"entity_id": {
"name": "Entity",
@@ -73,7 +73,7 @@
},
"set_light_brightness_tracked_state": {
"name": "Set light brightness tracked state",
"description": "Sets the tracked brightness state of a bond light.",
"description": "Sets the tracked brightness state of a Bond light.",
"fields": {
"entity_id": {
"name": "Entity",
@@ -87,15 +87,15 @@
},
"start_increasing_brightness": {
"name": "Start increasing brightness",
"description": "Start increasing the brightness of the light. (deprecated)."
"description": "Starts increasing the brightness of the light (deprecated)."
},
"start_decreasing_brightness": {
"name": "Start decreasing brightness",
"description": "Start decreasing the brightness of the light. (deprecated)."
"description": "Starts decreasing the brightness of the light (deprecated)."
},
"stop": {
"name": "[%key:common::action::stop%]",
"description": "Stop any in-progress action and empty the queue. (deprecated)."
"description": "Stops any in-progress action and empty the queue (deprecated)."
}
}
}
@@ -7,5 +7,6 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["bring_api"],
"quality_scale": "platinum",
"requirements": ["bring-api==1.0.2"]
}
@@ -10,9 +10,9 @@ rules:
config-flow: done
dependency-transparency: done
docs-actions: done
docs-high-level-description: todo
docs-installation-instructions: todo
docs-removal-instructions: todo
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: The integration registers no events
@@ -26,8 +26,10 @@ rules:
# Silver
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters: todo
docs-installation-parameters: todo
docs-configuration-parameters:
status: exempt
comment: Integration has no configuration parameters
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable:
@@ -46,13 +48,15 @@ rules:
discovery:
status: exempt
comment: Integration is a service and has no devices.
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
docs-data-update: done
docs-examples: done
docs-known-limitations: done
docs-supported-devices:
status: exempt
comment: Integration is a service and has no devices.
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: done
entity-category: done
entity-device-class: done
+3 -3
View File
@@ -17,13 +17,13 @@ class BroadlinkEntity(Entity):
self._device = device
self._coordinator = device.update_manager.coordinator
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Call when the entity is added to hass."""
self.async_on_remove(self._coordinator.async_add_listener(self._recv_data))
if self._coordinator.data:
self._update_state(self._coordinator.data)
async def async_update(self):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self._coordinator.async_request_refresh()
@@ -49,7 +49,7 @@ class BroadlinkEntity(Entity):
"""
@property
def available(self):
def available(self) -> bool:
"""Return True if the entity is available."""
return self._device.available
+1 -1
View File
@@ -30,7 +30,7 @@
"message": "Can't set preset mode to {preset_mode} when HVAC mode is not set to auto"
},
"set_data_error": {
"message": "An error occurred while sending the data to the BSBLAN device"
"message": "An error occurred while sending the data to the BSB-Lan device"
},
"set_temperature_error": {
"message": "An error occurred while setting the temperature"
@@ -169,6 +169,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
translation_key="windazimuth",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key="pressure",
@@ -530,30 +531,35 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
translation_key="windazimuth_1d",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key="windazimuth_2d",
translation_key="windazimuth_2d",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key="windazimuth_3d",
translation_key="windazimuth_3d",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key="windazimuth_4d",
translation_key="windazimuth_4d",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key="windazimuth_5d",
translation_key="windazimuth_5d",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
),
SensorEntityDescription(
key="condition_1d",
+25 -1
View File
@@ -6,6 +6,7 @@ import asyncio
from collections.abc import Awaitable, Callable
from datetime import datetime, timedelta
from enum import Enum
import logging
from typing import cast
from hass_nabucasa import Cloud
@@ -19,6 +20,7 @@ from homeassistant.const import (
CONF_NAME,
CONF_REGION,
EVENT_HOMEASSISTANT_STOP,
FORMAT_DATETIME,
Platform,
)
from homeassistant.core import Event, HassJob, HomeAssistant, ServiceCall, callback
@@ -33,7 +35,7 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.util.signal_type import SignalType
# Pre-import backup to avoid it being imported
@@ -62,11 +64,13 @@ from .const import (
CONF_THINGTALK_SERVER,
CONF_USER_POOL_ID,
DATA_CLOUD,
DATA_CLOUD_LOG_HANDLER,
DATA_PLATFORMS_SETUP,
DOMAIN,
MODE_DEV,
MODE_PROD,
)
from .helpers import FixedSizeQueueLogHandler
from .prefs import CloudPreferences
from .repairs import async_manage_legacy_subscription_issue
from .subscription import async_subscription_info
@@ -245,6 +249,8 @@ def async_remote_ui_url(hass: HomeAssistant) -> str:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Initialize the Home Assistant cloud."""
log_handler = hass.data[DATA_CLOUD_LOG_HANDLER] = await _setup_log_handler(hass)
# Process configs
if DOMAIN in config:
kwargs = dict(config[DOMAIN])
@@ -267,6 +273,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def _shutdown(event: Event) -> None:
"""Shutdown event."""
await cloud.stop()
logging.root.removeHandler(log_handler)
del hass.data[DATA_CLOUD_LOG_HANDLER]
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
@@ -405,3 +413,19 @@ def _setup_services(hass: HomeAssistant, prefs: CloudPreferences) -> None:
async_register_admin_service(
hass, DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler
)
async def _setup_log_handler(hass: HomeAssistant) -> FixedSizeQueueLogHandler:
fmt = (
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
)
handler = FixedSizeQueueLogHandler()
handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
integration = await async_get_integration(hass, DOMAIN)
loggers: set[str] = {integration.pkg_path, *(integration.loggers or [])}
for logger_name in loggers:
logging.getLogger(logger_name).addHandler(handler)
return handler
+41 -33
View File
@@ -3,17 +3,20 @@
from __future__ import annotations
import asyncio
import base64
from collections.abc import AsyncIterator, Callable, Coroutine, Mapping
import hashlib
import logging
import random
from typing import Any, Literal
from typing import Any
from aiohttp import ClientError
from hass_nabucasa import Cloud, CloudError
from hass_nabucasa.api import CloudApiNonRetryableError
from hass_nabucasa.cloud_api import async_files_delete_file, async_files_list
from hass_nabucasa.cloud_api import (
FilesHandlerListEntry,
async_files_delete_file,
async_files_list,
)
from hass_nabucasa.files import FilesError, StorageType, calculate_b64md5
from homeassistant.components.backup import AgentBackup, BackupAgent, BackupAgentError
from homeassistant.core import HomeAssistant, callback
@@ -24,20 +27,11 @@ from .client import CloudClient
from .const import DATA_CLOUD, DOMAIN, EVENT_CLOUD_EVENT
_LOGGER = logging.getLogger(__name__)
_STORAGE_BACKUP: Literal["backup"] = "backup"
_RETRY_LIMIT = 5
_RETRY_SECONDS_MIN = 60
_RETRY_SECONDS_MAX = 600
async def _b64md5(stream: AsyncIterator[bytes]) -> str:
"""Calculate the MD5 hash of a file."""
file_hash = hashlib.md5()
async for chunk in stream:
file_hash.update(chunk)
return base64.b64encode(file_hash.digest()).decode()
async def async_get_backup_agents(
hass: HomeAssistant,
**kwargs: Any,
@@ -86,11 +80,6 @@ class CloudBackupAgent(BackupAgent):
self._cloud = cloud
self._hass = hass
@callback
def _get_backup_filename(self) -> str:
"""Return the backup filename."""
return f"{self._cloud.client.prefs.instance_id}.tar"
async def async_download_backup(
self,
backup_id: str,
@@ -101,13 +90,13 @@ class CloudBackupAgent(BackupAgent):
:param backup_id: The ID of the backup that was returned in async_list_backups.
:return: An async iterator that yields bytes.
"""
if not await self.async_get_backup(backup_id):
if not (backup := await self._async_get_backup(backup_id)):
raise BackupAgentError("Backup not found")
try:
content = await self._cloud.files.download(
storage_type=_STORAGE_BACKUP,
filename=self._get_backup_filename(),
storage_type=StorageType.BACKUP,
filename=backup["Key"],
)
except CloudError as err:
raise BackupAgentError(f"Failed to download backup: {err}") from err
@@ -129,16 +118,19 @@ class CloudBackupAgent(BackupAgent):
if not backup.protected:
raise BackupAgentError("Cloud backups must be protected")
base64md5hash = await _b64md5(await open_stream())
filename = self._get_backup_filename()
metadata = backup.as_dict()
size = backup.size
try:
base64md5hash = await calculate_b64md5(open_stream, size)
except FilesError as err:
raise BackupAgentError(err) from err
filename = f"{self._cloud.client.prefs.instance_id}.tar"
metadata = backup.as_dict()
tries = 1
while tries <= _RETRY_LIMIT:
try:
await self._cloud.files.upload(
storage_type=_STORAGE_BACKUP,
storage_type=StorageType.BACKUP,
open_stream=open_stream,
filename=filename,
base64md5hash=base64md5hash,
@@ -179,27 +171,34 @@ class CloudBackupAgent(BackupAgent):
:param backup_id: The ID of the backup that was returned in async_list_backups.
"""
if not await self.async_get_backup(backup_id):
if not (backup := await self._async_get_backup(backup_id)):
return
try:
await async_files_delete_file(
self._cloud,
storage_type=_STORAGE_BACKUP,
filename=self._get_backup_filename(),
storage_type=StorageType.BACKUP,
filename=backup["Key"],
)
except (ClientError, CloudError) as err:
raise BackupAgentError("Failed to delete backup") from err
async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
"""List backups."""
backups = await self._async_list_backups()
return [AgentBackup.from_dict(backup["Metadata"]) for backup in backups]
async def _async_list_backups(self) -> list[FilesHandlerListEntry]:
"""List backups."""
try:
backups = await async_files_list(self._cloud, storage_type=_STORAGE_BACKUP)
_LOGGER.debug("Cloud backups: %s", backups)
backups = await async_files_list(
self._cloud, storage_type=StorageType.BACKUP
)
except (ClientError, CloudError) as err:
raise BackupAgentError("Failed to list backups") from err
return [AgentBackup.from_dict(backup["Metadata"]) for backup in backups]
_LOGGER.debug("Cloud backups: %s", backups)
return backups
async def async_get_backup(
self,
@@ -207,10 +206,19 @@ class CloudBackupAgent(BackupAgent):
**kwargs: Any,
) -> AgentBackup | None:
"""Return a backup."""
backups = await self.async_list_backups()
if not (backup := await self._async_get_backup(backup_id)):
return None
return AgentBackup.from_dict(backup["Metadata"])
async def _async_get_backup(
self,
backup_id: str,
) -> FilesHandlerListEntry | None:
"""Return a backup."""
backups = await self._async_list_backups()
for backup in backups:
if backup.backup_id == backup_id:
if backup["Metadata"]["backup_id"] == backup_id:
return backup
return None
+2
View File
@@ -12,12 +12,14 @@ if TYPE_CHECKING:
from hass_nabucasa import Cloud
from .client import CloudClient
from .helpers import FixedSizeQueueLogHandler
DOMAIN = "cloud"
DATA_CLOUD: HassKey[Cloud[CloudClient]] = HassKey(DOMAIN)
DATA_PLATFORMS_SETUP: HassKey[dict[str, asyncio.Event]] = HassKey(
"cloud_platforms_setup"
)
DATA_CLOUD_LOG_HANDLER: HassKey[FixedSizeQueueLogHandler] = HassKey("cloud_log_handler")
EVENT_CLOUD_EVENT = "cloud_event"
REQUEST_TIMEOUT = 10
+31
View File
@@ -0,0 +1,31 @@
"""Helpers for the cloud component."""
from collections import deque
import logging
from homeassistant.core import HomeAssistant
class FixedSizeQueueLogHandler(logging.Handler):
"""Log handler to store messages, with auto rotation."""
MAX_RECORDS = 500
def __init__(self) -> None:
"""Initialize a new LogHandler."""
super().__init__()
self._records: deque[logging.LogRecord] = deque(maxlen=self.MAX_RECORDS)
def emit(self, record: logging.LogRecord) -> None:
"""Store log message."""
self._records.append(record)
async def get_logs(self, hass: HomeAssistant) -> list[str]:
"""Get stored logs."""
def _get_logs() -> list[str]:
# copy the queue since it can mutate while iterating
records = self._records.copy()
return [self.format(record) for record in records]
return await hass.async_add_executor_job(_get_logs)
+45 -9
View File
@@ -8,14 +8,15 @@ from contextlib import suppress
import dataclasses
from functools import wraps
from http import HTTPStatus
import json
import logging
import time
from typing import Any, Concatenate
from typing import Any, Concatenate, cast
import aiohttp
from aiohttp import web
import attr
from hass_nabucasa import Cloud, auth, thingtalk
from hass_nabucasa import AlreadyConnectedError, Cloud, auth, thingtalk
from hass_nabucasa.const import STATE_DISCONNECTED
from hass_nabucasa.voice import TTS_VOICES
import voluptuous as vol
@@ -43,6 +44,7 @@ from .assist_pipeline import async_create_cloud_pipeline
from .client import CloudClient
from .const import (
DATA_CLOUD,
DATA_CLOUD_LOG_HANDLER,
EVENT_CLOUD_EVENT,
LOGIN_MFA_TIMEOUT,
PREF_ALEXA_REPORT_STATE,
@@ -63,7 +65,9 @@ from .subscription import async_subscription_info
_LOGGER = logging.getLogger(__name__)
_CLOUD_ERRORS: dict[type[Exception], tuple[HTTPStatus, str]] = {
_CLOUD_ERRORS: dict[
type[Exception], tuple[HTTPStatus, Callable[[Exception], str] | str]
] = {
TimeoutError: (
HTTPStatus.BAD_GATEWAY,
"Unable to reach the Home Assistant cloud.",
@@ -132,6 +136,10 @@ def async_setup(hass: HomeAssistant) -> None:
HTTPStatus.BAD_REQUEST,
"Multi-factor authentication expired, or not started. Please try again.",
),
AlreadyConnectedError: (
HTTPStatus.CONFLICT,
lambda x: json.dumps(cast(AlreadyConnectedError, x).details),
),
}
)
@@ -196,7 +204,11 @@ def _process_cloud_exception(exc: Exception, where: str) -> tuple[HTTPStatus, st
for err, value_info in _CLOUD_ERRORS.items():
if isinstance(exc, err):
err_info = value_info
status, content = value_info
err_info = (
status,
content if isinstance(content, str) else content(exc),
)
break
if err_info is None:
@@ -239,6 +251,7 @@ class CloudLoginView(HomeAssistantView):
vol.All(
{
vol.Required("email"): str,
vol.Optional("check_connection", default=False): bool,
vol.Exclusive("password", "login"): str,
vol.Exclusive("code", "login"): str,
},
@@ -257,7 +270,11 @@ class CloudLoginView(HomeAssistantView):
code = data.get("code")
if email and password:
await cloud.login(email, password)
await cloud.login(
email,
password,
check_connection=data["check_connection"],
)
else:
if (
@@ -269,7 +286,12 @@ class CloudLoginView(HomeAssistantView):
# Voluptuous should ensure that code is not None because password is
assert code is not None
await cloud.login_verify_totp(email, code, self._mfa_tokens)
await cloud.login_verify_totp(
email,
code,
self._mfa_tokens,
check_connection=data["check_connection"],
)
self._mfa_tokens = {}
self._mfa_tokens_set_time = 0
@@ -397,8 +419,11 @@ class DownloadSupportPackageView(HomeAssistantView):
url = "/api/cloud/support_package"
name = "api:cloud:support_package"
def _generate_markdown(
self, hass_info: dict[str, Any], domains_info: dict[str, dict[str, str]]
async def _generate_markdown(
self,
hass: HomeAssistant,
hass_info: dict[str, Any],
domains_info: dict[str, dict[str, str]],
) -> str:
def get_domain_table_markdown(domain_info: dict[str, Any]) -> str:
if len(domain_info) == 0:
@@ -424,6 +449,17 @@ class DownloadSupportPackageView(HomeAssistantView):
"</details>\n\n"
)
log_handler = hass.data[DATA_CLOUD_LOG_HANDLER]
logs = "\n".join(await log_handler.get_logs(hass))
markdown += (
"## Full logs\n\n"
"<details><summary>Logs</summary>\n\n"
"```logs\n"
f"{logs}\n"
"```\n\n"
"</details>\n"
)
return markdown
async def get(self, request: web.Request) -> web.Response:
@@ -433,7 +469,7 @@ class DownloadSupportPackageView(HomeAssistantView):
domain_health = await get_system_health_info(hass)
hass_info = domain_health.pop("homeassistant", {})
markdown = self._generate_markdown(hass_info, domain_health)
markdown = await self._generate_markdown(hass, hass_info, domain_health)
return web.Response(
body=markdown,
+2 -2
View File
@@ -12,7 +12,7 @@
"documentation": "https://www.home-assistant.io/integrations/cloud",
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["hass_nabucasa"],
"requirements": ["hass-nabucasa==0.89.0"],
"loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==0.92.0"],
"single_config_entry": true
}
@@ -2,10 +2,12 @@
from __future__ import annotations
from collections.abc import Callable
import logging
import re
from typing import Literal
from hassil.recognize import RecognizeResult
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
@@ -241,7 +243,10 @@ async def async_handle_sentence_triggers(
async def async_handle_intents(
hass: HomeAssistant, user_input: ConversationInput
hass: HomeAssistant,
user_input: ConversationInput,
*,
intent_filter: Callable[[RecognizeResult], bool] | None = None,
) -> intent.IntentResponse | None:
"""Try to match input against registered intents and return response.
@@ -250,7 +255,9 @@ async def async_handle_intents(
default_agent = async_get_agent(hass)
assert isinstance(default_agent, DefaultAgent)
return await default_agent.async_handle_intents(user_input)
return await default_agent.async_handle_intents(
user_input, intent_filter=intent_filter
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@@ -53,6 +53,7 @@ from homeassistant.helpers import (
)
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_state_added_domain
from homeassistant.util import language as language_util
from homeassistant.util.json import JsonObjectType, json_loads_object
from .chat_log import AssistantContent, async_get_chat_log
@@ -184,21 +185,6 @@ class IntentCache:
self.cache.clear()
def _get_language_variations(language: str) -> Iterable[str]:
"""Generate language codes with and without region."""
yield language
parts = re.split(r"([-_])", language)
if len(parts) == 3:
lang, sep, region = parts
if sep == "_":
# en_US -> en-US
yield f"{lang}-{region}"
# en-US -> en
yield lang
async def async_setup_default_agent(
hass: core.HomeAssistant,
entity_component: EntityComponent[ConversationEntity],
@@ -914,26 +900,20 @@ class DefaultAgent(ConversationEntity):
def _load_intents(self, language: str) -> LanguageIntents | None:
"""Load all intents for language (run inside executor)."""
intents_dict: dict[str, Any] = {}
language_variant: str | None = None
supported_langs = set(get_languages())
# Choose a language variant upfront and commit to it for custom
# sentences, etc.
all_language_variants = {lang.lower(): lang for lang in supported_langs}
lang_matches = language_util.matches(language, supported_langs)
# en-US, en_US, en, ...
for maybe_variant in _get_language_variations(language):
matching_variant = all_language_variants.get(maybe_variant.lower())
if matching_variant:
language_variant = matching_variant
break
if not language_variant:
if not lang_matches:
_LOGGER.warning(
"Unable to find supported language variant for %s", language
)
return None
language_variant = lang_matches[0]
# Load intents for this language variant
lang_variant_intents = get_intents(language_variant, json_load=json_load)
@@ -1329,6 +1309,8 @@ class DefaultAgent(ConversationEntity):
async def async_handle_intents(
self,
user_input: ConversationInput,
*,
intent_filter: Callable[[RecognizeResult], bool] | None = None,
) -> intent.IntentResponse | None:
"""Try to match sentence against registered intents and return response.
@@ -1336,7 +1318,9 @@ class DefaultAgent(ConversationEntity):
Returns None if no match or a matching error occurred.
"""
result = await self.async_recognize_intent(user_input, strict_intents_only=True)
if not isinstance(result, RecognizeResult):
if not isinstance(result, RecognizeResult) or (
intent_filter is not None and intent_filter(result)
):
# No error message on failed match
return None
@@ -23,14 +23,14 @@
}
},
"error": {
"discovery_error": "Failed to discover a Denon AVR Network Receiver"
"discovery_error": "Failed to discover a Denon AVR network receiver"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "Failed to connect, please try again, disconnecting mains power and ethernet cables and reconnecting them may help",
"not_denonavr_manufacturer": "Not a Denon AVR Network Receiver, discovered manufacturer did not match",
"not_denonavr_missing": "Not a Denon AVR Network Receiver, discovery information not complete"
"cannot_connect": "Failed to connect, please try again, disconnecting mains power and Ethernet cables and reconnecting them may help",
"not_denonavr_manufacturer": "Not a Denon AVR network receiver, discovered manufacturer did not match",
"not_denonavr_missing": "Not a Denon AVR network receiver, discovery information not complete"
}
},
"options": {
@@ -64,7 +64,7 @@
"fields": {
"dynamic_eq": {
"name": "Dynamic equalizer",
"description": "True/false for enable/disable."
"description": "Whether DynamicEQ should be enabled or disabled."
}
}
},
+1 -1
View File
@@ -59,7 +59,7 @@ class DSMRConnection:
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
if dsmr_version == "5B":
self._equipment_identifier = obis_ref.BELGIUM_EQUIPMENT_IDENTIFIER
if dsmr_version == "5L":
if dsmr_version in ("5L", "5EONHU"):
self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER
if dsmr_version == "Q3D":
self._equipment_identifier = obis_ref.Q3D_EQUIPMENT_IDENTIFIER
+1 -1
View File
@@ -28,7 +28,7 @@ DEVICE_NAME_GAS = "Gas Meter"
DEVICE_NAME_WATER = "Water Meter"
DEVICE_NAME_HEAT = "Heat Meter"
DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"}
DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D", "5EONHU"}
DSMR_PROTOCOL = "dsmr_protocol"
RFXTRX_DSMR_PROTOCOL = "rfxtrx_dsmr_protocol"
+152 -8
View File
@@ -115,7 +115,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_active_tariff",
translation_key="electricity_active_tariff",
obis_reference="ELECTRICITY_ACTIVE_TARIFF",
dsmr_versions={"2.2", "4", "5", "5B", "5L"},
dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"},
device_class=SensorDeviceClass.ENUM,
options=["low", "normal"],
),
@@ -123,7 +123,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_used_tariff_1",
translation_key="electricity_used_tariff_1",
obis_reference="ELECTRICITY_USED_TARIFF_1",
dsmr_versions={"2.2", "4", "5", "5B", "5L"},
dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -131,7 +131,25 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_used_tariff_2",
translation_key="electricity_used_tariff_2",
obis_reference="ELECTRICITY_USED_TARIFF_2",
dsmr_versions={"2.2", "4", "5", "5B", "5L"},
dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
DSMRSensorEntityDescription(
key="electricity_used_tariff_3",
translation_key="electricity_used_tariff_3",
obis_reference="ELECTRICITY_USED_TARIFF_3",
dsmr_versions={"5EONHU"},
force_update=True,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
DSMRSensorEntityDescription(
key="electricity_used_tariff_4",
translation_key="electricity_used_tariff_4",
obis_reference="ELECTRICITY_USED_TARIFF_4",
dsmr_versions={"5EONHU"},
force_update=True,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -139,7 +157,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_delivered_tariff_1",
translation_key="electricity_delivered_tariff_1",
obis_reference="ELECTRICITY_DELIVERED_TARIFF_1",
dsmr_versions={"2.2", "4", "5", "5B", "5L"},
dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -147,7 +165,25 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_delivered_tariff_2",
translation_key="electricity_delivered_tariff_2",
obis_reference="ELECTRICITY_DELIVERED_TARIFF_2",
dsmr_versions={"2.2", "4", "5", "5B", "5L"},
dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
DSMRSensorEntityDescription(
key="electricity_delivered_tariff_3",
translation_key="electricity_delivered_tariff_3",
obis_reference="ELECTRICITY_DELIVERED_TARIFF_3",
dsmr_versions={"5EONHU"},
force_update=True,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
DSMRSensorEntityDescription(
key="electricity_delivered_tariff_4",
translation_key="electricity_delivered_tariff_4",
obis_reference="ELECTRICITY_DELIVERED_TARIFF_4",
dsmr_versions={"5EONHU"},
force_update=True,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -341,7 +377,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_imported_total",
translation_key="electricity_imported_total",
obis_reference="ELECTRICITY_IMPORTED_TOTAL",
dsmr_versions={"5L", "5S", "Q3D"},
dsmr_versions={"5L", "5S", "Q3D", "5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -349,7 +385,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
key="electricity_exported_total",
translation_key="electricity_exported_total",
obis_reference="ELECTRICITY_EXPORTED_TOTAL",
dsmr_versions={"5L", "5S", "Q3D"},
dsmr_versions={"5L", "5S", "Q3D", "5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -387,6 +423,113 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.GAS,
state_class=SensorStateClass.TOTAL_INCREASING,
),
DSMRSensorEntityDescription(
key="actual_threshold_electricity",
translation_key="actual_threshold_electricity",
obis_reference="ACTUAL_TRESHOLD_ELECTRICITY", # Misspelled in external tool
dsmr_versions={"5EONHU"},
device_class=SensorDeviceClass.POWER,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="eon_hu_electricity_combined",
translation_key="electricity_combined",
obis_reference="EON_HU_ELECTRICITY_COMBINED",
dsmr_versions={"5EONHU"},
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
DSMRSensorEntityDescription(
key="eon_hu_instantaneous_power_factor_total",
translation_key="instantaneous_power_factor_total",
obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_TOTAL",
dsmr_versions={"5EONHU"},
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="eon_hu_instantaneous_power_factor_l1",
translation_key="instantaneous_power_factor_l1",
obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_L1",
dsmr_versions={"5EONHU"},
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="eon_hu_instantaneous_power_factor_l2",
translation_key="instantaneous_power_factor_l2",
obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_L2",
dsmr_versions={"5EONHU"},
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="eon_hu_instantaneous_power_factor_l3",
translation_key="instantaneous_power_factor_l3",
obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_L3",
dsmr_versions={"5EONHU"},
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="eon_hu_frequency",
translation_key="frequency",
obis_reference="EON_HU_FREQUENCY",
dsmr_versions={"5EONHU"},
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.FREQUENCY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="fuse_threshold_l1",
translation_key="fuse_threshold_l1",
obis_reference="FUSE_THRESHOLD_L1",
dsmr_versions={"5EONHU"},
device_class=SensorDeviceClass.CURRENT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="fuse_threshold_l2",
translation_key="fuse_threshold_l2",
obis_reference="FUSE_THRESHOLD_L2",
dsmr_versions={"5EONHU"},
device_class=SensorDeviceClass.CURRENT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="fuse_threshold_l3",
translation_key="fuse_threshold_l3",
obis_reference="FUSE_THRESHOLD_L3",
dsmr_versions={"5EONHU"},
device_class=SensorDeviceClass.CURRENT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
key="text_message",
translation_key="text_message",
obis_reference="TEXT_MESSAGE",
dsmr_versions={"5EONHU"},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
)
SENSORS_MBUS_DEVICE_TYPE: dict[int, tuple[DSMRSensorEntityDescription, ...]] = {
@@ -844,8 +987,9 @@ class DSMREntity(SensorEntity):
def translate_tariff(value: str, dsmr_version: str) -> str | None:
"""Convert 2/1 to normal/low depending on DSMR version."""
# DSMR V5B: Note: In Belgium values are swapped:
# DSMR V5EONHU: Note: In EON HUngary values are swapped:
# Rate code 2 is used for low rate and rate code 1 is used for normal rate.
if dsmr_version == "5B":
if dsmr_version in ("5B", "5EONHU"):
if value == "0001":
value = "0002"
elif value == "0002":
@@ -61,6 +61,12 @@
"electricity_delivered_tariff_2": {
"name": "Energy production (tarif 2)"
},
"electricity_delivered_tariff_3": {
"name": "Energy production (tarif 3)"
},
"electricity_delivered_tariff_4": {
"name": "Energy production (tarif 4)"
},
"electricity_exported_total": {
"name": "Energy production (total)"
},
@@ -73,6 +79,12 @@
"electricity_used_tariff_2": {
"name": "Energy consumption (tarif 2)"
},
"electricity_used_tariff_3": {
"name": "Energy consumption (tarif 3)"
},
"electricity_used_tariff_4": {
"name": "Energy consumption (tarif 4)"
},
"gas_meter_reading": {
"name": "Gas consumption"
},
@@ -150,6 +162,57 @@
},
"water_meter_reading": {
"name": "Water consumption"
},
"actual_threshold_electricity": {
"name": "Actual threshold electricity"
},
"electricity_reactive_imported_total": {
"name": "Imported reactive electricity (total)"
},
"electricity_reactive_exported_total": {
"name": "Exported reactive electricity (total)"
},
"electricity_reactive_total_q1": {
"name": "Reactive electricity (Q1)"
},
"electricity_reactive_total_q2": {
"name": "Reactive electricity (Q2)"
},
"electricity_reactive_total_q3": {
"name": "Reactive electricity (Q3)"
},
"electricity_reactive_total_q4": {
"name": "Reactive electricity (Q4)"
},
"electricity_combined": {
"name": "Energy combined"
},
"instantaneous_power_factor_total": {
"name": "Instantaneous power factor (total)"
},
"instantaneous_power_factor_l1": {
"name": "Instantaneous power factor L1"
},
"instantaneous_power_factor_l2": {
"name": "Instantaneous power factor L2"
},
"instantaneous_power_factor_l3": {
"name": "Instantaneous power factor L3"
},
"frequency": {
"name": "Frequency"
},
"fuse_threshold_l1": {
"name": "Fuse threshold on L1"
},
"fuse_threshold_l2": {
"name": "Fuse threshold on L2"
},
"fuse_threshold_l3": {
"name": "Fuse threshold on L3"
},
"text_message": {
"name": "Text message"
}
}
},
@@ -60,12 +60,12 @@
"description": "Requests gas prices from easyEnergy.",
"fields": {
"config_entry": {
"name": "Config Entry",
"name": "Config entry",
"description": "The configuration entry to use for this action."
},
"incl_vat": {
"name": "VAT Included",
"description": "Include or exclude VAT in the prices, default is true."
"name": "VAT included",
"description": "Whether the prices should include VAT."
},
"start": {
"name": "Start",
+27 -9
View File
@@ -3,11 +3,13 @@
from __future__ import annotations
from datetime import timedelta
from typing import Any
from homeassistant.components.humidifier import (
DEFAULT_MAX_HUMIDITY,
DEFAULT_MIN_HUMIDITY,
MODE_AUTO,
HumidifierAction,
HumidifierDeviceClass,
HumidifierEntity,
HumidifierEntityFeature,
@@ -41,6 +43,12 @@ async def async_setup_entry(
async_add_entities(entities, True)
ECOBEE_HUMIDIFIER_ACTION_TO_HASS = {
"humidifier": HumidifierAction.HUMIDIFYING,
"dehumidifier": HumidifierAction.DRYING,
}
class EcobeeHumidifier(HumidifierEntity):
"""A humidifier class for an ecobee thermostat with humidifier attached."""
@@ -52,7 +60,7 @@ class EcobeeHumidifier(HumidifierEntity):
_attr_has_entity_name = True
_attr_name = None
def __init__(self, data, thermostat_index):
def __init__(self, data, thermostat_index) -> None:
"""Initialize ecobee humidifier platform."""
self.data = data
self.thermostat_index = thermostat_index
@@ -80,11 +88,11 @@ class EcobeeHumidifier(HumidifierEntity):
)
@property
def available(self):
def available(self) -> bool:
"""Return if device is available."""
return self.thermostat["runtime"]["connected"]
async def async_update(self):
async def async_update(self) -> None:
"""Get the latest state from the thermostat."""
if self.update_without_throttle:
await self.data.update(no_throttle=True)
@@ -96,12 +104,20 @@ class EcobeeHumidifier(HumidifierEntity):
self._last_humidifier_on_mode = self.mode
@property
def is_on(self):
def action(self) -> HumidifierAction:
"""Return the current action."""
for status in self.thermostat["equipmentStatus"].split(","):
if status in ECOBEE_HUMIDIFIER_ACTION_TO_HASS:
return ECOBEE_HUMIDIFIER_ACTION_TO_HASS[status]
return HumidifierAction.IDLE if self.is_on else HumidifierAction.OFF
@property
def is_on(self) -> bool:
"""Return True if the humidifier is on."""
return self.mode != MODE_OFF
@property
def mode(self):
def mode(self) -> str:
"""Return the current mode, e.g., off, auto, manual."""
return self.thermostat["settings"]["humidifierMode"]
@@ -118,9 +134,11 @@ class EcobeeHumidifier(HumidifierEntity):
except KeyError:
return None
def set_mode(self, mode):
def set_mode(self, mode: str) -> None:
"""Set humidifier mode (auto, off, manual)."""
if mode.lower() not in (self.available_modes):
if self.available_modes is None:
raise NotImplementedError("Humidifier does not support modes.")
if mode.lower() not in self.available_modes:
raise ValueError(
f"Invalid mode value: {mode} Valid values are"
f" {', '.join(self.available_modes)}."
@@ -134,10 +152,10 @@ class EcobeeHumidifier(HumidifierEntity):
self.data.ecobee.set_humidity(self.thermostat_index, humidity)
self.update_without_throttle = True
def turn_off(self, **kwargs):
def turn_off(self, **kwargs: Any) -> None:
"""Set humidifier to off mode."""
self.set_mode(MODE_OFF)
def turn_on(self, **kwargs):
def turn_on(self, **kwargs: Any) -> None:
"""Set humidifier to on mode."""
self.set_mode(self._last_humidifier_on_mode)
+9 -4
View File
@@ -23,7 +23,7 @@ from homeassistant.components.climate import (
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.issue_registry import IssueSeverity, create_issue
from . import EconetConfigEntry
from .const import DOMAIN
@@ -35,8 +35,13 @@ ECONET_STATE_TO_HA = {
ThermostatOperationMode.OFF: HVACMode.OFF,
ThermostatOperationMode.AUTO: HVACMode.HEAT_COOL,
ThermostatOperationMode.FAN_ONLY: HVACMode.FAN_ONLY,
ThermostatOperationMode.EMERGENCY_HEAT: HVACMode.HEAT,
}
HA_STATE_TO_ECONET = {
value: key
for key, value in ECONET_STATE_TO_HA.items()
if key != ThermostatOperationMode.EMERGENCY_HEAT
}
HA_STATE_TO_ECONET = {value: key for key, value in ECONET_STATE_TO_HA.items()}
ECONET_FAN_STATE_TO_HA = {
ThermostatFanMode.AUTO: FAN_AUTO,
@@ -209,7 +214,7 @@ class EcoNetThermostat(EcoNetEntity[Thermostat], ClimateEntity):
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
async_create_issue(
create_issue(
self.hass,
DOMAIN,
"migrate_aux_heat",
@@ -223,7 +228,7 @@ class EcoNetThermostat(EcoNetEntity[Thermostat], ClimateEntity):
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
async_create_issue(
create_issue(
self.hass,
DOMAIN,
"migrate_aux_heat",
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/econet",
"iot_class": "cloud_push",
"loggers": ["paho_mqtt", "pyeconet"],
"requirements": ["pyeconet==0.1.27"]
"requirements": ["pyeconet==0.1.28"]
}
@@ -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.0.0"]
"requirements": ["py-sucks==0.9.10", "deebot-client==12.1.0"]
}
@@ -250,7 +250,7 @@
"message": "Params are required for the command: {command}"
},
"vacuum_raw_get_positions_not_supported": {
"message": "Getting the positions of the chargers and the device itself is not supported"
"message": "Retrieving the positions of the chargers and the device itself is not supported"
}
},
"selector": {
@@ -264,7 +264,7 @@
"services": {
"raw_get_positions": {
"name": "Get raw positions",
"description": "Get the raw response for the positions of the chargers and the device itself."
"description": "Retrieves a raw response containing the positions of the chargers and the device itself."
}
}
}
+3 -1
View File
@@ -65,7 +65,9 @@ ECOWITT_SENSORS_MAPPING: Final = {
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.DEGREE: SensorEntityDescription(
key="DEGREE", native_unit_of_measurement=DEGREE
key="DEGREE",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
EcoWittSensorTypes.WATT_METERS_SQUARED: SensorEntityDescription(
key="WATT_METERS_SQUARED",
@@ -40,12 +40,10 @@ async def async_setup_entry(
coordinator = entry.runtime_data
def async_setup_device_entities(
device_address: str | dict[str, EheimDigitalDevice],
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the climate entities for one or multiple devices."""
entities: list[EheimDigitalHeaterClimate] = []
if isinstance(device_address, str):
device_address = {device_address: coordinator.hub.devices[device_address]}
for device in device_address.values():
if isinstance(device, EheimDigitalHeater):
entities.append(EheimDigitalHeaterClimate(coordinator, device))
@@ -2,25 +2,25 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
from aiohttp import ClientError
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.hub import EheimDigitalHub
from eheimdigital.types import EheimDeviceType
from eheimdigital.types import EheimDeviceType, EheimDigitalClientError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER
type AsyncSetupDeviceEntitiesCallback = Callable[
[str | dict[str, EheimDigitalDevice]], None
]
type AsyncSetupDeviceEntitiesCallback = Callable[[dict[str, EheimDigitalDevice]], None]
type EheimDigitalConfigEntry = ConfigEntry[EheimDigitalUpdateCoordinator]
@@ -43,12 +43,14 @@ class EheimDigitalUpdateCoordinator(
name=DOMAIN,
update_interval=DEFAULT_SCAN_INTERVAL,
)
self.main_device_added_event = asyncio.Event()
self.hub = EheimDigitalHub(
host=self.config_entry.data[CONF_HOST],
session=async_get_clientsession(hass),
loop=hass.loop,
receive_callback=self._async_receive_callback,
device_found_callback=self._async_device_found,
main_device_added_event=self.main_device_added_event,
)
self.known_devices: set[str] = set()
self.platform_callbacks: set[AsyncSetupDeviceEntitiesCallback] = set()
@@ -70,14 +72,23 @@ class EheimDigitalUpdateCoordinator(
if device_address not in self.known_devices:
for platform_callback in self.platform_callbacks:
platform_callback(device_address)
platform_callback({device_address: self.hub.devices[device_address]})
async def _async_receive_callback(self) -> None:
self.async_set_updated_data(self.hub.devices)
async def _async_setup(self) -> None:
await self.hub.connect()
await self.hub.update()
try:
await self.hub.connect()
async with asyncio.timeout(2):
# This event gets triggered when the first message is received from
# the device, it contains the data necessary to create the main device.
# This removes the race condition where the main device is accessed
# before the response from the device is parsed.
await self.main_device_added_event.wait()
await self.hub.update()
except (TimeoutError, EheimDigitalClientError) as err:
raise ConfigEntryNotReady from err
async def _async_update_data(self) -> dict[str, EheimDigitalDevice]:
try:
@@ -38,12 +38,10 @@ async def async_setup_entry(
coordinator = entry.runtime_data
def async_setup_device_entities(
device_address: str | dict[str, EheimDigitalDevice],
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the light entities for one or multiple devices."""
entities: list[EheimDigitalClassicLEDControlLight] = []
if isinstance(device_address, str):
device_address = {device_address: coordinator.hub.devices[device_address]}
for device in device_address.values():
if isinstance(device, EheimDigitalClassicLEDControl):
for channel in range(2):
@@ -2,7 +2,7 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
@@ -28,11 +28,13 @@ async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if all(
config_entry.state is ConfigEntryState.NOT_LOADED
for config_entry in hass.config_entries.async_entries(DOMAIN)
if config_entry.entry_id != entry.entry_id
):
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
return True
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Remove a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
# Remove any remaining disabled or ignored entries
for _entry in hass.config_entries.async_entries(DOMAIN):
hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id))
@@ -13,6 +13,11 @@
"hop_power_savings": {
"default": "mdi:percent"
}
},
"select": {
"hop_selector": {
"default": "mdi:lightning-bolt"
}
}
}
}
@@ -7,5 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/electric_kiwi",
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["electrickiwi-api==0.9.14"]
}
@@ -0,0 +1,105 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
No custom actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
No custom actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: |
Does not subscribe to event explicitly.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
No actions
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: |
Has no options flow
docs-installation-parameters:
status: exempt
comment: |
Handled by OAuth flow (HA is only one with credentials, users cannot get them)
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: done
test-coverage: todo
# Gold
devices:
status: exempt
comment: |
Web services only
diagnostics: todo
discovery-update-info:
status: exempt
comment: |
Web services only
discovery:
status: exempt
comment: |
Web services only
docs-data-update: todo
docs-examples: done
docs-known-limitations: done
docs-supported-devices:
status: exempt
comment: |
No devices
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: done
dynamic-devices:
status: exempt
comment: |
No devices
entity-category: done
entity-device-class: done
entity-disabled-by-default:
status: exempt
comment: |
No unnecessary or noisy entities
entity-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow:
status: exempt
comment: |
Handled by OAuth
repair-issues:
status: exempt
comment: |
Does not have any repairs
stale-devices:
status: exempt
comment: |
Does not have devices
# Platinum
async-dependency: todo
inject-websession: todo
strict-typing: todo
@@ -13,6 +13,8 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTRIBUTION
from .coordinator import ElectricKiwiConfigEntry, ElectricKiwiHOPDataCoordinator
PARALLEL_UPDATES = 1
_LOGGER = logging.getLogger(__name__)
ATTR_EK_HOP_SELECT = "hop_select"
@@ -27,6 +27,8 @@ from .coordinator import (
ElectricKiwiHOPDataCoordinator,
)
PARALLEL_UPDATES = 0
ATTR_EK_HOP_START = "hop_power_start"
ATTR_EK_HOP_END = "hop_power_end"
ATTR_TOTAL_RUNNING_BALANCE = "total_running_balance"
@@ -498,7 +498,11 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN):
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle device found via zeroconf."""
host = discovery_info.host
host = (
f"[{discovery_info.ip_address}]"
if discovery_info.ip_address.version == 6
else str(discovery_info.ip_address)
)
https_port = (
int(discovery_info.port)
if discovery_info.port is not None
+1 -1
View File
@@ -19,7 +19,7 @@
},
"abort": {
"metering_point_id_already_configured": "Metering point with ID `{metering_point_id}` is already configured.",
"no_metering_points": "The provived API token has no metering points."
"no_metering_points": "The provided API token has no metering points."
}
}
}
@@ -1,7 +1,7 @@
{
"config": {
"error": {
"api_error": "An error occured in the pyemoncms API : {details}"
"api_error": "An error occurred in the pyemoncms API : {details}"
},
"step": {
"user": {
@@ -6,5 +6,5 @@
"iot_class": "local_push",
"loggers": ["sense_energy"],
"quality_scale": "internal",
"requirements": ["sense-energy==0.13.4"]
"requirements": ["sense-energy==0.13.5"]
}
+64 -48
View File
@@ -12,8 +12,8 @@ from typing import Any, Final, Literal, cast
from homeassistant.components.sensor import (
ATTR_LAST_RESET,
ATTR_STATE_CLASS,
RestoreSensor,
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.components.sensor.recorder import reset_detected
@@ -222,7 +222,7 @@ def _set_result_unless_done(future: asyncio.Future[None]) -> None:
future.set_result(None)
class EnergyCostSensor(SensorEntity):
class EnergyCostSensor(RestoreSensor):
"""Calculate costs incurred by consuming energy.
This is intended as a fallback for when no specific cost sensor is available for the
@@ -312,40 +312,22 @@ class EnergyCostSensor(SensorEntity):
return
# Determine energy price
if self._config["entity_energy_price"] is not None:
energy_price_state = self.hass.states.get(
self._config["entity_energy_price"]
)
if energy_price_state is None:
return
try:
energy_price = float(energy_price_state.state)
except ValueError:
if self._last_energy_sensor_state is None:
# Initialize as it's the first time all required entities except
# price are in place. This means that the cost will update the first
# time the energy is updated after the price entity is in place.
self._reset(energy_state)
return
energy_price_unit: str | None = energy_price_state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT, ""
).partition("/")[2]
# For backwards compatibility we don't validate the unit of the price
# If it is not valid, we assume it's our default price unit.
if energy_price_unit not in valid_units:
energy_price_unit = default_price_unit
else:
energy_price = cast(float, self._config["number_energy_price"])
energy_price_unit = default_price_unit
energy_price_tuple = self._get_energy_price(valid_units, default_price_unit)
if energy_price_tuple is None:
return
if self._last_energy_sensor_state is None:
# Initialize as it's the first time all required entities are in place.
self._reset(energy_state)
# Initialize as it's the first time all required entities are in place or
# only the price is missing. In the later case, cost will update the first
# time the energy is updated after the price entity is in place.
if self._attr_native_value is None:
self._reset(energy_state)
else:
self._last_energy_sensor_state = energy_state
return
energy_price, energy_price_unit = energy_price_tuple
if energy_price is None:
return
energy_unit: str | None = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
@@ -383,20 +365,9 @@ class EnergyCostSensor(SensorEntity):
old_energy_value = float(self._last_energy_sensor_state.state)
cur_value = cast(float, self._attr_native_value)
if energy_price_unit is None:
converted_energy_price = energy_price
else:
converter: Callable[[float, str, str], float]
if energy_unit in VALID_ENERGY_UNITS:
converter = unit_conversion.EnergyConverter.convert
else:
converter = unit_conversion.VolumeConverter.convert
converted_energy_price = converter(
energy_price,
energy_unit,
energy_price_unit,
)
converted_energy_price = self._convert_energy_price(
energy_price, energy_price_unit, energy_unit
)
self._attr_native_value = (
cur_value + (energy - old_energy_value) * converted_energy_price
@@ -404,8 +375,53 @@ class EnergyCostSensor(SensorEntity):
self._last_energy_sensor_state = energy_state
def _get_energy_price(
self,
valid_units: set[str],
default_unit: str | None,
) -> tuple[float | None, str | None] | None:
if self._config["entity_energy_price"] is None:
return cast(float, self._config["number_energy_price"]), default_unit
energy_price_state = self.hass.states.get(self._config["entity_energy_price"])
if energy_price_state is None:
return None
try:
energy_price = float(energy_price_state.state)
except ValueError:
return (None, None)
energy_price_unit: str | None = energy_price_state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT, ""
).partition("/")[2]
# For backwards compatibility we don't validate the unit of the price
# If it is not valid, we assume it's our default price unit.
if energy_price_unit not in valid_units:
energy_price_unit = default_unit
return energy_price, energy_price_unit
def _convert_energy_price(
self, energy_price: float, energy_price_unit: str | None, energy_unit: str
) -> float:
if energy_price_unit is None:
return energy_price
converter: Callable[[float, str, str], float]
if energy_unit in VALID_ENERGY_UNITS:
converter = unit_conversion.EnergyConverter.convert
else:
converter = unit_conversion.VolumeConverter.convert
return converter(energy_price, energy_unit, energy_price_unit)
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
if (sensor_data := await self.async_get_last_sensor_data()) is not None:
self._attr_native_value = sensor_data.native_value
energy_state = self.hass.states.get(self._config[self._adapter.stat_energy_key])
if energy_state:
name = energy_state.name
+1 -1
View File
@@ -16,7 +16,7 @@ class EnOceanEntity(Entity):
"""Initialize the device."""
self.dev_id = dev_id
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
self.async_on_remove(
async_dispatcher_connect(
@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"requirements": ["pyenphase==1.23.1"],
"requirements": ["pyenphase==1.25.1"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."
@@ -23,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator
from .entity import EnvoyBaseEntity
from .entity import EnvoyBaseEntity, exception_handler
PARALLEL_UPDATES = 1
@@ -132,6 +132,7 @@ class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity):
self.data.dry_contact_settings[self._relay_id]
)
@exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Update the relay."""
await self.envoy.update_dry_contact(
@@ -185,6 +186,7 @@ class EnvoyStorageSettingsNumberEntity(EnvoyBaseEntity, NumberEntity):
assert self.data.tariff.storage_settings is not None
return self.entity_description.value_fn(self.data.tariff.storage_settings)
@exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Update the storage setting."""
await self.entity_description.update_fn(self.envoy, value)
@@ -18,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator
from .entity import EnvoyBaseEntity
from .entity import EnvoyBaseEntity, exception_handler
PARALLEL_UPDATES = 1
@@ -192,6 +192,7 @@ class EnvoyRelaySelectEntity(EnvoyBaseEntity, SelectEntity):
"""Return the state of the Enpower switch."""
return self.entity_description.value_fn(self.relay)
@exception_handler
async def async_select_option(self, option: str) -> None:
"""Update the relay."""
await self.entity_description.update_fn(self.envoy, self.relay, option)
@@ -243,6 +244,7 @@ class EnvoyStorageSettingsSelectEntity(EnvoyBaseEntity, SelectEntity):
assert self.data.tariff.storage_settings is not None
return self.entity_description.value_fn(self.data.tariff.storage_settings)
@exception_handler
async def async_select_option(self, option: str) -> None:
"""Update the relay."""
await self.entity_description.update_fn(self.envoy, option)
@@ -57,7 +57,7 @@
"init": {
"title": "Envoy {serial} {host} options",
"data": {
"diagnostics_include_fixtures": "Include test fixture data in diagnostic report. Use when requested to provide test data for troubleshooting or development activies. With this option enabled the diagnostic report may take more time to download. When report is created best disable this option again.",
"diagnostics_include_fixtures": "Include test fixture data in diagnostic report. Use when requested to provide test data for troubleshooting or development activities. With this option enabled the diagnostic report may take more time to download. When report is created best disable this option again.",
"disable_keep_alive": "Always use a new connection when requesting data from the Envoy. May resolve communication issues with some Envoy firmwares."
},
"data_description": {
@@ -5,3 +5,4 @@ ATTR_STATION = "station"
CONF_STATION = "station"
CONF_TITLE = "title"
DOMAIN = "environment_canada"
SERVICE_ENVIRONMENT_CANADA_FORECASTS = "get_forecasts"
@@ -21,6 +21,9 @@
"services": {
"set_radar_type": {
"service": "mdi:radar"
},
"get_forecasts": {
"service": "mdi:weather-cloudy-clock"
}
}
}
@@ -167,6 +167,7 @@ SENSOR_TYPES: tuple[ECSensorEntityDescription, ...] = (
translation_key="wind_bearing",
native_unit_of_measurement=DEGREE,
value_fn=lambda data: data.conditions.get("wind_bearing", {}).get("value"),
device_class=SensorDeviceClass.WIND_DIRECTION,
),
ECSensorEntityDescription(
key="wind_chill",
@@ -1,3 +1,9 @@
get_forecasts:
target:
entity:
integration: environment_canada
domain: weather
set_radar_type:
target:
entity:
@@ -113,6 +113,10 @@
}
},
"services": {
"get_forecasts": {
"name": "Get forecasts",
"description": "Retrieves the forecast from selected weather services."
},
"set_radar_type": {
"name": "Set radar type",
"description": "Sets the type of radar image to retrieve.",
@@ -35,11 +35,16 @@ from homeassistant.const import (
UnitOfSpeed,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.core import (
HomeAssistant,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.helpers import entity_platform, entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .const import DOMAIN, SERVICE_ENVIRONMENT_CANADA_FORECASTS
from .coordinator import ECConfigEntry, ECDataUpdateCoordinator
# Icon codes from http://dd.weatheroffice.ec.gc.ca/citypage_weather/
@@ -78,6 +83,14 @@ async def async_setup_entry(
async_add_entities([ECWeatherEntity(config_entry.runtime_data.weather_coordinator)])
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_ENVIRONMENT_CANADA_FORECASTS,
None,
"_async_environment_canada_forecasts",
supports_response=SupportsResponse.ONLY,
)
def _calculate_unique_id(config_entry_unique_id: str | None, hourly: bool) -> str:
"""Calculate unique ID."""
@@ -185,6 +198,23 @@ class ECWeatherEntity(
"""Return the hourly forecast in native units."""
return get_forecast(self.ec_data, True)
def _async_environment_canada_forecasts(self) -> ServiceResponse:
"""Return the native Environment Canada forecast."""
daily = []
for f in self.ec_data.daily_forecasts:
day = f.copy()
day["timestamp"] = day["timestamp"].isoformat()
daily.append(day)
hourly = []
for f in self.ec_data.hourly_forecasts:
hour = f.copy()
hour["timestamp"] = hour["period"].isoformat()
del hour["period"]
hourly.append(hour)
return {"daily_forecast": daily, "hourly_forecast": hourly}
def get_forecast(ec_data, hourly) -> list[Forecast] | None:
"""Build the forecast array."""
@@ -6,7 +6,7 @@ import asyncio
import logging
from typing import Any
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -108,8 +108,7 @@ class ESPHomeDashboardManager:
reloads = [
hass.config_entries.async_reload(entry.entry_id)
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state is ConfigEntryState.LOADED
for entry in hass.config_entries.async_loaded_entries(DOMAIN)
]
# Re-auth flows will check the dashboard for encryption key when the form is requested
# but we only trigger reauth if the dashboard is available.
@@ -16,7 +16,7 @@
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
"mqtt": ["esphome/discover/#"],
"requirements": [
"aioesphomeapi==29.0.0",
"aioesphomeapi==29.1.1",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==2.7.1"
],
@@ -10,17 +10,17 @@
},
"period": {
"name": "Period",
"description": "A period of time in days; used only with Away, DayOff, or Custom. The system will revert to Auto at midnight (up to 99 days, today is day 1)."
"description": "A period of time in days; used only with Away, DayOff, or Custom mode. The system will revert to Auto mode at midnight (up to 99 days, today is day 1)."
},
"duration": {
"name": "Duration",
"description": "The duration in hours; used only with AutoWithEco (up to 24 hours)."
"description": "The duration in hours; used only with AutoWithEco mode (up to 24 hours)."
}
}
},
"reset_system": {
"name": "Reset system",
"description": "Sets the system to Auto mode and reset all the zones to follow their schedules. Not all Evohome systems support this feature (i.e. AutoWithReset mode)."
"description": "Sets the system to Auto mode and resets all the zones to follow their schedules. Not all Evohome systems support this feature (i.e. AutoWithReset mode)."
},
"refresh_system": {
"name": "Refresh system",
@@ -36,7 +36,7 @@
"issues": {
"import_yaml_error_url_error": {
"title": "The Feedreader YAML configuration import failed",
"description": "Configuring the Feedreader using YAML is being removed but there was a connection error when trying to import the YAML configuration for `{url}`.\n\nPlease verify that url is reachable and accessable for Home Assistant and restart Home Assistant to try again or remove the Feedreader YAML configuration from your configuration.yaml file and continue to set up the integration manually."
"description": "Configuring the Feedreader using YAML is being removed but there was a connection error when trying to import the YAML configuration for `{url}`.\n\nPlease verify that url is reachable and accessible for Home Assistant and restart Home Assistant to try again or remove the Feedreader YAML configuration from your configuration.yaml file and continue to set up the integration manually."
}
}
}

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