Compare commits

..

505 Commits

Author SHA1 Message Date
Franck Nijhof
782ff12e6e Bump version to 2024.8.0b7 2024-08-07 11:26:03 +02:00
lunmay
af6f78a784 Fix typo on one of islamic_prayer_times calculation_method option (#123281) 2024-08-07 11:25:55 +02:00
Paulus Schoutsen
db32460f3b Reload conversation entries on update (#123279) 2024-08-07 11:25:52 +02:00
Erwin Douna
270990fe39 Tado change repair issue (#123256) 2024-08-07 11:25:48 +02:00
Franck Nijhof
a10fed9d72 Bump version to 2024.8.0b6 2024-08-07 10:22:39 +02:00
tronikos
cc5699bf08 Fix Google Cloud TTS not respecting config values (#123275) 2024-08-07 10:22:30 +02:00
Jesse Hills
ad674a1c2b Update ESPHome voice assistant pipeline log warning (#123269) 2024-08-07 10:22:27 +02:00
J. Nick Koston
b0269faae4 Allow non-admins to subscribe to newer registry update events (#123267) 2024-08-07 10:22:24 +02:00
starkillerOG
1143efedc5 Bump reolink-aio to 0.9.7 (#123263) 2024-08-07 10:22:21 +02:00
Matthias Alphart
9e75b63925 Update knx-frontend to 2024.8.6.211307 (#123261) 2024-08-07 10:22:18 +02:00
puddly
940327dccf Bump ZHA to 0.0.28 (#123259)
* Bump ZHA to 0.0.28

* Drop redundant radio schema conversion
2024-08-07 10:22:14 +02:00
Steve Repsher
0270026f7c Adapt static resource handler to aiohttp 3.10 (#123166) 2024-08-07 10:22:11 +02:00
Franck Nijhof
b636096ac3 Bump version to 2024.8.0b5 2024-08-06 18:08:19 +02:00
Franck Nijhof
a243ed5b23 Update frontend to 20240806.1 (#123252) 2024-08-06 18:07:49 +02:00
Joost Lekkerkerker
3cf3780587 Bump mficlient to 0.5.0 (#123250) 2024-08-06 18:06:50 +02:00
Robert Resch
3d0a0cf376 Bump deebot-client to 8.3.0 (#123249) 2024-08-06 18:05:00 +02:00
J. Nick Koston
7aae9d9ad3 Fix sense doing blocking I/O in the event loop (#123247) 2024-08-06 18:04:57 +02:00
Franck Nijhof
870bb7efd4 Mark FFmpeg integration as system type (#123241) 2024-08-06 18:04:53 +02:00
Robert Resch
35a6679ae9 Delete mobile_app cloudhook if not logged into the cloud (#123234) 2024-08-06 18:04:49 +02:00
Yehazkel
a09d0117b1 Fix Tami4 device name is None (#123156)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-08-06 18:04:44 +02:00
Franck Nijhof
e9fe98f7f9 Bump version to 2024.8.0b4 2024-08-06 13:22:46 +02:00
Franck Nijhof
5b2e188b52 Mark Google Assistant integration as system type (#123233) 2024-08-06 13:22:03 +02:00
Franck Nijhof
c1953e938d Mark Alexa integration as system type (#123232) 2024-08-06 13:21:59 +02:00
Franck Nijhof
77bcbbcf53 Update frontend to 20240806.0 (#123230) 2024-08-06 12:51:24 +02:00
Joost Lekkerkerker
97587fae08 Bump yt-dlp to 2023.08.06 (#123229) 2024-08-06 12:51:21 +02:00
Matthias Alphart
01b54fe1a9 Update knx-frontend to 2024.8.6.85349 (#123226) 2024-08-06 12:51:17 +02:00
Clifford Roche
f796950493 Update greeclimate to 2.1.0 (#123210) 2024-08-06 12:51:14 +02:00
flopp999
495fd946bc Fix growatt server tlx battery api key (#123191) 2024-08-06 12:51:10 +02:00
Jesse Hills
6af1e25d7e Show project version as sw_version in ESPHome (#123183) 2024-08-06 12:51:07 +02:00
Jesse Hills
6d47a4d7e4 Add support for ESPHome update entities to be checked on demand (#123161) 2024-08-06 12:51:04 +02:00
Petro31
fd5533d719 Fix yamaha legacy receivers (#122985) 2024-08-06 12:50:59 +02:00
Franck Nijhof
d530137bec Bump version to 2024.8.0b3 2024-08-05 21:12:09 +02:00
Franck Nijhof
4f722e864c Mark webhook as a system integration type (#123204) 2024-08-05 21:11:46 +02:00
Franck Nijhof
62d38e786d Mark assist_pipeline as a system integration type (#123202) 2024-08-05 21:10:49 +02:00
Franck Nijhof
859874487e Mark tag to be an entity component (#123200) 2024-08-05 21:09:50 +02:00
Bram Kragten
b16bf29819 Update frontend to 20240805.1 (#123196) 2024-08-05 21:09:46 +02:00
Marius
6b10dbb38c Fix state icon for closed valve entities (#123190) 2024-08-05 21:09:43 +02:00
Joost Lekkerkerker
ea20c4b375 Fix MPD issue creation (#123187) 2024-08-05 21:09:40 +02:00
musapinar
0427aeccb0 Add Matter Leedarson RGBTW Bulb to the transition blocklist (#123182) 2024-08-05 21:09:37 +02:00
Matthias Alphart
4898ba932d Use KNX UI entity platform controller class (#123128) 2024-08-05 21:09:32 +02:00
Franck Nijhof
35a3d2306c Bump version to 2024.8.0b2 2024-08-05 12:22:03 +02:00
Calvin Walton
cdb378066c Add Govee H612B to the Matter transition blocklist (#123163) 2024-08-05 12:21:40 +02:00
Brett Adams
85700fd80f Fix class attribute condition in Tesla Fleet (#123162) 2024-08-05 12:21:37 +02:00
J. Nick Koston
73a2ad7304 Bump aiohttp to 3.10.1 (#123159) 2024-08-05 12:21:34 +02:00
dupondje
f6c4b6b045 dsmr: migrate hourly_gas_meter_reading to mbus device (#123149) 2024-08-05 12:21:30 +02:00
Steve Repsher
0b4d921762 Restore old service worker URL (#123131) 2024-08-05 12:21:27 +02:00
David F. Mulcahey
c8a0e5228d Bump ZHA lib to 0.0.27 (#123125) 2024-08-05 12:21:23 +02:00
Kim de Vos
832bac8c63 Use slugify to create id for UniFi WAN latency (#123108)
Use slugify to create id for latency
2024-08-05 12:21:20 +02:00
Arie Catsman
eccce7017f Bump pyenphase to 1.22.0 (#123103) 2024-08-05 12:21:16 +02:00
Louis Christ
fdb1baadbe Fix wrong DeviceInfo in bluesound integration (#123101)
Fix bluesound device info
2024-08-05 12:21:12 +02:00
Shay Levy
7623ee49e4 Ignore Shelly IPv6 address in zeroconf (#123081) 2024-08-05 12:20:20 +02:00
Mr. Bubbles
fa241dcd04 Catch exception in coordinator setup of IronOS integration (#123079) 2024-08-05 12:20:17 +02:00
Denis Shulyaka
bee77041e8 Change enum type to string for Google Generative AI Conversation (#123069) 2024-08-05 12:20:13 +02:00
Paulus Schoutsen
50b7eb44d1 Add CONTROL supported feature to Google conversation when API access (#123046)
* Add CONTROL supported feature to Google conversation when API access

* Better function name

* Handle entry update inline

* Reload instead of update
2024-08-05 12:20:10 +02:00
Clifford Roche
7b1bf82e3c Update greeclimate to 2.0.0 (#121030)
Co-authored-by: Joostlek <joostlek@outlook.com>
2024-08-05 12:20:01 +02:00
Franck Nijhof
fe82e7f24d Bump version to 2024.8.0b1 2024-08-02 17:46:01 +02:00
Bram Kragten
433c1a57e7 Update frontend to 20240802.0 (#123072) 2024-08-02 17:45:50 +02:00
Joost Lekkerkerker
b36059fc64 Do not raise repair issue about missing integration in safe mode (#123066) 2024-08-02 17:45:47 +02:00
Philip Vanloo
13c9d69440 Add additional items to REPEAT_MAP in LinkPlay (#123063)
* Upgrade python-linkplay, add items to REPEAT_MAP

* Undo dependency bump
2024-08-02 17:45:43 +02:00
Philip Vanloo
9c7134a865 LinkPlay: Bump python-linkplay to 0.0.6 (#123062)
Bump python-linkplay to 0.0.6
2024-08-02 17:45:39 +02:00
Erik Montnemery
d7cc2a7e9a Correct squeezebox service (#123060) 2024-08-02 17:45:36 +02:00
Fabian
f9276e28b0 Add device class (#123059) 2024-08-02 17:45:32 +02:00
H. Árkosi Róbert
15ad6db1a7 Add LinkPlay models (#123056)
* Add some LinkPlay models

* Update utils.py

* Update utils.py

* Update utils.py

* Update homeassistant/components/linkplay/utils.py

* Update homeassistant/components/linkplay/utils.py

* Update utils.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-08-02 17:45:29 +02:00
Erik Montnemery
c1043ada22 Correct type annotation for EntityPlatform.async_register_entity_service (#123054)
Correct type annotation for EntityPlatform.async_register_entity_service

Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
2024-08-02 17:45:26 +02:00
Paulus Schoutsen
d141122008 Ollama implement CONTROL supported feature (#123049) 2024-08-02 17:45:23 +02:00
Paulus Schoutsen
abeba39842 OpenAI make supported features reflect the config entry options (#123047) 2024-08-02 17:45:19 +02:00
Matthias Alphart
bb597a908d Use freezer in KNX tests (#123044)
use freezer in tests
2024-08-02 17:45:16 +02:00
Matthias Alphart
dcae2f35ce Mitigate breaking change for KNX climate schema (#123043) 2024-08-02 17:45:12 +02:00
Matthias Alphart
b06a5af069 Address post-merge reviews for KNX integration (#123038) 2024-08-02 17:45:09 +02:00
J. Nick Koston
a624ada8d6 Fix doorbird models are missing the schedule API (#123033)
* Fix doorbird models are missing the schedule API

fixes #122997

* cover
2024-08-02 17:45:06 +02:00
David F. Mulcahey
d87366b1e7 Make ZHA load quirks earlier (#123027) 2024-08-02 17:45:03 +02:00
Michael Hansen
5ce8a2d974 Standardize assist pipelines on 10ms chunk size (#123024)
* Make chunk size always 10ms

* Fix voip
2024-08-02 17:44:59 +02:00
Robert Resch
a42615add0 Fix and improve tedee lock states (#123022)
Improve tedee lock states
2024-08-02 17:44:56 +02:00
Matrix
ecbff61332 Bump yolink api to 0.4.6 (#123012) 2024-08-02 17:44:53 +02:00
Paulus Schoutsen
e9bfe82582 Make the Android timer notification high priority (#123006) 2024-08-02 17:44:50 +02:00
Ivan Belokobylskiy
55abe68a5f Bump aioymaps to 1.2.5 (#123005)
Bump aiomaps, fix sessionId parsing
2024-08-02 17:44:46 +02:00
amccook
acf523b5fb Fix handling of directory type playlists in Plex (#122990)
Ignore type directory
2024-08-02 17:44:43 +02:00
Matrix
0216455137 Fix yolink protocol changed (#122989) 2024-08-02 17:44:40 +02:00
J. Nick Koston
cb37ae6608 Update doorbird error notification to be a repair flow (#122987) 2024-08-02 17:44:37 +02:00
karwosts
3b462906d9 Restrict nws.get_forecasts_extra selector to nws weather entities (#122986) 2024-08-02 17:44:34 +02:00
Matrix
dfb4e9c159 Yolink device model adaptation (#122824) 2024-08-02 17:44:31 +02:00
karwosts
6a6814af61 Use text/multiple selector for input_select.set_options (#122539) 2024-08-02 17:44:27 +02:00
Denis Shulyaka
1a7085b068 Add aliases to script llm tool description (#122380)
* Add aliases to script llm tool description

* Also add name
2024-08-02 17:44:24 +02:00
Christopher Fenner
804d7aa4c0 Fix translation key for power exchange sensor in ViCare (#122339) 2024-08-02 17:44:21 +02:00
DeerMaximum
1b1d86409c Velux use node id as fallback for unique id (#117508)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-08-02 17:44:18 +02:00
Ryan Mattson
2520fcd284 Lyric: Properly tie room accessories to the data coordinator (#115902)
* properly tie lyric accessories to the data coordinator so sensors recieve updates

* only check for accessories for LCC devices

* revert: meant to give it its own branch and PR
2024-08-02 17:44:13 +02:00
Franck Nijhof
18afe07c16 Bump version to 2024.8.0b0 2024-07-31 22:38:50 +02:00
Michael Hansen
6baee603a5 Bump pymicro-vad to 1.0.1 (#122973) 2024-07-31 22:10:50 +02:00
Simon
5fefa606b6 Add ElevenLabs text-to-speech integration (#115645)
* Add ElevenLabs text-to-speech integration

* Remove commented out code

* Use model_id instead of model_name for elevenlabs api

* Apply suggestions from code review

Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com>

* Use async client instead of sync

* Add ElevenLabs code owner

* Apply suggestions from code review

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

* Set entity title to voice

* Rename to elevenlabs

* Apply suggestions from code review

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

* Allow multiple voices and options flow

* Sort default voice at beginning

* Rework config flow to include default model and reloading on options flow

* Add error to strings

* Add ElevenLabsData and suggestions from code review

* Shorten options and config flow

* Fix comments

* Fix comments

* Add wip

* Fix

* Cleanup

* Bump elevenlabs version

* Add data description

* Fix

---------

Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Michael Hansen <mike@rhasspy.org>
Co-authored-by: Joostlek <joostlek@outlook.com>
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
2024-07-31 21:31:09 +02:00
Mr. Bubbles
7bc2381a45 Add Pinecil virtual integration supported by IronOS (#122803) 2024-07-31 21:24:15 +02:00
Jack Gaino
2910369647 Optionally return response data when calling services through the API (#115046)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2024-07-31 21:00:04 +02:00
alexfp14
17f34b452e Add HVAC mode support for AtlanticPassAPCHeatPumpMainComponent (heati… (#122175)
Co-authored-by: Mick Vleeshouwer <mick@imick.nl>
2024-07-31 20:52:32 +02:00
epenet
220f686078 Remove invalid type hint and assignment in number (#122906) 2024-07-31 20:51:24 +02:00
epenet
d878d744e7 Fix implicit-return in irish_rail_transport (#122916) 2024-07-31 20:50:31 +02:00
epenet
b31263b747 Fix implicit-return in itunes (#122917) 2024-07-31 20:50:11 +02:00
epenet
4a4209647e Fix implicit-return in humidifier (#122921) 2024-07-31 20:49:40 +02:00
epenet
9860109db9 Fix implicit-return in satel_integra (#122925) 2024-07-31 20:49:19 +02:00
epenet
b8ac86939b Fix implicit-return in smartthings (#122927) 2024-07-31 20:49:06 +02:00
epenet
9023d80d1b Fix implicit-return in twitter (#122931) 2024-07-31 20:48:51 +02:00
epenet
c702ffa7dd Fix implicit-return in uk_transport (#122932) 2024-07-31 20:48:30 +02:00
epenet
be8186126e Fix implicit-return in valve (#122933) 2024-07-31 20:47:48 +02:00
epenet
4fda025106 Fix implicit-return in wsdot (#122935) 2024-07-31 20:47:33 +02:00
J. Nick Koston
c7f863a141 Drop some unnecessary lambdas in powerwall (#122936) 2024-07-31 20:47:19 +02:00
epenet
4aacec2de7 Fix implicit-return in xiaomi (#122938) 2024-07-31 20:46:30 +02:00
epenet
a6aae4e857 Fix implicit-return in xiaomi_miio (#122939) 2024-07-31 20:45:48 +02:00
epenet
dde97a02f0 Fix implicit-return in xiaomi_aqara (#122940) 2024-07-31 20:45:29 +02:00
epenet
d393317eb2 Fix implicit-return in yamaha (#122942) 2024-07-31 20:45:10 +02:00
J. Nick Koston
2f3f124aa1 Drop unnecessary lambdas in the entity filter (#122941) 2024-07-31 20:44:47 +02:00
epenet
79a741486c Fix implicit-return in wyoming (#122946) 2024-07-31 20:42:57 +02:00
epenet
c0fe65fa60 Fix unnecessary-return-none in homematic (#122948) 2024-07-31 20:42:42 +02:00
J. Nick Koston
8de0e4ca7c Remove aiohappyeyeballs license exception (#122969) 2024-07-31 13:42:33 -05:00
epenet
7c179c33b5 Fix unnecessary-return-none in tradfri (#122950) 2024-07-31 20:42:19 +02:00
epenet
177690bcb3 Rename variable in sensor tests (#122954) 2024-07-31 20:42:05 +02:00
epenet
a23b3f84f0 Fix implicit-return in garadget (#122923) 2024-07-31 20:41:44 +02:00
Michael Hansen
d5388452d4 Use finished speaking detection in ESPHome/Wyoming (#122962) 2024-07-31 20:39:03 +02:00
Marcel van der Veldt
8a4206da99 Matter handle FeatureMap update (#122544) 2024-07-31 20:37:57 +02:00
Pete Sage
f1084a57df Fix Sonos media_player control may fail when grouping speakers (#121853) 2024-07-31 20:36:59 +02:00
Marcel van der Veldt
0189a05297 Extend Matter select entity (#122513) 2024-07-31 20:36:43 +02:00
Steven B.
7276b4b3ad Bump python-kasa to 0.7.1 (#122967) 2024-07-31 13:31:53 -05:00
epenet
93bcd413a7 Fix unnecessary-return-none in iotty (#122947) 2024-07-31 20:21:26 +02:00
epenet
bc25657f0a Fix unnecessary-return-none in telnet (#122949) 2024-07-31 20:21:04 +02:00
Steven B.
9db42beade Fix handling of tplink light effects for scenes (#122965)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-07-31 12:57:12 -05:00
Thomas55555
cc1a6d60c0 Add override for work areas in Husqvarna Automower (#120427)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-07-31 19:28:46 +02:00
G Johansson
a913587eb6 Climate validate temperature(s) out of range (#118649)
* Climate temperature out of range

* Fix test sensibo

* use temp converting for min/max

* Fix

* Fix mqtt tests

* Fix honeywell tests

* Fix Balboa tests

* Fix whirlpool test

* Fix teslemetry test

* Fix plugwise test

* Fix tplink test

* Fix generic thermostat test

* Fix modbus test

* Fix fritzbox tests

* Honewell
2024-07-31 19:17:53 +02:00
Louis Christ
ae9e8ca419 Simplify async_setup_entry in bluesound integration (#122874)
* Use async_added_to_hass and async_will_remove_from_hass

* Remove self._hass
2024-07-31 19:04:17 +02:00
epenet
69a8c5dc9f Fix implicit-return in hddtemp (#122919) 2024-07-31 18:44:36 +02:00
epenet
3f091470fd Use pytest.mark.usefixtures in risco tests (#122955) 2024-07-31 18:28:35 +02:00
Marcel van der Veldt
c888908cc8 Add default warning for installing matter device updates (#122597) 2024-07-31 18:23:40 +02:00
J. Nick Koston
172e2125f6 Switch to using update for headers middleware (#122952) 2024-07-31 18:17:45 +02:00
Michael
69f54656c4 Fix cleanup of orphan device entries in AVM Fritz!Box Tools (#122937)
* fix cleanup of orphan device entries

* add test for cleanup button
2024-07-31 17:58:11 +02:00
Bram Kragten
c359d4a419 Update frontend to 20240731.0 (#122956) 2024-07-31 17:53:52 +02:00
epenet
a1b8545568 Fix unnecessary-return-none in nest (#122951) 2024-07-31 17:13:53 +02:00
starkillerOG
8c0d9a1320 Add Reolink chime support (#122752) 2024-07-31 17:04:09 +02:00
Christopher Fenner
f764705629 Add support for ventilation device to ViCare (#114175)
* add ventilation program & mode

* add ventilation device

* Update climate.py

* Update climate.py

* Update climate.py

* Update climate.py

* Update climate.py

* Update const.py

* Create fan.py

* Update fan.py

* Update types.py

* add test case

* add translation key

* use translation key

* update snapshot

* fix ruff findings

* fix ruff findings

* add log messages to setter

* adjust test case

* reset climate entity

* do not display speed if not in permanent mode

* update snapshot

* update test cases

* add comment

* mark fan as always on

* prevent turning off device

* allow to set permanent mode

* make speed_count static

* add debug outputs

* add preset state translations

* allow permanent mode

* update snapshot

* add test case

* load programs only on init

* comment on ventilation modes

* adjust test cases

* add exception message

* ignore test coverage on fan.py

* Update test_fan.py

* simplify

* Apply suggestions from code review

* remove tests

* remove extra state attributes

* fix leftover

* add missing labels

* adjust label

* change state keys

* use _attr_preset_modes

* fix ruff findings

* fix attribute access

* fix from_ha_mode

* fix ruff findings

* fix mypy findings

* simplify

* format

* fix typo

* fix ruff finding

* Apply suggestions from code review

* change fan mode handling

* add test cases

* remove turn_off

* Apply suggestions from code review

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Apply suggestions from code review

* Update fan.py

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2024-07-31 16:23:27 +02:00
karwosts
3df78043c0 Add enable_millisecond to duration selector (#122821)
* Add enable_milliseconds to duration selector.

* One more test
2024-07-31 16:13:05 +02:00
epenet
97de1c2b66 Fix implicit-return in recorder (#122924) 2024-07-31 15:46:13 +02:00
Michael
f7f0f49015 Move lifespan attributes into own sensors for legacy Ecovacs bots (#122740)
* move available property to base entity class

* add lifespan sensors

* apply suggestion, simplify the method

* don't touch internals in tests

* apply suggestion

* apply suggestions
2024-07-31 15:36:57 +02:00
J. Nick Koston
a35fa0e95a Warn that the minimum SQLite version will change to 3.40.1 as of 2025.2 (#104298)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-07-31 08:13:04 -05:00
Kevin Stillhammer
cddb3bb668 Add reconfigure step for here_travel_time (#114667)
* Add reconfigure step for here_travel_time

* Add comments, reuse step_user, TYPE_CHECKING, remove defaults
2024-07-31 15:08:25 +02:00
epenet
e64e3c2778 Fix implicit-return in time_date (#122929) 2024-07-31 15:00:53 +02:00
epenet
5e1cca1c58 Fix implicit-return in shelly (#122926) 2024-07-31 14:54:52 +02:00
epenet
2f181cbe41 Fix implicit-return in vera (#122934) 2024-07-31 14:53:05 +02:00
epenet
e706ff0564 Fix implicit-return in transport_nsw (#122930) 2024-07-31 14:44:14 +02:00
Allen Porter
8d0e998e54 Improve conversation agent tracing to help with eval and data collection (#122542) 2024-07-31 14:38:44 +02:00
Allen Porter
4f5eab4646 Improve quality of ollama tool calling by repairing arguments (#122749)
* Improve quality of ollama function calling by repairing function call arguments

* Fix formatting of the tests

* Run ruff format on ollama conversation

* Add test for non-string arguments
2024-07-31 14:37:39 +02:00
Franck Nijhof
8b96c7873f Rename 'service' to 'action' in automations and scripts (#122845) 2024-07-31 14:36:53 +02:00
Allen Porter
f14471112d Improve LLM tool quality by more clearly specifying device_class slots (#122723)
* Limit intent / llm API device_class slots to only necessary services and limited set of values

* Fix ruff errors

* Run ruff format

* Fix typing and improve output schema

* Fix schema and improve flattening

* Revert conftest

* Revert recorder

* Fix ruff format errors

* Update using latest version of voluptuous
2024-07-31 14:36:02 +02:00
epenet
7c7b408df1 Fix implicit-return in homekit_controller (#122920) 2024-07-31 14:21:58 +02:00
epenet
3bf00822b0 Fix implicit-return in kodi (#122914) 2024-07-31 13:42:07 +02:00
Yuxin Wang
bf3a2cf393 Add graceful handling for LASTSTEST sensor in APCUPSD (#113125)
* Add handling for LASTSTEST sensor

* Set the state to unknown instead of unavailable

* Use LASTSTEST constant and revise the logic to add it to the entity list

* Use LASTSTEST constant
2024-07-31 13:01:48 +02:00
Maikel Punie
c8dccec956 Bump velbusaio to 2024.07.06 (#122905)
bumpo velbusaio to 2024.07.06
2024-07-31 12:48:08 +02:00
epenet
ed9c4e0c0d Fix implicit-return in landisgyr_heat_meter (#122912) 2024-07-31 12:41:10 +02:00
epenet
cd552ceb2b Fix implicit-return in mystrom (#122911) 2024-07-31 12:40:48 +02:00
epenet
c4398efbbb Fix implicit-return in meteo_france (#122910) 2024-07-31 12:40:30 +02:00
epenet
8b1a527602 Fix implicit-return in meraki (#122909) 2024-07-31 12:39:59 +02:00
epenet
01f41a597e Fix implicit-return in melissa (#122908) 2024-07-31 12:39:39 +02:00
epenet
c32f1efad0 Fix implicit-return in maxcube (#122907) 2024-07-31 12:39:21 +02:00
epenet
47c96c52b1 Fix implicit-return in niko_home_control (#122904) 2024-07-31 12:39:01 +02:00
epenet
dbdb148e12 Fix implicit-return in plaato (#122902) 2024-07-31 12:38:36 +02:00
epenet
6a45124878 Fix implicit-return in qnap (#122901) 2024-07-31 12:38:15 +02:00
Diogo Gomes
02d4d1a75b Adds new sensors and configuration entities to V2C Trydan (#122883)
* Adds new controls and sensors

* update snapshot

* Update homeassistant/components/v2c/strings.json

Co-authored-by: Charles Garwood <cgarwood@newdealmultimedia.com>

* Add unit

* move icons to icons.json

* update snapshot

* missing translation fix

---------

Co-authored-by: Charles Garwood <cgarwood@newdealmultimedia.com>
2024-07-31 12:31:35 +02:00
Alex MF
233c04a469 Add number entity for Ecovacs mower cut direction (#122598) 2024-07-31 12:22:07 +02:00
epenet
8b4f607806 Fix implicit-return in plant (#122903) 2024-07-31 11:39:51 +02:00
Diogo Gomes
68f06e63e2 Bump pytrydan to 0.8.0 (#122898)
bump pytrydan to 0.8.0
2024-07-31 10:32:13 +01:00
Matthias Alphart
67ed8b207a KNX: use xknx 3.0.0 eager telegram decoding (#122896)
* Use KNX xknx 3.0.0 eager telegram decoding

* review suggestion
2024-07-31 11:08:05 +02:00
Erik Montnemery
222011fc5c Log tests in test group (#122892)
* Log tests in test group

* Simplify print
2024-07-31 10:36:46 +02:00
Erik Montnemery
f6f7459c36 Add support for login credentials to homeworks (#122877)
* Add support for login credentials to homeworks

* Store credentials in config entry data
2024-07-31 10:35:05 +02:00
Franck Nijhof
718bc61c88 Merge branch 'master' into dev 2024-07-31 10:31:28 +02:00
Paarth Shah
015a1a6ebc Fix blocking event loop call in matrix (#122730)
Wrap load_json_object in async_add_executor_job
2024-07-31 09:45:30 +02:00
Paarth Shah
e0a1aaa1b9 Fix matrix blocking call by running sync_forever in background_task (#122800)
Fix blocking call by running sync_forever in background_task
2024-07-31 09:44:59 +02:00
Michael Hansen
7f4dabf546 Switch from WebRTC to microVAD (#122861)
* Switch WebRTC to microVAD

* Remove webrtc-noise-gain from licenses
2024-07-31 09:42:45 +02:00
Denis Shulyaka
beb2ef121e Update todo intent slot schema (#122335)
* Update todo intent slot schema

* Update intent.py

* ruff
2024-07-31 09:37:55 +02:00
Denis Shulyaka
5a04d982d9 Bump ollama to 0.3.1 (#122866) 2024-07-31 09:35:45 +02:00
David Bonnes
35bfd0b88f Evohome drops use of async_call_later to avoid lingering task (#122879)
initial commit
2024-07-31 09:35:21 +02:00
Matthias Alphart
9351f300b0 Update xknx to 3.0.0 - more DPT definitions (#122891)
* Support DPTComplex objects and validate sensor types

* Gracefully start and stop xknx device objects

* Use non-awaitable XknxDevice callbacks

* Use non-awaitable xknx.TelegramQueue callbacks

* Use non-awaitable xknx.ConnectionManager callbacks

* Remove unnecessary `hass.async_block_till_done()` calls

* Wait for StateUpdater logic to proceed when receiving responses

* Update import module paths for specific DPTs

* Support Enum data types

* New HVAC mode names

* HVAC Enums instead of Enum member value strings

* New date and time devices

* Update xknx to 3.0.0

* Fix expose tests and DPTEnumData check

* ruff and mypy fixes
2024-07-31 09:10:36 +02:00
Thomas55555
0d678120e4 Bump aioautomower to 2024.7.3 (#121983)
* Bump aioautomower to 2024.7.0

* tests

* Bump to 2024.7.1

* bump to 2024.7.2

* use timezone Europe/Berlin

* bump to 2024.7.3

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2024-07-31 08:28:39 +02:00
Lukas Kolletzki
5766ea9541 Add generic URL handler to blueprint importer (#110576)
* Add generic url handler to blueprint importer

* Update tests/components/blueprint/test_importer.py

* Update tests/components/blueprint/test_importer.py

* Update test_importer.py

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2024-07-31 08:26:57 +02:00
Erik Montnemery
6d8bc84db3 Allow [##:##:##:##:##] type device address in homeworks (#122872)
* Allow [##:##:##:##:##] type device address in homeworks

* Simplify regex
2024-07-31 08:02:15 +02:00
J. Nick Koston
823910b69e Bump ulid-transform to 0.13.1 (#122884)
* Bump ulid-transform to 0.13.0

changelog: https://github.com/bdraco/ulid-transform/compare/v0.10.1...v0.13.0

* Bump ulid-transform to 0.13.1
2024-07-31 07:20:09 +02:00
J. Nick Koston
aa801d9cc6 Bump bluetooth-data-tools to 1.19.4 (#122886) 2024-07-30 18:20:55 -05:00
Jeef
067acce4de Add SimpleFin sensor to show age of data (#122550) 2024-07-30 23:42:10 +02:00
J. Nick Koston
6999c6b0cf Bump aiohttp to 3.10.0 (#122880) 2024-07-30 16:40:38 -05:00
Michael Hansen
da18aae2d8 Bump intents to 2024.7.29 (#122811) 2024-07-30 15:27:16 -05:00
Erik Montnemery
6362ca1052 Bump pyhomeworks to 1.1.0 (#122870) 2024-07-30 21:52:25 +02:00
Bill Flood
022e1b0c02 Add other medium types to Mopeka sensor (#122705)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-07-30 14:07:12 -05:00
Erik Montnemery
94c0b9fc06 Bump pyhomeworks to 1.0.0 (#122867) 2024-07-30 19:39:53 +02:00
Robert Svensson
5eff4f9816 Unifi improve fixture typing (#122864)
* Improve typing of UniFi fixtures

* Improve fixture typing, excluding image, sensor, switch

* Improve fixture typing in image tests

* Improve fixtures typing in sensor tests

* Improve fixture typing in switch tests

* Fix review comment
2024-07-30 19:33:25 +02:00
Erik Montnemery
fb229fcae8 Improve test coverage of the homeworks integration (#122865)
* Improve test coverage of the homeworks integration

* Revert changes from the future

* Revert changes from the future
2024-07-30 18:40:36 +02:00
Mr. Bubbles
50b35ac4bc Add number platform to IronOS integration (#122801)
* Add setpoint temperature number entity to IronOS integration

* Add tests for number platform

* Initialize settings in coordinator

* Remove unused code
2024-07-30 18:14:01 +02:00
Simon Hörrle
ea727546d6 Add apsystems power switch (#122447)
* bring back power switch

* fix pylint issues

* add SWITCH to platform list

* improve run_on and turn_off functions

* ruff formatting

* replace _state with _attr_is_on

* Update homeassistant/components/apsystems/switch.py

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

* remove unused dependencies

* Update homeassistant/components/apsystems/switch.py

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

* use async functions from api

* convert Api IntEnum Status Information to bool

* add translation key

* implement async_update again

* replace finally with else

* better handling of bool value

* Update homeassistant/components/apsystems/switch.py

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

* Update homeassistant/components/apsystems/switch.py

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

* rename power switch to inverter switch

* add test_number and test_switch module

* remove test_number

* Add mock entry for get_device_power_status

* Add mock entry for get_device_power_status

* Update test snapshots

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-07-30 18:11:08 +02:00
bdowden
18a7d15d14 Add Traffic Rule switches to UniFi Network (#118821)
* Add Traffic Rule switches to UniFi Network

* Retrieve Fix unifi traffic rule switches

Poll for traffic rule updates; have immediate feedback in the UI for modifying traffic rules

* Remove default values for unifi entity; Remove unnecessary code

* Begin updating traffic rule unit tests

* For the mock get request, allow for meta and data properties to not be appended to support v2 api requests

Fix traffic rule unit tests;

* inspect path to determine json response instead of passing an argument

* Remove entity id parameter from tests; remove unused code; rename traffic rule unique ID prefix

* Remove parameter with default.

* More code removal;

* Rename copy/paste variable; remove commented code; remove duplicate default code

---------

Co-authored-by: ViViDboarder <ViViDboarder@gmail.com>
2024-07-30 17:26:08 +02:00
Denis Shulyaka
be24475cee Update selector converters for llm script tools (#122830) 2024-07-30 08:24:03 -07:00
epenet
8066c7dec6 Fix implicit-return in deconz (#122836) 2024-07-30 17:21:45 +02:00
Kim de Vos
896cd27bea Add sensors for Unifi latency (#116737)
* Add sensors for Unifi latency

* Add needed guard and casting

* Use new types

* Add WAN2 support

* Add literals

* Make ids for WAN and WAN2 unique

* Make methods general

* Update sensor.py

* add more typing

* Simplify usage make_wan_latency_sensors

* Simplify further

* Move latency entity creation to method

* Make method internal

* simplify tests

* Apply feedback

* Reduce boiler copied code and add support function

* Add external method for share logic between

* Remove none

* Add more tests

* Apply feedback and change code to improve code coverage
2024-07-30 17:20:56 +02:00
David Bonnes
1ffde403f0 Ensure evohome leaves no lingering timers (#122860) 2024-07-30 17:18:33 +02:00
Guido Schmitz
b69b927795 Set parallel updates in devolo_home_network (#122847) 2024-07-30 17:17:20 +02:00
Erik Montnemery
6840f27bc6 Verify respx mock routes are cleaned up when tests finish (#122852) 2024-07-30 17:12:58 +02:00
epenet
4a34855a92 Fix implicit-return in scripts (#122831) 2024-07-30 16:57:42 +02:00
Joakim Plate
b3f7f379df Upgrade dsmr-parser to 1.4.2 (#121929) 2024-07-30 16:51:02 +02:00
Marius
4994e46ad0 Add mdi:alert-circle-outline to degrade status (#122859) 2024-07-30 16:44:04 +02:00
Erik Montnemery
1382f7a3dc Fix generic IP camera tests affecting other tests (#122858) 2024-07-30 16:29:59 +02:00
Erik Montnemery
b973455037 Fix template image test affecting other tests (#122849) 2024-07-30 16:28:55 +02:00
Thomas55555
a5136a1021 Speed up slow tests in Husqvarna Automower (#122854) 2024-07-30 16:27:58 +02:00
Erik Montnemery
d9e996def5 Fix template binary sensor test (#122855) 2024-07-30 16:18:47 +02:00
Erik Montnemery
224228e448 Fix Axis tests affecting other tests (#122857) 2024-07-30 16:16:33 +02:00
epenet
c8372a3aa5 Fix implicit-return in ecobee (#122832) 2024-07-30 15:33:57 +02:00
epenet
2135691b90 Fix implicit-return in dublin bus transport (#122833) 2024-07-30 15:33:05 +02:00
epenet
ea508b2629 Fix implicit-return in dialogflow (#122834) 2024-07-30 15:32:29 +02:00
epenet
09cd79772f Fix implicit-return in airtouch4 (#122839) 2024-07-30 15:29:53 +02:00
epenet
7b5db6521c Fix implicit-return in advantage_air (#122840) 2024-07-30 15:29:23 +02:00
epenet
27eba3cd46 Fix implicit-return in fixer (#122841) 2024-07-30 15:24:35 +02:00
epenet
41c7414d97 Fix implicit-return in forked_daapd (#122842) 2024-07-30 15:23:53 +02:00
epenet
fd7c92879c Fix implicit-return in foursquare (#122843) 2024-07-30 15:23:04 +02:00
Sébastien Clément
e7971f5a67 Fix qbittorent current_status key in strings.json (#122848) 2024-07-30 15:03:36 +02:00
epenet
72f9d85bbe Fix implicit-return in whirlpool tests (#122775) 2024-07-30 14:57:43 +02:00
Matthias Alphart
956cc6a85c Add UI to create KNX switch and light entities (#122630)
Update KNX frontend to 2024.7.25.204106
2024-07-30 13:54:44 +02:00
epenet
015c50bbdb Fix implicit-return in ddwrt (#122837) 2024-07-30 14:44:11 +03:00
epenet
b6f0893c33 Fix implicit-return in denon (#122835) 2024-07-30 13:05:38 +02:00
Luke Wale
7c92287f97 Add Airtouch5 cover tests (#122769)
add airtouch5 cover tests
2024-07-30 12:34:49 +02:00
Kristof Mariën
53a59412bb Add Foscam sleep switch (#109491)
* Add sleep switch

* Replace awake with sleep switch
2024-07-30 11:34:30 +02:00
Sébastien Clément
d78acd480a Add QBittorent switch to control alternative speed (#107637)
* Fix key in strings.json for current_status in QBittorrent

* Add switch on QBittorent to control alternative speed

* Add switch file to .coveragerc

* Fix some typo

* Use coordinator for switch

* Update to mach new lib

* Import annotation

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Remove quoted coordinator

* Revert "Fix key in strings.json for current_status in QBittorrent"

This reverts commit 962fd0474f.

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2024-07-30 11:23:55 +02:00
Franck Nijhof
17930a6d66 2024.7.4 (#122770) 2024-07-30 11:12:45 +02:00
Bruno Pantaleão Gonçalves
d825ac346e Add 'use_custom_colors' to iOS Action configuration (#122767) 2024-07-30 11:00:08 +02:00
Denis Shulyaka
fa53055485 Bump voluptuous-openapi (#122828) 2024-07-30 10:57:56 +02:00
epenet
b4cba01870 Fix implicit-return in command_line (#122838) 2024-07-30 10:17:01 +02:00
Robert Svensson
70e368a57e Use snapshot in Axis switch tests (#122680) 2024-07-30 07:14:56 +02:00
David F. Mulcahey
004eccec89 Fix supported_features for ZHA fans (#122813) 2024-07-30 02:10:02 +02:00
David F. Mulcahey
36c01042c1 Enhance ZHA device removal (#122815) 2024-07-30 02:08:21 +02:00
David F. Mulcahey
bd3f0da385 Bump ZHA lib to 0.0.24 and universal-silabs-flasher to 0.0.22 (#122812)
* Bump ZHA lib to 0.0.24

* update for node state change for coordinator data

* bump flasher
2024-07-29 18:16:54 -04:00
Louis Christ
9450744b3b Add device _info to bluesound integration (#122795)
* Add device_info

* Use _attr_unique_id instead of custom methode

* Use different DeviceInfo if port is not DEFAULT_PORT

* Remove name method; Add has_entity_name=True

* Remove self._name completely

* move _attr_has_entity_name and _attr_name out of __init__

* log error if status update fails

* use error for remaining info logs
2024-07-29 23:11:51 +02:00
J. Nick Koston
1c03c83c0a Fix blocking stat() via is_file in image_upload (#122808) 2024-07-29 22:38:58 +02:00
epenet
fdab23c3f9 Fix implicit-return in test schema extractions (#122787) 2024-07-29 22:16:00 +02:00
epenet
7b08e625b4 Fix implicit-return in websocket_api tests (#122779) 2024-07-29 22:14:41 +02:00
epenet
2102a104d2 Adjust DOMAIN imports in homeassistant integration (#122774) 2024-07-29 22:14:05 +02:00
J. Nick Koston
3e1aee4cbc Remove unused constant in august (#122804) 2024-07-29 15:13:39 -05:00
Milan Meulemans
b5b01d97f1 Add support for ASIN Pool devices to ASEKO (#122773) 2024-07-29 22:12:34 +02:00
epenet
02581bbf02 Enforce HOMEASSISTANT_DOMAIN alias for core DOMAIN (#122763) 2024-07-29 22:10:44 +02:00
epenet
4ac85829c8 Fix implicit-return in season tests (#122784) 2024-07-29 22:09:40 +02:00
Robert Resch
ad50136dbd Add created_at/modified_at to config entries (#122456) 2024-07-29 22:08:46 +02:00
Markus Jacobsen
20c4f84a4e Fix incorrect Bang & Olufsen MDNS announcements (#122782) 2024-07-29 22:04:54 +02:00
epenet
bf38db0035 Fix implicit-return in surepetcare tests (#122785) 2024-07-29 22:03:44 +02:00
epenet
b8c363a82c Fix implicit-return in tplink_omada tests (#122776) 2024-07-29 22:03:14 +02:00
epenet
9393dcddb7 Fix implicit-return in nx584 tests (#122788) 2024-07-29 21:59:59 +02:00
epenet
5b434ee336 Fix implicit-return in xiaomi tests (#122778) 2024-07-29 21:58:03 +02:00
epenet
6ba6334512 Fix implicit-return in enigma2 tests (#122790) 2024-07-29 21:57:35 +02:00
J. Nick Koston
8de7a2e3c7 Bump aiohttp to 3.10.0rc0 (#122793) 2024-07-29 21:55:22 +02:00
epenet
197ac8b950 Fix implicit-return in netatmo tests (#122789) 2024-07-29 21:53:22 +02:00
epenet
1958a149c3 Fix implicit-return in ipma tests (#122791) 2024-07-29 21:52:47 +02:00
epenet
7bbbda8d2b Fix implicit-return in sonos tests (#122780) 2024-07-29 21:52:15 +02:00
epenet
5d87a74c3c Fix implicit-return in unifiprotect tests (#122781) 2024-07-29 21:50:45 +02:00
puddly
1f488b00f8 Abstract SkyConnect firmware config flow to the hardware platform (#122140)
* Move the SkyConnect config flow to hardware;

* Clean up

* Get SkyConnect unit tests passing

* Split apart `test_util.py`

* Migrate `test_config_flow`

* Remove unnecessary constants

* Re-apply `contextmanager` typing from #122250

* Move the SkyConnect translation strings into hardware
2024-07-29 12:39:25 -04:00
epenet
570725293c Fix implicit-return in arcam_fmj tests (#122792) 2024-07-29 17:13:31 +02:00
Christian Neumeier
732b9e47c8 Add missing variable 'energy_today' to Zeversolar diagnostics. (#122786)
added var 'energy_today' to zeversolar diagnostics.
2024-07-29 16:48:58 +02:00
Erik Montnemery
ea75c8864f Remove support for live schema migration of old recorder databases (#122399)
* Remove support for live schema migration of old recorder databases

* Update test
2024-07-29 15:52:18 +02:00
epenet
9514a38320 Fix implicit-return rule in zha tests (#122772) 2024-07-29 15:22:08 +02:00
Bram Kragten
d94e79d57a Add Macedonian language (#122768) 2024-07-29 14:49:34 +02:00
Franck Nijhof
02c592d6af Bump version to 2024.7.4 2024-07-29 14:40:02 +02:00
J. Nick Koston
d51d584aed Retry later on OSError during apple_tv entry setup (#122747) 2024-07-29 14:39:27 +02:00
G Johansson
e5fd9819da Return unknown when data is missing in Trafikverket Weather (#122652)
Return unknown when data is missing
2024-07-29 14:23:09 +02:00
Marcel van der Veldt
00c3b0d888 Bump aiohue to version 4.7.2 (#122651) 2024-07-29 14:23:06 +02:00
Robert Resch
aa44c54a19 Bump deebot-client to 8.2.0 (#122612) 2024-07-29 14:23:02 +02:00
Avi Miller
9940d0281b Bump aiolifx to 1.0.6 (#122569) 2024-07-29 13:48:19 +02:00
Andrew Jackson
586a0b12ab Fix target service attribute on Mastodon integration (#122546)
* Fix target

* Fix
2024-07-29 13:48:16 +02:00
Marcel van der Veldt
75f0384a15 Fix typo in Matter lock platform (#122536) 2024-07-29 13:48:13 +02:00
Denis Shulyaka
56f51d3e35 Fix gemini api format conversion (#122403)
* Fix gemini api format conversion

* add tests

* fix tests

* fix tests

* fix coverage
2024-07-29 13:48:10 +02:00
starkillerOG
7135a919e3 Bump reolink-aio to 0.9.5 (#122366) 2024-07-29 13:48:06 +02:00
Denis Shulyaka
9d6bd359c4 Ensure script llm tool name does not start with a digit (#122349)
* Ensure script tool name does not start with a digit

* Fix test name
2024-07-29 13:48:03 +02:00
Denis Shulyaka
f739644735 Goofle Generative AI: Fix string format (#122348)
* Ignore format for string tool args

* Add tests
2024-07-29 13:48:00 +02:00
Christopher Fenner
b63bc72450 Fix device class on sensor in ViCare (#122334)
update device class on init
2024-07-29 13:47:57 +02:00
Alexander Schneider
683069cb98 Add Z-Wave discovery schema for ZVIDAR roller shades (#122332)
Add discovery schema for ZVIDAR roller shades
2024-07-29 13:47:53 +02:00
Jan Bouwhuis
cf20e67f1f Ensure mqtt subscriptions are in a set (#122201) 2024-07-29 13:47:50 +02:00
Maciej Bieniek
74d10b9824 Bump aiotractive to 0.6.0 (#121155)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-07-29 13:47:46 +02:00
Mick Vleeshouwer
61d4bc1430 Fix device class of water consumption sensor in Overkiz (#122766)
Fixes #118959
2024-07-29 13:38:58 +02:00
epenet
07c7bb8b2a Use HOMEASSISTANT_DOMAIN alias for core DOMAIN (#122760) 2024-07-29 13:35:36 +02:00
epenet
9ce7779bde Use correct constant in rest tests (#122765) 2024-07-29 12:59:57 +02:00
epenet
9e10126505 Revert "Small refactor to cleanup unnecessary returns (#121653)" (#122756) 2024-07-29 12:59:25 +02:00
Pete Sage
cfef72ae57 Add Sonos tests for media_player volume (#122283) 2024-07-29 12:56:26 +02:00
Andrew Jackson
86bfc7ada8 Remove UE Smart Radio integration (#122578) 2024-07-29 12:52:37 +02:00
Rami Mosleh
0de75aeee1 Wait for initial scan to finish before setting up platforms (#122360) 2024-07-29 12:52:01 +02:00
epenet
075550b7ba Use HOMEASSISTANT_DOMAIN alias for core DOMAIN in tests (#122762) 2024-07-29 12:51:12 +02:00
Allen Porter
e5bb1b2cc6 Update LLM prompt to improve quality for local LLMs (#122746) 2024-07-29 12:04:23 +02:00
Marcel van der Veldt
85aca4f095 Fix default turn_on without explicit preset or percentage in Matter Fan platform (#122591) 2024-07-29 12:03:40 +02:00
Richard Kroegel
745eea9a29 Bump bimmer_connected to 0.16.1 (#122699)
Co-authored-by: Richard <rikroe@users.noreply.github.com>
2024-07-29 12:02:47 +02:00
J. Nick Koston
d586e7df33 Retry later on OSError during apple_tv entry setup (#122747) 2024-07-29 11:59:31 +02:00
Marcel van der Veldt
6d4711ce43 Bump aiohue to version 4.7.2 (#122651) 2024-07-29 11:59:13 +02:00
Erik Montnemery
8a84addc54 Add test of recorder platform with statistics support (#122754)
* Add test of recorder platform with statistics support

* Remove excessive line breaks
2024-07-29 11:57:53 +02:00
J. Nick Koston
1879db9f8f Revert to using call_soon for event triggers and state changed event trackers (#122735) 2024-07-29 11:45:39 +02:00
Alexey ALERT Rubashёff
869ec3f670 Bump pyOverkiz to 1.13.14 (#122691) 2024-07-29 11:44:28 +02:00
Mr. Bubbles
70df4ca461 Integration for IronOS (Pinecil V2) soldering irons (#120802)
* Add Pinecil integration

* Refactor with new library

* Add tests for config flow, remove unused code

* requested changes

* update requirements

* Move some sensor values to diagnostics, add tests for sensors

* User service uuid in discovery

* fix manufacturer name

* Bump pynecil to version 0.2.0

* Rename integration to IronOS

* Recreate snapshot

* Update strings

* type checking

* Update snapshot

* Add async_setup to coordinator

* Show device id with serial number

* Added missing boost to operation mode states

* remove super call

* Refactor

* tests
2024-07-29 11:44:01 +02:00
Robert Svensson
06ee8fdd47 Do not use get_hub in deCONZ tests (#122706) 2024-07-29 11:43:04 +02:00
Erik Montnemery
5467685bd8 Adjust warning message when recorder is doing offline migration (#122509)
* Adjust warning message when recorder is doing offline migration

* Address review comments
2024-07-29 11:38:36 +02:00
Erik Montnemery
efaf75f2e6 Rename recorder INTEGRATION_PLATFORMS_RUN_IN_RECORDER_THREAD (#122758) 2024-07-29 11:38:21 +02:00
J. Nick Koston
ca430f0e7b Add coverage for fixing missing params in the doorbird schedule (#122745) 2024-07-29 11:36:44 +02:00
danielsmyers
fa61ad072d Add Bryant Evolution Integration (#119788)
* Add an integration for Bryant Evolution HVAC systems.

* Update newly created tests so that they pass.

* Improve compliance with home assistant guidelines.

* Added tests

* remove xxx

* Minor test cleanups

* Add a test for reading HVAC actions.

* Update homeassistant/components/bryant_evolution/__init__.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/bryant_evolution/climate.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/bryant_evolution/climate.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/bryant_evolution/climate.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/bryant_evolution/climate.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/bryant_evolution/climate.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Update homeassistant/components/bryant_evolution/config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Address reviewer comments.

* Address additional reviewer comments.

* Use translation for exception error messages.

* Simplify config flow.

* Continue addressing comments

* Use mocking rather than DI to provide a for-test client in tests.

* Fix a failure in test_config_flow.py

* Track host->filename in strings.json.

* Use config entry ID for climate entity unique id

* Guard against fan mode returning None in async_update.

* Move unavailable-client check from climate.py to init.py.

* Improve test coverage

* Bump evolutionhttp version

* Address comments

* update comment

* only have one _can_reach_device fn

* Auto-detect which systems and zones are attached.

* Add support for reconfiguration

* Fix a few review comments

* Introduce multiple devices

* Track evolutionhttp library change that returns additional per-zone information during enumeration

* Move construction of devices to init

* Avoid triplicate writing

* rework tests to use mocks

* Correct attribute name to unbreak test

* Pull magic tuple of system-zone into a constant

* Address some test comments

* Create test_init.py

* simplify test_reconfigure

* Replace disable_auto_entity_update with mocks.

* Update tests/components/bryant_evolution/test_climate.py

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

* Update tests/components/bryant_evolution/test_climate.py

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

* Update tests/components/bryant_evolution/test_config_flow.py

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

* Update homeassistant/components/bryant_evolution/config_flow.py

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

* Update tests/components/bryant_evolution/test_config_flow.py

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

* Update tests/components/bryant_evolution/test_config_flow.py

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

* fix test errors

* do not access runtime_data in tests

* use snapshot_platform and type fixtures

---------

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-07-29 11:25:04 +02:00
Michael
686598b6b3 Don't block HA startup while set up legacy Ecovacs bot (#122732)
wait for connection in background
2024-07-29 11:24:14 +02:00
Erik Montnemery
5f5dcec0b9 Revert unneeded type annotation in the api integration (#122757) 2024-07-29 10:57:49 +02:00
Erik Montnemery
2a5cb8da32 Fix copy-paste errors in alarm_control_panel tests (#122755) 2024-07-29 10:57:34 +02:00
Erik Montnemery
9b497aebb4 Fix bug in timeout util related to multiple global freezes (#122466) 2024-07-29 10:12:18 +02:00
dependabot[bot]
5f08883227 Bump github/codeql-action from 3.25.14 to 3.25.15 (#122753)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-29 09:58:15 +02:00
Denis Shulyaka
4b2073ca59 Add LLM tools support for Ollama (#120454)
* Add LLM tools support for Ollama

* fix tests

* coverage

* Separate call for tool parameters

* Fix example

* hint on parameters schema if LLM forgot to request it

* Switch to native tool call functionality

* Fix tests

* Fix tools list

* update strings and default model

* Ignore mypy error until fixed upstream

* Ignore mypy error until fixed upstream

* Add missing prompt part

* Update default model
2024-07-28 18:19:53 -07:00
Louis Christ
f98487ef18 Add config_flow to bluesound integration (#115207)
* Add config flow to bluesound

* update init

* abort flow if connection is not possible

* add to codeowners

* update unique id

* add async_unload_entry

* add import flow

* add device_info

* add zeroconf

* fix errors

* formatting

* use bluos specific zeroconf service type

* implement requested changes

* implement requested changes

* fix test; add more tests

* use AsyncMock assert functions

* fix potential naming collision

* move setup_services back to media_player.py

* implement requested changes

* add port to zeroconf flow

* Fix comments

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2024-07-28 20:48:20 +02:00
starkillerOG
dff964582b Bump reolink-aio to 0.9.6 (#122738) 2024-07-28 19:47:37 +02:00
Arie Catsman
e5c36c8d56 Refactor asserts in enphase_envoy test_sensor (#122726)
refactor asserts in enphase_envoy test_sensor
2024-07-28 16:36:36 +02:00
Michael
d765b92cca Unsubscribe event listeners on remove of Ecovacs legacy bot entities (#122731)
* unsubscribe on entity remove, create base EcovacsLegacyEntity

* fix name and model in device info

* apply suggestion

* add manufacturer to device info

* fix device info
2024-07-28 15:01:34 +02:00
J. Nick Koston
ba266ab13c Add coverage for calling doorbird webhook with the wrong token (#122700) 2024-07-28 07:11:56 -05:00
Bill Flood
d4aa981fd7 Bump mopeka-iot-ble to version 0.8.0 (#122717) 2024-07-28 07:07:00 -05:00
dependabot[bot]
ac0d0b21e2 Bump github/codeql-action from 3.25.13 to 3.25.14 (#122632) 2024-07-28 11:50:00 +02:00
Michael
092ab823d1 Add device info for legacy Ecovacs bots (#122671)
* add device info

* add tests
2024-07-28 11:06:32 +02:00
Phill (pssc)
3ad2456dd9 Add yamaha platform retry if receiver unavailable at setup (#122679)
* Add platform retry if recieiver unavailable at setup

* Fix Excpetion handling after testing
2024-07-28 10:39:41 +02:00
Avi Miller
146ec4e760 Create theme select entities on matrix devices (#122695)
Signed-off-by: Avi Miller <me@dje.li>
2024-07-28 10:28:42 +02:00
Jafar Atili
f563817b98 Bump pyElectra to 1.2.4 (#122724)
* Bump PyElectra to 1.2.3

* one more thing

* Bump PyElectra to 1.2.4

* fixed pyElectra license
2024-07-28 10:18:21 +02:00
Sid
ec15a66a68 Bump ruff to 0.5.5 (#122722) 2024-07-28 09:37:38 +02:00
Jafar Atili
e708e30c33 Bump pyswitchbee to 1.8.3 (#122713)
* Bump pyswitchbee to 1.8.3

* fix license
2024-07-27 23:11:42 +02:00
J. Nick Koston
383dd80919 Bump aiohomekit to 3.2.1 (#122704) 2024-07-27 12:13:11 -05:00
Robert Svensson
6752bd450b Use snapshot in Axis light tests (#122703) 2024-07-27 17:41:42 +02:00
Arie Catsman
b0780e1db5 Remove conditions from enphase_envoy test_switch (#122693) 2024-07-27 14:32:37 +02:00
Joakim Plate
02a5df0aee Update nibe library to 2.11.0 (#122697)
Update nibe library to 2.11.0 with changes

Addition of BT71 for F1155/F1255
Addition of climate zones for S1155/S1255
Include log information on incomplete reads
Correct fan speeds from being a percentage to a mapping on F series pumps
Corrections for airflow units
Let denied alarm resets writes be considered as a valid connection on setup
2024-07-27 14:01:58 +02:00
Andrew Jackson
cb4a48ca02 Migrate Mastodon integration to config flow (#122376)
* Migrate to config flow

* Fixes & add code owner

* Add codeowners

* Import within notify module

* Fixes from review

* Fixes

* Remove config schema
2024-07-27 13:07:02 +02:00
Luke Wale
64f997718a Add AirTouch5 cover (#122462)
* AirTouch5 - add cover

Each zone has a damper that can be controlled as a cover.

* remove unused assignment

* remove opinionated feature support

* Revert "remove unused assignment"

This reverts commit b4205a60a22ae3869436229b4a45547348496d39.

* ruff formatting changes

* git push translation and refactor
2024-07-27 12:36:48 +02:00
J. Nick Koston
482cf261c0 Small speedups to unifi (#122684)
- Use a set for event_is_on to avoid linear search
- Avoid many duplicate property lookups
2024-07-27 10:19:53 +02:00
J. Nick Koston
1a5706a693 Cache unifi device_tracker properties that never change (#122683) 2024-07-27 10:14:40 +02:00
Phill (pssc)
13c320902e Fix yamaha uid where host in config is defined (#122676)
Fix for uid where host in config is defined
2024-07-27 08:23:41 +02:00
Joost Lekkerkerker
bfbd01a4e5 Add typing to Comfoconnect (#122669) 2024-07-27 08:07:36 +02:00
SplicedNZ
09622e180e Add virtual integraion for "Mercury NZ Limited" (opower) (#122650)
* Add virtual integraion for "Mercury NZ Limited" and bump opower version requirement

* revert opower version bump, fix newlines

* Update name
2024-07-26 20:00:01 -07:00
Phill (pssc)
84486bad78 Yamaha device setup enhancement with unique id based on serial (#120764)
* fix server unavailale at HA startup Fixes #111108

Remove receiver zone confusion for mediaplayer instances
fix uniq id based on serial where avaialble
get serial suppiled by discovery for config entries.

* Fix linter errors

* ruff format

* Enhance debug to find setup code path for tests

* Enhance debug to find setup code path for tests

* Fix formatting

* Revered uid chanages as not needed yet and cuases other issues

* Revert "Fix formatting"

This reverts commit f3324868d2.

* Fix formatting

* Refector tests to cope with changes to plaform init to get serial numbers

* Update test patch

* Update test formatting

* remove all fixes revert code to only make clear we deal with zones and improve debuging
2024-07-26 23:36:34 +02:00
Robert Svensson
c486baccaa Patch import where its used in Axis hub test (#122674) 2024-07-26 23:33:37 +02:00
David Bonnes
57554aba57 Fix broken token caching for evohome (#122664)
* bugfix token caching
2024-07-26 21:28:58 +01:00
Álvaro Fernández Rojas
c9eb1a2e9c Fix Airzone Cloud WebServer memory usage unit (#122670)
airzone_cloud: sensor: fix webserver memory usage unit

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-07-26 21:59:16 +02:00
Álvaro Fernández Rojas
1a64489121 Add Airzone Cloud low thermostat battery binary sensor (#122665)
airzone_cloud: binary_sensor: add low thermostat battery

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-07-26 21:36:39 +02:00
Álvaro Fernández Rojas
888ffc002f Add Airzone Cloud WebServer CPU/Memory sensors (#122667)
airzone_cloud: sensor: add WebServer CPU/Memory

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-07-26 21:36:21 +02:00
Robert Svensson
58419f14e8 Less use of hass.data[DECONZ_DOMAIN] in deCONZ tests (#122657)
* Less use of hass.data[DECONZ_DOMAIN] in deCONZ tests

* Fix review comment

* Change patch path
2024-07-26 20:58:00 +02:00
Álvaro Fernández Rojas
57a5c7c8b6 Update aioairzone-cloud to v0.6.1 (#122661)
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-07-26 20:41:31 +02:00
SplicedNZ
b2b40d9ed6 Bump opower to 6.0.0 (#122658)
* Bump opower to 0.6.0

* Bump opower to 0.6.0
2024-07-26 20:19:58 +02:00
J. Nick Koston
8e578227c3 Add test coverage for doorbird cameras (#122660) 2024-07-26 20:04:23 +02:00
Álvaro Fernández Rojas
d3d522c463 Add Airzone Cloud zone thermostat sensors (#122648)
* airzone_cloud: sensor: add zone thermostat sensors

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

* airzone_cloud: sensor: add missing signal percentage icon

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

* airzone_cloud: sensor: add signal percentage translation

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

* airzone_cloud: sensor: disable thermostat_coverage

Also add to diagnostics category.

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

* Update homeassistant/components/airzone_cloud/strings.json

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

---------

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-07-26 19:28:39 +02:00
Robert Svensson
53131390ac Use snapshot in UniFi image tests (#122608)
* Use snapshot in UniFi image tests

* Make Image access_token deterministic
2024-07-26 19:22:09 +02:00
David Knowles
7820bcf218 Add entity services to the Hydrawise integration (#120883)
* Add services to the Hydrawise integration

* Add validation of duration ranges

* Remove clamping test

* Fix duration type in test

* Changes requested during review

* Add back the HydrawiseZoneBinarySensor class
2024-07-26 17:25:56 +02:00
Mr. Bubbles
49e2bfae31 Bump bring-api to v0.8.1 (#122653)
* Bump bring-api to v0.8.1

* update imports
2024-07-26 16:59:28 +02:00
G Johansson
55a1082866 Return unknown when data is missing in Trafikverket Weather (#122652)
Return unknown when data is missing
2024-07-26 16:59:12 +02:00
J. Nick Koston
5bb6272dfa Add test coverage for doorbird events (#122617) 2024-07-26 09:55:14 -05:00
Álvaro Fernández Rojas
850703824b Update aioairzone-cloud to v0.6.0 (#122647)
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-07-26 14:15:48 +02:00
Robert Svensson
72fdcd1cb1 Final steps to runtime_data in Axis integration (#122641)
* Rework connection error test to check config entry status

* Remove final dependencies to hass.data[AXIS_DOMAIN]
2024-07-26 12:29:47 +02:00
Robert Svensson
33ea67e1d0 Remove last references to hass.data[UNIFI_DOMAIN] (#122642) 2024-07-26 12:29:21 +02:00
Robert Svensson
047100069b Clean up some fixtures not referenced within deCONZ tests (#122637) 2024-07-26 11:21:48 +02:00
Andrew Jackson
ecadf6a330 Log line wrap in Mealie integration (#122635)
Log line wrap
2024-07-26 10:21:39 +02:00
Robert Svensson
b41b7aeb5b Remove validation of state==UNAVAILABLE on config entry unload in deCONZ test (#122558)
Only test remove entry marks entities unavailable in one place
2024-07-26 10:06:58 +02:00
Robert Svensson
c9b81a5c04 Replace ConfigEntry with MockConfigEntry in Axis tests (#122629)
* Remove unused fixtures in Axis tests

* Replace ConfigEntry with MockConfigEntry
2024-07-26 09:48:37 +02:00
Robert Svensson
51d5e21203 Remove unused fixtures in UniFi tests (#122628) 2024-07-26 09:48:12 +02:00
Brett Adams
621bd5f0c3 Add dynamic coordinator interval to Tesla Fleet (#122234)
* Add dynamic rate limiter

* tweaks

* Revert min polling back to 2min

* Set max 1 hour

* Remove redundant update_interval

* Tuning and fixes

* Reduce double API calls

* Type test

* Remove RateCalculator
2024-07-26 09:40:49 +02:00
Robert Svensson
9b4cf873c1 Replace ConfigEntry with MockConfigEntry in deCONZ tests (#122631) 2024-07-26 09:36:41 +02:00
J. Nick Koston
e262f759af Speed up bluetooth matching (#122626)
- use a defaultdict to avoid lots of setdefault
- move the intersection outside of the genexpr
  to avoid entering the genexpr if there is no
  intersection
2024-07-26 09:22:56 +02:00
Denis Shulyaka
78a98afb8d Remove obsolete string from openai_conversation strings.json (#122623) 2024-07-26 03:48:26 +02:00
Marc Mueller
e5f2046b19 Update mypy-dev to 1.12.0a2 (#122613) 2024-07-25 21:48:10 +02:00
Robert Resch
d77b5cbbbf Bump deebot-client to 8.2.0 (#122612) 2024-07-25 21:23:14 +02:00
Erik Montnemery
5dbd7684ce Fail tests if recorder creates nested sessions (#122579)
* Fail tests if recorder creates nested sessions

* Adjust import order

* Move get_instance
2024-07-25 21:18:55 +02:00
Robert Resch
32a0463f47 Update Ecovacs translations (#122610)
* Update Ecovacs translations

* Update tests
2024-07-25 21:18:42 +02:00
Robert Resch
62a3902de7 Set mode for Ecovacs clean count entity (#122611) 2024-07-25 21:18:28 +02:00
huettner94
eb3686af06 Add shelly overcurrent sensor for switches (#122494)
shelly: add overcurrent sensor for switches

just like overvoltage shelly switches can react to overcurrent and
diable the switch. Unfortunately this is is not mentioned anywhere in
the documentation.
It can be triggered by a device using more amps than set in
"Output protections" under the name "Overcurrent in amperes".
2024-07-25 21:22:18 +03:00
Erik Montnemery
81983d66f4 Avoid nesting sessions in recorder auto repairs tests (#122596) 2024-07-25 12:52:13 -05:00
Robert Svensson
e015c0a6ae Use snapshot in UniFi device tracker tests (#122603) 2024-07-25 18:16:58 +02:00
Robert Svensson
81c8ba87ab Use snapshot in UniFi button tests (#122602) 2024-07-25 18:16:25 +02:00
Erik Montnemery
ec957e4a94 Run statistics on 5-minute intervals in tests (#122592)
* Run statistics on 5-minute intervals in tests

* Fix test failing when mysql does not return rows in insert order
2024-07-25 17:32:49 +02:00
Robert Svensson
08d7beb803 Use snapshots in UniFi update tests (#122599) 2024-07-25 17:32:31 +02:00
Andrew Jackson
131ce09490 Allow nightly Mealie versions to pass (#121761)
* Allow invalid versions to pass

* Add log warning

* Change log message

* Add assert for log
2024-07-25 17:27:08 +02:00
Allen Porter
e8eb1ed35c Bump airgradient to 0.7.1 removing mashumaro direct dependency (#122534) 2024-07-25 07:46:09 -07:00
Erik Montnemery
0c7ab2062f Avoid creating nested sessions in recorder migration (#122580) 2024-07-25 15:44:48 +02:00
Josef Zweck
f1b933ae0c Add uncalibrated sensor for tedee (#122594)
* add uncalibrated sensor

* change off icon
2024-07-25 15:42:10 +02:00
G Johansson
e6d0bc7d5d Add device to Worldclock (#122557)
* Add device to Worldclock

* has_entity_name
2024-07-25 14:16:21 +02:00
J. Nick Koston
e795f81f73 Add support for govee presence sensor h5127 (#122568) 2024-07-25 13:35:00 +02:00
Álvaro Fernández Rojas
3caffa4dad Update aioqsw to v0.4.0 (#122586)
* Update aioqsw to v0.4.0

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

* trigger CI

---------

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-07-25 13:34:02 +02:00
Erik Montnemery
1f2c54f112 Avoid nesting sessions in recorder purge tests (#122581) 2024-07-25 13:12:10 +02:00
Erik Montnemery
c12a79ecba Deduplicate sensor recorder tests (#122516) 2024-07-25 12:31:11 +02:00
J. Nick Koston
a94e9d472b Add support for govee H5124 vibration sensors (#122562) 2024-07-25 12:29:52 +02:00
Philip Vanloo
cde22a44db Add LinkPlay integration (#113940)
* Intial commit

* Add artsound as virtual integration

* Add config_flow test
Add linkplay to .coveragerc
Add linkplay to .strict-typing

* Remove artsound component

* Bump package version

* Address mypy and coveragerc

* Address comments

* Address more feedback, add zeroconf and user flow

* Catch broken bridge in async_setup_entry

* Raise ConfigEntryNotReady, add __all__

* Implement new tests for the config_flow

* Fix async warning

* Fix test

* Address feedback

* Address comments

* Address comment

---------

Co-authored-by: Philip Vanloo <26272906+pvanloo@users.noreply.github.com>
2024-07-25 12:27:10 +02:00
Erik Montnemery
33d5ed52e6 Avoid nesting sessions in recorder statistics tests (#122582) 2024-07-25 12:26:44 +02:00
J. Nick Koston
78e24be1e7 Convert qingping to use entry.runtime_data (#122528) 2024-07-25 12:19:55 +02:00
J. Nick Koston
a89853da9d Migrate switchbot to use entry.runtime_data (#122530) 2024-07-25 12:18:24 +02:00
J. Nick Koston
3b01a57de3 Bump aioesphomeapi to 24.6.2 (#122566) 2024-07-25 12:16:16 +02:00
J. Nick Koston
256a2276ef Bump govee-ble to 0.40.0 (#122564) 2024-07-25 12:15:40 +02:00
Erik Montnemery
6223fe93c8 Fix typo in conftest.py (#122583) 2024-07-25 12:08:52 +02:00
J. Nick Koston
7348a1fd0c Convert homekit to use entry.runtime_data (#122533) 2024-07-25 11:06:55 +02:00
karwosts
8687b438f1 Use appropriate selector for homeassistant.update_entity (#122497) 2024-07-25 11:05:31 +02:00
Avi Miller
4901ecba7f Bump aiolifx to 1.0.6 (#122569) 2024-07-24 18:44:56 -05:00
G Johansson
59637d2391 Address Wake on Lan post-merge feedback (#122549)
Address Wake on Late post-merge feedback
2024-07-25 00:26:18 +02:00
Alexandre CUER
fcccd85ac4 Add tests to emoncms (#122547)
* Add tests to emoncms

* Reduce snapshot size

* Reduce snapshot size

* run hassfest to update CODEOWNERS file

* Update requirements_test_all.txt

* Update tests/components/emoncms/test_sensor.py

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

* Dont use snapshot when testing state change

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-07-24 21:40:05 +02:00
Stefano Semeraro
34b32ced25 Add CCT support to WLED (#122488) 2024-07-24 20:37:38 +02:00
Marcel van der Veldt
d7c713d18d Fix typo in Matter lock platform (#122536) 2024-07-24 20:12:51 +02:00
Marcel van der Veldt
4876e35fd8 Matter event follow up (#122553) 2024-07-24 20:12:20 +02:00
Robert Svensson
9198234465 Use snapshot in deCONZ light tests (#122548) 2024-07-24 20:08:42 +02:00
Robert Svensson
be8e432bea Use snapshot in deCONZ alarm control panel tests (#122551)
* Use snapshot in deCONZ alarm control panel tests

* Clean up comments
2024-07-24 20:08:06 +02:00
Andrew Jackson
943b1afb55 Fix target service attribute on Mastodon integration (#122546)
* Fix target

* Fix
2024-07-24 18:19:12 +02:00
Ian
3e8d3083ac Refactor NextBus integration to use new API (#121133)
* Refactor NextBus integration to use new API

This removes the `messages`, `directions`, and `attribution` attributes
from the sensor. Those may be added back in the future with additional
refactoring.

Some existing sensors may be broken today because of deprecated Agency
names. This patch will not migrate them as the migration path is
ambiguous. Setting up again should work though.

* Move result indexing outside of try/except
2024-07-24 18:18:21 +02:00
Robert Svensson
3c4f2c2dcf Use snapshot in deCONZ select tests (#122541) 2024-07-24 18:07:40 +02:00
Robert Svensson
277883e756 Use snapshot in deCONZ sensor tests (#122543) 2024-07-24 18:07:18 +02:00
Robert Svensson
5bda072141 Use snapshot in deCONZ scene tests (#122540) 2024-07-24 17:32:57 +02:00
Robert Svensson
a8e60a6c53 Use snapshot in deCONZ number tests (#122538) 2024-07-24 17:28:47 +02:00
Robert Svensson
50da3c5c5b Use snapshot in deCONZ climate tests (#122535) 2024-07-24 17:15:01 +02:00
Robert Svensson
c5f9ff6ac5 Use snapshot in deCONZ cover tests (#122537) 2024-07-24 17:14:40 +02:00
J. Nick Koston
6393f1f02d Convert rainmachine to use entry.runtime_data (#122532) 2024-07-24 08:52:14 -06:00
J. Nick Koston
c81b9d624b Convert oralb to use entry.runtime_data (#122527) 2024-07-24 16:23:42 +02:00
Noah Husby
cae992f5e6 Add volume step to Russound media player (#122523)
* Add volume step to Russound media player

* Add return types
2024-07-24 14:44:44 +02:00
Joost Lekkerkerker
9ecdee3b78 Extract Evohome base entities to separate module (#122515)
* Extract Evohome base entities to separate module

* Extract Evohome base entities to separate module
2024-07-24 13:22:48 +02:00
Franck Nijhof
267dfac737 2024.7.3 (#122194) 2024-07-19 19:38:08 +02:00
Franck Nijhof
a08ffdc8d3 Bump version to 2024.7.3 2024-07-19 18:50:21 +02:00
Mr. Bubbles
1ef4332af6 Fix KeyError in config flow of Bring integration (#122136) 2024-07-19 18:49:45 +02:00
Marc Mueller
d0d2fd7918 Update yt-dlp to 2024.07.16 (#122124) 2024-07-19 18:49:41 +02:00
Steven B.
c518c4756b Bump tplink dependency python-kasa to 0.7.0.5 (#122119) 2024-07-19 18:49:38 +02:00
Shay Levy
a3a99cc631 Prevent connecting to a Shelly device that is already connected (#122105) 2024-07-19 18:49:35 +02:00
Steven B.
977a55e3b8 Update tplink device config during reauth flow (#122089) 2024-07-19 18:49:31 +02:00
Harry Martland
002db3c3e9 Fix hive not updating when boosting (#122042)
* fixes issue where hive does not update when boosting

* formats files
2024-07-19 18:49:28 +02:00
Robert Svensson
d9e44bab69 Mark UniFi power cycle button as unavailable if PoE is not enabled on port (#122035) 2024-07-19 18:49:25 +02:00
G Johansson
4b93fc61b5 Bump python-holidays to 0.53 (#122021) 2024-07-19 18:49:21 +02:00
J. Nick Koston
214b5efd72 Narrow sqlite database corruption check to ensure disk image is malformed (#121947)
* Narrow sqlite database corruption check to ensure disk image is malformed

The database corruption check would also replace the database when it
locked externally instead of only when its malformed.

This was discovered in https://github.com/home-assistant/core/issues/121909#issuecomment-2227409124
when a user did a manual index creation while HA was online

* tweak

* tweak

* fix

* fix
2024-07-19 18:49:16 +02:00
Maciej Bieniek
9bd822d693 Fix configuration_url for Shelly device using IPv6 (#121939)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-07-19 18:48:32 +02:00
Tomasz Gorochowik
bf89eaae25 Fix enigma2 mute (#121928) 2024-07-19 18:42:44 +02:00
Scott K Logan
f9b359ae30 Fix rainforest_raven closing device due to timeout (#121905) 2024-07-19 18:42:41 +02:00
mvn23
24ed003471 Fix opentherm_gw availability (#121892) 2024-07-19 18:42:38 +02:00
J. Nick Koston
a835750252 Log add/remove index complete at the same level as when it starts (#121852) 2024-07-19 18:42:35 +02:00
Avi Miller
ad07bdb62b Bump aiolifx to 1.0.5 (#121824) 2024-07-19 18:42:32 +02:00
Avi Miller
41104324ec Bump aiolifx to 1.0.4 (#121267) 2024-07-19 18:42:28 +02:00
ollo69
e9344ae101 Bump PySwitchbot to 0.48.1 (#121804) 2024-07-19 18:40:25 +02:00
starkillerOG
56a9167ed2 Reolink media second lens (#121800)
DUO lens camera distinguish between lenses for media playback
2024-07-19 18:40:22 +02:00
Jan Bouwhuis
e0b90c4b36 Fix alexa does to check current_position correctly when handling cover range changes (#121798) 2024-07-19 18:40:19 +02:00
Jan-Philipp Benecke
976902f22c Add missing translations to Roborock (#121796) 2024-07-19 18:40:16 +02:00
Steven B
0f69c58ba9 Bump python-kasa to 0.7.0.4 (#121791) 2024-07-19 18:40:13 +02:00
Glenn Waters
1e6c96c6eb Use async_connect in newly bumped 0.5.8 UPB library (#121789) 2024-07-19 18:40:09 +02:00
J. Nick Koston
63b14d14c1 Add some missing tplink ouis (#121785) 2024-07-19 18:40:06 +02:00
Josef Zweck
6aaaba6419 Bump pytedee_async to 0.2.20 (#121783) 2024-07-19 18:40:03 +02:00
Allen Porter
3b8e736fe3 Pin mashumaro version >= 3.13.1 for python 3.12.4 compatibility. (#121782)
Pin mashumaro version for python 3.12.4 compatibility.
2024-07-19 18:40:00 +02:00
tronikos
68841b3d8a Bump opower to 0.5.2 to fix 403 forbidden errors for users with multiple accounts (#121762) 2024-07-19 18:39:56 +02:00
Abílio Costa
3d8afe7cb8 Update Idasen Desk library to 2.6.2 (#121729) 2024-07-19 18:29:45 +02:00
Robert Svensson
8595242142 Fix bad access to UniFi runtime_data when not assigned (#121725)
* Fix bad access to runtime_data when not assigned

* Fix review comment

* Clean up if statements
2024-07-19 18:29:42 +02:00
Joost Lekkerkerker
ebe7bc0686 Bump knocki to 0.3.1 (#121717) 2024-07-19 18:29:39 +02:00
J. Nick Koston
4ab180f016 Fix update happening too early in unifiprotect (#121714) 2024-07-19 18:29:36 +02:00
Mr. Bubbles
372649069e Bump pyloadapi to v1.3.2 (#121709) 2024-07-19 18:29:33 +02:00
Joost Lekkerkerker
98df46f3ea Bump knocki to 0.3.0 (#121704) 2024-07-19 18:29:30 +02:00
Steven B
269fb23527 Fix tplink bug changing color temp on bulbs with light effects (#121696) 2024-07-19 18:29:27 +02:00
Sid
ad5cbf0da6 Allow enigma2 devices to use different source bouquets (#121686) 2024-07-19 18:29:24 +02:00
Lucas Mindêllo de Andrade
10cdf64f90 Bump sunweg 3.0.2 (#121684) 2024-07-19 18:29:21 +02:00
Tomek Porozynski
ec8e639804 Update Supla async_set_cover_position to use "REVEAL_PARTIALLY" (#121663) 2024-07-19 18:29:17 +02:00
Jan Stienstra
37f37f7287 Retain Jellyfin config flow input on connection issue (#121618) 2024-07-19 18:29:13 +02:00
Mr. Bubbles
ef7d68bfd6 Fix reauth error and exception in ista EcoTrend integration (#121482) 2024-07-19 18:29:08 +02:00
Franck Nijhof
058b012e6c 2024.7.2 (#121671) 2024-07-10 13:18:48 +02:00
Franck Nijhof
71370758a8 Bump version to 2024.7.2 2024-07-10 12:06:02 +02:00
Franck Nijhof
38a44676eb Block variable <=3.4.4 custom integration from breaking the recorder (#121670) 2024-07-10 12:01:11 +02:00
Marcel van der Veldt
05ce3d35b3 Matter lock state follow-up (#121669) 2024-07-10 12:01:08 +02:00
Marcel van der Veldt
2151086b0a Fix state for Matter Locks (including optional door sensor) (#121665) 2024-07-10 12:01:05 +02:00
Franck Nijhof
9c83af3789 Block places <=2.7.0 custom integration from breaking the recorder (#121662) 2024-07-10 12:01:01 +02:00
tronikos
ac3eecc879 Handle errors in Fully Kiosk camera (#121659) 2024-07-10 12:00:58 +02:00
Franck Nijhof
ec0910e3da Block icloud3 custom integration from breaking the recorder (#121658) 2024-07-10 12:00:55 +02:00
Maikel Punie
fd0c26cd56 Small fix in velbus cover for the assumed states (#121656) 2024-07-10 12:00:52 +02:00
Paul Bottein
a4c5dee082 Update frontend to 20240710.0 (#121651) 2024-07-10 12:00:48 +02:00
Joakim Plate
37c09dbdb6 Allow ambilight when we have connection (philips_js) (#121620) 2024-07-10 12:00:45 +02:00
Arie Catsman
73d1973625 Bump pyenphase to 1.20.6 (#121583)
bump pyenphase to 1.20.6
2024-07-10 12:00:42 +02:00
Glenn Waters
5a04a886cf Fix upb config flow connect (#121571) 2024-07-10 12:00:39 +02:00
Franck Nijhof
50802f84f0 Update tailscale to 0.6.1 (#121557) 2024-07-10 12:00:36 +02:00
Franck Nijhof
138b68ecc0 Update vehicle to 2.2.2 (#121556) 2024-07-10 12:00:33 +02:00
Christoph
e0b01ee94e Remove homematic state_class from GAS_POWER sensor (#121533) 2024-07-10 12:00:30 +02:00
J. Nick Koston
4f2c3df518 Fix person tracking in unifiprotect (#121528) 2024-07-10 12:00:26 +02:00
Paulus Schoutsen
51a6bb1c22 Include hass device ID in mobile app get_config webhook (#121496) 2024-07-10 12:00:23 +02:00
Ovidiu D. Nițan
6bf9ec69f3 Bump xiaomi-ble to 0.30.2 (#121471) 2024-07-10 12:00:19 +02:00
Joost Lekkerkerker
21309eeb5d Bump xiaomi-ble to 0.30.1 (#120743) 2024-07-10 12:00:14 +02:00
J. Nick Koston
0a1b46c52f Bump yalexs to 6.4.2 (#121467) 2024-07-10 11:52:56 +02:00
Jason R. Coombs
9512f9eec3 Bump jaraco.abode to 5.2.1 (#121446)
Bump dependency on jaraco.abode to 5.2.1.

Closes #121300
2024-07-10 11:52:53 +02:00
jan iversen
ab94422c18 Bump pymodbus to 3.6.9 (#121445)
Bump pymodbus 3.6.9.
2024-07-10 11:52:50 +02:00
Joost Lekkerkerker
ec105e5265 Fix Mealie URL field (#121434) 2024-07-10 11:52:47 +02:00
Joost Lekkerkerker
cadd8521ae Sort mealie mealplans (#121433) 2024-07-10 11:52:43 +02:00
Joost Lekkerkerker
8825c50671 Fix MPD config flow (#121431) 2024-07-10 11:52:40 +02:00
Michael
a72cc3c248 Allow current empty feeds to be configured in Feedreader (#121421) 2024-07-10 11:52:37 +02:00
G Johansson
780f7254c1 Fix feature flag in climate (#121398) 2024-07-10 11:52:34 +02:00
G Johansson
37621e77ae Fix timezone issue in smhi weather (#121389) 2024-07-10 11:52:31 +02:00
G Johansson
8017386c73 Fix unnecessary logging of turn on/off feature flags in Climate (#121387) 2024-07-10 11:52:28 +02:00
G Johansson
a5f4c25a2c Bump psutil to 6.0.0 (#121385) 2024-07-10 11:52:25 +02:00
Brett Adams
1d7bddf449 Fix initial Wall Connector values in Tessie (#121353) 2024-07-10 11:52:21 +02:00
Luke Lashley
711bdaf373 Bump anova-wifi to 0.17.0 (#121337)
* Bump anova-wifi to 0.16.0

* Bump to .17
2024-07-10 11:52:18 +02:00
Jan Temešinko
803d9c5a8e Fix ombi configuration validation (#121314) 2024-07-10 11:52:15 +02:00
Rasmus Lundsgaard
1133c41fa8 Fix empty list in kodi media_player (#121250)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-07-10 11:52:12 +02:00
Alan
a06af7ee93 LLM to handle int attributes (#121037) 2024-07-10 11:52:08 +02:00
Robert C. Maehl
c54717707e Direct Users to App-Specific Passwords for iCloud integration to prevent MFA spam (#120945)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-07-10 11:52:05 +02:00
J. Nick Koston
440d83d754 Remove legacy foreign key constraint from sqlite states table (#120779) 2024-07-10 11:51:59 +02:00
Franck Nijhof
1cf62916a7 2024.7.1 (#121289) 2024-07-05 17:25:40 +02:00
Bram Kragten
e3958d4adb Update frontend to 20240705.0 (#121295) 2024-07-05 15:04:01 +02:00
Franck Nijhof
dfccd4abf9 Bump version to 2024.7.1 2024-07-05 11:21:36 +02:00
Steven B
994d6f552c Fix tplink light effect behaviour when activating a scene (#121288) 2024-07-05 11:21:07 +02:00
G Johansson
b015611a2a Bump python-holidays to 0.52 (#121283) 2024-07-05 11:21:04 +02:00
Shay Levy
f4e362c5d0 Bump aiowebostv to 0.4.2 (#121258) 2024-07-05 11:21:00 +02:00
Jordi
a542236614 Bump aioaquacell to 0.1.8 (#121253) 2024-07-05 11:20:57 +02:00
Shay Levy
651439ea06 Fix WebOS TV media player status when OFF after IDLE (#121251) 2024-07-05 11:20:54 +02:00
Robert Resch
eda450838e Bump deebot-client to 8.1.1 (#121241) 2024-07-05 11:20:50 +02:00
hahn-th
b906daa493 Revert Homematic IP Cloud unique ID changes (#121231) 2024-07-05 11:20:47 +02:00
Thomas55555
ac668dce7d Fix work area sensor in Husqvarna Automower (#121228) 2024-07-05 11:20:44 +02:00
Luke Lashley
1bb4d62a3e Bump anova-wifi to 0.15.0 (#121222) 2024-07-05 11:20:40 +02:00
Marcel van der Veldt
0b970f9a85 Listen for attribute changes of OnOff cluster in appliances (#121198) 2024-07-05 11:20:37 +02:00
Marcel van der Veldt
d2b695e7b5 Fix Matter light discovery schema for DimmerSwitch (#121185) 2024-07-05 11:20:34 +02:00
Steven B
b2f23c1a5e Bump python-kasa to 0.7.0.3 (#121183) 2024-07-05 11:20:31 +02:00
Gerben Jongerius
f403afb012 Bump youless library version 2.1.2 (#121181) 2024-07-05 11:20:27 +02:00
Maciej Bieniek
ee276aff44 Fix pulse counter frequency sensors for Shelly Plus Uni (#121178)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-07-05 11:20:23 +02:00
Maikel Punie
0acd1dc5d1 Bump velbusaio to 2024.7.5 (#121156)
* Bump velbusaio to 2024.7.4

* bump to 2024.7.5 to remove print functions
2024-07-05 11:20:20 +02:00
Martin Weinelt
21815e1621 Fix broken pathlib import in august integration (#121135) 2024-07-05 11:20:17 +02:00
J. Nick Koston
15933bb16f Bump inkbird-ble to 0.5.8 (#121134) 2024-07-05 11:20:13 +02:00
Pavel Skuratovich
930cd0dc50 Starline: Fix "Error updating SLNet token" message in Log (#121122)
Fixes https://github.com/home-assistant/core/issues/116715
2024-07-05 11:20:10 +02:00
Christoph
fc4af48179 Fix HmIP-ESI GAS sensor DeviceClass (#121106)
should be SensorDeviceClass:GAS instead of SensorDeviceClass:VOLUME to be supported in the Energy Dashboard
2024-07-05 11:20:07 +02:00
Marcel van der Veldt
ba1cf84ea5 Fix locking/unlocking transition state in Matter lock platform (#121099) 2024-07-05 11:20:04 +02:00
dougiteixeira
59cf01e252 Add device class translations in Random (#120890) 2024-07-05 11:20:00 +02:00
Allen Porter
46e681f4fc Improve redaction for stream error messages (#120867) 2024-07-05 11:19:56 +02:00
910 changed files with 33082 additions and 9885 deletions

View File

@@ -904,6 +904,7 @@ jobs:
cov_params+=(--cov-report=xml)
fi
echo "Test group ${{ matrix.group }}: $(sed -n "${{ matrix.group }},1p" pytest_buckets.txt)"
python3 -b -X dev -m pytest \
-qq \
--timeout=9 \

View File

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

View File

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

View File

@@ -120,6 +120,7 @@ homeassistant.components.bond.*
homeassistant.components.braviatv.*
homeassistant.components.brother.*
homeassistant.components.browser.*
homeassistant.components.bryant_evolution.*
homeassistant.components.bthome.*
homeassistant.components.button.*
homeassistant.components.calendar.*
@@ -167,6 +168,7 @@ homeassistant.components.ecowitt.*
homeassistant.components.efergy.*
homeassistant.components.electrasmart.*
homeassistant.components.electric_kiwi.*
homeassistant.components.elevenlabs.*
homeassistant.components.elgato.*
homeassistant.components.elkm1.*
homeassistant.components.emulated_hue.*
@@ -280,6 +282,7 @@ homeassistant.components.lidarr.*
homeassistant.components.lifx.*
homeassistant.components.light.*
homeassistant.components.linear_garage_door.*
homeassistant.components.linkplay.*
homeassistant.components.litejet.*
homeassistant.components.litterrobot.*
homeassistant.components.local_ip.*

View File

@@ -197,7 +197,8 @@ build.json @home-assistant/supervisor
/tests/components/bluemaestro/ @bdraco
/homeassistant/components/blueprint/ @home-assistant/core
/tests/components/blueprint/ @home-assistant/core
/homeassistant/components/bluesound/ @thrawnarn
/homeassistant/components/bluesound/ @thrawnarn @LouisChrist
/tests/components/bluesound/ @thrawnarn @LouisChrist
/homeassistant/components/bluetooth/ @bdraco
/tests/components/bluetooth/ @bdraco
/homeassistant/components/bluetooth_adapters/ @bdraco
@@ -220,6 +221,8 @@ build.json @home-assistant/supervisor
/tests/components/brottsplatskartan/ @gjohansson-ST
/homeassistant/components/brunt/ @eavanvalkenburg
/tests/components/brunt/ @eavanvalkenburg
/homeassistant/components/bryant_evolution/ @danielsmyers
/tests/components/bryant_evolution/ @danielsmyers
/homeassistant/components/bsblan/ @liudger
/tests/components/bsblan/ @liudger
/homeassistant/components/bt_smarthub/ @typhoon2099
@@ -373,6 +376,8 @@ build.json @home-assistant/supervisor
/tests/components/electrasmart/ @jafar-atili
/homeassistant/components/electric_kiwi/ @mikey0000
/tests/components/electric_kiwi/ @mikey0000
/homeassistant/components/elevenlabs/ @sorgfresser
/tests/components/elevenlabs/ @sorgfresser
/homeassistant/components/elgato/ @frenck
/tests/components/elgato/ @frenck
/homeassistant/components/elkm1/ @gwww @bdraco
@@ -384,6 +389,7 @@ build.json @home-assistant/supervisor
/tests/components/elvia/ @ludeeus
/homeassistant/components/emby/ @mezz64
/homeassistant/components/emoncms/ @borpin @alexandrecuer
/tests/components/emoncms/ @borpin @alexandrecuer
/homeassistant/components/emonitor/ @bdraco
/tests/components/emonitor/ @bdraco
/homeassistant/components/emulated_hue/ @bdraco @Tho85
@@ -706,6 +712,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/iqvia/ @bachya
/tests/components/iqvia/ @bachya
/homeassistant/components/irish_rail_transport/ @ttroy50
/homeassistant/components/iron_os/ @tr4nt0r
/tests/components/iron_os/ @tr4nt0r
/homeassistant/components/isal/ @bdraco
/tests/components/isal/ @bdraco
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
@@ -794,6 +802,8 @@ build.json @home-assistant/supervisor
/tests/components/light/ @home-assistant/core
/homeassistant/components/linear_garage_door/ @IceBotYT
/tests/components/linear_garage_door/ @IceBotYT
/homeassistant/components/linkplay/ @Velleman
/tests/components/linkplay/ @Velleman
/homeassistant/components/linux_battery/ @fabaff
/homeassistant/components/litejet/ @joncar
/tests/components/litejet/ @joncar
@@ -836,7 +846,8 @@ build.json @home-assistant/supervisor
/tests/components/lyric/ @timmo001
/homeassistant/components/madvr/ @iloveicedgreentea
/tests/components/madvr/ @iloveicedgreentea
/homeassistant/components/mastodon/ @fabaff
/homeassistant/components/mastodon/ @fabaff @andrew-codechimp
/tests/components/mastodon/ @fabaff @andrew-codechimp
/homeassistant/components/matrix/ @PaarthShah
/tests/components/matrix/ @PaarthShah
/homeassistant/components/matter/ @home-assistant/matter
@@ -1042,8 +1053,8 @@ build.json @home-assistant/supervisor
/tests/components/otbr/ @home-assistant/core
/homeassistant/components/ourgroceries/ @OnFreund
/tests/components/ourgroceries/ @OnFreund
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
/homeassistant/components/ovo_energy/ @timmo001
/tests/components/ovo_energy/ @timmo001
/homeassistant/components/p1_monitor/ @klaasnicolaas

View File

@@ -18,9 +18,12 @@ from homeassistant.const import (
EVENT_THEMES_UPDATED,
)
from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED
from homeassistant.helpers.category_registry import EVENT_CATEGORY_REGISTRY_UPDATED
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
from homeassistant.helpers.floor_registry import EVENT_FLOOR_REGISTRY_UPDATED
from homeassistant.helpers.issue_registry import EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED
from homeassistant.helpers.label_registry import EVENT_LABEL_REGISTRY_UPDATED
from homeassistant.util.event_type import EventType
# These are events that do not contain any sensitive data
@@ -41,4 +44,7 @@ SUBSCRIBE_ALLOWLIST: Final[set[EventType[Any] | str]] = {
EVENT_SHOPPING_LIST_UPDATED,
EVENT_STATE_CHANGED,
EVENT_THEMES_UPDATED,
EVENT_LABEL_REGISTRY_UPDATED,
EVENT_CATEGORY_REGISTRY_UPDATED,
EVENT_FLOOR_REGISTRY_UPDATED,
}

View File

@@ -1,5 +1,5 @@
{
"domain": "logitech",
"name": "Logitech",
"integrations": ["harmony", "ue_smart_radio", "squeezebox"]
"integrations": ["harmony", "squeezebox"]
}

View File

@@ -206,7 +206,8 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC Mode and State."""
if hvac_mode == HVACMode.OFF:
return await self.async_turn_off()
await self.async_turn_off()
return
if hvac_mode == HVACMode.HEAT_COOL and self.preset_mode != ADVANTAGE_AIR_MYAUTO:
raise ServiceValidationError("Heat/Cool is not supported in this mode")
await self.async_update_ac(

View File

@@ -2,9 +2,13 @@
from typing import Any
from airgradient import AirGradientClient, AirGradientError, ConfigurationControl
from airgradient import (
AirGradientClient,
AirGradientError,
AirGradientParseError,
ConfigurationControl,
)
from awesomeversion import AwesomeVersion
from mashumaro import MissingField
import voluptuous as vol
from homeassistant.components import zeroconf
@@ -83,10 +87,10 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
self.client = AirGradientClient(user_input[CONF_HOST], session=session)
try:
current_measures = await self.client.get_current_measures()
except AirGradientParseError:
return self.async_abort(reason="invalid_version")
except AirGradientError:
errors["base"] = "cannot_connect"
except MissingField:
return self.async_abort(reason="invalid_version")
else:
await self.async_set_unique_id(current_measures.serial_number)
self._abort_if_unique_id_configured()

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.7.0"],
"requirements": ["airgradient==0.7.1"],
"zeroconf": ["_airgradient._tcp.local."]
}

View File

@@ -156,7 +156,8 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
if hvac_mode == HVACMode.OFF:
return await self.async_turn_off()
await self.async_turn_off()
return
await self._airtouch.SetCoolingModeForAc(
self._ac_number, HA_STATE_TO_AT[hvac_mode]
)
@@ -262,7 +263,8 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
if hvac_mode == HVACMode.OFF:
return await self.async_turn_off()
await self.async_turn_off()
return
if self.hvac_mode == HVACMode.OFF:
await self.async_turn_on()
self._unit = self._airtouch.GetGroups()[self._group_number]

View File

@@ -11,7 +11,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
PLATFORMS: list[Platform] = [Platform.CLIMATE]
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.COVER]
type Airtouch5ConfigEntry = ConfigEntry[Airtouch5SimpleClient]

View File

@@ -121,6 +121,7 @@ class Airtouch5ClimateEntity(ClimateEntity, Airtouch5Entity):
"""Base class for Airtouch5 Climate Entities."""
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = DOMAIN
_attr_target_temperature_step = 1
_attr_name = None
_enable_turn_on_off_backwards_compatibility = False

View File

@@ -0,0 +1,134 @@
"""Representation of the Damper for AirTouch 5 Devices."""
import logging
from typing import Any
from airtouch5py.airtouch5_simple_client import Airtouch5SimpleClient
from airtouch5py.packets.zone_control import (
ZoneControlZone,
ZoneSettingPower,
ZoneSettingValue,
)
from airtouch5py.packets.zone_name import ZoneName
from airtouch5py.packets.zone_status import ZoneStatusZone
from homeassistant.components.cover import (
ATTR_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import Airtouch5ConfigEntry
from .const import DOMAIN
from .entity import Airtouch5Entity
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: Airtouch5ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Airtouch 5 Cover entities."""
client = config_entry.runtime_data
# Each zone has a cover for its open percentage
async_add_entities(
Airtouch5ZoneOpenPercentage(
client, zone, client.latest_zone_status[zone.zone_number].has_sensor
)
for zone in client.zones
)
class Airtouch5ZoneOpenPercentage(CoverEntity, Airtouch5Entity):
"""How open the damper is in each zone."""
_attr_device_class = CoverDeviceClass.DAMPER
_attr_translation_key = "damper"
# Zones with temperature sensors shouldn't be manually controlled.
# We allow it but warn the user in the integration documentation.
_attr_supported_features = (
CoverEntityFeature.SET_POSITION
| CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
)
def __init__(
self, client: Airtouch5SimpleClient, zone_name: ZoneName, has_sensor: bool
) -> None:
"""Initialise the Cover Entity."""
super().__init__(client)
self._zone_name = zone_name
self._attr_unique_id = f"zone_{zone_name.zone_number}_open_percentage"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"zone_{zone_name.zone_number}")},
name=zone_name.zone_name,
manufacturer="Polyaire",
model="AirTouch 5",
)
@callback
def _async_update_attrs(self, data: dict[int, ZoneStatusZone]) -> None:
if self._zone_name.zone_number not in data:
return
status = data[self._zone_name.zone_number]
self._attr_current_cover_position = int(status.open_percentage * 100)
if status.open_percentage == 0:
self._attr_is_closed = True
else:
self._attr_is_closed = False
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Add data updated listener after this object has been initialized."""
await super().async_added_to_hass()
self._client.zone_status_callbacks.append(self._async_update_attrs)
self._async_update_attrs(self._client.latest_zone_status)
async def async_will_remove_from_hass(self) -> None:
"""Remove data updated listener after this object has been initialized."""
await super().async_will_remove_from_hass()
self._client.zone_status_callbacks.remove(self._async_update_attrs)
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the damper."""
await self._set_cover_position(100)
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close damper."""
await self._set_cover_position(0)
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Update the damper to a specific position."""
if (position := kwargs.get(ATTR_POSITION)) is None:
_LOGGER.debug("Argument `position` is missing in set_cover_position")
return
await self._set_cover_position(position)
async def _set_cover_position(self, position_percent: float) -> None:
power: ZoneSettingPower
if position_percent == 0:
power = ZoneSettingPower.SET_TO_OFF
else:
power = ZoneSettingPower.SET_TO_ON
zcz = ZoneControlZone(
self._zone_name.zone_number,
ZoneSettingValue.SET_OPEN_PERCENTAGE,
power,
position_percent / 100.0,
)
packet = self._client.data_packet_factory.zone_control([zcz])
await self._client.send_packet(packet)

View File

@@ -6,15 +6,12 @@ from airtouch5py.airtouch5_simple_client import Airtouch5SimpleClient
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
from .const import DOMAIN
class Airtouch5Entity(Entity):
"""Base class for Airtouch5 entities."""
_attr_should_poll = False
_attr_has_entity_name = True
_attr_translation_key = DOMAIN
def __init__(self, client: Airtouch5SimpleClient) -> None:
"""Initialise the Entity."""

View File

@@ -27,6 +27,11 @@
}
}
}
},
"cover": {
"damper": {
"name": "[%key:component::cover::entity_component::damper::name%]"
}
}
}
}

View File

@@ -14,6 +14,7 @@ from aioairzone_cloud.const import (
AZD_FLOOR_DEMAND,
AZD_PROBLEMS,
AZD_SYSTEMS,
AZD_THERMOSTAT_BATTERY_LOW,
AZD_WARNINGS,
AZD_ZONES,
)
@@ -88,6 +89,10 @@ ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]
key=AZD_AQ_ACTIVE,
translation_key="air_quality_active",
),
AirzoneBinarySensorEntityDescription(
device_class=BinarySensorDeviceClass.BATTERY,
key=AZD_THERMOSTAT_BATTERY_LOW,
),
AirzoneBinarySensorEntityDescription(
device_class=BinarySensorDeviceClass.RUNNING,
key=AZD_FLOOR_DEMAND,

View File

@@ -13,9 +13,12 @@ from aioairzone_cloud.const import (
AZD_GROUPS,
AZD_HOT_WATERS,
AZD_INSTALLATIONS,
AZD_MODEL,
AZD_NAME,
AZD_SYSTEM_ID,
AZD_SYSTEMS,
AZD_THERMOSTAT_FW,
AZD_THERMOSTAT_MODEL,
AZD_WEBSERVER,
AZD_WEBSERVERS,
AZD_ZONES,
@@ -69,6 +72,7 @@ class AirzoneAidooEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, aidoo_id)},
manufacturer=MANUFACTURER,
model=aidoo_data[AZD_MODEL],
name=aidoo_data[AZD_NAME],
via_device=(DOMAIN, aidoo_data[AZD_WEBSERVER]),
)
@@ -111,6 +115,7 @@ class AirzoneGroupEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, group_id)},
model="Group",
manufacturer=MANUFACTURER,
name=group_data[AZD_NAME],
)
@@ -154,6 +159,7 @@ class AirzoneHotWaterEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, dhw_id)},
manufacturer=MANUFACTURER,
model="Hot Water",
name=dhw_data[AZD_NAME],
via_device=(DOMAIN, dhw_data[AZD_WEBSERVER]),
)
@@ -195,6 +201,7 @@ class AirzoneInstallationEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, inst_id)},
manufacturer=MANUFACTURER,
model="Installation",
name=inst_data[AZD_NAME],
)
@@ -240,9 +247,11 @@ class AirzoneSystemEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, system_id)},
model=system_data.get(AZD_MODEL),
manufacturer=MANUFACTURER,
name=system_data[AZD_NAME],
via_device=(DOMAIN, system_data[AZD_WEBSERVER]),
sw_version=system_data.get(AZD_FIRMWARE),
)
def get_airzone_value(self, key: str) -> Any:
@@ -270,6 +279,7 @@ class AirzoneWebServerEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, ws_id)},
identifiers={(DOMAIN, ws_id)},
model="WebServer",
manufacturer=MANUFACTURER,
name=ws_data[AZD_NAME],
sw_version=ws_data[AZD_FIRMWARE],
@@ -300,9 +310,11 @@ class AirzoneZoneEntity(AirzoneEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, zone_id)},
model=zone_data.get(AZD_THERMOSTAT_MODEL),
manufacturer=MANUFACTURER,
name=zone_data[AZD_NAME],
via_device=(DOMAIN, self.system_id),
sw_version=zone_data.get(AZD_THERMOSTAT_FW),
)
def get_airzone_value(self, key: str) -> Any:

View File

@@ -0,0 +1,15 @@
{
"entity": {
"sensor": {
"cpu_usage": {
"default": "mdi:cpu-32-bit"
},
"free_memory": {
"default": "mdi:memory"
},
"thermostat_coverage": {
"default": "mdi:signal"
}
}
}
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.5.5"]
"requirements": ["aioairzone-cloud==0.6.1"]
}

View File

@@ -10,8 +10,12 @@ from aioairzone_cloud.const import (
AZD_AQ_PM_1,
AZD_AQ_PM_2P5,
AZD_AQ_PM_10,
AZD_CPU_USAGE,
AZD_HUMIDITY,
AZD_MEMORY_FREE,
AZD_TEMP,
AZD_THERMOSTAT_BATTERY,
AZD_THERMOSTAT_COVERAGE,
AZD_WEBSERVERS,
AZD_WIFI_RSSI,
AZD_ZONES,
@@ -28,6 +32,7 @@ from homeassistant.const import (
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfInformation,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
@@ -52,6 +57,22 @@ AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
)
WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription(
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
key=AZD_CPU_USAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="cpu_usage",
),
SensorEntityDescription(
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
key=AZD_MEMORY_FREE,
native_unit_of_measurement=UnitOfInformation.BYTES,
state_class=SensorStateClass.MEASUREMENT,
translation_key="free_memory",
),
SensorEntityDescription(
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -98,6 +119,20 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
device_class=SensorDeviceClass.BATTERY,
key=AZD_THERMOSTAT_BATTERY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
key=AZD_THERMOSTAT_COVERAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="thermostat_coverage",
),
)

View File

@@ -37,6 +37,17 @@
"auto": "Auto"
}
}
},
"sensor": {
"cpu_usage": {
"name": "CPU usage"
},
"free_memory": {
"name": "Free memory"
},
"thermostat_coverage": {
"name": "Signal percentage"
}
}
}
}

View File

@@ -5,5 +5,6 @@
"codeowners": ["@home-assistant/cloud", "@ochlocracy", "@jbouwh"],
"dependencies": ["http"],
"documentation": "https://www.home-assistant.io/integrations/alexa",
"integration_type": "system",
"iot_class": "cloud_push"
}

View File

@@ -4,3 +4,6 @@ from typing import Final
DOMAIN: Final = "apcupsd"
CONNECTION_TIMEOUT: int = 10
# Field name of last self test retrieved from apcupsd.
LASTSTEST: Final = "laststest"

View File

@@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
STATE_UNKNOWN,
UnitOfApparentPower,
UnitOfElectricCurrent,
UnitOfElectricPotential,
@@ -25,7 +26,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .const import DOMAIN, LASTSTEST
from .coordinator import APCUPSdCoordinator
PARALLEL_UPDATES = 0
@@ -156,8 +157,8 @@ SENSORS: dict[str, SensorEntityDescription] = {
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
"laststest": SensorEntityDescription(
key="laststest",
LASTSTEST: SensorEntityDescription(
key=LASTSTEST,
translation_key="last_self_test",
),
"lastxfer": SensorEntityDescription(
@@ -417,7 +418,12 @@ async def async_setup_entry(
available_resources: set[str] = {k.lower() for k, _ in coordinator.data.items()}
entities = []
for resource in available_resources:
# "laststest" is a special sensor that only appears when the APC UPS daemon has done a
# periodical (or manual) self test since last daemon restart. It might not be available
# when we set up the integration, and we do not know if it would ever be available. Here we
# add it anyway and mark it as unknown initially.
for resource in available_resources | {LASTSTEST}:
if resource not in SENSORS:
_LOGGER.warning("Invalid resource from APCUPSd: %s", resource.upper())
continue
@@ -473,6 +479,14 @@ class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
def _update_attrs(self) -> None:
"""Update sensor attributes based on coordinator data."""
key = self.entity_description.key.upper()
# For most sensors the key will always be available for each refresh. However, some sensors
# (e.g., "laststest") will only appear after certain event occurs (e.g., a self test is
# performed) and may disappear again after certain event. So we mark the state as "unknown"
# when it becomes unknown after such events.
if key not in self.coordinator.data:
self._attr_native_value = STATE_UNKNOWN
return
self._attr_native_value, inferred_unit = infer_unit(self.coordinator.data[key])
if not self.native_unit_of_measurement:
self._attr_native_unit_of_measurement = inferred_unit

View File

@@ -118,7 +118,7 @@ class APICoreStateView(HomeAssistantView):
Home Assistant core is running. Its primary use case is for supervisor
to check if Home Assistant is running.
"""
hass: HomeAssistant = request.app[KEY_HASS]
hass = request.app[KEY_HASS]
migration = recorder.async_migration_in_progress(hass)
live = recorder.async_migration_is_live(hass)
recorder_state = {"migration_in_progress": migration, "migration_is_live": live}
@@ -390,6 +390,27 @@ class APIDomainServicesView(HomeAssistantView):
)
context = self.context(request)
if not hass.services.has_service(domain, service):
raise HTTPBadRequest from ServiceNotFound(domain, service)
if response_requested := "return_response" in request.query:
if (
hass.services.supports_response(domain, service)
is ha.SupportsResponse.NONE
):
return self.json_message(
"Service does not support responses. Remove return_response from request.",
HTTPStatus.BAD_REQUEST,
)
elif (
hass.services.supports_response(domain, service) is ha.SupportsResponse.ONLY
):
return self.json_message(
"Service call requires responses but caller did not ask for responses. "
"Add ?return_response to query parameters.",
HTTPStatus.BAD_REQUEST,
)
changed_states: list[json_fragment] = []
@ha.callback
@@ -406,13 +427,14 @@ class APIDomainServicesView(HomeAssistantView):
try:
# shield the service call from cancellation on connection drop
await shield(
response = await shield(
hass.services.async_call(
domain,
service,
data, # type: ignore[arg-type]
blocking=True,
context=context,
return_response=response_requested,
)
)
except (vol.Invalid, ServiceNotFound) as ex:
@@ -420,6 +442,11 @@ class APIDomainServicesView(HomeAssistantView):
finally:
cancel_listen()
if response_requested:
return self.json(
{"changed_states": changed_states, "service_response": response}
)
return self.json(changed_states)

View File

@@ -60,6 +60,7 @@ AUTH_EXCEPTIONS = (
exceptions.NoCredentialsError,
)
CONNECTION_TIMEOUT_EXCEPTIONS = (
OSError,
asyncio.CancelledError,
TimeoutError,
exceptions.ConnectionLostError,

View File

@@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
from .const import DEFAULT_PORT
from .coordinator import ApSystemsDataCoordinator
PLATFORMS: list[Platform] = [Platform.NUMBER, Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]
@dataclass

View File

@@ -20,18 +20,43 @@
},
"entity": {
"sensor": {
"total_power": { "name": "Total power" },
"total_power_p1": { "name": "Power of P1" },
"total_power_p2": { "name": "Power of P2" },
"lifetime_production": { "name": "Total lifetime production" },
"lifetime_production_p1": { "name": "Lifetime production of P1" },
"lifetime_production_p2": { "name": "Lifetime production of P2" },
"today_production": { "name": "Production of today" },
"today_production_p1": { "name": "Production of today from P1" },
"today_production_p2": { "name": "Production of today from P2" }
"total_power": {
"name": "Total power"
},
"total_power_p1": {
"name": "Power of P1"
},
"total_power_p2": {
"name": "Power of P2"
},
"lifetime_production": {
"name": "Total lifetime production"
},
"lifetime_production_p1": {
"name": "Lifetime production of P1"
},
"lifetime_production_p2": {
"name": "Lifetime production of P2"
},
"today_production": {
"name": "Production of today"
},
"today_production_p1": {
"name": "Production of today from P1"
},
"today_production_p2": {
"name": "Production of today from P2"
}
},
"number": {
"max_output": { "name": "Max output" }
"max_output": {
"name": "Max output"
}
},
"switch": {
"inverter_status": {
"name": "Inverter status"
}
}
}
}

View File

@@ -0,0 +1,56 @@
"""The power switch which can be toggled via the APsystems local API integration."""
from __future__ import annotations
from typing import Any
from aiohttp.client_exceptions import ClientConnectionError
from APsystemsEZ1 import Status
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ApSystemsConfigEntry, ApSystemsData
from .entity import ApSystemsEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ApSystemsConfigEntry,
add_entities: AddEntitiesCallback,
) -> None:
"""Set up the switch platform."""
add_entities([ApSystemsInverterSwitch(config_entry.runtime_data)], True)
class ApSystemsInverterSwitch(ApSystemsEntity, SwitchEntity):
"""The switch class for APSystems switches."""
_attr_device_class = SwitchDeviceClass.SWITCH
_attr_translation_key = "inverter_status"
def __init__(self, data: ApSystemsData) -> None:
"""Initialize the switch."""
super().__init__(data)
self._api = data.coordinator.api
self._attr_unique_id = f"{data.device_id}_inverter_status"
async def async_update(self) -> None:
"""Update switch status and availability."""
try:
status = await self._api.get_device_power_status()
except (TimeoutError, ClientConnectionError):
self._attr_available = False
else:
self._attr_available = True
self._attr_is_on = status == Status.normal
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self._api.set_device_power_status(0)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self._api.set_device_power_status(1)

View File

@@ -19,7 +19,10 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
super().__init__(coordinator)
self._unit = unit
self._device_model = f"ASIN AQUA {self._unit.type}"
if self._unit.type == "Remote":
self._device_model = "ASIN Pool"
else:
self._device_model = f"ASIN AQUA {self._unit.type}"
self._device_name = self._unit.name if self._unit.name else self._device_model
self._attr_device_info = DeviceInfo(

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
"iot_class": "cloud_polling",
"loggers": ["aioaseko"],
"requirements": ["aioaseko==0.1.1"]
"requirements": ["aioaseko==0.2.0"]
}

View File

@@ -16,6 +16,10 @@ from .const import (
DATA_LAST_WAKE_UP,
DOMAIN,
EVENT_RECORDING,
SAMPLE_CHANNELS,
SAMPLE_RATE,
SAMPLE_WIDTH,
SAMPLES_PER_CHUNK,
)
from .error import PipelineNotFound
from .pipeline import (
@@ -53,6 +57,10 @@ __all__ = (
"PipelineNotFound",
"WakeWordSettings",
"EVENT_RECORDING",
"SAMPLES_PER_CHUNK",
"SAMPLE_RATE",
"SAMPLE_WIDTH",
"SAMPLE_CHANNELS",
)
CONFIG_SCHEMA = vol.Schema(

View File

@@ -0,0 +1,72 @@
"""Audio enhancement for Assist."""
from abc import ABC, abstractmethod
from dataclasses import dataclass
import logging
from pymicro_vad import MicroVad
from .const import BYTES_PER_CHUNK
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True, slots=True)
class EnhancedAudioChunk:
"""Enhanced audio chunk and metadata."""
audio: bytes
"""Raw PCM audio @ 16Khz with 16-bit mono samples"""
timestamp_ms: int
"""Timestamp relative to start of audio stream (milliseconds)"""
is_speech: bool | None
"""True if audio chunk likely contains speech, False if not, None if unknown"""
class AudioEnhancer(ABC):
"""Base class for audio enhancement."""
def __init__(
self, auto_gain: int, noise_suppression: int, is_vad_enabled: bool
) -> None:
"""Initialize audio enhancer."""
self.auto_gain = auto_gain
self.noise_suppression = noise_suppression
self.is_vad_enabled = is_vad_enabled
@abstractmethod
def enhance_chunk(self, audio: bytes, timestamp_ms: int) -> EnhancedAudioChunk:
"""Enhance chunk of PCM audio @ 16Khz with 16-bit mono samples."""
class MicroVadEnhancer(AudioEnhancer):
"""Audio enhancer that just runs microVAD."""
def __init__(
self, auto_gain: int, noise_suppression: int, is_vad_enabled: bool
) -> None:
"""Initialize audio enhancer."""
super().__init__(auto_gain, noise_suppression, is_vad_enabled)
self.vad: MicroVad | None = None
self.threshold = 0.5
if self.is_vad_enabled:
self.vad = MicroVad()
_LOGGER.debug("Initialized microVAD with threshold=%s", self.threshold)
def enhance_chunk(self, audio: bytes, timestamp_ms: int) -> EnhancedAudioChunk:
"""Enhance 10ms chunk of PCM audio @ 16Khz with 16-bit mono samples."""
is_speech: bool | None = None
if self.vad is not None:
# Run VAD
assert len(audio) == BYTES_PER_CHUNK
speech_prob = self.vad.Process10ms(audio)
is_speech = speech_prob > self.threshold
return EnhancedAudioChunk(
audio=audio, timestamp_ms=timestamp_ms, is_speech=is_speech
)

View File

@@ -15,3 +15,10 @@ DATA_LAST_WAKE_UP = f"{DOMAIN}.last_wake_up"
WAKE_WORD_COOLDOWN = 2 # seconds
EVENT_RECORDING = f"{DOMAIN}_recording"
SAMPLE_RATE = 16000 # hertz
SAMPLE_WIDTH = 2 # bytes
SAMPLE_CHANNELS = 1 # mono
MS_PER_CHUNK = 10
SAMPLES_PER_CHUNK = SAMPLE_RATE // (1000 // MS_PER_CHUNK) # 10 ms @ 16Khz
BYTES_PER_CHUNK = SAMPLES_PER_CHUNK * SAMPLE_WIDTH * SAMPLE_CHANNELS # 16-bit

View File

@@ -4,7 +4,8 @@
"codeowners": ["@balloob", "@synesthesiam"],
"dependencies": ["conversation", "stt", "tts", "wake_word"],
"documentation": "https://www.home-assistant.io/integrations/assist_pipeline",
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["webrtc-noise-gain==1.2.3"]
"requirements": ["pymicro-vad==1.0.1"]
}

View File

@@ -13,14 +13,11 @@ from pathlib import Path
from queue import Empty, Queue
from threading import Thread
import time
from typing import TYPE_CHECKING, Any, Final, Literal, cast
from typing import Any, Literal, cast
import wave
import voluptuous as vol
if TYPE_CHECKING:
from webrtc_noise_gain import AudioProcessor
from homeassistant.components import (
conversation,
media_source,
@@ -52,12 +49,19 @@ from homeassistant.util import (
)
from homeassistant.util.limited_size_dict import LimitedSizeDict
from .audio_enhancer import AudioEnhancer, EnhancedAudioChunk, MicroVadEnhancer
from .const import (
BYTES_PER_CHUNK,
CONF_DEBUG_RECORDING_DIR,
DATA_CONFIG,
DATA_LAST_WAKE_UP,
DATA_MIGRATIONS,
DOMAIN,
MS_PER_CHUNK,
SAMPLE_CHANNELS,
SAMPLE_RATE,
SAMPLE_WIDTH,
SAMPLES_PER_CHUNK,
WAKE_WORD_COOLDOWN,
)
from .error import (
@@ -111,9 +115,6 @@ STORED_PIPELINE_RUNS = 10
SAVE_DELAY = 10
AUDIO_PROCESSOR_SAMPLES: Final = 160 # 10 ms @ 16 Khz
AUDIO_PROCESSOR_BYTES: Final = AUDIO_PROCESSOR_SAMPLES * 2 # 16-bit samples
@callback
def _async_resolve_default_pipeline_settings(
@@ -503,8 +504,8 @@ class AudioSettings:
is_vad_enabled: bool = True
"""True if VAD is used to determine the end of the voice command."""
is_chunking_enabled: bool = True
"""True if audio is automatically split into 10 ms chunks (required for VAD, etc.)"""
silence_seconds: float = 0.5
"""Seconds of silence after voice command has ended."""
def __post_init__(self) -> None:
"""Verify settings post-initialization."""
@@ -514,9 +515,6 @@ class AudioSettings:
if (self.auto_gain_dbfs < 0) or (self.auto_gain_dbfs > 31):
raise ValueError("auto_gain_dbfs must be in [0, 31]")
if self.needs_processor and (not self.is_chunking_enabled):
raise ValueError("Chunking must be enabled for audio processing")
@property
def needs_processor(self) -> bool:
"""True if an audio processor is needed."""
@@ -527,20 +525,6 @@ class AudioSettings:
)
@dataclass(frozen=True, slots=True)
class ProcessedAudioChunk:
"""Processed audio chunk and metadata."""
audio: bytes
"""Raw PCM audio @ 16Khz with 16-bit mono samples"""
timestamp_ms: int
"""Timestamp relative to start of audio stream (milliseconds)"""
is_speech: bool | None
"""True if audio chunk likely contains speech, False if not, None if unknown"""
@dataclass
class PipelineRun:
"""Running context for a pipeline."""
@@ -573,10 +557,12 @@ class PipelineRun:
debug_recording_queue: Queue[str | bytes | None] | None = None
"""Queue to communicate with debug recording thread"""
audio_processor: AudioProcessor | None = None
audio_enhancer: AudioEnhancer | None = None
"""VAD/noise suppression/auto gain"""
audio_processor_buffer: AudioBuffer = field(init=False, repr=False)
audio_chunking_buffer: AudioBuffer = field(
default_factory=lambda: AudioBuffer(BYTES_PER_CHUNK)
)
"""Buffer used when splitting audio into chunks for audio processing"""
_device_id: str | None = None
@@ -601,17 +587,12 @@ class PipelineRun:
pipeline_data.pipeline_runs.add_run(self)
# Initialize with audio settings
self.audio_processor_buffer = AudioBuffer(AUDIO_PROCESSOR_BYTES)
if self.audio_settings.needs_processor:
# Delay import of webrtc so HA start up is not crashing
# on older architectures (armhf).
#
# pylint: disable=import-outside-toplevel
from webrtc_noise_gain import AudioProcessor
self.audio_processor = AudioProcessor(
if self.audio_settings.needs_processor and (self.audio_enhancer is None):
# Default audio enhancer
self.audio_enhancer = MicroVadEnhancer(
self.audio_settings.auto_gain_dbfs,
self.audio_settings.noise_suppression_level,
self.audio_settings.is_vad_enabled,
)
def __eq__(self, other: object) -> bool:
@@ -688,8 +669,8 @@ class PipelineRun:
async def wake_word_detection(
self,
stream: AsyncIterable[ProcessedAudioChunk],
audio_chunks_for_stt: list[ProcessedAudioChunk],
stream: AsyncIterable[EnhancedAudioChunk],
audio_chunks_for_stt: list[EnhancedAudioChunk],
) -> wake_word.DetectionResult | None:
"""Run wake-word-detection portion of pipeline. Returns detection result."""
metadata_dict = asdict(
@@ -732,10 +713,11 @@ class PipelineRun:
# Audio chunk buffer. This audio will be forwarded to speech-to-text
# after wake-word-detection.
num_audio_chunks_to_buffer = int(
(wake_word_settings.audio_seconds_to_buffer * 16000)
/ AUDIO_PROCESSOR_SAMPLES
(wake_word_settings.audio_seconds_to_buffer * SAMPLE_RATE)
/ SAMPLES_PER_CHUNK
)
stt_audio_buffer: deque[ProcessedAudioChunk] | None = None
stt_audio_buffer: deque[EnhancedAudioChunk] | None = None
if num_audio_chunks_to_buffer > 0:
stt_audio_buffer = deque(maxlen=num_audio_chunks_to_buffer)
@@ -797,7 +779,7 @@ class PipelineRun:
# speech-to-text so the user does not have to pause before
# speaking the voice command.
audio_chunks_for_stt.extend(
ProcessedAudioChunk(
EnhancedAudioChunk(
audio=chunk_ts[0], timestamp_ms=chunk_ts[1], is_speech=False
)
for chunk_ts in result.queued_audio
@@ -819,18 +801,17 @@ class PipelineRun:
async def _wake_word_audio_stream(
self,
audio_stream: AsyncIterable[ProcessedAudioChunk],
stt_audio_buffer: deque[ProcessedAudioChunk] | None,
audio_stream: AsyncIterable[EnhancedAudioChunk],
stt_audio_buffer: deque[EnhancedAudioChunk] | None,
wake_word_vad: VoiceActivityTimeout | None,
sample_rate: int = 16000,
sample_width: int = 2,
sample_rate: int = SAMPLE_RATE,
sample_width: int = SAMPLE_WIDTH,
) -> AsyncIterable[tuple[bytes, int]]:
"""Yield audio chunks with timestamps (milliseconds since start of stream).
Adds audio to a ring buffer that will be forwarded to speech-to-text after
detection. Times out if VAD detects enough silence.
"""
chunk_seconds = AUDIO_PROCESSOR_SAMPLES / sample_rate
async for chunk in audio_stream:
if self.abort_wake_word_detection:
raise WakeWordDetectionAborted
@@ -845,6 +826,7 @@ class PipelineRun:
stt_audio_buffer.append(chunk)
if wake_word_vad is not None:
chunk_seconds = (len(chunk.audio) // sample_width) / sample_rate
if not wake_word_vad.process(chunk_seconds, chunk.is_speech):
raise WakeWordTimeoutError(
code="wake-word-timeout", message="Wake word was not detected"
@@ -881,7 +863,7 @@ class PipelineRun:
async def speech_to_text(
self,
metadata: stt.SpeechMetadata,
stream: AsyncIterable[ProcessedAudioChunk],
stream: AsyncIterable[EnhancedAudioChunk],
) -> str:
"""Run speech-to-text portion of pipeline. Returns the spoken text."""
# Create a background task to prepare the conversation agent
@@ -916,7 +898,9 @@ class PipelineRun:
# Transcribe audio stream
stt_vad: VoiceCommandSegmenter | None = None
if self.audio_settings.is_vad_enabled:
stt_vad = VoiceCommandSegmenter()
stt_vad = VoiceCommandSegmenter(
silence_seconds=self.audio_settings.silence_seconds
)
result = await self.stt_provider.async_process_audio_stream(
metadata,
@@ -957,18 +941,18 @@ class PipelineRun:
async def _speech_to_text_stream(
self,
audio_stream: AsyncIterable[ProcessedAudioChunk],
audio_stream: AsyncIterable[EnhancedAudioChunk],
stt_vad: VoiceCommandSegmenter | None,
sample_rate: int = 16000,
sample_width: int = 2,
sample_rate: int = SAMPLE_RATE,
sample_width: int = SAMPLE_WIDTH,
) -> AsyncGenerator[bytes]:
"""Yield audio chunks until VAD detects silence or speech-to-text completes."""
chunk_seconds = AUDIO_PROCESSOR_SAMPLES / sample_rate
sent_vad_start = False
async for chunk in audio_stream:
self._capture_chunk(chunk.audio)
if stt_vad is not None:
chunk_seconds = (len(chunk.audio) // sample_width) / sample_rate
if not stt_vad.process(chunk_seconds, chunk.is_speech):
# Silence detected at the end of voice command
self.process_event(
@@ -1072,8 +1056,8 @@ class PipelineRun:
tts_options[tts.ATTR_PREFERRED_FORMAT] = self.tts_audio_output
if self.tts_audio_output == "wav":
# 16 Khz, 16-bit mono
tts_options[tts.ATTR_PREFERRED_SAMPLE_RATE] = 16000
tts_options[tts.ATTR_PREFERRED_SAMPLE_CHANNELS] = 1
tts_options[tts.ATTR_PREFERRED_SAMPLE_RATE] = SAMPLE_RATE
tts_options[tts.ATTR_PREFERRED_SAMPLE_CHANNELS] = SAMPLE_CHANNELS
try:
options_supported = await tts.async_support_options(
@@ -1218,53 +1202,31 @@ class PipelineRun:
self.debug_recording_thread = None
async def process_volume_only(
self,
audio_stream: AsyncIterable[bytes],
sample_rate: int = 16000,
sample_width: int = 2,
) -> AsyncGenerator[ProcessedAudioChunk]:
self, audio_stream: AsyncIterable[bytes]
) -> AsyncGenerator[EnhancedAudioChunk]:
"""Apply volume transformation only (no VAD/audio enhancements) with optional chunking."""
ms_per_sample = sample_rate // 1000
ms_per_chunk = (AUDIO_PROCESSOR_SAMPLES // sample_width) // ms_per_sample
timestamp_ms = 0
async for chunk in audio_stream:
if self.audio_settings.volume_multiplier != 1.0:
chunk = _multiply_volume(chunk, self.audio_settings.volume_multiplier)
if self.audio_settings.is_chunking_enabled:
# 10 ms chunking
for chunk_10ms in chunk_samples(
chunk, AUDIO_PROCESSOR_BYTES, self.audio_processor_buffer
):
yield ProcessedAudioChunk(
audio=chunk_10ms,
timestamp_ms=timestamp_ms,
is_speech=None, # no VAD
)
timestamp_ms += ms_per_chunk
else:
# No chunking
yield ProcessedAudioChunk(
audio=chunk,
for sub_chunk in chunk_samples(
chunk, BYTES_PER_CHUNK, self.audio_chunking_buffer
):
yield EnhancedAudioChunk(
audio=sub_chunk,
timestamp_ms=timestamp_ms,
is_speech=None, # no VAD
)
timestamp_ms += (len(chunk) // sample_width) // ms_per_sample
timestamp_ms += MS_PER_CHUNK
async def process_enhance_audio(
self,
audio_stream: AsyncIterable[bytes],
sample_rate: int = 16000,
sample_width: int = 2,
) -> AsyncGenerator[ProcessedAudioChunk]:
"""Split audio into 10 ms chunks and apply VAD/noise suppression/auto gain/volume transformation."""
assert self.audio_processor is not None
self, audio_stream: AsyncIterable[bytes]
) -> AsyncGenerator[EnhancedAudioChunk]:
"""Split audio into chunks and apply VAD/noise suppression/auto gain/volume transformation."""
assert self.audio_enhancer is not None
ms_per_sample = sample_rate // 1000
ms_per_chunk = (AUDIO_PROCESSOR_SAMPLES // sample_width) // ms_per_sample
timestamp_ms = 0
async for dirty_samples in audio_stream:
if self.audio_settings.volume_multiplier != 1.0:
# Static gain
@@ -1272,18 +1234,12 @@ class PipelineRun:
dirty_samples, self.audio_settings.volume_multiplier
)
# Split into 10ms chunks for audio enhancements/VAD
for dirty_10ms_chunk in chunk_samples(
dirty_samples, AUDIO_PROCESSOR_BYTES, self.audio_processor_buffer
# Split into chunks for audio enhancements/VAD
for dirty_chunk in chunk_samples(
dirty_samples, BYTES_PER_CHUNK, self.audio_chunking_buffer
):
ap_result = self.audio_processor.Process10ms(dirty_10ms_chunk)
yield ProcessedAudioChunk(
audio=ap_result.audio,
timestamp_ms=timestamp_ms,
is_speech=ap_result.is_speech,
)
timestamp_ms += ms_per_chunk
yield self.audio_enhancer.enhance_chunk(dirty_chunk, timestamp_ms)
timestamp_ms += MS_PER_CHUNK
def _multiply_volume(chunk: bytes, volume_multiplier: float) -> bytes:
@@ -1323,9 +1279,9 @@ def _pipeline_debug_recording_thread_proc(
wav_path = run_recording_dir / f"{message}.wav"
wav_writer = wave.open(str(wav_path), "wb")
wav_writer.setframerate(16000)
wav_writer.setsampwidth(2)
wav_writer.setnchannels(1)
wav_writer.setframerate(SAMPLE_RATE)
wav_writer.setsampwidth(SAMPLE_WIDTH)
wav_writer.setnchannels(SAMPLE_CHANNELS)
elif isinstance(message, bytes):
# Chunk of 16-bit mono audio at 16Khz
if wav_writer is not None:
@@ -1368,8 +1324,8 @@ class PipelineInput:
"""Run pipeline."""
self.run.start(device_id=self.device_id)
current_stage: PipelineStage | None = self.run.start_stage
stt_audio_buffer: list[ProcessedAudioChunk] = []
stt_processed_stream: AsyncIterable[ProcessedAudioChunk] | None = None
stt_audio_buffer: list[EnhancedAudioChunk] = []
stt_processed_stream: AsyncIterable[EnhancedAudioChunk] | None = None
if self.stt_stream is not None:
if self.run.audio_settings.needs_processor:
@@ -1423,7 +1379,7 @@ class PipelineInput:
# Send audio in the buffer first to speech-to-text, then move on to stt_stream.
# This is basically an async itertools.chain.
async def buffer_then_audio_stream() -> (
AsyncGenerator[ProcessedAudioChunk]
AsyncGenerator[EnhancedAudioChunk]
):
# Buffered audio
for chunk in stt_audio_buffer:

View File

@@ -2,12 +2,11 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Iterable
from collections.abc import Callable, Iterable
from dataclasses import dataclass
from enum import StrEnum
import logging
from typing import Final, cast
from typing import Final
_LOGGER = logging.getLogger(__name__)
@@ -35,44 +34,6 @@ class VadSensitivity(StrEnum):
return 1.0
class VoiceActivityDetector(ABC):
"""Base class for voice activity detectors (VAD)."""
@abstractmethod
def is_speech(self, chunk: bytes) -> bool:
"""Return True if audio chunk contains speech."""
@property
@abstractmethod
def samples_per_chunk(self) -> int | None:
"""Return number of samples per chunk or None if chunking is not required."""
class WebRtcVad(VoiceActivityDetector):
"""Voice activity detector based on webrtc."""
def __init__(self) -> None:
"""Initialize webrtcvad."""
# Delay import of webrtc so HA start up is not crashing
# on older architectures (armhf).
#
# pylint: disable=import-outside-toplevel
from webrtc_noise_gain import AudioProcessor
# Just VAD: no noise suppression or auto gain
self._audio_processor = AudioProcessor(0, 0)
def is_speech(self, chunk: bytes) -> bool:
"""Return True if audio chunk contains speech."""
result = self._audio_processor.Process10ms(chunk)
return cast(bool, result.is_speech)
@property
def samples_per_chunk(self) -> int | None:
"""Return 10 ms."""
return int(0.01 * _SAMPLE_RATE) # 10 ms
class AudioBuffer:
"""Fixed-sized audio buffer with variable internal length."""
@@ -119,7 +80,7 @@ class VoiceCommandSegmenter:
speech_seconds: float = 0.3
"""Seconds of speech before voice command has started."""
silence_seconds: float = 0.5
silence_seconds: float = 1.0
"""Seconds of silence after voice command has ended."""
timeout_seconds: float = 15.0
@@ -176,29 +137,38 @@ class VoiceCommandSegmenter:
if self._speech_seconds_left <= 0:
# Inside voice command
self.in_command = True
self._silence_seconds_left = self.silence_seconds
_LOGGER.debug("Voice command started")
else:
# Reset if enough silence
self._reset_seconds_left -= chunk_seconds
if self._reset_seconds_left <= 0:
self._speech_seconds_left = self.speech_seconds
self._reset_seconds_left = self.reset_seconds
elif not is_speech:
# Silence in command
self._reset_seconds_left = self.reset_seconds
self._silence_seconds_left -= chunk_seconds
if self._silence_seconds_left <= 0:
# Command finished successfully
self.reset()
_LOGGER.debug("Voice command finished")
return False
else:
# Reset if enough speech
# Speech in command.
# Reset silence counter if enough speech.
self._reset_seconds_left -= chunk_seconds
if self._reset_seconds_left <= 0:
self._silence_seconds_left = self.silence_seconds
self._reset_seconds_left = self.reset_seconds
return True
def process_with_vad(
self,
chunk: bytes,
vad: VoiceActivityDetector,
vad_samples_per_chunk: int | None,
vad_is_speech: Callable[[bytes], bool],
leftover_chunk_buffer: AudioBuffer | None,
) -> bool:
"""Process an audio chunk using an external VAD.
@@ -207,20 +177,20 @@ class VoiceCommandSegmenter:
Returns False when voice command is finished.
"""
if vad.samples_per_chunk is None:
if vad_samples_per_chunk is None:
# No chunking
chunk_seconds = (len(chunk) // _SAMPLE_WIDTH) / _SAMPLE_RATE
is_speech = vad.is_speech(chunk)
is_speech = vad_is_speech(chunk)
return self.process(chunk_seconds, is_speech)
if leftover_chunk_buffer is None:
raise ValueError("leftover_chunk_buffer is required when vad uses chunking")
# With chunking
seconds_per_chunk = vad.samples_per_chunk / _SAMPLE_RATE
bytes_per_chunk = vad.samples_per_chunk * _SAMPLE_WIDTH
seconds_per_chunk = vad_samples_per_chunk / _SAMPLE_RATE
bytes_per_chunk = vad_samples_per_chunk * _SAMPLE_WIDTH
for vad_chunk in chunk_samples(chunk, bytes_per_chunk, leftover_chunk_buffer):
is_speech = vad.is_speech(vad_chunk)
is_speech = vad_is_speech(vad_chunk)
if not self.process(seconds_per_chunk, is_speech):
return False

View File

@@ -24,6 +24,9 @@ from .const import (
DEFAULT_WAKE_WORD_TIMEOUT,
DOMAIN,
EVENT_RECORDING,
SAMPLE_CHANNELS,
SAMPLE_RATE,
SAMPLE_WIDTH,
)
from .error import PipelineNotFound
from .pipeline import (
@@ -92,7 +95,6 @@ def async_register_websocket_api(hass: HomeAssistant) -> None:
vol.Optional("volume_multiplier"): float,
# Advanced use cases/testing
vol.Optional("no_vad"): bool,
vol.Optional("no_chunking"): bool,
}
},
extra=vol.ALLOW_EXTRA,
@@ -170,9 +172,14 @@ async def websocket_run(
# Yield until we receive an empty chunk
while chunk := await audio_queue.get():
if incoming_sample_rate != 16000:
if incoming_sample_rate != SAMPLE_RATE:
chunk, state = audioop.ratecv(
chunk, 2, 1, incoming_sample_rate, 16000, state
chunk,
SAMPLE_WIDTH,
SAMPLE_CHANNELS,
incoming_sample_rate,
SAMPLE_RATE,
state,
)
yield chunk
@@ -206,7 +213,6 @@ async def websocket_run(
auto_gain_dbfs=msg_input.get("auto_gain_dbfs", 0),
volume_multiplier=msg_input.get("volume_multiplier", 1.0),
is_vad_enabled=not msg_input.get("no_vad", False),
is_chunking_enabled=not msg_input.get("no_chunking", False),
)
elif start_stage == PipelineStage.INTENT:
# Input to conversation agent
@@ -424,9 +430,9 @@ def websocket_list_languages(
connection.send_result(
msg["id"],
{
"languages": sorted(pipeline_languages)
if pipeline_languages
else pipeline_languages
"languages": (
sorted(pipeline_languages) if pipeline_languages else pipeline_languages
)
},
)

View File

@@ -16,8 +16,6 @@ NOTIFICATION_TITLE = "August"
MANUFACTURER = "August Home Inc."
DEFAULT_AUGUST_CONFIG_FILE = ".august.conf"
DEFAULT_NAME = "August"
DOMAIN = "august"

View File

@@ -7,7 +7,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN as AXIS_DOMAIN, PLATFORMS
from .const import PLATFORMS
from .errors import AuthenticationRequired, CannotConnect
from .hub import AxisHub, get_axis_api
@@ -18,8 +18,6 @@ type AxisConfigEntry = ConfigEntry[AxisHub]
async def async_setup_entry(hass: HomeAssistant, config_entry: AxisConfigEntry) -> bool:
"""Set up the Axis integration."""
hass.data.setdefault(AXIS_DOMAIN, {})
try:
api = await get_axis_api(hass, config_entry.data)
except CannotConnect as err:

View File

@@ -135,6 +135,15 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN):
except AddressValueError:
return self.async_abort(reason="ipv6_address")
# Check connection to ensure valid address is received
self._client = MozartClient(self._host)
async with self._client:
try:
await self._client.get_beolink_self(_request_timeout=3)
except (ClientConnectorError, TimeoutError):
return self.async_abort(reason="invalid_address")
self._model = discovery_info.hostname[:-16].replace("-", " ")
self._serial_number = discovery_info.properties[ATTR_SERIAL_NUMBER]
self._beolink_jid = f"{discovery_info.properties[ATTR_TYPE_NUMBER]}.{discovery_info.properties[ATTR_ITEM_NUMBER]}.{self._serial_number}@products.bang-olufsen.com"

View File

@@ -245,14 +245,36 @@ async def fetch_blueprint_from_website_url(
return ImportedBlueprint(suggested_filename, raw_yaml, blueprint)
async def fetch_blueprint_from_generic_url(
hass: HomeAssistant, url: str
) -> ImportedBlueprint:
"""Get a blueprint from a generic website."""
session = aiohttp_client.async_get_clientsession(hass)
resp = await session.get(url, raise_for_status=True)
raw_yaml = await resp.text()
data = yaml.parse_yaml(raw_yaml)
assert isinstance(data, dict)
blueprint = Blueprint(data)
parsed_import_url = yarl.URL(url)
suggested_filename = f"{parsed_import_url.host}/{parsed_import_url.parts[-1][:-5]}"
return ImportedBlueprint(suggested_filename, raw_yaml, blueprint)
FETCH_FUNCTIONS = (
fetch_blueprint_from_community_post,
fetch_blueprint_from_github_url,
fetch_blueprint_from_github_gist_url,
fetch_blueprint_from_website_url,
fetch_blueprint_from_generic_url,
)
async def fetch_blueprint_from_url(hass: HomeAssistant, url: str) -> ImportedBlueprint:
"""Get a blueprint from a url."""
for func in (
fetch_blueprint_from_community_post,
fetch_blueprint_from_github_url,
fetch_blueprint_from_github_gist_url,
fetch_blueprint_from_website_url,
):
for func in FETCH_FUNCTIONS:
with suppress(UnsupportedUrl):
imported_bp = await func(hass, url)
imported_bp.blueprint.update_metadata(source_url=url)

View File

@@ -21,7 +21,7 @@ from homeassistant.const import (
CONF_PATH,
__version__,
)
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import yaml
@@ -372,7 +372,7 @@ class DomainBlueprints:
shutil.copytree(
integration.file_path / BLUEPRINT_FOLDER,
self.blueprint_folder / HA_DOMAIN,
self.blueprint_folder / HOMEASSISTANT_DOMAIN,
)
await self.hass.async_add_executor_job(populate)

View File

@@ -1 +1,70 @@
"""The bluesound component."""
from dataclasses import dataclass
import aiohttp
from pyblu import Player, SyncStatus
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
from .media_player import setup_services
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = [Platform.MEDIA_PLAYER]
@dataclass
class BluesoundData:
"""Bluesound data class."""
player: Player
sync_status: SyncStatus
type BluesoundConfigEntry = ConfigEntry[BluesoundData]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Bluesound."""
if DOMAIN not in hass.data:
hass.data[DOMAIN] = []
setup_services(hass)
return True
async def async_setup_entry(
hass: HomeAssistant, config_entry: BluesoundConfigEntry
) -> bool:
"""Set up the Bluesound entry."""
host = config_entry.data[CONF_HOST]
port = config_entry.data[CONF_PORT]
session = async_get_clientsession(hass)
async with Player(host, port, session=session, default_timeout=10) as player:
try:
sync_status = await player.sync_status(timeout=1)
except TimeoutError as ex:
raise ConfigEntryNotReady(
f"Timeout while connecting to {host}:{port}"
) from ex
except aiohttp.ClientError as ex:
raise ConfigEntryNotReady(f"Error connecting to {host}:{port}") from ex
config_entry.runtime_data = BluesoundData(player, sync_status)
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@@ -0,0 +1,150 @@
"""Config flow for bluesound."""
import logging
from typing import Any
import aiohttp
from pyblu import Player, SyncStatus
import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .media_player import DEFAULT_PORT
from .utils import format_unique_id
_LOGGER = logging.getLogger(__name__)
class BluesoundConfigFlow(ConfigFlow, domain=DOMAIN):
"""Bluesound config flow."""
VERSION = 1
MINOR_VERSION = 1
def __init__(self) -> None:
"""Initialize the config flow."""
self._host: str | None = None
self._port = DEFAULT_PORT
self._sync_status: SyncStatus | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by the user."""
errors: dict[str, str] = {}
if user_input is not None:
session = async_get_clientsession(self.hass)
async with Player(
user_input[CONF_HOST], user_input[CONF_PORT], session=session
) as player:
try:
sync_status = await player.sync_status(timeout=1)
except (TimeoutError, aiohttp.ClientError):
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
format_unique_id(sync_status.mac, user_input[CONF_PORT])
)
self._abort_if_unique_id_configured(
updates={
CONF_HOST: user_input[CONF_HOST],
}
)
return self.async_create_entry(
title=sync_status.name,
data=user_input,
)
return self.async_show_form(
step_id="user",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Optional(CONF_PORT, default=11000): int,
}
),
)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Import bluesound config entry from configuration.yaml."""
session = async_get_clientsession(self.hass)
async with Player(
import_data[CONF_HOST], import_data[CONF_PORT], session=session
) as player:
try:
sync_status = await player.sync_status(timeout=1)
except (TimeoutError, aiohttp.ClientError):
return self.async_abort(reason="cannot_connect")
await self.async_set_unique_id(
format_unique_id(sync_status.mac, import_data[CONF_PORT])
)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=sync_status.name,
data=import_data,
)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle a flow initialized by zeroconf discovery."""
if discovery_info.port is not None:
self._port = discovery_info.port
session = async_get_clientsession(self.hass)
try:
async with Player(
discovery_info.host, self._port, session=session
) as player:
sync_status = await player.sync_status(timeout=1)
except (TimeoutError, aiohttp.ClientError):
return self.async_abort(reason="cannot_connect")
await self.async_set_unique_id(format_unique_id(sync_status.mac, self._port))
self._host = discovery_info.host
self._sync_status = sync_status
self._abort_if_unique_id_configured(
updates={
CONF_HOST: self._host,
}
)
self.context.update(
{
"title_placeholders": {"name": sync_status.name},
"configuration_url": f"http://{discovery_info.host}",
}
)
return await self.async_step_confirm()
async def async_step_confirm(self, user_input=None) -> ConfigFlowResult:
"""Confirm the zeroconf setup."""
assert self._sync_status is not None
assert self._host is not None
if user_input is not None:
return self.async_create_entry(
title=self._sync_status.name,
data={
CONF_HOST: self._host,
CONF_PORT: self._port,
},
)
return self.async_show_form(
step_id="confirm",
description_placeholders={
"name": self._sync_status.name,
"host": self._host,
},
)

View File

@@ -1,7 +1,10 @@
"""Constants for the Bluesound HiFi wireless speakers and audio integrations component."""
DOMAIN = "bluesound"
INTEGRATION_TITLE = "Bluesound"
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
SERVICE_JOIN = "join"
SERVICE_SET_TIMER = "set_sleep_timer"
SERVICE_UNJOIN = "unjoin"
ATTR_BLUESOUND_GROUP = "bluesound_group"
ATTR_MASTER = "master"

View File

@@ -1,8 +1,15 @@
{
"domain": "bluesound",
"name": "Bluesound",
"codeowners": ["@thrawnarn"],
"after_dependencies": ["zeroconf"],
"codeowners": ["@thrawnarn", "@LouisChrist"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling",
"requirements": ["pyblu==0.4.0"]
"requirements": ["pyblu==0.4.0"],
"zeroconf": [
{
"type": "_musc._tcp.local."
}
]
}

View File

@@ -3,11 +3,11 @@
from __future__ import annotations
import asyncio
from asyncio import CancelledError
from asyncio import CancelledError, Task
from contextlib import suppress
from datetime import datetime, timedelta
import logging
from typing import Any, NamedTuple
from typing import TYPE_CHECKING, Any, NamedTuple
from aiohttp.client_exceptions import ClientError
from pyblu import Input, Player, Preset, Status, SyncStatus
@@ -23,40 +23,50 @@ from homeassistant.components.media_player import (
MediaType,
async_process_play_media_url,
)
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
CONF_HOSTS,
CONF_NAME,
CONF_PORT,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.core import (
DOMAIN as HOMEASSISTANT_DOMAIN,
HomeAssistant,
ServiceCall,
)
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
DeviceInfo,
format_mac,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
from .const import (
ATTR_BLUESOUND_GROUP,
ATTR_MASTER,
DOMAIN,
INTEGRATION_TITLE,
SERVICE_CLEAR_TIMER,
SERVICE_JOIN,
SERVICE_SET_TIMER,
SERVICE_UNJOIN,
)
from .utils import format_unique_id
if TYPE_CHECKING:
from . import BluesoundConfigEntry
_LOGGER = logging.getLogger(__name__)
ATTR_BLUESOUND_GROUP = "bluesound_group"
ATTR_MASTER = "master"
DATA_BLUESOUND = "bluesound"
DATA_BLUESOUND = DOMAIN
DEFAULT_PORT = 11000
NODE_OFFLINE_CHECK_TIMEOUT = 180
@@ -83,6 +93,10 @@ PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
}
)
BS_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
BS_JOIN_SCHEMA = BS_SCHEMA.extend({vol.Required(ATTR_MASTER): cv.entity_id})
class ServiceMethodDetails(NamedTuple):
"""Details for SERVICE_TO_METHOD mapping."""
@@ -91,10 +105,6 @@ class ServiceMethodDetails(NamedTuple):
schema: vol.Schema
BS_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
BS_JOIN_SCHEMA = BS_SCHEMA.extend({vol.Required(ATTR_MASTER): cv.entity_id})
SERVICE_TO_METHOD = {
SERVICE_JOIN: ServiceMethodDetails(method="async_join", schema=BS_JOIN_SCHEMA),
SERVICE_UNJOIN: ServiceMethodDetails(method="async_unjoin", schema=BS_SCHEMA),
@@ -107,78 +117,51 @@ SERVICE_TO_METHOD = {
}
def _add_player(hass: HomeAssistant, async_add_entities, host, port=None, name=None):
"""Add Bluesound players."""
@callback
def _init_player(event=None):
"""Start polling."""
hass.async_create_task(player.async_init())
@callback
def _start_polling(event=None):
"""Start polling."""
player.start_polling()
@callback
def _stop_polling(event=None):
"""Stop polling."""
player.stop_polling()
@callback
def _add_player_cb():
"""Add player after first sync fetch."""
if player.id in [x.id for x in hass.data[DATA_BLUESOUND]]:
_LOGGER.warning("Player already added %s", player.id)
async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
"""Import config entry from configuration.yaml."""
if not hass.config_entries.async_entries(DOMAIN):
# Start import flow
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
if (
result["type"] == FlowResultType.ABORT
and result["reason"] == "cannot_connect"
):
ir.async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_import_issue_{result['reason']}",
breaks_in_ha_version="2025.2.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key=f"deprecated_yaml_import_issue_{result['reason']}",
translation_placeholders={
"domain": DOMAIN,
"integration_title": INTEGRATION_TITLE,
},
)
return
hass.data[DATA_BLUESOUND].append(player)
async_add_entities([player])
_LOGGER.info("Added device with name: %s", player.name)
if hass.is_running:
_start_polling()
else:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _start_polling)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_polling)
player = BluesoundPlayer(hass, host, port, name, _add_player_cb)
if hass.is_running:
_init_player()
else:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _init_player)
ir.async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2025.2.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": INTEGRATION_TITLE,
},
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Bluesound platforms."""
if DATA_BLUESOUND not in hass.data:
hass.data[DATA_BLUESOUND] = []
if discovery_info:
_add_player(
hass,
async_add_entities,
discovery_info.get(CONF_HOST),
discovery_info.get(CONF_PORT),
)
return
if hosts := config.get(CONF_HOSTS):
for host in hosts:
_add_player(
hass,
async_add_entities,
host.get(CONF_HOST),
host.get(CONF_PORT),
host.get(CONF_NAME),
)
def setup_services(hass: HomeAssistant) -> None:
"""Set up services for Bluesound component."""
async def async_service_handler(service: ServiceCall) -> None:
"""Map services to method of Bluesound devices."""
@@ -190,12 +173,10 @@ async def async_setup_platform(
}
if entity_ids := service.data.get(ATTR_ENTITY_ID):
target_players = [
player
for player in hass.data[DATA_BLUESOUND]
if player.entity_id in entity_ids
player for player in hass.data[DOMAIN] if player.entity_id in entity_ids
]
else:
target_players = hass.data[DATA_BLUESOUND]
target_players = hass.data[DOMAIN]
for player in target_players:
await getattr(player, method.method)(**params)
@@ -206,42 +187,92 @@ async def async_setup_platform(
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BluesoundConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Bluesound entry."""
bluesound_player = BluesoundPlayer(
config_entry.data[CONF_HOST],
config_entry.data[CONF_PORT],
config_entry.runtime_data.player,
config_entry.runtime_data.sync_status,
)
hass.data[DATA_BLUESOUND].append(bluesound_player)
async_add_entities([bluesound_player])
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None,
) -> None:
"""Trigger import flows."""
hosts = config.get(CONF_HOSTS, [])
for host in hosts:
import_data = {
CONF_HOST: host[CONF_HOST],
CONF_PORT: host.get(CONF_PORT, 11000),
}
hass.async_create_task(_async_import(hass, import_data))
class BluesoundPlayer(MediaPlayerEntity):
"""Representation of a Bluesound Player."""
_attr_media_content_type = MediaType.MUSIC
_attr_has_entity_name = True
_attr_name = None
def __init__(
self, hass: HomeAssistant, host, port=None, name=None, init_callback=None
self,
host: str,
port: int,
player: Player,
sync_status: SyncStatus,
) -> None:
"""Initialize the media player."""
self.host = host
self._hass = hass
self.port = port
self._polling_task = None # The actual polling task.
self._name = name
self._id = None
self._polling_task: Task[None] | None = None # The actual polling task.
self._id = sync_status.id
self._last_status_update = None
self._sync_status: SyncStatus | None = None
self._sync_status = sync_status
self._status: Status | None = None
self._inputs: list[Input] = []
self._presets: list[Preset] = []
self._is_online = False
self._retry_remove = None
self._muted = False
self._master: BluesoundPlayer | None = None
self._is_master = False
self._group_name = None
self._group_list: list[str] = []
self._bluesound_device_name = None
self._player = Player(
host, port, async_get_clientsession(hass), default_timeout=10
)
self._bluesound_device_name = sync_status.name
self._player = player
self._init_callback = init_callback
if self.port is None:
self.port = DEFAULT_PORT
self._attr_unique_id = format_unique_id(sync_status.mac, port)
# there should always be one player with the default port per mac
if port == DEFAULT_PORT:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, format_mac(sync_status.mac))},
connections={(CONNECTION_NETWORK_MAC, format_mac(sync_status.mac))},
name=sync_status.name,
manufacturer=sync_status.brand,
model=sync_status.model_name,
model_id=sync_status.model,
)
else:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, format_unique_id(sync_status.mac, port))},
name=sync_status.name,
manufacturer=sync_status.brand,
model=sync_status.model_name,
model_id=sync_status.model,
via_device=(DOMAIN, format_mac(sync_status.mac)),
)
@staticmethod
def _try_get_index(string, search_string):
@@ -251,25 +282,18 @@ class BluesoundPlayer(MediaPlayerEntity):
except ValueError:
return -1
async def force_update_sync_status(self, on_updated_cb=None) -> bool:
async def force_update_sync_status(self) -> bool:
"""Update the internal status."""
sync_status = await self._player.sync_status()
self._sync_status = sync_status
if not self._name:
self._name = sync_status.name if sync_status.name else self.host
if not self._id:
self._id = sync_status.id
if not self._bluesound_device_name:
self._bluesound_device_name = self._name
if sync_status.master is not None:
self._is_master = False
master_id = f"{sync_status.master.ip}:{sync_status.master.port}"
master_device = [
device
for device in self._hass.data[DATA_BLUESOUND]
for device in self.hass.data[DATA_BLUESOUND]
if device.id == master_id
]
@@ -284,8 +308,6 @@ class BluesoundPlayer(MediaPlayerEntity):
slaves = self._sync_status.slaves
self._is_master = slaves is not None
if on_updated_cb:
on_updated_cb()
return True
async def _start_poll_command(self):
@@ -295,7 +317,7 @@ class BluesoundPlayer(MediaPlayerEntity):
await self.async_update_status()
except (TimeoutError, ClientError):
_LOGGER.info("Node %s:%s is offline, retrying later", self.name, self.port)
_LOGGER.error("Node %s:%s is offline, retrying later", self.name, self.port)
await asyncio.sleep(NODE_OFFLINE_CHECK_TIMEOUT)
self.start_polling()
@@ -305,32 +327,21 @@ class BluesoundPlayer(MediaPlayerEntity):
_LOGGER.exception("Unexpected error in %s:%s", self.name, self.port)
raise
def start_polling(self):
async def async_added_to_hass(self) -> None:
"""Start the polling task."""
self._polling_task = self._hass.async_create_task(self._start_poll_command())
await super().async_added_to_hass()
def stop_polling(self):
self._polling_task = self.hass.async_create_task(self._start_poll_command())
async def async_will_remove_from_hass(self) -> None:
"""Stop the polling task."""
self._polling_task.cancel()
await super().async_will_remove_from_hass()
async def async_init(self, triggered=None):
"""Initialize the player async."""
try:
if self._retry_remove is not None:
self._retry_remove()
self._retry_remove = None
assert self._polling_task is not None
if self._polling_task.cancel():
await self._polling_task
await self.force_update_sync_status(self._init_callback)
except (TimeoutError, ClientError):
_LOGGER.info("Node %s:%s is offline, retrying later", self.host, self.port)
self._retry_remove = async_track_time_interval(
self._hass, self.async_init, NODE_RETRY_INITIATION
)
except Exception:
_LOGGER.exception(
"Unexpected when initiating error in %s:%s", self.host, self.port
)
raise
self.hass.data[DATA_BLUESOUND].remove(self)
async def async_update(self) -> None:
"""Update internal status of the entity."""
@@ -384,26 +395,23 @@ class BluesoundPlayer(MediaPlayerEntity):
self._last_status_update = None
self._status = None
self.async_write_ha_state()
_LOGGER.info("Client connection error, marking %s as offline", self._name)
_LOGGER.error(
"Client connection error, marking %s as offline",
self._bluesound_device_name,
)
raise
@property
def unique_id(self) -> str | None:
"""Return an unique ID."""
assert self._sync_status is not None
return f"{format_mac(self._sync_status.mac)}-{self.port}"
async def async_trigger_sync_on_all(self):
"""Trigger sync status update on all devices."""
_LOGGER.debug("Trigger sync status on all devices")
for player in self._hass.data[DATA_BLUESOUND]:
for player in self.hass.data[DATA_BLUESOUND]:
await player.force_update_sync_status()
@Throttle(SYNC_STATUS_INTERVAL)
async def async_update_sync_status(self, on_updated_cb=None):
async def async_update_sync_status(self):
"""Update sync status."""
await self.force_update_sync_status(on_updated_cb)
await self.force_update_sync_status()
@Throttle(UPDATE_CAPTURE_INTERVAL)
async def async_update_captures(self) -> list[Input] | None:
@@ -522,7 +530,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is not None:
volume = self._status.volume
if self.is_grouped and self._sync_status is not None:
if self.is_grouped:
volume = self._sync_status.volume
if volume is None:
@@ -537,7 +545,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is not None:
mute = self._status.mute
if self.is_grouped and self._sync_status is not None:
if self.is_grouped:
mute = self._sync_status.mute_volume is not None
return mute
@@ -547,11 +555,6 @@ class BluesoundPlayer(MediaPlayerEntity):
"""Get id of device."""
return self._id
@property
def name(self) -> str | None:
"""Return the name of the device."""
return self._name
@property
def bluesound_device_name(self) -> str | None:
"""Return the device name as returned by the device."""
@@ -690,7 +693,7 @@ class BluesoundPlayer(MediaPlayerEntity):
device_group = self._group_name.split("+")
sorted_entities = sorted(
self._hass.data[DATA_BLUESOUND],
self.hass.data[DATA_BLUESOUND],
key=lambda entity: entity.is_master,
reverse=True,
)

View File

@@ -1,4 +1,30 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]"
},
"data_description": {
"host": "Hostname or IP address of your Bluesound player",
"port": "Port of your Bluesound player. This is usually 11000."
}
},
"confirm": {
"title": "Discover Bluesound player",
"description": "[%key:common::config_flow::description::confirm_setup%]"
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}
},
"services": {
"join": {
"name": "Join",

View File

@@ -0,0 +1,8 @@
"""Utility functions for the Bluesound component."""
from homeassistant.helpers.device_registry import format_mac
def format_unique_id(mac: str, port: int) -> str:
"""Generate a unique ID based on the MAC address and port number."""
return f"{format_mac(mac)}-{port}"

View File

@@ -18,7 +18,7 @@
"bleak-retry-connector==3.5.0",
"bluetooth-adapters==0.19.3",
"bluetooth-auto-recovery==1.4.2",
"bluetooth-data-tools==1.19.3",
"bluetooth-data-tools==1.19.4",
"dbus-fast==2.22.1",
"habluetooth==3.1.3"
]

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from fnmatch import translate
from functools import lru_cache
@@ -173,10 +174,10 @@ class BluetoothMatcherIndexBase[
def __init__(self) -> None:
"""Initialize the matcher index."""
self.local_name: dict[str, list[_T]] = {}
self.service_uuid: dict[str, list[_T]] = {}
self.service_data_uuid: dict[str, list[_T]] = {}
self.manufacturer_id: dict[int, list[_T]] = {}
self.local_name: defaultdict[str, list[_T]] = defaultdict(list)
self.service_uuid: defaultdict[str, list[_T]] = defaultdict(list)
self.service_data_uuid: defaultdict[str, list[_T]] = defaultdict(list)
self.manufacturer_id: defaultdict[int, list[_T]] = defaultdict(list)
self.service_uuid_set: set[str] = set()
self.service_data_uuid_set: set[str] = set()
self.manufacturer_id_set: set[int] = set()
@@ -190,26 +191,22 @@ class BluetoothMatcherIndexBase[
"""
# Local name is the cheapest to match since its just a dict lookup
if LOCAL_NAME in matcher:
self.local_name.setdefault(
_local_name_to_index_key(matcher[LOCAL_NAME]), []
).append(matcher)
self.local_name[_local_name_to_index_key(matcher[LOCAL_NAME])].append(
matcher
)
return True
# Manufacturer data is 2nd cheapest since its all ints
if MANUFACTURER_ID in matcher:
self.manufacturer_id.setdefault(matcher[MANUFACTURER_ID], []).append(
matcher
)
self.manufacturer_id[matcher[MANUFACTURER_ID]].append(matcher)
return True
if SERVICE_UUID in matcher:
self.service_uuid.setdefault(matcher[SERVICE_UUID], []).append(matcher)
self.service_uuid[matcher[SERVICE_UUID]].append(matcher)
return True
if SERVICE_DATA_UUID in matcher:
self.service_data_uuid.setdefault(matcher[SERVICE_DATA_UUID], []).append(
matcher
)
self.service_data_uuid[matcher[SERVICE_DATA_UUID]].append(matcher)
return True
return False
@@ -260,32 +257,38 @@ class BluetoothMatcherIndexBase[
if ble_device_matches(matcher, service_info)
)
if self.service_data_uuid_set and service_info.service_data:
if (
(service_data_uuid_set := self.service_data_uuid_set)
and (service_data := service_info.service_data)
and (matched_uuids := service_data_uuid_set.intersection(service_data))
):
matches.extend(
matcher
for service_data_uuid in self.service_data_uuid_set.intersection(
service_info.service_data
)
for service_data_uuid in matched_uuids
for matcher in self.service_data_uuid[service_data_uuid]
if ble_device_matches(matcher, service_info)
)
if self.manufacturer_id_set and service_info.manufacturer_data:
if (
(manufacturer_id_set := self.manufacturer_id_set)
and (manufacturer_data := service_info.manufacturer_data)
and (matched_ids := manufacturer_id_set.intersection(manufacturer_data))
):
matches.extend(
matcher
for manufacturer_id in self.manufacturer_id_set.intersection(
service_info.manufacturer_data
)
for manufacturer_id in matched_ids
for matcher in self.manufacturer_id[manufacturer_id]
if ble_device_matches(matcher, service_info)
)
if self.service_uuid_set and service_info.service_uuids:
if (
(service_uuid_set := self.service_uuid_set)
and (service_uuids := service_info.service_uuids)
and (matched_uuids := service_uuid_set.intersection(service_uuids))
):
matches.extend(
matcher
for service_uuid in self.service_uuid_set.intersection(
service_info.service_uuids
)
for service_uuid in matched_uuids
for matcher in self.service_uuid[service_uuid]
if ble_device_matches(matcher, service_info)
)
@@ -310,7 +313,9 @@ class BluetoothCallbackMatcherIndex(
def __init__(self) -> None:
"""Initialize the matcher index."""
super().__init__()
self.address: dict[str, list[BluetoothCallbackMatcherWithCallback]] = {}
self.address: defaultdict[str, list[BluetoothCallbackMatcherWithCallback]] = (
defaultdict(list)
)
self.connectable: list[BluetoothCallbackMatcherWithCallback] = []
def add_callback_matcher(
@@ -323,7 +328,7 @@ class BluetoothCallbackMatcherIndex(
We put them in the bucket that they are most likely to match.
"""
if ADDRESS in matcher:
self.address.setdefault(matcher[ADDRESS], []).append(matcher)
self.address[matcher[ADDRESS]].append(matcher)
return
if super().add(matcher):

View File

@@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"quality_scale": "platinum",
"requirements": ["bimmer-connected[china]==0.15.3"]
"requirements": ["bimmer-connected[china]==0.16.1"]
}

View File

@@ -86,7 +86,8 @@
"name": "Charging Mode",
"state": {
"immediate_charging": "Immediate charging",
"delayed_charging": "Delayed charging"
"delayed_charging": "Delayed charging",
"no_action": "No action"
}
}
},

View File

@@ -4,8 +4,8 @@ from __future__ import annotations
import logging
from bring_api.bring import Bring
from bring_api.exceptions import (
from bring_api import (
Bring,
BringAuthException,
BringParseException,
BringRequestException,

View File

@@ -6,9 +6,12 @@ from collections.abc import Mapping
import logging
from typing import Any
from bring_api.bring import Bring
from bring_api.exceptions import BringAuthException, BringRequestException
from bring_api.types import BringAuthResponse
from bring_api import (
Bring,
BringAuthException,
BringAuthResponse,
BringRequestException,
)
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult

View File

@@ -5,8 +5,8 @@ from __future__ import annotations
from datetime import timedelta
import logging
from bring_api.bring import Bring
from bring_api.exceptions import (
from bring_api import (
Bring,
BringAuthException,
BringParseException,
BringRequestException,

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bring",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["bring-api==0.7.1"]
"requirements": ["bring-api==0.8.1"]
}

View File

@@ -5,8 +5,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import uuid
from bring_api.exceptions import BringRequestException
from bring_api.types import BringItem, BringItemOperation, BringNotificationType
from bring_api import (
BringItem,
BringItemOperation,
BringNotificationType,
BringRequestException,
)
import voluptuous as vol
from homeassistant.components.todo import (

View File

@@ -0,0 +1,84 @@
"""The Bryant Evolution integration."""
from __future__ import annotations
import logging
from evolutionhttp import BryantEvolutionLocalClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_FILENAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from . import names
from .const import CONF_SYSTEM_ZONE, DOMAIN
PLATFORMS: list[Platform] = [Platform.CLIMATE]
type BryantEvolutionLocalClients = dict[tuple[int, int], BryantEvolutionLocalClient]
type BryantEvolutionConfigEntry = ConfigEntry[BryantEvolutionLocalClients]
_LOGGER = logging.getLogger(__name__)
async def _can_reach_device(client: BryantEvolutionLocalClient) -> bool:
"""Return whether we can reach the device at the given filename."""
# Verify that we can read current temperature to check that the
# (filename, system, zone) is valid.
return await client.read_current_temperature() is not None
async def async_setup_entry(
hass: HomeAssistant, entry: BryantEvolutionConfigEntry
) -> bool:
"""Set up Bryant Evolution from a config entry."""
# Add a device for the SAM itself.
sam_uid = names.sam_device_uid(entry)
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, sam_uid)},
manufacturer="Bryant",
name="System Access Module",
)
# Add a device for each system.
for sys_id in (1, 2):
if not any(sz[0] == sys_id for sz in entry.data[CONF_SYSTEM_ZONE]):
_LOGGER.debug(
"Skipping system %s because it is not configured for this integration: %s",
sys_id,
entry.data[CONF_SYSTEM_ZONE],
)
continue
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, names.system_device_uid(sam_uid, sys_id))},
via_device=(DOMAIN, names.sam_device_uid(entry)),
manufacturer="Bryant",
name=f"System {sys_id}",
)
# Create a client for every zone.
entry.runtime_data = {}
for sz in entry.data[CONF_SYSTEM_ZONE]:
try:
client = await BryantEvolutionLocalClient.get_client(
sz[0], sz[1], entry.data[CONF_FILENAME]
)
if not await _can_reach_device(client):
raise ConfigEntryNotReady
entry.runtime_data[tuple(sz)] = client
except FileNotFoundError as f:
raise ConfigEntryNotReady from f
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(
hass: HomeAssistant, entry: BryantEvolutionConfigEntry
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -0,0 +1,252 @@
"""Support for Bryant Evolution HVAC systems."""
from datetime import timedelta
import logging
from typing import Any
from evolutionhttp import BryantEvolutionLocalClient
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BryantEvolutionConfigEntry, names
from .const import CONF_SYSTEM_ZONE, DOMAIN
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BryantEvolutionConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
# Add a climate entity for each system/zone.
sam_uid = names.sam_device_uid(config_entry)
entities: list[Entity] = []
for sz in config_entry.data[CONF_SYSTEM_ZONE]:
system_id = sz[0]
zone_id = sz[1]
client = config_entry.runtime_data.get(tuple(sz))
climate = BryantEvolutionClimate(
client,
system_id,
zone_id,
sam_uid,
)
entities.append(climate)
async_add_entities(entities, update_before_add=True)
class BryantEvolutionClimate(ClimateEntity):
"""ClimateEntity for Bryant Evolution HVAC systems.
Design note: this class updates using polling. However, polling
is very slow (~1500 ms / parameter). To improve the user
experience on updates, we also locally update this instance and
call async_write_ha_state as well.
"""
_attr_has_entity_name = True
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
)
_attr_hvac_modes = [
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.HEAT_COOL,
HVACMode.OFF,
]
_attr_fan_modes = ["auto", "low", "med", "high"]
_enable_turn_on_off_backwards_compatibility = False
def __init__(
self,
client: BryantEvolutionLocalClient,
system_id: int,
zone_id: int,
sam_uid: str,
) -> None:
"""Initialize an entity from parts."""
self._client = client
self._attr_name = None
self._attr_unique_id = names.zone_entity_uid(sam_uid, system_id, zone_id)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)},
manufacturer="Bryant",
via_device=(DOMAIN, names.system_device_uid(sam_uid, system_id)),
name=f"System {system_id} Zone {zone_id}",
)
async def async_update(self) -> None:
"""Update the entity state."""
self._attr_current_temperature = await self._client.read_current_temperature()
if (fan_mode := await self._client.read_fan_mode()) is not None:
self._attr_fan_mode = fan_mode.lower()
else:
self._attr_fan_mode = None
self._attr_target_temperature = None
self._attr_target_temperature_high = None
self._attr_target_temperature_low = None
self._attr_hvac_mode = await self._read_hvac_mode()
# Set target_temperature or target_temperature_{high, low} based on mode.
match self._attr_hvac_mode:
case HVACMode.HEAT:
self._attr_target_temperature = (
await self._client.read_heating_setpoint()
)
case HVACMode.COOL:
self._attr_target_temperature = (
await self._client.read_cooling_setpoint()
)
case HVACMode.HEAT_COOL:
self._attr_target_temperature_high = (
await self._client.read_cooling_setpoint()
)
self._attr_target_temperature_low = (
await self._client.read_heating_setpoint()
)
case HVACMode.OFF:
pass
case _:
_LOGGER.error("Unknown HVAC mode %s", self._attr_hvac_mode)
# Note: depends on current temperature and target temperature low read
# above.
self._attr_hvac_action = await self._read_hvac_action()
async def _read_hvac_mode(self) -> HVACMode:
mode_and_active = await self._client.read_hvac_mode()
if not mode_and_active:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_read_hvac_mode"
)
mode = mode_and_active[0]
mode_enum = {
"HEAT": HVACMode.HEAT,
"COOL": HVACMode.COOL,
"AUTO": HVACMode.HEAT_COOL,
"OFF": HVACMode.OFF,
}.get(mode.upper())
if mode_enum is None:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="failed_to_parse_hvac_mode",
translation_placeholders={"mode": mode},
)
return mode_enum
async def _read_hvac_action(self) -> HVACAction:
"""Return the current running hvac operation."""
mode_and_active = await self._client.read_hvac_mode()
if not mode_and_active:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_read_hvac_action"
)
mode, is_active = mode_and_active
if not is_active:
return HVACAction.OFF
match mode.upper():
case "HEAT":
return HVACAction.HEATING
case "COOL":
return HVACAction.COOLING
case "OFF":
return HVACAction.OFF
case "AUTO":
# In AUTO, we need to figure out what the actual action is
# based on the setpoints.
if (
self.current_temperature is not None
and self.target_temperature_low is not None
):
if self.current_temperature > self.target_temperature_low:
# If the system is on and the current temperature is
# higher than the point at which heating would activate,
# then we must be cooling.
return HVACAction.COOLING
return HVACAction.HEATING
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="failed_to_parse_hvac_mode",
translation_placeholders={
"mode_and_active": mode_and_active,
"current_temperature": str(self.current_temperature),
"target_temperature_low": str(self.target_temperature_low),
},
)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
if hvac_mode == HVACMode.HEAT_COOL:
hvac_mode = HVACMode.AUTO
if not await self._client.set_hvac_mode(hvac_mode):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_set_hvac_mode"
)
self._attr_hvac_mode = hvac_mode
self._async_write_ha_state()
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if kwargs.get("target_temp_high"):
temp = int(kwargs["target_temp_high"])
if not await self._client.set_cooling_setpoint(temp):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_set_clsp"
)
self._attr_target_temperature_high = temp
if kwargs.get("target_temp_low"):
temp = int(kwargs["target_temp_low"])
if not await self._client.set_heating_setpoint(temp):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_set_htsp"
)
self._attr_target_temperature_low = temp
if kwargs.get("temperature"):
temp = int(kwargs["temperature"])
fn = (
self._client.set_heating_setpoint
if self.hvac_mode == HVACMode.HEAT
else self._client.set_cooling_setpoint
)
if not await fn(temp):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_set_temp"
)
self._attr_target_temperature = temp
# If we get here, we must have changed something unless HA allowed an
# invalid service call (without any recognized kwarg).
self._async_write_ha_state()
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
if not await self._client.set_fan_mode(fan_mode):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="failed_to_set_fan_mode"
)
self._attr_fan_mode = fan_mode.lower()
self.async_write_ha_state()

View File

@@ -0,0 +1,87 @@
"""Config flow for Bryant Evolution integration."""
from __future__ import annotations
import logging
from typing import Any
from evolutionhttp import BryantEvolutionLocalClient
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_FILENAME
from homeassistant.helpers.typing import UNDEFINED
from .const import CONF_SYSTEM_ZONE, DOMAIN
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_FILENAME, default="/dev/ttyUSB0"): str,
}
)
async def _enumerate_sz(tty: str) -> list[tuple[int, int]]:
"""Return (system, zone) tuples for each system+zone accessible through tty."""
return [
(system_id, zone.zone_id)
for system_id in (1, 2)
for zone in await BryantEvolutionLocalClient.enumerate_zones(system_id, tty)
]
class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Bryant Evolution."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
system_zone = await _enumerate_sz(user_input[CONF_FILENAME])
except FileNotFoundError:
_LOGGER.error("Could not open %s: not found", user_input[CONF_FILENAME])
errors["base"] = "cannot_connect"
else:
if len(system_zone) != 0:
return self.async_create_entry(
title=f"SAM at {user_input[CONF_FILENAME]}",
data={
CONF_FILENAME: user_input[CONF_FILENAME],
CONF_SYSTEM_ZONE: system_zone,
},
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle integration reconfiguration."""
errors: dict[str, str] = {}
if user_input is not None:
system_zone = await _enumerate_sz(user_input[CONF_FILENAME])
if len(system_zone) != 0:
our_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
assert our_entry is not None, "Could not find own entry"
return self.async_update_reload_and_abort(
entry=our_entry,
data={
CONF_FILENAME: user_input[CONF_FILENAME],
CONF_SYSTEM_ZONE: system_zone,
},
unique_id=UNDEFINED,
reason="reconfigured",
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="reconfigure", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

View File

@@ -0,0 +1,4 @@
"""Constants for the Bryant Evolution integration."""
DOMAIN = "bryant_evolution"
CONF_SYSTEM_ZONE = "system_zone"

View File

@@ -0,0 +1,10 @@
{
"domain": "bryant_evolution",
"name": "Bryant Evolution",
"codeowners": ["@danielsmyers"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bryant_evolution",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["evolutionhttp==0.0.18"]
}

View File

@@ -0,0 +1,18 @@
"""Functions to generate names for devices and entities."""
from homeassistant.config_entries import ConfigEntry
def sam_device_uid(entry: ConfigEntry) -> str:
"""Return the UID for the SAM device."""
return entry.entry_id
def system_device_uid(sam_uid: str, system_id: int) -> str:
"""Return the UID for a given system (e.g., 1) under a SAM."""
return f"{sam_uid}-S{system_id}"
def zone_entity_uid(sam_uid: str, system_id: int, zone_id: int) -> str:
"""Return the UID for a given system and zone (e.g., 1 and 2) under a SAM."""
return f"{sam_uid}-S{system_id}-Z{zone_id}"

View File

@@ -0,0 +1,48 @@
{
"config": {
"step": {
"user": {
"data": {
"filename": "Serial port filename"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"exceptions": {
"failed_to_read_hvac_mode": {
"message": "Failed to read current HVAC mode"
},
"failed_to_parse_hvac_mode": {
"message": "Cannot parse response to HVACMode: {mode}"
},
"failed_to_read_hvac_action": {
"message": "Failed to read current HVAC action"
},
"failed_to_parse_hvac_action": {
"message": "Could not determine HVAC action: {mode_and_active}, {self.current_temperature}, {self.target_temperature_low}"
},
"failed_to_set_hvac_mode": {
"message": "Failed to set HVAC mode"
},
"failed_to_set_clsp": {
"message": "Failed to set cooling setpoint"
},
"failed_to_set_htsp": {
"message": "Failed to set heating setpoint"
},
"failed_to_set_temp": {
"message": "Failed to set temperature"
},
"failed_to_set_fan_mode": {
"message": "Failed to set fan mode"
}
}
}

View File

@@ -914,12 +914,37 @@ async def async_service_temperature_set(
"""Handle set temperature service."""
hass = entity.hass
kwargs = {}
min_temp = entity.min_temp
max_temp = entity.max_temp
temp_unit = entity.temperature_unit
for value, temp in service_call.data.items():
if value in CONVERTIBLE_ATTRIBUTE:
kwargs[value] = TemperatureConverter.convert(
temp, hass.config.units.temperature_unit, entity.temperature_unit
kwargs[value] = check_temp = TemperatureConverter.convert(
temp, hass.config.units.temperature_unit, temp_unit
)
_LOGGER.debug(
"Check valid temperature %d %s (%d %s) in range %d %s - %d %s",
check_temp,
entity.temperature_unit,
temp,
hass.config.units.temperature_unit,
min_temp,
temp_unit,
max_temp,
temp_unit,
)
if check_temp < min_temp or check_temp > max_temp:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="temp_out_of_range",
translation_placeholders={
"check_temp": str(check_temp),
"min_temp": str(min_temp),
"max_temp": str(max_temp),
},
)
else:
kwargs[value] = temp

View File

@@ -266,6 +266,9 @@
},
"not_valid_fan_mode": {
"message": "Fan mode {mode} is not valid. Valid fan modes are: {modes}."
},
"temp_out_of_range": {
"message": "Provided temperature {check_temp} is not valid. Accepted range is {min_temp} to {max_temp}."
}
}
}

View File

@@ -6,7 +6,7 @@ from collections.abc import Callable, Coroutine
from typing import Any
import uuid
from hass_nabucasa.voice import MAP_VOICE
from hass_nabucasa.voice import MAP_VOICE, Gender
from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.auth.models import User
@@ -91,8 +91,8 @@ class CloudPreferencesStore(Store):
# The new second item is the voice name.
default_tts_voice = old_data.get(PREF_TTS_DEFAULT_VOICE)
if default_tts_voice and (voice_item_two := default_tts_voice[1]) in (
"female",
"male",
Gender.FEMALE,
Gender.MALE,
):
language: str = default_tts_voice[0]
if voice := MAP_VOICE.get((language, voice_item_two)):

View File

@@ -13,7 +13,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
@@ -76,7 +76,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
ccb.connect()
# Schedule disconnect on shutdown
def _shutdown(_event):
def _shutdown(_event: Event) -> None:
ccb.disconnect()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
@@ -90,7 +90,15 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
class ComfoConnectBridge:
"""Representation of a ComfoConnect bridge."""
def __init__(self, hass, bridge, name, token, friendly_name, pin):
def __init__(
self,
hass: HomeAssistant,
bridge: Bridge,
name: str,
token: str,
friendly_name: str,
pin: int,
) -> None:
"""Initialize the ComfoConnect bridge."""
self.name = name
self.hass = hass
@@ -104,17 +112,17 @@ class ComfoConnectBridge:
)
self.comfoconnect.callback_sensor = self.sensor_callback
def connect(self):
def connect(self) -> None:
"""Connect with the bridge."""
_LOGGER.debug("Connecting with bridge")
self.comfoconnect.connect(True)
def disconnect(self):
def disconnect(self) -> None:
"""Disconnect from the bridge."""
_LOGGER.debug("Disconnecting from bridge")
self.comfoconnect.disconnect()
def sensor_callback(self, var, value):
def sensor_callback(self, var: str, value: str) -> None:
"""Notify listeners that we have received an update."""
_LOGGER.debug("Received update for %s: %s", var, value)
dispatcher_send(

View File

@@ -327,7 +327,7 @@ class ComfoConnectSensor(SensorEntity):
self._ccb.comfoconnect.register_sensor, self.entity_description.sensor_id
)
def _handle_update(self, value):
def _handle_update(self, value: float) -> None:
"""Handle update callbacks."""
_LOGGER.debug(
"Handle update for sensor %s (%d): %s",

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, cast
from typing import Any, cast
from homeassistant.components.cover import CoverEntity
from homeassistant.const import (
@@ -145,8 +145,7 @@ class CommandCover(ManualTriggerEntity, CoverEntity):
if self._command_state:
LOGGER.info("Running state value command: %s", self._command_state)
return await async_check_output_or_log(self._command_state, self._timeout)
if TYPE_CHECKING:
return None
return None
async def _update_entity_state(self, now: datetime | None = None) -> None:
"""Update the state of the entity."""

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, cast
from typing import Any, cast
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity
from homeassistant.const import (
@@ -147,8 +147,7 @@ class CommandSwitch(ManualTriggerEntity, SwitchEntity):
if self._value_template:
return await self._async_query_state_value(self._command_state)
return await self._async_query_state_code(self._command_state)
if TYPE_CHECKING:
return None
return None
async def _update_entity_state(self, now: datetime | None = None) -> None:
"""Update the state of the entity."""

View File

@@ -11,7 +11,7 @@ from homeassistant.components.scene import (
)
from homeassistant.config import SCENE_CONFIG_PATH
from homeassistant.const import CONF_ID, SERVICE_RELOAD
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_registry as er
from .const import ACTION_DELETE
@@ -32,7 +32,9 @@ def async_setup(hass: HomeAssistant) -> bool:
ent_reg = er.async_get(hass)
entity_id = ent_reg.async_get_entity_id(DOMAIN, HA_DOMAIN, config_key)
entity_id = ent_reg.async_get_entity_id(
DOMAIN, HOMEASSISTANT_DOMAIN, config_key
)
if entity_id is None:
return

View File

@@ -47,6 +47,7 @@ from homeassistant.util.json import JsonObjectType, json_loads_object
from .const import DEFAULT_EXPOSED_ATTRIBUTES, DOMAIN, ConversationEntityFeature
from .entity import ConversationEntity
from .models import ConversationInput, ConversationResult
from .trace import ConversationTraceEventType, async_conversation_trace_append
_LOGGER = logging.getLogger(__name__)
_DEFAULT_ERROR_TEXT = "Sorry, I couldn't understand that"
@@ -348,6 +349,16 @@ class DefaultAgent(ConversationEntity):
}
for entity in result.entities_list
}
async_conversation_trace_append(
ConversationTraceEventType.TOOL_CALL,
{
"intent_name": result.intent.name,
"slots": {
entity.name: entity.value or entity.text
for entity in result.entities_list
},
},
)
try:
intent_response = await intent.async_handle(

View File

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

View File

@@ -22,8 +22,8 @@ class ConversationTraceEventType(enum.StrEnum):
AGENT_DETAIL = "agent_detail"
"""Event detail added by a conversation agent."""
LLM_TOOL_CALL = "llm_tool_call"
"""An LLM Tool call"""
TOOL_CALL = "tool_call"
"""A conversation agent Tool call or default agent intent call."""
@dataclass(frozen=True)

View File

@@ -4,7 +4,7 @@ from homeassistant.const import SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
from . import DOMAIN
from . import DOMAIN, CoverDeviceClass
INTENT_OPEN_COVER = "HassOpenCover"
INTENT_CLOSE_COVER = "HassCloseCover"
@@ -21,6 +21,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
"Opening {}",
description="Opens a cover",
platforms={DOMAIN},
device_classes={CoverDeviceClass},
),
)
intent.async_register(
@@ -32,5 +33,6 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
"Closing {}",
description="Closes a cover",
platforms={DOMAIN},
device_classes={CoverDeviceClass},
),
)

View File

@@ -162,6 +162,7 @@ class DdWrtDeviceScanner(DeviceScanner):
)
return None
_LOGGER.error("Invalid response from DD-WRT: %s", response)
return None
def _parse_ddwrt_response(data_str):

View File

@@ -66,7 +66,6 @@ class DeconzFan(DeconzDevice[Light], FanEntity):
def __init__(self, device: Light, hub: DeconzHub) -> None:
"""Set up fan."""
super().__init__(device, hub)
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
if device.fan_speed in ORDERED_NAMED_FAN_SPEEDS:
self._default_on_speed = device.fan_speed
@@ -96,7 +95,8 @@ class DeconzFan(DeconzDevice[Light], FanEntity):
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan."""
if percentage == 0:
return await self.async_turn_off()
await self.async_turn_off()
return
await self.hub.api.lights.lights.set_state(
id=self._device.resource_id,
fan_speed=percentage_to_ordered_list_item(

View File

@@ -253,11 +253,12 @@ class DenonDevice(MediaPlayerEntity):
return SUPPORT_DENON
@property
def source(self):
def source(self) -> str | None:
"""Return the current input source."""
for pretty_name, name in self._source_list.items():
if self._mediasource == name:
return pretty_name
return None
def turn_off(self) -> None:
"""Turn off media player."""

View File

@@ -21,6 +21,8 @@ from . import DevoloHomeNetworkConfigEntry
from .const import CONNECTED_PLC_DEVICES, CONNECTED_TO_ROUTER
from .entity import DevoloCoordinatorEntity
PARALLEL_UPDATES = 1
def _is_connected_to_router(entity: DevoloBinarySensorEntity) -> bool:
"""Check, if device is attached to the router."""

View File

@@ -22,6 +22,8 @@ from . import DevoloHomeNetworkConfigEntry
from .const import DOMAIN, IDENTIFY, PAIRING, RESTART, START_WPS
from .entity import DevoloEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class DevoloButtonEntityDescription(ButtonEntityDescription):

View File

@@ -22,6 +22,8 @@ from homeassistant.helpers.update_coordinator import (
from . import DevoloHomeNetworkConfigEntry
from .const import CONNECTED_WIFI_CLIENTS, DOMAIN, WIFI_APTYPE, WIFI_BANDS
PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,

View File

@@ -20,6 +20,8 @@ from . import DevoloHomeNetworkConfigEntry
from .const import IMAGE_GUEST_WIFI, SWITCH_GUEST_WIFI
from .entity import DevoloCoordinatorEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class DevoloImageEntityDescription(ImageEntityDescription):

View File

@@ -31,6 +31,8 @@ from .const import (
)
from .entity import DevoloCoordinatorEntity
PARALLEL_UPDATES = 1
_CoordinatorDataT = TypeVar(
"_CoordinatorDataT",
bound=LogicalNetwork | DataRate | list[ConnectedStationInfo] | list[NeighborAPInfo],

View File

@@ -21,6 +21,8 @@ from . import DevoloHomeNetworkConfigEntry
from .const import DOMAIN, SWITCH_GUEST_WIFI, SWITCH_LEDS
from .entity import DevoloCoordinatorEntity
PARALLEL_UPDATES = 1
_DataT = TypeVar("_DataT", bound=WifiGuestAccessGet | bool)

View File

@@ -26,6 +26,8 @@ from . import DevoloHomeNetworkConfigEntry
from .const import DOMAIN, REGULAR_FIRMWARE
from .entity import DevoloCoordinatorEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class DevoloUpdateEntityDescription(UpdateEntityDescription):

View File

@@ -103,6 +103,8 @@ def get_api_version(message):
if message.get("responseId") is not None:
return V2
raise ValueError(f"Unable to extract API version from message: {message}")
async def async_handle_message(hass, message):
"""Handle a DialogFlow message."""
@@ -173,3 +175,5 @@ class DialogflowResponse:
if self.api_version is V2:
return {"fulfillmentText": self.speech, "source": SOURCE}
raise ValueError(f"Invalid API version: {self.api_version}")

View File

@@ -3,11 +3,11 @@
from __future__ import annotations
from http import HTTPStatus
import logging
from aiohttp import ClientResponseError
from doorbirdpy import DoorBird
from homeassistant.components import persistent_notification
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
@@ -17,6 +17,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
@@ -30,6 +31,8 @@ CONF_CUSTOM_URL = "hass_url_override"
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the DoorBird component."""
@@ -68,7 +71,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: DoorBirdConfigEntry) ->
door_bird_data = DoorBirdData(door_station, info, event_entity_ids)
door_station.update_events(events)
# Subscribe to doorbell or motion events
if not await _async_register_events(hass, door_station):
if not await _async_register_events(hass, door_station, entry):
raise ConfigEntryNotReady
entry.async_on_unload(entry.add_update_listener(_update_listener))
@@ -84,24 +87,30 @@ async def async_unload_entry(hass: HomeAssistant, entry: DoorBirdConfigEntry) ->
async def _async_register_events(
hass: HomeAssistant, door_station: ConfiguredDoorBird
hass: HomeAssistant, door_station: ConfiguredDoorBird, entry: DoorBirdConfigEntry
) -> bool:
"""Register events on device."""
issue_id = f"doorbird_schedule_error_{entry.entry_id}"
try:
await door_station.async_register_events()
except ClientResponseError:
persistent_notification.async_create(
except ClientResponseError as ex:
ir.async_create_issue(
hass,
(
"Doorbird configuration failed. Please verify that API "
"Operator permission is enabled for the Doorbird user. "
"A restart will be required once permissions have been "
"verified."
),
title="Doorbird Configuration Failure",
notification_id="doorbird_schedule_error",
DOMAIN,
issue_id,
severity=ir.IssueSeverity.ERROR,
translation_key="error_registering_events",
data={"entry_id": entry.entry_id},
is_fixable=True,
translation_placeholders={
"error": str(ex),
"name": door_station.name or entry.data[CONF_NAME],
},
)
_LOGGER.debug("Error registering DoorBird events", exc_info=True)
return False
else:
ir.async_delete_issue(hass, DOMAIN, issue_id)
return True
@@ -111,4 +120,4 @@ async def _update_listener(hass: HomeAssistant, entry: DoorBirdConfigEntry) -> N
door_station = entry.runtime_data.door_station
door_station.update_events(entry.options[CONF_EVENTS])
# Subscribe to doorbell or motion events
await _async_register_events(hass, door_station)
await _async_register_events(hass, door_station, entry)

View File

@@ -5,9 +5,11 @@ from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from functools import cached_property
from http import HTTPStatus
import logging
from typing import Any
from aiohttp import ClientResponseError
from doorbirdpy import (
DoorBird,
DoorBirdScheduleEntry,
@@ -103,9 +105,8 @@ class ConfiguredDoorBird:
async def async_register_events(self) -> None:
"""Register events on device."""
if not self.door_station_events:
# User may not have permission to get the favorites
# The config entry might not have any events configured yet
return
http_fav = await self._async_register_events()
event_config = await self._async_get_event_config(http_fav)
_LOGGER.debug("%s: Event config: %s", self.name, event_config)
@@ -171,15 +172,21 @@ class ConfiguredDoorBird:
) -> DoorbirdEventConfig:
"""Get events and unconfigured favorites from http favorites."""
device = self.device
schedule = await device.schedule()
events: list[DoorbirdEvent] = []
unconfigured_favorites: defaultdict[str, list[str]] = defaultdict(list)
try:
schedule = await device.schedule()
except ClientResponseError as ex:
if ex.status == HTTPStatus.NOT_FOUND:
# D301 models do not support schedules
return DoorbirdEventConfig(events, [], unconfigured_favorites)
raise
favorite_input_type = {
output.param: entry.input
for entry in schedule
for output in entry.output
if output.event == HTTP_EVENT_TYPE
}
events: list[DoorbirdEvent] = []
unconfigured_favorites: defaultdict[str, list[str]] = defaultdict(list)
default_event_types = {
self._get_event_name(event): event_type
for event, event_type in DEFAULT_EVENT_TYPES

View File

@@ -3,7 +3,7 @@
"name": "DoorBird",
"codeowners": ["@oblogic7", "@bdraco", "@flacjacket"],
"config_flow": true,
"dependencies": ["http"],
"dependencies": ["http", "repairs"],
"documentation": "https://www.home-assistant.io/integrations/doorbird",
"iot_class": "local_push",
"loggers": ["doorbirdpy"],

View File

@@ -0,0 +1,55 @@
"""Repairs for DoorBird."""
from __future__ import annotations
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.repairs import RepairsFlow
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
class DoorBirdReloadConfirmRepairFlow(RepairsFlow):
"""Handler to show doorbird error and reload."""
def __init__(self, entry_id: str) -> None:
"""Initialize the flow."""
self.entry_id = entry_id
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
return await self.async_step_confirm()
async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
self.hass.config_entries.async_schedule_reload(self.entry_id)
return self.async_create_entry(data={})
issue_registry = ir.async_get(self.hass)
description_placeholders = None
if issue := issue_registry.async_get_issue(self.handler, self.issue_id):
description_placeholders = issue.translation_placeholders
return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema({}),
description_placeholders=description_placeholders,
)
async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, str | int | float | None] | None,
) -> RepairsFlow:
"""Create flow."""
assert data is not None
entry_id = data["entry_id"]
assert isinstance(entry_id, str)
return DoorBirdReloadConfirmRepairFlow(entry_id=entry_id)

View File

@@ -11,6 +11,19 @@
}
}
},
"issues": {
"error_registering_events": {
"title": "DoorBird {name} configuration failure",
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::doorbird::issues::error_registering_events::title%]",
"description": "Configuring DoorBird {name} failed with error: `{error}`. Please enable the API Operator permission for the DoorBird user and continue to reload the integration."
}
}
}
}
},
"config": {
"step": {
"user": {

View File

@@ -25,19 +25,12 @@ class DoorBirdRequestView(HomeAssistantView):
"""Respond to requests from the device."""
hass = request.app[KEY_HASS]
token: str | None = request.query.get("token")
if (
token is None
or (door_station := get_door_station_by_token(hass, token)) is None
):
if not token or not (door_station := get_door_station_by_token(hass, token)):
return web.Response(
status=HTTPStatus.UNAUTHORIZED, text="Invalid token provided."
)
if door_station:
event_data = door_station.get_event_data(event)
else:
event_data = {}
event_data = door_station.get_event_data(event)
#
# This integration uses a multiple different events.
# It would be a major breaking change to change this to

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["dsmr_parser"],
"requirements": ["dsmr-parser==1.3.1"]
"requirements": ["dsmr-parser==1.4.2"]
}

View File

@@ -4,10 +4,11 @@ from __future__ import annotations
import asyncio
from asyncio import CancelledError
from collections.abc import Callable
from collections.abc import Callable, Generator
from contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta
from enum import IntEnum
from functools import partial
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
@@ -15,7 +16,7 @@ from dsmr_parser.clients.rfxtrx_protocol import (
create_rfxtrx_dsmr_reader,
create_rfxtrx_tcp_dsmr_reader,
)
from dsmr_parser.objects import DSMRObject, Telegram
from dsmr_parser.objects import DSMRObject, MbusDevice, Telegram
import serial
from homeassistant.components.sensor import (
@@ -77,6 +78,13 @@ class DSMRSensorEntityDescription(SensorEntityDescription):
obis_reference: str
class MbusDeviceType(IntEnum):
"""Types of mbus devices (13757-3:2013)."""
GAS = 3
WATER = 7
SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
DSMRSensorEntityDescription(
key="timestamp",
@@ -318,7 +326,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
DSMRSensorEntityDescription(
key="belgium_max_current_per_phase",
translation_key="max_current_per_phase",
obis_reference="BELGIUM_MAX_CURRENT_PER_PHASE",
obis_reference="FUSE_THRESHOLD_L1",
dsmr_versions={"5B"},
device_class=SensorDeviceClass.CURRENT,
entity_registry_enabled_default=False,
@@ -377,38 +385,36 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
),
)
def create_mbus_entity(
mbus: int, mtype: int, telegram: Telegram
) -> DSMRSensorEntityDescription | None:
"""Create a new MBUS Entity."""
if mtype == 3 and hasattr(telegram, f"BELGIUM_MBUS{mbus}_METER_READING2"):
return DSMRSensorEntityDescription(
key=f"mbus{mbus}_gas_reading",
SENSORS_MBUS_DEVICE_TYPE: dict[int, tuple[DSMRSensorEntityDescription, ...]] = {
MbusDeviceType.GAS: (
DSMRSensorEntityDescription(
key="gas_reading",
translation_key="gas_meter_reading",
obis_reference=f"BELGIUM_MBUS{mbus}_METER_READING2",
obis_reference="MBUS_METER_READING",
is_gas=True,
device_class=SensorDeviceClass.GAS,
state_class=SensorStateClass.TOTAL_INCREASING,
)
if mtype == 7 and (hasattr(telegram, f"BELGIUM_MBUS{mbus}_METER_READING1")):
return DSMRSensorEntityDescription(
key=f"mbus{mbus}_water_reading",
),
),
MbusDeviceType.WATER: (
DSMRSensorEntityDescription(
key="water_reading",
translation_key="water_meter_reading",
obis_reference=f"BELGIUM_MBUS{mbus}_METER_READING1",
obis_reference="MBUS_METER_READING",
is_water=True,
device_class=SensorDeviceClass.WATER,
state_class=SensorStateClass.TOTAL_INCREASING,
)
return None
),
),
}
def device_class_and_uom(
telegram: dict[str, DSMRObject],
data: Telegram | MbusDevice,
entity_description: DSMRSensorEntityDescription,
) -> tuple[SensorDeviceClass | None, str | None]:
"""Get native unit of measurement from telegram,."""
dsmr_object = getattr(telegram, entity_description.obis_reference)
dsmr_object = getattr(data, entity_description.obis_reference)
uom: str | None = getattr(dsmr_object, "unit") or None
with suppress(ValueError):
if entity_description.device_class == SensorDeviceClass.GAS and (
@@ -433,7 +439,9 @@ def rename_old_gas_to_mbus(
entries = er.async_entries_for_device(ent_reg, device_id)
for entity in entries:
if entity.unique_id.endswith("belgium_5min_gas_meter_reading"):
if entity.unique_id.endswith(
"belgium_5min_gas_meter_reading"
) or entity.unique_id.endswith("hourly_gas_meter_reading"):
try:
ent_reg.async_update_entity(
entity.entity_id,
@@ -460,37 +468,60 @@ def rename_old_gas_to_mbus(
dev_reg.async_remove_device(device_id)
def is_supported_description(
data: Telegram | MbusDevice,
description: DSMRSensorEntityDescription,
dsmr_version: str,
) -> bool:
"""Check if this is a supported description for this telegram."""
return hasattr(data, description.obis_reference) and (
description.dsmr_versions is None or dsmr_version in description.dsmr_versions
)
def create_mbus_entities(
hass: HomeAssistant, telegram: Telegram, entry: ConfigEntry
) -> list[DSMREntity]:
hass: HomeAssistant, telegram: Telegram, entry: ConfigEntry, dsmr_version: str
) -> Generator[DSMREntity]:
"""Create MBUS Entities."""
entities = []
for idx in range(1, 5):
if (
device_type := getattr(telegram, f"BELGIUM_MBUS{idx}_DEVICE_TYPE", None)
) is None:
mbus_devices: list[MbusDevice] = getattr(telegram, "MBUS_DEVICES", [])
for device in mbus_devices:
if (device_type := getattr(device, "MBUS_DEVICE_TYPE", None)) is None:
continue
if (type_ := int(device_type.value)) not in (3, 7):
continue
if identifier := getattr(
telegram, f"BELGIUM_MBUS{idx}_EQUIPMENT_IDENTIFIER", None
):
type_ = int(device_type.value)
if identifier := getattr(device, "MBUS_EQUIPMENT_IDENTIFIER", None):
serial_ = identifier.value
rename_old_gas_to_mbus(hass, entry, serial_)
else:
serial_ = ""
if description := create_mbus_entity(idx, type_, telegram):
entities.append(
DSMREntity(
description,
entry,
telegram,
*device_class_and_uom(telegram, description), # type: ignore[arg-type]
serial_,
idx,
)
for description in SENSORS_MBUS_DEVICE_TYPE.get(type_, ()):
if not is_supported_description(device, description, dsmr_version):
continue
yield DSMREntity(
description,
entry,
telegram,
*device_class_and_uom(device, description), # type: ignore[arg-type]
serial_,
device.channel_id,
)
return entities
def get_dsmr_object(
telegram: Telegram | None, mbus_id: int, obis_reference: str
) -> DSMRObject | None:
"""Extract DSMR object from telegram."""
if not telegram:
return None
telegram_or_device: Telegram | MbusDevice | None = telegram
if mbus_id:
telegram_or_device = telegram.get_mbus_device_by_channel(mbus_id)
if telegram_or_device is None:
return None
return getattr(telegram_or_device, obis_reference, None)
async def async_setup_entry(
@@ -510,8 +541,7 @@ async def async_setup_entry(
add_entities_handler()
add_entities_handler = None
if dsmr_version == "5B":
entities.extend(create_mbus_entities(hass, telegram, entry))
entities.extend(create_mbus_entities(hass, telegram, entry, dsmr_version))
entities.extend(
[
@@ -522,12 +552,8 @@ async def async_setup_entry(
*device_class_and_uom(telegram, description), # type: ignore[arg-type]
)
for description in SENSORS
if (
description.dsmr_versions is None
or dsmr_version in description.dsmr_versions
)
if is_supported_description(telegram, description, dsmr_version)
and (not description.is_gas or CONF_SERIAL_ID_GAS in entry.data)
and hasattr(telegram, description.obis_reference)
]
)
async_add_entities(entities)
@@ -723,6 +749,7 @@ class DSMREntity(SensorEntity):
identifiers={(DOMAIN, device_serial)},
name=device_name,
)
self._mbus_id = mbus_id
if mbus_id != 0:
if serial_id:
self._attr_unique_id = f"{device_serial}"
@@ -737,20 +764,22 @@ class DSMREntity(SensorEntity):
self.telegram = telegram
if self.hass and (
telegram is None
or hasattr(telegram, self.entity_description.obis_reference)
or get_dsmr_object(
telegram, self._mbus_id, self.entity_description.obis_reference
)
):
self.async_write_ha_state()
def get_dsmr_object_attr(self, attribute: str) -> str | None:
"""Read attribute from last received telegram for this DSMR object."""
# Make sure telegram contains an object for this entities obis
if self.telegram is None or not hasattr(
self.telegram, self.entity_description.obis_reference
):
# Get the object
dsmr_object = get_dsmr_object(
self.telegram, self._mbus_id, self.entity_description.obis_reference
)
if dsmr_object is None:
return None
# Get the attribute value if the object has it
dsmr_object = getattr(self.telegram, self.entity_description.obis_reference)
attr: str | None = getattr(dsmr_object, attribute)
return attr

View File

@@ -9,6 +9,7 @@ from __future__ import annotations
from contextlib import suppress
from datetime import datetime, timedelta
from http import HTTPStatus
from typing import Any
import requests
import voluptuous as vol
@@ -102,7 +103,7 @@ class DublinPublicTransportSensor(SensorEntity):
return self._state
@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes."""
if self._times is not None:
next_up = "None"
@@ -117,6 +118,7 @@ class DublinPublicTransportSensor(SensorEntity):
ATTR_ROUTE: self._times[0][ATTR_ROUTE],
ATTR_NEXT_UP: next_up,
}
return None
@property
def native_unit_of_measurement(self):

View File

@@ -46,7 +46,7 @@ class EcobeeBinarySensor(BinarySensorEntity):
self.index = sensor_index
@property
def unique_id(self):
def unique_id(self) -> str | None:
"""Return a unique identifier for this sensor."""
for sensor in self.data.ecobee.get_remote_sensors(self.index):
if sensor["name"] == self.sensor_name:
@@ -54,6 +54,7 @@ class EcobeeBinarySensor(BinarySensorEntity):
return f"{sensor['code']}-{self.device_class}"
thermostat = self.data.ecobee.get_thermostat(self.index)
return f"{thermostat['identifier']}-{sensor['id']}-{self.device_class}"
return None
@property
def device_info(self) -> DeviceInfo | None:

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