Compare commits

..

449 Commits

Author SHA1 Message Date
jbouwh
6da2ea28fc Make export selectors readonly 2025-06-18 17:27:51 +00:00
jbouwh
e7b5c599dc typo 2025-05-26 09:55:00 +00:00
jbouwh
ec41abd821 Add YAML and discovery info export feature for MQTT device subentries 2025-05-24 09:42:24 +00:00
Jan Bouwhuis
f92d14d87c Bump incomfort-client to v0.6.9 (#145546) 2025-05-24 10:53:08 +02:00
J. Nick Koston
2d3a6d780c Bump aiohttp to 3.12.0rc0 (#145540)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.12.0b3...v3.12.0rc0
2025-05-24 09:52:48 +02:00
Petro31
c359765a29 Remove inactive codeowner from template integration (#145535) 2025-05-23 23:59:22 +02:00
Michael
d8ed10bcc7 Use _handle_coordinator_update() instead of own callback in Feedreader event entity (#145520)
use _handle_coordinator_update() instead of own callback
2025-05-23 21:10:26 +02:00
karwosts
19259d5cad Add read_only selectors to Statistics Options Flow (#145522) 2025-05-23 17:58:45 +02:00
epenet
102230bf9d Remove repoze.lru from license exceptions (#145519)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-23 17:46:09 +02:00
Jan Bouwhuis
2a38f03ec9 Add MQTT fan as entity platform on MQTT subentries (#144698) 2025-05-23 17:40:54 +02:00
Ludovic BOUÉ
e22ea85e84 Add Matter Pump device type (#145335)
* Pump status

* Pump speed

* PumpStatusRunning

* ControlModeEnum

* Add tests

* Clean code

* Update tests and sensors

* Review fixes

* Add RPM unit

* Fix for unknown value

* Update snapshot

* OperationMode

* Update snapshots

* Update snapshot

* Update tests/components/matter/test_select.py

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

* Handle SupplyFault bit enabled too

* Review fix

* Unmove

* Remove pump_operation_mode

* Update snapshot

---------

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
2025-05-23 17:20:27 +02:00
Michael
ed0ff93d1e Bump py-sucks to 0.9.11 (#145518)
bump py-sucks to 0.9.11
2025-05-23 17:12:43 +02:00
karwosts
7af731694f Support readonly selectors in config_flows (#129456)
* Allow disabled selectors in config flows. Show hidden options for history_stats.

* fix tests

* use optional instead of required

* rename flag to readonly

* rename to read_only

* Update to use read_only field as part of selector definition

* lint fix

* Fix test

* All selectors
2025-05-23 17:05:43 +02:00
epenet
83ec45e4fc Use runtime_data in xiaomi_miio (#145517)
* Use runtime_data in xiaomi_miio

* Reduce changes
2025-05-23 17:03:33 +02:00
Michael
086e97821f Add automatic backup event entity to Home Assistant Backup system (#145350)
* add automatic backup event entity

* add tests

* fix test

* Apply suggestions from code review

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

* implement _handle_coordinator_update

* add translations for event attributes

* simplify condition

* Apply suggestions from code review

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

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-23 17:01:57 +02:00
Petro31
5048d1512c Add trigger based template cover (#145455)
* Add trigger based template cover

* address comments

* update position template in test
2025-05-23 16:32:21 +02:00
Denis Shulyaka
199c565bf2 Add Anthropic Claude 4 support (#145505)
Add Claude 4 support

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-05-23 10:31:44 -04:00
epenet
cbeefdaf26 Mark humidifier methods and properties as mandatory in pylint plugin (#145507) 2025-05-23 16:26:22 +02:00
Michael
4747de4703 Don't manipulate hvac modes based on device active mode in AVM Fritz!SmartHome (#145513) 2025-05-23 16:12:13 +02:00
starkillerOG
fc2fe32f34 Reolink fix device migration (#145443) 2025-05-23 15:33:03 +02:00
epenet
528a509479 Mark light methods and properties as mandatory in pylint plugin (#145510) 2025-05-23 14:28:41 +01:00
Franck Nijhof
bca4793c69 Add concentration conversion support for mg/m³ (#145325) 2025-05-23 14:24:18 +01:00
Josef Zweck
0c9b1b5c58 Add cloud as after_dependency to onedrive (#145301) 2025-05-23 15:07:06 +02:00
Robert Resch
7bf4239789 Bump deebot-client to 13.2.1 (#145492) 2025-05-23 14:54:18 +02:00
epenet
71ac2d3d75 Improve type hints in xiaomi_miio humidifier (#145506) 2025-05-23 14:54:09 +02:00
Denis Shulyaka
f019e8a36c Bump Anthropic library to 0.52.0 (#145494) 2025-05-23 14:48:54 +02:00
J. Nick Koston
44560dd298 Bump aiohttp to 3.12.0b3 (#145358) 2025-05-23 14:44:47 +02:00
Jan Bouwhuis
e8ea5c9d62 Add MQTT cover as entity platform on MQTT subentries (#144381)
* Add MQTT cover as entity platform on MQTT subentries

* Revert change vol.Coerce wrappers on cover schema

* Fix template validator and cleanup redundant validators

* Cleanup more redundant validators
2025-05-23 14:25:00 +02:00
Jan Bouwhuis
17297ab929 Improve mqtt subentry selector validation and remove redundant validators (#145499) 2025-05-23 13:23:36 +02:00
rappenze
041c09380b Bump pyfibaro to 0.8.3 (#145488) 2025-05-23 12:05:13 +02:00
Markus Lanthaler
553d420db9 Add support for Tuya Wireless Switch entity (#123284)
Add support for Tuya Wireless Switch entity
2025-05-23 08:42:09 +02:00
Joost Lekkerkerker
3f99a0bb65 Add diagnostics to Paperless-ngx (#145465)
* Add diagnostics to Paperless-ngx

* Add diagnostics to Paperless-ngx
2025-05-23 08:09:54 +02:00
Joost Lekkerkerker
c3d318ff51 Add paperless-ngx to strict typing (#145466) 2025-05-23 08:08:44 +02:00
Erik Montnemery
19345b0e18 Prefer to create backups in local storage if selected (#145331) 2025-05-23 08:00:35 +02:00
tronikos
e13abf2034 Make Gemma models work in Google AI (#145479)
* Make Gemma models work in Google AI

* move one line to be improve readability
2025-05-22 22:02:30 -07:00
tronikos
61248c561d Fix strings related to Google search tool in Google AI (#145480) 2025-05-22 22:01:48 -07:00
epenet
8561721faf Add pytest/codecov to forbidden runtime dependencies (#145447)
Add pytest/codecov to forbidden runtime packages
2025-05-22 23:15:21 +02:00
Manu
2f318927bc Add pending damage and pending quest items sensors (#145449)
Add pending damage and quest items sensors
2025-05-22 23:10:49 +02:00
tronikos
a15572bb8c Bump opower to 0.12.1 (#145464) 2025-05-22 22:22:20 +02:00
Bonne Eggleston
b532776d78 Make Powerwall energy sensors TOTAL_INCREASING to fix hardware swaps (#145165) 2025-05-22 14:49:39 -05:00
Abílio Costa
4ad34c57b5 Replace empty mock in GoalZero tests (#145463) 2025-05-22 20:22:09 +01:00
Abílio Costa
228beacca8 Add default sensor data for Tesla Wall Connector tests (#145462) 2025-05-22 21:20:57 +02:00
Norbert Rittel
c130a9f31c Fix typo in reauth_confirm description of metoffice (#145458) 2025-05-22 21:12:37 +02:00
Michael
622ab922b5 Add configuration url to Immich device info (#145456)
add configuration url to device info
2025-05-22 21:09:28 +02:00
epenet
6de2258325 Mark device_tracker methods and properties as mandatory in pylint plugin (#145309) 2025-05-22 19:15:00 +01:00
jz-v
d8e0be69d1 Add HomeKit thermostat fan state mapping for preheating, defrosting (#145353)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-05-22 11:57:01 -05:00
Petro31
4ee9fdc9fb Add AbstractTemplateVacuum to prepare for trigger based template vacuums (#144990)
* Add AbstractTemplateVacuum

* fix typo from copypaste

* update after rebase
2025-05-22 17:50:26 +02:00
Petro31
a8823cc1d1 Add AbstractTempleAlarmControlPanel class to prepare for trigger based template alarm control panels (#144974)
* Add AbstractTempleAlarmControlPanel class

* update after rebase

* remove unused list
2025-05-22 17:50:15 +02:00
Petro31
83ee9e9540 Add AbstractTemplate cover to prepare for trigger based template covers (#144907)
* Add AbstractTemplate cover to prepare for trigger based template covers

* add reflection and improve test coverage

* update class after rebase

* remove test
2025-05-22 17:49:50 +02:00
Petro31
9a74390143 Add AbstractTemplateLock to prepare for trigger based template locks (#144978)
* Add AbstractTemplateLock

* update after rebase
2025-05-22 17:33:57 +02:00
Joost Lekkerkerker
64d6552890 Bump pysmartthings to 3.2.3 (#145444) 2025-05-22 17:26:59 +02:00
Joost Lekkerkerker
65ebdb4292 Bump yt-dlp to 2025.05.22 (#145441) 2025-05-22 17:26:04 +02:00
Petro31
7a55abaa42 Add AbstractTemplateFan class in preparation for trigger based entity (#144968)
* Add AbstractTemplateFan class in preparation for trigger based entity

* update after rebase
2025-05-22 17:18:48 +02:00
dalan
8f05a639f3 HomeKit Bridge integration: Adding h264_qsv as valid VIDEO_CODEC option (#145448) 2025-05-22 09:52:58 -05:00
TimL
917b467b85 Add SMLIGHT button entities for second radio (#141463)
* Add button entities for second radio

* Update tests for second router reconnect button
2025-05-22 14:50:22 +02:00
J. Diego Rodríguez Royo
a938001805 Don't add dynamically Home Connect event sensors and disable them by default (#144757)
* Don't add dynamically Home Connect sensors and disable them by default

* Fix test

* Check for None

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-22 12:55:11 +02:00
Ludovic BOUÉ
9e6de48a22 Matter Device Energy Management cluster ESAState attribute (#144430)
* ESAState

* Update strings.json

* Add test
2025-05-22 12:53:08 +02:00
jvmahon
569aeff054 Add matter attributes (#140843)
* Add Matter attributes

* Add Matter attributes

* Add Matter attributes

* Add Matter attributes

* Update strings.json

* Update homeassistant/components/matter/select.py

Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>

* Update select.py

Deleted items to be added as switch entities instead.

* Update strings.json

* Update select.py

* Update strings.json

* Fix

* Update strings.json

* Update strings.json

* Fix

* Update select.py

---------

Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-22 12:42:05 +02:00
Joost Lekkerkerker
c86ba49a79 Add Matter test to select attribute (#145440) 2025-05-22 12:40:56 +02:00
epenet
3b4004607d Mark image_processing methods and properties as mandatory in pylint plugin (#145435) 2025-05-22 12:19:22 +02:00
Gigatrappeur
c68e663a1c Add webhook in switchbot cloud integration (#132882)
* add webhook in switchbot cloud integration

* Rename _need_initialized to _is_initialized and reduce nb line in async_setup_entry

* Add unit tests

* Enhance poll management

* fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-22 12:19:08 +02:00
Tamer Wahba
d870410413 Quantum Gateway device tracker tests (#145161)
* move constants to central const file

* add none return type to device scanner constructor

* add quantum gateway device tracker tests

* fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-22 12:18:56 +02:00
Florian von Garrel
9a8c29e05d Add paperless integration (#145239)
* add paperless integration - config flow and initialisation

* Add first sensors - documents, inbox, storage total and available

* Add status sensors with error attributes

* add status coordinator and organized code

* Fixed None error

* Organized code and moved requests to coordinator

* Organized code

* optimized code

* Add statustype state strings

* Error handling

* Organized code

* Add update sensor and one coordinator for integration

* add sanity sensor and timer for version request

* Add sensors and icons.json. better errorhandling

* Add tests and error handling

* FIxed tests

* Add tests for coverage

* Quality scale

* Stuff

* Improved code structure

* Removed sensor platform and reauth / reconfigure flow

* bump pypaperless to 4.1.0

* Optimized tests; update sensor as update platform; little optimizations

* Code optimizations with update platform

* Add sensor platform

* Removed update platform

* quality scale

* removed unused const

* Removed update snapshot; better code

* Changed name of entry

* Fixed bugs

* Minor changes

* Minor changed and renamed sensors

* Sensors to measurement

* Fixed snapshot; test data to json; minor changes

* removed mypy errors

* Changed translation

* minor changes

* Update homeassistant/components/paperless_ngx/strings.json

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-22 12:17:38 +02:00
epenet
a54c8a88ff Improve type hints in microsoft_face_detect (#145421)
* Improve type hints in microsoft_face_detect

* Improve
2025-05-22 11:52:26 +02:00
Manuel Rüger
ca914d8e4f switchbot_cloud: Add Smart Lock door and calibration state (#143695)
* switchbot_cloud: Add Smart Lock door and calibration state

* Incorporate review
2025-05-22 11:51:28 +02:00
epenet
687bedd251 Improve type hints in sighthound (#145432)
* Improve type hints in sighthound

* More
2025-05-22 11:35:06 +02:00
Franck Nijhof
5ddadcbd65 Add range support to icon translations (#145340) 2025-05-22 11:32:33 +02:00
epenet
981842ee87 Improve type hints in seven_segments (#145431)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-22 11:31:17 +02:00
epenet
ab69223d75 Improve type hints in openalpr_cloud (#145429)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-22 11:30:53 +02:00
epenet
69f0f38a09 Improve type hints in qrcode (#145430)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-22 11:30:38 +02:00
epenet
40267760fd Improve type hints in tensorflow (#145433)
* Improve type hints in tensorflow

* Use ANTIALIAS again

* Use Image.Resampling.LANCZOS
2025-05-22 11:23:33 +02:00
marc7s
6e74b56649 Catch invalid settings error in geocaching (#139944)
Refactoring and preparation for other sensor types
2025-05-22 11:22:33 +02:00
epenet
d35802a996 Improve type hints in microsoft_face (#145417)
* Improve type hints in microsoft_face

* Remove hass from init
2025-05-22 11:21:45 +02:00
Martin Hjelmare
c007286fd6 Improve Z-Wave config flow test typing (#145438) 2025-05-22 11:11:38 +02:00
Joost Lekkerkerker
b5a3cedacd Move to explicit exports in test helpers (#145392) 2025-05-22 10:44:31 +02:00
epenet
3d53bdc6c5 Improve type hints in dlib_face_identify (#145423) 2025-05-22 10:37:44 +02:00
epenet
a7f6a6f22c Improve type hints in dlib_face_detect (#145422) 2025-05-22 10:37:03 +02:00
epenet
7893eaa389 Improve type hints in microsoft_face_identify (#145419) 2025-05-22 10:36:12 +02:00
Lektri.co
e410977e64 Add new button to the Lektrico integration (#145420) 2025-05-22 10:34:14 +02:00
peteS-UK
d48ca1d858 Hotfix for incorrect bracket in messages for Squeezebox (#145418)
hotfix for wrong bracket
2025-05-22 10:07:10 +02:00
epenet
8758a086c1 Improve type hints in doods (#145426) 2025-05-22 09:18:28 +02:00
Pete Sage
1db5c514e6 Add binary_sensor platform to Rehlko (#145391)
* feat: add binary_sensor platform to Rehlko

* feat: add binary sensor platform

* fix: simplify availability logic

* fix: simplify availability logic

* fix: simplify

* fix: rename sensor

* fix: rename sensor

* fix: rename sensor

* fix: remove unneeded type

* fix: rename sensor to 'Auto run'

* fix: use device_class name
2025-05-22 09:07:38 +02:00
Joost Lekkerkerker
613aa9b2cf Add climate entity for heatpump zones in SmartThings (#144991)
* Add climate entity for heatpump zones in SmartThings

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Sync SmartThings EHS fixture
2025-05-22 08:27:01 +02:00
Joost Lekkerkerker
4c6e854cad Add valve position capability to SmartThings (#144923)
* Add another EHS SmartThings fixture

* Add another EHS

* Add valve position capability to SmartThings

* Fix

* Fix
2025-05-22 08:25:41 +02:00
Michael
66a6e55310 Centralise MockStreamReaderChunked helper (#145404)
centralize MockStreamReaderChunked helper
2025-05-22 08:22:02 +02:00
Pete Sage
bffbd5607b Remove unneeded parenthesis in comparison for Sonos (#145413)
fix: remove unneeded paren
2025-05-22 08:17:31 +02:00
Ludovic BOUÉ
12b5dbdd83 Add CancelBoost for Matter Water heater (#145316)
* Update water_heater.py

Add CancelBoost

* Add test for CancelBoost

* Update water_heater.py
2025-05-22 08:01:42 +02:00
epenet
9e7ae1daa4 Catch blocking version pinning in dependencies early (#145364)
* Catch upper bindings in dependencies early

* One more

* Apply suggestions from code review
2025-05-22 07:56:18 +02:00
Petro31
f36ee88a87 Clean up AbstractTemplateEntity (#145409)
Clean up abstract templates
2025-05-22 07:23:44 +02:00
Manu
8b3bad1f54 Bump habiticalib to v.0.4.0 (#145414)
Bump habiticalib to v0.4.0
2025-05-22 07:22:50 +02:00
epenet
b407792bd1 Mark geo_location methods and properties as mandatory in pylint plugin (#145313) 2025-05-22 01:57:39 +03:00
Paulus Schoutsen
12376a2338 Mark LLMs that support streaming as such (#145405) 2025-05-22 01:54:29 +03:00
epenet
088cfc3576 Mark fan methods and properties as mandatory in pylint plugin (#145311) 2025-05-21 23:29:33 +02:00
epenet
01b8f97201 Mark cover methods and properties as mandatory in pylint plugin (#145308) 2025-05-21 22:27:53 +01:00
Lektri.co
195e34cc09 Bump lektricowifi to 0.1 (#145393)
Use lektricowifi 0.1: add a new command
2025-05-21 22:43:42 +02:00
peteS-UK
fbab6741af Update exception handling for initialization for Squeezebox (#144674)
* initial

* tests

* translate exceptions

* updates

* tests updates

* remove bare exception

* merge fix
2025-05-21 22:37:07 +02:00
J. Nick Koston
e2b9e21c6a Bump ESPHome stable BLE version to 2025.5.0 (#144857) 2025-05-21 16:23:04 -04:00
Michael
4a26352c50 Bump py-synologydsm-api to 2.7.2 (#145403)
bump py-synologydsm-api to 2.7.2
2025-05-21 22:22:33 +02:00
Josef Zweck
d43371ed2f Bump pylamarzocco to 2.0.4 (#145402) 2025-05-21 22:02:14 +02:00
avee87
4f24d63de1 Update metoffice to use DataHub API (#131425)
* Update metoffice to use DataHub API

* Reauth test

* Updated to datapoint 0.11.0

* Less hacky check for day/night in twice-daily forecasts

* Updated to datapoint 0.12.1, added daily forecast

* addressed review comments

* one more nit

* validate credewntials in reauth flow

* Addressed review comments

* Attempt to improve coverage

* Addressed comments

* Reverted unnecessary reordering

* Update homeassistant/components/metoffice/sensor.py

* Update tests/components/metoffice/test_sensor.py

* Update homeassistant/components/metoffice/sensor.py

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-21 21:56:32 +02:00
Joost Lekkerkerker
cd9339903f Add thermostat fixture to SmartThings (#145407) 2025-05-21 21:17:03 +02:00
Josef Zweck
ca01bdc481 Mark backflush binary sensor not supported for GS3 MP in lamarzocco (#145406) 2025-05-21 21:12:43 +02:00
Joost Lekkerkerker
39a5341ab8 Add SmartThings capability for Washer soil level (#145041) 2025-05-21 20:27:38 +02:00
c0ffeeca7
3c93f6e3f9 ZHA repairs: remove links to obsolete docs (#145398) 2025-05-21 20:23:05 +02:00
Tsvi Mostovicz
13ce4322ac Reword sunset event exception (#145400) 2025-05-21 20:21:06 +02:00
Jeremiah Paige
b3ba506e6c wsdot component adopts wsdot package (#144914)
* wsdot component adopts wsdot package

* update generated files

* format code

* move wsdot to async_setup_platform

* Fix tests

* cast wsdot travel id

* bump wsdot to 0.0.1

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-21 20:15:26 +02:00
Joost Lekkerkerker
ea9fc6052d Add power cool and power freeze to SmartThings (#145102) 2025-05-21 20:14:13 +02:00
Joost Lekkerkerker
c8ceea4be8 Add SmartThings capability for Washer spin level (#145039) 2025-05-21 20:01:12 +02:00
Marc Mueller
3df993b9a4 Update igloohome-api to 0.1.1 (#145401) 2025-05-21 19:42:13 +02:00
Marc Mueller
980f19173f Update sensorpro-ble to 0.7.1 (#145397) 2025-05-21 18:40:32 +02:00
Marc Mueller
b1da600269 Update inkbird-ble to 0.16.2 (#145396) 2025-05-21 18:40:24 +02:00
Marc Mueller
34c5f79983 Update bluetooth-auto-recovery to 1.5.2 (#145395) 2025-05-21 18:40:18 +02:00
Joost Lekkerkerker
4cd3527761 Enable B009 (#144192) 2025-05-21 16:37:51 +01:00
TheOneValen
1dbe1955eb Allow image send with read-only access (matrix notify) (#144819) 2025-05-21 17:18:34 +02:00
c0ffeeca7
743abadfcf OTBR: remove links to obsolete multiprotocol docs (#145394) 2025-05-21 17:11:19 +02:00
Tsvi Mostovicz
bbd223af1f Jewish Calendar: Make exception translatable (#145376) 2025-05-21 17:07:36 +02:00
Martin Hjelmare
cb717c0ec6 Improve Z-Wave config flow test fixtures (#145378) 2025-05-21 17:06:36 +02:00
Martin Hjelmare
4956cf3727 Fix Z-Wave installation type string (#145390) 2025-05-21 17:04:48 +02:00
epenet
f76165e761 Prevent types-*/setuptools/wheel runtime requirements in dependencies (#145381)
Prevent setuptools/wheel runtime requirements in dependencies
2025-05-21 16:17:24 +02:00
Erik Montnemery
dd00d0daad Improve failing backup repair messages (#145388) 2025-05-21 16:16:50 +02:00
Andre Lengwenus
77ec87d0ac Bump lcn-frontend to 0.2.5 (#144983) 2025-05-21 16:10:25 +02:00
Raj Laud
2209f0b884 Bump pysqueezebox to v0.12.1 (#145384) 2025-05-21 15:17:51 +02:00
Andy
61fd073a5c Fix: Revert Ecovacs mower total_stats_area unit to square meters (#145380) 2025-05-21 14:19:37 +02:00
Petar Petrov
efa7fe0dc9 Recommended installation option for Z-Wave (#145327)
Recommended installation option for ZWave
2025-05-21 13:30:59 +02:00
Erik Montnemery
00a1d9d1b0 Improve comment explaining planned backup store version bump (#145368) 2025-05-21 13:22:05 +02:00
Retha Runolfsson
630c438834 Add lock ultra and lock lite for switchbot integration (#145373) 2025-05-21 12:37:47 +02:00
Sid
3ada93b293 Bump eheimdigital to 1.2.0 (#145372) 2025-05-21 12:35:10 +02:00
c0ffeeca7
291499d5e1 Update links to user docs: Connect-ZBT-1, Green, Yellow (#145374) 2025-05-21 10:57:20 +01:00
Retha Runolfsson
08c453581c Add hub3 support for switchbot integration (#145371)
add support for hub3
2025-05-21 11:12:08 +02:00
Michael
5e25bbba2d Fix limit of shown backups on Synology DSM location (#145342) 2025-05-21 10:22:47 +02:00
Erik Montnemery
eb85185072 Minor code deduplication in backup manager (#145366) 2025-05-21 10:19:53 +02:00
Retha Runolfsson
3f72030d5f Bump pyswitchbot to 0.64.1 (#145360) 2025-05-21 10:08:32 +02:00
peteS-UK
69a4d2107f Add initial coordinator refresh for players in Squeezebox (#145347)
* initial

* add test for new player
2025-05-20 23:29:55 +02:00
Joris Drenth
46fe132e83 Add sensors to Wallbox (#145247) 2025-05-20 22:02:07 +01:00
Ernst Klamer
c60f19b35b Bump xiaomi-ble to 0.39.0 (#145348) 2025-05-20 22:37:27 +02:00
Petar Petrov
ba44986524 Remove the old ZWave controller from the list of migration targets (#145281)
* Remove the old ZWave controller from the list of migration targets

* ensure addon device path is serial/by_id

* Use executor

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-20 22:11:03 +02:00
Pete Sage
73811eac0a Add support for music library folder to Sonos (#139554)
* initial prototype

* use constants

* make playing work

* remove unneeded code

* remove unneeded code

* fix regressions issues

* refactor add_to_queue

* refactor add_to_queue

* refactor add_to_queue

* simplify

* add tests

* remove bad test

* rename constants

* comments

* comments

* comments

* use snapshot

* refactor to use add_to_queue

* refactor to use add_to_queue

* add comments, redo snapshots

* update comment

* merge formatting

* code review changes

* fix: merge issue

* fix: update snapshot to include new can_search field
2025-05-20 21:48:33 +02:00
Pete Sage
3ff3cb975b Add date sensors to Rehlko (#145314)
* feat: add datetime sensors

* fix: constants

* fix: constants

* fix: move tz conversion to api

* fix: update typing
2025-05-20 21:47:45 +02:00
Lode Smets
8ec5472b79 Added support for shared spaces in Synology DSM (Photo Station) (#144044)
* Added shared space to the list of all the albums

* Added tests

* added more tests

* Apply suggestions from code review

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2025-05-20 21:17:43 +02:00
Michael
b0415588d7 Add support for videos in Immich media source (#145254)
add support for videos
2025-05-20 20:40:22 +02:00
Paulus Schoutsen
abcf925b79 Assist Pipeline stream TTS when supported and long response (#145264)
* Assist Pipeline stream TTS when supported and long response

* Indicate in run-start if streaming supported

* Simplify a little bit

* Trigger streaming based on characters

* 60
2025-05-20 13:00:27 -05:00
Josef Zweck
37e13505cf Handle more exceptions in azure_storage (#145320) 2025-05-20 19:42:10 +02:00
Pete Sage
5d76d92bcf bump aiokem to 0.5.11 (#145332)
fix: bump aiokem
2025-05-20 19:13:40 +02:00
Tsvi Mostovicz
b71870aba3 Jewish calendar: move value calculation to entity description (1/3) (#144272)
* Move make_zmanim() method to entity

* Move enum values to setup

* Create a Jewish Calendar Sensor Description

* Hold a single variable for the runtime data in the entity

* Move value calculation to sensor description

* Use a base class to keep timestamp sensor inheritance

* Move attr to entity description as well

* Move options to entity description as well

* Fix tests after merge

* Put multiline in parentheses

* Fix diagnostics tests
2025-05-20 19:01:24 +02:00
Michael
734d6cd247 bump aioimmich to 0.6.0 (#145334) 2025-05-20 18:52:52 +02:00
Tsvi Mostovicz
40faa156e2 Jewish calendar : icon translations (#145329)
* Move icons to icons.json

* Fix tests
2025-05-20 17:35:24 +02:00
jb101010-2
4737091722 Suez water: fetch historical data in statistics (#131166)
* Suez water: fetch historical data in statistics

* test review

* wip: fix few things

* Python is smarter than me

* use snapshots for statistics and add hard limit for historical stats

* refactor refresh + handle missing price

* No more auth error raised

* fix after rebase

* Review - much cleaner <3

* fix changes

* test without snapshots

* fix imports
2025-05-20 16:22:35 +02:00
Bram Kragten
4160ed190c Add Albanian (Shqip) language (#145324) 2025-05-20 16:20:06 +02:00
Erik Montnemery
8e74f63d47 Create repair issue if not all add-ons or folders were backed up (#144999)
* Create repair issue if not all add-ons or folders were backed up

* Fix spelling

* Fix _collect_errors

* Make time patching by freezegun work with mashumaro

* Addd test to hassio

* Add fixture

* Fix generating list of folders

* Add issue creation tests

* Include name of failing add-on in message

* Improve code formatting

* Rename AddonError to AddonErrorData
2025-05-20 15:23:52 +02:00
Joris Drenth
fc62bc5fc1 Add solar charging options to Wallbox integration (#139286)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-05-20 14:19:48 +01:00
G Johansson
010b4f6b15 Remove deprecated aux heat from Climate Entity component (#145151) 2025-05-20 14:48:33 +02:00
Norbert Rittel
b16d4dd94b Use preferred spelling of "setpoint" in smartthings (#145319)
* Use preferred spelling of "setpoint" in `smartthings`

Change three occurrences of "set point" to "setpoint" to match the preferred spelling in Home Assistant.

* Update test_sensor.ambr

* Update test_sensor.ambr (2)
2025-05-20 14:31:51 +02:00
Sanjay Govind
0813adc327 Update binary sensor translations for bosch_alarm (#145315)
update binary sensor translations
2025-05-20 14:19:51 +02:00
Norbert Rittel
e68cf80531 Make spelling of "setpoint" consistent in opentherm_gw (#145318) 2025-05-20 15:07:57 +03:00
epenet
258c91d483 Mark climate methods and properties as mandatory in pylint plugin (#145280)
* Mark climate methods and properties as mandatory in pylint plugin

* One more
2025-05-20 13:40:17 +02:00
epenet
64d6101fb7 Mark camera methods and properties as mandatory in pylint plugin (#145272) 2025-05-20 12:30:22 +01:00
Franck Nijhof
fb0cb7cad6 Add Wh/km unit for energy distance (#145243) 2025-05-20 12:16:27 +01:00
Brett Adams
a3c0b83dee Bump teslemetry_stream to 0.7.9 in Teslemetry (#145303)
Bump stream to 0.7.9
2025-05-20 12:33:49 +02:00
Sanjay Govind
1ff5dd8ef5 Fix issues with bosch alarm dhcp discovery (#145034)
* fix issues with checking mac address for panels added manually

* add test

* don't allow discovery to pick up a host twice

* make sure we validate tests without a mac address

* check entry is loaded

* Update config_flow.py

* apply changes from review

* assert unique id

* assert unique id
2025-05-20 12:26:41 +02:00
epenet
f3f5fca0b9 Mark turn_on/turn_off/toggle as mandatory in pylint plugin (#145249)
* Mark turn_on/turn_off/toggle as mandatory in pylint plugin

* Fixes
2025-05-20 12:00:10 +02:00
Sanjay Govind
d15a1a6711 Tidy up service call for bosch_alarm (#145306)
tidy up service call for bosch_alarm
2025-05-20 11:56:53 +02:00
epenet
e39c8e350c Add class init type hint to xiaomi_aqara (#145255) 2025-05-20 11:56:17 +02:00
epenet
c1da554eb1 Mark calendar methods and properties as mandatory in pylint plugin (#145271) 2025-05-20 11:50:31 +02:00
epenet
1f1fd8de87 Mark alarm_control_panel methods and properties as mandatory in pylint plugin (#145270) 2025-05-20 11:50:22 +02:00
Norbert Rittel
cf6cb0bd39 Fix typos in user-facing strings of zha (#145305) 2025-05-20 11:49:50 +02:00
epenet
a8264ae8ae Mark button methods and properties as mandatory in pylint plugin (#145269) 2025-05-20 11:48:33 +02:00
epenet
642dc5b49c Use shorthand attributes in rpi_camera camera (#145274)
* Use shorthand attributes in rpi_camera camera

* Improve
2025-05-20 11:47:49 +02:00
epenet
43ae0f2541 Use shorthand attributes in xiaomi_aqara (#145253)
* Use shorthand attributes for is_on/is_locked in xiaomi_aqara

* Use _attr_changed_by

* Use _attr_device_class

* Remove unused class variable

* More
2025-05-20 11:47:26 +02:00
epenet
f2233b3034 Refactor set_temperature in venstar climate (#145297)
Clarify logic in venstar climate set_temperature
2025-05-20 11:46:53 +02:00
Sanjay Govind
c3fe5f012e add date and time service to bosch_alarm (#142243)
* add date and time service

* update quality scale

* add changes from review

* fix issues after merge

* fix icons

* apply changes from review

* remove list from service schema

* update quality scale

* update strings

* Update homeassistant/components/bosch_alarm/services.py

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

* apply changes from review

* apply changes from review

* Update tests/components/bosch_alarm/test_services.py

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

* validate exception messages

* use schema to validate service call

* update docstring

* update error message

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-20 11:09:46 +02:00
epenet
7f9b454922 Improve type hints in xs1 entities (#145299) 2025-05-20 10:44:34 +02:00
epenet
15915680b5 Use shorthand attributes in xs1 climate (#145298)
* Use shorthand attributes in xs1 climate

* Improve
2025-05-20 10:42:41 +02:00
epenet
2e4226d7d3 Use shorthand attributes in venstar climate (#145294) 2025-05-20 10:25:22 +02:00
epenet
0cd93e7e65 Use shorthand attributes in vivotek camera (#145275) 2025-05-20 10:15:04 +02:00
epenet
611d5be40a Use shorthand attributes in touchline climate (#145292) 2025-05-20 10:12:20 +02:00
epenet
072bf75d71 Improve type hints in homematic climate (#145283) 2025-05-20 10:11:23 +02:00
epenet
f9000ae08c Use shorthand attributes in push camera (#145273)
* Use shorthand attributes in push camera

* Improve
2025-05-20 10:10:42 +02:00
epenet
c8183bd35a Use shorthand attributes in intesishome climate (#145285) 2025-05-20 10:10:24 +02:00
epenet
99f91003d8 Use shorthand attributes in melissa climate (#145286) 2025-05-20 10:10:12 +02:00
epenet
ed2024e67a Drop useless unit conversion in smarttub (#145287) 2025-05-20 10:09:47 +02:00
epenet
cd91aca3b5 Use shorthand attributes in tfiac climate (#145289) 2025-05-20 10:09:25 +02:00
Matthew FitzGerald-Chamberlain
77ea654a1f Bump pyaprilaire to 0.9.0 (#145260) 2025-05-20 09:51:29 +02:00
Manu
ef6d3a5236 Bump aiontfy to 0.5.3 (#145263) 2025-05-20 09:49:27 +02:00
epenet
502574e86f Use shorthand attributes in yi camera (#145276) 2025-05-20 09:32:50 +02:00
epenet
fd1ddbd93d Improve type hints in blebox climate (#145282) 2025-05-20 09:31:42 +02:00
epenet
a12bc70543 Use runtime_data in smarttub (#145279) 2025-05-20 09:15:26 +02:00
Petar Petrov
b84e93f462 Sort usb ports in Z-Wave flow so unknown devices are last (#145211)
* Sort usb ports in Z-Wave flow so unknown devices are last

* tweak

* Apply suggestions from code review

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-20 08:25:44 +03:00
epenet
f4b0baecd3 Improve type hints in omnilogic (#145259) 2025-05-20 00:21:44 +02:00
epenet
f700a1faa3 Use shorthand attributes in raspyrfm (#145250) 2025-05-20 00:11:52 +02:00
epenet
eb90f5a581 Improve type hints in xiaomi_aqara light turn_on (#145257) 2025-05-20 00:11:34 +02:00
Jordan Harvey
20ce879471 Add new Probe Plus integration (#143424)
* Add probe_plus integration

* Changes for quality scale

* sentence-casing

* Update homeassistant/components/probe_plus/config_flow.py

Co-authored-by: Erwin Douna <e.douna@gmail.com>

* Update homeassistant/components/probe_plus/config_flow.py

Co-authored-by: Erwin Douna <e.douna@gmail.com>

* Update tests/components/probe_plus/test_config_flow.py

Co-authored-by: Erwin Douna <e.douna@gmail.com>

* Update tests/components/probe_plus/test_config_flow.py

Co-authored-by: Erwin Douna <e.douna@gmail.com>

* remove version from configflow

* remove address var from async_step_bluetooth_confirm

* move timedelta to SCAN_INTERVAL in coordinator

* update tests

* updates from review

* add voltage device class

* remove unused logger

* remove names

* update tests

* Update config flow tests

* Update unit tests

* Reorder successful tests

* Update config entry typing

* Remove icons

* ruff

* Update async_add_entities logic

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

* sensor platform formatting

---------

Co-authored-by: Erwin Douna <e.douna@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-19 22:50:09 +02:00
epenet
df3688ef08 Mark entity methods and properties as mandatory in pylint plugin (#145210)
* Mark entity methods and properties as mandatory in pylint plugin

* Fixes
2025-05-19 22:47:24 +02:00
Michael
e76bd1bbb9 Add media_source platform to Immich integration (#145159)
* add media_source platform

* fix error messages

* use mime-type from asset info, instead of guessing it

* add dependency for http

* add tests

* use direct imports and set can_play=False for images

* fix tests
2025-05-19 22:39:04 +02:00
Nick Kuiper
d580f8a8a2 Updated code owners for the blue current integration. (#144962)
Changed code owners for the blue current integration.
2025-05-19 22:21:06 +02:00
Brett Adams
ffb485aa87 Fix streaming window cover entity in Teslemetry (#145012) 2025-05-19 22:13:15 +02:00
Paulus Schoutsen
741cb23776 Only pass serializable data to media player intent (#145244) 2025-05-19 15:03:21 -05:00
Tsvi Mostovicz
6afb60d31b Jewish Calendar - quality scale - use specific config flow (#144408) 2025-05-19 21:52:06 +02:00
disforw
761bb65ac6 Fix QNAP fail to load (#144675)
* Update coordinator.py

* Update coordinator.py

@peternash

* Update coordinator.py

* Update coordinator.py

* Update coordinator.py

* Update coordinator.py
2025-05-19 21:47:01 +02:00
Tsvi Mostovicz
7464e3944e Jewish calendar: set parallel updates to 0 (#144986)
* Set all Jewish calendar parallel updates to 0

* Update homeassistant/components/jewish_calendar/service.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-19 21:44:28 +02:00
Tsvi Mostovicz
e2f2c13e5e Jewish calendar - quality scale - fix missing translations (#144410) 2025-05-19 21:44:13 +02:00
Åke Strandberg
0ee0b2fcba Add missing Miele tumble dryer program codes (#145236) 2025-05-19 21:34:36 +02:00
G Johansson
1e9c585e8b Bump holidays to 0.73 (#145238) 2025-05-19 21:12:51 +02:00
Paulus Schoutsen
e78f4d2a29 TTS to only use stream entity method when streaming request comes in (#145167)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-05-19 20:54:21 +02:00
Tsvi Mostovicz
1f6faaacab Jewish Calendar: Implement diagnostics (#145180)
* Implement diagnostics

* Add testing

* Remove implicitly tested code
2025-05-19 20:41:00 +02:00
karwosts
7e895f7d10 Fix history_stats with sliding window that ends before now (#145117) 2025-05-19 20:03:59 +02:00
Norbert Rittel
5031ffe767 Fix wording of "Estimated power production" sensors in forecast_solar (#145201) 2025-05-19 20:02:37 +02:00
Paulus Schoutsen
37fe25cfdc Add support_streaming to ConversationEntity (#144998)
* Add support_streaming to ConversationEntity

* pipeline tests
2025-05-19 13:43:06 -04:00
epenet
cff7aa229e Add missing type hint in plex (#145217) 2025-05-19 19:18:22 +02:00
Paulus Schoutsen
e09dde2ea9 Allow TTS streams to generate temporary media source IDs (#145080)
* Allow TTS streams to generate temporary media source IDs

* Update tests/components/tts/test_media_source.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update assist snapshots

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-19 12:04:19 -04:00
epenet
cadbe885d1 Add missing type hint in homematic (#145214) 2025-05-19 17:52:35 +02:00
epenet
366f592a8a Fix invalid type hints in netgear switch (#145226)
* Fix invalid type hints in netgear switch

* Adjust
2025-05-19 17:52:23 +02:00
epenet
e491629143 Split update method in pioneer media player (#145212)
Split method in pioneer media player
2025-05-19 17:40:12 +02:00
Simone Chemelli
f44cb9b03e Add action exceptions to Comelit integration (#143581)
* Add action exceptions to Comelit integration

* missing decorator

* update quality scale
2025-05-19 17:30:34 +02:00
Petro31
752c73a2ed Add trigger_variables to template trigger 'for' field (#136672)
* Add trigger_variables to template trigger for

* address comments
2025-05-19 17:26:42 +02:00
Simone Chemelli
a8ecdb3bff Finish reconfigure test for Vodafone Station (#145230) 2025-05-19 16:34:41 +02:00
Simone Chemelli
9c798cbb5d Add device reconfigure to Comelit config flow (#142866)
* Add device reconfigure to Comelit config flow

* tweak

* tweak

* update quality scale

* apply review comment

* apply review comment

* review comment

* complete test
2025-05-19 16:12:27 +02:00
Erik Montnemery
8938c109c2 Improve entity registry restore test (#145220) 2025-05-19 16:09:23 +02:00
Martin Hjelmare
85448ea903 Fix Z-Wave config entry unique id after NVM restore (#145221)
* Fix Z-Wave config entry unique id after NVM restore

* Remove stale comment
2025-05-19 17:05:48 +03:00
epenet
e3d2f917e2 Use shorthand attributes in yandex transport sensor (#145225) 2025-05-19 15:58:34 +02:00
epenet
05795d0ad8 Use _attr_native_value in repetier (#145219) 2025-05-19 15:56:27 +02:00
epenet
a38e033e13 Improve type hints in rtorrent (#145222) 2025-05-19 15:53:41 +02:00
epenet
8df447091d Add missing type hint in vlc (#145223) 2025-05-19 15:52:40 +02:00
Simone Chemelli
760f2d1959 Remove pylance warnings for Comelit tests (#145199) 2025-05-19 15:27:41 +02:00
Simone Chemelli
e64f76bebe Add full test coverage for Comelit cover (#144761) 2025-05-19 15:01:41 +02:00
epenet
7c5090d627 Add missing type hint in zestimate (#145218) 2025-05-19 14:55:48 +02:00
Robert Resch
f6a0d630c3 Fix typo in Ecovacs get_supported_entities (#145215) 2025-05-19 14:45:30 +02:00
markhannon
880f5faeec Add cover entity to Zimi integration (#144330) 2025-05-19 14:24:25 +02:00
Retha Runolfsson
0cf503d871 Add exception translation for switchbot device initialization (#144828) 2025-05-19 14:22:10 +02:00
epenet
9d050360c8 Prevent import from syrupy.SnapshotAssertion (#145208) 2025-05-19 14:18:35 +02:00
Simone Chemelli
0c0c61f9e0 Bump aiocomelit to 0.12.3 (#145209) 2025-05-19 14:16:12 +02:00
Erik Montnemery
e868b3e8ff Sort and simplify DeletedRegistryEntry (#145207) 2025-05-19 14:13:57 +02:00
Simone Chemelli
555215a848 Update quality_scale rules status for Comelit (#143592) 2025-05-19 14:05:08 +02:00
Simone Chemelli
484a547758 Fix pylance warning on SnapshotAssertion import (#145206) 2025-05-19 13:55:48 +02:00
wuede
7d25f68fa5 update pyatmo to version 9.2.0 (#145203) 2025-05-19 13:21:19 +02:00
Maikel Punie
8b22ab93c1 Bump velbusaio to 2025.5.0 (#145198) 2025-05-19 13:20:02 +02:00
epenet
78e3a2d0c6 Mark all _CLASS_MATCH as mandatory in pylint plugin (#145200) 2025-05-19 12:12:17 +01:00
Robert Resch
241c89e885 Bump go2rtc-client to 0.1.3b0 (#145192) 2025-05-19 12:11:07 +01:00
Marc Mueller
7d96a2a620 [ci] Skip step if coverage is skipped (#145202) 2025-05-19 12:46:38 +02:00
Martin Hjelmare
08104eec56 Fix Z-Wave unique id update during controller migration (#145185) 2025-05-19 13:43:06 +03:00
Michael
0fc81d6b33 Add diagnostics platform to Immich integration (#145162)
* add diagnostics platform

* also redact host
2025-05-19 12:23:04 +02:00
Joost Lekkerkerker
cb84e55c34 Map auto to heat_cool for thermostat in SmartThings (#145098) 2025-05-19 12:09:27 +02:00
Joost Lekkerkerker
68c3d5a159 Add lamp capability for hood component in SmartThings (#145036) 2025-05-19 12:07:50 +02:00
epenet
77bab39ed0 Move downloader service to separate module (#145183) 2025-05-19 12:05:33 +02:00
Maciej Bieniek
92e570ffc1 Revert "Link Shelly device entry with Shelly BT scanner entry (#144626)" (#145177)
This reverts commit b15c9ad130.
2025-05-19 13:01:54 +03:00
Paulus Schoutsen
919684e20a Minor cleanup for pipeline tts stream test (#145146) 2025-05-19 11:58:58 +02:00
G Johansson
a1d6df6ce9 Remove deprecated aux heat from ephember (#145152) 2025-05-19 11:58:35 +02:00
epenet
07c3c3bba8 Mark type hint as compulsory for entity.assumed_state property (#145187) 2025-05-19 11:56:05 +02:00
epenet
f11e040662 Mark all _FUNCTION_MATCH as mandatory in pylint plugin (#145194) 2025-05-19 11:55:15 +02:00
epenet
8d83341308 Mark type hint as compulsory for entity.available property (#145189) 2025-05-19 11:50:41 +02:00
Erik Montnemery
f27b2c4df1 Improve device registry restore tests (#145186) 2025-05-19 11:06:16 +02:00
Matrix
717b84bab9 Add battery entity for LockV2 in yolink (#145169)
Add battery entity for LockV2
2025-05-19 11:01:30 +02:00
epenet
a34bce6202 Fix runtime_data in iqvia (#145181) 2025-05-19 10:59:46 +02:00
epenet
bd190b9b4c Use runtime_data in icloud (#145179) 2025-05-19 10:59:06 +02:00
epenet
da6c6c5201 Use runtime_data in ialarm (#145178) 2025-05-19 10:58:34 +02:00
epenet
f50afae1c3 Use runtime_data in hvv_departures (#144951) 2025-05-19 10:58:01 +02:00
epenet
177afea5ad Use runtime_data in huisbaasje (#144953) 2025-05-19 10:57:22 +02:00
Joost Lekkerkerker
a3aae68229 Add athmospheric pressure capability to SmartThings (#145103) 2025-05-19 10:41:22 +02:00
Robert Resch
9ff9d9230e Fix test results parsing error (#145077) 2025-05-19 10:40:03 +02:00
epenet
2bb0843c30 Add ability to mark type hints as compulsory on specific functions (#139730) 2025-05-19 10:27:07 +02:00
Joakim Sørensen
5f2425f421 Bump hass-nabucasa from 0.100.0 to 0.101.0 (#145172) 2025-05-19 10:24:08 +02:00
J. Nick Koston
e46ca41697 Bump aioesphomeapi to 31.1.0 (#145170) 2025-05-19 10:22:47 +02:00
dependabot[bot]
fa5a7aea7e Bump github/codeql-action from 3.28.17 to 3.28.18 (#145173)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 10:14:37 +02:00
Tsvi Mostovicz
030681a443 Jewish calendar: use const in action code (#145007)
* Use const defines in code

* Added exception raises

* Revert "Added exception raises"

This reverts commit e8849e586c83b45ecfd374986edb0d8c64b263e4.
2025-05-19 10:14:22 +02:00
epenet
aa3cbf2473 Cleanup unused string in samsungtv (#145174) 2025-05-19 09:10:01 +02:00
Erik Montnemery
ce71f6444c Sort and simplify DeletedDeviceEntry (#145171)
* Sort and simplify DeletedDeviceEntry

* Fix sort

* Fix sort
2025-05-19 08:40:22 +02:00
J. Nick Koston
eb4d561b96 Bump grpcio to 1.72.0 and protobuf to 6.30.2 (#143633) 2025-05-18 20:10:38 -04:00
peteS-UK
075a41c69a Fix album and artist returning "None" rather than None for Squeezebox media player. (#144971)
* fix

* snapshot update

* cast type
2025-05-18 23:37:06 +02:00
G Johansson
2ba2248f67 Remove deprecated aux heat from econet (#145149) 2025-05-18 23:03:13 +02:00
starkillerOG
ff5ed82de8 Add Kaiser Nienhaus virtual motionblinds integration (#145096)
* Add Kaiser Nienhaus virtual motionblinds integration

* fix typo
2025-05-18 23:01:02 +02:00
wuede
541b969d3b Netatmo: do not fail on schedule updates (#142933)
* do not fail on schedule updates

* add test to check that the store data remains unchanged
2025-05-18 23:00:36 +02:00
javicalle
3d83c6299b Enable RFDEBUG on RFLink "Enable debug logging" (#138571)
* Enable RFDEBUG on "Enable debug logging"

* fix checks

* fix checks

* one more lap

* fix test

* wait to init rflink 

In my dev env this is not needed

* use hass.async_create_task(handle_logging_changed())

instead async_at_started(hass, handle_logging_changed)

* revert unneeded async_block_till_done

* Remove the startup management

There's a race condition at startup that can't be managed nicely
2025-05-18 22:51:42 +02:00
generically-named
3ecde49dca Add energy/water forecast for Miele integration (#144822)
* Add energy/water forecast & fix drying_step error

Adding the energy forecast and water forecast entities that are present in the HACS version of this integration but absent in the HA Core implantation. 

Also fixed the state_drying_step sensor which wasn't handling casting of the API value to an int correctly due to the API sometimes giving a None value.

* Fix formatting issues from previous commit

* Fix missing translation_key line 202

* Remove icon entries

* Update icons.json

* Update strings.json

* Update strings.json (correcting mixed up energy/water forecast names)

* Update homeassistant/components/miele/strings.json

Co-authored-by: Åke Strandberg <ake@strandberg.eu>

* Update homeassistant/components/miele/strings.json

Co-authored-by: Åke Strandberg <ake@strandberg.eu>

* Fix tests

---------

Co-authored-by: Åke Strandberg <ake@strandberg.eu>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-18 22:33:27 +02:00
Nils Müller
c1fcd8ea7f Fix Nanoleaf light state propagation after change from home asisstant (#144291)
* Fix Nanoleaf light state propagation after change from home asisstant

* Add tests to check if nanoleaf light is triggering async_write_ha_state

* Fix pylint for test case

* Fix use coordinator.async_refresh instead of async_write_ha_state

* Fix tests

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-18 22:26:02 +02:00
G Johansson
78ac8ba841 Remove deprecated aux heat from Nexia (#145147) 2025-05-18 16:14:22 -04:00
Keilin Bickar
d9cfab4c8e Bump sense-energy to 0.13.8 (#145156) 2025-05-18 21:45:11 +02:00
Oliver
4c10502b0e Update denonavr to 1.1.1 (#145155) 2025-05-18 21:44:53 +02:00
Michael
a576f7baf3 Add Immich integration (#145125)
* add immich integration

* bump aioimmich==0.3.1

* rework to require an url as input and pare it afterwards

* fix doc strings

* remove name attribute from deviceinfo as it is default behaviour

* add translated uom for count sensors

* explicitly pass in the config_entry in coordinator

* fix url in strings to uppercase

* use data_updates attribute instead of data

* remove left over

* match entries only by host

* remove quotes

* import SOURCE_USER directly, instead of config_entries

* split happy and sad flow tests

* remove unneccessary async_block_till_done() calls

* replace url example by "full URL"

* bump aioimmich==0.4.0

* bump aioimmich==0.5.0

* allow multiple users for same immich instance

* Fix tests

* limit entities when user has no admin rights

* Fix tests

* Fix tests

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-18 21:28:15 +02:00
G Johansson
520c964656 Remove deprecated aux heat from elkm1 (#145148) 2025-05-18 20:50:33 +02:00
Matrix
3f59b1c376 Add YoLink new device types support 5009 & 5029 (#144323)
* Leak Stop

* Fix as suggested.
2025-05-18 19:59:19 +02:00
Markus Adrario
3ff095cc51 Add Homee alarm-control-panel platform (#140041)
* Add alarm control panel

* Add alarm control panel tests

* add disarm function

* reuse state setting code

* change sleeping to night

* review change 1

* fix review comments

* fix review comments
2025-05-18 17:25:09 +02:00
Marc Hörsken
aa4c41abe8 Postpone update in WMSPro after service call (#144836)
* Reduce stress on WMS WebControl pro with higher scan interval

Avoid delays and connection issues due to overloaded hub.
Fixes #133832 and #134413

* Schedule an entity state update after performing an action

Avoid delaying immediate status updates, e.g. on/off changes.

* Replace scheduled state updates with delayed action completion

Suggested-by: joostlek
2025-05-18 17:23:21 +02:00
Sid
906b3901fb Add select platform to eheimdigital (#145031)
* Add select platform to eheimdigital

* Review

* Review

* Fix tests
2025-05-18 16:52:27 +02:00
Andre Lengwenus
2aba4f261f Add has_entity_name attribute to LCN entities (#145045)
* Add _attr_has_entity_name

* Fix tests
2025-05-18 16:48:44 +02:00
elmurato
3eb0c8ddff Add Pterodactyl binary sensor tests (#142401)
* Add binary sensor tests

* Wait for background tasks as well in test_binary_sensor_update_failure

* Fix module docstring

* Use snapshot_platform, move constants to const.py, do not use snapshot for testing state updates

* Use JSON fixtures

* Use helper for loading JSON fixtures, remove unneeded mock in setup_integration

* Move mocks to pytest markers where possible
2025-05-18 16:46:11 +02:00
Andrea Turri
705a987547 Fix enum values for program phases by appliance type on Miele appliances (#144916) 2025-05-18 11:00:21 +02:00
J. Nick Koston
888f17c504 Bump google-maps-routing to 0.6.15 (#145130) 2025-05-18 09:11:13 +02:00
markhannon
2f4d0ede0f Bump zcc-helper to 3.5.2 (#144926) 2025-05-18 07:13:23 +02:00
Paulus Schoutsen
6fd9857666 OpenAI Conversation split out chat log processing (#145129) 2025-05-17 22:00:24 -07:00
XiaoXianNv-boot
f07265ece4 Set the default upgrade icon for the MQTT device to the default icon for Home Assistant instead of the icon for the MQTT integration (#144295)
* Set the default upgrade icon for the MQTT device to the default icon for Home Assistant instead of the icon for the MQTT integration

* Set the default upgrade icon for the MQTT device to the default icon for Home Assistant instead of the icon for the MQTT integration

* Set the default upgrade icon for the MQTT device to the default icon for Home Assistant instead of the icon for the MQTT integration

* Set the default upgrade icon for the MQTT device to the default icon for Home Assistant instead of the icon for the MQTT integration

* Fix failed tests

* Fix failed tests

* Cleanup unused helper option

* ruff

---------

Co-authored-by: jbouwh <jan@jbsoft.nl>
2025-05-18 01:30:04 +02:00
J. Nick Koston
a169d6ca97 Bump cryptography to 45.0.1 and pyopenssl to 25.1.0 (#145121) 2025-05-17 23:57:28 +02:00
Joost Lekkerkerker
ebed38c1dc Add Steam closet sanitize to SmartThings (#145110) 2025-05-17 21:03:24 +02:00
Joost Lekkerkerker
5619042fe7 Add Steam closet auto cycle link to SmartThings (#145111) 2025-05-17 20:39:17 +02:00
Joost Lekkerkerker
67b3428b07 Add Steam closet keep fresh mode to SmartThings (#145107) 2025-05-17 20:19:31 +02:00
Jan-Philipp Benecke
2302a3bb33 Add missing device condition translations to lock component (#145104) 2025-05-17 20:18:14 +02:00
Åke Strandberg
a83eafd949 Fix mapping from program_phase to vacuum_activity for Miele integration (#145115) 2025-05-17 20:17:15 +02:00
Paulus Schoutsen
2956f4fea1 Ensure that OpenAI tool call deltas have a role (#145085) 2025-05-17 09:36:14 -07:00
Robert Resch
180e1f462c Fix proberly Ecovacs mower area sensors (#145078) 2025-05-17 16:44:53 +02:00
cdnninja
2dc63eb8c5 Refactor fan in vesync (#135744)
* Refactor Fan

* Add tower fan tests and mode

* Schedule update after turn off

* Adjust updates to refresh library

* correct off command

* Revert changes

* Merge corrections

* Remove unused code to increase test coverage

* Ruff

* Tests

* Test for preset mode

* Adjust to increase coverage

* Test Corrections

* tests to match other PR

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-17 15:57:55 +02:00
Manu
4c40ec4948 Bump aiontfy to 0.5.2 (#145044) 2025-05-17 13:06:02 +02:00
starkillerOG
56b3dc02a7 Bump motionblinds to 0.6.27 (#145094) 2025-05-17 12:45:18 +02:00
Franck Nijhof
db5bcd9fc4 Pin rpds-py to 0.24.0 (#145074) 2025-05-16 22:39:05 +02:00
jb101010-2
c845f4e9b2 Bump pysuezV2 to 2.0.5 (#145047) 2025-05-16 22:33:14 +02:00
Joost Lekkerkerker
5aff3499a0 Add number entities for freezer setpoint in SmartThings (#145069) 2025-05-16 22:29:00 +02:00
Andre Lengwenus
a501451038 Remove address parameter from services.yaml (#145052) 2025-05-16 22:27:09 +02:00
Paulus Schoutsen
0deed82bea OpenAI prompt is optional (#145065) 2025-05-16 22:22:46 +02:00
starkillerOG
f9231de824 Add additional explanation for Reolink password requirements (#145000) 2025-05-16 22:12:59 +02:00
Joost Lekkerkerker
757c66613d Deprecate SmartThings water heater sensors (#145060) 2025-05-16 21:59:12 +02:00
epenet
9d2302f2f5 Use runtime_data in homeworks (#144944) 2025-05-16 21:57:36 +02:00
epenet
0bbbd2cd54 Use runtime_data in hydrawise (#144950) 2025-05-16 21:45:11 +02:00
Robert Resch
dbc15a2dda Fix Ecovacs mower area sensors (#145071) 2025-05-16 21:22:43 +02:00
Joost Lekkerkerker
7fefd58b84 Don't create entities for Smartthings smarttags (#145066) 2025-05-16 21:17:07 +02:00
Joost Lekkerkerker
87b60967a6 Map SmartThings auto mode correctly (#145061) 2025-05-16 20:14:41 +02:00
Joost Lekkerkerker
e80069545f Only set suggested area for new SmartThings devices (#145063) 2025-05-16 19:53:46 +02:00
Joost Lekkerkerker
be5685695e Fix fan AC mode in SmartThings AC (#145064) 2025-05-16 19:38:18 +02:00
Bram Kragten
6b769ac263 Update frontend to 20250516.0 (#145062) 2025-05-16 19:37:22 +02:00
Simone Chemelli
9114816384 Fix climate idle state for Comelit (#145059) 2025-05-16 18:51:30 +02:00
Ludovic BOUÉ
db3e596e48 Update Matter MicrowaveOven fixture (#145057)
Update microwave_oven.json

PowerInWatts feature
2025-05-16 18:19:36 +02:00
Joost Lekkerkerker
bdc21da076 Sync SmartThings EHS fixture (#145042) 2025-05-16 15:08:24 +02:00
epenet
a500eeb831 Use runtime_data in hue (#144946)
* Use runtime_data in hue

* More

* Tests
2025-05-16 08:35:46 -04:00
Joost Lekkerkerker
119d0c576a Add hood fan speed capability to SmartThings (#144919) 2025-05-16 13:39:03 +02:00
Bouwe Westerdijk
38cee53999 Small code optimization for Plugwise (#145037) 2025-05-16 13:28:31 +02:00
Joost Lekkerkerker
2ca9d4689e Set SmartThings oven setpoint to unknown if its 1 Fahrenheit (#145038) 2025-05-16 13:17:56 +02:00
Joost Lekkerkerker
8a32ffc7b9 Bump pySmartThings to 3.2.2 (#145033) 2025-05-16 13:10:58 +02:00
Matthias Alphart
6475b1a446 Ignore Fronius Gen24 firmware 1.35.4-1 SSL verification issue for new setups (#144940) 2025-05-16 12:58:59 +02:00
starkillerOG
07db244f91 Cleanup wrongly combined Reolink devices (#144771) 2025-05-16 12:58:28 +02:00
Joost Lekkerkerker
ff4aed1f6e Fix errors in strings in SmartThings (#145030) 2025-05-16 12:22:17 +02:00
Joost Lekkerkerker
3208815e10 Fix non-DHW heat pump in SmartThings (#145008) 2025-05-16 12:08:32 +02:00
epenet
b4a1bdcb83 Move huisbaasje coordinator to separate module (#144955) 2025-05-16 12:07:19 +02:00
epenet
97869636f8 Use typed config entry in Habitica coordinator (#144956) 2025-05-16 11:59:11 +02:00
epenet
cbb092f792 Move icloud services to separate module (#144980) 2025-05-16 11:56:07 +02:00
Sanjay Govind
0c5ee37721 Update bosch_alarm door switch strings so they are more user friendly (#144607)
* Update door switch strings so they are more user friendly

* Update door switch strings so they are more user friendly

* Update door switch strings so they are more user friendly

* update strings

* update strings
2025-05-16 11:43:31 +02:00
epenet
e74aeeab1a Use runtime_data in iaqualink (#144988) 2025-05-16 11:41:16 +02:00
Sanjay Govind
b8df9c7e97 Set parallel_updates for bosch_alarm (#145028) 2025-05-16 11:26:22 +02:00
epenet
82a9e67b7e Use generic in iaqualink entity (#144989) 2025-05-16 10:53:24 +02:00
Joost Lekkerkerker
7410b8778a Deprecate DHW switch for SmartThings (#145011) 2025-05-16 10:47:23 +02:00
epenet
3e92f23680 Cleanup huisbaasje tests (#144954) 2025-05-16 10:38:17 +02:00
rjblake
3942e6a841 Fix some Home Connect translation strings (#144905)
* Update strings.json

Corrected program names:
changed "Pre_rinse" to "Pre-Rinse"
changed "Kurz 60°C" to "Speed 60°C"

Both match the Home Connect app; although the UK documentation refers to "Speed 60°C" as "Quick 60°C"

* Adjust casing

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-16 10:37:11 +02:00
Joost Lekkerkerker
e76b483067 Add lamp capability to SmartThings (#144918) 2025-05-16 10:36:58 +02:00
Retha Runolfsson
3de740ed1e Bump PySwitchbot to 0.62.2 (#145018) 2025-05-16 10:30:30 +02:00
Bouwe Westerdijk
bbe975baef Bump plugwise to v1.7.4 (#145021) 2025-05-16 10:28:57 +02:00
Sid
6dff975711 Initialize select _attr_current_option with None (#145026) 2025-05-16 10:27:59 +02:00
Jan Bouwhuis
71108d9ca0 Do not show an empty component name on MQTT device subentries not as None if it is not set (#144792) 2025-05-16 10:26:00 +02:00
puddly
053e5417a7 Strip _CLIENT suffix from ZHA event unique_id (#145006) 2025-05-16 10:25:24 +02:00
Sanjay Govind
9bbc49e842 Add DHCP discovery flow to bosch_alarm (#142250)
* Add dhcp discovery

* Update homeassistant/components/bosch_alarm/config_flow.py

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

* put mac address in entry instead of unique id

* Update host and mac via dhcp discovery

* add mac to connections

* Abort dhcp flow if there is already an ongoing flow

* apply changes from review

* apply change from review

* remove outdated test

* fix snapshots

* apply change from review

* clean definition for connections

* update quality scale

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-16 10:21:41 +02:00
dependabot[bot]
270780ef5f Bump docker/build-push-action from 6.16.0 to 6.17.0 (#145022)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-16 09:42:24 +02:00
dependabot[bot]
e15963b422 Bump codecov/codecov-action from 5.4.2 to 5.4.3 (#145023)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-16 08:54:13 +02:00
starkillerOG
52e8196d0a Mark Reolink doorbell visitor sensor as always available (#145002)
Mark doorbell visitor sensor as always available
2025-05-15 20:34:55 -04:00
Odd Stråbø
cc62943835 Fix ESPHome entities unavailable if deep sleep enabled after entry setup (#144970) 2025-05-15 18:57:16 -05:00
epenet
d195726ed2 Use runtime_data in isy994 (#144961) 2025-05-15 13:50:48 -05:00
epenet
50e6c83dd8 Fix missing mock in hue v2 bridge tests (#144947) 2025-05-15 13:53:12 -04:00
alorente
3a58d97496 Fix wrong UNIT_CLASS for reactive energy converter (#144982) 2025-05-15 18:27:16 +01:00
epenet
ace12958d1 Use runtime_data in iqvia (#144984) 2025-05-15 17:48:02 +02:00
Joost Lekkerkerker
d33a0f75fd Add water heater support to SmartThings (#144927)
* Add another EHS SmartThings fixture

* Add another EHS

* Add water heater support to SmartThings

* Add water heater support to SmartThings

* Add water heater support to SmartThings

* Add water heater support to SmartThings

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Add more tests

* Make target temp setting conditional

* Make target temp setting conditional

* Finish tests

* Fix
2025-05-15 17:42:38 +02:00
Erik Montnemery
d24a60777b Fix Home Assistant Yellow config entry data (#144948) 2025-05-15 10:07:53 -04:00
epenet
f2a3a5cbbd Move iqvia coordinator to separate module (#144969)
* Move iqvia coordinator to separate module

* Adjust
2025-05-15 15:50:46 +02:00
Petro31
3bf9908789 Add template vacuum modern style (#144843)
* Add template vacuum modern style

* address comments and add tests for coverage

* address comments

* update vacuum and sort domains
2025-05-15 15:46:00 +02:00
epenet
912798ee34 Use runtime_data in intellifire (#144979) 2025-05-15 14:57:26 +02:00
epenet
28990e1db5 Use runtime_data in ipma (#144972)
* Use runtime_data in ipma

* Cleanup const
2025-05-15 14:43:58 +02:00
epenet
e8281bb009 Use runtime_data in iotawatt (#144977) 2025-05-15 14:43:35 +02:00
Robert Resch
334f9deaec Bump deebot-client to 13.2.0 (#144957) 2025-05-15 13:46:15 +02:00
alorente
1d47dc41c9 Add reactive energy device class and units (#143941) 2025-05-15 12:05:46 +01:00
Petro31
66ecc4d69d Add modern configuration for template alarm control panel (#144834)
* Add modern configuration for template alarm control panel

* address comments and add tests for coverage

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-05-15 11:46:57 +02:00
starkillerOG
fa3edb5c01 Fix Netgear handeling of missing MAC in device registry (#144722) 2025-05-15 10:56:54 +02:00
Petro31
ea046f32be Add modern style template lock (#144756)
* Add modern style lock

* add tests

* Add tests and address comments

* Update homeassistant/components/template/lock.py

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-05-15 10:43:56 +02:00
markhannon
fd09476b28 Add sensor entity to Zimi integration (#144329)
* Import sensor.py

* Light design alignment

* Fix merge error

* Refactor with extend

* Update homeassistant/components/zimi/sensor.py

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

* value_fn and inline refactoring

* strings.json and translation_key

* Add sensor_name

* Revert "Add sensor_name"

This reverts commit ad3da048e9c5a6ecdb15052c253de7dc46b1120f.

* Default naming for sensors

* Remove uneeded 'garage' and use default battery name

* Bump to zcc-helper 3.5.2 which maps "Garage Controller" tp "Garage" in device.name

* Update homeassistant/components/zimi/sensor.py

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

* Update homeassistant/components/zimi/sensor.py

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

* Update strings.json

* Revert "Bump to zcc-helper 3.5.2 which maps "Garage Controller" tp "Garage" in device.name"

This reverts commit 345ef8a4859c8d0e188462c9a69a4fab8f284b69.

* Update homeassistant/components/zimi/sensor.py

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-05-15 10:12:18 +02:00
Alexandre CUER
7c306acd5d Emoncms remove useless var in tests (#144942) 2025-05-15 09:48:01 +02:00
G Johansson
9c4733595a Fix unknown Pure AQI in Sensibo (#144924)
* Fix unknown Pure AQI in Sensibo

* Fix mypy
2025-05-15 10:27:48 +03:00
Petro31
c7cf9585ae Add modern style configuration for template fan (#144751)
* add modern template fan

* address comments and add tests for coverage
2025-05-15 08:18:37 +02:00
J. Nick Koston
301ca88f41 Bump aioesphomeapi to 31.0.1 (#144939) 2025-05-14 22:27:25 -05:00
peteS-UK
9a0fed89bd Translate raised exceptions for Squeezebox (#144842)
* initial

* tweak

* review updates
2025-05-14 19:39:00 -04:00
Joost Lekkerkerker
2050b0b375 Add another EHS SmartThings fixture (#144920)
* Add another EHS SmartThings fixture

* Add another EHS
2025-05-14 23:23:18 +02:00
epenet
34c7c3f384 Use runtime_data in homematicip_cloud (#144892) 2025-05-14 23:14:02 +02:00
epenet
3b9d8e00bc Use runtime_data and HassKey in geofency (#144886) 2025-05-14 23:13:37 +02:00
Abílio Costa
6b35b069b2 Remove duplicated code in unit conversion util (#144912) 2025-05-14 22:05:29 +01:00
Paulus Schoutsen
9428127021 Add media search and play intent (#144269)
* Add media search intent

* Add PLAY_MEDIA as required feature and remove explicit responses

---------

Co-authored-by: Michael Hansen <mike@rhasspy.org>
2025-05-14 15:45:40 -04:00
Sanjay Govind
1e8843947c Add sensor for alarm status in bosch_alarm (#142564)
* Add sensor for alarm status

* style fixes

* fix icons

* style fixes

* update tests

* apply change from code review

* add alarm to alarm sensor state

* Apply changes from review
2025-05-14 21:00:41 +02:00
Sanjay Govind
dbdffbba23 Add binary sensors to bosch_alarm (#142147)
* Add binary sensors to bosch_alarm

* make one device per sensor, remove device class guessing

* fix tests

* update tests

* Apply suggested changes

* add binary sensors

* make fault sensors diagnostic

* update tests

* update binary sensors to use base entity

* fix strings

* fix icons

* add state translations for area ready sensors

* use constants in tests

* apply changes from review

* remove fault prefix, use default translation for battery low

* update tests
2025-05-14 20:56:08 +02:00
Daniel Hjelseth Høyer
460f02ede5 Update mill library 0.12.5 (#144911)
* Update mill library 0.12.5

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

* Update mill library 0.12.5

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

---------

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2025-05-14 20:46:28 +02:00
Sid
0eb6c88bc5 Add system LED brightness to eheimdigital (#144915) 2025-05-14 20:45:58 +02:00
Nick Kuiper
4b7650f2d2 Add buttons to Blue current integration (#143964)
* Add buttons to Blue current integration

* Apply feedback

* Changed configEntry to use the BlueCurrentConfigEntry.

The connector is now accessed via the entry instead of hass.data.

* Changed test_buttons_created test to use the snapshot_platform function.

Also removed the entry.unique_id check in the test_charge_point_buttons function because this is not needed anymore, according to https://github.com/home-assistant/core/pull/114000#discussion_r1627201872

* Applied requested changes.

Changes requested by joostlek.

* Moved has_value from BlueCurrentEntity to class level.

This value was still inside the __init__ function, so the value was not overwritten by the ChargePointButton.

---------

Co-authored-by: Floris272 <florispuijk@outlook.com>
2025-05-14 19:37:16 +02:00
Daniel Hjelseth Høyer
8004c6605b Update Tibber lib 0.31.2 (#144908)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2025-05-14 19:25:01 +02:00
Marc Hörsken
9d451b6358 Add support for identify buttons to WMS WebControl pro (#143339)
* Remove _attr_name = None from generic base class

* Add support for identify buttons to WMS WebControl pro

* Fix PERF401 as suggested by joostlek

* Fix fixture name after rebase

* Split test

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-14 18:06:21 +02:00
LG-ThinQ-Integration
7963665c40 Add fan for ventilator (#142444)
Add ventilator device

Co-authored-by: yunseon.park <yunseon.park@lge.com>
2025-05-14 17:58:25 +02:00
Erik Montnemery
d44a34ce1e Refactor DeviceAutomationTriggerProtocol (#144888) 2025-05-14 17:24:19 +02:00
Joost Lekkerkerker
49b7559b1f Fix snapshots in APC (#144901) 2025-05-14 17:14:57 +02:00
Glenn Vandeuren (aka Iondependent)
43b1dd64a7 Handle unit conversion in lib for niko_home_control (#141837)
* handle unit conversion in lib

* bump lib

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-14 17:13:06 +02:00
Matthias Alphart
2d0c1fac24 Fix "tunneling" spelling in KNX (#144895) 2025-05-14 17:05:45 +02:00
Andre Lengwenus
a0f35a84ae Positioning for LCN covers (#143588)
* Fix motor control function names

* Add position logic for BS4

* Use helper methods from pypck

* Add motor positioning to domain_data schema

* Fix tests

* Add motor positioning via module

* Invert motor cover positions

* Merge relay cover classes back into one class

* Update snapshot for covers

* Revert bump lcn-frontend to 0.2.4
2025-05-14 16:49:30 +02:00
epenet
4bc5987f36 Use runtime_data in rachio (#144896) 2025-05-14 16:46:36 +02:00
Yuxin Wang
11644d48ee Use snapshot testing for APCUPSD integration (#130770)
* First try to use snapshot testing for sensors

* Use snapshot testing

* Add ambr files

* Update comment

* Address review comments

* Remove duplicate async init integration call

* Add device test for cases w/o SERIALNO

* Use friendlier snapshot names

* Use * to mandate keyed argument for async_init_integration

* Always pass mock config entry ID

* Fix incorrect ID
2025-05-14 16:04:07 +02:00
Petro31
d273a92a19 Refactor template optional configuration attributes (#144887) 2025-05-14 15:54:40 +02:00
Brian Rogers
b0ff4b5841 Add flow detection to Rachio hose timer (#144075)
* flow binary sensor

* rename property

* Move const and update coordinator reference

* update controller descriptions

* Address review comments

* Use lookup for rain sensor

* Update online binary sensor

* make it a bit more readable

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-05-14 15:01:01 +02:00
epenet
ef99658919 Use runtime_data in gpslogger (#144884) 2025-05-14 14:59:10 +02:00
epenet
a9238c7577 Use entry.async_on_unload in gpslogger (#144883) 2025-05-14 14:31:50 +02:00
Brett Adams
993e98a43f Fix pin strings in Teslemetry (#144873)
pinstring
2025-05-14 14:31:41 +02:00
epenet
10dd11f257 Use HassKey in greeneye_monitor (#144878) 2025-05-14 14:30:45 +02:00
epenet
fb9be3da79 Use entry.async_on_unload in geofency (#144882) 2025-05-14 14:30:02 +02:00
Åke Strandberg
3b1a33d606 Fix substitutions in strings.json in Miele integration (#144881)
Fix substitutions in strings.json
2025-05-14 14:14:48 +02:00
epenet
710e18f399 Use runtime_data in gree (#144880) 2025-05-14 14:06:40 +02:00
Maximilian Arzberger
67b9904740 Add Kostal plenticore Installer login support (#133773)
* feat: Add Installer login, Add ManualCharge Switch

* remove unnecessary field

* replace strings with consts

* change to CONF and camel_case

* Improve existing code

* Add translation string

* format code

* add service code test

* format code

* format code

* remove manual charge switch

* add reconfigure config flow

* fix flow

* add return type

* add reconfigure strings

* adjust tests

* change string

* simlify tests

* add reconfigure test

* add more tests

* Fix

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-14 14:05:23 +02:00
Robert Resch
e413e9b93b Add mac address to airgradient devices (#144876) 2025-05-14 13:40:38 +02:00
Matthias Alphart
5c86042b31 Add Fronius current and voltage for up to 4 MPP trackers (#140120)
Support current and voltage of up to 4 MPP trackers
2025-05-14 13:37:02 +02:00
Martin Hjelmare
e89333811e Improve Z-Wave config flow tests (#144871)
* Improve Z-Wave config flow tests

* Fix test

* Use identify check for result type
2025-05-14 13:08:26 +02:00
Dmytro Tkach
4f723232e3 Add modbus light brightness and color temperature (#139703)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-05-14 12:07:19 +01:00
Åke Strandberg
48520d90ef Add plate sensors for Miele hobs (#144400)
* Add plate sensors for miele hobs

* Address review comments

* Update snapshot
2025-05-14 13:02:05 +02:00
Jeremiah Paige
2fdda91cb8 Fix pandora.media_player to not sleep during event loop (#141957)
* Fix pandora.media_player to not sleep during event loop

* factor out pianobar spawn

* linting cleanup

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-14 12:34:40 +02:00
Sören Beye
c023f610dd Introduce recorder.get_statistics service (#142602)
Co-authored-by: abmantis <amfcalt@gmail.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-05-14 11:28:32 +01:00
epenet
161b62d8fa Drop alias from local DOMAIN import (#144867) 2025-05-14 12:24:46 +02:00
Brett Adams
8ccedd4064 Add credit balance sensor to Teslemetry (#144365)
* Add credits

* Credits string and icon

* Add test

* tests and fixes

* Add units

* Update homeassistant/components/teslemetry/sensor.py

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

* Update snapshot with unit

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-14 12:21:45 +02:00
Rob Bierbooms
9a06584a1d Bump influxdb-client to 1.48.0 (#144845)
* Bump influxdb-client to 1.48.0

* Adjust typing, fix mypy

* Update homeassistant/components/influxdb/__init__.py

* Update homeassistant/components/influxdb/__init__.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-14 12:21:26 +02:00
Daniël van den Berg
a21e586140 Show Sonos playlists under favorites (#142357)
* Update media_browser.py

* Update favorites.py

* Update media_player.py

* Update media_browser.py

* Update media_player.py

* Update favorites.py

* Update media_browser.py

* Update media_player.py

* Update favorites.py

* Added/fixed testing for showing sonos native playlists in the media browser

* Create a DidlFavorite to wrap playlists.

* Processed PR feedback
2025-05-14 12:14:20 +02:00
epenet
91f01d660f Move ps4 services to separate module (#144870) 2025-05-14 12:04:43 +02:00
peteS-UK
1748dbd60f Add parallel_updates to new updates platform for Squeezebox (#144864)
initial
2025-05-14 11:59:28 +02:00
starkillerOG
5acae7f86d Fix Reolink setup when ONVIF push is unsupported (#144869)
* Fix setup when ONVIF push is not supported

* fix styling
2025-05-14 11:58:29 +02:00
epenet
30ecba9944 Finish cleaning up SamsungTV init tests (#144865)
FInish cleaning up SamsungTV init tests
2025-05-14 11:58:01 +02:00
epenet
4287df5f3d Use HassKey in ps4 (#144868) 2025-05-14 11:51:32 +02:00
hahn-th
063deab3cb Doorbell Event is fired just once in homematicip_cloud (#144357)
* fire event if event type if correct

* Fix requested changes
2025-05-14 11:44:59 +02:00
dependabot[bot]
27798a6004 Bump actions/dependency-review-action from 4.7.0 to 4.7.1 (#144856)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/v4.7.0...v4.7.1)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: 4.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 11:43:14 +02:00
Luke Lashley
577ddd9021 Bump python-snoo to 0.6.6 (#144849) 2025-05-14 11:42:43 +02:00
Allen Porter
34663e160d Bump ical to 9.2.4 (#144852) 2025-05-14 11:42:22 +02:00
Norbert Rittel
ac54b81289 Fix spelling of "IP address" in plugwise (#144861) 2025-05-14 11:01:14 +03:00
Penny Wood
67174fb07e Remove myself as code owner of sun component (#144854)
* Remove myself as code owner

I'm haven't actively been working on this for some time. Have removed myself as a code owner.

* cleanup

* cleanup

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-05-14 09:37:51 +03:00
Brett Adams
d2a692393f Fix wall connector states in Teslemetry (#144855)
* Fix wall connector

* Update snapshot
2025-05-14 08:08:24 +02:00
Åke Strandberg
9aa2664188 Change unknown to unknown_code for missing Miele codes to avoid confusion (#144699)
* Change unknown to unknown_code

* Update snapshot

* Automatically replace missing codes with None

* Update snapshot
2025-05-14 08:07:38 +02:00
David Rapan
ab5d60e33d Make DHCP discovery aware of the network integration (#144767)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-05-14 00:59:48 -05:00
epenet
31847d8cfb Adjust handling of SamsungTV misaligned MAC (#144810)
* Cleanup SamsungTV misaligned MAC formatting

* Simplify

* One more

* Revert and add comment

* Adjust comment

* One more
2025-05-14 07:57:33 +02:00
John Hillery
9729f1f38b Provide ability to select nexia RoomIQ sensors (#144278)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-05-13 23:16:05 -05:00
peteS-UK
6bc6733c40 Add config flow data descriptions to Squeezebox (#144619)
* add data_descriptions

* tweaks

* review updates
2025-05-13 21:10:47 -07:00
Tsvi Mostovicz
b1ffcb4245 Jewish calendar - Fix Parasha values (#144646)
* Fix Parasha values

* Fix test

* Update sensor.py
2025-05-13 21:08:47 -07:00
Josef Zweck
f0c5fbfb8a Bump pylamarzocco to 2.0.3 (#144825) 2025-05-13 21:04:38 -07:00
J. Nick Koston
c76239806d Bump aioesphomeapi to 31.0.0 (#144778)
* aioesphomeapi update

* Bump aioesphomeapi to 31.0.0

There are some breaking changes with the protobuf naming and types
required some refactoring

changelog: https://github.com/esphome/aioesphomeapi/compare/v30.2.0...v31.0.0

* actually include the commit to bump the lib
2025-05-13 20:39:53 -04:00
Abílio Costa
6d809b0b5a Add service response support to admin services (#144837) 2025-05-13 21:57:15 +01:00
Norbert Rittel
de2cbb7f5c Improve user-facing strings of incomfort (#144844)
* Improvements in user-facing strings of `incomfort`

Fix spelling of "IP address" and "timeout"

Remove "temperature" from "Shortcut outside sensor temperature" as this makes no sense and leads to completely wrong translations. This is to indicate an electrical shortcut on the sensor so this should be the last word.

This also matches the naming in the user manual.

* Suggestion from review

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

---------

Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-05-13 22:47:21 +02:00
Erik Montnemery
cd61f37df7 Remove support for condition platforms defining only a CONDITION_SCHEMA (#144832) 2025-05-13 20:53:08 +02:00
epenet
26796f87cd Add device registry snapshots to samsungtv tests (#144804)
* Add device registry snapshots to samsungtv tests

* Simplify

* Adjust

* Reduce
2025-05-13 18:20:43 +02:00
Åke Strandberg
e2dd897ac7 Bump dependency pymiele -> 0.5.2 (#144758) 2025-05-13 18:19:49 +02:00
Retha Runolfsson
3bbe4baaf7 Update codeowner for switchbot Integration (#144829)
update codeowners
2025-05-13 18:16:05 +02:00
Alistair Francis
d409b86217 Bump automower-ble to 0.2.1 (#144817) 2025-05-13 14:21:56 +01:00
Josef Zweck
7928c15849 Fix blocking call in azure_storage config flow (#144818)
* Fix blocking call in azure_storage config flow

* Fix blocking call in azure_storage config_flow as well

* move session getting to event flow
2025-05-13 14:23:41 +02:00
epenet
d197debbc0 Improve SamsungTV config flow type hints (#144820) 2025-05-13 14:02:07 +02:00
Martin Hjelmare
55b9dee448 Fix Z-Wave unique id after controller reset (#144813) 2025-05-13 14:12:00 +03:00
epenet
5c6984d326 Do not abort on invalid host in SamsungTV user flow (#144794) 2025-05-13 10:47:26 +02:00
Josef Zweck
a7787d6080 Fix blocking call in azure storage (#144803) 2025-05-13 10:46:46 +02:00
Jeremiah Paige
2db60340c2 Add typing to wsdot (#143117)
* increase wsdot typing

* remove Final types

* help out mypy

* simplify wsdot types

* minor wsdot type changes

* type wsdot state
2025-05-13 10:43:03 +02:00
Mick Vleeshouwer
c121631fef Refactor config flow tests to improve result variable usage in Overkiz (#143374)
* Refactor test setup for unique ID migration in Overkiz integration

* Refactor test cases to unify result variable usage in Overkiz config flow tests (resultn -> result)

* Revert change in test_init
2025-05-13 10:35:32 +02:00
epenet
b0fb16d48d Remove obsolete compatibility code from SamsungTV (#144800) 2025-05-13 09:54:26 +02:00
Franck Nijhof
3e07f6543e Update debugpy to v1.8.14 (#144755) 2025-05-13 08:14:55 +02:00
Brett Adams
d4c2356c70 Create stream on demand in Teslemetry (#144777)
Create stream on demand
2025-05-13 08:05:33 +02:00
epenet
eec617b391 Add comments to samsungtv config flow tests (#144787) 2025-05-13 07:54:37 +02:00
1580 changed files with 57762 additions and 15239 deletions

View File

@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.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@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile

View File

@@ -653,7 +653,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Dependency review
uses: actions/dependency-review-action@v4.7.0
uses: actions/dependency-review-action@v4.7.1
with:
license-check: false # We use our own license audit checks
@@ -944,7 +944,8 @@ jobs:
bluez \
ffmpeg \
libturbojpeg \
libgammu-dev
libgammu-dev \
libxml2-utils
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
@@ -1020,6 +1021,12 @@ jobs:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
overwrite: true
- name: Beautify test results
# For easier identification of parsing errors
if: needs.info.outputs.skip_coverage != 'true'
run: |
xmllint --format "junit.xml" > "junit.xml-tmp"
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@v4.6.2
@@ -1070,7 +1077,8 @@ jobs:
bluez \
ffmpeg \
libturbojpeg \
libmariadb-dev-compat
libmariadb-dev-compat \
libxml2-utils
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
@@ -1154,6 +1162,12 @@ jobs:
steps.pytest-partial.outputs.mariadb }}
path: coverage.xml
overwrite: true
- name: Beautify test results
# For easier identification of parsing errors
if: needs.info.outputs.skip_coverage != 'true'
run: |
xmllint --format "junit.xml" > "junit.xml-tmp"
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@v4.6.2
@@ -1202,7 +1216,8 @@ jobs:
sudo apt-get -y install \
bluez \
ffmpeg \
libturbojpeg
libturbojpeg \
libxml2-utils
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
sudo apt-get -y install \
postgresql-server-dev-14
@@ -1290,6 +1305,12 @@ jobs:
steps.pytest-partial.outputs.postgresql }}
path: coverage.xml
overwrite: true
- name: Beautify test results
# For easier identification of parsing errors
if: needs.info.outputs.skip_coverage != 'true'
run: |
xmllint --format "junit.xml" > "junit.xml-tmp"
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@v4.6.2
@@ -1320,7 +1341,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v5.4.2
uses: codecov/codecov-action@v5.4.3
with:
fail_ci_if_error: true
flags: full-suite
@@ -1357,7 +1378,8 @@ jobs:
bluez \
ffmpeg \
libturbojpeg \
libgammu-dev
libgammu-dev \
libxml2-utils
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
@@ -1436,6 +1458,12 @@ jobs:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
overwrite: true
- name: Beautify test results
# For easier identification of parsing errors
if: needs.info.outputs.skip_coverage != 'true'
run: |
xmllint --format "junit.xml" > "junit.xml-tmp"
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@v4.6.2
@@ -1463,7 +1491,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v5.4.2
uses: codecov/codecov-action@v5.4.3
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

View File

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

View File

@@ -270,6 +270,7 @@ homeassistant.components.image_processing.*
homeassistant.components.image_upload.*
homeassistant.components.imap.*
homeassistant.components.imgw_pib.*
homeassistant.components.immich.*
homeassistant.components.incomfort.*
homeassistant.components.input_button.*
homeassistant.components.input_select.*
@@ -385,6 +386,7 @@ homeassistant.components.overseerr.*
homeassistant.components.p1_monitor.*
homeassistant.components.pandora.*
homeassistant.components.panel_custom.*
homeassistant.components.paperless_ngx.*
homeassistant.components.peblar.*
homeassistant.components.peco.*
homeassistant.components.pegel_online.*

23
CODEOWNERS generated
View File

@@ -202,8 +202,8 @@ build.json @home-assistant/supervisor
/tests/components/blebox/ @bbx-a @swistakm
/homeassistant/components/blink/ @fronzbot @mkmer
/tests/components/blink/ @fronzbot @mkmer
/homeassistant/components/blue_current/ @Floris272 @gleeuwen
/tests/components/blue_current/ @Floris272 @gleeuwen
/homeassistant/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
/tests/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
/homeassistant/components/bluemaestro/ @bdraco
/tests/components/bluemaestro/ @bdraco
/homeassistant/components/blueprint/ @home-assistant/core
@@ -710,6 +710,8 @@ build.json @home-assistant/supervisor
/tests/components/imeon_inverter/ @Imeon-Energy
/homeassistant/components/imgw_pib/ @bieniu
/tests/components/imgw_pib/ @bieniu
/homeassistant/components/immich/ @mib1185
/tests/components/immich/ @mib1185
/homeassistant/components/improv_ble/ @emontnemery
/tests/components/improv_ble/ @emontnemery
/homeassistant/components/incomfort/ @jbouwh
@@ -1138,6 +1140,8 @@ build.json @home-assistant/supervisor
/tests/components/palazzetti/ @dotvav
/homeassistant/components/panel_custom/ @home-assistant/frontend
/tests/components/panel_custom/ @home-assistant/frontend
/homeassistant/components/paperless_ngx/ @fvgarrel
/tests/components/paperless_ngx/ @fvgarrel
/homeassistant/components/peblar/ @frenck
/tests/components/peblar/ @frenck
/homeassistant/components/peco/ @IceBotYT
@@ -1176,6 +1180,8 @@ build.json @home-assistant/supervisor
/tests/components/powerwall/ @bdraco @jrester @daniel-simpson
/homeassistant/components/private_ble_device/ @Jc2k
/tests/components/private_ble_device/ @Jc2k
/homeassistant/components/probe_plus/ @pantherale0
/tests/components/probe_plus/ @pantherale0
/homeassistant/components/profiler/ @bdraco
/tests/components/profiler/ @bdraco
/homeassistant/components/progettihwsw/ @ardaseremet
@@ -1222,6 +1228,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/qnap_qsw/ @Noltari
/tests/components/qnap_qsw/ @Noltari
/homeassistant/components/quantum_gateway/ @cisasteelersfan
/tests/components/quantum_gateway/ @cisasteelersfan
/homeassistant/components/qvr_pro/ @oblogic7
/homeassistant/components/qwikswitch/ @kellerza
/tests/components/qwikswitch/ @kellerza
@@ -1484,8 +1491,8 @@ build.json @home-assistant/supervisor
/tests/components/subaru/ @G-Two
/homeassistant/components/suez_water/ @ooii @jb101010-2
/tests/components/suez_water/ @ooii @jb101010-2
/homeassistant/components/sun/ @Swamp-Ig
/tests/components/sun/ @Swamp-Ig
/homeassistant/components/sun/ @home-assistant/core
/tests/components/sun/ @home-assistant/core
/homeassistant/components/supla/ @mwegrzynek
/homeassistant/components/surepetcare/ @benleb @danielhiversen
/tests/components/surepetcare/ @benleb @danielhiversen
@@ -1498,8 +1505,8 @@ build.json @home-assistant/supervisor
/tests/components/switch_as_x/ @home-assistant/core
/homeassistant/components/switchbee/ @jafar-atili
/tests/components/switchbee/ @jafar-atili
/homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
/tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
/homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski @zerzhang
/tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski @zerzhang
/homeassistant/components/switchbot_cloud/ @SeraphicRav @laurence-presland @Gigatrappeur
/tests/components/switchbot_cloud/ @SeraphicRav @laurence-presland @Gigatrappeur
/homeassistant/components/switcher_kis/ @thecode @YogevBokobza
@@ -1539,8 +1546,8 @@ build.json @home-assistant/supervisor
/tests/components/tedee/ @patrickhilker @zweckj
/homeassistant/components/tellduslive/ @fredrike
/tests/components/tellduslive/ @fredrike
/homeassistant/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
/tests/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
/homeassistant/components/template/ @Petro31 @home-assistant/core
/tests/components/template/ @Petro31 @home-assistant/core
/homeassistant/components/tesla_fleet/ @Bre77
/tests/components/tesla_fleet/ @Bre77
/homeassistant/components/tesla_wall_connector/ @einarhauks

View File

@@ -6,6 +6,7 @@ from typing import Any, Concatenate
from airgradient import AirGradientConnectionError, AirGradientError, get_model_name
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -29,6 +30,7 @@ class AirGradientEntity(CoordinatorEntity[AirGradientCoordinator]):
model_id=measures.model,
serial_number=coordinator.serial_number,
sw_version=measures.firmware_version,
connections={(dr.CONNECTION_NETWORK_MAC, coordinator.serial_number)},
)

View File

@@ -142,7 +142,7 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
return AT_TO_HA_STATE[self._airtouch.acs[self._ac_number].AcMode]
@property
def hvac_modes(self):
def hvac_modes(self) -> list[HVACMode]:
"""Return the list of available operation modes."""
airtouch_modes = self._airtouch.GetSupportedCoolingModesForAc(self._ac_number)
modes = [AT_TO_HA_STATE[mode] for mode in airtouch_modes]
@@ -226,12 +226,12 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
return super()._handle_coordinator_update()
@property
def min_temp(self):
def min_temp(self) -> float:
"""Return Minimum Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MinSetpoint
@property
def max_temp(self):
def max_temp(self) -> float:
"""Return Max Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint

View File

@@ -17,4 +17,11 @@ CONF_THINKING_BUDGET = "thinking_budget"
RECOMMENDED_THINKING_BUDGET = 0
MIN_THINKING_BUDGET = 1024
THINKING_MODELS = ["claude-3-7-sonnet-20250219", "claude-3-7-sonnet-latest"]
THINKING_MODELS = [
"claude-3-7-sonnet-20250219",
"claude-3-7-sonnet-latest",
"claude-opus-4-20250514",
"claude-opus-4-0",
"claude-sonnet-4-20250514",
"claude-sonnet-4-0",
]

View File

@@ -294,6 +294,8 @@ async def _transform_stream( # noqa: C901 - This is complex, but better to have
elif isinstance(response, RawMessageDeltaEvent):
if (usage := response.usage) is not None:
chat_log.async_trace(_create_token_stats(input_usage, usage))
if response.delta.stop_reason == "refusal":
raise HomeAssistantError("Potential policy violation detected")
elif isinstance(response, RawMessageStopEvent):
if current_message is not None:
messages.append(current_message)
@@ -326,6 +328,7 @@ class AnthropicConversationEntity(
_attr_has_entity_name = True
_attr_name = None
_attr_supports_streaming = True
def __init__(self, entry: AnthropicConfigEntry) -> None:
"""Initialize the agent."""

View File

@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/anthropic",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["anthropic==0.47.2"]
"requirements": ["anthropic==0.52.0"]
}

View File

@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["pyaprilaire"],
"requirements": ["pyaprilaire==0.8.1"]
"requirements": ["pyaprilaire==0.9.0"]
}

View File

@@ -20,9 +20,6 @@ import hass_nabucasa
import voluptuous as vol
from homeassistant.components import conversation, stt, tts, wake_word, websocket_api
from homeassistant.components.tts import (
generate_media_source_id as tts_generate_media_source_id,
)
from homeassistant.const import ATTR_SUPPORTED_FEATURES, MATCH_ALL
from homeassistant.core import Context, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
@@ -92,6 +89,8 @@ KEY_ASSIST_PIPELINE: HassKey[PipelineData] = HassKey(DOMAIN)
KEY_PIPELINE_CONVERSATION_DATA: HassKey[dict[str, PipelineConversationData]] = HassKey(
"pipeline_conversation_data"
)
# Number of response parts to handle before streaming the response
STREAM_RESPONSE_CHARS = 60
def validate_language(data: dict[str, Any]) -> Any:
@@ -555,7 +554,7 @@ class PipelineRun:
event_callback: PipelineEventCallback
language: str = None # type: ignore[assignment]
runner_data: Any | None = None
intent_agent: str | None = None
intent_agent: conversation.AgentInfo | None = None
tts_audio_output: str | dict[str, Any] | None = None
wake_word_settings: WakeWordSettings | None = None
audio_settings: AudioSettings = field(default_factory=AudioSettings)
@@ -591,6 +590,9 @@ class PipelineRun:
_intent_agent_only = False
"""If request should only be handled by agent, ignoring sentence triggers and local processing."""
_streamed_response_text = False
"""If the conversation agent streamed response text to TTS result."""
def __post_init__(self) -> None:
"""Set language for pipeline."""
self.language = self.pipeline.language or self.hass.config.language
@@ -652,6 +654,11 @@ class PipelineRun:
"token": self.tts_stream.token,
"url": self.tts_stream.url,
"mime_type": self.tts_stream.content_type,
"stream_response": (
self.tts_stream.supports_streaming_input
and self.intent_agent
and self.intent_agent.supports_streaming
),
}
self.process_event(PipelineEvent(PipelineEventType.RUN_START, data))
@@ -899,12 +906,12 @@ class PipelineRun:
) -> str:
"""Run speech-to-text portion of pipeline. Returns the spoken text."""
# Create a background task to prepare the conversation agent
if self.end_stage >= PipelineStage.INTENT:
if self.end_stage >= PipelineStage.INTENT and self.intent_agent:
self.hass.async_create_background_task(
conversation.async_prepare_agent(
self.hass, self.intent_agent, self.language
self.hass, self.intent_agent.id, self.language
),
f"prepare conversation agent {self.intent_agent}",
f"prepare conversation agent {self.intent_agent.id}",
)
if isinstance(self.stt_provider, stt.Provider):
@@ -1045,7 +1052,7 @@ class PipelineRun:
message=f"Intent recognition engine {engine} is not found",
)
self.intent_agent = agent_info.id
self.intent_agent = agent_info
async def recognize_intent(
self,
@@ -1078,7 +1085,7 @@ class PipelineRun:
PipelineEvent(
PipelineEventType.INTENT_START,
{
"engine": self.intent_agent,
"engine": self.intent_agent.id,
"language": input_language,
"intent_input": intent_input,
"conversation_id": conversation_id,
@@ -1095,11 +1102,11 @@ class PipelineRun:
conversation_id=conversation_id,
device_id=device_id,
language=input_language,
agent_id=self.intent_agent,
agent_id=self.intent_agent.id,
extra_system_prompt=conversation_extra_system_prompt,
)
agent_id = self.intent_agent
agent_id = self.intent_agent.id
processed_locally = agent_id == conversation.HOME_ASSISTANT_AGENT
intent_response: intent.IntentResponse | None = None
if not processed_locally and not self._intent_agent_only:
@@ -1121,7 +1128,7 @@ class PipelineRun:
# 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)
intent_agent_state := self.hass.states.get(self.intent_agent.id)
) and intent_agent_state.attributes.get(
ATTR_SUPPORTED_FEATURES, 0
) & conversation.ConversationEntityFeature.CONTROL:
@@ -1143,6 +1150,13 @@ class PipelineRun:
agent_id = conversation.HOME_ASSISTANT_AGENT
processed_locally = True
if self.tts_stream and self.tts_stream.supports_streaming_input:
tts_input_stream: asyncio.Queue[str | None] | None = asyncio.Queue()
else:
tts_input_stream = None
chat_log_role = None
delta_character_count = 0
@callback
def chat_log_delta_listener(
chat_log: conversation.ChatLog, delta: dict
@@ -1156,6 +1170,42 @@ class PipelineRun:
},
)
)
if tts_input_stream is None:
return
nonlocal chat_log_role
if role := delta.get("role"):
chat_log_role = role
# We are only interested in assistant deltas with content
if chat_log_role != "assistant" or not (
content := delta.get("content")
):
return
tts_input_stream.put_nowait(content)
if self._streamed_response_text:
return
nonlocal delta_character_count
delta_character_count += len(content)
if delta_character_count < STREAM_RESPONSE_CHARS:
return
# Streamed responses are not cached. We only start streaming text after
# we have received a couple of words that indicates it will be a long response.
self._streamed_response_text = True
async def tts_input_stream_generator() -> AsyncGenerator[str]:
"""Yield TTS input stream."""
while (tts_input := await tts_input_stream.get()) is not None:
yield tts_input
assert self.tts_stream is not None
self.tts_stream.async_set_message_stream(tts_input_stream_generator())
with (
chat_session.async_get_chat_session(
@@ -1199,6 +1249,8 @@ class PipelineRun:
speech = conversation_result.response.speech.get("plain", {}).get(
"speech", ""
)
if tts_input_stream and self._streamed_response_text:
tts_input_stream.put_nowait(None)
except Exception as src_error:
_LOGGER.exception("Unexpected error during intent recognition")
@@ -1276,26 +1328,11 @@ class PipelineRun:
)
)
try:
# Synthesize audio and get URL
tts_media_id = tts_generate_media_source_id(
self.hass,
tts_input,
engine=self.tts_stream.engine,
language=self.tts_stream.language,
options=self.tts_stream.options,
)
except Exception as src_error:
_LOGGER.exception("Unexpected error during text-to-speech")
raise TextToSpeechError(
code="tts-failed",
message="Unexpected error during text-to-speech",
) from src_error
self.tts_stream.async_set_message(tts_input)
if not self._streamed_response_text:
self.tts_stream.async_set_message(tts_input)
tts_output = {
"media_id": tts_media_id,
"media_id": self.tts_stream.media_source_id,
"token": self.tts_stream.token,
"url": self.tts_stream.url,
"mime_type": self.tts_stream.content_type,

View File

@@ -47,7 +47,7 @@ from .const import (
CONF_VIDEO_SOURCE,
DEFAULT_STREAM_PROFILE,
DEFAULT_VIDEO_SOURCE,
DOMAIN as AXIS_DOMAIN,
DOMAIN,
)
from .errors import AuthenticationRequired, CannotConnect
from .hub import AxisHub, get_axis_api
@@ -58,7 +58,7 @@ DEFAULT_PROTOCOL = "https"
PROTOCOL_CHOICES = ["https", "http"]
class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN):
class AxisFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a Axis config flow."""
VERSION = 3
@@ -146,7 +146,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN):
model = self.config[CONF_MODEL]
same_model = [
entry.data[CONF_NAME]
for entry in self.hass.config_entries.async_entries(AXIS_DOMAIN)
for entry in self.hass.config_entries.async_entries(DOMAIN)
if entry.source != SOURCE_IGNORE and entry.data[CONF_MODEL] == model
]

View File

@@ -2,8 +2,8 @@
from aiohttp import ClientTimeout
from azure.core.exceptions import (
AzureError,
ClientAuthenticationError,
HttpResponseError,
ResourceNotFoundError,
)
from azure.core.pipeline.transport._aiohttp import (
@@ -39,11 +39,20 @@ async def async_setup_entry(
session = async_create_clientsession(
hass, timeout=ClientTimeout(connect=10, total=12 * 60 * 60)
)
container_client = ContainerClient(
account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/",
container_name=entry.data[CONF_CONTAINER_NAME],
credential=entry.data[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=session),
def create_container_client() -> ContainerClient:
"""Create a ContainerClient."""
return ContainerClient(
account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/",
container_name=entry.data[CONF_CONTAINER_NAME],
credential=entry.data[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=session),
)
# has a blocking call to open in cpython
container_client: ContainerClient = await hass.async_add_executor_job(
create_container_client
)
try:
@@ -61,7 +70,7 @@ async def async_setup_entry(
translation_key="invalid_auth",
translation_placeholders={CONF_ACCOUNT_NAME: entry.data[CONF_ACCOUNT_NAME]},
) from err
except HttpResponseError as err:
except AzureError as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="cannot_connect",

View File

@@ -8,7 +8,7 @@ import json
import logging
from typing import Any, Concatenate
from azure.core.exceptions import HttpResponseError
from azure.core.exceptions import AzureError, HttpResponseError, ServiceRequestError
from azure.storage.blob import BlobProperties
from homeassistant.components.backup import (
@@ -80,6 +80,20 @@ def handle_backup_errors[_R, **P](
f"Error during backup operation in {func.__name__}:"
f" Status {err.status_code}, message: {err.message}"
) from err
except ServiceRequestError as err:
raise BackupAgentError(
f"Timeout during backup operation in {func.__name__}"
) from err
except AzureError as err:
_LOGGER.debug(
"Error during backup in %s: %s",
func.__name__,
err,
exc_info=True,
)
raise BackupAgentError(
f"Error during backup operation in {func.__name__}: {err}"
) from err
return wrapper

View File

@@ -27,9 +27,25 @@ _LOGGER = logging.getLogger(__name__)
class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for azure storage."""
def get_account_url(self, account_name: str) -> str:
"""Get the account URL."""
return f"https://{account_name}.blob.core.windows.net/"
async def get_container_client(
self, account_name: str, container_name: str, storage_account_key: str
) -> ContainerClient:
"""Get the container client.
ContainerClient has a blocking call to open in cpython
"""
session = async_get_clientsession(self.hass)
def create_container_client() -> ContainerClient:
return ContainerClient(
account_url=f"https://{account_name}.blob.core.windows.net/",
container_name=container_name,
credential=storage_account_key,
transport=AioHttpTransport(session=session),
)
return await self.hass.async_add_executor_job(create_container_client)
async def validate_config(
self, container_client: ContainerClient
@@ -58,11 +74,10 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
self._async_abort_entries_match(
{CONF_ACCOUNT_NAME: user_input[CONF_ACCOUNT_NAME]}
)
container_client = ContainerClient(
account_url=self.get_account_url(user_input[CONF_ACCOUNT_NAME]),
container_client = await self.get_container_client(
account_name=user_input[CONF_ACCOUNT_NAME],
container_name=user_input[CONF_CONTAINER_NAME],
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
)
errors = await self.validate_config(container_client)
@@ -99,12 +114,12 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
reauth_entry = self._get_reauth_entry()
if user_input is not None:
container_client = ContainerClient(
account_url=self.get_account_url(reauth_entry.data[CONF_ACCOUNT_NAME]),
container_client = await self.get_container_client(
account_name=reauth_entry.data[CONF_ACCOUNT_NAME],
container_name=reauth_entry.data[CONF_CONTAINER_NAME],
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
)
errors = await self.validate_config(container_client)
if not errors:
return self.async_update_reload_and_abort(
@@ -129,13 +144,10 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
reconfigure_entry = self._get_reconfigure_entry()
if user_input is not None:
container_client = ContainerClient(
account_url=self.get_account_url(
reconfigure_entry.data[CONF_ACCOUNT_NAME]
),
container_client = await self.get_container_client(
account_name=reconfigure_entry.data[CONF_ACCOUNT_NAME],
container_name=user_input[CONF_CONTAINER_NAME],
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
)
errors = await self.validate_config(container_client)
if not errors:

View File

@@ -23,6 +23,7 @@ from .const import DATA_MANAGER, DOMAIN
from .coordinator import BackupConfigEntry, BackupDataUpdateCoordinator
from .http import async_register_http_views
from .manager import (
AddonErrorData,
BackupManager,
BackupManagerError,
BackupPlatformEvent,
@@ -48,6 +49,7 @@ from .util import suggested_filename, suggested_filename_from_name_date
from .websocket import async_register_websocket_handlers
__all__ = [
"AddonErrorData",
"AddonInfo",
"AgentBackup",
"BackupAgent",
@@ -79,7 +81,7 @@ __all__ = [
"suggested_filename_from_name_date",
]
PLATFORMS = [Platform.SENSOR]
PLATFORMS = [Platform.EVENT, Platform.SENSOR]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

View File

@@ -33,6 +33,7 @@ class BackupCoordinatorData:
last_attempted_automatic_backup: datetime | None
last_successful_automatic_backup: datetime | None
next_scheduled_automatic_backup: datetime | None
last_event: ManagerStateEvent | BackupPlatformEvent | None
class BackupDataUpdateCoordinator(DataUpdateCoordinator[BackupCoordinatorData]):
@@ -60,11 +61,13 @@ class BackupDataUpdateCoordinator(DataUpdateCoordinator[BackupCoordinatorData]):
]
self.backup_manager = backup_manager
self._last_event: ManagerStateEvent | BackupPlatformEvent | None = None
@callback
def _on_event(self, event: ManagerStateEvent | BackupPlatformEvent) -> None:
"""Handle new event."""
LOGGER.debug("Received backup event: %s", event)
self._last_event = event
self.config_entry.async_create_task(self.hass, self.async_refresh())
async def _async_update_data(self) -> BackupCoordinatorData:
@@ -74,6 +77,7 @@ class BackupDataUpdateCoordinator(DataUpdateCoordinator[BackupCoordinatorData]):
self.backup_manager.config.data.last_attempted_automatic_backup,
self.backup_manager.config.data.last_completed_automatic_backup,
self.backup_manager.config.data.schedule.next_automatic_backup,
self._last_event,
)
@callback

View File

@@ -11,7 +11,7 @@ from .const import DOMAIN
from .coordinator import BackupDataUpdateCoordinator
class BackupManagerEntity(CoordinatorEntity[BackupDataUpdateCoordinator]):
class BackupManagerBaseEntity(CoordinatorEntity[BackupDataUpdateCoordinator]):
"""Base entity for backup manager."""
_attr_has_entity_name = True
@@ -19,12 +19,9 @@ class BackupManagerEntity(CoordinatorEntity[BackupDataUpdateCoordinator]):
def __init__(
self,
coordinator: BackupDataUpdateCoordinator,
entity_description: EntityDescription,
) -> None:
"""Initialize base entity."""
super().__init__(coordinator)
self.entity_description = entity_description
self._attr_unique_id = entity_description.key
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, "backup_manager")},
manufacturer="Home Assistant",
@@ -34,3 +31,17 @@ class BackupManagerEntity(CoordinatorEntity[BackupDataUpdateCoordinator]):
entry_type=DeviceEntryType.SERVICE,
configuration_url="homeassistant://config/backup",
)
class BackupManagerEntity(BackupManagerBaseEntity):
"""Entity for backup manager."""
def __init__(
self,
coordinator: BackupDataUpdateCoordinator,
entity_description: EntityDescription,
) -> None:
"""Initialize entity."""
super().__init__(coordinator)
self.entity_description = entity_description
self._attr_unique_id = entity_description.key

View File

@@ -0,0 +1,59 @@
"""Event platform for Home Assistant Backup integration."""
from __future__ import annotations
from typing import Final
from homeassistant.components.event import EventEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import BackupConfigEntry, BackupDataUpdateCoordinator
from .entity import BackupManagerBaseEntity
from .manager import CreateBackupEvent, CreateBackupState
ATTR_BACKUP_STAGE: Final[str] = "backup_stage"
ATTR_FAILED_REASON: Final[str] = "failed_reason"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BackupConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Event set up for backup config entry."""
coordinator = config_entry.runtime_data
async_add_entities([AutomaticBackupEvent(coordinator)])
class AutomaticBackupEvent(BackupManagerBaseEntity, EventEntity):
"""Representation of an automatic backup event."""
_attr_event_types = [s.value for s in CreateBackupState]
_unrecorded_attributes = frozenset({ATTR_FAILED_REASON, ATTR_BACKUP_STAGE})
coordinator: BackupDataUpdateCoordinator
def __init__(self, coordinator: BackupDataUpdateCoordinator) -> None:
"""Initialize the automatic backup event."""
super().__init__(coordinator)
self._attr_unique_id = "automatic_backup_event"
self._attr_translation_key = "automatic_backup_event"
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if (
not (data := self.coordinator.data)
or (event := data.last_event) is None
or not isinstance(event, CreateBackupEvent)
):
return
self._trigger_event(
event.state,
{
ATTR_BACKUP_STAGE: event.stage,
ATTR_FAILED_REASON: event.reason,
},
)
self.async_write_ha_state()

View File

@@ -1,4 +1,11 @@
{
"entity": {
"event": {
"automatic_backup_event": {
"default": "mdi:database"
}
}
},
"services": {
"create": {
"service": "mdi:cloud-upload"

View File

@@ -106,11 +106,21 @@ class ManagerBackup(BaseBackup):
with_automatic_settings: bool | None
@dataclass(frozen=True, kw_only=True, slots=True)
class AddonErrorData:
"""Addon error class."""
name: str
errors: list[tuple[str, str]]
@dataclass(frozen=True, kw_only=True, slots=True)
class WrittenBackup:
"""Written backup class."""
addon_errors: dict[str, AddonErrorData]
backup: AgentBackup
folder_errors: dict[Folder, list[tuple[str, str]]]
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]]
release_stream: Callable[[], Coroutine[Any, Any, None]]
@@ -1208,7 +1218,9 @@ class BackupManager:
backup_success = True
if with_automatic_settings:
self._update_issue_after_agent_upload(agent_errors, unavailable_agents)
self._update_issue_after_agent_upload(
written_backup, agent_errors, unavailable_agents
)
# delete old backups more numerous than copies
# try this regardless of agent errors above
await delete_backups_exceeding_configured_count(self)
@@ -1354,8 +1366,10 @@ class BackupManager:
for subscription in self._backup_event_subscriptions:
subscription(event)
def _update_issue_backup_failed(self) -> None:
"""Update issue registry when a backup fails."""
def _create_automatic_backup_failed_issue(
self, translation_key: str, translation_placeholders: dict[str, str] | None
) -> None:
"""Create an issue in the issue registry for automatic backup failures."""
ir.async_create_issue(
self.hass,
DOMAIN,
@@ -1364,37 +1378,64 @@ class BackupManager:
is_persistent=True,
learn_more_url="homeassistant://config/backup",
severity=ir.IssueSeverity.WARNING,
translation_key="automatic_backup_failed_create",
translation_key=translation_key,
translation_placeholders=translation_placeholders,
)
def _update_issue_backup_failed(self) -> None:
"""Update issue registry when a backup fails."""
self._create_automatic_backup_failed_issue(
"automatic_backup_failed_create", None
)
def _update_issue_after_agent_upload(
self, agent_errors: dict[str, Exception], unavailable_agents: list[str]
self,
written_backup: WrittenBackup,
agent_errors: dict[str, Exception],
unavailable_agents: list[str],
) -> None:
"""Update issue registry after a backup is uploaded to agents."""
if not agent_errors and not unavailable_agents:
addon_errors = written_backup.addon_errors
failed_agents = unavailable_agents + [
self.backup_agents[agent_id].name for agent_id in agent_errors
]
folder_errors = written_backup.folder_errors
if not failed_agents and not addon_errors and not folder_errors:
# No issues to report, clear previous error
ir.async_delete_issue(self.hass, DOMAIN, "automatic_backup_failed")
return
ir.async_create_issue(
self.hass,
DOMAIN,
"automatic_backup_failed",
is_fixable=False,
is_persistent=True,
learn_more_url="homeassistant://config/backup",
severity=ir.IssueSeverity.WARNING,
translation_key="automatic_backup_failed_upload_agents",
translation_placeholders={
"failed_agents": ", ".join(
chain(
(
self.backup_agents[agent_id].name
for agent_id in agent_errors
),
unavailable_agents,
)
)
},
)
if failed_agents and not (addon_errors or folder_errors):
# No issues with add-ons or folders, but issues with agents
self._create_automatic_backup_failed_issue(
"automatic_backup_failed_upload_agents",
{"failed_agents": ", ".join(failed_agents)},
)
elif addon_errors and not (failed_agents or folder_errors):
# No issues with agents or folders, but issues with add-ons
self._create_automatic_backup_failed_issue(
"automatic_backup_failed_addons",
{"failed_addons": ", ".join(val.name for val in addon_errors.values())},
)
elif folder_errors and not (failed_agents or addon_errors):
# No issues with agents or add-ons, but issues with folders
self._create_automatic_backup_failed_issue(
"automatic_backup_failed_folders",
{"failed_folders": ", ".join(folder for folder in folder_errors)},
)
else:
# Issues with agents, add-ons, and/or folders
self._create_automatic_backup_failed_issue(
"automatic_backup_failed_agents_addons_folders",
{
"failed_agents": ", ".join(failed_agents) or "-",
"failed_addons": (
", ".join(val.name for val in addon_errors.values()) or "-"
),
"failed_folders": ", ".join(f for f in folder_errors) or "-",
},
)
async def async_can_decrypt_on_download(
self,
@@ -1677,7 +1718,11 @@ class CoreBackupReaderWriter(BackupReaderWriter):
raise BackupReaderWriterError(str(err)) from err
return WrittenBackup(
backup=backup, open_stream=open_backup, release_stream=remove_backup
addon_errors={},
backup=backup,
folder_errors={},
open_stream=open_backup,
release_stream=remove_backup,
)
finally:
# Inform integrations the backup is done
@@ -1816,7 +1861,11 @@ class CoreBackupReaderWriter(BackupReaderWriter):
await async_add_executor_job(temp_file.unlink, True)
return WrittenBackup(
backup=backup, open_stream=open_backup, release_stream=remove_backup
addon_errors={},
backup=backup,
folder_errors={},
open_stream=open_backup,
release_stream=remove_backup,
)
async def async_restore_backup(

View File

@@ -77,7 +77,10 @@ class _BackupStore(Store[StoredBackupData]):
for agent in data["config"]["agents"]:
data["config"]["agents"][agent]["retention"] = None
# Note: We allow reading data with major version 2.
# Note: We allow reading data with major version 2 in which the unused key
# data["config"]["schedule"]["state"] will be removed. The bump to 2 is
# planned to happen after a 6 month quiet period with no minor version
# changes.
# Reject if major version is higher than 2.
if old_major_version > 2:
raise NotImplementedError

View File

@@ -11,6 +11,18 @@
"automatic_backup_failed_upload_agents": {
"title": "Automatic backup could not be uploaded to the configured locations",
"description": "The automatic backup could not be uploaded to the configured locations {failed_agents}. Please check the logs for more information. Another attempt will be made at the next scheduled time if a backup schedule is configured."
},
"automatic_backup_failed_addons": {
"title": "Not all add-ons could be included in automatic backup",
"description": "Add-ons {failed_addons} could not be included in automatic backup. Please check the supervisor logs for more information. Another attempt will be made at the next scheduled time if a backup schedule is configured."
},
"automatic_backup_failed_agents_addons_folders": {
"title": "Automatic backup was created with errors",
"description": "The automatic backup was created with errors:\n* Locations which the backup could not be uploaded to: {failed_agents}\n* Add-ons which could not be backed up: {failed_addons}\n* Folders which could not be backed up: {failed_folders}\n\nPlease check the core and supervisor logs for more information. Another attempt will be made at the next scheduled time if a backup schedule is configured."
},
"automatic_backup_failed_folders": {
"title": "Not all folders could be included in automatic backup",
"description": "Folders {failed_folders} could not be included in automatic backup. Please check the supervisor logs for more information. Another attempt will be made at the next scheduled time if a backup schedule is configured."
}
},
"services": {
@@ -24,6 +36,22 @@
}
},
"entity": {
"event": {
"automatic_backup_event": {
"name": "Automatic backup",
"state_attributes": {
"event_type": {
"state": {
"completed": "Completed successfully",
"failed": "Failed",
"in_progress": "In progress"
}
},
"backup_stage": { "name": "Backup stage" },
"failed_reason": { "name": "Failure reason" }
}
}
},
"sensor": {
"backup_manager_state": {
"name": "Backup Manager state",

View File

@@ -21,7 +21,6 @@ from .entity import BleBoxEntity
SCAN_INTERVAL = timedelta(seconds=5)
BLEBOX_TO_HVACMODE = {
None: None,
0: HVACMode.OFF,
1: HVACMode.HEAT,
2: HVACMode.COOL,
@@ -59,12 +58,14 @@ class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEn
_attr_temperature_unit = UnitOfTemperature.CELSIUS
@property
def hvac_modes(self):
def hvac_modes(self) -> list[HVACMode]:
"""Return list of supported HVAC modes."""
if self._feature.mode is None:
return [HVACMode.OFF]
return [HVACMode.OFF, BLEBOX_TO_HVACMODE[self._feature.mode]]
@property
def hvac_mode(self):
def hvac_mode(self) -> HVACMode | None:
"""Return the desired HVAC mode."""
if self._feature.is_on is None:
return None
@@ -75,7 +76,7 @@ class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEn
return HVACMode.HEAT if self._feature.is_on else HVACMode.OFF
@property
def hvac_action(self):
def hvac_action(self) -> HVACAction | None:
"""Return the actual current HVAC action."""
if self._feature.hvac_action is not None:
if not self._feature.is_on:
@@ -88,22 +89,22 @@ class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEn
return HVACAction.HEATING if self._feature.is_heating else HVACAction.IDLE
@property
def max_temp(self):
def max_temp(self) -> float:
"""Return the maximum temperature supported."""
return self._feature.max_temp
@property
def min_temp(self):
def min_temp(self) -> float:
"""Return the maximum temperature supported."""
return self._feature.min_temp
@property
def current_temperature(self):
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._feature.current
@property
def target_temperature(self):
def target_temperature(self) -> float | None:
"""Return the desired thermostat temperature."""
return self._feature.desired

View File

@@ -84,7 +84,7 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
return color_util.color_temperature_mired_to_kelvin(self._feature.color_temp)
@property
def color_mode(self):
def color_mode(self) -> ColorMode:
"""Return the color mode.
Set values to _attr_ibutes if needed.
@@ -92,7 +92,7 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
return COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)
@property
def supported_color_modes(self):
def supported_color_modes(self) -> set[ColorMode]:
"""Return supported color modes."""
return {self.color_mode}
@@ -107,7 +107,7 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
return self._feature.effect
@property
def rgb_color(self):
def rgb_color(self) -> tuple[int, int, int] | None:
"""Return value for rgb."""
if (rgb_hex := self._feature.rgb_hex) is None:
return None
@@ -118,14 +118,14 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
)
@property
def rgbw_color(self):
def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the hue and saturation."""
if (rgbw_hex := self._feature.rgbw_hex) is None:
return None
return tuple(blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgbw_hex)[0:4])
@property
def rgbww_color(self):
def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
"""Return value for rgbww."""
if (rgbww_hex := self._feature.rgbww_hex) is None:
return None

View File

@@ -24,7 +24,7 @@ from .const import DOMAIN, EVSE_ID, LOGGER, MODEL_TYPE
type BlueCurrentConfigEntry = ConfigEntry[Connector]
PLATFORMS = [Platform.SENSOR]
PLATFORMS = [Platform.BUTTON, Platform.SENSOR]
CHARGE_POINTS = "CHARGE_POINTS"
DATA = "data"
DELAY = 5

View File

@@ -0,0 +1,89 @@
"""Support for Blue Current buttons."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from bluecurrent_api.client import Client
from homeassistant.components.button import (
ButtonDeviceClass,
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BlueCurrentConfigEntry, Connector
from .entity import ChargepointEntity
@dataclass(kw_only=True, frozen=True)
class ChargePointButtonEntityDescription(ButtonEntityDescription):
"""Describes a Blue Current button entity."""
function: Callable[[Client, str], Coroutine[Any, Any, None]]
CHARGE_POINT_BUTTONS = (
ChargePointButtonEntityDescription(
key="reset",
translation_key="reset",
function=lambda client, evse_id: client.reset(evse_id),
device_class=ButtonDeviceClass.RESTART,
),
ChargePointButtonEntityDescription(
key="reboot",
translation_key="reboot",
function=lambda client, evse_id: client.reboot(evse_id),
device_class=ButtonDeviceClass.RESTART,
),
ChargePointButtonEntityDescription(
key="stop_charge_session",
translation_key="stop_charge_session",
function=lambda client, evse_id: client.stop_session(evse_id),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: BlueCurrentConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Blue Current buttons."""
connector: Connector = entry.runtime_data
async_add_entities(
ChargePointButton(
connector,
button,
evse_id,
)
for evse_id in connector.charge_points
for button in CHARGE_POINT_BUTTONS
)
class ChargePointButton(ChargepointEntity, ButtonEntity):
"""Define a charge point button."""
has_value = True
entity_description: ChargePointButtonEntityDescription
def __init__(
self,
connector: Connector,
description: ChargePointButtonEntityDescription,
evse_id: str,
) -> None:
"""Initialize the button."""
super().__init__(connector, evse_id)
self.entity_description = description
self._attr_unique_id = f"{description.key}_{evse_id}"
async def async_press(self) -> None:
"""Handle the button press."""
await self.entity_description.function(self.connector.client, self.evse_id)

View File

@@ -1,7 +1,5 @@
"""Entity representing a Blue Current charge point."""
from abc import abstractmethod
from homeassistant.const import ATTR_NAME
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
@@ -17,12 +15,12 @@ class BlueCurrentEntity(Entity):
_attr_has_entity_name = True
_attr_should_poll = False
has_value = False
def __init__(self, connector: Connector, signal: str) -> None:
"""Initialize the entity."""
self.connector = connector
self.signal = signal
self.has_value = False
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
@@ -43,7 +41,6 @@ class BlueCurrentEntity(Entity):
return self.connector.connected and self.has_value
@callback
@abstractmethod
def update_from_latest_data(self) -> None:
"""Update the entity from the latest data."""

View File

@@ -19,6 +19,17 @@
"current_left": {
"default": "mdi:gauge"
}
},
"button": {
"reset": {
"default": "mdi:restart"
},
"reboot": {
"default": "mdi:restart-alert"
},
"stop_charge_session": {
"default": "mdi:stop"
}
}
}
}

View File

@@ -1,7 +1,7 @@
{
"domain": "blue_current",
"name": "Blue Current",
"codeowners": ["@Floris272", "@gleeuwen"],
"codeowners": ["@gleeuwen", "@NickKoepr", "@jtodorova23"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/blue_current",
"iot_class": "cloud_push",

View File

@@ -113,6 +113,17 @@
"grid_max_current": {
"name": "Max grid current"
}
},
"button": {
"stop_charge_session": {
"name": "Stop charge session"
},
"reboot": {
"name": "Reboot"
},
"reset": {
"name": "Reset"
}
}
}
}

View File

@@ -18,7 +18,7 @@
"bleak==0.22.3",
"bleak-retry-connector==3.9.0",
"bluetooth-adapters==0.21.4",
"bluetooth-auto-recovery==1.5.1",
"bluetooth-auto-recovery==1.5.2",
"bluetooth-data-tools==1.28.1",
"dbus-fast==2.43.0",
"habluetooth==3.48.2"

View File

@@ -22,13 +22,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.ssl import get_default_context
from .const import (
CONF_GCID,
CONF_READ_ONLY,
CONF_REFRESH_TOKEN,
DOMAIN as BMW_DOMAIN,
SCAN_INTERVALS,
)
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN, SCAN_INTERVALS
_LOGGER = logging.getLogger(__name__)
@@ -63,7 +57,7 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
hass,
_LOGGER,
config_entry=config_entry,
name=f"{BMW_DOMAIN}-{config_entry.data[CONF_USERNAME]}",
name=f"{DOMAIN}-{config_entry.data[CONF_USERNAME]}",
update_interval=timedelta(
seconds=SCAN_INTERVALS[config_entry.data[CONF_REGION]]
),
@@ -81,26 +75,26 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
except MyBMWCaptchaMissingError as err:
# If a captcha is required (user/password login flow), always trigger the reauth flow
raise ConfigEntryAuthFailed(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="missing_captcha",
) from err
except MyBMWAuthError as err:
# Allow one retry interval before raising AuthFailed to avoid flaky API issues
if self.last_update_success:
raise UpdateFailed(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="update_failed",
translation_placeholders={"exception": str(err)},
) from err
# Clear refresh token and trigger reauth if previous update failed as well
self._update_config_entry_refresh_token(None)
raise ConfigEntryAuthFailed(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="invalid_auth",
) from err
except (MyBMWAPIError, RequestError) as err:
raise UpdateFailed(
translation_domain=BMW_DOMAIN,
translation_domain=DOMAIN,
translation_key="update_failed",
translation_placeholders={"exception": str(err)},
) from err

View File

@@ -6,21 +6,31 @@ from ssl import SSLError
from bosch_alarm_mode2 import Panel
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, Platform
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.typing import ConfigType
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE, DOMAIN
from .services import setup_services
from .types import BoschAlarmConfigEntry
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS: list[Platform] = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.SENSOR,
Platform.SWITCH,
]
type BoschAlarmConfigEntry = ConfigEntry[Panel]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up bosch alarm services."""
setup_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: BoschAlarmConfigEntry) -> bool:
@@ -52,8 +62,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: BoschAlarmConfigEntry) -
device_registry = dr.async_get(hass)
mac = entry.data.get(CONF_MAC)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, mac)} if mac else set(),
identifiers={(DOMAIN, entry.unique_id or entry.entry_id)},
name=f"Bosch {panel.model}",
manufacturer="Bosch Security Systems",

View File

@@ -12,8 +12,8 @@ from homeassistant.components.alarm_control_panel import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BoschAlarmConfigEntry
from .entity import BoschAlarmAreaEntity
from .types import BoschAlarmConfigEntry
async def async_setup_entry(
@@ -34,6 +34,9 @@ async def async_setup_entry(
)
PARALLEL_UPDATES = 0
class AreaAlarmControlPanel(BoschAlarmAreaEntity, AlarmControlPanelEntity):
"""An alarm control panel entity for a bosch alarm panel."""

View File

@@ -0,0 +1,220 @@
"""Support for Bosch Alarm Panel binary sensors."""
from __future__ import annotations
from dataclasses import dataclass
from bosch_alarm_mode2 import Panel
from bosch_alarm_mode2.const import ALARM_PANEL_FAULTS
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BoschAlarmConfigEntry
from .entity import BoschAlarmAreaEntity, BoschAlarmEntity, BoschAlarmPointEntity
@dataclass(kw_only=True, frozen=True)
class BoschAlarmFaultEntityDescription(BinarySensorEntityDescription):
"""Describes Bosch Alarm sensor entity."""
fault: int
FAULT_TYPES = [
BoschAlarmFaultEntityDescription(
key="panel_fault_battery_low",
entity_registry_enabled_default=True,
device_class=BinarySensorDeviceClass.BATTERY,
fault=ALARM_PANEL_FAULTS.BATTERY_LOW,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_battery_mising",
translation_key="panel_fault_battery_mising",
entity_registry_enabled_default=True,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.BATTERY_MISING,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_ac_fail",
translation_key="panel_fault_ac_fail",
entity_registry_enabled_default=True,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.AC_FAIL,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_phone_line_failure",
translation_key="panel_fault_phone_line_failure",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.CONNECTIVITY,
fault=ALARM_PANEL_FAULTS.PHONE_LINE_FAILURE,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_parameter_crc_fail_in_pif",
translation_key="panel_fault_parameter_crc_fail_in_pif",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.PARAMETER_CRC_FAIL_IN_PIF,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_communication_fail_since_rps_hang_up",
translation_key="panel_fault_communication_fail_since_rps_hang_up",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.COMMUNICATION_FAIL_SINCE_RPS_HANG_UP,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_sdi_fail_since_rps_hang_up",
translation_key="panel_fault_sdi_fail_since_rps_hang_up",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.SDI_FAIL_SINCE_RPS_HANG_UP,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_user_code_tamper_since_rps_hang_up",
translation_key="panel_fault_user_code_tamper_since_rps_hang_up",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.USER_CODE_TAMPER_SINCE_RPS_HANG_UP,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_fail_to_call_rps_since_rps_hang_up",
translation_key="panel_fault_fail_to_call_rps_since_rps_hang_up",
entity_registry_enabled_default=False,
fault=ALARM_PANEL_FAULTS.FAIL_TO_CALL_RPS_SINCE_RPS_HANG_UP,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_point_bus_fail_since_rps_hang_up",
translation_key="panel_fault_point_bus_fail_since_rps_hang_up",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.POINT_BUS_FAIL_SINCE_RPS_HANG_UP,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_log_overflow",
translation_key="panel_fault_log_overflow",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.LOG_OVERFLOW,
),
BoschAlarmFaultEntityDescription(
key="panel_fault_log_threshold",
translation_key="panel_fault_log_threshold",
entity_registry_enabled_default=False,
device_class=BinarySensorDeviceClass.PROBLEM,
fault=ALARM_PANEL_FAULTS.LOG_THRESHOLD,
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BoschAlarmConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up binary sensors for alarm points and the connection status."""
panel = config_entry.runtime_data
entities: list[BinarySensorEntity] = [
PointSensor(panel, point_id, config_entry.unique_id or config_entry.entry_id)
for point_id in panel.points
]
entities.extend(
PanelFaultsSensor(
panel,
config_entry.unique_id or config_entry.entry_id,
fault_type,
)
for fault_type in FAULT_TYPES
)
entities.extend(
AreaReadyToArmSensor(
panel, area_id, config_entry.unique_id or config_entry.entry_id, "away"
)
for area_id in panel.areas
)
entities.extend(
AreaReadyToArmSensor(
panel, area_id, config_entry.unique_id or config_entry.entry_id, "home"
)
for area_id in panel.areas
)
async_add_entities(entities)
PARALLEL_UPDATES = 0
class PanelFaultsSensor(BoschAlarmEntity, BinarySensorEntity):
"""A binary sensor entity for each fault type in a bosch alarm panel."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
entity_description: BoschAlarmFaultEntityDescription
def __init__(
self,
panel: Panel,
unique_id: str,
entity_description: BoschAlarmFaultEntityDescription,
) -> None:
"""Set up a binary sensor entity for each fault type in a bosch alarm panel."""
super().__init__(panel, unique_id, True)
self.entity_description = entity_description
self._fault_type = entity_description.fault
self._attr_unique_id = f"{unique_id}_fault_{entity_description.key}"
@property
def is_on(self) -> bool:
"""Return if this fault has occurred."""
return self._fault_type in self.panel.panel_faults_ids
class AreaReadyToArmSensor(BoschAlarmAreaEntity, BinarySensorEntity):
"""A binary sensor entity showing if a panel is ready to arm."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self, panel: Panel, area_id: int, unique_id: str, arm_type: str
) -> None:
"""Set up a binary sensor entity for the arming status in a bosch alarm panel."""
super().__init__(panel, area_id, unique_id, False, False, True)
self.panel = panel
self._arm_type = arm_type
self._attr_translation_key = f"area_ready_to_arm_{arm_type}"
self._attr_unique_id = f"{self._area_unique_id}_ready_to_arm_{arm_type}"
@property
def is_on(self) -> bool:
"""Return if this panel is ready to arm."""
if self._arm_type == "away":
return self._area.all_ready
if self._arm_type == "home":
return self._area.all_ready or self._area.part_ready
return False
class PointSensor(BoschAlarmPointEntity, BinarySensorEntity):
"""A binary sensor entity for a point in a bosch alarm panel."""
_attr_name = None
def __init__(self, panel: Panel, point_id: int, unique_id: str) -> None:
"""Set up a binary sensor entity for a point in a bosch alarm panel."""
super().__init__(panel, point_id, unique_id)
self._attr_unique_id = self._point_unique_id
@property
def is_on(self) -> bool:
"""Return if this point sensor is on."""
return self._point.is_open()

View File

@@ -6,25 +6,30 @@ import asyncio
from collections.abc import Mapping
import logging
import ssl
from typing import Any
from typing import Any, Self
from bosch_alarm_mode2 import Panel
import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_DHCP,
SOURCE_RECONFIGURE,
SOURCE_USER,
ConfigEntryState,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import (
CONF_CODE,
CONF_HOST,
CONF_MAC,
CONF_MODEL,
CONF_PASSWORD,
CONF_PORT,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE, DOMAIN
@@ -88,6 +93,12 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
"""Init config flow."""
self._data: dict[str, Any] = {}
self.mac: str | None = None
self.host: str | None = None
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
return self.mac == other_flow.mac or self.host == other_flow.host
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -96,9 +107,12 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
self.host = user_input[CONF_HOST]
if self.source == SOURCE_USER:
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
try:
# Use load_selector = 0 to fetch the panel model without authentication.
(model, serial) = await try_connect(user_input, 0)
(model, _) = await try_connect(user_input, 0)
except (
OSError,
ConnectionRefusedError,
@@ -129,6 +143,70 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle DHCP discovery."""
self.mac = format_mac(discovery_info.macaddress)
self.host = discovery_info.ip
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
for entry in self.hass.config_entries.async_entries(DOMAIN):
if entry.data.get(CONF_MAC) == self.mac:
result = self.hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
CONF_HOST: discovery_info.ip,
},
)
if result:
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="already_configured")
if entry.data[CONF_HOST] == discovery_info.ip:
if (
not entry.data.get(CONF_MAC)
and entry.state is ConfigEntryState.LOADED
):
result = self.hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
CONF_MAC: self.mac,
},
)
if result:
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="already_configured")
try:
# Use load_selector = 0 to fetch the panel model without authentication.
(model, _) = await try_connect(
{CONF_HOST: discovery_info.ip, CONF_PORT: 7700}, 0
)
except (
OSError,
ConnectionRefusedError,
ssl.SSLError,
asyncio.exceptions.TimeoutError,
):
return self.async_abort(reason="cannot_connect")
except Exception:
_LOGGER.exception("Unexpected exception")
return self.async_abort(reason="unknown")
self.context["title_placeholders"] = {
"model": model,
"host": discovery_info.ip,
}
self._data = {
CONF_HOST: discovery_info.ip,
CONF_MAC: self.mac,
CONF_MODEL: model,
CONF_PORT: 7700,
}
return await self.async_step_auth()
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -172,7 +250,7 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
else:
if serial_number:
await self.async_set_unique_id(str(serial_number))
if self.source == SOURCE_USER:
if self.source in (SOURCE_USER, SOURCE_DHCP):
if serial_number:
self._abort_if_unique_id_configured()
else:
@@ -184,6 +262,7 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
)
if serial_number:
self._abort_if_unique_id_mismatch(reason="device_mismatch")
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data=self._data,

View File

@@ -1,6 +1,9 @@
"""Constants for the Bosch Alarm integration."""
DOMAIN = "bosch_alarm"
HISTORY_ATTR = "history"
ATTR_HISTORY = "history"
CONF_INSTALLER_CODE = "installer_code"
CONF_USER_CODE = "user_code"
ATTR_DATETIME = "datetime"
SERVICE_SET_DATE_TIME = "set_date_time"
ATTR_CONFIG_ENTRY_ID = "config_entry_id"

View File

@@ -6,8 +6,8 @@ from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from . import BoschAlarmConfigEntry
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE
from .types import BoschAlarmConfigEntry
TO_REDACT = [CONF_INSTALLER_CODE, CONF_USER_CODE, CONF_PASSWORD]

View File

@@ -17,9 +17,13 @@ class BoschAlarmEntity(Entity):
_attr_has_entity_name = True
def __init__(self, panel: Panel, unique_id: str) -> None:
def __init__(
self, panel: Panel, unique_id: str, observe_faults: bool = False
) -> None:
"""Set up a entity for a bosch alarm panel."""
self.panel = panel
self._observe_faults = observe_faults
self._attr_should_poll = False
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
name=f"Bosch {panel.model}",
@@ -34,10 +38,14 @@ class BoschAlarmEntity(Entity):
async def async_added_to_hass(self) -> None:
"""Observe state changes."""
self.panel.connection_status_observer.attach(self.schedule_update_ha_state)
if self._observe_faults:
self.panel.faults_observer.attach(self.schedule_update_ha_state)
async def async_will_remove_from_hass(self) -> None:
"""Stop observing state changes."""
self.panel.connection_status_observer.detach(self.schedule_update_ha_state)
if self._observe_faults:
self.panel.faults_observer.attach(self.schedule_update_ha_state)
class BoschAlarmAreaEntity(BoschAlarmEntity):
@@ -88,6 +96,33 @@ class BoschAlarmAreaEntity(BoschAlarmEntity):
self._area.status_observer.detach(self.schedule_update_ha_state)
class BoschAlarmPointEntity(BoschAlarmEntity):
"""A base entity for point related entities within a bosch alarm panel."""
def __init__(self, panel: Panel, point_id: int, unique_id: str) -> None:
"""Set up a area related entity for a bosch alarm panel."""
super().__init__(panel, unique_id)
self._point_id = point_id
self._point_unique_id = f"{unique_id}_point_{point_id}"
self._point = panel.points[point_id]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._point_unique_id)},
name=self._point.name,
manufacturer="Bosch Security Systems",
via_device=(DOMAIN, unique_id),
)
async def async_added_to_hass(self) -> None:
"""Observe state changes."""
await super().async_added_to_hass()
self._point.status_observer.attach(self.schedule_update_ha_state)
async def async_will_remove_from_hass(self) -> None:
"""Stop observing state changes."""
await super().async_added_to_hass()
self._point.status_observer.detach(self.schedule_update_ha_state)
class BoschAlarmDoorEntity(BoschAlarmEntity):
"""A base entity for area related entities within a bosch alarm panel."""

View File

@@ -1,6 +1,20 @@
{
"services": {
"set_date_time": {
"service": "mdi:clock-edit"
}
},
"entity": {
"sensor": {
"alarms_gas": {
"default": "mdi:alert-circle"
},
"alarms_fire": {
"default": "mdi:alert-circle"
},
"alarms_burglary": {
"default": "mdi:alert-circle"
},
"faulting_points": {
"default": "mdi:alert-circle"
}
@@ -24,6 +38,44 @@
"on": "mdi:lock-open"
}
}
},
"binary_sensor": {
"panel_fault_parameter_crc_fail_in_pif": {
"default": "mdi:alert-circle"
},
"panel_fault_phone_line_failure": {
"default": "mdi:alert-circle"
},
"panel_fault_sdi_fail_since_rps_hang_up": {
"default": "mdi:alert-circle"
},
"panel_fault_user_code_tamper_since_rps_hang_up": {
"default": "mdi:alert-circle"
},
"panel_fault_fail_to_call_rps_since_rps_hang_up": {
"default": "mdi:alert-circle"
},
"panel_fault_point_bus_fail_since_rps_hang_up": {
"default": "mdi:alert-circle"
},
"panel_fault_log_overflow": {
"default": "mdi:alert-circle"
},
"panel_fault_log_threshold": {
"default": "mdi:alert-circle"
},
"area_ready_to_arm_away": {
"default": "mdi:shield",
"state": {
"on": "mdi:shield-lock"
}
},
"area_ready_to_arm_home": {
"default": "mdi:shield",
"state": {
"on": "mdi:shield-home"
}
}
}
}
}

View File

@@ -3,6 +3,11 @@
"name": "Bosch Alarm",
"codeowners": ["@mag1024", "@sanjay900"],
"config_flow": true,
"dhcp": [
{
"macaddress": "000463*"
}
],
"documentation": "https://www.home-assistant.io/integrations/bosch_alarm",
"integration_type": "device",
"iot_class": "local_push",

View File

@@ -13,10 +13,7 @@ rules:
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
No custom actions are defined.
docs-actions: done
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
@@ -29,25 +26,22 @@ rules:
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
No custom actions are defined.
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters: todo
docs-installation-parameters: todo
entity-unavailable: todo
integration-owner: done
log-when-unavailable: todo
parallel-updates: todo
parallel-updates: done
reauthentication-flow: done
test-coverage: done
# Gold
devices: done
diagnostics: todo
discovery-update-info: todo
discovery: todo
discovery-update-info: done
discovery: done
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo

View File

@@ -6,6 +6,7 @@ from collections.abc import Callable
from dataclasses import dataclass
from bosch_alarm_mode2 import Panel
from bosch_alarm_mode2.const import ALARM_MEMORY_PRIORITIES
from bosch_alarm_mode2.panel import Area
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
@@ -15,18 +16,53 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BoschAlarmConfigEntry
from .entity import BoschAlarmAreaEntity
ALARM_TYPES = {
"burglary": {
ALARM_MEMORY_PRIORITIES.BURGLARY_SUPERVISORY: "supervisory",
ALARM_MEMORY_PRIORITIES.BURGLARY_TROUBLE: "trouble",
ALARM_MEMORY_PRIORITIES.BURGLARY_ALARM: "alarm",
},
"gas": {
ALARM_MEMORY_PRIORITIES.GAS_SUPERVISORY: "supervisory",
ALARM_MEMORY_PRIORITIES.GAS_TROUBLE: "trouble",
ALARM_MEMORY_PRIORITIES.GAS_ALARM: "alarm",
},
"fire": {
ALARM_MEMORY_PRIORITIES.FIRE_SUPERVISORY: "supervisory",
ALARM_MEMORY_PRIORITIES.FIRE_TROUBLE: "trouble",
ALARM_MEMORY_PRIORITIES.FIRE_ALARM: "alarm",
},
}
@dataclass(kw_only=True, frozen=True)
class BoschAlarmSensorEntityDescription(SensorEntityDescription):
"""Describes Bosch Alarm sensor entity."""
value_fn: Callable[[Area], int]
value_fn: Callable[[Area], str | int]
observe_alarms: bool = False
observe_ready: bool = False
observe_status: bool = False
def priority_value_fn(priority_info: dict[int, str]) -> Callable[[Area], str]:
"""Build a value_fn for a given priority type."""
return lambda area: next(
(key for priority, key in priority_info.items() if priority in area.alarms_ids),
"no_issues",
)
SENSOR_TYPES: list[BoschAlarmSensorEntityDescription] = [
*[
BoschAlarmSensorEntityDescription(
key=f"alarms_{key}",
translation_key=f"alarms_{key}",
value_fn=priority_value_fn(priority_type),
observe_alarms=True,
)
for key, priority_type in ALARM_TYPES.items()
],
BoschAlarmSensorEntityDescription(
key="faulting_points",
translation_key="faulting_points",
@@ -81,6 +117,6 @@ class BoschAreaSensor(BoschAlarmAreaEntity, SensorEntity):
self._attr_unique_id = f"{self._area_unique_id}_{entity_description.key}"
@property
def native_value(self) -> int:
def native_value(self) -> str | int:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self._area)

View File

@@ -0,0 +1,77 @@
"""Services for the bosch_alarm integration."""
from __future__ import annotations
import asyncio
import datetime as dt
from typing import Any
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.util import dt as dt_util
from .const import ATTR_CONFIG_ENTRY_ID, ATTR_DATETIME, DOMAIN, SERVICE_SET_DATE_TIME
from .types import BoschAlarmConfigEntry
def validate_datetime(value: Any) -> dt.datetime:
"""Validate that a provided datetime is supported on a bosch alarm panel."""
date_val = cv.datetime(value)
if date_val.year < 2010:
raise vol.RangeInvalid("datetime must be after 2009")
if date_val.year > 2037:
raise vol.RangeInvalid("datetime must be before 2038")
return date_val
SET_DATE_TIME_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
vol.Optional(ATTR_DATETIME): validate_datetime,
}
)
async def async_set_panel_date(call: ServiceCall) -> None:
"""Set the date and time on a bosch alarm panel."""
config_entry: BoschAlarmConfigEntry | None
value: dt.datetime = call.data.get(ATTR_DATETIME, dt_util.now())
entry_id = call.data[ATTR_CONFIG_ENTRY_ID]
if not (config_entry := call.hass.config_entries.async_get_entry(entry_id)):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="integration_not_found",
translation_placeholders={"target": entry_id},
)
if config_entry.state is not ConfigEntryState.LOADED:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="not_loaded",
translation_placeholders={"target": config_entry.title},
)
panel = config_entry.runtime_data
try:
await panel.set_panel_date(value)
except asyncio.InvalidStateError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
translation_placeholders={"target": config_entry.title},
) from err
def setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the bosch alarm integration."""
hass.services.async_register(
DOMAIN,
SERVICE_SET_DATE_TIME,
async_set_panel_date,
schema=SET_DATE_TIME_SCHEMA,
)

View File

@@ -0,0 +1,12 @@
set_date_time:
fields:
config_entry_id:
required: true
selector:
config_entry:
integration: bosch_alarm
datetime:
required: false
example: "2025-05-10 00:00:00"
selector:
datetime:

View File

@@ -1,5 +1,6 @@
{
"config": {
"flow_title": "{model} ({host})",
"step": {
"user": {
"data": {
@@ -42,6 +43,7 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
@@ -49,6 +51,18 @@
}
},
"exceptions": {
"integration_not_found": {
"message": "Integration \"{target}\" not found in registry."
},
"not_loaded": {
"message": "{target} is not loaded."
},
"connection_error": {
"message": "Could not connect to \"{target}\"."
},
"unknown_error": {
"message": "An unknown error occurred while setting the date and time on \"{target}\"."
},
"cannot_connect": {
"message": "Could not connect to panel."
},
@@ -56,22 +70,111 @@
"message": "Incorrect credentials for panel."
},
"incorrect_door_state": {
"message": "Door cannot be manipulated while it is being cycled."
"message": "Door cannot be manipulated while it is momentarily unlocked."
}
},
"services": {
"set_date_time": {
"name": "Set date & time",
"description": "Sets the date and time on the alarm panel.",
"fields": {
"datetime": {
"name": "Date & time",
"description": "The date and time to set. The time zone of the Home Assistant instance is assumed. If omitted, the current date and time is used."
},
"config_entry_id": {
"name": "Config entry",
"description": "The Bosch Alarm integration ID."
}
}
}
},
"entity": {
"binary_sensor": {
"panel_fault_battery_mising": {
"name": "Battery missing"
},
"panel_fault_ac_fail": {
"name": "AC Failure"
},
"panel_fault_parameter_crc_fail_in_pif": {
"name": "CRC failure in panel configuration"
},
"panel_fault_phone_line_failure": {
"name": "Phone line failure"
},
"panel_fault_sdi_fail_since_rps_hang_up": {
"name": "SDI failure since last RPS connection"
},
"panel_fault_user_code_tamper_since_rps_hang_up": {
"name": "User code tamper since last RPS connection"
},
"panel_fault_fail_to_call_rps_since_rps_hang_up": {
"name": "Failure to call RPS since last RPS connection"
},
"panel_fault_point_bus_fail_since_rps_hang_up": {
"name": "Point bus failure since last RPS connection"
},
"panel_fault_log_overflow": {
"name": "Log overflow"
},
"panel_fault_log_threshold": {
"name": "Log threshold reached"
},
"area_ready_to_arm_away": {
"name": "Area ready to arm away",
"state": {
"on": "Ready",
"off": "Not ready"
}
},
"area_ready_to_arm_home": {
"name": "Area ready to arm home",
"state": {
"on": "Ready",
"off": "Not ready"
}
}
},
"switch": {
"secured": {
"name": "Secured"
},
"cycling": {
"name": "Cycling"
"name": "Momentarily unlocked"
},
"locked": {
"name": "Locked"
}
},
"sensor": {
"alarms_gas": {
"name": "Gas alarm issues",
"state": {
"supervisory": "Supervisory",
"trouble": "Trouble",
"alarm": "Alarm",
"no_issues": "No issues"
}
},
"alarms_fire": {
"name": "Fire alarm issues",
"state": {
"supervisory": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::supervisory%]",
"trouble": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::trouble%]",
"alarm": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::alarm%]",
"no_issues": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::no_issues%]"
}
},
"alarms_burglary": {
"name": "Burglary alarm issues",
"state": {
"supervisory": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::supervisory%]",
"trouble": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::trouble%]",
"alarm": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::alarm%]",
"no_issues": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::no_issues%]"
}
},
"faulting_points": {
"name": "Faulting points",
"unit_of_measurement": "points"

View File

@@ -0,0 +1,7 @@
"""Types for the Bosch Alarm integration."""
from bosch_alarm_mode2 import Panel
from homeassistant.config_entries import ConfigEntry
type BoschAlarmConfigEntry = ConfigEntry[Panel]

View File

@@ -60,7 +60,7 @@ from .const import (
ADDED_CAST_DEVICES_KEY,
CAST_MULTIZONE_MANAGER_KEY,
CONF_IGNORE_CEC,
DOMAIN as CAST_DOMAIN,
DOMAIN,
SIGNAL_CAST_DISCOVERED,
SIGNAL_CAST_REMOVED,
SIGNAL_HASS_CAST_SHOW_VIEW,
@@ -315,7 +315,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
self._cast_view_remove_handler: CALLBACK_TYPE | None = None
self._attr_unique_id = str(cast_info.uuid)
self._attr_device_info = DeviceInfo(
identifiers={(CAST_DOMAIN, str(cast_info.uuid).replace("-", ""))},
identifiers={(DOMAIN, str(cast_info.uuid).replace("-", ""))},
manufacturer=str(cast_info.cast_info.manufacturer),
model=cast_info.cast_info.model_name,
name=str(cast_info.friendly_name),
@@ -591,7 +591,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
"""Generate root node."""
children = []
# Add media browsers
for platform in self.hass.data[CAST_DOMAIN]["cast_platform"].values():
for platform in self.hass.data[DOMAIN]["cast_platform"].values():
children.extend(
await platform.async_get_media_browser_root_object(
self.hass, self._chromecast.cast_type
@@ -650,7 +650,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
platform: CastProtocol
assert media_content_type is not None
for platform in self.hass.data[CAST_DOMAIN]["cast_platform"].values():
for platform in self.hass.data[DOMAIN]["cast_platform"].values():
browse_media = await platform.async_browse_media(
self.hass,
media_content_type,
@@ -680,7 +680,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
extra = kwargs.get(ATTR_MEDIA_EXTRA, {})
# Handle media supported by a known cast app
if media_type == CAST_DOMAIN:
if media_type == DOMAIN:
try:
app_data = json.loads(media_id)
if metadata := extra.get("metadata"):
@@ -712,7 +712,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return
# Try the cast platforms
for platform in self.hass.data[CAST_DOMAIN]["cast_platform"].values():
for platform in self.hass.data[DOMAIN]["cast_platform"].values():
result = await platform.async_play_media(
self.hass, self.entity_id, chromecast, media_type, media_id
)

View File

@@ -18,23 +18,20 @@ from homeassistant.const import (
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue
from homeassistant.loader import async_suggest_report_issue
from homeassistant.util.hass_dict import HassKey
from homeassistant.util.unit_conversion import TemperatureConverter
from .const import ( # noqa: F401
ATTR_AUX_HEAT,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE,
@@ -77,7 +74,6 @@ from .const import ( # noqa: F401
PRESET_HOME,
PRESET_NONE,
PRESET_SLEEP,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE,
@@ -168,12 +164,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"async_handle_set_preset_mode_service",
[ClimateEntityFeature.PRESET_MODE],
)
component.async_register_entity_service(
SERVICE_SET_AUX_HEAT,
{vol.Required(ATTR_AUX_HEAT): cv.boolean},
async_service_aux_heat,
[ClimateEntityFeature.AUX_HEAT],
)
component.async_register_entity_service(
SERVICE_SET_TEMPERATURE,
SET_TEMPERATURE_SCHEMA,
@@ -239,7 +229,6 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
"target_temperature_low",
"preset_mode",
"preset_modes",
"is_aux_heat",
"fan_mode",
"fan_modes",
"swing_mode",
@@ -279,7 +268,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_attr_hvac_action: HVACAction | None = None
_attr_hvac_mode: HVACMode | None
_attr_hvac_modes: list[HVACMode]
_attr_is_aux_heat: bool | None
_attr_max_humidity: float = DEFAULT_MAX_HUMIDITY
_attr_max_temp: float
_attr_min_humidity: float = DEFAULT_MIN_HUMIDITY
@@ -299,52 +287,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_attr_target_temperature: float | None = None
_attr_temperature_unit: str
__climate_reported_legacy_aux = False
def _report_legacy_aux(self) -> None:
"""Log warning and create an issue if the entity implements legacy auxiliary heater."""
report_issue = async_suggest_report_issue(
self.hass,
integration_domain=self.platform.platform_name,
module=type(self).__module__,
)
_LOGGER.warning(
(
"%s::%s implements the `is_aux_heat` property or uses the auxiliary "
"heater methods in a subclass of ClimateEntity which is "
"deprecated and will be unsupported from Home Assistant 2025.4."
" Please %s"
),
self.platform.platform_name,
self.__class__.__name__,
report_issue,
)
translation_placeholders = {"platform": self.platform.platform_name}
translation_key = "deprecated_climate_aux_no_url"
issue_tracker = async_get_issue_tracker(
self.hass,
integration_domain=self.platform.platform_name,
module=type(self).__module__,
)
if issue_tracker:
translation_placeholders["issue_tracker"] = issue_tracker
translation_key = "deprecated_climate_aux_url_custom"
ir.async_create_issue(
self.hass,
DOMAIN,
f"deprecated_climate_aux_{self.platform.platform_name}",
breaks_in_ha_version="2025.4.0",
is_fixable=False,
is_persistent=False,
issue_domain=self.platform.platform_name,
severity=ir.IssueSeverity.WARNING,
translation_key=translation_key,
translation_placeholders=translation_placeholders,
)
self.__climate_reported_legacy_aux = True
@final
@property
def state(self) -> str | None:
@@ -453,14 +395,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode
if ClimateEntityFeature.AUX_HEAT in supported_features:
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
if (
self.__climate_reported_legacy_aux is False
and "custom_components" in type(self).__module__
):
self._report_legacy_aux()
return data
@cached_property
@@ -540,14 +474,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""
return self._attr_preset_modes
@cached_property
def is_aux_heat(self) -> bool | None:
"""Return true if aux heater.
Requires ClimateEntityFeature.AUX_HEAT.
"""
return self._attr_is_aux_heat
@cached_property
def fan_mode(self) -> str | None:
"""Return the fan setting.
@@ -732,22 +658,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Set new preset mode."""
await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode)
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
raise NotImplementedError
async def async_turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
await self.hass.async_add_executor_job(self.turn_aux_heat_on)
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
raise NotImplementedError
async def async_turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
await self.hass.async_add_executor_job(self.turn_aux_heat_off)
def turn_on(self) -> None:
"""Turn the entity on."""
raise NotImplementedError
@@ -845,16 +755,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return self._attr_max_humidity
async def async_service_aux_heat(
entity: ClimateEntity, service_call: ServiceCall
) -> None:
"""Handle aux heat service."""
if service_call.data[ATTR_AUX_HEAT]:
await entity.async_turn_aux_heat_on()
else:
await entity.async_turn_aux_heat_off()
async def async_service_humidity_set(
entity: ClimateEntity, service_call: ServiceCall
) -> None:

View File

@@ -96,7 +96,6 @@ class HVACAction(StrEnum):
CURRENT_HVAC_ACTIONS = [cls.value for cls in HVACAction]
ATTR_AUX_HEAT = "aux_heat"
ATTR_CURRENT_HUMIDITY = "current_humidity"
ATTR_CURRENT_TEMPERATURE = "current_temperature"
ATTR_FAN_MODES = "fan_modes"
@@ -128,7 +127,6 @@ DOMAIN = "climate"
INTENT_SET_TEMPERATURE = "HassClimateSetTemperature"
SERVICE_SET_AUX_HEAT = "set_aux_heat"
SERVICE_SET_FAN_MODE = "set_fan_mode"
SERVICE_SET_PRESET_MODE = "set_preset_mode"
SERVICE_SET_HUMIDITY = "set_humidity"
@@ -147,7 +145,6 @@ class ClimateEntityFeature(IntFlag):
FAN_MODE = 8
PRESET_MODE = 16
SWING_MODE = 32
AUX_HEAT = 64
TURN_OFF = 128
TURN_ON = 256
SWING_HORIZONTAL_MODE = 512

View File

@@ -1,17 +1,5 @@
# Describes the format for available climate services
set_aux_heat:
target:
entity:
domain: climate
supported_features:
- climate.ClimateEntityFeature.AUX_HEAT
fields:
aux_heat:
required: true
selector:
boolean:
set_preset_mode:
target:
entity:

View File

@@ -12,7 +12,6 @@ from homeassistant.helpers.significant_change import (
)
from . import (
ATTR_AUX_HEAT,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE,
@@ -27,7 +26,6 @@ from . import (
)
SIGNIFICANT_ATTRIBUTES: set[str] = {
ATTR_AUX_HEAT,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE,
@@ -67,7 +65,6 @@ def async_check_significant_change(
for attr_name in changed_attrs:
if attr_name in [
ATTR_AUX_HEAT,
ATTR_FAN_MODE,
ATTR_HVAC_ACTION,
ATTR_PRESET_MODE,

View File

@@ -36,9 +36,6 @@
"fan_only": "Fan only"
},
"state_attributes": {
"aux_heat": {
"name": "Aux heat"
},
"current_humidity": {
"name": "Current humidity"
},
@@ -149,16 +146,6 @@
}
},
"services": {
"set_aux_heat": {
"name": "Turn on/off auxiliary heater",
"description": "Turns auxiliary heater on/off.",
"fields": {
"aux_heat": {
"name": "Auxiliary heating",
"description": "New value of auxiliary heater."
}
}
},
"set_preset_mode": {
"name": "Set preset mode",
"description": "Sets preset mode.",
@@ -267,16 +254,6 @@
}
}
},
"issues": {
"deprecated_climate_aux_url_custom": {
"title": "The {platform} custom integration is using deprecated climate auxiliary heater",
"description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease create a bug report at {issue_tracker}.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
},
"deprecated_climate_aux_no_url": {
"title": "[%key:component::climate::issues::deprecated_climate_aux_url_custom::title%]",
"description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
}
},
"exceptions": {
"not_valid_preset_mode": {
"message": "Preset mode {mode} is not valid. Valid preset modes are: {modes}."

View File

@@ -43,7 +43,7 @@ from homeassistant.util.dt import utcnow
from .const import (
CONF_ENTITY_CONFIG,
CONF_FILTER,
DOMAIN as CLOUD_DOMAIN,
DOMAIN,
PREF_ALEXA_REPORT_STATE,
PREF_ENABLE_ALEXA,
PREF_SHOULD_EXPOSE,
@@ -55,7 +55,7 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__)
CLOUD_ALEXA = f"{CLOUD_DOMAIN}.{ALEXA_DOMAIN}"
CLOUD_ALEXA = f"{DOMAIN}.{ALEXA_DOMAIN}"
# Time to wait when entity preferences have changed before syncing it to
# the cloud.

View File

@@ -41,7 +41,7 @@ from .const import (
CONF_ENTITY_CONFIG,
CONF_FILTER,
DEFAULT_DISABLE_2FA,
DOMAIN as CLOUD_DOMAIN,
DOMAIN,
PREF_DISABLE_2FA,
PREF_SHOULD_EXPOSE,
)
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__)
CLOUD_GOOGLE = f"{CLOUD_DOMAIN}.{GOOGLE_DOMAIN}"
CLOUD_GOOGLE = f"{DOMAIN}.{GOOGLE_DOMAIN}"
SUPPORTED_DOMAINS = {

View File

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

View File

@@ -23,6 +23,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .entity import ComelitBridgeBaseEntity
from .utils import bridge_api_call
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -134,11 +135,9 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
self._attr_current_temperature = values[0] / 10
self._attr_hvac_action = None
if _mode == ClimaComelitMode.OFF:
self._attr_hvac_action = HVACAction.OFF
if not _active:
self._attr_hvac_action = HVACAction.IDLE
if _mode in API_STATUS:
elif _mode in API_STATUS:
self._attr_hvac_action = API_STATUS[_mode]["hvac_action"]
self._attr_hvac_mode = None
@@ -157,6 +156,7 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
self._update_attributes()
super()._handle_coordinator_update()
@bridge_api_call
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if (
@@ -173,6 +173,7 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
self._attr_target_temperature = target_temp
self.async_write_ha_state()
@bridge_api_call
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode."""

View File

@@ -28,20 +28,22 @@ DEFAULT_HOST = "192.168.1.252"
DEFAULT_PIN = 111111
def user_form_schema(user_input: dict[str, Any] | None) -> vol.Schema:
"""Return user form schema."""
user_input = user_input or {}
return vol.Schema(
{
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
vol.Required(CONF_TYPE, default=BRIDGE): vol.In(DEVICE_TYPE_LIST),
}
)
USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
vol.Required(CONF_TYPE, default=BRIDGE): vol.In(DEVICE_TYPE_LIST),
}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PIN): cv.positive_int})
STEP_RECONFIGURE = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
}
)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
@@ -87,13 +89,11 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=user_form_schema(user_input)
)
return self.async_show_form(step_id="user", data_schema=USER_SCHEMA)
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
errors = {}
errors: dict[str, str] = {}
try:
info = await validate_input(self.hass, user_input)
@@ -108,21 +108,21 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form(
step_id="user", data_schema=user_form_schema(user_input), errors=errors
step_id="user", data_schema=USER_SCHEMA, errors=errors
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reauth flow."""
self.context["title_placeholders"] = {"host": entry_data[CONF_HOST]}
self.context["title_placeholders"] = {CONF_HOST: entry_data[CONF_HOST]}
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauth confirm."""
errors = {}
errors: dict[str, str] = {}
reauth_entry = self._get_reauth_entry()
entry_data = reauth_entry.data
@@ -163,6 +163,42 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the device."""
reconfigure_entry = self._get_reconfigure_entry()
if not user_input:
return self.async_show_form(
step_id="reconfigure", data_schema=STEP_RECONFIGURE
)
updated_host = user_input[CONF_HOST]
self._async_abort_entries_match({CONF_HOST: updated_host})
errors: dict[str, str] = {}
try:
await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # noqa: BLE001
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_update_reload_and_abort(
reconfigure_entry, data_updates={CONF_HOST: updated_host}
)
return self.async_show_form(
step_id="reconfigure",
data_schema=STEP_RECONFIGURE,
errors=errors,
)
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""

View File

@@ -7,13 +7,14 @@ from typing import Any, cast
from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import COVER, STATE_COVER, STATE_OFF, STATE_ON
from homeassistant.components.cover import CoverDeviceClass, CoverEntity, CoverState
from homeassistant.components.cover import CoverDeviceClass, CoverEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .entity import ComelitBridgeBaseEntity
from .utils import bridge_api_call
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -68,16 +69,10 @@ class ComelitCoverEntity(ComelitBridgeBaseEntity, RestoreEntity, CoverEntity):
def is_closed(self) -> bool | None:
"""Return if the cover is closed."""
if self._last_state in [None, "unknown"]:
return None
if self.device_status != STATE_COVER.index("stopped"):
return False
if self._last_action:
return self._last_action == STATE_COVER.index("closing")
return self._last_state == CoverState.CLOSED
return None
@property
def is_closing(self) -> bool:
@@ -89,6 +84,7 @@ class ComelitCoverEntity(ComelitBridgeBaseEntity, RestoreEntity, CoverEntity):
"""Return if the cover is opening."""
return self._current_action("opening")
@bridge_api_call
async def _cover_set_state(self, action: int, state: int) -> None:
"""Set desired cover state."""
self._last_state = self.state

View File

@@ -23,6 +23,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .entity import ComelitBridgeBaseEntity
from .utils import bridge_api_call
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -154,6 +155,7 @@ class ComelitHumidifierEntity(ComelitBridgeBaseEntity, HumidifierEntity):
self._update_attributes()
super()._handle_coordinator_update()
@bridge_api_call
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
if not self._attr_is_on:
@@ -171,6 +173,7 @@ class ComelitHumidifierEntity(ComelitBridgeBaseEntity, HumidifierEntity):
self._attr_target_humidity = humidity
self.async_write_ha_state()
@bridge_api_call
async def async_set_mode(self, mode: str) -> None:
"""Set humidifier mode."""
await self.coordinator.api.set_humidity_status(
@@ -179,6 +182,7 @@ class ComelitHumidifierEntity(ComelitBridgeBaseEntity, HumidifierEntity):
self._attr_mode = mode
self.async_write_ha_state()
@bridge_api_call
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on."""
await self.coordinator.api.set_humidity_status(
@@ -187,6 +191,7 @@ class ComelitHumidifierEntity(ComelitBridgeBaseEntity, HumidifierEntity):
self._attr_is_on = True
self.async_write_ha_state()
@bridge_api_call
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off."""
await self.coordinator.api.set_humidity_status(

View File

@@ -12,6 +12,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .entity import ComelitBridgeBaseEntity
from .utils import bridge_api_call
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -39,6 +40,7 @@ class ComelitLightEntity(ComelitBridgeBaseEntity, LightEntity):
_attr_name = None
_attr_supported_color_modes = {ColorMode.ONOFF}
@bridge_api_call
async def _light_set_state(self, state: int) -> None:
"""Set desired light state."""
await self.coordinator.api.set_device_status(LIGHT, self._device.index, state)

View File

@@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aiocomelit"],
"quality_scale": "bronze",
"requirements": ["aiocomelit==0.12.1"]
"quality_scale": "silver",
"requirements": ["aiocomelit==0.12.3"]
}

View File

@@ -26,9 +26,7 @@ rules:
unique-config-entry: done
# Silver
action-exceptions:
status: todo
comment: wrap api calls in try block
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
@@ -55,10 +53,8 @@ rules:
docs-known-limitations:
status: exempt
comment: no known limitations, yet
docs-supported-devices:
status: todo
comment: review and complete missing ones
docs-supported-functions: todo
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices:
@@ -72,9 +68,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow:
status: todo
comment: PR in progress
reconfiguration-flow: done
repair-issues:
status: exempt
comment: no known use cases for repair issues or flows, yet

View File

@@ -23,11 +23,24 @@
"pin": "[%key:component::comelit::config::step::reauth_confirm::data_description::pin%]",
"type": "The type of your Comelit device."
}
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]",
"pin": "[%key:common::config_flow::data::pin%]"
},
"data_description": {
"host": "[%key:component::comelit::config::step::user::data_description::host%]",
"port": "[%key:component::comelit::config::step::user::data_description::port%]",
"pin": "[%key:component::comelit::config::step::reauth_confirm::data_description::pin%]"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
@@ -76,6 +89,9 @@
"cannot_authenticate": {
"message": "Error authenticating"
},
"cannot_retrieve_data": {
"message": "Error retrieving data: {error}"
},
"update_failed": {
"message": "Failed to update data: {error}"
}

View File

@@ -13,6 +13,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .entity import ComelitBridgeBaseEntity
from .utils import bridge_api_call
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -56,6 +57,7 @@ class ComelitSwitchEntity(ComelitBridgeBaseEntity, SwitchEntity):
if device.type == OTHER:
self._attr_device_class = SwitchDeviceClass.OUTLET
@bridge_api_call
async def _switch_set_state(self, state: int) -> None:
"""Set desired switch state."""
await self.coordinator.api.set_device_status(

View File

@@ -1,13 +1,53 @@
"""Utils for Comelit."""
from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
from typing import Any, Concatenate
from aiocomelit.exceptions import CannotAuthenticate, CannotConnect, CannotRetrieveData
from aiohttp import ClientSession, CookieJar
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import aiohttp_client
from .const import DOMAIN
from .entity import ComelitBridgeBaseEntity
async def async_client_session(hass: HomeAssistant) -> ClientSession:
"""Return a new aiohttp session."""
return aiohttp_client.async_create_clientsession(
hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True)
)
def bridge_api_call[_T: ComelitBridgeBaseEntity, **_P](
func: Callable[Concatenate[_T, _P], Awaitable[None]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
"""Catch Bridge API call exceptions."""
@wraps(func)
async def cmd_wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
"""Wrap all command methods."""
try:
await func(self, *args, **kwargs)
except CannotConnect as err:
self.coordinator.last_update_success = False
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_connect",
translation_placeholders={"error": repr(err)},
) from err
except CannotRetrieveData as err:
self.coordinator.last_update_success = False
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_retrieve_data",
translation_placeholders={"error": repr(err)},
) from err
except CannotAuthenticate:
self.coordinator.last_update_success = False
self.coordinator.config_entry.async_start_reauth(self.hass)
return cmd_wrapper

View File

@@ -203,7 +203,11 @@ def async_get_agent_info(
name = agent.name
if not isinstance(name, str):
name = agent.entity_id
return AgentInfo(id=agent.entity_id, name=name)
return AgentInfo(
id=agent.entity_id,
name=name,
supports_streaming=agent.supports_streaming,
)
manager = get_agent_manager(hass)

View File

@@ -166,6 +166,7 @@ class AgentManager:
AgentInfo(
id=agent_id,
name=config_entry.title or config_entry.domain,
supports_streaming=False,
)
)
return agents

View File

@@ -18,8 +18,14 @@ class ConversationEntity(RestoreEntity):
_attr_should_poll = False
_attr_supported_features = ConversationEntityFeature(0)
_attr_supports_streaming = False
__last_activity: str | None = None
@property
def supports_streaming(self) -> bool:
"""Return if the entity supports streaming responses."""
return self._attr_supports_streaming
@property
@final
def state(self) -> str | None:

View File

@@ -16,6 +16,7 @@ class AgentInfo:
id: str
name: str
supports_streaming: bool
@dataclass(slots=True)

View File

@@ -6,5 +6,5 @@
"integration_type": "service",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["debugpy==1.8.13"]
"requirements": ["debugpy==1.8.14"]
}

View File

@@ -17,12 +17,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send
from ..const import (
CONF_MASTER_GATEWAY,
DOMAIN as DECONZ_DOMAIN,
HASSIO_CONFIGURATION_URL,
PLATFORMS,
)
from ..const import CONF_MASTER_GATEWAY, DOMAIN, HASSIO_CONFIGURATION_URL, PLATFORMS
from .config import DeconzConfig
if TYPE_CHECKING:
@@ -193,7 +188,7 @@ class DeconzHub:
config_entry_id=self.config_entry.entry_id,
configuration_url=configuration_url,
entry_type=dr.DeviceEntryType.SERVICE,
identifiers={(DECONZ_DOMAIN, self.api.config.bridge_id)},
identifiers={(DOMAIN, self.api.config.bridge_id)},
manufacturer="Dresden Elektronik",
model=self.api.config.model_id,
name=self.api.config.name,

View File

@@ -6,12 +6,16 @@ from datetime import datetime
from typing import Any
from homeassistant.components.media_player import (
BrowseMedia,
MediaClass,
MediaPlayerDeviceClass,
MediaPlayerEntity,
MediaPlayerEntityFeature,
MediaPlayerState,
MediaType,
RepeatMode,
SearchMedia,
SearchMediaQuery,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -407,3 +411,18 @@ class DemoSearchPlayer(AbstractDemoPlayer):
"""A Demo media player that supports searching."""
_attr_supported_features = SEARCH_PLAYER_SUPPORT
async def async_search_media(self, query: SearchMediaQuery) -> SearchMedia:
"""Demo implementation of search media."""
return SearchMedia(
result=[
BrowseMedia(
title="Search result",
media_class=MediaClass.MOVIE,
media_content_type=MediaType.MOVIE,
media_content_id="search_result_id",
can_play=True,
can_expand=False,
)
]
)

View File

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

View File

@@ -8,11 +8,7 @@ import voluptuous as vol
from homeassistant.const import CONF_DOMAIN
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers.trigger import (
TriggerActionType,
TriggerInfo,
TriggerProtocol,
)
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
from . import (
@@ -25,13 +21,28 @@ from .helpers import async_validate_device_automation_config
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
class DeviceAutomationTriggerProtocol(TriggerProtocol, Protocol):
class DeviceAutomationTriggerProtocol(Protocol):
"""Define the format of device_trigger modules.
Each module must define either TRIGGER_SCHEMA or async_validate_trigger_config
from TriggerProtocol.
Each module must define either TRIGGER_SCHEMA or async_validate_trigger_config.
"""
TRIGGER_SCHEMA: vol.Schema
async def async_validate_trigger_config(
self, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
async def async_attach_trigger(
self,
hass: HomeAssistant,
config: ConfigType,
action: TriggerActionType,
trigger_info: TriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
async def async_get_trigger_capabilities(
self, hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:

View File

@@ -7,6 +7,7 @@ from collections.abc import Callable
from datetime import timedelta
from fnmatch import translate
from functools import lru_cache, partial
from ipaddress import IPv4Address
import itertools
import logging
import re
@@ -22,6 +23,7 @@ from aiodiscover.discovery import (
from cached_ipaddress import cached_ip_addresses
from homeassistant import config_entries
from homeassistant.components import network
from homeassistant.components.device_tracker import (
ATTR_HOST_NAME,
ATTR_IP,
@@ -421,9 +423,33 @@ class DHCPWatcher(WatcherBase):
response.ip_address, response.hostname, response.mac_address
)
async def async_get_adapter_indexes(self) -> list[int] | None:
"""Get the adapter indexes."""
adapters = await network.async_get_adapters(self.hass)
if network.async_only_default_interface_enabled(adapters):
return None
return [
adapter["index"]
for adapter in adapters
if (
adapter["enabled"]
and adapter["index"] is not None
and adapter["ipv4"]
and (
addresses := [IPv4Address(ip["address"]) for ip in adapter["ipv4"]]
)
and any(
ip for ip in addresses if not ip.is_loopback and not ip.is_global
)
)
]
async def async_start(self) -> None:
"""Start watching for dhcp packets."""
self._unsub = await aiodhcpwatcher.async_start(self._async_process_dhcp_request)
self._unsub = await aiodhcpwatcher.async_start(
self._async_process_dhcp_request,
await self.async_get_adapter_indexes(),
)
class RediscoveryWatcher(WatcherBase):

View File

@@ -2,6 +2,7 @@
"domain": "dhcp",
"name": "DHCP Discovery",
"codeowners": ["@bdraco"],
"dependencies": ["network"],
"documentation": "https://www.home-assistant.io/integrations/dhcp",
"integration_type": "system",
"iot_class": "local_push",

View File

@@ -25,37 +25,28 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Dlib Face detection platform."""
source: list[dict[str, str]] = config[CONF_SOURCE]
add_entities(
DlibFaceDetectEntity(camera[CONF_ENTITY_ID], camera.get(CONF_NAME))
for camera in config[CONF_SOURCE]
for camera in source
)
class DlibFaceDetectEntity(ImageProcessingFaceEntity):
"""Dlib Face API entity for identify."""
def __init__(self, camera_entity, name=None):
def __init__(self, camera_entity: str, name: str | None) -> None:
"""Initialize Dlib face entity."""
super().__init__()
self._camera = camera_entity
self._attr_camera_entity = camera_entity
if name:
self._name = name
self._attr_name = name
else:
self._name = f"Dlib Face {split_entity_id(camera_entity)[1]}"
self._attr_name = f"Dlib Face {split_entity_id(camera_entity)[1]}"
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
def process_image(self, image):
def process_image(self, image: bytes) -> None:
"""Process image."""
fak_file = io.BytesIO(image)

View File

@@ -11,6 +11,7 @@ import voluptuous as vol
from homeassistant.components.image_processing import (
CONF_CONFIDENCE,
PLATFORM_SCHEMA as IMAGE_PROCESSING_PLATFORM_SCHEMA,
FaceInformation,
ImageProcessingFaceEntity,
)
from homeassistant.const import ATTR_NAME, CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE
@@ -38,31 +39,40 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Dlib Face detection platform."""
confidence: float = config[CONF_CONFIDENCE]
faces: dict[str, str] = config[CONF_FACES]
source: list[dict[str, str]] = config[CONF_SOURCE]
add_entities(
DlibFaceIdentifyEntity(
camera[CONF_ENTITY_ID],
config[CONF_FACES],
faces,
camera.get(CONF_NAME),
config[CONF_CONFIDENCE],
confidence,
)
for camera in config[CONF_SOURCE]
for camera in source
)
class DlibFaceIdentifyEntity(ImageProcessingFaceEntity):
"""Dlib Face API entity for identify."""
def __init__(self, camera_entity, faces, name, tolerance):
def __init__(
self,
camera_entity: str,
faces: dict[str, str],
name: str | None,
tolerance: float,
) -> None:
"""Initialize Dlib face identify entry."""
super().__init__()
self._camera = camera_entity
self._attr_camera_entity = camera_entity
if name:
self._name = name
self._attr_name = name
else:
self._name = f"Dlib Face {split_entity_id(camera_entity)[1]}"
self._attr_name = f"Dlib Face {split_entity_id(camera_entity)[1]}"
self._faces = {}
for face_name, face_file in faces.items():
@@ -74,17 +84,7 @@ class DlibFaceIdentifyEntity(ImageProcessingFaceEntity):
self._tolerance = tolerance
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
def process_image(self, image):
def process_image(self, image: bytes) -> None:
"""Process image."""
fak_file = io.BytesIO(image)
@@ -94,7 +94,7 @@ class DlibFaceIdentifyEntity(ImageProcessingFaceEntity):
image = face_recognition.load_image_file(fak_file)
unknowns = face_recognition.face_encodings(image)
found = []
found: list[FaceInformation] = []
for unknown_face in unknowns:
for name, face in self._faces.items():
result = face_recognition.compare_faces(

View File

@@ -6,6 +6,7 @@ import io
import logging
import os
import time
from typing import Any
from PIL import Image, ImageDraw, UnidentifiedImageError
from pydoods import PyDOODS
@@ -88,10 +89,11 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Doods client."""
url = config[CONF_URL]
auth_key = config[CONF_AUTH_KEY]
detector_name = config[CONF_DETECTOR]
timeout = config[CONF_TIMEOUT]
url: str = config[CONF_URL]
auth_key: str = config[CONF_AUTH_KEY]
detector_name: str = config[CONF_DETECTOR]
source: list[dict[str, str]] = config[CONF_SOURCE]
timeout: int = config[CONF_TIMEOUT]
doods = PyDOODS(url, auth_key, timeout)
response = doods.get_detectors()
@@ -113,31 +115,35 @@ def setup_platform(
add_entities(
Doods(
hass,
camera[CONF_ENTITY_ID],
camera.get(CONF_NAME),
doods,
detector,
config,
)
for camera in config[CONF_SOURCE]
for camera in source
)
class Doods(ImageProcessingEntity):
"""Doods image processing service client."""
def __init__(self, hass, camera_entity, name, doods, detector, config):
def __init__(
self,
camera_entity: str,
name: str | None,
doods: PyDOODS,
detector: dict[str, Any],
config: dict[str, Any],
) -> None:
"""Initialize the DOODS entity."""
self.hass = hass
self._camera_entity = camera_entity
self._attr_camera_entity = camera_entity
if name:
self._name = name
self._attr_name = name
else:
name = split_entity_id(camera_entity)[1]
self._name = f"Doods {name}"
self._attr_name = f"Doods {split_entity_id(camera_entity)[1]}"
self._doods = doods
self._file_out = config[CONF_FILE_OUT]
self._file_out: list[template.Template] = config[CONF_FILE_OUT]
self._detector_name = detector["name"]
# detector config and aspect ratio
@@ -150,16 +156,16 @@ class Doods(ImageProcessingEntity):
self._aspect = self._width / self._height
# the base confidence
dconfig = {}
confidence = config[CONF_CONFIDENCE]
dconfig: dict[str, float] = {}
confidence: float = config[CONF_CONFIDENCE]
# handle labels and specific detection areas
labels = config[CONF_LABELS]
labels: list[str | dict[str, Any]] = config[CONF_LABELS]
self._label_areas = {}
self._label_covers = {}
for label in labels:
if isinstance(label, dict):
label_name = label[CONF_NAME]
label_name: str = label[CONF_NAME]
if label_name not in detector["labels"] and label_name != "*":
_LOGGER.warning("Detector does not support label %s", label_name)
continue
@@ -207,28 +213,18 @@ class Doods(ImageProcessingEntity):
self._covers = area_config[CONF_COVERS]
self._dconfig = dconfig
self._matches = {}
self._matches: dict[str, list[dict[str, Any]]] = {}
self._total_matches = 0
self._last_image = None
self._process_time = 0
self._process_time = 0.0
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera_entity
@property
def name(self):
"""Return the name of the image processor."""
return self._name
@property
def state(self):
def state(self) -> int:
"""Return the state of the entity."""
return self._total_matches
@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> dict[str, Any]:
"""Return device specific state attributes."""
return {
ATTR_MATCHES: self._matches,
@@ -281,7 +277,7 @@ class Doods(ImageProcessingEntity):
os.makedirs(os.path.dirname(path), exist_ok=True)
img.save(path)
def process_image(self, image):
def process_image(self, image: bytes) -> None:
"""Process the image."""
try:
img = Image.open(io.BytesIO(bytearray(image))).convert("RGB")
@@ -312,7 +308,7 @@ class Doods(ImageProcessingEntity):
time.monotonic() - start,
)
matches = {}
matches: dict[str, list[dict[str, Any]]] = {}
total_matches = 0
if not response or "error" in response:
@@ -382,9 +378,7 @@ class Doods(ImageProcessingEntity):
paths = []
for path_template in self._file_out:
if isinstance(path_template, template.Template):
paths.append(
path_template.render(camera_entity=self._camera_entity)
)
paths.append(path_template.render(camera_entity=self.camera_entity))
else:
paths.append(path_template)
self._save_image(image, matches, paths)

View File

@@ -2,32 +2,13 @@
from __future__ import annotations
from http import HTTPStatus
import os
import re
import threading
import requests
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.util import raise_if_invalid_filename, raise_if_invalid_path
from homeassistant.core import HomeAssistant
from .const import (
_LOGGER,
ATTR_FILENAME,
ATTR_OVERWRITE,
ATTR_SUBDIR,
ATTR_URL,
CONF_DOWNLOAD_DIR,
DOMAIN,
DOWNLOAD_COMPLETED_EVENT,
DOWNLOAD_FAILED_EVENT,
SERVICE_DOWNLOAD_FILE,
)
from .const import _LOGGER, CONF_DOWNLOAD_DIR
from .services import register_services
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -44,127 +25,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
return False
def download_file(service: ServiceCall) -> None:
"""Start thread to download file specified in the URL."""
def do_download() -> None:
"""Download the file."""
try:
url = service.data[ATTR_URL]
subdir = service.data.get(ATTR_SUBDIR)
filename = service.data.get(ATTR_FILENAME)
overwrite = service.data.get(ATTR_OVERWRITE)
if subdir:
# Check the path
raise_if_invalid_path(subdir)
final_path = None
req = requests.get(url, stream=True, timeout=10)
if req.status_code != HTTPStatus.OK:
_LOGGER.warning(
"Downloading '%s' failed, status_code=%d", url, req.status_code
)
hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
{"url": url, "filename": filename},
)
else:
if filename is None and "content-disposition" in req.headers:
match = re.findall(
r"filename=(\S+)", req.headers["content-disposition"]
)
if match:
filename = match[0].strip("'\" ")
if not filename:
filename = os.path.basename(url).strip()
if not filename:
filename = "ha_download"
# Check the filename
raise_if_invalid_filename(filename)
# Do we want to download to subdir, create if needed
if subdir:
subdir_path = os.path.join(download_path, subdir)
# Ensure subdir exist
os.makedirs(subdir_path, exist_ok=True)
final_path = os.path.join(subdir_path, filename)
else:
final_path = os.path.join(download_path, filename)
path, ext = os.path.splitext(final_path)
# If file exist append a number.
# We test filename, filename_2..
if not overwrite:
tries = 1
final_path = path + ext
while os.path.isfile(final_path):
tries += 1
final_path = f"{path}_{tries}.{ext}"
_LOGGER.debug("%s -> %s", url, final_path)
with open(final_path, "wb") as fil:
for chunk in req.iter_content(1024):
fil.write(chunk)
_LOGGER.debug("Downloading of %s done", url)
hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_COMPLETED_EVENT}",
{"url": url, "filename": filename},
)
except requests.exceptions.ConnectionError:
_LOGGER.exception("ConnectionError occurred for %s", url)
hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
{"url": url, "filename": filename},
)
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):
os.remove(final_path)
except ValueError:
_LOGGER.exception("Invalid value")
hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
{"url": url, "filename": filename},
)
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):
os.remove(final_path)
threading.Thread(target=do_download).start()
async_register_admin_service(
hass,
DOMAIN,
SERVICE_DOWNLOAD_FILE,
download_file,
schema=vol.Schema(
{
vol.Optional(ATTR_FILENAME): cv.string,
vol.Optional(ATTR_SUBDIR): cv.string,
vol.Required(ATTR_URL): cv.url,
vol.Optional(ATTR_OVERWRITE, default=False): cv.boolean,
}
),
)
register_services(hass)
return True

View File

@@ -0,0 +1,159 @@
"""Support for functionality to download files."""
from __future__ import annotations
from http import HTTPStatus
import os
import re
import threading
import requests
import voluptuous as vol
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.util import raise_if_invalid_filename, raise_if_invalid_path
from .const import (
_LOGGER,
ATTR_FILENAME,
ATTR_OVERWRITE,
ATTR_SUBDIR,
ATTR_URL,
CONF_DOWNLOAD_DIR,
DOMAIN,
DOWNLOAD_COMPLETED_EVENT,
DOWNLOAD_FAILED_EVENT,
SERVICE_DOWNLOAD_FILE,
)
def download_file(service: ServiceCall) -> None:
"""Start thread to download file specified in the URL."""
entry = service.hass.config_entries.async_loaded_entries(DOMAIN)[0]
download_path = entry.data[CONF_DOWNLOAD_DIR]
def do_download() -> None:
"""Download the file."""
try:
url = service.data[ATTR_URL]
subdir = service.data.get(ATTR_SUBDIR)
filename = service.data.get(ATTR_FILENAME)
overwrite = service.data.get(ATTR_OVERWRITE)
if subdir:
# Check the path
raise_if_invalid_path(subdir)
final_path = None
req = requests.get(url, stream=True, timeout=10)
if req.status_code != HTTPStatus.OK:
_LOGGER.warning(
"Downloading '%s' failed, status_code=%d", url, req.status_code
)
service.hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
{"url": url, "filename": filename},
)
else:
if filename is None and "content-disposition" in req.headers:
match = re.findall(
r"filename=(\S+)", req.headers["content-disposition"]
)
if match:
filename = match[0].strip("'\" ")
if not filename:
filename = os.path.basename(url).strip()
if not filename:
filename = "ha_download"
# Check the filename
raise_if_invalid_filename(filename)
# Do we want to download to subdir, create if needed
if subdir:
subdir_path = os.path.join(download_path, subdir)
# Ensure subdir exist
os.makedirs(subdir_path, exist_ok=True)
final_path = os.path.join(subdir_path, filename)
else:
final_path = os.path.join(download_path, filename)
path, ext = os.path.splitext(final_path)
# If file exist append a number.
# We test filename, filename_2..
if not overwrite:
tries = 1
final_path = path + ext
while os.path.isfile(final_path):
tries += 1
final_path = f"{path}_{tries}.{ext}"
_LOGGER.debug("%s -> %s", url, final_path)
with open(final_path, "wb") as fil:
for chunk in req.iter_content(1024):
fil.write(chunk)
_LOGGER.debug("Downloading of %s done", url)
service.hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_COMPLETED_EVENT}",
{"url": url, "filename": filename},
)
except requests.exceptions.ConnectionError:
_LOGGER.exception("ConnectionError occurred for %s", url)
service.hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
{"url": url, "filename": filename},
)
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):
os.remove(final_path)
except ValueError:
_LOGGER.exception("Invalid value")
service.hass.bus.fire(
f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
{"url": url, "filename": filename},
)
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):
os.remove(final_path)
threading.Thread(target=do_download).start()
def register_services(hass: HomeAssistant) -> None:
"""Register the services for the downloader component."""
async_register_admin_service(
hass,
DOMAIN,
SERVICE_DOWNLOAD_FILE,
download_file,
schema=vol.Schema(
{
vol.Optional(ATTR_FILENAME): cv.string,
vol.Optional(ATTR_SUBDIR): cv.string,
vol.Required(ATTR_URL): cv.url,
vol.Optional(ATTR_OVERWRITE, default=False): cv.boolean,
}
),
)

View File

@@ -572,7 +572,7 @@ def device_class_and_uom(
) -> tuple[SensorDeviceClass | None, str | None]:
"""Get native unit of measurement from telegram,."""
dsmr_object = getattr(data, entity_description.obis_reference)
uom: str | None = getattr(dsmr_object, "unit") or None
uom: str | None = dsmr_object.unit or None
with suppress(ValueError):
if entity_description.device_class == SensorDeviceClass.GAS and (
enery_uom := UnitOfEnergy(str(uom))

View File

@@ -53,7 +53,6 @@ SUPPORT_FLAGS_THERMOSTAT = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
| ClimateEntityFeature.AUX_HEAT
)
@@ -148,11 +147,6 @@ class EcoNetThermostat(EcoNetEntity[Thermostat], ClimateEntity):
if target_temp_low or target_temp_high:
self._econet.set_set_point(None, target_temp_high, target_temp_low)
@property
def is_aux_heat(self) -> bool:
"""Return true if aux heater."""
return self._econet.mode == ThermostatOperationMode.EMERGENCY_HEAT
@property
def hvac_mode(self) -> HVACMode:
"""Return hvac operation ie. heat, cool, mode.
@@ -211,12 +205,12 @@ class EcoNetThermostat(EcoNetEntity[Thermostat], ClimateEntity):
self._econet.set_fan_mode(HA_FAN_STATE_TO_ECONET[fan_mode])
@property
def min_temp(self):
def min_temp(self) -> float:
"""Return the minimum temperature."""
return self._econet.set_point_limits[0]
@property
def max_temp(self):
def max_temp(self) -> float:
"""Return the maximum temperature."""
return self._econet.set_point_limits[1]

View File

@@ -17,7 +17,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import EcovacsConfigEntry
from .entity import EcovacsCapabilityEntityDescription, EcovacsDescriptionEntity, EventT
from .util import get_supported_entitites
from .util import get_supported_entities
@dataclass(kw_only=True, frozen=True)
@@ -49,7 +49,7 @@ async def async_setup_entry(
) -> None:
"""Add entities for passed config_entry in HA."""
async_add_entities(
get_supported_entitites(
get_supported_entities(
config_entry.runtime_data, EcovacsBinarySensor, ENTITY_DESCRIPTIONS
)
)

View File

@@ -16,13 +16,13 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import EcovacsConfigEntry
from .const import SUPPORTED_LIFESPANS, SUPPORTED_STATION_ACTIONS
from .const import SUPPORTED_LIFESPANS
from .entity import (
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EcovacsEntity,
)
from .util import get_supported_entitites
from .util import get_supported_entities
@dataclass(kw_only=True, frozen=True)
@@ -62,7 +62,7 @@ STATION_ENTITY_DESCRIPTIONS = tuple(
key=f"station_action_{action.name.lower()}",
translation_key=f"station_action_{action.name.lower()}",
)
for action in SUPPORTED_STATION_ACTIONS
for action in StationAction
)
@@ -85,7 +85,7 @@ async def async_setup_entry(
) -> None:
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
entities: list[EcovacsEntity] = get_supported_entitites(
entities: list[EcovacsEntity] = get_supported_entities(
controller, EcovacsButtonEntity, ENTITY_DESCRIPTIONS
)
entities.extend(

View File

@@ -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==13.1.0"]
"requirements": ["py-sucks==0.9.11", "deebot-client==13.2.1"]
}

View File

@@ -25,7 +25,7 @@ from .entity import (
EcovacsEntity,
EventT,
)
from .util import get_supported_entitites
from .util import get_supported_entities
@dataclass(kw_only=True, frozen=True)
@@ -87,7 +87,7 @@ async def async_setup_entry(
) -> None:
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
entities: list[EcovacsEntity] = get_supported_entitites(
entities: list[EcovacsEntity] = get_supported_entities(
controller, EcovacsNumberEntity, ENTITY_DESCRIPTIONS
)
if entities:

View File

@@ -16,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import EcovacsConfigEntry
from .entity import EcovacsCapabilityEntityDescription, EcovacsDescriptionEntity, EventT
from .util import get_name_key, get_supported_entitites
from .util import get_name_key, get_supported_entities
@dataclass(kw_only=True, frozen=True)
@@ -59,7 +59,7 @@ async def async_setup_entry(
) -> None:
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
entities = get_supported_entitites(
entities = get_supported_entities(
controller, EcovacsSelectEntity, ENTITY_DESCRIPTIONS
)
if entities:

View File

@@ -6,7 +6,8 @@ from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Generic
from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan
from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan, DeviceType
from deebot_client.device import Device
from deebot_client.events import (
BatteryEvent,
ErrorEvent,
@@ -34,7 +35,7 @@ from homeassistant.const import (
UnitOfArea,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
@@ -47,7 +48,7 @@ from .entity import (
EcovacsLegacyEntity,
EventT,
)
from .util import get_name_key, get_options, get_supported_entitites
from .util import get_name_key, get_options, get_supported_entities
@dataclass(kw_only=True, frozen=True)
@@ -59,6 +60,15 @@ class EcovacsSensorEntityDescription(
"""Ecovacs sensor entity description."""
value_fn: Callable[[EventT], StateType]
native_unit_of_measurement_fn: Callable[[DeviceType], str | None] | None = None
@callback
def get_area_native_unit_of_measurement(device_type: DeviceType) -> str | None:
"""Get the area native unit of measurement based on device type."""
if device_type is DeviceType.MOWER:
return UnitOfArea.SQUARE_CENTIMETERS
return UnitOfArea.SQUARE_METERS
ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
@@ -68,7 +78,9 @@ ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
capability_fn=lambda caps: caps.stats.clean,
value_fn=lambda e: e.area,
translation_key="stats_area",
native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
device_class=SensorDeviceClass.AREA,
native_unit_of_measurement_fn=get_area_native_unit_of_measurement,
suggested_unit_of_measurement=UnitOfArea.SQUARE_METERS,
),
EcovacsSensorEntityDescription[StatsEvent](
key="stats_time",
@@ -85,6 +97,7 @@ ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
value_fn=lambda e: e.area,
key="total_stats_area",
translation_key="total_stats_area",
device_class=SensorDeviceClass.AREA,
native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -197,7 +210,7 @@ async def async_setup_entry(
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
entities: list[EcovacsEntity] = get_supported_entitites(
entities: list[EcovacsEntity] = get_supported_entities(
controller, EcovacsSensor, ENTITY_DESCRIPTIONS
)
entities.extend(
@@ -249,6 +262,27 @@ class EcovacsSensor(
entity_description: EcovacsSensorEntityDescription
def __init__(
self,
device: Device,
capability: CapabilityEvent,
entity_description: EcovacsSensorEntityDescription,
**kwargs: Any,
) -> None:
"""Initialize entity."""
super().__init__(device, capability, entity_description, **kwargs)
if (
entity_description.native_unit_of_measurement_fn
and (
native_unit_of_measurement
:= entity_description.native_unit_of_measurement_fn(
device.capabilities.device_type
)
)
is not None
):
self._attr_native_unit_of_measurement = native_unit_of_measurement
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
await super().async_added_to_hass()

View File

@@ -17,7 +17,7 @@ from .entity import (
EcovacsDescriptionEntity,
EcovacsEntity,
)
from .util import get_supported_entitites
from .util import get_supported_entities
@dataclass(kw_only=True, frozen=True)
@@ -109,7 +109,7 @@ async def async_setup_entry(
) -> None:
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
entities: list[EcovacsEntity] = get_supported_entitites(
entities: list[EcovacsEntity] = get_supported_entities(
controller, EcovacsSwitchEntity, ENTITY_DESCRIPTIONS
)
if entities:

View File

@@ -32,7 +32,7 @@ def get_client_device_id(hass: HomeAssistant, self_hosted: bool) -> str:
)
def get_supported_entitites(
def get_supported_entities(
controller: EcovacsController,
entity_class: type[EcovacsDescriptionEntity],
descriptions: tuple[EcovacsCapabilityEntityDescription, ...],

View File

@@ -13,6 +13,7 @@ PLATFORMS = [
Platform.CLIMATE,
Platform.LIGHT,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.TIME,

View File

@@ -15,6 +15,12 @@
},
"night_temperature_offset": {
"default": "mdi:thermometer"
},
"system_led": {
"default": "mdi:led-on",
"state": {
"0": "mdi:led-off"
}
}
},
"sensor": {

View File

@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["eheimdigital"],
"quality_scale": "bronze",
"requirements": ["eheimdigital==1.1.0"],
"requirements": ["eheimdigital==1.2.0"],
"zeroconf": [
{ "type": "_http._tcp.local.", "name": "eheimdigital._http._tcp.local." }
]

View File

@@ -109,6 +109,20 @@ HEATER_DESCRIPTIONS: tuple[EheimDigitalNumberDescription[EheimDigitalHeater], ..
),
)
GENERAL_DESCRIPTIONS: tuple[EheimDigitalNumberDescription[EheimDigitalDevice], ...] = (
EheimDigitalNumberDescription[EheimDigitalDevice](
key="system_led",
translation_key="system_led",
entity_category=EntityCategory.CONFIG,
native_min_value=0,
native_max_value=100,
native_step=PRECISION_WHOLE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda device: device.sys_led,
set_value_fn=lambda device, value: device.set_sys_led(int(value)),
),
)
async def async_setup_entry(
hass: HomeAssistant,
@@ -138,6 +152,10 @@ async def async_setup_entry(
)
for description in HEATER_DESCRIPTIONS
)
entities.extend(
EheimDigitalNumber[EheimDigitalDevice](coordinator, device, description)
for description in GENERAL_DESCRIPTIONS
)
async_add_entities(entities)

View File

@@ -0,0 +1,102 @@
"""EHEIM Digital select entities."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Generic, TypeVar, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.types import FilterMode
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
from .entity import EheimDigitalEntity
PARALLEL_UPDATES = 0
_DeviceT_co = TypeVar("_DeviceT_co", bound=EheimDigitalDevice, covariant=True)
@dataclass(frozen=True, kw_only=True)
class EheimDigitalSelectDescription(SelectEntityDescription, Generic[_DeviceT_co]):
"""Class describing EHEIM Digital select entities."""
value_fn: Callable[[_DeviceT_co], str | None]
set_value_fn: Callable[[_DeviceT_co, str], Awaitable[None]]
CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalSelectDescription[EheimDigitalClassicVario], ...
] = (
EheimDigitalSelectDescription[EheimDigitalClassicVario](
key="filter_mode",
translation_key="filter_mode",
value_fn=(
lambda device: device.filter_mode.name.lower()
if device.filter_mode is not None
else None
),
set_value_fn=(
lambda device, value: device.set_filter_mode(FilterMode[value.upper()])
),
options=[name.lower() for name in FilterMode.__members__],
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: EheimDigitalConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the callbacks for the coordinator so select entities can be added as devices are found."""
coordinator = entry.runtime_data
def async_setup_device_entities(
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the number entities for one or multiple devices."""
entities: list[EheimDigitalSelect[EheimDigitalDevice]] = []
for device in device_address.values():
if isinstance(device, EheimDigitalClassicVario):
entities.extend(
EheimDigitalSelect[EheimDigitalClassicVario](
coordinator, device, description
)
for description in CLASSICVARIO_DESCRIPTIONS
)
async_add_entities(entities)
coordinator.add_platform_callback(async_setup_device_entities)
async_setup_device_entities(coordinator.hub.devices)
class EheimDigitalSelect(
EheimDigitalEntity[_DeviceT_co], SelectEntity, Generic[_DeviceT_co]
):
"""Represent an EHEIM Digital select entity."""
entity_description: EheimDigitalSelectDescription[_DeviceT_co]
def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
device: _DeviceT_co,
description: EheimDigitalSelectDescription[_DeviceT_co],
) -> None:
"""Initialize an EHEIM Digital select entity."""
super().__init__(coordinator, device)
self.entity_description = description
self._attr_unique_id = f"{self._device_address}_{description.key}"
@override
async def async_select_option(self, option: str) -> None:
return await self.entity_description.set_value_fn(self._device, option)
@override
def _async_update_attrs(self) -> None:
self._attr_current_option = self.entity_description.value_fn(self._device)

View File

@@ -62,6 +62,19 @@
},
"night_temperature_offset": {
"name": "Night temperature offset"
},
"system_led": {
"name": "System LED brightness"
}
},
"select": {
"filter_mode": {
"name": "Filter mode",
"state": {
"manual": "Manual",
"pulse": "Pulse",
"bio": "Bio"
}
}
},
"sensor": {

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