Compare commits

...

732 Commits

Author SHA1 Message Date
Erik Montnemery
ed737f306b Remove cloud assist pipeline setup from cloud client (#92056) 2023-04-26 06:53:58 -04:00
J. Nick Koston
6b931b208f Small cleanups to bluetooth internals (#92045)
* Small cleanups to bluetooth internals

Improve the performance of _async_on_advertisement

Fixes

```
tests/components/bluetooth/test_models.py::test_ble_device_with_proxy_client_out_of_connections_uses_best_available
tests/components/bluetooth/test_models.py::test_ble_device_with_proxy_client_out_of_connections_uses_best_available_macos
  /Users/bdraco/home-assistant/homeassistant/components/bluetooth/wrappers.py:226: FutureWarning: This method will be removed future version, pass the callback to the BleakClient constructor instead.
    self._backend.set_disconnected_callback(

tests/components/bluetooth/test_models.py::test_ble_device_with_proxy_client_out_of_connections_uses_best_available_macos
  /Users/bdraco/home-assistant/tests/components/bluetooth/test_models.py:506: FutureWarning: BLEDevice.metadata is deprecated and will be removed in a future version of Bleak, use AdvertisementData instead
    switchbot_proxy_device_no_connection_slot.metadata["delegate"] = 0

tests/components/bluetooth/test_models.py::test_ble_device_with_proxy_client_out_of_connections_uses_best_available_macos
  /Users/bdraco/home-assistant/tests/components/bluetooth/test_models.py:521: FutureWarning: BLEDevice.metadata is deprecated and will be removed in a future version of Bleak, use AdvertisementData instead
    switchbot_proxy_device_has_connection_slot.metadata["delegate"] = 0

tests/components/bluetooth/test_models.py::test_ble_device_with_proxy_client_out_of_connections_uses_best_available_macos
  /Users/bdraco/home-assistant/tests/components/bluetooth/test_models.py:535: FutureWarning: BLEDevice.metadata is deprecated and will be removed in a future version of Bleak, use AdvertisementData instead
    switchbot_device.metadata["delegate"] = 0

```

* put back kwargs
2023-04-26 11:16:34 +02:00
Joakim Sørensen
c429bfae3e Bump hass-nabucasa from 0.66.1 to 0.66.2 (#92054) 2023-04-26 10:47:41 +02:00
J. Nick Koston
cf69da40f3 Only check support_entry_unload/support_remove_from_device once (#92041) 2023-04-26 10:23:18 +02:00
Ondřej Kolenatý
5399dfd39d Set forecast-solar energy sensor's units of measurement to Wh (#92022)
* All energy units of measurement changed to Wh.

* All energy units of measurement changed to Wh.
2023-04-26 10:09:30 +02:00
Robert Svensson
09109d093e Allow UniFi control PoE passthrough properly (#92028) 2023-04-26 09:16:18 +02:00
J. Nick Koston
828aed73af Bump zeroconf to 0.58.2 (#92049) 2023-04-26 09:14:34 +02:00
J. Nick Koston
5f1fc3259c Bump aioesphomeapi to 13.7.2 (#92027) 2023-04-26 09:12:52 +02:00
Michael Hansen
8dfecac013 Time out TTS based on audio length (#92032)
* Time out TTS based on audio length

* Use async mock
2023-04-26 00:35:14 -04:00
Michael Hansen
257944c3b7 Allow numbers in TTS engine part of cache key (#92031) 2023-04-26 00:33:45 -04:00
Raman Gupta
3190e5d7cf Add button entities to manually idle zwave notification values (#91446)
* Add button entities to manually idle zwave notification values

* Update discovery.py

* Improve discovery check

* fix tests

* make unique ID more clear
2023-04-25 21:41:29 -04:00
Erik Montnemery
57a59d808b Automaticially create an assist pipeline using cloud stt + tts (#91991)
* Automaticially create an assist pipeline using cloud stt + tts

* Return the id of the cloud enabled pipeline

* Wait for platforms to load

* Fix typing

* Fix startup race

* Update tests

* Create a cloud pipeline only when logging in

* Fix tests

* Tweak _async_resolve_default_pipeline_settings

* Improve assist_pipeline test coverage

* Improve cloud test coverage
2023-04-25 20:40:01 -05:00
J. Nick Koston
74e0341d83 Bump yalexs-ble to 2.1.16 (#92034) 2023-04-25 18:53:08 -05:00
puddly
6842cdcb65 Clean up default ZHA entity names (#91841)
* Always use `Light` for lights, including subclasses

* Clean up other platforms

* Add a unit test to ensure all future entity classes have names

* Remove stale `_name`

* Address review feedback and rename `Open` to `Opening`
2023-04-25 19:51:39 -04:00
shbatm
da05763a5c ISY994 remove value when calling open cover with no position (#92036) 2023-04-25 18:17:11 -05:00
shbatm
969db343bd Invert ISY994 dusk/dawn sensors to match expected state (#92035) 2023-04-25 17:15:48 -05:00
nachonam
2d510bfe0d Add camera platform to Freebox (#88104)
* Add Freebox cameras

* Apply suggestions from code review

add code corrections after PR review

Co-authored-by: Quentame <polletquentin74@me.com>

* Update base_class.py

* add some code syntax corrections add unit tests

* add unit tests

* add syntax changes

* Update homeassistant/components/freebox/router.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/router.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/base_class.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/router.py

Co-authored-by: Quentame <polletquentin74@me.com>

* clear code  and add minor changes

* correct syntax error and check home granted access

* typing functions

* Update tests/components/freebox/conftest.py

don't needed, and will fix tests.

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

Rename _volume_micro variable

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

Use const not literal

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

set to true not needed

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

use _attr_supported_features instead _supported_features

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

overload the entity with command_flip property and set_flip not needed

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

Cameras does not default to False,

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

delete this function because is not needed

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

Co-authored-by: Quentame <polletquentin74@me.com>

* consts,  rollback _command flip is protected var

* VALUE_NOT_SET does not exists anymore

* Use HOME_COMPATIBLE_PLATFORMS

* Rename FreeboxHomeBaseClass to FreeboxHomeEntity

* Update Freebox Home comment

* Use CATEGORY_TO_MODEL to set model attr of FreeboxHomeEntity

* Use Home API from the router

* Add SERVICE_FLIP const

* Use SERVICE_FLIP const

* Fix typo in HOME_COMPATIBLE_PLATFORMS

* fix somme code issues

* use SERVICE_FLIP (lost in merge)

* use _attr_device_info

* clear code

* HOME_COMPATIBLE_PLATFORMS is a list

* Update homeassistant/components/freebox/home_base.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/home_base.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/config_flow.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/home_base.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/home_base.py

Co-authored-by: Quentame <polletquentin74@me.com>

* clear config_flow permission

* Update homeassistant/components/freebox/home_base.py

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

Co-authored-by: Quentame <polletquentin74@me.com>

* add untested files to. coveragerc

* clear unused attributes

* add not tested file camera.py

* clear unusued const

* add extra_state_attributes

* Update .coveragerc

Co-authored-by: Quentame <polletquentin74@me.com>

* Update homeassistant/components/freebox/camera.py

Co-authored-by: Quentame <polletquentin74@me.com>

* fetch _flip

* del flip service

* add device_info via_device

* Update .coveragerc

* Update .coveragerc

* Update .coveragerc

* Update .coveragerc

* Remove flip reference

* Fix issue on router without Home API

* Fix "Home access is not granted" log repeats every 30s

* Fix sensor device_info

---------

Co-authored-by: Quentame <polletquentin74@me.com>
2023-04-26 00:03:39 +02:00
Jan Bouwhuis
62bb584522 Add note for using xfail on test for mqtt_statestream (#91999)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-04-25 22:49:47 +02:00
Jan Bouwhuis
aab7dffdb9 Only expose MQTT advanced settings in advanced mode (#91996) 2023-04-25 22:23:17 +02:00
Martin Hjelmare
0594fefb0c Clean up tts fixtures (#92025) 2023-04-25 22:21:18 +02:00
Maciej Bieniek
5244f5731e Do not reload Shelly TRV config entry (#92000) 2023-04-25 22:20:59 +02:00
Michel van de Wetering
ee3cebe37b Enable open,close,stop device actions for all covers (#92006) 2023-04-25 22:19:37 +02:00
Erik Montnemery
18f7b92438 Fall back to domain for conversation agents without title (#92014) 2023-04-25 22:14:44 +02:00
Raman Gupta
78a49ecbce Bump zwave-js-server-python to 0.48.0 (#91989)
* Bump zwave-js-server-python to 0.48.0

* Add handling of FailedZWaveCommand exception

* Reset mock to avoid unintentional side effects

* Fix bump
2023-04-25 15:41:37 -04:00
Dave T
dedb3f8b6b Fix comment typos (#92021) 2023-04-25 21:04:36 +02:00
Erik Montnemery
64cbc91697 Use addon uuid as wyoming config entry unique id (#92008) 2023-04-25 21:04:11 +02:00
Arjan
74e3cac8b5 Fix timezone issue (#92005) 2023-04-25 21:02:02 +02:00
Joakim Sørensen
083f42cce6 Bump hass-nabucasa from 0.65.0 to 0.66.1 (#92020) 2023-04-25 21:00:23 +02:00
Daniel Hjelseth Høyer
9d3a0d676a Update tibber lib to 0.27.1 (#92016) 2023-04-25 13:40:48 -05:00
J. Nick Koston
2354f8194e Add reauth to onvif (#91957)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-04-25 12:20:17 -05:00
Erik Montnemery
8e70446ef8 Tweak a couple of stt and tts related asserts (#92012)
Tweak a couple of stt and tts related asserts
2023-04-25 11:50:37 -05:00
Erik Montnemery
933761d425 Fix cloud connected signal only firing once (#92009) 2023-04-25 12:15:27 -04:00
Alberto Geniola
dae05a0c93 Add check on disarm code for Alarm Control Panel - Elmax (#91158) 2023-04-25 17:56:01 +02:00
Erik Montnemery
792ea92e55 Remove fuzzy language matching from stt and tts (#92002)
* Remove fuzzy language matching from stt and tts

* Update tests
2023-04-25 17:54:42 +02:00
Clay Benson
d1e6e4078c Bump asyncsleepiq lib to 1.3.4 (#91860) 2023-04-25 09:54:32 -05:00
J. Nick Koston
e7e7990b60 Bump sqlalchemy to 2.0.10 (#91982) 2023-04-25 09:45:49 -05:00
Alberto Geniola
aa1304e93a Elmax/sensors improvements (#74323)
* Address suggestions in PR #64090

* Remove redundant force-refresh at discovery time

* Improve re-auth UX

* Resolve linting warning

* Cover reauth case when no entry is in place

* Add missing reauth_successful message

* Use ad-hoc schema for reauth flow

* Align test cases with latest modifications

* Improve re-authentication flow, align tests

* Remove None check (as it can never happen)

* Remove panel_id from reauth dialog

* Remove panel_id from reauth dialog

* Simplify try-catch block

* Address suggestions in PR #64090

* Remove redundant force-refresh at discovery time

* Improve re-auth UX

* Resolve linting warning

* Cover reauth case when no entry is in place

* Add missing reauth_successful message

* Use ad-hoc schema for reauth flow

* Align test cases with latest modifications

* Improve re-authentication flow, align tests

* Remove None check (as it can never happen)

* Remove panel_id from reauth dialog

* Remove panel_id from reauth dialog

* Simplify try-catch block

* Improve type handling

* Remove translations

* Add unique-id attribute within context object

* Optimize local variable usage
2023-04-25 15:40:46 +02:00
Diogo Gomes
a3048234d3 Support for multiple contracts in Prosegur (#89097)
* support for multiple contracts - tested live

* update tests

* address review

* address review

* fix
2023-04-25 15:16:51 +02:00
G Johansson
1fa82fa886 Move TriggerBaseEntity into helpers (#91945)
* Move TriggerBaseEntity

* mypy
2023-04-25 09:56:40 +02:00
Stephan Uhle
b56f0ad668 Remove unused attributes in EDL21 integration (#91922) 2023-04-25 09:50:23 +02:00
Diogo Gomes
48546c77b5 Follow the unavailability of the source sensor (#91975) 2023-04-25 09:49:58 +02:00
Erik Montnemery
63f3767a29 Include UUID in hassio discovery data (#91970) 2023-04-25 09:48:47 +02:00
puddly
0b0573c387 Bump ZHA dependencies (#91969) 2023-04-25 09:47:22 +02:00
Michael Hansen
ebabf504da Play pre-recorded message if pipeline is misconfigured (#91985)
* Play pre-recorded message if pipeline is misconfigured

* Use voip_device
2023-04-24 21:48:40 -05:00
J. Nick Koston
5114a1400f Ensure logbook still responds if describe event throws (#91961)
* Ensure logbook still responds if describe event throws

If describe fails, the logbook stream should not collapse

* Ensure logbook still responds if describe event throws

If describe fails, the logbook stream should not collapse
2023-04-24 22:22:53 -04:00
J. Nick Koston
18532e453a Bump home-assistant-bluetooth to 1.10.0 (#91977)
changelog: https://github.com/home-assistant-libs/home-assistant-bluetooth/blob/main/CHANGELOG.md
2023-04-24 22:22:11 -04:00
Robert Svensson
cecd657a09 Bump aiounifi to v47 (#91966) 2023-04-24 18:06:12 -05:00
Luke
b5bf88215c Add mopping abilities to Roborock (#91766)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2023-04-24 22:53:45 +02:00
J. Nick Koston
e3a110f04f Bump yalexs to 1.3.2 (#91963) 2023-04-24 22:13:30 +02:00
Michael Hansen
b4bd3b97f8 Use order in preferred regions list (#91959)
* Use order in preferred regions list

* Use float for score (inf = exact match)
2023-04-24 14:12:38 -04:00
Erik Montnemery
b601fb17d3 Create a default assist pipeline on start (#91947)
* Create a default assist pipeline on start

* Minor adjustments

* Address review comments

* Remove tts.async_get_agent

* Fix bugs, improve test coverage
2023-04-24 14:00:52 -04:00
Franck Nijhof
4b619f7251 Relax polling rate on Rituals Perfume Genie (#91949) 2023-04-24 19:28:14 +02:00
Michael Hansen
5a57602163 Wait for TTS before restarting pipeline (#91962) 2023-04-24 13:27:13 -04:00
Ondřej Kolenatý
36f90cda92 Dependency upgrade to forecast-solar 3.0.0 (#91951) 2023-04-24 19:18:18 +02:00
Alberto Geniola
f104bba683 Elmax -Handle 422 busy error with a retry approach (#91926) 2023-04-24 18:51:40 +02:00
J. Nick Koston
e2d2aeadaa Fix typo in powerwall strings (#91956) 2023-04-24 17:54:22 +02:00
Michael
2ba2c6c6ab Correct entity categories in AVM FRITZ!SmartHome (#91073) 2023-04-24 16:57:34 +02:00
Erik Montnemery
a956f16f73 Suppress pylint error related to audioop deprecation (#91950)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-04-24 16:56:20 +02:00
Michael Hansen
5075281add Fix zh Hant/Hans (#91390)
* Fix zh Hant/Hans

* Fix comments
2023-04-24 16:55:08 +02:00
Luke
1ae7610622 Add Vacuum test for Roborock (#91870) 2023-04-24 16:46:00 +02:00
Jan Bouwhuis
88f0e4000b Cleanup unreachable code where device_entry will never be None on an update event (#91855) 2023-04-24 16:09:36 +02:00
Jan Bouwhuis
3f6541a6db Add cipher list option to IMAP config flow (#91896)
* Add cipher list option to IMAP config flow

* Use client_context to get the ssl_context

* Formatting

* Add ssl error no make error handling more specific

* Make ssl_ciper_list an advanced option
2023-04-24 15:37:21 +02:00
J. Nick Koston
c3262ebdb3 Add rule parser for onvif LineDetector and CountAggregation (#91885) 2023-04-24 15:23:59 +02:00
J. Nick Koston
e25885b943 Improve error reporting in onvif in config flow (#91876) 2023-04-24 15:20:37 +02:00
Erik Montnemery
1c3e1d2e13 Don't resolve default tts engine in assist pipelines (#91943)
* Don't resolve default tts engine in assist pipelines

* Set tts engine when creating default pipeline

* Update tests
2023-04-24 14:40:11 +02:00
J. Nick Koston
392a9f32c9 Add fallback to device zone time or no timezone to onvif when setting time fails (#91882) 2023-04-24 14:37:56 +02:00
J. Nick Koston
ddb3955a23 Add guards to onvif for when the PullPoint to Webhook url changes (#91886) 2023-04-24 14:35:45 +02:00
J. Nick Koston
6c024405a6 Ensure onvif can still be unloaded if camera fails to unsubscribe (#91887) 2023-04-24 14:13:04 +02:00
Erik Montnemery
c5d0c392a9 Don't resolve default stt engine in assist pipelines (#91936)
* Don't resolve default stt engine in assist pipelines

* Apply suggestion from code review

* Add tests

* Tweak

* Add test

* Improve test coverage
2023-04-24 13:37:13 +02:00
Joost Lekkerkerker
0d815a1688 Bump Pylast to 5.1.0 (#91909) 2023-04-24 13:11:14 +02:00
Franck Nijhof
43880ffdc4 Remove unneeded pygatt package from machine builds (#91940) 2023-04-24 12:39:51 +02:00
rubenbe
2f1a5942ab Add MQTT fan direction support (#91700)
* Add MQTT fan direction support

* Add MQTT fan direction abbreviations

* Add MQTT fan direction tests

* Shorten MQTT fan test payloads
2023-04-24 11:48:00 +02:00
J. Nick Koston
739963b5ee Remove deprecated async_setup_platforms (#91929) 2023-04-23 22:38:35 -05:00
Paulus Schoutsen
a203149133 Allow entity names for STT entities (#91932)
* Allow entity names for STT entities

* Fix tests
2023-04-23 23:06:34 -04:00
J. Nick Koston
fba7c6cacd Remove deprecated async_get_registry (#91928)
Deprecated in 2021 via #46265, report was added a year ago in #72088
2023-04-23 23:02:21 -04:00
J. Nick Koston
f3838dde3a Cleanup onvif topic before lookup (#91914)
* Cleanup onvif topic before lookup

Some devices will append . and / to the path. We need
to strip it off to find the correct matcher

fixes #51870

* Update homeassistant/components/onvif/event.py

* Update homeassistant/components/onvif/event.py

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-23 23:02:08 -04:00
Michael Hansen
5348ea3ac4 Add processing tone (#91931)
* Add processing tone

* Update homeassistant/components/voip/voip.py

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Update homeassistant/components/voip/voip.py

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Fix linting

* Fix tests

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-23 21:28:56 -05:00
Paulus Schoutsen
c8c8e4dede Remove duplicate name from Wyoming TTS entity (#91930) 2023-04-23 20:13:32 -05:00
J. Nick Koston
14d2645de2 Avoid creating onvif switches on unsupported devices (#91907)
* Avoid creating onvif switches on unsupported devices

fixes #89064

* cover
2023-04-23 20:56:43 -04:00
J. Nick Koston
0b0c94ee52 Fix migration with negative event type cache (#91910)
* Fix migration with negative event type cache

fixes a regression with #91770

* Update homeassistant/components/recorder/table_managers/event_types.py
2023-04-23 20:56:17 -04:00
J. Nick Koston
c38839d72f Suppress events for tns1:MediaControl/VideoEncoderConfiguration with onvif (#91916)
These events are not useful to the integration and generate a
confusing log message
```
2023-04-23 14:01:21.866 INFO (MainThread) [homeassistant.components.onvif] DS-HD1: No registered handler for event from 80:91:33:88:8d:bd: {
    SubscriptionReference: None,
    Topic: {
        _value_1: tns1:MediaControl/VideoEncoderConfiguration,
        Dialect: http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet,
        _attr_1: {
    }
    },
    ProducerReference: None,
    Message: {
        _value_1: {
            Source: {
                SimpleItem: [
                    {
                        Name: VideoEncoderConfigurationToken,
                        Value: VideoEncoderToken_1
                    }
                ],
                ElementItem: [],
                Extension: None,
                _attr_1: None
            },
            Key: None,
            Data: {
                SimpleItem: [],
                ElementItem: [
                    {
                        _value_1: <Element {http://www.onvif.org/ver10/media/wsdl}Configuration at 0x2a9e103c0>,
                        Name: Config
                    }
                ],
                Extension: None,
                _attr_1: None
            },
            Extension: None,
            UtcTime: datetime.datetime(2023, 4, 23, 20, 1, 17, tzinfo=<isodate.tzinfo.Utc object at 0x1696b1270>),
            PropertyOperation: Initialized,
            _attr_1: {
        }
        }
    }
}

```

We now ignore them
2023-04-23 20:54:37 -04:00
J. Nick Koston
ed468fa390 Bump zeroconf to 0.58.0 (#91917)
* Bump zeroconf to 0.57.0

changelog: https://github.com/python-zeroconf/python-zeroconf/compare/0.56.0...0.57.0

* again
2023-04-23 20:54:13 -04:00
Franck Nijhof
e795ac5a8e Wheels: Fix typo in env_canada dep for armhf (#91918) 2023-04-23 14:53:42 -05:00
Pablo Estevez
780d0a484d Add NUT device actions (#80986)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-23 14:09:36 -05:00
Michael Hansen
b6f2b29a99 Wyoming tts (#91712)
* Add tts entity

* Add tts entity and tests

* Re-add name to TextToSpeechEntity

* Fix linting

* Fix ruff linting

* Support voice attr (unused)

* Remove async_get_text_to_speech_entity

* Move name property to Wyoming TTS entity

* Fix id

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-23 13:06:56 -05:00
Paulus Schoutsen
f4df0ca50a Assist pipeline to use configured values (#91901)
* Assist pipeline to use configured values

* Include voice in TTS-START event

* Use correct tts language var

* More vars

* Apply suggestions from code review

* Update

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-04-23 12:48:11 -04:00
Franck Nijhof
ec1952b926 Wheels: Disable Pandas for Python 3.11 on armhf (#91898) 2023-04-23 16:42:44 +02:00
Markus
1a14a93df6 HA deconz: Add support for Tuya Smart Air Housekeeper 6in1 Air Quality Monitor (#87866)
* fix check for airquality sensor (was always TRUE)

* import additional const required

* add support for formaldehyd sensor

* add support for co2 sensor

* add support for pm25 sensor

* sort const a-z

* adapt to typo fixed function

* adapt to new key names (formaldehyde)

* adapt co2 sensor to new key names

* Update homeassistant/components/deconz/sensor.py

remove unnecessary code

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

* add device_class for air_quality_ppb

* add test_air_quality_sensor_6_in_1

* fix Air quality PPB test to match device_class

* remove device_class of air_quality_ppb

* remove device_class test of airqualityppb

* explicit units

* remove device_class test of airquality_1_ppb

* dependency bump pydeconz to 111

* Update homeassistant/components/deconz/sensor.py

remove unnecessary device_class

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

* import formatting

* formatting

* Update homeassistant/components/deconz/sensor.py

change key for pm2_5

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

* add airquality co2 ch2o pm25 sensor testdata

* remove test_air_quality_sensor_6_in_1

* formatting

* bump pydeconz for requirements

---------

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
2023-04-23 16:12:52 +02:00
Marc Mueller
06520217bc Update pylint to 2.17.2 (#91894) 2023-04-23 14:46:48 +02:00
J. Nick Koston
1a00644b0a Bump ulid-transform to 0.7.0 (#91888)
changelog: https://github.com/bdraco/ulid-transform/compare/v0.6.3...v0.7.0
2023-04-23 06:53:53 -04:00
Marc Mueller
00e600d406 Improve notion generic typing (#91883) 2023-04-23 11:24:39 +02:00
Paulus Schoutsen
f18056f0a4 Add a VoIP user (#91884)
* Add a VoIP user

* Fix tests
2023-04-22 23:44:13 -04:00
Michael Hansen
7fcf07c964 Ensure .pcm binary files do not have line endings changed (#91881) 2023-04-22 22:22:30 -04:00
Michael Hansen
8abd047e21 Rename raw audio files to .pcm (#91879) 2023-04-22 22:03:08 -04:00
Paulus Schoutsen
1eef4af493 Use configured voice in TTS output for assist pipeline (#91878) 2023-04-22 22:01:32 -04:00
Matthias Alphart
33808cd268 Add entity name translations to KNX system sensors (#91396)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2023-04-23 01:31:59 +02:00
J. Nick Koston
82340907c2 Ensure onvif retries setup if camera fails to respond correctly (#91866) 2023-04-23 01:27:54 +02:00
[pʲɵs]
6013584b7b Add balance entity for Sonos speakers (#85205) 2023-04-23 01:18:17 +02:00
J. Nick Koston
942a955a77 Handle 404 for media/ptz/image onvif services to allow setup to proceed (#91875) 2023-04-23 01:16:37 +02:00
J. Nick Koston
e4744199ce Raise an exception when trying to save camera snapshots to a not allowed path (#91869) 2023-04-23 01:15:56 +02:00
Marc Mueller
9ccc0059d2 Add run-name for workflow_dispatch runs [ci] (#91873) 2023-04-23 01:15:09 +02:00
J. Nick Koston
bba225abc5 Mark onvif events as stale when the subscription renewal fails (#91567) 2023-04-23 01:14:22 +02:00
Franck Nijhof
dccef1020c Revert "Update opencv-python-headless to 4.7.0.72" (#91871) 2023-04-23 01:09:35 +02:00
Luke
498e69695b Add Anova integration (#86254)
* init setup of Anova Sous Vide

* bump anova-wifi to 0.2.4

* Removed yaml support

* Bump to anova-wifi 0.2.5

* Added support for adding sous vide while offline

* Added basic test for sensor

* added better tests for sensors and init

* expanded code coverage

* Decreased timedelta to lowest functioning value.

* Updating my username

* migrate to async_forward_entry_setups

* applying pr recommended changes

* bump anova-wifi to 0.2.7

* Improvements to hopefully get this review ready

* formatting changes

* clean ups for pr review

* remove unneeded unique id check.

* bump ao anova_wifi 0.3.0

* rename device_id to device_unique_id

* renamed to 'anova'

* added unique_id to MockConfigEntry

* removed leftover anova sous vides

* added device id to strings

* added error for incorrect device id

* add has_entity_name

* added attr name for tests

* added authentication functionality

* bump to 0.4.3

* split entity into its own class/object

* pulling firmware version out of async_setup

Co-authored-by: J. Nick Koston <nick@koston.org>

* addressed pr changes

* fixed pytest

* added anova data model

* removed unneeded time change

* add logging in package

* rework step_user

* Update homeassistant/components/anova/sensor.py

Co-authored-by: J. Nick Koston <nick@koston.org>

* Removed lower from attr unique id

Co-authored-by: J. Nick Koston <nick@koston.org>

* Removed unneeded member variables in sensor

Co-authored-by: J. Nick Koston <nick@koston.org>

* removed repeated subclass attr

Co-authored-by: J. Nick Koston <nick@koston.org>

* simplify update_failed test

* created descriptionentity

* bump to 0.6.1 limit ws connect

* add translation for sensor entities

* version bump - support pro model

* add anova to strict typing

* fixed sensor not getting datas type

* Apply suggestions from code review

Co-authored-by: J. Nick Koston <nick@koston.org>

* Check for new devices in init

* style changes

* return false instead of config entry not ready

* move serialize_device_list to utils

* move repeating device check into api

* moved unneeded code out of try except

* fixed tests to get 100% cov

* Update homeassistant/components/anova/strings.json

Co-authored-by: J. Nick Koston <nick@koston.org>

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-22 18:05:14 -05:00
Eric Severance
68ce59e9c1 Skip adding WeMo device on failure (#91621)
* Skip adding WeMo device on failure

* Only create a single log entry for each failed device

* Fix typo s/serial_number/serialnumber/

* Discard failed devices on success
2023-04-22 23:10:08 +02:00
Franck Nijhof
66a25a9e72 Wheels: Don't build opencv-python-headless on cp311 (#91872) 2023-04-22 22:44:29 +02:00
cirrusblau
ae91ef8e74 Bump locationsharinglib to 5.0.1 (#91259) 2023-04-22 22:05:23 +02:00
Franck Nijhof
5533eed30c Wheels: Don't build beacontools on cp311 (#91868) 2023-04-22 22:00:32 +02:00
epenet
7906e39df7 Add missing ViCare diagnostics tests (#90821)
Co-authored-by: Hans Oischinger <hans.oischinger@gmail.com>
2023-04-22 21:29:40 +02:00
epenet
98b1005b63 Ensure config entries are unloaded in tests (#90850) 2023-04-22 21:26:07 +02:00
Michael
4ed23b18e3 Add entity name translations to Supervisor (#90937) 2023-04-22 21:17:00 +02:00
epenet
41f8f9fcad Fix lingering timer in yeelight SSDP discovery (#91694) 2023-04-22 21:13:36 +02:00
epenet
102613a1af Fix lingering timer in wiz (#91745) 2023-04-22 21:12:48 +02:00
Michael
957181d38f Correct unit of measurement for Synology DSM sensors (#90633) 2023-04-22 21:11:03 +02:00
Ernst Klamer
328b79a4af Add events to BTHome (#91691)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-22 14:10:27 -05:00
Franck Nijhof
ca76285bcf CI: Cancel concurrent Wheel jobs (#91867) 2023-04-22 20:56:52 +02:00
Franck Nijhof
0f80d0625a Wheels: Don't build azure-servicebus on cp311 (#91862) 2023-04-22 19:29:42 +02:00
Michael
fe2c11a698 Add diagnostics to PI-Hole (#91383) 2023-04-22 19:29:28 +02:00
jjlawren
d5a6840588 Create repair issue if Sonos subscriptions fail (#87437)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2023-04-22 19:28:04 +02:00
David Poll
93a87d3c82 Add device_trackers attribute to person entities (#91312) 2023-04-22 19:26:12 +02:00
Tristan Richard
94a1429c65 Update Egardia to support more devices (#91430)
Co-authored-by: Tristan Richard <trichard@jabra.com>
2023-04-22 19:10:17 +02:00
Christopher Bailey
512e03746b Bump pyunifiprotect to 4.8.2 (#91861) 2023-04-22 18:44:27 +02:00
Paulus Schoutsen
d4fb2cdcf7 Conversation list command to support match-all languages agents (#91844)
* Conversation list command to support match-all languages agnets

* Reuse var
2023-04-22 12:43:09 -04:00
Matthias Alphart
95d44e100b Update xknx to 2.9.0 (#91282)
* Update xknx to 2.8.0

* add tests for validators

* Update strings.json

* Update xknx to 2.9.0
2023-04-22 18:25:14 +02:00
András Rutkai
2e9dc209f9 Updated list of supported voices in Watson TTS integration (#91458) 2023-04-22 18:11:18 +02:00
Maciej Bieniek
8389086388 Add new parental control switches for NextDNS (#91507) 2023-04-22 18:09:54 +02:00
MarkGodwin
e19c6ac011 Bump tplink-omada-client to 1.2.4 (#91853) 2023-04-22 18:09:02 +02:00
Jan Bouwhuis
8910afa474 Substate will never be None when subscribing mqtt topics (#91856) 2023-04-22 17:54:39 +02:00
mkmer
cb87942b28 Bump whirlpool-sixth-sense to 0.18.3 (#91859) 2023-04-22 17:53:04 +02:00
J. Nick Koston
3beb6e9718 Add webhook support to onvif (#91485) 2023-04-22 17:49:41 +02:00
Franck Nijhof
a491859875 Several changes to Wheels building for the cp311 ABI (#91850) 2023-04-22 17:23:02 +02:00
epenet
60d7ea6f0d Use SnapshotAssertion in 1-wire tests (#90782) 2023-04-22 17:22:05 +02:00
epenet
b568dd3060 Refactor SnapshotAssertion is SFR Box (#90775) 2023-04-22 17:20:27 +02:00
Maciej Bieniek
ee23d79a00 Add air quality sensors to Accuweather integration (#91516) 2023-04-22 17:19:21 +02:00
Franck Nijhof
20a42c65cf CI: Add automatic retries to codecov upload action (#91857) 2023-04-22 17:16:43 +02:00
starkillerOG
228233ae0c Bump reolink-aio to v0.5.13 (#91550) 2023-04-22 16:14:36 +02:00
Franck Nijhof
f39f07e19b Update YARL to 1.9.1 (#91852) 2023-04-22 09:14:27 -04:00
Lucas
48a2fe9e7a Add more PrusaLink sensors (#80424)
* Add more PrusaLink sensors

* Disable prusalink target temperatures by default, add prusa to brands

* Disable prusalink z-height by default

* Remove uneccessary Prusa from brands

* Move target temperatures to the telemetry section of the sensor

* Fix entity naming for prusalink

* Add tests for new prusalink sensors

* Test fixes, fix rebase errors

* Clean up deprecated unit usage

* Add translations for entity names

* Fix tests for translations

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-04-22 09:04:10 -04:00
J. Nick Koston
b76551cf35 Reduce number of columns selected for logbook (#91718) 2023-04-22 14:25:22 +02:00
J. Nick Koston
34b824a27b Reduce number of columns when selecting attributes for history (#91717) 2023-04-22 14:21:08 +02:00
J. Nick Koston
6e628d2f06 Add a negative cache to the recorder event type manager (#91770) 2023-04-22 14:09:04 +02:00
starkillerOG
8544d73b88 Reolink doorbell extend features (#90469) 2023-04-22 13:57:10 +02:00
Jan Bouwhuis
9197316b57 Add tests mqtt light with single supported color_mode (#91811) 2023-04-22 13:55:43 +02:00
J. Nick Koston
b5fbbf8410 Speed up processing binary columns on SQLite/MariaDB/MySQL (#91818) 2023-04-22 13:55:11 +02:00
Jan Bouwhuis
fc0c8bf113 Improve comment on patching the _import method in test bootstrap dependencies test (#91848) 2023-04-22 13:40:38 +02:00
Bouwe Westerdijk
624b8d1cbd Bump plugwise to v0.31.0 (#91831) 2023-04-22 12:59:47 +02:00
Jan Bouwhuis
ad355b3396 Remove unreachable code in mqtt integration discovery (#91796) 2023-04-22 12:31:32 +02:00
J. Nick Koston
7a02e992f9 Bump sense_energy to 0.11.2 (#91836) 2023-04-22 12:12:16 +02:00
J. Nick Koston
95fcdc5684 Speed up sql queries where ORM rows are not needed (#91839)
* Speed up logbook and history queries where ORM rows are not needed

This avoids having sqlalchemy wrap Result in ChunkedIteratorResult
which has additional overhead we do not need for these cases

* more places

* anything that uses _sorted_statistics_to_dict does not need orm rows either
2023-04-21 23:28:07 -04:00
J. Nick Koston
2663901603 Speed up LazyEventPartialState for logbook (#91840)
* Speed up LazyEventPartialState for logbook

We should avoid the getattr call since every row would
have to call the sqlalchemy key not found implemention
if we blindly getattr

* Speed up LazyEventPartialState for logbook

We should avoid the getattr call since every row would
have to call the sqlalchemy key not found implemention
if we blindly getattr
2023-04-21 23:27:23 -04:00
Erik Montnemery
9a0de43f98 Add name to tts voices (#91814)
* Add name to tts voices

* Add new file
2023-04-21 20:41:14 -04:00
Aaron Bach
c6d846453d Bump aionotion to 2023.04.2 to address imminent API change (#91786)
* Bump `aionotion` to 2023.04.0

* Bump `aionotion` to 2023.04.2 to address imminent API change

* Clean migration

* Reduce blast area

* Fix tests

* Better naming
2023-04-21 19:52:57 -04:00
Jan Bouwhuis
4de124cdd5 Follow up on comments on changes bootstrap tests (#91803)
* Remove commented code

* Add comment to explain mock

* typo
2023-04-21 21:44:14 +03:00
Franck Nijhof
5e243da470 Clean up gateway logic from Plugwise (#91769) 2023-04-21 21:38:45 +03:00
epenet
33a8eb1716 Add type hints to broadlink device/heartbeat (#91737)
* Add type hints to broadlink device/heartbeat

* Improve

* Force bool

* Revert "Force bool"

This reverts commit 65bce837d0.
2023-04-21 21:36:18 +03:00
CodingSquirrel
6137aeb30a Bump pyeconet to 0.1.20 (#90950)
chore: Bump pyeconet to 0.1.20 to fix reloading values
2023-04-21 19:25:00 +02:00
epenet
24428d98a1 Fix lingering timer in smartthings tests (#91697) 2023-04-21 18:58:07 +02:00
epenet
3d39854ffc Fix lingering timer in traccar (#91812) 2023-04-21 18:49:54 +02:00
epenet
4663ad75a0 Fix lingering timer in tplink (#91806) 2023-04-21 16:52:23 +02:00
epenet
07aef27ea8 Fix lingering timer in todoist (#91808) 2023-04-21 16:41:54 +02:00
Allen Porter
93eac97983 Relax the constraint that events must have a consistent timezone for start/end (#91788) 2023-04-21 10:25:52 -04:00
Paulus Schoutsen
78e29d526c Disallow uploading files to bypass the media dirs (#91817) 2023-04-21 10:21:20 -04:00
epenet
9665bc61f2 Fix lingering timer in usb (#91800) 2023-04-21 13:41:22 +02:00
Kevin Stillhammer
609a7ccda8 Use Selectors for waze_travel_time flows (#91778)
* Use Selectors for waze_travel_time flows

* Use correct selector option in tests

* Remove duplicate option

* Use suggested values
2023-04-21 12:57:45 +02:00
Franck Nijhof
3b0068bc85 Update opencv-python-headless to 4.7.0.72 (#91802) 2023-04-21 12:23:46 +02:00
Franck Nijhof
5b9ad6a6d3 Update psutil to 5.9.5 (#91807) 2023-04-21 12:06:28 +02:00
epenet
459af7f834 Fix lingering timer in discovery (#91804)
* Fix lingering timer in discovery

* type hint
2023-04-21 11:49:10 +02:00
Miroslav Ždrale
2198492f1b Bump openwrt-luci-rpc version to 1.1.16 (#91358) 2023-04-21 11:27:26 +02:00
Eduard van Valkenburg
09517668fe Update pysiaalarm to 3.1.0 (#91500)
* updated sia requirements

* updates because of changes in package

* linting and other small fixes

* linting and other small fixes

* small release on package that fixes issue with autospec
2023-04-21 10:51:49 +02:00
dependabot[bot]
faf78fc6b1 Bump codecov/codecov-action from 3.1.2 to 3.1.3 (#91791)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-21 10:48:28 +02:00
dependabot[bot]
0e2541c843 Bump actions/setup-python from 4.5.0 to 4.6.0 (#91792)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-21 10:47:35 +02:00
Franck Nijhof
7af63c2cdf Remove libexecinfo-dev package from Wheels jobs (#91798) 2023-04-21 10:36:13 +02:00
epenet
79ad9a3646 Shutdown coordinator on entry unload (#91748)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-21 10:07:31 +02:00
Franck Nijhof
a37a87e972 Build wheels for CPython ABI cp311 (#91736) 2023-04-21 09:10:39 +02:00
Jan Bouwhuis
92bb61d25b Let MQTT availability for snips be managed by dependencies (#91790)
MQTT availability for snips via dependencies
2023-04-21 09:05:13 +02:00
Jan Bouwhuis
0552ec834d Do not wait for mqtt at startup mqtt_statestream (#91721) 2023-04-21 09:00:48 +02:00
Jan Bouwhuis
da26b0a930 Ensure dependencies are awaited correctly when setting up integrations (#91454)
* Do not wait

* Correct tests

* Manage after dependencies stage 1

* test bootstrap dependencies

* Assert log the dependenciy is waited for

* Improve docstrings

* Assert outside callback

* Patch async_get_integrations

* Revert changes made to snips integration

* Undo changes to mqtt_statestream
2023-04-21 08:33:50 +02:00
J. Nick Koston
2e18b37291 Bump dbus-fast to 1.85.0 (#91784) 2023-04-21 08:26:44 +02:00
puddly
72414a5864 Load quirks in ZHA unit tests (#91779) 2023-04-21 08:24:39 +02:00
Franck Nijhof
f9416e1c34 Update sentry-sdk to 1.20.0 (#91771) 2023-04-21 08:23:09 +02:00
Stephan Uhle
beb0085b53 Bump pysml to 0.0.10 (#91773) 2023-04-21 08:16:32 +02:00
Franck Nijhof
65d7e48815 Update ruff to v0.0.262 (#91767) 2023-04-21 08:15:41 +02:00
kernelpanic85
7a20335943 Bump arcam-fmj to 1.3.0 (#91747)
Bump arcam-fmj to 1.3.0
2023-04-21 07:08:00 +02:00
Martin Hjelmare
1a18dc7425 Add tts entity (#91692)
* Add tts entity

* Allow passing engine id to url view

* Update async_resolve_engine

* Add and update more tests

* Fix assist pipeline tests temporarily

* Move fixtures

* Update notify platform

* Complete legacy tests

* Update media source tests

* Update async_get_text_to_speech_languages

* Address comment

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-20 22:55:46 -04:00
J. Nick Koston
458276a6a6 Bump aioesphomeapi to 13.7.1 (#91783)
changelog: https://github.com/esphome/aioesphomeapi/compare/v13.7.0...v13.7.1
2023-04-20 16:36:11 -10:00
J. Nick Koston
c407fb0861 Bump recommended esphome version to 2023.4.0 for bluetooth (#91509) 2023-04-20 15:36:12 -10:00
Michael Hansen
5080654776 VoIP listening tone and "not configured" message (#91762)
* Play tone when starting a VoIP call

* Play audio message when call is rejected

* Add option to disable tone for tests

* Send RTP audio in executor to reduce jitter

* Don't start pipeline until speech

* Bump voip utils
2023-04-20 20:30:51 -04:00
Keilin Bickar
f4f3962ee9 Bump asyncsleepiq lib to 1.3.3 (#91772) 2023-04-20 12:01:49 -10:00
epenet
2aa90b1d12 Fix lingering timer in netatmo (#91728) 2023-04-20 21:00:24 +02:00
Teemu R
dcae9a0d02 Bump python-songpal dependency (#91708) 2023-04-20 20:57:45 +02:00
epenet
821b9bdb5f Fix lingering timer in hassio (#91702) 2023-04-20 20:56:45 +02:00
Jannick
cf4c491e79 Update URLs forwarding to HA blog posts (#91698) 2023-04-20 20:55:13 +02:00
epenet
62f76a81bb Fix wallbox tests (#91752) 2023-04-20 08:42:22 -10:00
Franck Nijhof
fd3aa5338c Add Supervisor add-on discovery to Wyoming integration (#91761) 2023-04-20 20:38:39 +02:00
Nathan Spencer
8ac74c5979 Bump pylitterbot to 2023.4.0 (#91759) 2023-04-20 20:29:35 +02:00
Michael Hansen
9fdc794b36 Prefer country over language family + MATCH_ALL (#91753)
* Prefer country over language family

* More test fixes
2023-04-20 13:55:26 -04:00
Kevin McCormack
672fb44041 Add pjlink const (#91749)
* Add pjlink const

This is a precursor

* Use DOMAIN const in pjlink media player

* fixup! Add pjlink const
2023-04-20 19:27:44 +02:00
Erik Montnemery
b3d50e67cd Fix assist_pipeline tests (#91757) 2023-04-20 12:19:31 -04:00
epenet
62d38b49bc Fix lingering timers in bluetooth (part 1) (#91673)
* Fix lingering timers in bluetooth (part 1)

* Use a local var
2023-04-20 18:07:38 +02:00
Erik Montnemery
0525ce59d7 Add additional parameters to assist pipelines (#91619)
* Add additional parameters to assist pipelines

* Improve WS schema validation

* Tweak

* Add test

* Address review comments
2023-04-20 11:02:55 -04:00
Luke
b4e0a1f1fc Add new Roborock Integration (#89456)
* init roborock commit

* init commit of roborock

* removed some non-vacuum related code

* removed some non-needed constants

* removed translations

* removed options flow

* removed manual control

* remove password login

* removed go-to

* removed unneeded function and improved device_stat

* removed utils as it is unused

* typing changes in vacuum.py

* fixed test patch paths

* removed unneeded records

* removing unneeded code in tests

* remove password from strings

* removed maps in code

* changed const, reworked functions

* remove menu

* fixed tests

* 100% code coverage config_flow

* small changes

* removed unneeded patch

* bump to 0.1.7

* removed services

* removed extra functions and mop

* add () to configEntryNotReady

* moved coordinator into seperate file

* update roborock testing

* removed stale options code

* normalize username for unique id

* removed unneeded variables

* fixed linter problems

* removed stale comment

* additional pr changes

* simplify config_flow

* fix config flow test

* Apply suggestions from code review

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

* First pass at resolving PR comments

* reworked config flow

* moving vacuum attr

* attempt to clean up conflig flow more

* update package and use offline functionality

* Fixed errors and fan bug

* rework model and some other small changes

* bump version

* used default factory

* moved some client creation into coord

* fixed patch

* Update homeassistant/components/roborock/coordinator.py

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

* moved async functions into gather

* reworked gathers

* removed random line

* error catch if networking doesn't exist or timeout

* bump to 0.6.5

* fixed mocked data reference url

* change checking if we have no network information

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

---------

Co-authored-by: Allen Porter <allen.porter@gmail.com>
Co-authored-by: Allen Porter <allen@thebends.org>
2023-04-20 07:02:58 -07:00
Erik Montnemery
af193094b5 Add WS command for getting an assist pipeline (#91725)
* Add WS command for getting an assist pipeline

* Return preferred pipeline if none is specified
2023-04-20 09:15:19 -04:00
Franck Nijhof
a419c78524 Rename HomeKit to HomeKit Bridge (#91741) 2023-04-20 09:03:12 -04:00
Erik Montnemery
a98be9dc84 Test specifying pipeline in calls to async_pipeline_from_audio_stream (#91739) 2023-04-20 09:01:31 -04:00
Erik Montnemery
0429b321b8 Include matching languages in WS conversation/agent/list (#91730)
* Include matching languages in WS conversation/agent/list

* Allow specifying country
2023-04-20 08:58:43 -04:00
Erik Montnemery
768c499b6f Include matching languages in WS stt/engine/list (#91731)
* Include matching languages in WS stt/engine/list

* Allow specifying country
2023-04-20 08:57:48 -04:00
Erik Montnemery
79de27a4a9 Include matching languages in WS tts/engine/list (#91732)
* Include matching languages in WS tts/engine/list

* Allow specifying country
2023-04-20 08:56:50 -04:00
Erik Montnemery
6d619579b4 Add WS API for listing languages supported by a full assist pipeline (#91669)
* Add WS API for listing languages supported by a full assist pipeline

* Address review comments, change logic
2023-04-20 08:55:17 -04:00
Erik Montnemery
03dcb915e3 Add missing callback decorator on StorageCollectionWebsocket.ws_list_item (#91727) 2023-04-20 07:58:11 -04:00
epenet
bb2461ea93 Fix lingering timers in bluetooth (part 2) (#91674) 2023-04-20 11:04:07 +02:00
Franck Nijhof
d8c9ed3a64 Update Home Assistant base image to 2023.04.0 (#91720) 2023-04-20 10:57:18 +02:00
Jan Bouwhuis
0bcda9fe9c Make sure MQTT client is available when starting depending platforms (#91164)
* Make sure MQTT is available starting mqtt_json

* Wait for mqtt client

* Sync client connect

* Simplify

* Addiitional tests async_wait_for_mqtt_client

* Improve comment waiting for mqtt

* Improve docstr

* Do not wait unless the MQTT client is in setup

* Handle entry errors during setup

* More comments - do not clear event

* Add snips and mqtt_room

* Add manual_mqtt

* Update homeassistant/components/mqtt/__init__.py

Co-authored-by: J. Nick Koston <nick@koston.org>

* Use a fixture, improve tests

* Simplify

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-20 08:07:35 +02:00
J. Nick Koston
adc472862b Fallback to generating a new ULID on migraiton if context is missing or invalid (#91704)
* Fallback to generating a new ULID on migraiton if context is missing or invalid

It was discovered that postgresql will do a full scan if
there is a low cardinality on the index because of missing
context ids. We will now generate a ULID for the timestamp
of the row if the context data is missing or invalid

fixes #91514

* tests

* tweak

* tweak

* preen
2023-04-19 21:56:07 -04:00
J. Nick Koston
56a6244d90 Remove legacy context lookup implementation from logbook (#91710)
* Remove legacy context lookup implemention from logbook

This object can now be replaced with a simple dict

* Remove legacy context lookup implemention from logbook

This object can now be replaced with a simple dict

* scope

* fix order issue
2023-04-19 21:54:34 -04:00
Mike Degatano
24fe6dfc63 Fix from feedback on supervisor issues to repairs (#91680)
* Fix from feedback on supervisor issues to repairs

* Use cls parameter in classmethods
2023-04-20 01:02:40 +02:00
Paulus Schoutsen
6342992791 Detect eero brand for Thread (#91699) 2023-04-19 22:18:09 +02:00
epenet
8d35426c69 Fix lingering timer in ZHA cluster tests (#91693) 2023-04-19 21:04:53 +02:00
epenet
2df5d34374 Fix lingering timer in unifiprotect discovery (#91695) 2023-04-19 21:02:37 +02:00
Shay Levy
54a659c51b Bump aioshelly to 5.3.2 (#91679) 2023-04-19 20:26:45 +03:00
epenet
1797dca0b8 Fix lingering timer in ZHA tests (#91688)
* Fix lingering timer in ZHA discovery tests

* Add type hints

* Also update gateway tests
2023-04-19 13:00:25 -04:00
epenet
d70ae8afc5 Fix lingering timer in ZHA debouncers (#91685) 2023-04-19 18:45:27 +02:00
epenet
ec914815bd Fix lingering timer in ZHA light transition (#91683) 2023-04-19 18:20:17 +02:00
epenet
dace1add1f Fix lingering timer in SinopeTechnologiesThermostat (#91681) 2023-04-19 18:19:58 +02:00
Erik Montnemery
4e0b8a7363 Allow complex schemas for validating WS commands (#91655) 2023-04-19 11:37:09 -04:00
J. Nick Koston
90e92aa9d8 Add test case for matching the shelly button (#91642) 2023-04-19 11:26:21 -04:00
J. Nick Koston
573c15d67a Update bluetooth debug logging for newer bleak (#91643)
fixes

```
homeassistant/components/bluetooth/wrappers.py:268: FutureWarning: BLEDevice.rssi is deprecated and will be removed in a future version of Bleak, use AdvertisementData.rssi instead
  rssi = wrapped_backend.device.rssi
```
2023-04-19 11:26:02 -04:00
J. Nick Koston
f8fa382ebc Add test coverage for bluetooth devices being rediscoverable after they go unavailable (#91645)
* Fix bluetooth devices not being discovered after they go unavailable and reavailable

* Update homeassistant/components/bluetooth/manager.py
2023-04-19 11:25:38 -04:00
Erik Montnemery
b5ab83def4 Add test for WS conversation/agent/info (#91652) 2023-04-19 11:15:21 -04:00
Duco Sebel
9092f6a60f Handle UnsupportedError in HomeWizard (#91608)
* Handle UnsupportedEror

* Make error message more clear

* Remove debug line, whoops
2023-04-19 11:14:28 -04:00
epenet
55c723753e Cleanup device handles on ZHA controller shutdown (#91591) 2023-04-19 17:13:58 +02:00
Erik Montnemery
9bd739df82 Add assist_pipeline to default_config (#91651) 2023-04-19 10:59:43 -04:00
Erik Montnemery
eabbe8969d Adjust typing of AbstractConversationAgent.supported_languages (#91648)
* Adjust typing of AbstractConversationAgent.supported_languages

* Update test
2023-04-19 10:53:49 -04:00
Erik Montnemery
5e9bbeb4ad Refactor conversation agent WS API for listing agents (#91590)
* Refactor conversation agent WS API for listing agents

* Add conversation/agent/info back
2023-04-19 10:53:24 -04:00
David F. Mulcahey
9c784ac622 Refactor ZHA (#91476)
* rename channel -> cluster handler

* remove refs to channels and create endpoint class

* remove remaining references to channels

* fix filter

* take in latest changes from #91403

* missed one

* missed a reference
2023-04-19 10:47:07 -04:00
Erik Montnemery
090f59aaa2 Make context a mandatory parameter for async_pipeline_from_audio_stream (#91658) 2023-04-19 09:30:29 -04:00
Tom Harris
ebd20c8a7b Fix Insteon thermostat issue (#91568)
* Bump pyinsteon

* Bump pyinsteon

* Bump pyinsteon
2023-04-19 08:41:32 -04:00
J. Nick Koston
408b2171ae Handle long format context UUIDs during migration (#91657)
In https://github.com/home-assistant/core/issues/91514 is was discovered
these exist in older versions
2023-04-19 08:40:04 -04:00
Joost Lekkerkerker
162c36f108 Move Steam Entity to separate file (#91630) 2023-04-19 14:14:59 +02:00
Joost Lekkerkerker
88f5f04be8 Refactor LastFM to use shorthand attributes (#91606)
* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Apply suggestions from code review

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

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Preliminary PR for the coordinator

* Apply feedback

* Apply feedback

* Apply feedback

* Apply feedback

* Apply feedback

* Update homeassistant/components/lastfm/sensor.py

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

* Update homeassistant/components/lastfm/sensor.py

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

* Apply feedback

* Apply feedback

* Fix tests

* Update homeassistant/components/lastfm/sensor.py

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

* Fix tests

* Fix feedback

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-19 14:13:43 +02:00
Erik Montnemery
f3e6d6dfc0 Add async_get_supported_voices to tts.Provider (#91649)
* Add async_get_supported_voices to tts.Provider

* Update WS API
2023-04-19 13:47:49 +02:00
Michael Hansen
85d57a046c Add wyoming integration with stt (#91579)
* Add wyoming integration with stt/tts

* Forward config entry setup

* Use SpeechToTextEntity

* Add strings to config flow

* Move connection into config flow

* Add tests

* On load/unload used platforms

* Tweaks

* Add unload test

* Fix stt

* Add missing file

* Add test for no services

* Improve coverage

* Finish test coverage

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-19 06:10:59 -04:00
G Johansson
f74103c57e Add config flow to Workday (#72558)
* Initial commit Workday Config Flow

* Add tests

* Remove day_to_string

* new entity name, new depr. version, clean

* Use repairs for depr. warning

* Fix issue_registry moved

* tweaks

* hassfest

* Fix CI

* FlowResultType

* breaking version

* remove translation

* Fixes

* naming

* duplicates

* abort entries match

* add_suggested_values_to_schema

* various

* validate country

* abort_entries_match in option flow

* Remove country test

* remove country not exist string

* docstring exceptions

* easier

* break version

* unneeded check

* slim tests

* Fix import test

* Fix province in abort_match

* review comments

* Fix import province

* Add review fixes

* fix reviews

* Review fixes
2023-04-19 11:50:11 +02:00
Mike Degatano
a511e7d6bc Make repairs out of select supervisor issues (#90893)
* Make repairs out of select supervisor issues

* Fix comment formatting

* Add a test case for API error

* Testing and type fix
2023-04-19 08:07:38 +02:00
Michael Hansen
6b5e82ed40 Support both stt entity and legacy providers (#91633) 2023-04-18 16:59:06 -05:00
Erik Montnemery
dc3c47986b Add property supported_languages to AbstractConversationAgent (#91588)
* Add property supported_languages to AbstractConversationAgent

* Fix test

* Use MATCH_ALL for openai supported languages
2023-04-18 15:11:04 -05:00
epenet
d7eb4c4740 Bump renault-api to 0.1.13 (#91609) 2023-04-18 19:33:09 +02:00
epenet
1a787bba3f Remove webhook translation file (#91616) 2023-04-18 19:29:37 +02:00
epenet
ae0cbffdd8 Add ability to shutdown update coordinator (#91456)
* Add ability to shutdown update coordinator

* Adjust nibe_heatpump

* Add tests

* Use async

* Remove duplicate code in update coordinator

* Adjust

* Revert nibe changes - it can now be done in a follow-up PR

* Adjust

* Fix incorrect merge

* async_fire_time_changed
2023-04-18 18:56:43 +02:00
Erik Montnemery
bdffb1f298 Drop language parameter from async_get_pipeline (#91612) 2023-04-18 18:07:20 +02:00
Erik Montnemery
10606c4d1e Use the preferred assist pipeline if none was specified (#91611)
* Use the preferred assist pipeline if none was specified

* Add test
2023-04-18 17:35:33 +02:00
Erik Montnemery
016e051db6 Add timestamp to pipeline runs (#91599)
* Add timestamp to pipeline runs

* Include the timestamp in the list
2023-04-18 10:43:46 -04:00
Jan Bouwhuis
4132f08146 Remove check on remove deprecated call back for mqtt subscribe (#91464)
Remove check on deprecated callback wrapper
2023-04-18 16:02:24 +02:00
Erik Montnemery
5f7d98f15b Improve comment in conversation (#91595) 2023-04-18 09:27:14 -04:00
Paulus Schoutsen
f3897d8dae Bump hass-nabucasa to 0.65.0 (#91565) 2023-04-18 08:42:03 -04:00
Jan Bouwhuis
599cc4a5c6 Cleanup MQTT platform set up and discovery start (#91007)
Cleanup platform set up and discovery start
2023-04-18 14:33:58 +02:00
J. Nick Koston
2ec1359063 Dynamically size recorder max backlog based on available memory (#90894)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-04-18 12:35:49 +02:00
Sander Striker
f49dc65ff2 EasyEnergy: Add two additional sensors to help pick the best hours (#90543) 2023-04-18 10:18:51 +02:00
J. Nick Koston
2530031454 Use cache to lookup event type ids in logbook (#91576)
noticed we can speed this up while looking at
https://github.com/home-assistant/core/issues/91514

Note: this will not fix that issue as there is more going on there
2023-04-17 23:40:03 -04:00
Michael Hansen
f96515b90a Use language util in stt/tts (#91521)
* Use language util in stt/tts

* Test language util in stt/tts

* Fix common in TTS

* Update snapshot
2023-04-17 23:23:43 -04:00
Michael Hansen
95d16c9829 VoIP audio queue (#91577)
* Clear audio queue after every conversation turn

* Stream STT audio when voice command starts
2023-04-17 22:51:14 -04:00
Jesse Hills
aeb19831d2 ESPHome select for assist pipeline selection (#91526)
* ESPHome: Add assist pipeline select entity

* Add translation strings

* Tests
2023-04-17 22:22:11 -04:00
shbatm
ef7e3e27ba Remove previously deprecated ISY994 YAML support (#91575) 2023-04-17 15:37:19 -10:00
Jesse Hills
8d201b205f ESPHome binary sensor representing assist pipeline running (#91406)
* ESPHome binary sensor representing assist pipeline running

* Apply suggestions from code review

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Rename to call active
Simplify with attrs a little

* Load binary sensor if voice assistant on device

* Add some tests

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-17 19:52:37 -04:00
epenet
28652345bd Remove duplicate code in update coordinator (#91573) 2023-04-17 13:07:58 -10:00
Bram Kragten
c6b4c88355 Fix pipeline select (#91570)
Fix pipline select
2023-04-17 18:55:38 -04:00
shbatm
c663f7677c Remove previously deprecated ISY994 services (#91569) 2023-04-17 11:43:01 -10:00
J. Nick Koston
da4c144a5e Fix history stats query using incorrect microseconds (#91250) 2023-04-17 11:37:30 -10:00
epenet
81f018b7e5 Make Debouncer shutdown async (#91542)
* Make shutdown async in Debouncer

* Adjust test
2023-04-17 23:31:30 +02:00
Erik Montnemery
e32dacc62d Add WS API to tts (#91330)
* Add WS API to tts

* Use language util, change from entity_id to engine_id

* Fix rebase mistake
2023-04-17 22:52:19 +02:00
Michael Hansen
2819ad9a16 Bump intents dependency (#91556) 2023-04-17 22:13:31 +02:00
Erik Montnemery
b5817e40f7 Add WS API to stt (#91329) 2023-04-17 22:09:30 +02:00
Bram Kragten
e3ff7d048a Make tts/stt/conversation optional on pipeline (#91555) 2023-04-17 20:54:04 +02:00
G Johansson
afc9e4303a Create base TriggerEntity (#91128)
* Trigger entity base class

* mods

* TriggerEntity to CoordinatorTriggerEntity

* variables to variable

* mypy

* unique_id

* Fix

* docstring

* _render_templates

* split manual vs coordinator

* name

* ManualTriggerEntity

* value

* use super

* Remove ManualTriggerEntity

* Use super()
2023-04-17 19:55:54 +02:00
Paulus Schoutsen
bd22e0bd43 Allow picking a pipeline for voip devices (#91524)
* Allow picking a pipeline for voip device

* Add tests

* Fix test

* Adjust on new pipeline data
2023-04-17 12:09:11 -05:00
Martin Hjelmare
9bd12f6503 Move legacy tts (#91538)
* Move legacy tts

* Add error log on unknown platform

* Add legacy tests and delint all tests

* Consolidate log format

* Add more legacy tests

* Test default legacy provider attributes

* Remove test generated files

* Clean up after merge conflict
2023-04-17 13:01:50 -04:00
Erik Montnemery
0ecd23baee Add WS API for debugging previous assist_pipeline runs (#91541)
* Add WS API for debugging previous assist_pipeline runs

* Improve typing
2023-04-17 11:48:02 -04:00
Erik Montnemery
b597415b01 Make it non-optional to implement supported_languages in tts.Provider (#91347) 2023-04-17 10:59:21 -04:00
Erik Montnemery
8c1c7e1e4c Remove PipelineEvent.as_dict (#91546) 2023-04-17 16:33:53 +02:00
mkmer
799080eb00 Don't reload integration for AuthError in Honeywell (#91228) 2023-04-17 14:55:52 +02:00
epenet
3364f0fce2 Allow config entries unload action to be coroutine (#91531)
* Allow config entries unload action to be coroutine

* Adjust comment
2023-04-17 08:41:25 -04:00
Franck Nijhof
6a80f5fc60 Update cryptography to 40.0.2 (#91528) 2023-04-17 08:36:42 -04:00
Allen Porter
4c5746d6ed Update Todoist all day event handling following best practices (#90491) 2023-04-17 14:21:49 +02:00
J. Nick Koston
9be9defbb8 Add more rule parsers to onvif (#91533) 2023-04-17 13:41:15 +02:00
G Johansson
c3e22cfa63 SQL strings update db_url (#91537) 2023-04-17 13:37:27 +02:00
Franck Nijhof
f1bb4ed0ed Update pip constraint to allow for pip 23.1 (#91535) 2023-04-17 12:39:13 +02:00
epenet
d1b8f2987c Fix incorrect regex in translation script (#91536) 2023-04-17 12:18:34 +02:00
Martin Hjelmare
5c7d124f02 Add core as codeowner to tts (#91539) 2023-04-17 12:05:54 +02:00
krazos
717898fc92 Change entity category of IR light setting from control to config (#91382) 2023-04-17 11:10:06 +02:00
rappenze
a5b863cd75 Fix state mapping in fibaro climate (#91505) 2023-04-17 11:09:11 +02:00
starkillerOG
88bde2a914 Reolink ONVIF move read to primary callback (#91478)
* Move read to primary callback

* fix styling

* Do not raise on ConnectionResetError

* Split request.text() to .read() and decode("utf-8")
2023-04-16 22:48:39 -10:00
epenet
dd7de48efc Fix lingering timers in sia tests (#91407) 2023-04-17 10:36:13 +02:00
epenet
7c9242b4a7 Fix lingering timer in alert integration (#91452) 2023-04-17 10:35:53 +02:00
Erik Montnemery
3367e86686 Enable strict typing of assist_pipeline (#91529) 2023-04-17 10:32:14 +02:00
J. Nick Koston
9985516f80 Build protobuf upb wheels (#90812) 2023-04-17 10:30:22 +02:00
Jeef
752d5958dc Add monessen virtual integration for Intellifire (#89315) 2023-04-17 10:03:37 +02:00
J. Nick Koston
b875706bdd Default homekit to listening on all interfaces to match Home Assistant behavior (#91520) 2023-04-17 09:41:57 +02:00
rappenze
01046b88e5 Update pyfibaro to 0.7.0 (#91510) 2023-04-17 09:39:13 +02:00
jjlawren
c88d4b09c9 Handle invalidated Plex token (#91438) 2023-04-17 09:38:16 +02:00
epenet
d26160a509 Prevent combined translations in strings.json (#91334) 2023-04-17 09:36:25 +02:00
Jesse Hills
42b0602190 ESPHome: Use existing property with generated signal string (#91525) 2023-04-16 18:37:12 -10:00
Christopher Bailey
5dcc4d49c8 Bump unifiprotect to 4.8.1 (#91522) 2023-04-16 17:50:06 -10:00
Christopher Bailey
fdc80e14e6 Remove deprecated set_doorbell_message UniFi Protect service (#91523)
* Removes deprecated service

* Linting

* Linting

* More cleanup

* Linting
2023-04-16 17:30:41 -10:00
Jan Čermák
9680161701 Add RAPT Bluetooth integration (#87872)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-16 17:19:03 -10:00
Paulus Schoutsen
2b6fd0df6a VoIP: Add is active call binary sensor (#91486)
* Refactor VoIP integration for more entities

* Add active call binary sensor

* Add actually missing binary sensor files

* Improve test coverage
2023-04-16 22:59:05 -04:00
Brett Adams
58ea657fbc Add a data model to Advantage Air (#91519)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-16 15:04:30 -10:00
Brett Adams
8fe900885a Bump Advantage Air to 0.4.4 (#91147) 2023-04-16 14:56:57 -10:00
Lode Smets
263901841f Add Synology Photos support (#86894)
Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
Co-authored-by: mib1185 <mail@mib85.de>
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-16 12:29:15 -10:00
Brett Adams
5001a50876 Add MyPlace support to Advantage Air (#91108)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-16 12:27:37 -10:00
J. Nick Koston
4420201fe6 Use bluetooth data tools for address conversion in esphome (#91443) 2023-04-16 11:55:08 -10:00
J. Nick Koston
ac76a2b1df Bump bluetooth-data-tools to 0.4.0 (#91442) 2023-04-16 11:06:03 -10:00
MarkGodwin
691cd4f2ed Bump TP-Link Omada API to enforce minimum controller version (#91498)
Bump omada API
2023-04-16 16:45:02 -04:00
J. Nick Koston
4d266e99dc Bump aioruuvigateway to 0.1.0 (#91512) 2023-04-16 10:26:08 -10:00
J. Nick Koston
7f7909e0d1 Add dhcp ip update support to onvif (#91474)
* Add dhcp ip update support to onvif

If we know the mac address of the camera we can
update the config entry when the ip changes

* fix lookup

* coverage

* remove unreachable

* remove unreachable

* remove unreachable
2023-04-16 15:55:33 -04:00
Ben Morton
d16e1b4ed0 Resolve issue with switchbot blind tilt devices getting stuck in opening/closing state (#91495) 2023-04-16 09:32:51 -10:00
J. Nick Koston
fdc6cf3472 Continue recording events until final write (#91260) 2023-04-16 09:22:47 -10:00
Jack Boswell
00191ace6c Expose ping drop rate Starlink sensor (#91444) 2023-04-16 08:25:36 -10:00
Franck Nijhof
1dc0870163 Update pytest to 7.3.1 (#91497) 2023-04-16 12:21:10 -04:00
J. Nick Koston
9625444989 Automatically retry lost/timed out LIFX requests (#91157) 2023-04-16 14:27:17 +02:00
epenet
3ff03eef46 Fix lingering timer in buienradar (#91378) 2023-04-16 14:20:54 +02:00
Allen Porter
b0e0ada512 Streamline todoist test fixtures (#91405) 2023-04-16 14:20:07 +02:00
J. Nick Koston
cb6ffa5b03 Handle a few more transient onvif errors (#91473) 2023-04-16 14:06:30 +02:00
J. Nick Koston
e7373d979b Fix onvif failing to reload (#91482) 2023-04-16 14:05:10 +02:00
Mark Adkins
0cf29f0f84 Fix SharkIQ token expiration (#89357) 2023-04-16 14:04:18 +02:00
Maciej Bieniek
9d68cdca18 Remove ozone state attribute and ozone sensors from Accuweather (#91492) 2023-04-16 14:01:22 +02:00
J. Nick Koston
24538a44fc Bump onvif-zeep-async to 1.2.11 (#91472) 2023-04-16 13:56:10 +02:00
Maciej Bieniek
09c41ca2d8 Bump nextdns to version 1.4.0 (#91490) 2023-04-16 13:51:07 +02:00
epenet
bdb55a4262 Fix lingering timer in samsungtv (#91450) 2023-04-16 08:24:04 +02:00
dougiteixeira
dc03a5c81e Fix error for when column does not exist in query to SQL (#91166)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-15 12:54:56 -10:00
Michael
323d16cc21 Add ssl_cipher_list option to rest (#91078) 2023-04-15 11:22:41 -10:00
Michael Davie
59dc0ea2e0 Bump env_canada to v0.5.33 (#91468) 2023-04-15 09:44:07 -10:00
Meow
963648a333 Add SetSynchronizationPoint fallback to onvif (#86400)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-15 09:41:34 -10:00
J. Nick Koston
72dfd95831 Fix creating onvif pull point subscriptions when InitialTerminationTime is required (#91470)
* Fix creating onvif pull point subscriptions when InitialTerminationTime is required

fixes #85902

* Bump again because I got it wrong the first time.. this is why retest is good
2023-04-15 15:34:07 -04:00
Michael
67c4de90f3 Add option to select list of accepted ssl ciphers in httpx client (#91389) 2023-04-15 09:32:30 -10:00
Jan Bouwhuis
f37b1fc9f8 Improve tests subscribe connection for mqtt status (#91463)
Improve test_subscribe_connection_status
2023-04-15 15:29:31 -04:00
epenet
a018ba0696 Improve async_track_point_in_time (#91451)
* Adjust async_track_point_in_time

* Adjust name
2023-04-15 20:28:08 +02:00
Erik Montnemery
8f8a398631 Support marking an assist pipeline as preferred (#91418)
* Support marking an assist pipeline as preferred

* Adjust

* Revert unneeded change

* Send preferred pipeline id in pipeline list

* Don't use property functions for the preferred pipeline
2023-04-15 10:05:46 -04:00
Joost Lekkerkerker
714ec3f023 Update screenshot of featured integrations (#91345) 2023-04-15 15:56:46 +02:00
epenet
d173590477 Remove combined translations in integrations (#91337)
Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2023-04-15 15:51:04 +02:00
Aidan Timson
3bc023986c Fix listener running in foreground for System Bridge integration (#91391)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-15 15:49:05 +02:00
puddly
a9db39a833 Fix attribute reporting config failures in ZHA (#91403) 2023-04-15 15:48:34 +02:00
starkillerOG
adc8a13f93 Reolink prevent ONVIF push being lost due to ConnectionResetError (#91070)
* Make "Connection lost" error less likely

* Handle connection loss during ONVIF event reading

* tweak

* fix styling

* catch asyncio.CancelledError from request.text()

* missing ()

* re-raise cancelation for proper cleanup

* Simplify

* Also set webhook_reachable if connection lost

* fix styntax

* Send HTTP_OK directly after data read done

* protect agains garbage collection

* Protect shielded task (inner) not shielded future (outer)

* fix black

* Make sure exceptions are logged

* fix spelling

* fix black

* fix spelling

* Simplify using hass.async_create_task

* clarify comment

* Eleborate comment

* Update homeassistant/components/reolink/host.py

Co-authored-by: J. Nick Koston <nick@koston.org>

* Apply suggestions from bdraco

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-14 21:05:22 -04:00
J. Nick Koston
1379ad60c8 Ensure recorder always attempts clean shutdown if recorder thread raises (#91261)
* Ensure recorder run shutdown if the run loop raises

If anything goes wrong with the recorder we should
still try to shutdown cleanly

* tweak

* tests

* tests

* handle migraiton failure

* tweak comment

* naming

* order

* order

* order

* reword

* adjust test

* fixes

* threading

* failure case

* fix test

* have to wait for stop because the task blocks on thread join
2023-04-14 21:03:24 -04:00
J. Nick Koston
56cc6633f5 Use fast path for track_time_change that fires every second (#91432)
We were missing a check for `*` and were only checking
`None`. Automations use `*`, python code uses `None`.
2023-04-14 21:02:54 -04:00
J. Nick Koston
5ffd833fdf Improve performance of tracking time changes (#91433)
* Improve performance of async_track_time_interval

Uses HassJob internally to avoid looking up the function
target type every time it fires

* name
2023-04-14 21:02:13 -04:00
epenet
19a6530c3c Add ability to shutdown a Debouncer (#91439)
* Add ability to shutdown a Debouncer

* Use async_create_task
2023-04-14 21:01:21 -04:00
J. Nick Koston
698345e88b Bump yalexs to 1.3.0 (#91431)
changelog: https://github.com/bdraco/yalexs/compare/v1.2.7...v1.3.0

fixes for missing user ids
2023-04-14 20:02:09 -04:00
Harvey
bf4559719a History API entity_id validation (#90067)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-14 09:41:54 -10:00
rlippmann
f5911bcad6 Add slots to dataclasses in default_config (#91410)
* add dataclass slots to default config items

* remove slots from sun mixing
2023-04-14 14:22:39 -04:00
PatrickGlesner
f65e06dc26 Delete obsolete lines and add some missing type hints in OpenTherm Gateway integration (#90530) 2023-04-14 15:28:26 +02:00
epenet
f52fd13d6d Remove duplicate webhook test (#91420)
Fix webhook test
2023-04-14 14:52:28 +02:00
Maciej Bieniek
808830b90e Bump accuweather to version 0.5.1 (#91412)
Bump accuweather
2023-04-14 08:31:33 -04:00
Eric Severance
94f35ea968 Add webhook trigger allowed_methods/local_only options (#66494)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-04-14 12:49:12 +02:00
Erik Montnemery
b23cedeae9 Save Thread dataset store when changing preferred dataset (#91411) 2023-04-14 11:48:03 +02:00
Jarek Świerczyński
47f5160154 Allow GET in webhook triggers (#56446) 2023-04-14 09:46:00 +02:00
Franck Nijhof
ce16d8eeac Rename Particulate matter sensors to PM (#91384) 2023-04-14 09:43:15 +02:00
Jan Bouwhuis
fc8c5f1bbd Do not allow mqtt lights to set brightness to zero (#91296)
* Do not allow mqtt lights to set brightness to zero

* Loglevel to debug

* Typo
2023-04-14 09:01:29 +02:00
J. Nick Koston
025e1792db Bump onvif-zeep-async to 1.2.5 (#91399) 2023-04-14 08:37:12 +02:00
jjlawren
27f3b53872 Support Sonos announcements using websockets (#91145) 2023-04-13 20:08:53 -10:00
epenet
a061f56833 Fix lingering timer in EntityRegistryDisabledHandler (#91376)
Mark EntityRegistryDisabledHandler as cancellable
2023-04-14 06:37:49 +02:00
epenet
e39f0320df Fix lingering timers in analytics (#91363) 2023-04-14 06:37:22 +02:00
Aaron Godfrey
7061b104a9 Fix tasks with no due date from not triggering on calendar state. (#91196)
Fix tasks with no due date.

Prior to this change we were setting the start date/time to utc rather
than the user's timezone.
2023-04-13 21:12:58 -07:00
Jesse Hills
0ddccb26fa ESPHome voice assistant (#90691)
* Add ESPHome push-to-talk

* Send pipeline events to device

* Bump aioesphomeapi to 13.7.0

* Log error instead of print

* Rename variable

* lint

* Rename

* Fix type and cast

* Move event data manipulation into voice_assistant callback
Process full url

* Add a test?

* Remove import

* More tests

* Update import

* Update manifest

* fix tests

* Ugh

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-13 19:18:56 -04:00
epenet
1c0b2630da Fix lingering timers in flux_led (#91379) 2023-04-13 11:54:29 -10:00
J. Nick Koston
e1a5ad069c Improve performance of sums in the energy dashboard (#91342) 2023-04-13 11:52:38 -10:00
Erik Montnemery
4e80154ebe Rename voice_assistant to assist_pipeline (#91371)
* Rename voice_assistant to assist_pipeline

* Fix tests

* Fix voip test

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-04-13 17:25:38 -04:00
Franck Nijhof
32344a8488 Fix incorrect warn of async_update_ha_state use (#91387) 2023-04-13 22:39:03 +02:00
dependabot[bot]
b7b22b79d1 Bump actions/checkout from 3.5.0 to 3.5.2 (#91373)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 22:11:47 +02:00
epenet
9744e72d5a Add ability to auto-cancel track_time_interval (#91381) 2023-04-13 21:31:16 +02:00
Joost Lekkerkerker
77a445ee16 Add entity name translations to Picnic (#91362)
* Add translatable entities for Picnic

* Apply fixes
2023-04-13 20:27:33 +02:00
Martin Hjelmare
473cbf7f9b Add stt entity (#91230)
* Add stt entity

* Update demo platform

* Rename ProviderEntity to SpeechToTextEntity

* Fix get method

* Run all init tests for config entry setup

* Fix and test metadata from header

* Test config entry unload

* Rename get provider entity

* Test post for non existing provider

* Test entity name before addition

* Test restore state

* Use register shutdown

* Update deprecation comment
2023-04-13 13:58:35 -04:00
Franck Nijhof
22a1a6846d Warn for unneeded use of async_update_ha_state (#91372) 2023-04-13 13:49:56 -04:00
rich-kettlewell
f2997ce4cc Tado set_water_heater_timer should use water_heater domain (#91364) 2023-04-13 19:49:07 +02:00
epenet
592ac37436 Add missing mock in sharkiq tests (#91325) 2023-04-13 19:33:38 +02:00
Franck Nijhof
445b823232 Avoid task creation when calling schedule_update_ha_state without force update (#91352) 2023-04-13 18:39:03 +02:00
epenet
9e1a670e6e Fail CI on lingering timers (part 2) (#89976)
* Fail CI on lingering timers (part 2)

* Improve error message

* Adjust tts
2023-04-13 17:03:52 +02:00
Joost Lekkerkerker
a272f8dfb2 Move picnic sensor definitions (#91367)
* Move types and mixin to sensor

* Move types and mixin to sensor
2023-04-13 16:46:10 +02:00
epenet
c40836b49f Fix lingering timers in tts (#90834)
* Fix lingering timers in tts

* Improve

* Use HassJob with cancel_on_shutdown
2023-04-13 15:27:13 +02:00
epenet
c1b7aa084c Fix race condition in deconz (#91328)
* Fix race condition in deconz

* Use a new separate test
2023-04-13 15:04:21 +02:00
Duco Sebel
8ca3440f33 Bump python-homewizard-energy to 2.0.1 (#91097) 2023-04-13 13:42:35 +02:00
J. Nick Koston
274a6fd3d7 Bump aiolifx to 0.8.10 (#91324) 2023-04-13 13:38:27 +02:00
Bram Kragten
8527048f07 Update frontend to 20230411.1 (#91344) 2023-04-13 13:37:37 +02:00
Franck Nijhof
d320c73fb7 Use async_write_ha_state in tests (#91333) 2023-04-13 12:36:36 +02:00
Franck Nijhof
208a44e437 Use async_write_ha_state in generic hygrostat (#91331) 2023-04-13 09:42:12 +02:00
epenet
4a0988eb5d Adjust shutdown registration in EntityComponent (#90938)
* Adjust shutdown registration in EntityComponent

* Adjust

* Make it more explicit

* docstring
2023-04-13 08:47:04 +02:00
J. Nick Koston
6dbe67e909 Bump httpx to 0.24.0 and httpcore to 0.17.0 (#91308) 2023-04-12 20:11:59 -10:00
Paulus Schoutsen
c9d81bd217 Pipelines to default to Home Assistant agent (#91321)
* Pipelines to default to Home Assistant agent

* Tests fix
2023-04-13 00:34:19 -04:00
Paulus Schoutsen
0678ab4e45 Add VoIP entities (#91320)
* WIP

* Add VoIP entities to enable calls

* Mark voip entities as config only

* Remove commented code
2023-04-12 23:23:20 -04:00
Michael Hansen
f0c625b2ad Add language util (#91290)
* Add language util

* Add no match tests

* Update tests/util/test_language.py

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-12 20:27:09 -05:00
epenet
687c035bb2 Make auth test fixtures async (#91263)
Make auth fixtures async in tests
2023-04-12 20:43:50 -04:00
Franck Nijhof
e4d2409ca4 Update debugpy to 1.6.7 (#91267) 2023-04-12 20:43:26 -04:00
tronikos
27dd4cd261 Google Assistant SDK: Fix broadcast command for Portuguese (#91293)
Fix broadcast command for pt
2023-04-12 20:42:51 -04:00
Franck Nijhof
355404a959 Raise HomeAssistant error on failed Spotify service calls (#91299) 2023-04-12 20:42:33 -04:00
Franck Nijhof
99afab723b Fix combined translation in Pi-hole strings (#91305) 2023-04-12 20:41:27 -04:00
Franck Nijhof
02e79cf7e6 Update coverage to 7.2.3 (#91309) 2023-04-12 20:40:31 -04:00
Franck Nijhof
b2416a4020 Update sentry-sdk to 1.19.1 (#91310) 2023-04-12 20:40:19 -04:00
J. Nick Koston
4366f83ac8 Restore use of local timezone for MariaDB/MySQL in SQL integration (#91313)
* Use local timezone for recorder connection

The fix in #90335 had an unexpected side effect of
using UTC for the timezone since all recorder operations
use UTC. Since only sqlite much use the database executor
we can use a seperate connection pool which uses local time

This also ensures that the engines are disposed of
when Home Assistant is shutdown as previously we
did not cleanly disconnect

* coverage

* fix unclean shutdown in config flow

* tweaks
2023-04-12 20:24:55 -04:00
Olliver Schinagl
2d1ae6660b Dockerdev: Improve docker caching layers (#85186) 2023-04-13 00:46:54 +02:00
Joakim Sørensen
2c8b704a6d Add certificate status for cloud remote (#91277) 2023-04-13 00:38:32 +02:00
Aidan Timson
d6c954a909 Reduce startup time for System Bridge integration (#91171) 2023-04-13 00:36:51 +02:00
David Poll
ea12d7a86f Add pretty printing, key sorting, and better performance to to_json in Jinja (#91253)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-13 00:32:13 +02:00
J. Nick Koston
9b2e9b8746 Update typing on recorder pool for sqlalchemy 2.0 (#91244) 2023-04-13 00:09:15 +02:00
epenet
d483ad820c Make hass test fixture async (#91264) 2023-04-12 23:55:40 +02:00
Brig Lamoreaux
ff1fd86b91 Clean up srp_energy (#86822)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-04-12 23:47:37 +02:00
Assaf Inbal
7446ff478f Add h264_v4l2m2m codec and profiles to HomeKit cameras (#91246) 2023-04-12 10:46:21 -10:00
codyhackw
4f507e7f57 Update Inovelli Blue Series switch support in ZHA (#91254)
Co-authored-by: David F. Mulcahey <david.mulcahey@icloud.com>
2023-04-12 22:09:16 +02:00
puddly
fd7d0fff1f Bump ZHA dependencies (#91291) 2023-04-12 22:09:03 +02:00
Franck Nijhof
5b389a4dbc Remove codecov from Python test requirements (#91295) 2023-04-12 21:32:32 +02:00
Jan Bouwhuis
722b991234 Support unknown state, position or tilt for template cover (#91172)
* Support unknown state for template cover

* Remove not related changes
2023-04-12 21:23:24 +02:00
dougiteixeira
1ef6391e9c Preserves config flow information in case of error for SQL (#91142) 2023-04-12 09:03:14 -10:00
mkmer
6aa1460143 Add tests to honeywell (#87209)
* lower case aiosomecomfort

* add tests

* Test updates for 0.0.6

* lower case aiosomecomfort

* Missing changes after merge

* Add missing type hints

* Fix tests for PR#89393

* Test hold on when setting temperature

* Remove unnecessary init function

* Remove unnecessary assert

* Address missing tests
Cleanup related to comments for EM

* Move to snapshot for static test

* Updated snapshot

* Remove unnecessary assert
2023-04-12 20:34:32 +02:00
Jan Bouwhuis
e36fd5f222 Allow None device_class and UOM for mqtt entities (#91240)
* Allow None device_class and UOM for mqtt entities

* Rever not needed changes

* Revert another unwanted change
2023-04-12 19:14:16 +02:00
Paulus Schoutsen
325733158d Allow multiple instances for OpenAI (#90609)
* Allow multiple instances for OpenAI

* Add test
2023-04-12 08:29:13 -04:00
Chuck Deal
a4a8f6ebc8 Add platform state tests for vesync integration (#90466)
* Test coverage for init and common

* Update snapshot

* break setup state snapshot test across platforms

* Fix state snapshot test methods

* Remove the test_init snapshot

* Remove test_common and test_init changes

* refactor the request_mock fixture
2023-04-12 14:15:22 +02:00
Mike Knoop
e8fb7b8cf1 Fix Lutron keypad led state always off at startup (#91149) 2023-04-12 12:59:52 +02:00
Joakim Sørensen
8ce17458a9 Bump hass-nabucasa from 0.63.1 to 0.64.1 (#91271) 2023-04-12 12:38:57 +02:00
dependabot[bot]
5b89996476 Bump codecov/codecov-action from 3.1.1 to 3.1.2 (#91258)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-12 11:10:38 +02:00
G Johansson
00847ee4bc Add config flow to Brottsplatskartan (#70233)
* Brottsplatskartan Config Flow

* Fix import

* Modify sensor

* Mod version

* Mod version 2

* has_entity_name

* Fix api constructor

* Switch to issue for depr.

* Fix docstrings

* Minor cleaning

* Fix argument for bpk constructor

* remove translations

* Fix tests

* reset config

* uuid to conftest

* hassfest

* depr version

* unique id

* reset not linked changes

* review comments

* fix area none

* relevant changes

* depr version

* slim test

* unique_id

* create_entry

* review comments and tests

* fix init test

* review comments
2023-04-12 11:05:24 +02:00
Dmitry Vlasov
a409da947f Update zwave-me-ws version to 0.4.2 (#91068) 2023-04-12 10:18:01 +02:00
Matthias Alphart
0ba339e56c Run socket.gethostbyname in executor in Obihai and Sonos (#91190)
* Run  in executor in Obihai and Sonos

* fix Sonos test

* fix sonos test differently (review)
2023-04-12 09:58:27 +02:00
Jan Bouwhuis
bb15923968 Rename MQTT entry mock and cleanup (#91223)
Rename to mqtt_mock_entry and cleanup
2023-04-12 09:43:03 +02:00
G Johansson
b7cc42d135 Use RestoreSensor in derivative (#91071)
RestoreSensor
2023-04-12 08:46:55 +02:00
epenet
e277bbb513 Use tmp_path in tests (#91203)
* Use tmp_path in tests

* Use joinpath

* Prefer / operator

* Cleanup
2023-04-12 08:19:01 +02:00
epenet
4e78bcb236 Suppress CancelledError in zwave-js unload (#91222) 2023-04-12 08:09:00 +02:00
J. Nick Koston
237faf62ac Add a name to the background service call tasks (#91252) 2023-04-11 19:23:25 -10:00
Michael
0916701a0b Catch ssl errors in rest (#91074)
* catch ssl.SSLError

* add test

* fail setup on ssl error

* adjust tests
2023-04-12 06:51:41 +02:00
Erik Montnemery
2c9e9d0fde Allow UI configuration of entities exposed to voice_assistant (#91233)
* Allow UI configuration of entities exposed to voice_assistant

* Invalidate cache when settings change

* Add tests

* Expose entities to conversation by default

* Update tests
2023-04-11 22:39:40 -04:00
J. Nick Koston
e40a373c4b Call sqlite pragma optimize during periodic cleanup task (#91245)
https://www.sqlite.org/pragma.html#pragma_optimize

> To achieve the best long-term query performance without the need to do a detailed engineering analysis of the application schema and SQL, it is recommended that applications run "PRAGMA optimize" (with no arguments) just before closing each database connection. Long-running applications might also benefit from setting a timer to run "PRAGMA optimize" every few hours.

> This pragma is usually a no-op or nearly so and is very fast.

Since we keep the recorder connection open for the entire time HA
is running we fall into the long-running application bucket
2023-04-11 22:39:19 -04:00
J. Nick Koston
4e6937d20f Avoid multiple round trips to the database for history API calls (#91193)
* delete more code

* tweak

* tweak

* wrappers

* restore lost performance

* restore lost performance

* restore lost performance

* compact

* reduce

* fix refactor

* DRY

* tweak

* delete the start time state injector

* move away the legacy code

* tweak

* adjust

* adjust

* tweak

* ignore impossible

* fix a bug where the first start was changed to the start time when there was no previous history recorded before

* avoid the empty scan most cases

* postgresql

* fixes

* workaround for mariadb < 10.4

* remove unused

* remove unused

* adjust

* bail early

* tweak

* tweak

* fix more tests

* fix recorderrun being init in the future in the test

* run history tests on schema 30 as well

* Revert "run history tests on schema 30 as well"

This reverts commit d798b100ac.

* reduce

* cleanup

* tweak

* reduce

* prune

* adjust

* adjust

* adjust

* reverse later is faster because the index is in forward order and the data size we are reversing is much smaller even if we are in python code

* Revert "reverse later is faster because the index is in forward order and the data size we are reversing is much smaller even if we are in python code"

This reverts commit bf974e103e.

* fix test

* Revert "Revert "reverse later is faster because the index is in forward order and the data size we are reversing is much smaller even if we are in python code""

This reverts commit 119354499e.

* more coverage

* adjust

* fix for table order

* impossible for it to be missing

* remove some more legacy from the all states
2023-04-11 22:38:23 -04:00
epenet
7f62ed15fa Ensure entry is unloaded in azure event hub tests (#91224) 2023-04-11 16:31:05 -10:00
epenet
524832ceaf Fix config entry unload in withings tests (#91210) 2023-04-11 16:29:49 -10:00
Michael Hansen
78fec33b17 Voip integration (#90945)
* Media playback working

* Working on OPUS audio

* Before rollback

* Fix is_end

* First working pipeline

* Clean up

* Remove asserts

* Send HA version in SDP

* Use async_pipeline_from_audio_stream

* Use config flow with allowed IP

* Satisfy ruff

* Remove use of regex for SIP IP

* Use voip-utils

* Fix imports

* Add Pipeline to __all__

* Fix voice assistant tests

* Basic VoIP test

* Run hassfest

* Generate requirements

* Bump voip utils (missing requirement)

* Allow tts_options to be passed in to pipeline run

* Add config flow tests

* Update test snapshots

* More tests

* Remove get_extra_info

* Appeasing the codebot
2023-04-11 20:25:05 -04:00
rlippmann
3a72054f93 Make dataclasses in HA core slotted (#91208) 2023-04-11 07:58:28 -10:00
Erik Montnemery
eb63bc7967 Fix switch_as_x name (#91232) 2023-04-11 13:54:30 -04:00
Jan Bouwhuis
aa68d1d617 Cleanup mqtt CONFIG_SCHEMA_ENTRY (#90791) 2023-04-11 17:41:38 +02:00
Franck Nijhof
ca101cc7d1 Update Pillow to 9.5.0 (#91218) 2023-04-11 17:01:56 +02:00
Franck Nijhof
e418c66d69 Update spotipy to 2.23.0 (#91217) 2023-04-11 17:01:39 +02:00
Regev Brody
d14e96942d Bump aioswitcher to 3.3.0 (#91215)
fix: #85096 Switcher "No devices found on the network"
2023-04-11 09:16:26 -04:00
Bram Kragten
e65da42a39 Update frontend to 20230411.0 (#91219) 2023-04-11 08:35:08 -04:00
Erik Montnemery
3c8397a7b9 Flush conversation name cache when an entity is renamed (#91214) 2023-04-11 08:33:08 -04:00
Robert Svensson
0d7711f787 Fix UniFi client tracker host_name missing (#91188) 2023-04-11 11:56:55 +02:00
Pascal Reeb
62bc8df964 Fall back to polling if webhook cannot be registered on Nuki (#91013)
fix(nuki): throw warning if webhook cannot be created
2023-04-11 11:13:52 +02:00
Avi Miller
95109072b5 Remove myself as a codeowner of the LIFX integration (#91143) 2023-04-11 10:09:53 +02:00
epenet
7b3a932cd9 Remove incorrect constant usage in test (#91198) 2023-04-11 10:00:17 +02:00
epenet
2f7c5a56eb Use tmp_path in recorder tests (#91202) 2023-04-11 09:18:16 +02:00
epenet
a7093d3687 Fix flaky filesize tests (#91200)
* Fix flaky filesize tests

* Adjust constant usage

* Once more

* Use joinpath
2023-04-11 08:57:34 +02:00
Aaron Bach
504cedaa87 Bump pytile to 2023.04.0 (#91191) 2023-04-10 18:06:26 -06:00
Paulus Schoutsen
1aa8e94224 Voice Assistant: Require sample rate as input (#91182)
Require sample rate as input
2023-04-10 18:28:03 -05:00
David F. Mulcahey
0fee49a32e Cleanup ZHA from Zigpy deprecated property removal (#91180) 2023-04-10 14:16:11 -04:00
starkillerOG
e8142987a7 Reolink config flow fix custom port when USE_HTTPS not selected (#91137)
give USE_HTTPS a default
2023-04-10 12:44:25 -04:00
Diogo Gomes
86fe0c9683 Track availability of source sensor in utility meter (#91035)
* track availability of source sensor

* address review comments
2023-04-10 12:37:45 -04:00
Anthony Mattas
ee1644c24e Fix configuring Flo instances (#90990)
* Update config_flow.py

Used constant string for consistency

* Update config_flow.py

Removed code for location ID and name the integration using the username

* Update manifest.json

Updated codeowners

* Update config_flow.py

* Update config_flow.py

Formatted with black

* Update manifest.json

Updated codeowners

* Update test_config_flow.py

Updated test
2023-04-10 12:37:36 -04:00
J. Nick Koston
f2d10473eb Bump orjson to 3.8.10 (#91132)
changelog: https://github.com/ijl/orjson/compare/3.8.9...3.8.10
2023-04-10 12:23:19 -04:00
Allen Porter
cf9ada3b0e Fix all day event coercion logic (#91169) 2023-04-10 12:05:08 -04:00
J. Nick Koston
49079691d4 Reduce overhead of legacy database columns on new installs (#90246)
* Reduce overhead of legacy database columns on new installs

* Reduce overhead of legacy database columns on new installs

* Reduce overhead of legacy database columns on new installs

* Reduce overhead of legacy database columns on new installs

* not working as expected

* override the type compiler

* override the type compiler

* override the type compiler

* override the type compiler

* Apply suggestions from code review

* pgsql char1

* make entity filter test setup with old schema

* fix some more tests that were mutating state

* fix some more tests that were mutating state

* fix some more tests that were mutating state

* fix more dbstate mutations

* add shim for older tests

* split migration tests

* add coverage for purging legacy data

* tweak

* more fixes

* drop some legacy

* fix another test

* fix a few more

* add casts for postgresql in case someone deletes the schema changes table

* dry

* dry

* dry
2023-04-10 10:08:46 -04:00
Allen Porter
14b95ffe3a Relax calendar event validation to allow existing zero duration events (#91129)
Relax event valudation to allow existing zero duration events
2023-04-10 10:04:42 -04:00
J. Nick Koston
6b9d748529 Bump ulid-transform to 0.6.3 (#91133)
* Bump ulid-transform to 0.6.2

changelog: https://github.com/bdraco/ulid-transform/compare/v0.6.0...v0.6.2

32bit fixes

fixes #91092

* 0.6.3
2023-04-10 10:03:21 -04:00
J. Nick Koston
a62ede78ca Reduce space to store state_attributes and event_data for new MariaDB/MySQL databases (#91150)
Since MySQL and MariaDB support unsigned ints we can store the
hashes in 4 bytes instead of 8 bytes
2023-04-10 10:02:42 -04:00
J. Nick Koston
bd0378a961 Bump sqlalchemy to 2.0.9 (#91151)
changes: https://docs.sqlalchemy.org/en/20/changelog/changelog_20.html#change-2.0.9
2023-04-10 10:01:49 -04:00
J. Nick Koston
6e9fcbfec1 Fix false positive in SQL sensor full table scan check (#91134) 2023-04-09 19:45:08 -10:00
Brett Adams
82c80ec8d2 Bump Advantage Air to 0.4.2 (#91144) 2023-04-09 19:04:19 -10:00
hahn-th
a48ede7332 Bump homematicip to 1.0.14 (#91140)
Bump homematicip to 1.0.14
2023-04-09 18:50:34 -04:00
Jörg Thalheim
7e46d7e808 Bump transmission-rpc to 4.1.5 (#91088)
Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
2023-04-09 16:51:31 -04:00
Robert Hillis
e30c307f9f Bump aiopyarr to 23.4.0 (#91110) 2023-04-09 16:48:37 -04:00
Michael Davie
78cb0cd1e1 Bump env_canada to 0.5.32 (#91126) 2023-04-09 13:35:43 -04:00
J. Nick Koston
66b105fb21 Reduce creation of MQTT related discovery tasks (#90801)
* Reduce creation of MQTT related discovery tasks

Most of the branching can avoid creating a task as it
did not need to await for the majority of cases. We
fallback to creating a task for the cases were we do
need to await.

* comment

* revert
2023-04-08 23:14:22 -04:00
J. Nick Koston
d442f2aedb Add names to homekit tasks to better track startup problems (#90802)
* Add names to homekit tasks to better track startup problems

* fix test
2023-04-08 23:13:47 -04:00
J. Nick Koston
3be3226aaa Convert tasmota discovery callback function to a normal function (#90865)
* Convert tasmota discovery callback function to a normal function

Nothing was being awaited when the payload had not changed.
This allows us to avoid creating a task.

see #90801

* comment
2023-04-08 23:13:22 -04:00
J. Nick Koston
59872f1914 Reduce bond fallback polling interval when BPUP is alive (#90871)
* Reduce bond fallback polling interval when BPUP is alive

If push updates are alive we should not check every
10 seconds.

* tweak

* tweak

* coverage

* coverage

* coverage
2023-04-08 23:12:42 -04:00
J. Nick Koston
8fe597b7c6 Clarify HomeKit include behavior in the config flow (#91106)
* Clarify HomeKit include behavior in the config flow

If a domain is selected to be included and specific entities are
not selected in the domain, all entities will be included in that
domain.

* tweak
2023-04-08 23:12:02 -04:00
J. Nick Koston
a730ee2c43 Bump flux_led to 0.28.37 (#91099)
changes: https://github.com/Danielhiversen/flux_led/releases/tag/0.28.37
2023-04-08 22:48:01 -04:00
Allen Porter
23af02b941 Make location optional in google calendar create service (#91061) 2023-04-08 22:40:39 -04:00
J. Nick Koston
5f0d983df1 Make the device_tracker more forgiving when passed an empty ip address string (#91101)
This has come up over and over and over again

fixes #87165 fixes #51980
2023-04-08 22:32:56 -04:00
J. Nick Koston
69f751703b Fix context_user_id round trip when calling to_native (#91098)
We do not actually use this in the history or logbook
APIs so nothing broke but there was a bug here for anyone
calling this directly

fixes #91090
2023-04-08 22:31:28 -04:00
J. Nick Koston
3d1556a4a2 Switch back to using call_later for the slow entity update warning (#91067)
* Switch back to using call_later for the slow entity update warning

I had originally changed this to create a task and wait
in #41184 but that does not make sense anymore with newer
cpython as the profile now shows the original method is cheaper
(or I did it wrong the first time)

* fix missing block till done since there is no longer a task being created which would run the event loop once
2023-04-08 22:22:56 -04:00
J. Nick Koston
d0d4ab6056 Require a list of entity ids when fetching history (#90992) 2023-04-08 16:14:44 -10:00
Brett Adams
667a00e7f9 Handle all three operating modes in Advantage Air climate (#91107)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-08 13:50:26 -10:00
Michael Davie
5e2b0b23c9 Bump env_canada to v0.5.31 (#91094) 2023-04-08 09:25:58 -10:00
Diogo Gomes
fe393c84e2 Delay utility_meter until HA has started (#91017)
* increase information for end user

* only warn after home assistant has started

* delay utility_meter until HA has startED
2023-04-08 10:36:34 -04:00
tronikos
6c7f2167ff Android TV Remote: Bump androidtvremote2==0.0.7 (#91001)
* Bump androidtvremote2 to 0.0.5

* Log exception that caused disconnect

* fix test

* Revert "fix test"

This reverts commit 63fbedd208.

* Revert "Log exception that caused disconnect"

This reverts commit 110fa881ff.

* Bump androidtvremote2==0.0.7
2023-04-08 09:50:34 +02:00
Diogo Gomes
b1a23c5f73 Filtered values are no longer rounded if values are not changed/calculated (#76164)
* address 75732

* catchup

* catchup

* catchup

* catchup

* use default if precision is None

* Update homeassistant/components/filter/sensor.py

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

* fix type hint

* in progress

* refactor

* Update homeassistant/components/filter/sensor.py

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

* add *

* no need to check - review comment

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-07 23:26:07 -04:00
Allen Porter
96a3e10ff3 Bump gcal_sync to 4.1.4 (#91062) 2023-04-07 22:38:33 -04:00
J. Nick Koston
53d7e33607 Raise an issue for legacy SQL queries that will cause full table scans (#90971)
* Raise an issue for SQL queries that will cause full table scans

* Raise an issue for SQL queries that will cause full table scans

* Raise an issue for SQL queries that will cause full table scans

* Raise an issue for SQL queries that will cause full table scans

* Update homeassistant/components/sql/sensor.py

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* coverage

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-07 22:32:36 -04:00
J. Nick Koston
dbfd0d50ba Bump zeroconf to 0.56.0 (#91060) 2023-04-07 16:01:53 -10:00
Garrett
77287eb021 Bump subarulink to 0.7.6 (#91064) 2023-04-07 21:56:39 -04:00
David F. Mulcahey
90f857e926 Fix Smartthings acceleration sensor in ZHA (#91056) 2023-04-07 15:55:37 -10:00
Steven Looman
2f4325246b Make sure upnp-router is also initialized when first seen through an advertisement (#91037) 2023-04-07 13:11:31 -10:00
Joost Lekkerkerker
9b95a04c29 Bump roombapy to 1.6.8 (#91012)
* Update roombapy to 1.6.7

* Update roombapy to 1.6.8
2023-04-07 19:02:13 -04:00
David F. Mulcahey
62a6a4cd19 Bump ZHA quirks lib (#91054) 2023-04-07 19:00:03 -04:00
mrwogu
7eccef87c2 Disable lazy discover in xiaomi_miio (#82601)
* Add lazy discover config option to xiaomi_miio (#59215)

* disable lazy_discover for default

* extend tests to support lazy_discover

* revert config option
2023-04-07 16:27:59 +02:00
Joost Lekkerkerker
b8abc1350a Move plant const to separate file (#91008)
* Move plant const to separate file

* Fix feedback

* Fix feedback

* Update homeassistant/components/plant/const.py

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

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-07 13:45:40 +02:00
PatrickGlesner
06e36bcff5 Fix NMBS AttributeError (#90525)
* Fix NMBS AttributeError (Issue #90505)

* Set and use API_FAILURE

* Configure the logger to track API failures

* Remove broad exceptions and rewite logging
2023-04-07 12:13:39 +02:00
epenet
9705607db4 Fix lingering timer in cloud (#90822)
* Fix lingering timer in cloud

* Rename variable

* Improve

* Improve again

* Adjust

* Adjust

* Add property to HassJob instead

* Adjust

* Rename

* Adjust

* Adjust

* Make it read-only

* Add specific test
2023-04-07 11:38:17 +02:00
Aaron Bach
175f38b68a Bump aioambient to 2023.04.0 (#90991) 2023-04-06 19:22:27 -10:00
J. Nick Koston
fabfc59dfb Bump zeroconf to 0.55.0 (#90987) 2023-04-06 18:07:17 -10:00
J. Nick Koston
0fb210b886 Bump websockets constraint to 11.0.1+ (#90901) 2023-04-06 18:03:33 -10:00
Marc Mueller
823eb23600 Improve proxmoxve generic typing (#90948) 2023-04-06 19:32:49 -07:00
J. Nick Koston
9793cae2d3 Bump vallox-websocket-api to 3.2.1 (#90980)
unblocks https://github.com/home-assistant/core/pull/90901
which will finally fix the races in websockets
2023-04-06 21:19:10 -04:00
J. Nick Koston
398762fdd5 Resume entity id post migration after a restart (#90973)
* Restart entity id post migration after a restart

If the entity migration finished and Home Assistant was
restarted during the post migration it would never be resumed
which means the old index and space would never be recovered

* add migration resume test
2023-04-06 21:16:45 -04:00
tronikos
d4c10f0a98 Bump androidtvremote2 to 0.0.5 (#90922) 2023-04-06 17:54:45 -04:00
Heikki Partanen
0b1241cb8f Fix verisure autolock (#90960)
Fix verisure autolock #90959
2023-04-06 16:54:18 -04:00
Jan Bouwhuis
6becf523ec Fix error after losing an imap connection (#90966)
Cleanup first after losing an imap connection
2023-04-06 16:46:32 -04:00
Allen Porter
9f5dfdc67c Bump gcal_sync to 4.1.3 (#90968) 2023-04-06 16:44:34 -04:00
Allen Porter
3595e2fd5a Coerce previously persisted local calendars to have valid durations (#90970) 2023-04-06 16:41:38 -04:00
J. Nick Koston
87c22c3ad5 Fix state being cleared on disconnect with deep sleep esphome devices (#90925)
* Fix state being cleared on disconnect with deep sleep esphome devices

fixes #90923

* fix logic
2023-04-06 16:32:02 -04:00
J. Nick Koston
20d0362914 Add mysql 8.0.32 to the CI (#90898) 2023-04-06 09:33:08 -10:00
Steven Rollason
2b46734bd3 Fix command_template sensor value_template not being used if json_attributes set (#90603)
* Allow value_template to be used if json_attributes set

* Set state to None if no value_template and json_attributes used

* Refactor check for no value_template when json_attributes used

* Updated and additional unit test

* Updated to set _attr_native_value and return if value_template is None

* Update unit test docstring

* Updated test docstring based on feedback
2023-04-06 21:06:31 +02:00
starkillerOG
fca9052430 Bump reolink-aio to 0.5.10 (#90963)
* use is_doorbell instead of is_doorbell_enabled

* Bump reolink-aio to 0.5.10
2023-04-06 14:35:22 -04:00
Aaron Bach
e9f6a963a0 Bump aioambient to 2022.10.0 (#90940)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-06 14:33:41 -04:00
Erik Montnemery
44c89a6b6c Refactor handling of exposed entities for cloud Alexa and Google (#89877)
* Refactor handling of exposed entities for cloud Alexa

* Tweak WS API

* Validate assistant parameter

* Address some review comments

* Refactor handling of exposed entities for cloud Google

* Raise when attempting to expose an unknown entity

* Add tests

* Adjust cloud tests

* Allow getting expose new entities flag

* Test Alexa migration

* Test Google migration

* Add WS command cloud/google_assistant/entities/get

* Fix return value

* Update typing

* Address review comments

* Rename async_get_exposed_entities to async_get_assistant_settings
2023-04-06 13:09:45 -04:00
Bram Kragten
0d84106947 Update frontend to 20230406.1 (#90951) 2023-04-06 13:08:52 -04:00
Erik Montnemery
b3b83b7bb2 Add a pipeline store to voice_assistant (#90844)
* Add a pipeline store to voice_assistant

* Improve error handling

* Improve test coverage

* Improve test coverage

* Use StorageCollectionWebsocket

* Correct rebase
2023-04-06 12:55:16 -04:00
Marc Mueller
b2bcdf7c19 Update mypy to 1.2.0 (#90947)
* Update mypy to 1.2.0

* Add type ignore
2023-04-06 12:51:16 -04:00
epenet
385630f9b4 Fix flaky test in vesync (#90921)
* Fix flaky test in vesync

* Move sorting to the test
2023-04-06 12:51:02 -04:00
Paulus Schoutsen
86e9f6643f Allow TTS requests to resolve in the background (#90944) 2023-04-06 10:42:55 -05:00
Erik Montnemery
59a02cd08c Allow storing other items than untyped dict in StorageCollection (#90932)
Allow storing other items than untyped dict in StorageCollection
2023-04-06 10:57:00 -04:00
Erik Montnemery
8025fbf398 Remove the coronavirus integration (#90934)
* Remove the coronavirus integration

* Run hassfest and gen_requirements_all
2023-04-06 10:43:09 -04:00
Pascal Reeb
4667b27405 Handle NoURLAvailableError in Nuki component (#90927)
* fix(nuki): handle NoURLAvailableError

* only try internal URLs
2023-04-06 13:51:37 +02:00
epenet
842d89f419 Rewrite mailbox tests (#90906)
* Rewrite mailbox tests

* Use some bytes for get_media

* Split __init__ method

* Cleanup lingering timers

* Simplify message text

* Simplify msgtime

* Remove cleanup

* Use a constant
2023-04-06 13:48:19 +02:00
Erik Montnemery
fa308d8e10 Drop unused logger argument for StorageCollection() (#90913) 2023-04-06 13:28:34 +02:00
mkmer
3d426e1e2b Handle Uncaught exceptions in async_update Honeywell (#90746) 2023-04-06 10:44:13 +02:00
Michael
03e9cb233f Add entity name translations to CO2signal (#90877) 2023-04-06 10:39:49 +02:00
stickpin
e8cbf439e5 Return empty available programs list if an appliance is off during initial configuration (#90905) 2023-04-06 10:38:09 +02:00
J. Nick Koston
96c5e845e5 Guard against invalid ULIDs in contexts while recording events (#90889) 2023-04-06 10:34:54 +02:00
J. Nick Koston
19f71b3bb9 Fix entity_id migration query failing with MySQL 8.0.30 (#90895) 2023-04-06 10:34:13 +02:00
J. Nick Koston
571204fa44 Fix missing bluetooth client wrapper in bleak_retry_connector (#90885) 2023-04-06 10:33:00 +02:00
Aaron Bach
de3f25571d Bump simplisafe-python to 2023.04.0 (#90896)
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-06 10:32:35 +02:00
saschaabraham
df40b4bf9f Bump fritzconnection to 1.12.0 (#90799) 2023-04-06 10:31:43 +02:00
J. Nick Koston
e5931dfce2 Bump aiodiscover to 1.4.16 (#90903) 2023-04-06 10:30:29 +02:00
Franck Nijhof
823c506296 Merge branch 'master' into dev 2023-04-06 10:26:34 +02:00
J. Nick Koston
479a35c499 Revert "Move local calendar diagnostics to pytest.mark.freezetime" (#90899)
Revert "Move local calendar diagnostics to pytest.mark.freezetime (#90886)"

This reverts commit a01952981f.
2023-04-05 22:12:21 -07:00
Allen Porter
a01952981f Move local calendar diagnostics to pytest.mark.freezetime (#90886) 2023-04-05 19:57:09 -07:00
Jesse Hills
eb469d6a2f Move enum mapper to own file to prevent circular dependency (#90890)
* Move enum_mapper to own file to prevent circular dependency

* Add enum mapper test
2023-04-05 22:55:51 -04:00
J. Nick Koston
2fc34e7cce Bump ulid-transform 0.6.0 (#90888)
* Bump ulid-transform 0.6.0

changelog: https://github.com/bdraco/ulid-transform/compare/v0.5.1...v0.6.0

to find the source of the invalid ulids in https://github.com/home-assistant/core/issues/90887
2023-04-05 22:19:43 -04:00
Ernst Klamer
96ed4a1be1 Bump xiaomi-ble to 0.17.0 (#90806)
Add support for new Xiaomi BLE sensors
2023-04-05 22:14:21 -04:00
epenet
aa218e6f9c Fix lingering timer in device_tracker (#90824) 2023-04-05 21:12:26 -04:00
Paul Bottein
71697df3c2 Add device name fallback if no hostname in PrusaLink (#90831)
Device name fallback if no hostname in PrusaLink
2023-04-05 21:03:39 -04:00
tronikos
49468ef5d0 Android TV Remote integration (#89935)
* Android TV Remote integration

* Add diagnostics

* Remove test pem files from when api was not mocked

* Address review comments

* Remove hass.data call in test

* Store the certificate and key in /config/.storage

* update comments

* Update homeassistant/components/androidtv_remote/__init__.py

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

* import callback

* use async_generate_cert_if_missing

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2023-04-05 21:00:40 -04:00
Tom Harris
a6c5b5e238 Fix issue with Insteon All-Link Database loading (#90858)
Bump to 1.4.1
2023-04-05 20:53:44 -04:00
J. Nick Koston
4c767b2f72 Generate a seperate log message per dumped object for profiler.dump_log_objects (#90867)
Since some objects are very large we can generate overly large log messages
```
Event data for system_log_event exceed maximum size of 32768 bytes. This can cause database performance issues; Event data will not be stored
```

Reported in https://ptb.discord.com/channels/330944238910963714/427516175237382144/1093069996101472306
2023-04-05 20:53:19 -04:00
Michael
397fbc0e41 Migrate entity unique ids in PI-Hole (#90883)
* migrate entity unique ids

* Update homeassistant/components/pi_hole/__init__.py

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2023-04-05 20:52:37 -04:00
J. Nick Koston
b4fec762bc Switch to fnv-hash-fast from fnvhash (#90761)
* Switch to fnv-hash-fast from fnvhash

Replaces the pure python implemention with a fast cpp one
when available (with fallback to pure python)

changelog: https://github.com/bdraco/fnv-hash-fast/releases/tag/v0.3.1
source: https://github.com/bdraco/fnv-hash-fast/tree/main/src/fnv_hash_fast

* Apply suggestions from code review

* lint
2023-04-05 20:52:23 -04:00
J. Nick Koston
a8f1d033a0 Add MariaDB deadlock retry wrapper to database timestamp column migrations (#90880)
Add deadlock retry wrapper to timestamp column migrations

fixes #90819
2023-04-05 20:46:56 -04:00
epenet
60692bcfdb Fix lingering timers in calendar tests (#90845) 2023-04-05 20:46:05 -04:00
Erik Montnemery
4276ce96ea Use send_json_auto_id in voice_assistant tests (#90857) 2023-04-05 19:07:42 -05:00
Erik Montnemery
4104659986 Allow import of deprecated audioop module (#90869) 2023-04-05 19:03:46 -05:00
J. Nick Koston
ef5d8d83cb Add constraint for websockets to <11.0 (#90868) 2023-04-05 08:03:47 -10:00
epenet
19ab76dad9 Fix lingering timers in sensor tests (#90856) 2023-04-05 19:52:30 +02:00
shbatm
04cfd7b41d Use built-in percentage unit for ISY994 relative humidity (#90863) 2023-04-05 07:50:06 -10:00
Stackie Jia
5c2af99520 Improve the HomeKit (de)humidifier min/max humidity handling (#90854) 2023-04-05 06:35:47 -10:00
Tom Puttemans
3538844181 Support entity name translation in DSMR Reader component (#90836)
* Use translation_key instead of name for the entity names and enum values

This change allows for the translation of entity names and their values based on a key, instead of having the English text in the code

* Adjusted tariff options order

Not really wrong, but this way it is consistent with all other entities
2023-04-05 10:59:07 -04:00
epenet
c01b1eb013 Adjust async_track_time_interval name argument (#90838)
Adjust async_track_time_interval naming
2023-04-05 10:58:02 -04:00
Bram Kragten
0f8060fd00 Bump frontend to 20230405.0 (#90841) 2023-04-05 16:41:24 +02:00
Erik Montnemery
bff5b75377 Adjust OTBR channel conflict URL (#90847) 2023-04-05 16:06:00 +02:00
epenet
acec2fd7db Fix lingering timers in mailbox tests (#90830) 2023-04-05 14:09:51 +02:00
epenet
84f58543ef Fix lingering timers in image processing tests (#90829) 2023-04-05 14:09:15 +02:00
epenet
35b642c6c8 Improve type hints in calendar trigger tests (#90827) 2023-04-05 14:08:55 +02:00
J. Nick Koston
7cf1926081 Fix BLEDevice not getting updated when details change for remote scanners (#90815) 2023-04-05 12:19:37 +02:00
Penny Wood
3ddfe027fd Master RAS zone (#90825)
Fixes issue in some systems with different numbering systems
2023-04-05 12:18:54 +02:00
Jan Bouwhuis
94817f61e5 Suppress imap logging on reconnect and presume state (#90826) 2023-04-05 12:18:16 +02:00
Paul Bottein
58ac8404ef Add entity name translations to prusalink entities (#90833) 2023-04-05 12:17:00 +02:00
andarotajo
08dd0a5efa Move dwd_weather_warnings constants to separate file (#90810)
* Move constants to seperate file

* Use __package__ for the logger name

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

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-05 09:16:17 +02:00
J. Nick Koston
5eb0c35a97 Add names to common helper tasks (#90803) 2023-04-05 08:41:15 +02:00
Hans Oischinger
21a873f0af Remove myself from vicare codeowners (#90755) 2023-04-05 08:29:57 +02:00
mletenay
83704f0334 Update to goodwe v0.2.31 (#90808) 2023-04-05 07:52:05 +03:00
J. Nick Koston
02c749a111 Bump aioesphomeapi to 10.6.1 (#90816) 2023-04-04 17:22:01 -10:00
Mark Adkins
1f7ebe9249 SharkIQ Hotfix - Handle current installations by using default REGION (#90741)
* Add default region on async_setup_entry

* Move logic to migration function

* Move update logic back to setup function, but updates the config if needed.

* Remove commented out code

* Update Tests & Config setting method

* Update homeassistant/components/sharkiq/__init__.py

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

* Update homeassistant/components/sharkiq/__init__.py

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

* Accept Suggestions & Formatting

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2023-04-04 21:06:24 -04:00
Fabio De Simone
8495da1af0 Fix bluetooth_le_tracker reporting devices Home when they leave (#90641)
* fix bluetooth_le_tracker reporting devices Home when they leave

* refactor

* implement tests for BLE service_info.time check

* update bluetooth_le_tracker tests

* tweaks

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2023-04-04 20:59:57 -04:00
Vincent Knoop Pathuis
03caf63ec2 Remove getattr for Landis+Gyr Heat Meter (#90637)
* Remove getattr and update tests

* Apply suggestion for test from PR review

* Make constants capitalized
2023-04-04 22:01:35 +02:00
Patrick ZAJDA
e748f0c623 Add entity name translations to Broadlink sensors (#90783)
* Add entity name translations to Broadlink sensors

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Update tests

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Apply suggestions from code review

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

---------

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-04 15:18:41 -04:00
Guy Khmelnitsky
22a1c8f00f Bump locationsharinglib to 5.0.0 (#90790)
* Update GoogleMaps intergration dependency

* Update requirements_all.txt
2023-04-04 11:43:20 -07:00
Jan Bouwhuis
4a0d3e881a Rework MQTT config merging and adding defaults (#90529)
* Cleanup config merging and adding defaults

* Optimize and update tests

* Do not mix entry and yaml config

* Make sure hass.data is initilized

* remove check on get_mqtt_data

* Tweaks to MQTT client

* Remove None assigment mqtt client and fix mock
2023-04-04 18:12:18 +02:00
Erik Montnemery
690a0f34e5 Fix glob expansion for partial prettier run (#90787) 2023-04-04 17:27:15 +02:00
Erik Montnemery
6642db917f Remove legacy services from lock/services.yaml (#90779) 2023-04-04 17:03:43 +02:00
Erik Montnemery
28d85bc405 Run prettier on matter fixture (#90784) 2023-04-04 16:13:08 +02:00
Matija Kovacic
e7c5325ba8 Extract Supla base entity into its own file (#90781)
* Extracting Supla base entity

* Fix improper import

* Making Black happy.

* Use set for membership check

* Making ruff happy.
2023-04-04 16:11:27 +02:00
J. Nick Koston
5e3796c333 Prevent legacy device tracker from creating hundreds of executor jobs (#90690)
* Prevent legacy device tracker from creating hundreds of executor jobs

The legacy device tracker would create an executor job for
each set of extra state attributes and device name lookup.

For routers this meant hundreds of jobs

* Prevent legacy device tracker from creating hundreds of executor jobs

The legacy device tracker would create an executor job for
each set of extra state attributes and device name lookup.

For routers this meant hundreds of jobs

* tweak

* simplify
2023-04-04 08:55:25 -04:00
Martin Hjelmare
535fb34207 Move legacy stt (#90776)
* Move legacy stt to separate module

* Remove case for None as provider

* Add error log for unknown platform

* Add some tests
2023-04-04 14:52:36 +02:00
epenet
584066b809 Rename renault coordinator and base entity (#90760)
Refactor renault coordinator and base entity
2023-04-04 14:38:52 +02:00
epenet
f24634e198 Use domain constants in rest tests (#90765) 2023-04-04 14:33:41 +02:00
Martin Hjelmare
3b2127b1dc Add core as codeowner for stt integration (#90777) 2023-04-04 08:28:34 -04:00
hidaris
a9e14cd8d7 Preliminary support for Matter cover (#90262)
Preliminary support for Matter cover, curtain tilt support has not been added yet.
2023-04-04 14:16:11 +02:00
Patrick ZAJDA
e962dd64cf Add translations for Nuki entity name and battery critical state attribute (#90772)
* Add translations for Nuki entity name and battery critical state attribute

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Remove door sensor name

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

---------

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
2023-04-04 13:43:51 +02:00
Jan Bouwhuis
9b03d331ca Fix recovering imap connection triggers re-auth (#90762) 2023-04-04 12:59:57 +02:00
Michael
b4e12d34f6 Add entity name translations to SMS (#90727) 2023-04-04 12:58:42 +02:00
Maciej Bieniek
2f22613cf9 Address late review for NextDNS entity name translations (#90771) 2023-04-04 12:56:39 +02:00
J. Nick Koston
b66a99fe8a Bump sqlalchemy to 2.0.8 (#90756) 2023-04-04 12:47:54 +02:00
J. Nick Koston
0cc9b2e803 Use slots for discovery dataclasses (#90751) 2023-04-04 12:44:59 +02:00
Raman Gupta
bfea1367a7 Add ms to units comment for duration sensor device class (#90757) 2023-04-04 12:42:33 +02:00
starkillerOG
a05fbdeedb Add async_write_ha_state to Reolink select (#90764)
Add async_write_ha_state to select
2023-04-04 11:48:14 +02:00
epenet
dfa0b5439b Add myself to rest code owners (#90770) 2023-04-04 11:36:36 +02:00
Erik Montnemery
37661fe79f Update template environment from the event loop (#90758) 2023-04-04 09:52:47 +02:00
J. Nick Koston
edd93e989e Add render count to templates repr (#90753) 2023-04-03 19:38:15 -10:00
J. Nick Koston
a4bf71b655 Bump aiohomekit to 2.6.3 (#90752) 2023-04-03 19:38:04 -10:00
Paulus Schoutsen
79101b31d5 Fix frontend test again (#90754) 2023-04-04 00:11:20 -04:00
Paulus Schoutsen
6e4c78686e Run pipeline from audio stream function (#90748)
* Run pipeline from audio stream function

* Fix tests

---------

Co-authored-by: Michael Hansen <mike@rhasspy.org>
2023-04-04 00:06:51 -04:00
J. Nick Koston
4f1574b859 Prevent overly large event data from being stored in the database (#90747)
This is the same change as #87105 for events
2023-04-04 00:02:49 -04:00
J. Nick Koston
6dc55e4a3a Bump zeroconf to 0.54.0 (#90744)
* Bump zeroconf to 0.54.0

fixes incorrect addresses when the server name changes

changelog: https://github.com/python-zeroconf/python-zeroconf/compare/0.53.0...0.54.0

* fix
2023-04-04 00:02:07 -04:00
Maciej Bieniek
a854a5620f Add entity name translations to Tractive (#90738)
Add entity name translations
2023-04-04 00:01:46 -04:00
Maciej Bieniek
449f18c9c1 Add entity name translations to NextDNS (#90743)
Add entity name translations
2023-04-04 00:01:04 -04:00
Avi Miller
5e7e96c5da Remove the LIFX sensor update coordinator (#90740) 2023-04-03 17:48:32 -10:00
TheJulianJES
a58b3721ed Restore state for ZHA OnOff binary sensors (#90749)
* Restore state for ZHA OnOff binary sensors

* Let `Motion` extend `Opening`

`Motion` is just a specified version of `Opening` that only changes the device class for some motion sensors.
Since we have more "special code" in the OnOff/Opening sensor now, we also want to make sure that gets applied to `Motion` binary sensors.

* Improve comment and type

* Add test to verify that binary sensors restore last HA state
2023-04-03 22:27:57 -04:00
Erik Montnemery
59511cc3f7 Make service field filter parameters exclusive (#90728) 2023-04-03 21:46:23 +02:00
Michael
d4d77d9395 Add sort list service to Shopping List (#90671) 2023-04-03 21:34:44 +02:00
Bram Kragten
8c621699af Update frontend to 20230403.0 (#90735) 2023-04-03 21:32:40 +02:00
Emory Penney
7c6a32ebb5 Add DHCP discovery to Obihai (#88984)
* Add DHCP discovery to Obihai

* Unique ID is MAC

* Move try blocks, cleanup

* Migrate existing unique_ids

* Use PyObihai to update Unique ID

* Auth then use get_device_mac

* Config flow changes

* Reworking flow according to feedback

* Cleanup
2023-04-03 21:17:56 +02:00
Michael
fa332668d6 Add entity name translations to sun (#90732) 2023-04-03 21:15:44 +02:00
Matthias Alphart
edaee89e34 Use entity name translations in Fronius (#90463) 2023-04-03 20:05:52 +02:00
Michael
682ebbd4d5 Add entity name translations to Luftdaten (#90725) 2023-04-03 19:55:54 +02:00
Michael
7e543882fc Add entity name translations to Synology DSM (#90706)
* add entity name translation

* sort strings

* sort and capitalize strings
2023-04-03 13:08:22 -04:00
epenet
2d1bb6135c Use entity name translations in 1-wire (#90696)
* Use entity name translations in onewire

* Adjust binary sensors

* Adjust switches

* Cleanup
2023-04-03 13:07:02 -04:00
Michael
9d508ac7ae Add entity name translations to AVM Fritz!SmartHome (#90707)
* add entity name translation

* sort and capitalize

* adjust tests

* sort entities
2023-04-03 13:04:09 -04:00
Erik Montnemery
d52ef83899 Bump pychromecast to 13.0.7 (#90724) 2023-04-03 13:02:36 -04:00
Erik Montnemery
c3091fad4c Raise repair issue if OTBR and ZHA are on different channels (#90494)
* Raise repair issue if OTBR and ZHA are on different channels

* Update issues after creating or setting dataset

* Explain impact

* Add link to documentation, adjust language

* Update homeassistant/components/otbr/strings.json

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-04-03 13:02:10 -04:00
epenet
73d0124c98 Use entity name translations in Renault (#90697) 2023-04-03 18:20:11 +02:00
Michael
1cce55d176 Add entity name translations to NUT (#90709) 2023-04-03 18:18:23 +02:00
Aaron Bach
ba58fc25bd Fix missing battery sensors for SimpliSafe locks (#90722) 2023-04-03 16:42:24 +02:00
Michael
aeeadb570a Fix translation of status binary sensor in PI-Hole (#90719) 2023-04-03 16:38:54 +02:00
Patrick ZAJDA
4456557a02 Add entity name translations to Switchbot (#90600)
* Add entity name translations to Switchbot

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Apply suggestions from code review

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Fix tests

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Update homeassistant/components/switchbot/strings.json

Co-authored-by: Patrick ZAJDA <patrick@zajda.fr>

---------

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2023-04-03 10:10:30 -04:00
Franck Nijhof
186f47ba46 Add entity name translations to Plugwise (#90537)
* Add entity name translations to Plugwise

* Re-use extisting translation where possible
2023-04-03 09:25:02 -04:00
Pascal Reeb
e72c2029cb Add Warning in the issue registry if a HTTPS webhook is used for Nuki (#90718)
feat(nuki): create issue when https webhook URL was created
2023-04-03 14:49:15 +02:00
Felix Rotthowe
bacbe4aa58 Fix Livisi climate min/max temperature (#90712)
* Correctly set livisi climate min/max temp

* fix imports
2023-04-03 14:17:57 +02:00
epenet
45038bac16 Use entity name translations in SFR Box (#90698) 2023-04-03 14:04:02 +02:00
Michael
628142527d Add entity name translations to Pi-hole (#90713) 2023-04-03 14:00:27 +02:00
Matija Kovacic
34245a6b3d Add support for Supla garage doors (#90593)
* Adding support for additional Supla channel types

Newly supported channel types are - CONTROLLINGTHEGARAGEDOOR, DIMMER, RGBLIGHTING.

* Remove light platform additions

* Remove light devices

* Update homeassistant/components/supla/cover.py

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

* Removing some Black automatic formatting.

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-03 13:56:40 +02:00
Maciej Bieniek
1eadc63cd5 Add entity name translations to BraviaTV (#90702)
Add entity name translations
2023-04-03 07:40:52 -04:00
Michael
33a4c2c162 Add entity name translations to AVM Fritz!Tools (#90703)
* add entity name translation

* apply suggestions

* sort strings
2023-04-03 07:35:35 -04:00
Maciej Bieniek
eb748416ed Use the default entity names in GIOS (#90700) 2023-04-03 12:42:55 +02:00
Maciej Bieniek
34041c7564 Add entity name translations to NAM (#90681) 2023-04-03 12:37:01 +02:00
andarotajo
37556a57af Upgrade dwd_weather_warnings dependency dwdwfsapi to 1.0.6 (#90683) 2023-04-03 11:12:35 +02:00
Maciej Bieniek
cacd6708f0 Use the default entity names in Airly (#90693) 2023-04-03 10:50:24 +02:00
Stephan Uhle
3c536e31eb Fix ha version in EDL21 deprecation warning (#90699)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-04-03 10:48:06 +02:00
G Johansson
724eb7f2bd Remove Darksky integration (#90322) 2023-04-03 10:34:36 +02:00
Nerdix
d539bddabc Correct handling if WIFI combine suffix is "None" (#90528)
* Correct handling of "None" WIFI combine suffix

* Update tests/components/unifi/test_config_flow.py

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

* Update tests/components/unifi/test_config_flow.py

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

---------

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
2023-04-03 09:47:08 +02:00
Jan Bouwhuis
580b20b0a8 Deprecate imap_content_sensor (#90429)
* Deprecate imap_content_sensor

* Rename unique_id to issue_id

* Migrate config to imap entry

* Improve dialogs

* Improve dialog texts

* Add repairs.py to .coveragerc

* Test the integration component setup

* Text tweak

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

* Use flow for creating entries

* Rename schema add tests

* Patch client instead

* Add tests repairs - refactor async_step_confirm

* Comments test, correct calling next step

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-04-03 07:27:41 +02:00
starkillerOG
ddb1610e90 Reolink late review comments (#90668)
review comments
2023-04-03 07:22:38 +02:00
J. Nick Koston
73714a6656 Ensure system log does not raise while processing logger messages (#90652) 2023-04-02 15:18:50 -10:00
J. Nick Koston
51ff027fce Add object source logger to profiler (#90650)
* Add object source logger to profiler

* fixes

* cleanup

* tweaks

* logging

* logging

* too intensive

* adjust

* Update homeassistant/bootstrap.py

* fixes

* fixes

* coverage
2023-04-02 20:54:21 -04:00
Maciej Bieniek
2229a63acd Fix default sensor entity name for PM1 (#90684)
Fix PM1 text
2023-04-02 20:53:00 -04:00
J. Nick Koston
17719663f0 Fix memory churn in state templates (#90685)
* Fix memory churn in state templates

The LRU for state templates was limited to 512 states. As soon
as it was exaused, system performance would tank as each template
that iterated all states would have to create and GC any state
> 512

* does it scale?

* avoid copy on all

* comment

* preen

* cover

* cover

* comments

* comments

* comments

* preen

* preen
2023-04-02 20:51:25 -04:00
mletenay
0198c751b4 Update goodwe library to v0.2.30 (#90607) 2023-04-02 20:25:29 -04:00
Michael Davie
6a6b6cf826 Bump env_canada to v0.5.30 (#90644) 2023-04-02 20:20:11 -04:00
Patrick ZAJDA
22fd6138bd Add entity name translations for Nest sensors (#90677)
Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
2023-04-02 20:19:03 -04:00
J. Nick Koston
368d1c9b54 Bump zeroconf to 0.53.0 (#90682) 2023-04-02 13:32:00 -10:00
Paulus Schoutsen
c5a87addc1 Fix frontend test (#90679) 2023-04-02 14:28:52 -04:00
Maciej Bieniek
fc81b82932 Add entity name translations to GIOS (#90655)
* Add entity name translations

* Update tests
2023-04-02 14:25:38 -04:00
Maciej Bieniek
d32fb7c22f Add entity name translations to Airly (#90656)
Add entity name translations
2023-04-02 14:24:40 -04:00
tronikos
b52fab0f6d Rename Android TV to Android Debug Bridge (#90657)
* Rename Android TV to Android debug bridge

* More renaming
2023-04-02 14:22:16 -04:00
J. Nick Koston
17270979e6 Bump zeroconf to 0.52.0 (#90660)
* Bump zeroconf to 0.52.0

Switch to using the new ip_addresses_by_version which avoids
all the ip address conversions

* updates
2023-04-02 14:09:44 -04:00
Paulus Schoutsen
4a4d3201f5 Fix voice assistant error variable (#90658) 2023-04-01 22:34:52 -05:00
J. Nick Koston
84292d4797 Cleanup some duplicate code in recorder statistics (#90549)
* Cleanup some duplicate code in recorder statistics

* more cleanup

* reduce

* reduce
2023-04-01 21:40:14 -04:00
Maciej Bieniek
5fc103947f Add entity name translations to Brother (#90634)
* Add entity name translations

* Fix sensor name

* Update tests

* Suggested change
2023-04-01 21:39:46 -04:00
Bram Kragten
2852fe6786 Update frontend to 20230401.0 (#90646) 2023-04-01 15:21:51 -04:00
Jan Bouwhuis
9965d9d81d Fix mqtt device_tracker is not reloading yaml (#90639) 2023-04-01 15:17:53 -04:00
J. Nick Koston
8263c3de23 Bump zeroconf to 0.51.0 (#90622)
* Bump zeroconf to 0.50.0

changelog: https://github.com/python-zeroconf/python-zeroconf/compare/0.47.4...0.50.0

* bump to 51
2023-04-01 15:15:17 -04:00
J. Nick Koston
00a4279d64 Speed up backups (#90613) 2023-04-01 15:14:59 -04:00
starkillerOG
b47ac524ea Use async_timeout instead of asyncio.wait_for (#90496)
* Use async_timeout instead of asyncio.wait_for

* fix imports

* fix imports

* break out Event.wait patch

* Update tests/components/reolink/conftest.py

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

* Simplify

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2023-04-01 17:47:31 +02:00
nono
9cab05c4b9 Fix Rest switch init was not retrying if unreachable at setup (#90627)
* Fix Rest switch init was not retrying if unreachable at setup

* pass error log to platformnotready
prevents spamming the same message in logs.
2023-04-01 17:45:24 +02:00
J. Nick Koston
f1fa63281e Adjust context id variable names in the logbook processor to improve readability (#90617)
Adjust some variable names in the logbook process to improve readablity

There were some places were we used context_id that should have been context_id_bin
2023-04-01 10:24:02 -04:00
J. Nick Koston
e94c11371d Bump securetar to 2023.3.0 (#90612)
changelog: https://github.com/pvizeli/securetar/compare/2022.02.0...2023.3.0
2023-04-01 10:22:26 -04:00
Michael Hansen
90d81e9844 Use webrcvad to detect silence in pipelines (#90610)
* Add webrtcvad requirement

* Use webrcvad for voice command segmenting

* Add vad test
2023-03-31 23:55:07 -04:00
J. Nick Koston
44b35fea47 Speed up entity filter when there are many glob matchers (#90615)
* Speed up entity filter when there are many glob matchers

Since we do no care about which glob matches we can
combine all the translated globs into a single regex
which reduces the overhead

* delete unused code

* preen
2023-03-31 21:18:29 -04:00
J. Nick Koston
3e94f2a502 Small speed up to _collection_changed (#90621)
attrgetter builds a fast method which happens in native code
4664a7cf68/Modules/_operator.c (L1413)
2023-03-31 21:15:36 -04:00
Joakim Sørensen
3e59687902 Only limit stats to started add-ons (#90611) 2023-03-31 23:57:39 +02:00
J. Nick Koston
f4c341253b Avoid sorting domain/all states in templates (#90608) 2023-03-31 11:27:55 -10:00
Raman Gupta
6db96847d5 Bump zwave-js-server-python to 0.47.3 (#90606)
* Bump zwave-js-server-python to 0.47.2

* Bump zwave-js-server-python to 0.47.3
2023-03-31 15:39:08 -04:00
puddly
3f398818c5 Perform an energy scan when downloading ZHA diagnostics (#90605) 2023-03-31 15:37:00 -04:00
Paulus Schoutsen
01a05340c6 Voice Assistant: improve error handling (#90541)
Co-authored-by: Michael Hansen <mike@rhasspy.org>
2023-03-31 14:04:22 -05:00
puddly
84eb9c5f97 Fix ZHA definition error on received command (#90602)
* Fix use of deprecated command schema access

* Add a unit test
2023-03-31 14:53:42 -04:00
Paulus Schoutsen
ad26317b75 Conversation: allow getting agent info (#90540)
* Conversation: allow getting agent info

* Add unset agenet back
2023-03-31 14:36:39 -04:00
Paulus Schoutsen
8018be28ee TTS: allow resolving engine and test supported options (#90539)
TTS: allow resolving engine
2023-03-31 13:34:42 -05:00
J. Nick Koston
44eaf70625 Make sonos activity check a background task (#90553)
Ensures the task is canceled at shutdown if the device
is offline and the ping is still in progress
2023-03-31 14:33:44 -04:00
starkillerOG
09d54428c9 Bump reolink-aio to 0.5.9 (#90590) 2023-03-31 14:31:04 -04:00
Martin Hjelmare
8256d9b472 Remove xbox_live integration (#90592) 2023-03-31 14:30:04 -04:00
epenet
611d4135fd Add ComponentProtocol to improve type checking (#90586) 2023-03-31 14:19:58 -04:00
Bram Kragten
03137feba5 Update frontend to 20230331.0 (#90594) 2023-03-31 14:15:49 -04:00
J. Nick Koston
c566303edb Avoid writing state to all esphome entities at shutdown (#90555) 2023-03-31 18:23:05 +02:00
Erik Montnemery
149e610bca Drop __eq__ dunder method from Entity (#90585) 2023-03-31 17:03:02 +02:00
Franck Nijhof
469321157d Raise on invalid (dis)arm code in manual alarm (#90579) 2023-03-31 16:08:16 +02:00
Franck Nijhof
8e77d215e7 Raise on invalid (dis)arm code in manual mqtt alarm (#90584) 2023-03-31 16:08:02 +02:00
Erik Montnemery
9a17c437ad Remove some dead code from google_assistant (#90581) 2023-03-31 15:59:48 +02:00
Erik Montnemery
3467f4674e Remove unnecessary calls to async_update_entry from async_migrate_entry (#90575) 2023-03-31 15:53:35 +02:00
Erik Montnemery
1ca7f0dc6a Tweak yalexs_ble translations (#90582) 2023-03-31 15:50:49 +02:00
Franck Nijhof
23372e8bc4 Add arming/disarming state to Verisure (#90577) 2023-03-31 14:55:48 +02:00
epenet
4f54e33f67 Allow removal of sensor settings in scrape (#90412)
* Allow removal of sensor settings in scrape

* Adjust

* Adjust

* Add comment

* Simplify

* Simplify

* Adjust

* Don't allow empty string

* Only allow None

* Use default as None

* Use sentinel "none"

* Not needed

* Adjust unit of measurement

* Add translation keys for "none"

* Use translations

* Sort

* Add enum and timestamp

* Use translation references

* Remove default and set suggested_values

* Disallow enum device class

* Adjust tests

* Adjust _strip_sentinel
2023-03-31 14:34:20 +02:00
Hans Oischinger
ea32cc5d92 Refactor vicare config_flow tests (#90568)
* Refactor vicare config_flow tests

* Address review comments

* Remove unused parameters
2023-03-31 14:33:58 +02:00
Rami Mosleh
8cbe394028 Use get_ha_sensor_data method to update glances sensors (#83983)
* Use `get_ha_sensor_data` method to update sensor state

* update tests

* Use `get_ha_sensor_data` to validate connection

* Update test_sensor.py

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2023-03-31 14:27:37 +02:00
epenet
a616ac2b60 Move attribution constants to entity attributes (#90519)
* Move attribution constants to entity attributes

* Adjust meteo france

* Adjust meteoclimatic

* Adjust nws
2023-03-31 14:25:49 +02:00
epenet
ab699d17a5 Ensure numeric sensors have a valid value (#85605)
* Ensure numeric sensors have a valid value

* Flake8
2023-03-31 14:12:51 +02:00
Timm Schäuble
2e26b6e0cc Add attachments to simplepush (#81033)
* Add attachments

* Fix looking for attachment keywords in values

* Improve attachment input format

* Implement better approach to attachment parsing

* Make ruff happy

* Adjust attachment format and implementation according to comment from emontnemery
2023-03-31 14:10:12 +02:00
Franck Nijhof
28736e2ce4 Update orjson to 3.8.9 (#90570) 2023-03-31 13:59:49 +02:00
Franck Nijhof
6153f17155 Update sentry-sdk to 1.18.0 (#90571) 2023-03-31 13:58:53 +02:00
luar123
c7e8fc9f9d Use more meaningful states for snapcast groups and clients (#77449)
* Show muted snapcast groups as idle and use playing/idle state instead of on state for clients

* New module constant STREAM_STATUS

* Fix return type hint in snapcast

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-03-31 12:38:23 +02:00
Franck Nijhof
6bad5f02c6 Update black to 23.3.0 (#90569) 2023-03-31 12:20:08 +02:00
Franck Nijhof
b24a5750c3 Add CI timeout to codecov job (#90572) 2023-03-31 12:19:45 +02:00
Franck Nijhof
b9f0701336 Update ruff to v0.0.260 (#90566) 2023-03-31 11:43:28 +02:00
Hans Oischinger
b3887a633d Bump PyVicare to 2.25.0 (#90536) 2023-03-31 09:44:30 +02:00
epenet
d0c38c1e12 Move icon constants to entity attributes (#90518)
* Move icon constants to attribute

* Adjust test
2023-03-31 09:34:17 +02:00
J. Nick Koston
2e0ecf9bd9 Avoid more task creation in the discovery helper (#90552)
* Avoid more task creation in the discovery helper

There is no longer a reason to awaiti the jobs being dispatched
since nothing was using the result and there is no risk of
job being garbage collected prematurely anymore since
the task revamp

* Update homeassistant/helpers/discovery.py
2023-03-31 09:10:55 +02:00
J. Nick Koston
ed673a1b35 Avoid creating a task on callback in owntracks when using mqtt (#90548)
Nothing was being awaited in the callback. It did not
need to be a coro
2023-03-31 09:05:56 +02:00
puddly
3a3c738945 Bump ZHA dependencies (#90547)
* Bump ZHA dependencies

* Ensure the network is formed on channel 15 when multi-PAN is in use
2023-03-30 20:55:01 -04:00
Franck Nijhof
47af325a88 Add entity name translations to LaMetric (#90538)
* Add entity name translations to LaMetric

* Consistency
2023-03-30 20:54:31 -04:00
J. Nick Koston
a2efe2445a Fix migration when encountering a NULL entity_id/event_type (#90542)
* Fix migration when encountering a NULL entity_id/event_type

reported in #beta on discord

* simplify
2023-03-30 20:54:13 -04:00
J. Nick Koston
6b0c98045e Handle garbage in the context_id column during migration (#90544)
* Handle garbage in the context_id column during migration

* Update homeassistant/components/recorder/migration.py

* lint
2023-03-30 20:53:47 -04:00
Bram Kragten
6f89390251 Update frontend to 20230330.0 (#90524) 2023-03-30 13:48:21 -04:00
rikroe
565f311f5c Add EV charging remote services for BMW/Mini (#88759)
* Add select for EV charging to bmw_connected_drive

* Use snapshot for select tests, split select_option tests

* Apply suggestions from code review

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

* Further adjustments from code review

---------

Co-authored-by: rikroe <rikroe@users.noreply.github.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-03-30 19:37:03 +02:00
TheJulianJES
fd55d0f2dd Migrate old ZHA IasZone sensor state to zigpy cache (#90508)
* Migrate old ZHA IasZone sensor state to zigpy cache

* Use correct type for ZoneStatus

* Test that migration happens

* Test that migration only happens once

* Fix parametrize
2023-03-30 11:15:12 -04:00
Franck Nijhof
cf628dbf23 Add a device to the sun (#90517) 2023-03-30 10:38:35 -04:00
Paulus Schoutsen
87c4659520 Unregister webhook when registering webhook with nuki fials (#90514) 2023-03-30 15:23:13 +02:00
Paulus Schoutsen
0b72cc9f5e OpenAI to rely on built-in areas variable (#90481) 2023-03-30 15:21:45 +02:00
Erik Montnemery
976efb437b Include channel in response to WS thread/list_datasets (#90493) 2023-03-30 09:16:27 -04:00
Petro31
642984a042 Fix for is_hidden_entity when using it in select, selectattr, reject, and rejectattr (#90512)
fix
2023-03-30 09:14:58 -04:00
Maciej Bieniek
8d21e2b168 Use metric units internally in Accuweather integration (#90444)
* Use metric units internally

* Remove unnecessary code

* Simplify sensor classes

* Remove AccuWeatherForecastSensor class

* Update wind speed value in test

* Return suggested_unit_of_measurement for wind entities

* Clean test

* Use _attr_suggested_unit_of_measurement

* Remove _get_suggested_unit()

* Remove unnecessarey code
2023-03-30 13:11:33 +02:00
Erik Montnemery
ead88cc3f8 Add preferred wind speed unit to unit systems (#90504)
* Add preferred wind speed unit to unit systems

* Tweak

* Update tests
2023-03-30 12:54:12 +02:00
Erik Montnemery
b316ffff9b Rename hassfest _validate_dependencies_exist (#90503) 2023-03-30 12:05:11 +02:00
Aarni Koskela
196f5702b8 Make hassfest.dependencies faster with multiprocessing (#81486)
* hassfest.dependencies: split to two loops

* hassfest.dependencies: use multiprocessing for import scan
2023-03-30 11:25:14 +02:00
Nalin Mahajan
0e7d7f32c1 Add new control4 helper function (#90234)
* Add new helper function to retrieve device variables and update light platform

* seperate try catch from helper function and fix typing

* Change helper function name

* Remove unnecessary forced type changes

* More type changes
2023-03-30 10:33:01 +02:00
epenet
3599515325 Add missing strings to sensor integration (#90475)
* Add missing strings to sensor integration

* Enumeration

* Apply suggestion

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

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2023-03-30 10:21:11 +02:00
Vincent Knoop Pathuis
a7040a0487 Add Landis+Gyr MWh-readings from ultraheat-api (#89937)
* Use mwh values from ultraheat api when available

Remove manifest cleanup from PR

Remove added device class from this PR

Restore entity registry fixture

Replace filter by attr_entity_registry_enabled_default

* Catchup with #90182 and #90183

* Add comment explaining disabling some entities

* Add parameterisation of test cases
2023-03-30 09:07:47 +02:00
jellenijhof12
ba32e28fc6 Add dimmable lights support to niko home control (#90141)
* added support for dimmable lights and auto host discover

* split up merge request

* fixed feedback brightness support

* fixed feedback

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

* resolved feedback

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-03-30 08:59:29 +02:00
Thijs W
053ed3cfdc Add reauth to frontier_silicon config flow (#90443)
* Add reauth to frontier_silicon config flow

* Update patch target

* Apply suggestions from code review

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

* Add reauth_successful to strings.json

* Don't manually set "title_placeholders"

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

* Apply suggestions from code review

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

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-03-30 08:49:46 +02:00
Thijs W
40cb0eeb68 Add missing strings in frontier_silicon (#90446)
Improve confirm message for ssdp flow
2023-03-30 08:05:24 +02:00
luar123
f0710bae06 Add config-flow to Snapcast (#80288)
* initial stab at snapcast config flow

* fix linting errors

* Fix linter errors

* Add import flow, support unloading

* Add test for import flow

* Add dataclass and remove unique ID in config-flow

* remove translations

* Apply suggestions from code review

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

* Refactor config flow and terminate connection

* Rename test_config_flow.py

* Fix tests

* Minor fixes

* Make mock_create_server a fixture

* Combine tests

* Abort if entry already exists

* Apply suggestions from code review

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

* Move HomeAssistantSnapcast to own file. Clean-up last commit

* Split import flow from user flow. Fix tests.

* Use explicit asserts. Add default values to dataclass

* Change entry title to Snapcast

---------

Co-authored-by: Barrett Lowe <barrett.lowe@gmail.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-03-30 07:42:09 +02:00
Raman Gupta
fc78290e2f Remove callback decorators where unneeded (#90478)
* Remove callback decorators where unneeded

* revert extra replace
2023-03-30 00:04:39 +02:00
RenierM26
93d1961aae Use auth token in Ezviz (#54663)
* Initial commit

* Revert "Initial commit"

This reverts commit 452027f1a3c1be186cedd4115cea6928917c9467.

* Change ezviz to token auth

* Bump API version.

* Add fix for token expired. Fix options update and unload.

* Fix tests (PLATFORM to PLATFORM_BY_TYPE)

* Uses and stores token only, added reauth step when token expires.

* Add tests MFA code exceptions.

* Fix tests.

* Remove redundant try/except blocks.

* Rebase fixes.

* Fix errors in reauth config flow

* Implement recommendations

* Fix typing error in config_flow

* Fix tests after rebase, readd camera check on init

* Change to platform setup

* Cleanup init.

* Test for MFA required under user form

* Remove useless if block.

* Fix formating after rebase

* Fix formating.

* No longer stored in the repository

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2023-03-29 17:43:54 -04:00
J. Nick Koston
4c21caa917 Fix filesize doing blocking I/O in the event loop (#90479)
Fix filesize doing I/O in the event loop
2023-03-29 17:26:28 -04:00
starkillerOG
1023628821 Bump reolink-aio to 0.5.8 (#90467) 2023-03-29 17:26:05 -04:00
Guido Schmitz
706e6597d8 Add entity name translations for devolo Home Network (#90471) 2023-03-29 17:25:33 -04:00
J. Nick Koston
3bebd4318e Bump yalexs-ble to 2.1.14 (#90474)
changelog: https://github.com/bdraco/yalexs-ble/compare/v2.1.13...v2.1.14

reduces ble traffic (fixes a bug were we were checking when we did not need to be)
2023-03-29 17:24:47 -04:00
puddly
d0a492644d Correctly load ZHA settings from API when integration is not running (#90476)
Correctly load settings from the zigpy database when ZHA is not running
2023-03-29 17:24:26 -04:00
dougiteixeira
43a7247dde Move ProxmoxEntity to entity.py (#90480)
* Move ProxmoxEntity to entity.py

* Update homeassistant/components/proxmoxve/entity.py

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

* Update homeassistant/components/proxmoxve/entity.py

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

* Update homeassistant/components/proxmoxve/entity.py

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

* Update homeassistant/components/proxmoxve/entity.py

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

* Update homeassistant/components/proxmoxve/binary_sensor.py

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

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2023-03-29 23:04:37 +02:00
Franck Nijhof
7010447b04 Bump version to 2023.5.0dev0 (#90477) 2023-03-29 16:46:32 -04:00
Chris Xiao
7c778847e7 Add config flow to qBittorrent (#82560)
* qbittorrent: implement config_flow

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: add English translations

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: create sensors with config_flow

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: set unique_id and icon

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: add tests for config_flow

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: detect duplicate config entries

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: import YAML config

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: update coveragerc

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>

* qbittorrent: delete translations file

* create `deprecated_yaml` issue in `setup_platform`

* move qbittorrent test fixtures to conftest.py

* improve code quality & remove wrong unique_id

* keep PLATFORM_SCHEMA until YAML support is removed

* remove CONF_NAME in config entry, fix setup_entry

* improve test suite

* clean up QBittorrentSensor class

* improve user flow tests

* explicit result assertion & minor tweaks in tests

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

* implement entry unloading

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

* add type hints

* tweak config_flow data handling

---------

Signed-off-by: Chris Xiao <30990835+chrisx8@users.noreply.github.com>
Co-authored-by: epenet <epenet@users.noreply.github.com>
2023-03-29 22:13:41 +02:00
1421 changed files with 80894 additions and 22885 deletions

View File

@@ -226,6 +226,7 @@ omit =
homeassistant/components/dublin_bus_transport/sensor.py
homeassistant/components/dunehd/__init__.py
homeassistant/components/dunehd/media_player.py
homeassistant/components/dwd_weather_warnings/const.py
homeassistant/components/dwd_weather_warnings/sensor.py
homeassistant/components/dweet/*
homeassistant/components/ebox/sensor.py
@@ -385,7 +386,10 @@ omit =
homeassistant/components/foscam/camera.py
homeassistant/components/foursquare/*
homeassistant/components/free_mobile/notify.py
homeassistant/components/freebox/camera.py
homeassistant/components/freebox/device_tracker.py
homeassistant/components/freebox/home_base.py
homeassistant/components/freebox/router.py
homeassistant/components/freebox/sensor.py
homeassistant/components/freebox/switch.py
homeassistant/components/fritz/common.py
@@ -479,8 +483,6 @@ omit =
homeassistant/components/homematic/sensor.py
homeassistant/components/homematic/switch.py
homeassistant/components/homeworks/*
homeassistant/components/honeywell/__init__.py
homeassistant/components/honeywell/climate.py
homeassistant/components/horizon/media_player.py
homeassistant/components/hp_ilo/sensor.py
homeassistant/components/huawei_lte/__init__.py
@@ -831,6 +833,7 @@ omit =
homeassistant/components/onvif/event.py
homeassistant/components/onvif/parsers.py
homeassistant/components/onvif/sensor.py
homeassistant/components/onvif/util.py
homeassistant/components/open_meteo/weather.py
homeassistant/components/opencv/*
homeassistant/components/openevse/sensor.py
@@ -938,6 +941,7 @@ omit =
homeassistant/components/pushover/notify.py
homeassistant/components/pushsafer/notify.py
homeassistant/components/pyload/sensor.py
homeassistant/components/qbittorrent/__init__.py
homeassistant/components/qbittorrent/sensor.py
homeassistant/components/qnap/sensor.py
homeassistant/components/qrcode/image_processing.py
@@ -995,6 +999,7 @@ omit =
homeassistant/components/ridwell/switch.py
homeassistant/components/ring/camera.py
homeassistant/components/ripple/sensor.py
homeassistant/components/roborock/coordinator.py
homeassistant/components/rocketchat/notify.py
homeassistant/components/roomba/__init__.py
homeassistant/components/roomba/binary_sensor.py
@@ -1100,7 +1105,9 @@ omit =
homeassistant/components/sms/notify.py
homeassistant/components/sms/sensor.py
homeassistant/components/smtp/notify.py
homeassistant/components/snapcast/*
homeassistant/components/snapcast/__init__.py
homeassistant/components/snapcast/media_player.py
homeassistant/components/snapcast/server.py
homeassistant/components/snmp/device_tracker.py
homeassistant/components/snmp/sensor.py
homeassistant/components/snmp/switch.py
@@ -1379,7 +1386,6 @@ omit =
homeassistant/components/verisure/sensor.py
homeassistant/components/verisure/switch.py
homeassistant/components/versasense/*
homeassistant/components/vesync/common.py
homeassistant/components/vesync/fan.py
homeassistant/components/vesync/light.py
homeassistant/components/vesync/sensor.py
@@ -1435,7 +1441,6 @@ omit =
homeassistant/components/xbox/media_player.py
homeassistant/components/xbox/remote.py
homeassistant/components/xbox/sensor.py
homeassistant/components/xbox_live/sensor.py
homeassistant/components/xeoma/camera.py
homeassistant/components/xiaomi/camera.py
homeassistant/components/xiaomi_aqara/__init__.py
@@ -1509,7 +1514,7 @@ omit =
homeassistant/components/zeversolar/entity.py
homeassistant/components/zeversolar/sensor.py
homeassistant/components/zha/websocket_api.py
homeassistant/components/zha/core/channels/*
homeassistant/components/zha/core/cluster_handlers/*
homeassistant/components/zha/core/device.py
homeassistant/components/zha/core/gateway.py
homeassistant/components/zha/core/helpers.py

1
.gitattributes vendored
View File

@@ -8,5 +8,6 @@
*.png binary
*.zip binary
*.mp3 binary
*.pcm binary
Dockerfile.dev linguist-language=Dockerfile

View File

@@ -24,12 +24,12 @@ jobs:
publish: ${{ steps.version.outputs.publish }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
with:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -67,10 +67,10 @@ jobs:
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -105,7 +105,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
@@ -131,7 +131,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -249,7 +249,7 @@ jobs:
- yellow
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set build additional args
run: |
@@ -292,7 +292,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@@ -331,7 +331,7 @@ jobs:
- "homeassistant"
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Login to DockerHub
if: matrix.registry == 'homeassistant'

View File

@@ -1,4 +1,5 @@
name: CI
run-name: "${{ github.event_name == 'workflow_dispatch' && format('CI: {0}', github.ref_name) || '' }}"
# yamllint disable-line rule:truthy
on:
@@ -31,7 +32,7 @@ env:
CACHE_VERSION: 5
PIP_CACHE_VERSION: 4
MYPY_CACHE_VERSION: 4
HA_SHORT_VERSION: 2023.4
HA_SHORT_VERSION: 2023.5
DEFAULT_PYTHON: "3.10"
ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
# 10.3 is the oldest supported version
@@ -40,7 +41,9 @@ env:
# - 10.6.10 is the version currently shipped with the Add-on (as of 31 Jan 2023)
# 10.10 is the latest short-term-support
# - 10.10.3 is the latest (as of 6 Feb 2023)
MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3']"
# mysql 8.0.32 does not always behave the same as MariaDB
# and some queries that work on MariaDB do not work on MySQL
MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3','mysql:8.0.32']"
# 12 is the oldest supported version
# - 12.14 is the latest (as of 9 Feb 2023)
# 15 is the latest version
@@ -79,7 +82,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Generate partial Python venv restore key
id: generate_python_cache_key
run: >-
@@ -203,10 +206,10 @@ jobs:
- info
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -248,9 +251,9 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -294,9 +297,9 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -343,9 +346,9 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -381,9 +384,9 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -434,6 +437,7 @@ jobs:
shell: bash
run: |
. venv/bin/activate
shopt -s globstar
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*}
- name: Register check executables problem matcher
@@ -487,10 +491,10 @@ jobs:
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -539,7 +543,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<23.1" setuptools wheel
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<23.2" setuptools wheel
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
pip install -e .
@@ -555,10 +559,10 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -587,10 +591,10 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -620,10 +624,10 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -664,10 +668,10 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -730,10 +734,10 @@ jobs:
name: Run pip check ${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -783,10 +787,10 @@ jobs:
bluez \
ffmpeg
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -909,10 +913,10 @@ jobs:
ffmpeg \
libmariadb-dev-compat
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1017,10 +1021,10 @@ jobs:
ffmpeg \
postgresql-server-dev-14
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1091,19 +1095,28 @@ jobs:
needs:
- info
- pytest
timeout-minutes: 10
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Download all coverage artifacts
uses: actions/download-artifact@v3
- name: Upload coverage to Codecov (full coverage)
if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v3.1.1
uses: Wandalen/wretry.action@v1.0.36
with:
fail_ci_if_error: true
flags: full-suite
action: codecov/codecov-action@v3.1.3
with: |
fail_ci_if_error: true
flags: full-suite
attempt_limit: 5
attempt_delay: 30000
- name: Upload coverage to Codecov (partial coverage)
if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v3.1.1
uses: Wandalen/wretry.action@v1.0.36
with:
fail_ci_if_error: true
action: codecov/codecov-action@v3.1.3
with: |
fail_ci_if_error: true
attempt_limit: 5
attempt_delay: 30000

View File

@@ -19,10 +19,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.5.0
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@@ -13,6 +13,10 @@ on:
- "requirements.txt"
- "requirements_all.txt"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name}}
cancel-in-progress: true
jobs:
init:
name: Initialize wheels builder
@@ -22,7 +26,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Get information
id: info
@@ -72,17 +76,18 @@ jobs:
path: ./requirements_diff.txt
core:
name: Build musllinux wheels with musllinux_1_2 / cp310 at ${{ matrix.arch }} for core
name: Build Core wheels ${{ matrix.abi }} for ${{ matrix.arch }} (musllinux_1_2)
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
abi: ["cp310", "cp311"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Download env_file
uses: actions/download-artifact@v3
@@ -95,9 +100,9 @@ jobs:
name: requirements_diff
- name: Build wheels
uses: home-assistant/wheels@2022.10.1
uses: home-assistant/wheels@2023.04.0
with:
abi: cp310
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
@@ -108,18 +113,19 @@ jobs:
requirements-diff: "requirements_diff.txt"
requirements: "requirements.txt"
integrations:
name: Build musllinux wheels with musllinux_1_2 / cp310 at ${{ matrix.arch }} for integrations
integrations_cp310:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
abi: ["cp310"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Download env_file
uses: actions/download-artifact@v3
@@ -135,6 +141,7 @@ jobs:
run: |
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# azure-servicebus|azure-servicebus|g" ${requirement_file}
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
@@ -171,30 +178,177 @@ jobs:
sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels (part 1)
uses: home-assistant/wheels@2022.10.1
uses: home-assistant/wheels@2023.04.0
with:
abi: cp310
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
legacy: true
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtaa"
- name: Build wheels (part 2)
uses: home-assistant/wheels@2022.10.1
uses: home-assistant/wheels@2023.04.0
with:
abi: cp310
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
legacy: true
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtab"
# Wheels building for the cp311 ABI is currently split
# This is mainly until we have figured out to get all wheels built.
# Without harming our current workflow.
integrations_cp311:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
abi: ["cp311"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.2
- name: Write alternative env-file for cp311
run: |
(
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
# GRPC on armv7 needed -lexecinfo (issue #56669) since home assistant installed
# execinfo-dev when building wheels. However, this package is no longer available
# Alpine 3.17, which we use for the cp311 ABI, so the flag should no longer be needed.
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc" # -lexecinfo
# Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
# OpenCV headless installation
echo "CI_BUILD=1"
echo "ENABLE_HEADLESS=1"
# Use C-Extension for sqlalchemy
echo "REQUIRE_SQLALCHEMY_CEXT=1"
) > .env_file
- name: Download requirements_diff
uses: actions/download-artifact@v3
with:
name: requirements_diff
- name: (Un)comment packages
run: |
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
# PyBluez no longer compiles. Commented it out for now.
# It need further cleanup down the line, as all machine images
# try to install it.
# sed -i "s|# pybluez|pybluez|g" ${requirement_file}
# beacontools requires PyBluez.
# sed -i "s|# beacontools|beacontools|g" ${requirement_file}
# azure-servicebus requires uamqp, which requires OpenSSL 1.1 to
# compile/build. This is not available on Alpine 3.17. The compat
# layer offered by Alpine conflicts, so we have no way to build
# this package.
# sed -i "s|# azure-servicebus|azure-servicebus|g" ${requirement_file}
# It doesn't build for some reason, so we skip it for now.
# Bumping to the latest version (4.7.0.72) supporting Python 3.11
# doesn't help. Reverted bump in #91871. There are 8 registered
# instances using this integration according to analytics.
# sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
# Some packages are not buildable on armhf anymore
if [ "${{ matrix.arch }}" = "armhf" ]; then
# Pandas has issues building on armhf, it is expected they
# will drop the platform in the near future (they consider it
# "flimsy" on 386). The following packages depend on pandas,
# so we comment them out.
sed -i "s|env_canada|# env_canada|g" ${requirement_file}
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
fi
done
- name: Split requirements all
run: |
# We split requirements all into two different files.
# This is to prevent the build from running out of memory when
# resolving packages on 32-bits systems (like armhf, armv7).
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 2) requirements_all.txt requirements_all.txt
- name: Adjust build env
run: |
if [ "${{ matrix.arch }}" = "i386" ]; then
echo "NPY_DISABLE_SVML=1" >> .env_file
fi
# Probably not an issue anymore. Removing for now.
# (
# # cmake > 3.22.2 have issue on arm
# # Tested until 3.22.5
# echo "cmake==3.22.2"
# ) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels (part 1)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
legacy: true
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtaa"
- name: Build wheels (part 2)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
legacy: true
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"

View File

@@ -1,12 +1,12 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.256
rev: v0.0.262
hooks:
- id: ruff
args:
- --fix
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.3.0
hooks:
- id: black
args:

View File

@@ -57,10 +57,12 @@ homeassistant.components.ambient_station.*
homeassistant.components.amcrest.*
homeassistant.components.ampio.*
homeassistant.components.analytics.*
homeassistant.components.anova.*
homeassistant.components.anthemav.*
homeassistant.components.apcupsd.*
homeassistant.components.aqualogic.*
homeassistant.components.aseko_pool_live.*
homeassistant.components.assist_pipeline.*
homeassistant.components.asuswrt.*
homeassistant.components.auth.*
homeassistant.components.automation.*
@@ -137,6 +139,7 @@ homeassistant.components.hardkernel.*
homeassistant.components.hardware.*
homeassistant.components.here_travel_time.*
homeassistant.components.history.*
homeassistant.components.homeassistant.exposed_entities
homeassistant.components.homeassistant.triggers.event
homeassistant.components.homeassistant_alerts.*
homeassistant.components.homeassistant_hardware.*

4
.vscode/launch.json vendored
View File

@@ -23,7 +23,7 @@
"preLaunchTask": "Compile English translations"
},
{
// Debug by attaching to local Home Asistant server using Remote Python Debugger.
// Debug by attaching to local Home Assistant server using Remote Python Debugger.
// See https://www.home-assistant.io/integrations/debugpy/
"name": "Home Assistant: Attach Local",
"type": "python",
@@ -38,7 +38,7 @@
]
},
{
// Debug by attaching to remote Home Asistant server using Remote Python Debugger.
// Debug by attaching to remote Home Assistant server using Remote Python Debugger.
// See https://www.home-assistant.io/integrations/debugpy/
"name": "Home Assistant: Attach Remote",
"type": "python",

View File

@@ -80,6 +80,10 @@ build.json @home-assistant/supervisor
/tests/components/android_ip_webcam/ @engrbm87
/homeassistant/components/androidtv/ @JeffLIrion @ollo69
/tests/components/androidtv/ @JeffLIrion @ollo69
/homeassistant/components/androidtv_remote/ @tronikos
/tests/components/androidtv_remote/ @tronikos
/homeassistant/components/anova/ @Lash-L
/tests/components/anova/ @Lash-L
/homeassistant/components/anthemav/ @hyralex
/tests/components/anthemav/ @hyralex
/homeassistant/components/apache_kafka/ @bachya
@@ -103,6 +107,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/arris_tg2492lg/ @vanbalken
/homeassistant/components/aseko_pool_live/ @milanmeu
/tests/components/aseko_pool_live/ @milanmeu
/homeassistant/components/assist_pipeline/ @balloob @synesthesiam
/tests/components/assist_pipeline/ @balloob @synesthesiam
/homeassistant/components/asuswrt/ @kennedyshead @ollo69
/tests/components/asuswrt/ @kennedyshead @ollo69
/homeassistant/components/atag/ @MatsNL
@@ -168,6 +174,8 @@ build.json @home-assistant/supervisor
/tests/components/broadlink/ @danielhiversen @felipediel @L-I-Am
/homeassistant/components/brother/ @bieniu
/tests/components/brother/ @bieniu
/homeassistant/components/brottsplatskartan/ @gjohansson-ST
/tests/components/brottsplatskartan/ @gjohansson-ST
/homeassistant/components/brunt/ @eavanvalkenburg
/tests/components/brunt/ @eavanvalkenburg
/homeassistant/components/bsblan/ @liudger
@@ -215,8 +223,6 @@ build.json @home-assistant/supervisor
/tests/components/conversation/ @home-assistant/core @synesthesiam
/homeassistant/components/coolmaster/ @OnFreund
/tests/components/coolmaster/ @OnFreund
/homeassistant/components/coronavirus/ @home-assistant/core
/tests/components/coronavirus/ @home-assistant/core
/homeassistant/components/counter/ @fabaff
/tests/components/counter/ @fabaff
/homeassistant/components/cover/ @home-assistant/core
@@ -281,7 +287,7 @@ build.json @home-assistant/supervisor
/tests/components/dsmr_reader/ @depl0y @glodenox
/homeassistant/components/dunehd/ @bieniu
/tests/components/dunehd/ @bieniu
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95 @andarotajo
/homeassistant/components/dynalite/ @ziv1234
/tests/components/dynalite/ @ziv1234
/homeassistant/components/eafm/ @Jc2k
@@ -649,8 +655,8 @@ build.json @home-assistant/supervisor
/tests/components/lidarr/ @tkdrob
/homeassistant/components/life360/ @pnbruckner
/tests/components/life360/ @pnbruckner
/homeassistant/components/lifx/ @bdraco @Djelibeybi
/tests/components/lifx/ @bdraco @Djelibeybi
/homeassistant/components/lifx/ @bdraco
/tests/components/lifx/ @bdraco
/homeassistant/components/light/ @home-assistant/core
/tests/components/light/ @home-assistant/core
/homeassistant/components/linux_battery/ @fabaff
@@ -819,8 +825,8 @@ build.json @home-assistant/supervisor
/tests/components/numato/ @clssn
/homeassistant/components/number/ @home-assistant/core @Shulyaka
/tests/components/number/ @home-assistant/core @Shulyaka
/homeassistant/components/nut/ @bdraco @ollo69
/tests/components/nut/ @bdraco @ollo69
/homeassistant/components/nut/ @bdraco @ollo69 @pestevez
/tests/components/nut/ @bdraco @ollo69 @pestevez
/homeassistant/components/nws/ @MatthewFlamm @kamiyo
/tests/components/nws/ @MatthewFlamm @kamiyo
/homeassistant/components/nzbget/ @chriscla
@@ -893,8 +899,8 @@ build.json @home-assistant/supervisor
/tests/components/plaato/ @JohNan
/homeassistant/components/plex/ @jjlawren
/tests/components/plex/ @jjlawren
/homeassistant/components/plugwise/ @CoMPaTech @bouwew @brefra @frenck
/tests/components/plugwise/ @CoMPaTech @bouwew @brefra @frenck
/homeassistant/components/plugwise/ @CoMPaTech @bouwew @frenck
/tests/components/plugwise/ @CoMPaTech @bouwew @frenck
/homeassistant/components/plum_lightpad/ @ColinHarrington @prystupa
/tests/components/plum_lightpad/ @ColinHarrington @prystupa
/homeassistant/components/point/ @fredrike
@@ -931,6 +937,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/pvpc_hourly_pricing/ @azogue
/tests/components/pvpc_hourly_pricing/ @azogue
/homeassistant/components/qbittorrent/ @geoffreylagaisse
/tests/components/qbittorrent/ @geoffreylagaisse
/homeassistant/components/qingping/ @bdraco @skgsergio
/tests/components/qingping/ @bdraco @skgsergio
/homeassistant/components/qld_bushfire/ @exxamalte
@@ -958,6 +965,8 @@ build.json @home-assistant/supervisor
/tests/components/rainmachine/ @bachya
/homeassistant/components/random/ @fabaff
/tests/components/random/ @fabaff
/homeassistant/components/rapt_ble/ @sairon
/tests/components/rapt_ble/ @sairon
/homeassistant/components/raspberry_pi/ @home-assistant/core
/tests/components/raspberry_pi/ @home-assistant/core
/homeassistant/components/rdw/ @frenck
@@ -976,6 +985,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/repairs/ @home-assistant/core
/tests/components/repairs/ @home-assistant/core
/homeassistant/components/repetier/ @MTrab @ShadowBr0ther
/homeassistant/components/rest/ @epenet
/tests/components/rest/ @epenet
/homeassistant/components/rflink/ @javicalle
/tests/components/rflink/ @javicalle
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
@@ -990,6 +1001,8 @@ build.json @home-assistant/supervisor
/tests/components/rituals_perfume_genie/ @milanmeu
/homeassistant/components/rmvtransport/ @cgtobi
/tests/components/rmvtransport/ @cgtobi
/homeassistant/components/roborock/ @humbertogontijo @Lash-L
/tests/components/roborock/ @humbertogontijo @Lash-L
/homeassistant/components/roku/ @ctalkington
/tests/components/roku/ @ctalkington
/homeassistant/components/roomba/ @pschmitt @cyr-ius @shenxn
@@ -1102,6 +1115,7 @@ build.json @home-assistant/supervisor
/tests/components/smhi/ @gjohansson-ST
/homeassistant/components/sms/ @ocalvo
/homeassistant/components/snapcast/ @luar123
/tests/components/snapcast/ @luar123
/homeassistant/components/snooz/ @AustinBrunkhorst
/tests/components/snooz/ @AustinBrunkhorst
/homeassistant/components/solaredge/ @frenck
@@ -1130,8 +1144,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/splunk/ @Bre77
/homeassistant/components/spotify/ @frenck
/tests/components/spotify/ @frenck
/homeassistant/components/sql/ @dgomes @gjohansson-ST
/tests/components/sql/ @dgomes @gjohansson-ST
/homeassistant/components/sql/ @dgomes @gjohansson-ST @dougiteixeira
/tests/components/sql/ @dgomes @gjohansson-ST @dougiteixeira
/homeassistant/components/squeezebox/ @rajlaud
/tests/components/squeezebox/ @rajlaud
/homeassistant/components/srp_energy/ @briglx
@@ -1153,8 +1167,8 @@ build.json @home-assistant/supervisor
/tests/components/stookwijzer/ @fwestenberg
/homeassistant/components/stream/ @hunterjm @uvjustin @allenporter
/tests/components/stream/ @hunterjm @uvjustin @allenporter
/homeassistant/components/stt/ @pvizeli
/tests/components/stt/ @pvizeli
/homeassistant/components/stt/ @home-assistant/core @pvizeli
/tests/components/stt/ @home-assistant/core @pvizeli
/homeassistant/components/subaru/ @G-Two
/tests/components/subaru/ @G-Two
/homeassistant/components/suez_water/ @ooii
@@ -1249,8 +1263,8 @@ build.json @home-assistant/supervisor
/tests/components/trafikverket_weatherstation/ @endor-force @gjohansson-ST
/homeassistant/components/transmission/ @engrbm87 @JPHutchins
/tests/components/transmission/ @engrbm87 @JPHutchins
/homeassistant/components/tts/ @pvizeli
/tests/components/tts/ @pvizeli
/homeassistant/components/tts/ @home-assistant/core @pvizeli
/tests/components/tts/ @home-assistant/core @pvizeli
/homeassistant/components/tuya/ @Tuya @zlinoliver @frenck
/tests/components/tuya/ @Tuya @zlinoliver @frenck
/homeassistant/components/twentemilieu/ @frenck
@@ -1262,8 +1276,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/unifi/ @Kane610
/tests/components/unifi/ @Kane610
/homeassistant/components/unifiled/ @florisvdk
/homeassistant/components/unifiprotect/ @briis @AngellusMortis @bdraco
/tests/components/unifiprotect/ @briis @AngellusMortis @bdraco
/homeassistant/components/unifiprotect/ @AngellusMortis @bdraco
/tests/components/unifiprotect/ @AngellusMortis @bdraco
/homeassistant/components/upb/ @gwww
/tests/components/upb/ @gwww
/homeassistant/components/upc_connect/ @pvizeli @fabaff
@@ -1299,8 +1313,6 @@ build.json @home-assistant/supervisor
/tests/components/version/ @ludeeus
/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey
/homeassistant/components/vicare/ @oischinger
/tests/components/vicare/ @oischinger
/homeassistant/components/vilfo/ @ManneW
/tests/components/vilfo/ @ManneW
/homeassistant/components/vivotek/ @HarlemSquirrel
@@ -1308,8 +1320,8 @@ build.json @home-assistant/supervisor
/tests/components/vizio/ @raman325
/homeassistant/components/vlc_telnet/ @rodripf @MartinHjelmare
/tests/components/vlc_telnet/ @rodripf @MartinHjelmare
/homeassistant/components/voice_assistant/ @balloob @synesthesiam
/tests/components/voice_assistant/ @balloob @synesthesiam
/homeassistant/components/voip/ @balloob @synesthesiam
/tests/components/voip/ @balloob @synesthesiam
/homeassistant/components/volumio/ @OnFreund
/tests/components/volumio/ @OnFreund
/homeassistant/components/volvooncall/ @molobrakos
@@ -1361,9 +1373,10 @@ build.json @home-assistant/supervisor
/tests/components/worldclock/ @fabaff
/homeassistant/components/ws66i/ @ssaenger
/tests/components/ws66i/ @ssaenger
/homeassistant/components/wyoming/ @balloob @synesthesiam
/tests/components/wyoming/ @balloob @synesthesiam
/homeassistant/components/xbox/ @hunterjm
/tests/components/xbox/ @hunterjm
/homeassistant/components/xbox_live/ @MartinHjelmare
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
/tests/components/xiaomi_aqara/ @danielhiversen @syssi
/homeassistant/components/xiaomi_ble/ @Jc2k @Ernst79

View File

@@ -132,8 +132,8 @@ For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>. Translations are available at
<https://www.contributor-covenant.org/translations>.
[coc-blog]: /blog/2017/01/21/home-assistant-governance/
[coc2-blog]: /blog/2020/05/25/code-of-conduct-updated/
[coc-blog]: https://www.home-assistant.io/blog/2017/01/21/home-assistant-governance/
[coc2-blog]: https://www.home-assistant.io/blog/2020/05/25/code-of-conduct-updated/
[email]: mailto:safety@home-assistant.io
[homepage]: http://contributor-covenant.org
[mozilla]: https://github.com/mozilla/diversity

View File

@@ -4,11 +4,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Uninstall pre-installed formatting and linting tools
# They would conflict with our pinned versions
RUN pipx uninstall black
RUN pipx uninstall pydocstyle
RUN pipx uninstall pycodestyle
RUN pipx uninstall mypy
RUN pipx uninstall pylint
RUN \
pipx uninstall black \
&& pipx uninstall pydocstyle \
&& pipx uninstall pycodestyle \
&& pipx uninstall mypy \
&& pipx uninstall pylint
RUN \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \

View File

@@ -1,11 +1,11 @@
image: homeassistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.02.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.02.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.02.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.02.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.02.0
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.04.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.04.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.04.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.04.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.04.0
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

@@ -629,6 +629,9 @@ async def _async_set_up_integrations(
- stage_1_domains
)
# Enables after dependencies when setting up stage 1 domains
async_set_domains_to_be_loaded(hass, stage_1_domains)
# Start setup
if stage_1_domains:
_LOGGER.info("Setting up stage 1: %s", stage_1_domains)
@@ -640,7 +643,7 @@ async def _async_set_up_integrations(
except asyncio.TimeoutError:
_LOGGER.warning("Setup timed out for stage 1 - moving forward")
# Enables after dependencies
# Add after dependencies when setting up stage 2 domains
async_set_domains_to_be_loaded(hass, stage_2_domains)
if stage_2_domains:

View File

@@ -10,7 +10,6 @@
"microsoft_face",
"microsoft",
"msteams",
"xbox",
"xbox_live"
"xbox"
]
}

View File

@@ -10,14 +10,15 @@ from aiohttp import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
from async_timeout import timeout
from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.unit_system import METRIC_SYSTEM
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN, MANUFACTURER
@@ -49,6 +50,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# Remove ozone sensors from registry if they exist
ent_reg = er.async_get(hass)
for day in range(0, 5):
unique_id = f"{coordinator.location_key}-ozone-{day}"
if entity_id := ent_reg.async_get_entity_id(SENSOR_PLATFORM, DOMAIN, unique_id):
_LOGGER.debug("Removing ozone sensor entity %s", entity_id)
ent_reg.async_remove(entity_id)
return True
@@ -116,11 +125,7 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
async with timeout(10):
current = await self.accuweather.async_get_current_conditions()
forecast = (
await self.accuweather.async_get_forecast(
metric=self.hass.config.units is METRIC_SYSTEM
)
if self.forecast
else {}
await self.accuweather.async_get_forecast() if self.forecast else {}
)
except (
ApiError,

View File

@@ -20,7 +20,6 @@ from homeassistant.components.weather import (
ATTR_CONDITION_WINDY,
)
API_IMPERIAL: Final = "Imperial"
API_METRIC: Final = "Metric"
ATTRIBUTION: Final = "Data provided by AccuWeather"
ATTR_CATEGORY: Final = "Category"

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["accuweather"],
"quality_scale": "platinum",
"requirements": ["accuweather==0.5.0"]
"requirements": ["accuweather==0.5.1"]
}

View File

@@ -26,11 +26,9 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.unit_system import METRIC_SYSTEM
from . import AccuWeatherDataUpdateCoordinator
from .const import (
API_IMPERIAL,
API_METRIC,
ATTR_CATEGORY,
ATTR_DIRECTION,
@@ -51,7 +49,7 @@ PARALLEL_UPDATES = 1
class AccuWeatherSensorDescriptionMixin:
"""Mixin for AccuWeather sensor."""
value_fn: Callable[[dict[str, Any], str], StateType]
value_fn: Callable[[dict[str, Any]], StateType]
@dataclass
@@ -61,18 +59,25 @@ class AccuWeatherSensorDescription(
"""Class describing AccuWeather sensor entities."""
attr_fn: Callable[[dict[str, Any]], dict[str, StateType]] = lambda _: {}
metric_unit: str | None = None
us_customary_unit: str | None = None
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
AccuWeatherSensorDescription(
key="AirQuality",
icon="mdi:air-filter",
name="Air quality",
value_fn=lambda data: cast(str, data[ATTR_CATEGORY]),
device_class=SensorDeviceClass.ENUM,
options=["good", "hazardous", "high", "low", "moderate", "unhealthy"],
translation_key="air_quality",
),
AccuWeatherSensorDescription(
key="CloudCoverDay",
icon="mdi:weather-cloudy",
name="Cloud cover day",
entity_registry_enabled_default=False,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data, _: cast(int, data),
value_fn=lambda data: cast(int, data),
),
AccuWeatherSensorDescription(
key="CloudCoverNight",
@@ -80,7 +85,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Cloud cover night",
entity_registry_enabled_default=False,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data, _: cast(int, data),
value_fn=lambda data: cast(int, data),
),
AccuWeatherSensorDescription(
key="Grass",
@@ -88,15 +93,16 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Grass pollen",
entity_registry_enabled_default=False,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
value_fn=lambda data, _: cast(int, data[ATTR_VALUE]),
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
translation_key="grass_pollen",
),
AccuWeatherSensorDescription(
key="HoursOfSun",
icon="mdi:weather-partly-cloudy",
name="Hours of sun",
native_unit_of_measurement=UnitOfTime.HOURS,
value_fn=lambda data, _: cast(float, data),
value_fn=lambda data: cast(float, data),
),
AccuWeatherSensorDescription(
key="Mold",
@@ -104,16 +110,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Mold pollen",
entity_registry_enabled_default=False,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
value_fn=lambda data, _: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
),
AccuWeatherSensorDescription(
key="Ozone",
icon="mdi:vector-triangle",
name="Ozone",
entity_registry_enabled_default=False,
value_fn=lambda data, _: cast(int, data[ATTR_VALUE]),
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
translation_key="mold_pollen",
),
AccuWeatherSensorDescription(
key="Ragweed",
@@ -121,56 +120,53 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Ragweed pollen",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
value_fn=lambda data, _: cast(int, data[ATTR_VALUE]),
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
translation_key="ragweed_pollen",
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMax",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature max",
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, _: cast(float, data[ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMin",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature min",
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, _: cast(float, data[ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMax",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature shade max",
entity_registry_enabled_default=False,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, _: cast(float, data[ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMin",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature shade min",
entity_registry_enabled_default=False,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, _: cast(float, data[ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="ThunderstormProbabilityDay",
icon="mdi:weather-lightning",
name="Thunderstorm probability day",
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data, _: cast(int, data),
value_fn=lambda data: cast(int, data),
),
AccuWeatherSensorDescription(
key="ThunderstormProbabilityNight",
icon="mdi:weather-lightning",
name="Thunderstorm probability night",
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data, _: cast(int, data),
value_fn=lambda data: cast(int, data),
),
AccuWeatherSensorDescription(
key="Tree",
@@ -178,25 +174,26 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Tree pollen",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
value_fn=lambda data, _: cast(int, data[ATTR_VALUE]),
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
translation_key="tree_pollen",
),
AccuWeatherSensorDescription(
key="UVIndex",
icon="mdi:weather-sunny",
name="UV index",
native_unit_of_measurement=UV_INDEX,
value_fn=lambda data, _: cast(int, data[ATTR_VALUE]),
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
translation_key="uv_index",
),
AccuWeatherSensorDescription(
key="WindGustDay",
device_class=SensorDeviceClass.WIND_SPEED,
name="Wind gust day",
entity_registry_enabled_default=False,
metric_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
us_customary_unit=UnitOfSpeed.MILES_PER_HOUR,
value_fn=lambda data, _: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
),
AccuWeatherSensorDescription(
@@ -204,27 +201,24 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
device_class=SensorDeviceClass.WIND_SPEED,
name="Wind gust night",
entity_registry_enabled_default=False,
metric_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
us_customary_unit=UnitOfSpeed.MILES_PER_HOUR,
value_fn=lambda data, _: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
),
AccuWeatherSensorDescription(
key="WindDay",
device_class=SensorDeviceClass.WIND_SPEED,
name="Wind day",
metric_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
us_customary_unit=UnitOfSpeed.MILES_PER_HOUR,
value_fn=lambda data, _: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
),
AccuWeatherSensorDescription(
key="WindNight",
device_class=SensorDeviceClass.WIND_SPEED,
name="Wind night",
metric_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
us_customary_unit=UnitOfSpeed.MILES_PER_HOUR,
value_fn=lambda data, _: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[ATTR_SPEED][ATTR_VALUE]),
attr_fn=lambda data: {"direction": data[ATTR_DIRECTION][ATTR_ENGLISH]},
),
)
@@ -236,9 +230,8 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Apparent temperature",
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="Ceiling",
@@ -246,9 +239,8 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
icon="mdi:weather-fog",
name="Cloud ceiling",
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfLength.METERS,
us_customary_unit=UnitOfLength.FEET,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfLength.METERS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
suggested_display_precision=0,
),
AccuWeatherSensorDescription(
@@ -258,7 +250,7 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data, _: cast(int, data),
value_fn=lambda data: cast(int, data),
),
AccuWeatherSensorDescription(
key="DewPoint",
@@ -266,18 +258,16 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Dew point",
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="RealFeelTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature",
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShade",
@@ -285,18 +275,16 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="RealFeel temperature shade",
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="Precipitation",
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
name="Precipitation",
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
us_customary_unit=UnitOfVolumetricFlux.INCHES_PER_HOUR,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
attr_fn=lambda data: {"type": data["PrecipitationType"]},
),
AccuWeatherSensorDescription(
@@ -306,7 +294,7 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Pressure tendency",
options=["falling", "rising", "steady"],
translation_key="pressure_tendency",
value_fn=lambda data, _: cast(str, data["LocalizedText"]).lower(),
value_fn=lambda data: cast(str, data["LocalizedText"]).lower(),
),
AccuWeatherSensorDescription(
key="UVIndex",
@@ -314,7 +302,7 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="UV index",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UV_INDEX,
value_fn=lambda data, _: cast(int, data),
value_fn=lambda data: cast(int, data),
attr_fn=lambda data: {ATTR_LEVEL: data["UVIndexText"]},
),
AccuWeatherSensorDescription(
@@ -323,9 +311,8 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Wet bulb temperature",
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="WindChillTemperature",
@@ -333,18 +320,16 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Wind chill temperature",
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfTemperature.CELSIUS,
us_customary_unit=UnitOfTemperature.FAHRENHEIT,
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="Wind",
device_class=SensorDeviceClass.WIND_SPEED,
name="Wind",
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
us_customary_unit=UnitOfSpeed.MILES_PER_HOUR,
value_fn=lambda data, unit: cast(float, data[ATTR_SPEED][unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[ATTR_SPEED][API_METRIC][ATTR_VALUE]),
),
AccuWeatherSensorDescription(
key="WindGust",
@@ -352,9 +337,8 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
name="Wind gust",
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
metric_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
us_customary_unit=UnitOfSpeed.MILES_PER_HOUR,
value_fn=lambda data, unit: cast(float, data[ATTR_SPEED][unit][ATTR_VALUE]),
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
value_fn=lambda data: cast(float, data[ATTR_SPEED][API_METRIC][ATTR_VALUE]),
),
)
@@ -374,7 +358,7 @@ async def async_setup_entry(
# Some air quality/allergy sensors are only available for certain
# locations.
sensors.extend(
AccuWeatherForecastSensor(coordinator, description, forecast_day=day)
AccuWeatherSensor(coordinator, description, forecast_day=day)
for day in range(MAX_FORECAST_DAYS + 1)
for description in FORECAST_SENSOR_TYPES
if description.key in coordinator.data[ATTR_FORECAST][0]
@@ -413,34 +397,27 @@ class AccuWeatherSensor(
self._attr_unique_id = (
f"{coordinator.location_key}-{description.key}".lower()
)
self._attr_native_unit_of_measurement = description.native_unit_of_measurement
if self.coordinator.hass.config.units is METRIC_SYSTEM:
self._unit_system = API_METRIC
if metric_unit := description.metric_unit:
self._attr_native_unit_of_measurement = metric_unit
else:
self._unit_system = API_IMPERIAL
if us_customary_unit := description.us_customary_unit:
self._attr_native_unit_of_measurement = us_customary_unit
self._attr_device_info = coordinator.device_info
if forecast_day is not None:
self.forecast_day = forecast_day
self.forecast_day = forecast_day
@property
def native_value(self) -> StateType:
"""Return the state."""
return self.entity_description.value_fn(self._sensor_data, self._unit_system)
return self.entity_description.value_fn(self._sensor_data)
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
if self.forecast_day is not None:
return self.entity_description.attr_fn(self._sensor_data)
return self.entity_description.attr_fn(self.coordinator.data)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle data update."""
self._sensor_data = _get_sensor_data(
self.coordinator.data, self.entity_description.key
self.coordinator.data, self.entity_description.key, self.forecast_day
)
self.async_write_ha_state()
@@ -458,20 +435,3 @@ def _get_sensor_data(
return sensors["PrecipitationSummary"]["PastHour"]
return sensors[kind]
class AccuWeatherForecastSensor(AccuWeatherSensor):
"""Define an AccuWeather forecast entity."""
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return self.entity_description.attr_fn(self._sensor_data)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle data update."""
self._sensor_data = _get_sensor_data(
self.coordinator.data, self.entity_description.key, self.forecast_day
)
self.async_write_ha_state()

View File

@@ -30,6 +30,91 @@
"rising": "Rising",
"falling": "Falling"
}
},
"air_quality": {
"state": {
"good": "Good",
"hazardous": "Hazardous",
"high": "High",
"low": "Low",
"moderate": "Moderate",
"unhealthy": "Unhealthy"
}
},
"grass_pollen": {
"state_attributes": {
"level": {
"name": "Level",
"state": {
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
}
}
}
},
"mold_pollen": {
"state_attributes": {
"level": {
"name": "Level",
"state": {
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
}
}
}
},
"ragweed_pollen": {
"state_attributes": {
"level": {
"name": "Level",
"state": {
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
}
}
}
},
"tree_pollen": {
"state_attributes": {
"level": {
"name": "Level",
"state": {
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
}
}
}
},
"uv_index": {
"state_attributes": {
"level": {
"name": "Level",
"state": {
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
}
}
}
}
}
},

View File

@@ -28,17 +28,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utc_from_timestamp
from homeassistant.util.unit_system import METRIC_SYSTEM
from . import AccuWeatherDataUpdateCoordinator
from .const import (
API_IMPERIAL,
API_METRIC,
ATTR_FORECAST,
ATTRIBUTION,
CONDITION_CLASSES,
DOMAIN,
)
from .const import API_METRIC, ATTR_FORECAST, ATTRIBUTION, CONDITION_CLASSES, DOMAIN
PARALLEL_UPDATES = 1
@@ -66,20 +58,11 @@ class AccuWeatherEntity(
# Coordinator data is used also for sensors which don't have units automatically
# converted, hence the weather entity's native units follow the configured unit
# system
if coordinator.hass.config.units is METRIC_SYSTEM:
self._attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
self._attr_native_pressure_unit = UnitOfPressure.HPA
self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS
self._attr_native_visibility_unit = UnitOfLength.KILOMETERS
self._attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
self._unit_system = API_METRIC
else:
self._unit_system = API_IMPERIAL
self._attr_native_precipitation_unit = UnitOfPrecipitationDepth.INCHES
self._attr_native_pressure_unit = UnitOfPressure.INHG
self._attr_native_temperature_unit = UnitOfTemperature.FAHRENHEIT
self._attr_native_visibility_unit = UnitOfLength.MILES
self._attr_native_wind_speed_unit = UnitOfSpeed.MILES_PER_HOUR
self._attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
self._attr_native_pressure_unit = UnitOfPressure.HPA
self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS
self._attr_native_visibility_unit = UnitOfLength.KILOMETERS
self._attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
self._attr_unique_id = coordinator.location_key
self._attr_attribution = ATTRIBUTION
self._attr_device_info = coordinator.device_info
@@ -99,16 +82,12 @@ class AccuWeatherEntity(
@property
def native_temperature(self) -> float:
"""Return the temperature."""
return cast(
float, self.coordinator.data["Temperature"][self._unit_system]["Value"]
)
return cast(float, self.coordinator.data["Temperature"][API_METRIC]["Value"])
@property
def native_pressure(self) -> float:
"""Return the pressure."""
return cast(
float, self.coordinator.data["Pressure"][self._unit_system]["Value"]
)
return cast(float, self.coordinator.data["Pressure"][API_METRIC]["Value"])
@property
def humidity(self) -> int:
@@ -118,9 +97,7 @@ class AccuWeatherEntity(
@property
def native_wind_speed(self) -> float:
"""Return the wind speed."""
return cast(
float, self.coordinator.data["Wind"]["Speed"][self._unit_system]["Value"]
)
return cast(float, self.coordinator.data["Wind"]["Speed"][API_METRIC]["Value"])
@property
def wind_bearing(self) -> int:
@@ -130,19 +107,7 @@ class AccuWeatherEntity(
@property
def native_visibility(self) -> float:
"""Return the visibility."""
return cast(
float, self.coordinator.data["Visibility"][self._unit_system]["Value"]
)
@property
def ozone(self) -> int | None:
"""Return the ozone level."""
# We only have ozone data for certain locations and only in the forecast data.
if self.coordinator.forecast and self.coordinator.data[ATTR_FORECAST][0].get(
"Ozone"
):
return cast(int, self.coordinator.data[ATTR_FORECAST][0]["Ozone"]["Value"])
return None
return cast(float, self.coordinator.data["Visibility"][API_METRIC]["Value"])
@property
def forecast(self) -> list[Forecast] | None:

View File

@@ -7,11 +7,11 @@ from advantage_air import ApiError, advantage_air
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ADVANTAGE_AIR_RETRY, DOMAIN
from .models import AdvantageAirData
ADVANTAGE_AIR_SYNC_INTERVAL = 15
PLATFORMS = [
@@ -53,29 +53,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
update_interval=timedelta(seconds=ADVANTAGE_AIR_SYNC_INTERVAL),
)
def error_handle_factory(func):
"""Return the provided API function wrapped.
Adds an error handler and coordinator refresh.
"""
async def error_handle(param):
try:
if await func(param):
await coordinator.async_refresh()
except ApiError as err:
raise HomeAssistantError(err) from err
return error_handle
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
"coordinator": coordinator,
"aircon": error_handle_factory(api.aircon.async_set),
"lights": error_handle_factory(api.lights.async_set),
}
hass.data[DOMAIN][entry.entry_id] = AdvantageAirData(coordinator, api)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@@ -1,8 +1,6 @@
"""Binary Sensor platform for Advantage Air integration."""
from __future__ import annotations
from typing import Any
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
@@ -14,6 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
from .models import AdvantageAirData
PARALLEL_UPDATES = 0
@@ -25,10 +24,10 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir Binary Sensor platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[BinarySensorEntity] = []
if aircons := instance["coordinator"].data.get("aircons"):
if aircons := instance.coordinator.data.get("aircons"):
for ac_key, ac_device in aircons.items():
entities.append(AdvantageAirFilter(instance, ac_key))
for zone_key, zone in ac_device["zones"].items():
@@ -48,7 +47,7 @@ class AdvantageAirFilter(AdvantageAirAcEntity, BinarySensorEntity):
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_name = "Filter"
def __init__(self, instance: dict[str, Any], ac_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
"""Initialize an Advantage Air Filter sensor."""
super().__init__(instance, ac_key)
self._attr_unique_id += "-filter"
@@ -64,7 +63,7 @@ class AdvantageAirZoneMotion(AdvantageAirZoneEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.MOTION
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Motion sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} motion'
@@ -82,7 +81,7 @@ class AdvantageAirZoneMyZone(AdvantageAirZoneEntity, BinarySensorEntity):
_attr_entity_registry_enabled_default = False
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone MyZone sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} myZone'

View File

@@ -5,6 +5,8 @@ import logging
from typing import Any
from homeassistant.components.climate import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
FAN_AUTO,
FAN_HIGH,
FAN_LOW,
@@ -26,24 +28,17 @@ from .const import (
DOMAIN as ADVANTAGE_AIR_DOMAIN,
)
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
from .models import AdvantageAirData
ADVANTAGE_AIR_HVAC_MODES = {
"heat": HVACMode.HEAT,
"cool": HVACMode.COOL,
"vent": HVACMode.FAN_ONLY,
"dry": HVACMode.DRY,
"myauto": HVACMode.AUTO,
"myauto": HVACMode.HEAT_COOL,
}
HASS_HVAC_MODES = {v: k for k, v in ADVANTAGE_AIR_HVAC_MODES.items()}
AC_HVAC_MODES = [
HVACMode.OFF,
HVACMode.COOL,
HVACMode.HEAT,
HVACMode.FAN_ONLY,
HVACMode.DRY,
]
ADVANTAGE_AIR_FAN_MODES = {
"autoAA": FAN_AUTO,
"low": FAN_LOW,
@@ -53,7 +48,14 @@ ADVANTAGE_AIR_FAN_MODES = {
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL]
ADVANTAGE_AIR_AUTOFAN = "aaAutoFanModeEnabled"
ADVANTAGE_AIR_MYZONE = "MyZone"
ADVANTAGE_AIR_MYAUTO = "MyAuto"
ADVANTAGE_AIR_MYAUTO_ENABLED = "myAutoModeEnabled"
ADVANTAGE_AIR_MYTEMP = "MyTemp"
ADVANTAGE_AIR_MYTEMP_ENABLED = "climateControlModeEnabled"
ADVANTAGE_AIR_HEAT_TARGET = "myAutoHeatTargetTemp"
ADVANTAGE_AIR_COOL_TARGET = "myAutoCoolTargetTemp"
PARALLEL_UPDATES = 0
@@ -67,15 +69,15 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir climate platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[ClimateEntity] = []
if aircons := instance["coordinator"].data.get("aircons"):
if aircons := instance.coordinator.data.get("aircons"):
for ac_key, ac_device in aircons.items():
entities.append(AdvantageAirAC(instance, ac_key))
for zone_key, zone in ac_device["zones"].items():
# Only add zone climate control when zone is in temperature control
if zone["type"] != 0:
if zone["type"] > 0:
entities.append(AdvantageAirZone(instance, ac_key, zone_key))
async_add_entities(entities)
@@ -83,24 +85,56 @@ async def async_setup_entry(
class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
"""AdvantageAir AC unit."""
_attr_fan_modes = [FAN_LOW, FAN_MEDIUM, FAN_HIGH]
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = PRECISION_WHOLE
_attr_max_temp = 32
_attr_min_temp = 16
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
_attr_hvac_modes = AC_HVAC_MODES
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
)
def __init__(self, instance: dict[str, Any], ac_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
"""Initialize an AdvantageAir AC unit."""
super().__init__(instance, ac_key)
if self._ac.get("myAutoModeEnabled"):
self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO]
# Set supported features and HVAC modes based on current operating mode
if self._ac.get(ADVANTAGE_AIR_MYAUTO_ENABLED):
# MyAuto
self._attr_supported_features = (
ClimateEntityFeature.FAN_MODE
| ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
)
self._attr_hvac_modes = [
HVACMode.OFF,
HVACMode.COOL,
HVACMode.HEAT,
HVACMode.FAN_ONLY,
HVACMode.DRY,
HVACMode.HEAT_COOL,
]
elif self._ac.get(ADVANTAGE_AIR_MYTEMP_ENABLED):
# MyTemp
self._attr_supported_features = ClimateEntityFeature.FAN_MODE
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
else:
# MyZone
self._attr_supported_features = (
ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
)
self._attr_hvac_modes = [
HVACMode.OFF,
HVACMode.COOL,
HVACMode.HEAT,
HVACMode.FAN_ONLY,
HVACMode.DRY,
]
# Add "ezfan" mode if supported
if self._ac.get(ADVANTAGE_AIR_AUTOFAN):
self._attr_fan_modes += [FAN_AUTO]
@property
def target_temperature(self) -> float:
def target_temperature(self) -> float | None:
"""Return the current target temperature."""
return self._ac["setTemp"]
@@ -116,77 +150,71 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
"""Return the current fan modes."""
return ADVANTAGE_AIR_FAN_MODES.get(self._ac["fan"])
@property
def target_temperature_high(self) -> float | None:
"""Return the temperature cool mode is enabled."""
return self._ac.get(ADVANTAGE_AIR_COOL_TARGET)
@property
def target_temperature_low(self) -> float | None:
"""Return the temperature heat mode is enabled."""
return self._ac.get(ADVANTAGE_AIR_HEAT_TARGET)
async def async_turn_on(self) -> None:
"""Set the HVAC State to on."""
await self.aircon(
{
self.ac_key: {
"info": {
"state": ADVANTAGE_AIR_STATE_ON,
}
}
}
)
await self.async_update_ac({"state": ADVANTAGE_AIR_STATE_ON})
async def async_turn_off(self) -> None:
"""Set the HVAC State to off."""
await self.aircon(
await self.async_update_ac(
{
self.ac_key: {
"info": {
"state": ADVANTAGE_AIR_STATE_OFF,
}
}
"state": ADVANTAGE_AIR_STATE_OFF,
}
)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC Mode and State."""
if hvac_mode == HVACMode.OFF:
await self.aircon(
{self.ac_key: {"info": {"state": ADVANTAGE_AIR_STATE_OFF}}}
)
await self.async_update_ac({"state": ADVANTAGE_AIR_STATE_OFF})
else:
await self.aircon(
await self.async_update_ac(
{
self.ac_key: {
"info": {
"state": ADVANTAGE_AIR_STATE_ON,
"mode": HASS_HVAC_MODES.get(hvac_mode),
}
}
"state": ADVANTAGE_AIR_STATE_ON,
"mode": HASS_HVAC_MODES.get(hvac_mode),
}
)
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set the Fan Mode."""
await self.aircon(
{self.ac_key: {"info": {"fan": HASS_FAN_MODES.get(fan_mode)}}}
)
await self.async_update_ac({"fan": HASS_FAN_MODES.get(fan_mode)})
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the Temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
await self.aircon({self.ac_key: {"info": {"setTemp": temp}}})
if ATTR_TEMPERATURE in kwargs:
await self.async_update_ac({"setTemp": kwargs[ATTR_TEMPERATURE]})
if ATTR_TARGET_TEMP_LOW in kwargs and ATTR_TARGET_TEMP_HIGH in kwargs:
await self.async_update_ac(
{
ADVANTAGE_AIR_COOL_TARGET: kwargs[ATTR_TARGET_TEMP_HIGH],
ADVANTAGE_AIR_HEAT_TARGET: kwargs[ATTR_TARGET_TEMP_LOW],
}
)
class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
"""AdvantageAir Zone control."""
"""AdvantageAir MyTemp Zone control."""
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT_COOL]
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = PRECISION_WHOLE
_attr_max_temp = 32
_attr_min_temp = 16
_attr_hvac_modes = ZONE_HVAC_MODES
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an AdvantageAir Zone control."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = self._zone["name"]
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
)
@property
def hvac_mode(self) -> HVACMode:
@@ -196,7 +224,7 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
return HVACMode.OFF
@property
def current_temperature(self) -> float:
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._zone["measuredTemp"]
@@ -207,23 +235,11 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
async def async_turn_on(self) -> None:
"""Set the HVAC State to on."""
await self.aircon(
{
self.ac_key: {
"zones": {self.zone_key: {"state": ADVANTAGE_AIR_STATE_OPEN}}
}
}
)
await self.async_update_zone({"state": ADVANTAGE_AIR_STATE_OPEN})
async def async_turn_off(self) -> None:
"""Set the HVAC State to off."""
await self.aircon(
{
self.ac_key: {
"zones": {self.zone_key: {"state": ADVANTAGE_AIR_STATE_CLOSE}}
}
}
)
await self.async_update_zone({"state": ADVANTAGE_AIR_STATE_CLOSE})
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC Mode and State."""
@@ -235,4 +251,4 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the Temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
await self.aircon({self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}})
await self.async_update_zone({"setTemp": temp})

View File

@@ -16,7 +16,8 @@ from .const import (
ADVANTAGE_AIR_STATE_OPEN,
DOMAIN as ADVANTAGE_AIR_DOMAIN,
)
from .entity import AdvantageAirZoneEntity
from .entity import AdvantageAirThingEntity, AdvantageAirZoneEntity
from .models import AdvantageAirData
PARALLEL_UPDATES = 0
@@ -28,15 +29,25 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir cover platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[CoverEntity] = []
if aircons := instance["coordinator"].data.get("aircons"):
if aircons := instance.coordinator.data.get("aircons"):
for ac_key, ac_device in aircons.items():
for zone_key, zone in ac_device["zones"].items():
# Only add zone vent controls when zone in vent control mode.
if zone["type"] == 0:
entities.append(AdvantageAirZoneVent(instance, ac_key, zone_key))
if things := instance.coordinator.data.get("myThings"):
for thing in things["things"].values():
if thing["channelDipState"] in [1, 2]: # 1 = "Blind", 2 = "Blind 2"
entities.append(
AdvantageAirThingCover(instance, thing, CoverDeviceClass.BLIND)
)
elif thing["channelDipState"] == 3: # 3 = "Garage door"
entities.append(
AdvantageAirThingCover(instance, thing, CoverDeviceClass.GARAGE)
)
async_add_entities(entities)
@@ -50,7 +61,7 @@ class AdvantageAirZoneVent(AdvantageAirZoneEntity, CoverEntity):
| CoverEntityFeature.SET_POSITION
)
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Vent."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = self._zone["name"]
@@ -69,47 +80,52 @@ class AdvantageAirZoneVent(AdvantageAirZoneEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Fully open zone vent."""
await self.aircon(
{
self.ac_key: {
"zones": {
self.zone_key: {"state": ADVANTAGE_AIR_STATE_OPEN, "value": 100}
}
}
}
await self.async_update_zone(
{"state": ADVANTAGE_AIR_STATE_OPEN, "value": 100},
)
async def async_close_cover(self, **kwargs: Any) -> None:
"""Fully close zone vent."""
await self.aircon(
{
self.ac_key: {
"zones": {self.zone_key: {"state": ADVANTAGE_AIR_STATE_CLOSE}}
}
}
)
await self.async_update_zone({"state": ADVANTAGE_AIR_STATE_CLOSE})
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Change vent position."""
position = round(kwargs[ATTR_POSITION] / 5) * 5
if position == 0:
await self.aircon(
{
self.ac_key: {
"zones": {self.zone_key: {"state": ADVANTAGE_AIR_STATE_CLOSE}}
}
}
)
await self.async_update_zone({"state": ADVANTAGE_AIR_STATE_CLOSE})
else:
await self.aircon(
await self.async_update_zone(
{
self.ac_key: {
"zones": {
self.zone_key: {
"state": ADVANTAGE_AIR_STATE_OPEN,
"value": position,
}
}
}
"state": ADVANTAGE_AIR_STATE_OPEN,
"value": position,
}
)
class AdvantageAirThingCover(AdvantageAirThingEntity, CoverEntity):
"""Representation of Advantage Air Cover controlled by MyPlace."""
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
def __init__(
self,
instance: AdvantageAirData,
thing: dict[str, Any],
device_class: CoverDeviceClass,
) -> None:
"""Initialize an Advantage Air Things Cover."""
super().__init__(instance, thing)
self._attr_device_class = device_class
@property
def is_closed(self) -> bool:
"""Return if cover is fully closed."""
return self._data["value"] == 0
async def async_open_cover(self, **kwargs: Any) -> None:
"""Fully open zone vent."""
return await self.async_turn_on()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Fully close zone vent."""
return await self.async_turn_off()

View File

@@ -1,11 +1,14 @@
"""Advantage Air parent entity class."""
from typing import Any
from advantage_air import ApiError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .models import AdvantageAirData
class AdvantageAirEntity(CoordinatorEntity):
@@ -13,19 +16,34 @@ class AdvantageAirEntity(CoordinatorEntity):
_attr_has_entity_name = True
def __init__(self, instance: dict[str, Any]) -> None:
def __init__(self, instance: AdvantageAirData) -> None:
"""Initialize common aspects of an Advantage Air entity."""
super().__init__(instance["coordinator"])
super().__init__(instance.coordinator)
self._attr_unique_id: str = self.coordinator.data["system"]["rid"]
def update_handle_factory(self, func, *keys):
"""Return the provided API function wrapped.
Adds an error handler and coordinator refresh, and presets keys.
"""
async def update_handle(*values):
try:
if await func(*keys, *values):
await self.coordinator.async_refresh()
except ApiError as err:
raise HomeAssistantError(err) from err
return update_handle
class AdvantageAirAcEntity(AdvantageAirEntity):
"""Parent class for Advantage Air AC Entities."""
def __init__(self, instance: dict[str, Any], ac_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
"""Initialize common aspects of an Advantage Air ac entity."""
super().__init__(instance)
self.aircon = instance["aircon"]
self.ac_key: str = ac_key
self._attr_unique_id += f"-{ac_key}"
@@ -36,6 +54,9 @@ class AdvantageAirAcEntity(AdvantageAirEntity):
model=self.coordinator.data["system"]["sysType"],
name=self.coordinator.data["aircons"][self.ac_key]["info"]["name"],
)
self.async_update_ac = self.update_handle_factory(
instance.api.aircon.async_update_ac, self.ac_key
)
@property
def _ac(self) -> dict[str, Any]:
@@ -45,12 +66,56 @@ class AdvantageAirAcEntity(AdvantageAirEntity):
class AdvantageAirZoneEntity(AdvantageAirAcEntity):
"""Parent class for Advantage Air Zone Entities."""
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize common aspects of an Advantage Air zone entity."""
super().__init__(instance, ac_key)
self.zone_key: str = zone_key
self._attr_unique_id += f"-{zone_key}"
self.async_update_zone = self.update_handle_factory(
instance.api.aircon.async_update_zone, self.ac_key, self.zone_key
)
@property
def _zone(self) -> dict[str, Any]:
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
class AdvantageAirThingEntity(AdvantageAirEntity):
"""Parent class for Advantage Air Things Entities."""
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
"""Initialize common aspects of an Advantage Air Things entity."""
super().__init__(instance)
self._id = thing["id"]
self._attr_unique_id += f"-{self._id}"
self._attr_device_info = DeviceInfo(
via_device=(DOMAIN, self.coordinator.data["system"]["rid"]),
identifiers={(DOMAIN, self._attr_unique_id)},
manufacturer="Advantage Air",
model="MyPlace",
name=thing["name"],
)
self.async_update_value = self.update_handle_factory(
instance.api.things.async_update_value, self._id
)
@property
def _data(self) -> dict:
"""Return the thing data."""
return self.coordinator.data["myThings"]["things"][self._id]
@property
def is_on(self):
"""Return if the thing is considered on."""
return self._data["value"] > 0
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the thing on."""
await self.async_update_value(True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the thing off."""
await self.async_update_value(False)

View File

@@ -7,12 +7,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
ADVANTAGE_AIR_STATE_OFF,
ADVANTAGE_AIR_STATE_ON,
DOMAIN as ADVANTAGE_AIR_DOMAIN,
)
from .entity import AdvantageAirEntity
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity, AdvantageAirThingEntity
from .models import AdvantageAirData
async def async_setup_entry(
@@ -22,15 +19,21 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir light platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[LightEntity] = []
if my_lights := instance["coordinator"].data.get("myLights"):
if my_lights := instance.coordinator.data.get("myLights"):
for light in my_lights["lights"].values():
if light.get("relay"):
entities.append(AdvantageAirLight(instance, light))
else:
entities.append(AdvantageAirLightDimmable(instance, light))
if things := instance.coordinator.data.get("myThings"):
for thing in things["things"].values():
if thing["channelDipState"] == 4: # 4 = "Light (on/off)""
entities.append(AdvantageAirThingLight(instance, thing))
elif thing["channelDipState"] == 5: # 5 = "Light (Dimmable)""
entities.append(AdvantageAirThingLightDimmable(instance, thing))
async_add_entities(entities)
@@ -39,10 +42,10 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
_attr_supported_color_modes = {ColorMode.ONOFF}
def __init__(self, instance: dict[str, Any], light: dict[str, Any]) -> None:
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
"""Initialize an Advantage Air Light."""
super().__init__(instance)
self.lights = instance["lights"]
self._id: str = light["id"]
self._attr_unique_id += f"-{self._id}"
self._attr_device_info = DeviceInfo(
@@ -52,24 +55,27 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
model=light.get("moduleType"),
name=light["name"],
)
self.async_update_state = self.update_handle_factory(
instance.api.lights.async_update_state, self._id
)
@property
def _light(self) -> dict[str, Any]:
def _data(self) -> dict[str, Any]:
"""Return the light object."""
return self.coordinator.data["myLights"]["lights"][self._id]
@property
def is_on(self) -> bool:
"""Return if the light is on."""
return self._light["state"] == ADVANTAGE_AIR_STATE_ON
return self._data["state"] == ADVANTAGE_AIR_STATE_ON
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
await self.lights({"id": self._id, "state": ADVANTAGE_AIR_STATE_ON})
await self.async_update_state(True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self.lights({"id": self._id, "state": ADVANTAGE_AIR_STATE_OFF})
await self.async_update_state(False)
class AdvantageAirLightDimmable(AdvantageAirLight):
@@ -77,14 +83,41 @@ class AdvantageAirLightDimmable(AdvantageAirLight):
_attr_supported_color_modes = {ColorMode.ONOFF, ColorMode.BRIGHTNESS}
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
"""Initialize an Advantage Air Dimmable Light."""
super().__init__(instance, light)
self.async_update_value = self.update_handle_factory(
instance.api.lights.async_update_value, self._id
)
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return round(self._light["value"] * 255 / 100)
return round(self._data["value"] * 255 / 100)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on and optionally set the brightness."""
data: dict[str, Any] = {"id": self._id, "state": ADVANTAGE_AIR_STATE_ON}
if ATTR_BRIGHTNESS in kwargs:
data["value"] = round(kwargs[ATTR_BRIGHTNESS] * 100 / 255)
await self.lights(data)
return await self.async_update_value(round(kwargs[ATTR_BRIGHTNESS] / 2.55))
return await self.async_update_state(True)
class AdvantageAirThingLight(AdvantageAirThingEntity, LightEntity):
"""Representation of Advantage Air Light controlled by myThings."""
_attr_supported_color_modes = {ColorMode.ONOFF}
class AdvantageAirThingLightDimmable(AdvantageAirThingEntity, LightEntity):
"""Representation of Advantage Air Dimmable Light controlled by myThings."""
_attr_supported_color_modes = {ColorMode.ONOFF, ColorMode.BRIGHTNESS}
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return round(self._data["value"] * 255 / 100)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on by setting the brightness."""
await self.async_update_value(round(kwargs.get(ATTR_BRIGHTNESS, 255) / 2.55))

View File

@@ -7,5 +7,5 @@
"iot_class": "local_polling",
"loggers": ["advantage_air"],
"quality_scale": "platinum",
"requirements": ["advantage_air==0.4.1"]
"requirements": ["advantage_air==0.4.4"]
}

View File

@@ -0,0 +1,16 @@
"""The Advantage Air integration models."""
from __future__ import annotations
from dataclasses import dataclass
from advantage_air import advantage_air
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@dataclass
class AdvantageAirData:
"""Data for the Advantage Air integration."""
coordinator: DataUpdateCoordinator
api: advantage_air

View File

@@ -1,5 +1,4 @@
"""Select platform for Advantage Air integration."""
from typing import Any
from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
@@ -8,6 +7,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirAcEntity
from .models import AdvantageAirData
ADVANTAGE_AIR_INACTIVE = "Inactive"
@@ -19,10 +19,10 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir select platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[SelectEntity] = []
if aircons := instance["coordinator"].data.get("aircons"):
if aircons := instance.coordinator.data.get("aircons"):
for ac_key in aircons:
entities.append(AdvantageAirMyZone(instance, ac_key))
async_add_entities(entities)
@@ -34,7 +34,7 @@ class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity):
_attr_icon = "mdi:home-thermometer"
_attr_name = "MyZone"
def __init__(self, instance: dict[str, Any], ac_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
"""Initialize an Advantage Air MyZone control."""
super().__init__(instance, ac_key)
self._attr_unique_id += "-myzone"
@@ -42,11 +42,12 @@ class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity):
self._number_to_name = {0: ADVANTAGE_AIR_INACTIVE}
self._name_to_number = {ADVANTAGE_AIR_INACTIVE: 0}
for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values():
if zone["type"] > 0:
self._name_to_number[zone["name"]] = zone["number"]
self._number_to_name[zone["number"]] = zone["name"]
self._attr_options.append(zone["name"])
if "aircons" in instance.coordinator.data:
for zone in instance.coordinator.data["aircons"][ac_key]["zones"].values():
if zone["type"] > 0:
self._name_to_number[zone["name"]] = zone["number"]
self._number_to_name[zone["number"]] = zone["name"]
self._attr_options.append(zone["name"])
@property
def current_option(self) -> str:
@@ -55,6 +56,4 @@ class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Set the MyZone."""
await self.aircon(
{self.ac_key: {"info": {"myZone": self._name_to_number[option]}}}
)
await self.async_update_ac({"myZone": self._name_to_number[option]})

View File

@@ -19,6 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
from .models import AdvantageAirData
ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes"
ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min"
@@ -34,10 +35,10 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir sensor platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[SensorEntity] = []
if aircons := instance["coordinator"].data.get("aircons"):
if aircons := instance.coordinator.data.get("aircons"):
for ac_key, ac_device in aircons.items():
entities.append(AdvantageAirTimeTo(instance, ac_key, "On"))
entities.append(AdvantageAirTimeTo(instance, ac_key, "Off"))
@@ -65,7 +66,7 @@ class AdvantageAirTimeTo(AdvantageAirAcEntity, SensorEntity):
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, instance: dict[str, Any], ac_key: str, action: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, action: str) -> None:
"""Initialize the Advantage Air timer control."""
super().__init__(instance, ac_key)
self.action = action
@@ -88,7 +89,7 @@ class AdvantageAirTimeTo(AdvantageAirAcEntity, SensorEntity):
async def set_time_to(self, **kwargs: Any) -> None:
"""Set the timer value."""
value = min(720, max(0, int(kwargs[ADVANTAGE_AIR_SET_COUNTDOWN_VALUE])))
await self.aircon({self.ac_key: {"info": {self._time_key: value}}})
await self.async_update_ac({self._time_key: value})
class AdvantageAirZoneVent(AdvantageAirZoneEntity, SensorEntity):
@@ -98,7 +99,7 @@ class AdvantageAirZoneVent(AdvantageAirZoneEntity, SensorEntity):
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Vent Sensor."""
super().__init__(instance, ac_key, zone_key=zone_key)
self._attr_name = f'{self._zone["name"]} vent'
@@ -126,7 +127,7 @@ class AdvantageAirZoneSignal(AdvantageAirZoneEntity, SensorEntity):
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone wireless signal sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} signal'
@@ -160,7 +161,7 @@ class AdvantageAirZoneTemp(AdvantageAirZoneEntity, SensorEntity):
_attr_entity_registry_enabled_default = False
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, instance: dict[str, Any], ac_key: str, zone_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} temperature'

View File

@@ -1,7 +1,7 @@
"""Switch platform for Advantage Air integration."""
from typing import Any
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -11,7 +11,8 @@ from .const import (
ADVANTAGE_AIR_STATE_ON,
DOMAIN as ADVANTAGE_AIR_DOMAIN,
)
from .entity import AdvantageAirAcEntity
from .entity import AdvantageAirAcEntity, AdvantageAirThingEntity
from .models import AdvantageAirData
async def async_setup_entry(
@@ -21,13 +22,17 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir switch platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[SwitchEntity] = []
if aircons := instance["coordinator"].data.get("aircons"):
if aircons := instance.coordinator.data.get("aircons"):
for ac_key, ac_device in aircons.items():
if ac_device["info"]["freshAirStatus"] != "none":
entities.append(AdvantageAirFreshAir(instance, ac_key))
if things := instance.coordinator.data.get("myThings"):
for thing in things["things"].values():
if thing["channelDipState"] == 8: # 8 = Other relay
entities.append(AdvantageAirRelay(instance, thing))
async_add_entities(entities)
@@ -36,8 +41,9 @@ class AdvantageAirFreshAir(AdvantageAirAcEntity, SwitchEntity):
_attr_icon = "mdi:air-filter"
_attr_name = "Fresh air"
_attr_device_class = SwitchDeviceClass.SWITCH
def __init__(self, instance: dict[str, Any], ac_key: str) -> None:
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
"""Initialize an Advantage Air fresh air control."""
super().__init__(instance, ac_key)
self._attr_unique_id += "-freshair"
@@ -49,12 +55,14 @@ class AdvantageAirFreshAir(AdvantageAirAcEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn fresh air on."""
await self.aircon(
{self.ac_key: {"info": {"freshAirStatus": ADVANTAGE_AIR_STATE_ON}}}
)
await self.async_update_ac({"freshAirStatus": ADVANTAGE_AIR_STATE_ON})
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn fresh air off."""
await self.aircon(
{self.ac_key: {"info": {"freshAirStatus": ADVANTAGE_AIR_STATE_OFF}}}
)
await self.async_update_ac({"freshAirStatus": ADVANTAGE_AIR_STATE_OFF})
class AdvantageAirRelay(AdvantageAirThingEntity, SwitchEntity):
"""Representation of Advantage Air Thing."""
_attr_device_class = SwitchDeviceClass.SWITCH

View File

@@ -1,5 +1,4 @@
"""Advantage Air Update platform."""
from typing import Any
from homeassistant.components.update import UpdateEntity
from homeassistant.config_entries import ConfigEntry
@@ -9,6 +8,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity
from .models import AdvantageAirData
async def async_setup_entry(
@@ -18,7 +18,7 @@ async def async_setup_entry(
) -> None:
"""Set up AdvantageAir update platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
async_add_entities([AdvantageAirApp(instance)])
@@ -28,7 +28,7 @@ class AdvantageAirApp(AdvantageAirEntity, UpdateEntity):
_attr_name = "App"
def __init__(self, instance: dict[str, Any]) -> None:
def __init__(self, instance: AdvantageAirData) -> None:
"""Initialize the Advantage Air App."""
super().__init__(instance)
self._attr_device_info = DeviceInfo(

View File

@@ -380,7 +380,6 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
else:
entry.version = version
hass.config_entries.async_update_entry(entry)
LOGGER.info("Migration to version %s successful", version)

View File

@@ -25,7 +25,7 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.core import Event, HassJob, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -237,7 +237,13 @@ class Alert(Entity):
"""Schedule a notification."""
delay = self._delay[self._next_delay]
next_msg = now() + delay
self._cancel = async_track_point_in_time(self.hass, self._notify, next_msg)
self._cancel = async_track_point_in_time(
self.hass,
HassJob(
self._notify, name="Schedule notify alert", cancel_on_shutdown=True
),
next_msg,
)
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
async def _notify(self, *args: Any) -> None:

View File

@@ -117,7 +117,6 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
en_reg.async_clear_config_entry(entry.entry_id)
version = entry.version = 2
hass.config_entries.async_update_entry(entry)
LOGGER.info("Migration to version %s successful", version)

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["aioambient"],
"requirements": ["aioambient==2021.11.0"]
"requirements": ["aioambient==2023.04.0"]
}

View File

@@ -19,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from .const import ATTRIBUTION, CONF_STATION_ID, SCAN_INTERVAL
from .const import CONF_STATION_ID, SCAN_INTERVAL
_LOGGER: Final = logging.getLogger(__name__)
@@ -54,6 +54,8 @@ async def async_setup_platform(
class AmpioSmogQuality(AirQualityEntity):
"""Implementation of an Ampio Smog air quality entity."""
_attr_attribution = "Data provided by Ampio"
def __init__(
self, api: AmpioSmogMapData, station_id: str, name: str | None
) -> None:
@@ -82,11 +84,6 @@ class AmpioSmogQuality(AirQualityEntity):
"""Return the particulate matter 10 level."""
return self._ampio.api.pm10 # type: ignore[no-any-return]
@property
def attribution(self) -> str:
"""Return the attribution."""
return ATTRIBUTION
async def async_update(self) -> None:
"""Get the latest data from the AmpioMap API."""
await self._ampio.async_update()

View File

@@ -2,6 +2,5 @@
from datetime import timedelta
from typing import Final
ATTRIBUTION: Final = "Data provided by Ampio"
CONF_STATION_ID: Final = "station_id"
SCAN_INTERVAL: Final = timedelta(minutes=10)

View File

@@ -5,7 +5,7 @@ import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.core import Event, HassJob, HomeAssistant, callback
from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.typing import ConfigType
@@ -24,11 +24,23 @@ async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
def start_schedule(_event: Event) -> None:
"""Start the send schedule after the started event."""
# Wait 15 min after started
async_call_later(hass, 900, analytics.send_analytics)
async_call_later(
hass,
900,
HassJob(
analytics.send_analytics,
name="analytics schedule",
cancel_on_shutdown=True,
),
)
# Send every day
async_track_time_interval(
hass, analytics.send_analytics, INTERVAL, name="analytics daily"
hass,
analytics.send_analytics,
INTERVAL,
name="analytics daily",
cancel_on_shutdown=True,
)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_schedule)

View File

@@ -1,4 +1,4 @@
"""Support for functionality to interact with Android TV/Fire TV devices."""
"""Support for functionality to interact with Android/Fire TV devices."""
from __future__ import annotations
from collections.abc import Mapping
@@ -135,11 +135,11 @@ async def async_connect_androidtv(
if not aftv.available:
# Determine the name that will be used for the device in the log
if config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
device_name = "Android TV device"
device_name = "Android device"
elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
device_name = "Fire TV device"
else:
device_name = "Android TV / Fire TV device"
device_name = "Android / Fire TV device"
error_message = f"Could not connect to {device_name} at {address} {adb_log}"
return None, error_message
@@ -148,7 +148,7 @@ async def async_connect_androidtv(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Android TV platform."""
"""Set up Android Debug Bridge platform."""
state_det_rules = entry.options.get(CONF_STATE_DETECTION_RULES)
if CONF_ADB_SERVER_IP not in entry.data:
@@ -167,7 +167,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady(error_message)
async def async_close_connection(event):
"""Close Android TV connection on HA Stop."""
"""Close Android Debug Bridge connection on HA Stop."""
await aftv.adb_close()
entry.async_on_unload(

View File

@@ -1,4 +1,4 @@
"""Config flow to configure the Android TV integration."""
"""Config flow to configure the Android Debug Bridge integration."""
from __future__ import annotations
import logging
@@ -114,13 +114,14 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN):
async def _async_check_connection(
self, user_input: dict[str, Any]
) -> tuple[str | None, str | None]:
"""Attempt to connect the Android TV."""
"""Attempt to connect the Android device."""
try:
aftv, error_message = await async_connect_androidtv(self.hass, user_input)
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Unknown error connecting with Android TV at %s", user_input[CONF_HOST]
"Unknown error connecting with Android device at %s",
user_input[CONF_HOST],
)
return RESULT_UNKNOWN, None
@@ -130,7 +131,7 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN):
dev_prop = aftv.device_properties
_LOGGER.info(
"Android TV at %s: %s = %r, %s = %r",
"Android device at %s: %s = %r, %s = %r",
user_input[CONF_HOST],
PROP_ETHMAC,
dev_prop.get(PROP_ETHMAC),
@@ -184,7 +185,7 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN):
class OptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Handle an option flow for Android TV."""
"""Handle an option flow for Android Debug Bridge."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""

View File

@@ -1,4 +1,4 @@
"""Android TV component constants."""
"""Android Debug Bridge component constants."""
DOMAIN = "androidtv"
ANDROID_DEV = DOMAIN

View File

@@ -1,6 +1,6 @@
{
"domain": "androidtv",
"name": "Android TV",
"name": "Android Debug Bridge",
"codeowners": ["@JeffLIrion", "@ollo69"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/androidtv",

View File

@@ -1,4 +1,4 @@
"""Support for functionality to interact with Android TV / Fire TV devices."""
"""Support for functionality to interact with Android / Fire TV devices."""
from __future__ import annotations
from collections.abc import Awaitable, Callable, Coroutine
@@ -87,7 +87,7 @@ async def async_setup_entry(
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Android TV entity."""
"""Set up the Android Debug Bridge entity."""
aftv = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV]
device_class = aftv.DEVICE_CLASS
device_type = (
@@ -201,7 +201,7 @@ def adb_decorator(
class ADBDevice(MediaPlayerEntity):
"""Representation of an Android TV or Fire TV device."""
"""Representation of an Android or Fire TV device."""
_attr_device_class = MediaPlayerDeviceClass.TV
@@ -214,7 +214,7 @@ class ADBDevice(MediaPlayerEntity):
entry_id,
entry_data,
):
"""Initialize the Android TV / Fire TV device."""
"""Initialize the Android / Fire TV device."""
self.aftv = aftv
self._attr_name = name
self._attr_unique_id = unique_id
@@ -384,7 +384,7 @@ class ADBDevice(MediaPlayerEntity):
@adb_decorator()
async def adb_command(self, command):
"""Send an ADB command to an Android TV / Fire TV device."""
"""Send an ADB command to an Android / Fire TV device."""
if key := KEYS.get(command):
await self.aftv.adb_shell(f"input keyevent {key}")
return
@@ -422,13 +422,13 @@ class ADBDevice(MediaPlayerEntity):
persistent_notification.async_create(
self.hass,
msg,
title="Android TV",
title="Android Debug Bridge",
)
_LOGGER.info("%s", msg)
@adb_decorator()
async def service_download(self, device_path, local_path):
"""Download a file from your Android TV / Fire TV device to your Home Assistant instance."""
"""Download a file from your Android / Fire TV device to your Home Assistant instance."""
if not self.hass.config.is_allowed_path(local_path):
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
return
@@ -437,7 +437,7 @@ class ADBDevice(MediaPlayerEntity):
@adb_decorator()
async def service_upload(self, device_path, local_path):
"""Upload a file from your Home Assistant instance to an Android TV / Fire TV device."""
"""Upload a file from your Home Assistant instance to an Android / Fire TV device."""
if not self.hass.config.is_allowed_path(local_path):
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
return
@@ -446,7 +446,7 @@ class ADBDevice(MediaPlayerEntity):
class AndroidTVDevice(ADBDevice):
"""Representation of an Android TV device."""
"""Representation of an Android device."""
_attr_supported_features = (
MediaPlayerEntityFeature.PAUSE

View File

@@ -1,8 +1,8 @@
# Describes the format for available Android TV and Fire TV services
# Describes the format for available Android and Fire TV services
adb_command:
name: ADB command
description: Send an ADB command to an Android TV / Fire TV device.
description: Send an ADB command to an Android / Fire TV device.
target:
entity:
integration: androidtv
@@ -17,7 +17,7 @@ adb_command:
text:
download:
name: Download
description: Download a file from your Android TV / Fire TV device to your Home Assistant instance.
description: Download a file from your Android / Fire TV device to your Home Assistant instance.
target:
entity:
integration: androidtv
@@ -25,7 +25,7 @@ download:
fields:
device_path:
name: Device path
description: The filepath on the Android TV / Fire TV device.
description: The filepath on the Android / Fire TV device.
required: true
example: "/storage/emulated/0/Download/example.txt"
selector:
@@ -39,7 +39,7 @@ download:
text:
upload:
name: Upload
description: Upload a file from your Home Assistant instance to an Android TV / Fire TV device.
description: Upload a file from your Home Assistant instance to an Android / Fire TV device.
target:
entity:
integration: androidtv
@@ -47,7 +47,7 @@ upload:
fields:
device_path:
name: Device path
description: The filepath on the Android TV / Fire TV device.
description: The filepath on the Android / Fire TV device.
required: true
example: "/storage/emulated/0/Download/example.txt"
selector:

View File

@@ -38,7 +38,7 @@
}
},
"apps": {
"title": "Configure Android TV Apps",
"title": "Configure Android Apps",
"description": "Configure application id {app_id}",
"data": {
"app_name": "Application Name",
@@ -47,7 +47,7 @@
}
},
"rules": {
"title": "Configure Android TV state detection rules",
"title": "Configure Android state detection rules",
"description": "Configure detection rule for application id {rule_id}",
"data": {
"rule_id": "Application ID",

View File

@@ -0,0 +1,67 @@
"""The Android TV Remote integration."""
from __future__ import annotations
from androidtvremote2 import (
AndroidTVRemote,
CannotConnect,
ConnectionClosed,
InvalidAuth,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN
from .helpers import create_api
PLATFORMS: list[Platform] = [Platform.REMOTE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Android TV Remote from a config entry."""
api = create_api(hass, entry.data[CONF_HOST])
try:
await api.async_connect()
except InvalidAuth as exc:
# The Android TV is hard reset or the certificate and key files were deleted.
raise ConfigEntryAuthFailed from exc
except (CannotConnect, ConnectionClosed) as exc:
# The Android TV is network unreachable. Raise exception and let Home Assistant retry
# later. If device gets a new IP address the zeroconf flow will update the config.
raise ConfigEntryNotReady from exc
def reauth_needed() -> None:
"""Start a reauth flow if Android TV is hard reset while reconnecting."""
entry.async_start_reauth(hass)
# Start a task (canceled in disconnect) to keep reconnecting if device becomes
# network unreachable. If device gets a new IP address the zeroconf flow will
# update the config entry data and reload the config entry.
api.keep_reconnecting(reauth_needed)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = api
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@callback
def on_hass_stop(event) -> None:
"""Stop push updates when hass stops."""
api.disconnect()
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
api: AndroidTVRemote = hass.data[DOMAIN].pop(entry.entry_id)
api.disconnect()
return unload_ok

View File

@@ -0,0 +1,187 @@
"""Config flow for Android TV Remote integration."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from androidtvremote2 import (
AndroidTVRemote,
CannotConnect,
ConnectionClosed,
InvalidAuth,
)
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.device_registry import format_mac
from .const import DOMAIN
from .helpers import create_api
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
}
)
STEP_PAIR_DATA_SCHEMA = vol.Schema(
{
vol.Required("pin"): str,
}
)
class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Android TV Remote."""
VERSION = 1
def __init__(self) -> None:
"""Initialize a new AndroidTVRemoteConfigFlow."""
self.api: AndroidTVRemote | None = None
self.reauth_entry: config_entries.ConfigEntry | None = None
self.host: str | None = None
self.name: str | None = None
self.mac: str | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
self.host = user_input["host"]
assert self.host
api = create_api(self.hass, self.host)
try:
self.name, self.mac = await api.async_get_name_and_mac()
assert self.mac
await self.async_set_unique_id(format_mac(self.mac))
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Likely invalid IP address or device is network unreachable. Stay
# in the user step allowing the user to enter a different host.
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors,
)
async def _async_start_pair(self) -> FlowResult:
"""Start pairing with the Android TV. Navigate to the pair flow to enter the PIN shown on screen."""
assert self.host
self.api = create_api(self.hass, self.host)
await self.api.async_generate_cert_if_missing()
await self.api.async_start_pairing()
return await self.async_step_pair()
async def async_step_pair(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the pair step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
pin = user_input["pin"]
assert self.api
await self.api.async_finish_pairing(pin)
if self.reauth_entry:
await self.hass.config_entries.async_reload(
self.reauth_entry.entry_id
)
return self.async_abort(reason="reauth_successful")
assert self.name
return self.async_create_entry(
title=self.name,
data={
CONF_HOST: self.host,
CONF_NAME: self.name,
CONF_MAC: self.mac,
},
)
except InvalidAuth:
# Invalid PIN. Stay in the pair step allowing the user to enter
# a different PIN.
errors["base"] = "invalid_auth"
except ConnectionClosed:
# Either user canceled pairing on the Android TV itself (most common)
# or device doesn't respond to the specified host (device was unplugged,
# network was unplugged, or device got a new IP address).
# Attempt to pair again.
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Device doesn't respond to the specified host. Abort.
# If we are in the user flow we could go back to the user step to allow
# them to enter a new IP address but we cannot do that for the zeroconf
# flow. Simpler to abort for both flows.
return self.async_abort(reason="cannot_connect")
return self.async_show_form(
step_id="pair",
data_schema=STEP_PAIR_DATA_SCHEMA,
description_placeholders={CONF_NAME: self.name},
errors=errors,
)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""
self.host = discovery_info.host
self.name = discovery_info.name.removesuffix("._androidtvremote2._tcp.local.")
self.mac = discovery_info.properties.get("bt")
assert self.mac
await self.async_set_unique_id(format_mac(self.mac))
self._abort_if_unique_id_configured(
updates={CONF_HOST: self.host, CONF_NAME: self.name}
)
self.context.update({"title_placeholders": {CONF_NAME: self.name}})
return await self.async_step_zeroconf_confirm()
async def async_step_zeroconf_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initiated by zeroconf."""
if user_input is not None:
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Device became network unreachable after discovery.
# Abort and let discovery find it again later.
return self.async_abort(reason="cannot_connect")
return self.async_show_form(
step_id="zeroconf_confirm",
description_placeholders={CONF_NAME: self.name},
)
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle configuration by re-auth."""
self.host = entry_data[CONF_HOST]
self.name = entry_data[CONF_NAME]
self.mac = entry_data[CONF_MAC]
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Dialog that informs the user that reauth is required."""
errors: dict[str, str] = {}
if user_input is not None:
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Device is network unreachable. Abort.
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="reauth_confirm",
description_placeholders={CONF_NAME: self.name},
errors=errors,
)

View File

@@ -0,0 +1,6 @@
"""Constants for the Android TV Remote integration."""
from __future__ import annotations
from typing import Final
DOMAIN: Final = "androidtv_remote"

View File

@@ -0,0 +1,29 @@
"""Diagnostics support for Android TV Remote."""
from __future__ import annotations
from typing import Any
from androidtvremote2 import AndroidTVRemote
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MAC
from homeassistant.core import HomeAssistant
from .const import DOMAIN
TO_REDACT = {CONF_HOST, CONF_MAC}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
api: AndroidTVRemote = hass.data[DOMAIN].pop(entry.entry_id)
return async_redact_data(
{
"api_device_info": api.device_info,
"config_entry_data": entry.data,
},
TO_REDACT,
)

View File

@@ -0,0 +1,18 @@
"""Helper functions for Android TV Remote integration."""
from __future__ import annotations
from androidtvremote2 import AndroidTVRemote
from homeassistant.core import HomeAssistant
from homeassistant.helpers.storage import STORAGE_DIR
def create_api(hass: HomeAssistant, host: str) -> AndroidTVRemote:
"""Create an AndroidTVRemote instance."""
return AndroidTVRemote(
client_name="Home Assistant",
certfile=hass.config.path(STORAGE_DIR, "androidtv_remote_cert.pem"),
keyfile=hass.config.path(STORAGE_DIR, "androidtv_remote_key.pem"),
host=host,
loop=hass.loop,
)

View File

@@ -0,0 +1,13 @@
{
"domain": "androidtv_remote",
"name": "Android TV Remote",
"codeowners": ["@tronikos"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/androidtv_remote",
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["androidtvremote2"],
"quality_scale": "platinum",
"requirements": ["androidtvremote2==0.0.7"],
"zeroconf": ["_androidtvremote2._tcp.local."]
}

View File

@@ -0,0 +1,154 @@
"""Remote control support for Android TV Remote."""
from __future__ import annotations
import asyncio
from collections.abc import Iterable
import logging
from typing import Any
from androidtvremote2 import AndroidTVRemote, ConnectionClosed
from homeassistant.components.remote import (
ATTR_ACTIVITY,
ATTR_DELAY_SECS,
ATTR_HOLD_SECS,
ATTR_NUM_REPEATS,
DEFAULT_DELAY_SECS,
DEFAULT_HOLD_SECS,
DEFAULT_NUM_REPEATS,
RemoteEntity,
RemoteEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
PARALLEL_UPDATES = 0
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Android TV remote entity based on a config entry."""
api: AndroidTVRemote = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities([AndroidTVRemoteEntity(api, config_entry)])
class AndroidTVRemoteEntity(RemoteEntity):
"""Representation of an Android TV Remote."""
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(self, api: AndroidTVRemote, config_entry: ConfigEntry) -> None:
"""Initialize device."""
self._api = api
self._host = config_entry.data[CONF_HOST]
self._name = config_entry.data[CONF_NAME]
self._attr_unique_id = config_entry.unique_id
self._attr_supported_features = RemoteEntityFeature.ACTIVITY
self._attr_is_on = api.is_on
self._attr_current_activity = api.current_app
device_info = api.device_info
assert config_entry.unique_id
assert device_info
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, config_entry.data[CONF_MAC])},
identifiers={(DOMAIN, config_entry.unique_id)},
name=self._name,
manufacturer=device_info["manufacturer"],
model=device_info["model"],
)
@callback
def is_on_updated(is_on: bool) -> None:
self._attr_is_on = is_on
self.async_write_ha_state()
@callback
def current_app_updated(current_app: str) -> None:
self._attr_current_activity = current_app
self.async_write_ha_state()
@callback
def is_available_updated(is_available: bool) -> None:
if is_available:
_LOGGER.info(
"Reconnected to %s at %s",
self._name,
self._host,
)
else:
_LOGGER.warning(
"Disconnected from %s at %s",
self._name,
self._host,
)
self._attr_available = is_available
self.async_write_ha_state()
api.add_is_on_updated_callback(is_on_updated)
api.add_current_app_updated_callback(current_app_updated)
api.add_is_available_updated_callback(is_available_updated)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the Android TV on."""
if not self.is_on:
self._send_key_command("POWER")
activity = kwargs.get(ATTR_ACTIVITY, "")
if activity:
self._send_launch_app_command(activity)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the Android TV off."""
if self.is_on:
self._send_key_command("POWER")
async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
"""Send commands to one device."""
num_repeats = kwargs.get(ATTR_NUM_REPEATS, DEFAULT_NUM_REPEATS)
delay_secs = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS)
hold_secs = kwargs.get(ATTR_HOLD_SECS, DEFAULT_HOLD_SECS)
for _ in range(num_repeats):
for single_command in command:
if hold_secs:
self._send_key_command(single_command, "START_LONG")
await asyncio.sleep(hold_secs)
self._send_key_command(single_command, "END_LONG")
else:
self._send_key_command(single_command, "SHORT")
await asyncio.sleep(delay_secs)
def _send_key_command(self, key_code: str, direction: str = "SHORT") -> None:
"""Send a key press to Android TV.
This does not block; it buffers the data and arranges for it to be sent out asynchronously.
"""
try:
self._api.send_key_command(key_code, direction)
except ConnectionClosed as exc:
raise HomeAssistantError(
"Connection to Android TV device is closed"
) from exc
def _send_launch_app_command(self, app_link: str) -> None:
"""Launch an app on Android TV.
This does not block; it buffers the data and arranges for it to be sent out asynchronously.
"""
try:
self._api.send_launch_app_command(app_link)
except ConnectionClosed as exc:
raise HomeAssistantError(
"Connection to Android TV device is closed"
) from exc

View File

@@ -0,0 +1,38 @@
{
"config": {
"flow_title": "{name}",
"step": {
"user": {
"description": "Enter the IP address of the Android TV you want to add to Home Assistant. It will turn on and a pairing code will be displayed on it that you will need to enter in the next screen.",
"data": {
"host": "[%key:common::config_flow::data::host%]"
}
},
"zeroconf_confirm": {
"title": "Discovered Android TV",
"description": "Do you want to add the Android TV ({name}) to Home Assistant? It will turn on and a pairing code will be displayed on it that you will need to enter in the next screen."
},
"pair": {
"description": "Enter the pairing code displayed on the Android TV ({name}).",
"data": {
"pin": "[%key:common::config_flow::data::pin%]"
}
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "You need to pair again with the Android TV ({name})."
}
},
"error": {
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"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%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
}
}

View File

@@ -0,0 +1,86 @@
"""The Anova integration."""
from __future__ import annotations
import logging
from anova_wifi import (
AnovaApi,
AnovaPrecisionCooker,
AnovaPrecisionCookerSensor,
InvalidLogin,
NoDevicesFound,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client
from .const import DOMAIN
from .coordinator import AnovaCoordinator
from .models import AnovaData
from .util import serialize_device_list
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Anova from a config entry."""
api = AnovaApi(
aiohttp_client.async_get_clientsession(hass),
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
)
try:
await api.authenticate()
except InvalidLogin as err:
_LOGGER.error(
"Login was incorrect - please log back in through the config flow. %s", err
)
return False
assert api.jwt
api.existing_devices = [
AnovaPrecisionCooker(
aiohttp_client.async_get_clientsession(hass),
device[0],
device[1],
api.jwt,
)
for device in entry.data["devices"]
]
try:
new_devices = await api.get_devices()
except NoDevicesFound:
# get_devices raises an exception if no devices are online
new_devices = []
devices = api.existing_devices
if new_devices:
hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
**{"devices": serialize_device_list(devices)},
},
)
coordinators = [AnovaCoordinator(hass, device) for device in devices]
for coordinator in coordinators:
await coordinator.async_config_entry_first_refresh()
firmware_version = coordinator.data["sensors"][
AnovaPrecisionCookerSensor.FIRMWARE_VERSION
]
coordinator.async_setup(str(firmware_version))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AnovaData(
api_jwt=api.jwt, precision_cookers=devices, coordinators=coordinators
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@@ -0,0 +1,61 @@
"""Config flow for Anova."""
from __future__ import annotations
from anova_wifi import AnovaApi, InvalidLogin, NoDevicesFound
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client
from .const import DOMAIN
from .util import serialize_device_list
class AnovaConfligFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Sets up a config flow for Anova."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle a flow initiated by the user."""
errors: dict[str, str] = {}
if user_input is not None:
api = AnovaApi(
aiohttp_client.async_get_clientsession(self.hass),
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
await self.async_set_unique_id(user_input[CONF_USERNAME].lower())
self._abort_if_unique_id_configured()
try:
await api.authenticate()
devices = await api.get_devices()
except InvalidLogin:
errors["base"] = "invalid_auth"
except NoDevicesFound:
errors["base"] = "no_devices_found"
except Exception: # pylint: disable=broad-except
errors["base"] = "unknown"
else:
# We store device list in config flow in order to persist found devices on restart, as the Anova api get_devices does not return any devices that are offline.
device_list = serialize_device_list(devices)
return self.async_create_entry(
title="Anova",
data={
CONF_USERNAME: api.username,
CONF_PASSWORD: api.password,
"devices": device_list,
},
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
),
errors=errors,
)

View File

@@ -0,0 +1,6 @@
"""Constants for the Anova integration."""
DOMAIN = "anova"
ANOVA_CLIENT = "anova_api_client"
ANOVA_FIRMWARE_VERSION = "anova_firmware_version"

View File

@@ -0,0 +1,55 @@
"""Support for Anova Coordinators."""
from datetime import timedelta
import logging
from anova_wifi import AnovaOffline, AnovaPrecisionCooker
import async_timeout
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
class AnovaCoordinator(DataUpdateCoordinator):
"""Anova custom coordinator."""
data: dict[str, dict[str, str | int | float]]
def __init__(
self,
hass: HomeAssistant,
anova_device: AnovaPrecisionCooker,
) -> None:
"""Set up Anova Coordinator."""
super().__init__(
hass,
name="Anova Precision Cooker",
logger=_LOGGER,
update_interval=timedelta(seconds=30),
)
assert self.config_entry is not None
self._device_unique_id = anova_device.device_key
self.anova_device = anova_device
self.device_info: DeviceInfo | None = None
@callback
def async_setup(self, firmware_version: str) -> None:
"""Set the firmware version info."""
self.device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_unique_id)},
name="Anova Precision Cooker",
manufacturer="Anova",
model="Precision Cooker",
sw_version=firmware_version,
)
async def _async_update_data(self) -> dict[str, dict[str, str | int | float]]:
try:
async with async_timeout.timeout(5):
return await self.anova_device.update()
except AnovaOffline as err:
raise UpdateFailed(err) from err

View File

@@ -0,0 +1,30 @@
"""Base entity for the Anova integration."""
from __future__ import annotations
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import AnovaCoordinator
class AnovaEntity(CoordinatorEntity[AnovaCoordinator], Entity):
"""Defines a Anova entity."""
def __init__(self, coordinator: AnovaCoordinator) -> None:
"""Initialize the Anova entity."""
super().__init__(coordinator)
self.device = coordinator.anova_device
self._attr_device_info = coordinator.device_info
self._attr_has_entity_name = True
class AnovaDescriptionEntity(AnovaEntity, Entity):
"""Defines a Anova entity that uses a description."""
def __init__(
self, coordinator: AnovaCoordinator, description: EntityDescription
) -> None:
"""Initialize the entity and declare unique id based on description key."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator._device_unique_id}_{description.key}"

View File

@@ -0,0 +1,10 @@
{
"domain": "anova",
"name": "Anova",
"codeowners": ["@Lash-L"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/anova",
"iot_class": "cloud_polling",
"loggers": ["anova_wifi"],
"requirements": ["anova-wifi==0.8.0"]
}

View File

@@ -0,0 +1,15 @@
"""Dataclass models for the Anova integration."""
from dataclasses import dataclass
from anova_wifi import AnovaPrecisionCooker
from .coordinator import AnovaCoordinator
@dataclass
class AnovaData:
"""Data for the Anova integration."""
api_jwt: str
precision_cookers: list[AnovaPrecisionCooker]
coordinators: list[AnovaCoordinator]

View File

@@ -0,0 +1,97 @@
"""Support for Anova Sensors."""
from __future__ import annotations
from anova_wifi import AnovaPrecisionCookerSensor
from homeassistant import config_entries
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfTemperature, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .entity import AnovaDescriptionEntity
from .models import AnovaData
SENSOR_DESCRIPTIONS: list[SensorEntityDescription] = [
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.COOK_TIME,
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfTime.SECONDS,
icon="mdi:clock-outline",
translation_key="cook_time",
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.STATE, translation_key="state"
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.MODE, translation_key="mode"
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.TARGET_TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:thermometer",
translation_key="target_temperature",
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.COOK_TIME_REMAINING,
native_unit_of_measurement=UnitOfTime.SECONDS,
icon="mdi:clock-outline",
translation_key="cook_time_remaining",
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.HEATER_TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:thermometer",
translation_key="heater_temperature",
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.TRIAC_TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:thermometer",
translation_key="triac_temperature",
),
SensorEntityDescription(
key=AnovaPrecisionCookerSensor.WATER_TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:thermometer",
translation_key="water_temperature",
),
]
async def async_setup_entry(
hass: HomeAssistant,
entry: config_entries.ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Anova device."""
anova_data: AnovaData = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
AnovaSensor(coordinator, description)
for coordinator in anova_data.coordinators
for description in SENSOR_DESCRIPTIONS
)
class AnovaSensor(AnovaDescriptionEntity, SensorEntity):
"""A sensor using Anova coordinator."""
@property
def native_value(self) -> StateType:
"""Return the state."""
return self.coordinator.data["sensors"][self.entity_description.key]

View File

@@ -0,0 +1,51 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
},
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
}
},
"abort": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"no_devices_found": "No devices were found. Make sure you have at least one Anova device online"
}
},
"entity": {
"sensor": {
"cook_time": {
"name": "Cook time"
},
"state": {
"name": "State"
},
"mode": {
"name": "Mode"
},
"target_temperature": {
"name": "Target temperature"
},
"cook_time_remaining": {
"name": "Cook time remaining"
},
"heater_temperature": {
"name": "Heater temperature"
},
"triac_temperature": {
"name": "Triac temperature"
},
"water_temperature": {
"name": "Water temperature"
}
}
}
}

View File

@@ -0,0 +1,8 @@
"""Anova utilities."""
from anova_wifi import AnovaPrecisionCooker
def serialize_device_list(devices: list[AnovaPrecisionCooker]) -> list[tuple[str, str]]:
"""Turn the device list into a serializable list that can be reconstructed."""
return [(device.device_key, device.type) for device in devices]

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_status": "No status is reported from [%key:common::config_flow::data::host%]"
"no_status": "No status is reported from host"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"

View File

@@ -75,7 +75,7 @@ class AuthorizationServer:
token_url: str
class ApplicationCredentialsStorageCollection(collection.StorageCollection):
class ApplicationCredentialsStorageCollection(collection.DictStorageCollection):
"""Application credential collection stored in storage."""
CREATE_SCHEMA = vol.Schema(CREATE_FIELDS)
@@ -94,7 +94,7 @@ class ApplicationCredentialsStorageCollection(collection.StorageCollection):
return f"{info[CONF_DOMAIN]}.{info[CONF_CLIENT_ID]}"
async def _update_data(
self, data: dict[str, str], update_data: dict[str, str]
self, item: dict[str, str], update_data: dict[str, str]
) -> dict[str, str]:
"""Return a new updated data object."""
raise ValueError("Updates not supported")
@@ -144,13 +144,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
id_manager = collection.IDManager()
storage_collection = ApplicationCredentialsStorageCollection(
Store(hass, STORAGE_VERSION, STORAGE_KEY),
logging.getLogger(f"{__name__}.storage_collection"),
id_manager,
)
await storage_collection.async_load()
hass.data[DOMAIN][DATA_STORAGE] = storage_collection
collection.StorageCollectionWebsocket(
collection.DictStorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
).async_setup(hass)

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
"iot_class": "local_polling",
"loggers": ["arcam"],
"requirements": ["arcam-fmj==1.2.1"],
"requirements": ["arcam-fmj==1.3.0"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@@ -0,0 +1,80 @@
"""The Assist pipeline integration."""
from __future__ import annotations
from collections.abc import AsyncIterable
from homeassistant.components import stt
from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
from .error import PipelineNotFound
from .pipeline import (
Pipeline,
PipelineEvent,
PipelineEventCallback,
PipelineEventType,
PipelineInput,
PipelineRun,
PipelineStage,
async_create_default_pipeline,
async_get_pipeline,
async_get_pipelines,
async_setup_pipeline_store,
)
from .websocket_api import async_register_websocket_api
__all__ = (
"DOMAIN",
"async_create_default_pipeline",
"async_get_pipelines",
"async_setup",
"async_pipeline_from_audio_stream",
"Pipeline",
"PipelineEvent",
"PipelineEventType",
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Assist pipeline integration."""
await async_setup_pipeline_store(hass)
async_register_websocket_api(hass)
return True
async def async_pipeline_from_audio_stream(
hass: HomeAssistant,
context: Context,
event_callback: PipelineEventCallback,
stt_metadata: stt.SpeechMetadata,
stt_stream: AsyncIterable[bytes],
pipeline_id: str | None = None,
conversation_id: str | None = None,
tts_audio_output: str | None = None,
) -> None:
"""Create an audio pipeline from an audio stream."""
pipeline = async_get_pipeline(hass, pipeline_id=pipeline_id)
if pipeline is None:
raise PipelineNotFound(
"pipeline_not_found", f"Pipeline {pipeline_id} not found"
)
pipeline_input = PipelineInput(
conversation_id=conversation_id,
stt_metadata=stt_metadata,
stt_stream=stt_stream,
run=PipelineRun(
hass,
context=context,
pipeline=pipeline,
start_stage=PipelineStage.STT,
end_stage=PipelineStage.TTS,
event_callback=event_callback,
tts_audio_output=tts_audio_output,
),
)
await pipeline_input.validate()
await pipeline_input.execute()

View File

@@ -0,0 +1,2 @@
"""Constants for the Assist pipeline integration."""
DOMAIN = "assist_pipeline"

View File

@@ -0,0 +1,30 @@
"""Assist pipeline errors."""
from homeassistant.exceptions import HomeAssistantError
class PipelineError(HomeAssistantError):
"""Base class for pipeline errors."""
def __init__(self, code: str, message: str) -> None:
"""Set error message."""
self.code = code
self.message = message
super().__init__(f"Pipeline error code={code}, message={message}")
class PipelineNotFound(PipelineError):
"""Unspecified pipeline picked."""
class SpeechToTextError(PipelineError):
"""Error in speech to text portion of pipeline."""
class IntentRecognitionError(PipelineError):
"""Error in intent recognition portion of pipeline."""
class TextToSpeechError(PipelineError):
"""Error in text to speech portion of pipeline."""

View File

@@ -0,0 +1,10 @@
{
"domain": "assist_pipeline",
"name": "Assist pipeline",
"codeowners": ["@balloob", "@synesthesiam"],
"dependencies": ["conversation", "stt", "tts"],
"documentation": "https://www.home-assistant.io/integrations/assist_pipeline",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["webrtcvad==2.0.10"]
}

View File

@@ -0,0 +1,972 @@
"""Classes for voice assistant pipelines."""
from __future__ import annotations
import asyncio
from collections.abc import AsyncIterable, Callable, Iterable
from dataclasses import asdict, dataclass, field
import logging
from typing import Any
import voluptuous as vol
from homeassistant.backports.enum import StrEnum
from homeassistant.components import conversation, media_source, stt, tts, websocket_api
from homeassistant.components.tts.media_source import (
generate_media_source_id as tts_generate_media_source_id,
)
from homeassistant.core import Context, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.collection import (
CollectionError,
ItemNotFound,
SerializedStorageCollection,
StorageCollection,
StorageCollectionWebsocket,
)
from homeassistant.helpers.storage import Store
from homeassistant.util import (
dt as dt_util,
language as language_util,
ulid as ulid_util,
)
from homeassistant.util.limited_size_dict import LimitedSizeDict
from .const import DOMAIN
from .error import (
IntentRecognitionError,
PipelineError,
SpeechToTextError,
TextToSpeechError,
)
_LOGGER = logging.getLogger(__name__)
STORAGE_KEY = f"{DOMAIN}.pipelines"
STORAGE_VERSION = 1
ENGINE_LANGUAGE_PAIRS = (
("stt_engine", "stt_language"),
("tts_engine", "tts_language"),
)
def validate_language(data: dict[str, Any]) -> Any:
"""Validate language settings."""
for engine, language in ENGINE_LANGUAGE_PAIRS:
if data[engine] is not None and data[language] is None:
raise vol.Invalid(f"Need language {language} for {engine} {data[engine]}")
return data
PIPELINE_FIELDS = {
vol.Required("conversation_engine"): str,
vol.Required("conversation_language"): str,
vol.Required("language"): str,
vol.Required("name"): str,
vol.Required("stt_engine"): vol.Any(str, None),
vol.Required("stt_language"): vol.Any(str, None),
vol.Required("tts_engine"): vol.Any(str, None),
vol.Required("tts_language"): vol.Any(str, None),
vol.Required("tts_voice"): vol.Any(str, None),
}
STORED_PIPELINE_RUNS = 10
SAVE_DELAY = 10
async def _async_resolve_default_pipeline_settings(
hass: HomeAssistant,
stt_engine_id: str | None,
tts_engine_id: str | None,
) -> dict[str, str | None]:
"""Resolve settings for a default pipeline.
The default pipeline will use the homeassistant conversation agent and the
default stt / tts engines if none are specified.
"""
conversation_language = "en"
pipeline_language = "en"
pipeline_name = "Home Assistant"
stt_engine = None
stt_language = None
tts_engine = None
tts_language = None
tts_voice = None
# Find a matching language supported by the Home Assistant conversation agent
conversation_languages = language_util.matches(
hass.config.language,
await conversation.async_get_conversation_languages(
hass, conversation.HOME_ASSISTANT_AGENT
),
country=hass.config.country,
)
if conversation_languages:
pipeline_language = hass.config.language
conversation_language = conversation_languages[0]
if stt_engine_id is None:
stt_engine_id = stt.async_default_engine(hass)
if stt_engine_id is not None:
stt_engine = stt.async_get_speech_to_text_engine(hass, stt_engine_id)
if stt_engine is None:
stt_engine_id = None
if stt_engine:
stt_languages = language_util.matches(
pipeline_language,
stt_engine.supported_languages,
country=hass.config.country,
)
if stt_languages:
stt_language = stt_languages[0]
else:
_LOGGER.debug(
"Speech to text engine '%s' does not support language '%s'",
stt_engine_id,
pipeline_language,
)
stt_engine_id = None
if tts_engine_id is None:
tts_engine_id = tts.async_default_engine(hass)
if tts_engine_id is not None:
tts_engine = tts.get_engine_instance(hass, tts_engine_id)
if tts_engine is None:
tts_engine_id = None
if tts_engine:
tts_languages = language_util.matches(
pipeline_language,
tts_engine.supported_languages,
country=hass.config.country,
)
if tts_languages:
tts_language = tts_languages[0]
tts_voices = tts_engine.async_get_supported_voices(tts_language)
if tts_voices:
tts_voice = tts_voices[0].voice_id
else:
_LOGGER.debug(
"Text to speech engine '%s' does not support language '%s'",
tts_engine_id,
pipeline_language,
)
tts_engine_id = None
if stt_engine_id == "cloud" and tts_engine_id == "cloud":
pipeline_name = "Home Assistant Cloud"
return {
"conversation_engine": conversation.HOME_ASSISTANT_AGENT,
"conversation_language": conversation_language,
"language": hass.config.language,
"name": pipeline_name,
"stt_engine": stt_engine_id,
"stt_language": stt_language,
"tts_engine": tts_engine_id,
"tts_language": tts_language,
"tts_voice": tts_voice,
}
async def _async_create_default_pipeline(
hass: HomeAssistant, pipeline_store: PipelineStorageCollection
) -> Pipeline:
"""Create a default pipeline.
The default pipeline will use the homeassistant conversation agent and the
default stt / tts engines.
"""
pipeline_settings = await _async_resolve_default_pipeline_settings(hass, None, None)
return await pipeline_store.async_create_item(pipeline_settings)
async def async_create_default_pipeline(
hass: HomeAssistant, stt_engine_id: str, tts_engine_id: str
) -> Pipeline | None:
"""Create a pipeline with default settings.
The default pipeline will use the homeassistant conversation agent and the
specified stt / tts engines.
"""
pipeline_data: PipelineData = hass.data[DOMAIN]
pipeline_store = pipeline_data.pipeline_store
pipeline_settings = await _async_resolve_default_pipeline_settings(
hass, stt_engine_id, tts_engine_id
)
if (
pipeline_settings["stt_engine"] != stt_engine_id
or pipeline_settings["tts_engine"] != tts_engine_id
):
return None
return await pipeline_store.async_create_item(pipeline_settings)
@callback
def async_get_pipeline(
hass: HomeAssistant, pipeline_id: str | None = None
) -> Pipeline | None:
"""Get a pipeline by id or the preferred pipeline."""
pipeline_data: PipelineData = hass.data[DOMAIN]
if pipeline_id is None:
# A pipeline was not specified, use the preferred one
pipeline_id = pipeline_data.pipeline_store.async_get_preferred_item()
return pipeline_data.pipeline_store.data.get(pipeline_id)
@callback
def async_get_pipelines(hass: HomeAssistant) -> Iterable[Pipeline]:
"""Get all pipelines."""
pipeline_data: PipelineData = hass.data[DOMAIN]
return pipeline_data.pipeline_store.data.values()
class PipelineEventType(StrEnum):
"""Event types emitted during a pipeline run."""
RUN_START = "run-start"
RUN_END = "run-end"
STT_START = "stt-start"
STT_END = "stt-end"
INTENT_START = "intent-start"
INTENT_END = "intent-end"
TTS_START = "tts-start"
TTS_END = "tts-end"
ERROR = "error"
@dataclass(frozen=True)
class PipelineEvent:
"""Events emitted during a pipeline run."""
type: PipelineEventType
data: dict[str, Any] | None = None
timestamp: str = field(default_factory=lambda: dt_util.utcnow().isoformat())
PipelineEventCallback = Callable[[PipelineEvent], None]
@dataclass(frozen=True)
class Pipeline:
"""A voice assistant pipeline."""
conversation_engine: str
conversation_language: str
language: str
name: str
stt_engine: str | None
stt_language: str | None
tts_engine: str | None
tts_language: str | None
tts_voice: str | None
id: str = field(default_factory=ulid_util.ulid)
def to_json(self) -> dict[str, Any]:
"""Return a JSON serializable representation for storage."""
return {
"conversation_engine": self.conversation_engine,
"conversation_language": self.conversation_language,
"id": self.id,
"language": self.language,
"name": self.name,
"stt_engine": self.stt_engine,
"stt_language": self.stt_language,
"tts_engine": self.tts_engine,
"tts_language": self.tts_language,
"tts_voice": self.tts_voice,
}
class PipelineStage(StrEnum):
"""Stages of a pipeline."""
STT = "stt"
INTENT = "intent"
TTS = "tts"
PIPELINE_STAGE_ORDER = [
PipelineStage.STT,
PipelineStage.INTENT,
PipelineStage.TTS,
]
class PipelineRunValidationError(Exception):
"""Error when a pipeline run is not valid."""
class InvalidPipelineStagesError(PipelineRunValidationError):
"""Error when given an invalid combination of start/end stages."""
def __init__(
self,
start_stage: PipelineStage,
end_stage: PipelineStage,
) -> None:
"""Set error message."""
super().__init__(
f"Invalid stage combination: start={start_stage}, end={end_stage}"
)
@dataclass
class PipelineRun:
"""Running context for a pipeline."""
hass: HomeAssistant
context: Context
pipeline: Pipeline
start_stage: PipelineStage
end_stage: PipelineStage
event_callback: PipelineEventCallback
language: str = None # type: ignore[assignment]
runner_data: Any | None = None
stt_provider: stt.SpeechToTextEntity | stt.Provider | None = None
intent_agent: str | None = None
tts_engine: str | None = None
tts_audio_output: str | None = None
id: str = field(default_factory=ulid_util.ulid)
tts_options: dict | None = field(init=False, default=None)
def __post_init__(self) -> None:
"""Set language for pipeline."""
self.language = self.pipeline.language or self.hass.config.language
# stt -> intent -> tts
if PIPELINE_STAGE_ORDER.index(self.end_stage) < PIPELINE_STAGE_ORDER.index(
self.start_stage
):
raise InvalidPipelineStagesError(self.start_stage, self.end_stage)
pipeline_data: PipelineData = self.hass.data[DOMAIN]
if self.pipeline.id not in pipeline_data.pipeline_runs:
pipeline_data.pipeline_runs[self.pipeline.id] = LimitedSizeDict(
size_limit=STORED_PIPELINE_RUNS
)
pipeline_data.pipeline_runs[self.pipeline.id][self.id] = PipelineRunDebug()
@callback
def process_event(self, event: PipelineEvent) -> None:
"""Log an event and call listener."""
self.event_callback(event)
pipeline_data: PipelineData = self.hass.data[DOMAIN]
if self.id not in pipeline_data.pipeline_runs[self.pipeline.id]:
# This run has been evicted from the logged pipeline runs already
return
pipeline_data.pipeline_runs[self.pipeline.id][self.id].events.append(event)
def start(self) -> None:
"""Emit run start event."""
data = {
"pipeline": self.pipeline.name,
"language": self.language,
}
if self.runner_data is not None:
data["runner_data"] = self.runner_data
self.process_event(PipelineEvent(PipelineEventType.RUN_START, data))
def end(self) -> None:
"""Emit run end event."""
self.process_event(
PipelineEvent(
PipelineEventType.RUN_END,
)
)
async def prepare_speech_to_text(self, metadata: stt.SpeechMetadata) -> None:
"""Prepare speech to text."""
stt_provider: stt.SpeechToTextEntity | stt.Provider | None = None
# pipeline.stt_engine can't be None or this function is not called
stt_provider = stt.async_get_speech_to_text_engine(
self.hass,
self.pipeline.stt_engine, # type: ignore[arg-type]
)
if stt_provider is None:
engine = self.pipeline.stt_engine
raise SpeechToTextError(
code="stt-provider-missing",
message=f"No speech to text provider for: {engine}",
)
metadata.language = self.pipeline.stt_language or self.language
if not stt_provider.check_metadata(metadata):
raise SpeechToTextError(
code="stt-provider-unsupported-metadata",
message=(
f"Provider {stt_provider.name} does not support input speech "
f"to text metadata {metadata}"
),
)
self.stt_provider = stt_provider
async def speech_to_text(
self,
metadata: stt.SpeechMetadata,
stream: AsyncIterable[bytes],
) -> str:
"""Run speech to text portion of pipeline. Returns the spoken text."""
if self.stt_provider is None:
raise RuntimeError("Speech to text was not prepared")
if isinstance(self.stt_provider, stt.Provider):
engine = self.stt_provider.name
else:
engine = self.stt_provider.entity_id
self.process_event(
PipelineEvent(
PipelineEventType.STT_START,
{
"engine": engine,
"metadata": asdict(metadata),
},
)
)
try:
# Transcribe audio stream
result = await self.stt_provider.async_process_audio_stream(
metadata, stream
)
except Exception as src_error:
_LOGGER.exception("Unexpected error during speech to text")
raise SpeechToTextError(
code="stt-stream-failed",
message="Unexpected error during speech to text",
) from src_error
_LOGGER.debug("speech-to-text result %s", result)
if result.result != stt.SpeechResultState.SUCCESS:
raise SpeechToTextError(
code="stt-stream-failed",
message="Speech to text failed",
)
if not result.text:
raise SpeechToTextError(
code="stt-no-text-recognized", message="No text recognized"
)
self.process_event(
PipelineEvent(
PipelineEventType.STT_END,
{
"stt_output": {
"text": result.text,
}
},
)
)
return result.text
async def prepare_recognize_intent(self) -> None:
"""Prepare recognizing an intent."""
agent_info = conversation.async_get_agent_info(
self.hass,
# If no conversation engine is set, use the Home Assistant agent
# (the conversation integration default is currently the last one set)
self.pipeline.conversation_engine or conversation.HOME_ASSISTANT_AGENT,
)
if agent_info is None:
engine = self.pipeline.conversation_engine or "default"
raise IntentRecognitionError(
code="intent-not-supported",
message=f"Intent recognition engine {engine} is not found",
)
self.intent_agent = agent_info.id
async def recognize_intent(
self, intent_input: str, conversation_id: str | None
) -> str:
"""Run intent recognition portion of pipeline. Returns text to speak."""
if self.intent_agent is None:
raise RuntimeError("Recognize intent was not prepared")
self.process_event(
PipelineEvent(
PipelineEventType.INTENT_START,
{
"engine": self.intent_agent,
"language": self.pipeline.conversation_language,
"intent_input": intent_input,
},
)
)
try:
conversation_result = await conversation.async_converse(
hass=self.hass,
text=intent_input,
conversation_id=conversation_id,
context=self.context,
language=self.pipeline.conversation_language,
agent_id=self.intent_agent,
)
except Exception as src_error:
_LOGGER.exception("Unexpected error during intent recognition")
raise IntentRecognitionError(
code="intent-failed",
message="Unexpected error during intent recognition",
) from src_error
_LOGGER.debug("conversation result %s", conversation_result)
self.process_event(
PipelineEvent(
PipelineEventType.INTENT_END,
{"intent_output": conversation_result.as_dict()},
)
)
speech: str = conversation_result.response.speech.get("plain", {}).get(
"speech", ""
)
return speech
async def prepare_text_to_speech(self) -> None:
"""Prepare text to speech."""
engine = self.pipeline.tts_engine
tts_options = {}
if self.pipeline.tts_voice is not None:
tts_options[tts.ATTR_VOICE] = self.pipeline.tts_voice
if self.tts_audio_output is not None:
tts_options[tts.ATTR_AUDIO_OUTPUT] = self.tts_audio_output
try:
# pipeline.tts_engine can't be None or this function is not called
if not await tts.async_support_options(
self.hass,
engine, # type: ignore[arg-type]
self.pipeline.tts_language,
tts_options,
):
raise TextToSpeechError(
code="tts-not-supported",
message=(
f"Text to speech engine {engine} "
f"does not support language {self.pipeline.tts_language} or options {tts_options}"
),
)
except HomeAssistantError as err:
raise TextToSpeechError(
code="tts-not-supported",
message=f"Text to speech engine '{engine}' not found",
) from err
self.tts_engine = engine
self.tts_options = tts_options
async def text_to_speech(self, tts_input: str) -> str:
"""Run text to speech portion of pipeline. Returns URL of TTS audio."""
if self.tts_engine is None:
raise RuntimeError("Text to speech was not prepared")
self.process_event(
PipelineEvent(
PipelineEventType.TTS_START,
{
"engine": self.tts_engine,
"language": self.pipeline.tts_language,
"voice": self.pipeline.tts_voice,
"tts_input": tts_input,
},
)
)
try:
# Synthesize audio and get URL
tts_media_id = tts_generate_media_source_id(
self.hass,
tts_input,
engine=self.tts_engine,
language=self.pipeline.tts_language,
options=self.tts_options,
)
tts_media = await media_source.async_resolve_media(
self.hass,
tts_media_id,
None,
)
except Exception as src_error:
_LOGGER.exception("Unexpected error during text to speech")
raise TextToSpeechError(
code="tts-failed",
message="Unexpected error during text to speech",
) from src_error
_LOGGER.debug("TTS result %s", tts_media)
self.process_event(
PipelineEvent(
PipelineEventType.TTS_END,
{
"tts_output": {
"media_id": tts_media_id,
**asdict(tts_media),
}
},
)
)
return tts_media.url
@dataclass
class PipelineInput:
"""Input to a pipeline run."""
run: PipelineRun
stt_metadata: stt.SpeechMetadata | None = None
"""Metadata of stt input audio. Required when start_stage = stt."""
stt_stream: AsyncIterable[bytes] | None = None
"""Input audio for stt. Required when start_stage = stt."""
intent_input: str | None = None
"""Input for conversation agent. Required when start_stage = intent."""
tts_input: str | None = None
"""Input for text to speech. Required when start_stage = tts."""
conversation_id: str | None = None
async def execute(self) -> None:
"""Run pipeline."""
self.run.start()
current_stage = self.run.start_stage
try:
# Speech to text
intent_input = self.intent_input
if current_stage == PipelineStage.STT:
assert self.stt_metadata is not None
assert self.stt_stream is not None
intent_input = await self.run.speech_to_text(
self.stt_metadata,
self.stt_stream,
)
current_stage = PipelineStage.INTENT
if self.run.end_stage != PipelineStage.STT:
tts_input = self.tts_input
if current_stage == PipelineStage.INTENT:
assert intent_input is not None
tts_input = await self.run.recognize_intent(
intent_input, self.conversation_id
)
current_stage = PipelineStage.TTS
if self.run.end_stage != PipelineStage.INTENT:
if current_stage == PipelineStage.TTS:
assert tts_input is not None
await self.run.text_to_speech(tts_input)
except PipelineError as err:
self.run.process_event(
PipelineEvent(
PipelineEventType.ERROR,
{"code": err.code, "message": err.message},
)
)
return
self.run.end()
async def validate(self) -> None:
"""Validate pipeline input against start stage."""
if self.run.start_stage == PipelineStage.STT:
if self.run.pipeline.stt_engine is None:
raise PipelineRunValidationError(
"the pipeline does not support speech to text"
)
if self.stt_metadata is None:
raise PipelineRunValidationError(
"stt_metadata is required for speech to text"
)
if self.stt_stream is None:
raise PipelineRunValidationError(
"stt_stream is required for speech to text"
)
elif self.run.start_stage == PipelineStage.INTENT:
if self.intent_input is None:
raise PipelineRunValidationError(
"intent_input is required for intent recognition"
)
elif self.run.start_stage == PipelineStage.TTS:
if self.tts_input is None:
raise PipelineRunValidationError(
"tts_input is required for text to speech"
)
if self.run.end_stage == PipelineStage.TTS:
if self.run.pipeline.tts_engine is None:
raise PipelineRunValidationError(
"the pipeline does not support text to speech"
)
start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage)
prepare_tasks = []
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.STT):
# self.stt_metadata can't be None or we'd raise above
prepare_tasks.append(self.run.prepare_speech_to_text(self.stt_metadata)) # type: ignore[arg-type]
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT):
prepare_tasks.append(self.run.prepare_recognize_intent())
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS):
prepare_tasks.append(self.run.prepare_text_to_speech())
if prepare_tasks:
await asyncio.gather(*prepare_tasks)
class PipelinePreferred(CollectionError):
"""Raised when attempting to delete the preferred pipelen."""
def __init__(self, item_id: str) -> None:
"""Initialize pipeline preferred error."""
super().__init__(f"Item {item_id} preferred.")
self.item_id = item_id
class SerializedPipelineStorageCollection(SerializedStorageCollection):
"""Serialized pipeline storage collection."""
preferred_item: str
class PipelineStorageCollection(
StorageCollection[Pipeline, SerializedPipelineStorageCollection]
):
"""Pipeline storage collection."""
_preferred_item: str
async def _async_load_data(self) -> SerializedPipelineStorageCollection | None:
"""Load the data."""
if not (data := await super()._async_load_data()):
pipeline = await _async_create_default_pipeline(self.hass, self)
self._preferred_item = pipeline.id
return data
self._preferred_item = data["preferred_item"]
return data
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
validated_data: dict = validate_language(data)
return validated_data
@callback
def _get_suggested_id(self, info: dict) -> str:
"""Suggest an ID based on the config."""
return ulid_util.ulid()
async def _update_data(self, item: Pipeline, update_data: dict) -> Pipeline:
"""Return a new updated item."""
update_data = validate_language(update_data)
return Pipeline(id=item.id, **update_data)
def _create_item(self, item_id: str, data: dict) -> Pipeline:
"""Create an item from validated config."""
return Pipeline(id=item_id, **data)
def _deserialize_item(self, data: dict) -> Pipeline:
"""Create an item from its serialized representation."""
return Pipeline(**data)
def _serialize_item(self, item_id: str, item: Pipeline) -> dict:
"""Return the serialized representation of an item for storing."""
return item.to_json()
async def async_delete_item(self, item_id: str) -> None:
"""Delete item."""
if self._preferred_item == item_id:
raise PipelinePreferred(item_id)
await super().async_delete_item(item_id)
@callback
def async_get_preferred_item(self) -> str:
"""Get the id of the preferred item."""
return self._preferred_item
@callback
def async_set_preferred_item(self, item_id: str) -> None:
"""Set the preferred pipeline."""
if item_id not in self.data:
raise ItemNotFound(item_id)
self._preferred_item = item_id
self._async_schedule_save()
@callback
def _data_to_save(self) -> SerializedPipelineStorageCollection:
"""Return JSON-compatible date for storing to file."""
base_data = super()._base_data_to_save()
return {
"items": base_data["items"],
"preferred_item": self._preferred_item,
}
class PipelineStorageCollectionWebsocket(
StorageCollectionWebsocket[PipelineStorageCollection]
):
"""Class to expose storage collection management over websocket."""
@callback
def async_setup(
self,
hass: HomeAssistant,
*,
create_list: bool = True,
create_create: bool = True,
) -> None:
"""Set up the websocket commands."""
super().async_setup(hass, create_list=create_list, create_create=create_create)
websocket_api.async_register_command(
hass,
f"{self.api_prefix}/get",
self.ws_get_item,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): f"{self.api_prefix}/get",
vol.Optional(self.item_id_key): str,
}
),
)
websocket_api.async_register_command(
hass,
f"{self.api_prefix}/set_preferred",
websocket_api.require_admin(
websocket_api.async_response(self.ws_set_preferred_item)
),
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): f"{self.api_prefix}/set_preferred",
vol.Required(self.item_id_key): str,
}
),
)
async def ws_delete_item(
self, hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Delete an item."""
try:
await super().ws_delete_item(hass, connection, msg)
except PipelinePreferred as exc:
connection.send_error(
msg["id"], websocket_api.const.ERR_NOT_ALLOWED, str(exc)
)
@callback
def ws_get_item(
self, hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Get an item."""
item_id = msg.get(self.item_id_key)
if item_id is None:
item_id = self.storage_collection.async_get_preferred_item()
if item_id not in self.storage_collection.data:
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_FOUND,
f"Unable to find {self.item_id_key} {item_id}",
)
return
connection.send_result(msg["id"], self.storage_collection.data[item_id])
@callback
def ws_list_item(
self, hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""List items."""
connection.send_result(
msg["id"],
{
"pipelines": self.storage_collection.async_items(),
"preferred_pipeline": self.storage_collection.async_get_preferred_item(),
},
)
async def ws_set_preferred_item(
self,
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Set the preferred item."""
try:
self.storage_collection.async_set_preferred_item(msg[self.item_id_key])
except ItemNotFound:
connection.send_error(
msg["id"], websocket_api.const.ERR_NOT_FOUND, "unknown item"
)
return
connection.send_result(msg["id"])
@dataclass
class PipelineData:
"""Store and debug data stored in hass.data."""
pipeline_runs: dict[str, LimitedSizeDict[str, PipelineRunDebug]]
pipeline_store: PipelineStorageCollection
@dataclass
class PipelineRunDebug:
"""Debug data for a pipelinerun."""
events: list[PipelineEvent] = field(default_factory=list, init=False)
timestamp: str = field(
default_factory=lambda: dt_util.utcnow().isoformat(),
init=False,
)
async def async_setup_pipeline_store(hass: HomeAssistant) -> None:
"""Set up the pipeline storage collection."""
pipeline_store = PipelineStorageCollection(
Store(hass, STORAGE_VERSION, STORAGE_KEY)
)
await pipeline_store.async_load()
PipelineStorageCollectionWebsocket(
pipeline_store,
f"{DOMAIN}/pipeline",
"pipeline",
PIPELINE_FIELDS,
PIPELINE_FIELDS,
).async_setup(hass)
hass.data[DOMAIN] = PipelineData({}, pipeline_store)

View File

@@ -0,0 +1,95 @@
"""Select entities for a pipeline."""
from __future__ import annotations
from collections.abc import Iterable
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import collection, entity_registry as er, restore_state
from .const import DOMAIN
from .pipeline import PipelineStorageCollection
OPTION_PREFERRED = "preferred"
@callback
def get_chosen_pipeline(
hass: HomeAssistant, domain: str, unique_id_prefix: str
) -> str | None:
"""Get the chosen pipeline for a domain."""
ent_reg = er.async_get(hass)
pipeline_entity_id = ent_reg.async_get_entity_id(
Platform.SELECT, domain, f"{unique_id_prefix}-pipeline"
)
if pipeline_entity_id is None:
return None
state = hass.states.get(pipeline_entity_id)
if state is None or state.state == OPTION_PREFERRED:
return None
pipeline_store: PipelineStorageCollection = hass.data[DOMAIN].pipeline_store
return next(
(item.id for item in pipeline_store.async_items() if item.name == state.state),
None,
)
class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
"""Entity to represent a pipeline selector."""
entity_description = SelectEntityDescription(
key="pipeline",
translation_key="pipeline",
entity_category=EntityCategory.CONFIG,
)
_attr_should_poll = False
_attr_current_option = OPTION_PREFERRED
_attr_options = [OPTION_PREFERRED]
def __init__(self, hass: HomeAssistant, unique_id_prefix: str) -> None:
"""Initialize a pipeline selector."""
self._attr_unique_id = f"{unique_id_prefix}-pipeline"
self.hass = hass
self._update_options()
async def async_added_to_hass(self) -> None:
"""When entity is added to Home Assistant."""
await super().async_added_to_hass()
pipeline_store: PipelineStorageCollection = self.hass.data[
DOMAIN
].pipeline_store
pipeline_store.async_add_change_set_listener(self._pipelines_updated)
state = await self.async_get_last_state()
if state is not None and state.state in self.options:
self._attr_current_option = state.state
async def async_select_option(self, option: str) -> None:
"""Select an option."""
self._attr_current_option = option
self.async_write_ha_state()
async def _pipelines_updated(
self, change_sets: Iterable[collection.CollectionChangeSet]
) -> None:
"""Handle pipeline update."""
self._update_options()
self.async_write_ha_state()
@callback
def _update_options(self) -> None:
"""Handle pipeline update."""
pipeline_store: PipelineStorageCollection = self.hass.data[
DOMAIN
].pipeline_store
options = [OPTION_PREFERRED]
options.extend(sorted(item.name for item in pipeline_store.async_items()))
self._attr_options = options
if self._attr_current_option not in options:
self._attr_current_option = OPTION_PREFERRED

View File

@@ -0,0 +1,12 @@
{
"entity": {
"select": {
"pipeline": {
"name": "Assist Pipeline",
"state": {
"preferred": "Preferred"
}
}
}
}
}

View File

@@ -0,0 +1,128 @@
"""Voice activity detection."""
from dataclasses import dataclass, field
import webrtcvad
_SAMPLE_RATE = 16000
@dataclass
class VoiceCommandSegmenter:
"""Segments an audio stream into voice commands using webrtcvad."""
vad_mode: int = 3
"""Aggressiveness in filtering out non-speech. 3 is the most aggressive."""
vad_frames: int = 480 # 30 ms
"""Must be 10, 20, or 30 ms at 16Khz."""
speech_seconds: float = 0.3
"""Seconds of speech before voice command has started."""
silence_seconds: float = 0.5
"""Seconds of silence after voice command has ended."""
timeout_seconds: float = 15.0
"""Maximum number of seconds before stopping with timeout=True."""
reset_seconds: float = 1.0
"""Seconds before reset start/stop time counters."""
in_command: bool = False
"""True if inside voice command."""
_speech_seconds_left: float = 0.0
"""Seconds left before considering voice command as started."""
_silence_seconds_left: float = 0.0
"""Seconds left before considering voice command as stopped."""
_timeout_seconds_left: float = 0.0
"""Seconds left before considering voice command timed out."""
_reset_seconds_left: float = 0.0
"""Seconds left before resetting start/stop time counters."""
_vad: webrtcvad.Vad = None
_audio_buffer: bytes = field(default_factory=bytes)
_bytes_per_chunk: int = 480 * 2 # 16-bit samples
_seconds_per_chunk: float = 0.03 # 30 ms
def __post_init__(self) -> None:
"""Initialize VAD."""
self._vad = webrtcvad.Vad(self.vad_mode)
self._bytes_per_chunk = self.vad_frames * 2
self._seconds_per_chunk = self.vad_frames / _SAMPLE_RATE
self.reset()
def reset(self) -> None:
"""Reset all counters and state."""
self._audio_buffer = b""
self._speech_seconds_left = self.speech_seconds
self._silence_seconds_left = self.silence_seconds
self._timeout_seconds_left = self.timeout_seconds
self._reset_seconds_left = self.reset_seconds
self.in_command = False
def process(self, samples: bytes) -> bool:
"""Process a 16-bit 16Khz mono audio samples.
Returns False when command is done.
"""
self._audio_buffer += samples
# Process in 10, 20, or 30 ms chunks.
num_chunks = len(self._audio_buffer) // self._bytes_per_chunk
for chunk_idx in range(num_chunks):
chunk_offset = chunk_idx * self._bytes_per_chunk
chunk = self._audio_buffer[
chunk_offset : chunk_offset + self._bytes_per_chunk
]
if not self._process_chunk(chunk):
self.reset()
return False
if num_chunks > 0:
# Remove from buffer
self._audio_buffer = self._audio_buffer[
num_chunks * self._bytes_per_chunk :
]
return True
def _process_chunk(self, chunk: bytes) -> bool:
"""Process a single chunk of 16-bit 16Khz mono audio.
Returns False when command is done.
"""
is_speech = self._vad.is_speech(chunk, _SAMPLE_RATE)
self._timeout_seconds_left -= self._seconds_per_chunk
if self._timeout_seconds_left <= 0:
return False
if not self.in_command:
if is_speech:
self._reset_seconds_left = self.reset_seconds
self._speech_seconds_left -= self._seconds_per_chunk
if self._speech_seconds_left <= 0:
# Inside voice command
self.in_command = True
else:
# Reset if enough silence
self._reset_seconds_left -= self._seconds_per_chunk
if self._reset_seconds_left <= 0:
self._speech_seconds_left = self.speech_seconds
else:
if not is_speech:
self._reset_seconds_left = self.reset_seconds
self._silence_seconds_left -= self._seconds_per_chunk
if self._silence_seconds_left <= 0:
return False
else:
# Reset if enough speech
self._reset_seconds_left -= self._seconds_per_chunk
if self._reset_seconds_left <= 0:
self._silence_seconds_left = self.silence_seconds
return True

View File

@@ -0,0 +1,333 @@
"""Assist pipeline Websocket API."""
import asyncio
# Suppressing disable=deprecated-module is needed for Python 3.11
import audioop # pylint: disable=deprecated-module
from collections.abc import AsyncGenerator, Callable
import logging
from typing import Any
import async_timeout
import voluptuous as vol
from homeassistant.components import conversation, stt, tts, websocket_api
from homeassistant.const import MATCH_ALL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.util import language as language_util
from .const import DOMAIN
from .pipeline import (
PipelineData,
PipelineError,
PipelineEvent,
PipelineEventType,
PipelineInput,
PipelineRun,
PipelineStage,
async_get_pipeline,
)
from .vad import VoiceCommandSegmenter
DEFAULT_TIMEOUT = 30
_LOGGER = logging.getLogger(__name__)
@callback
def async_register_websocket_api(hass: HomeAssistant) -> None:
"""Register the websocket API."""
websocket_api.async_register_command(hass, websocket_run)
websocket_api.async_register_command(hass, websocket_list_languages)
websocket_api.async_register_command(hass, websocket_list_runs)
websocket_api.async_register_command(hass, websocket_get_run)
@websocket_api.websocket_command(
vol.All(
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): "assist_pipeline/run",
# pylint: disable-next=unnecessary-lambda
vol.Required("start_stage"): lambda val: PipelineStage(val),
# pylint: disable-next=unnecessary-lambda
vol.Required("end_stage"): lambda val: PipelineStage(val),
vol.Optional("input"): dict,
vol.Optional("pipeline"): str,
vol.Optional("conversation_id"): vol.Any(str, None),
vol.Optional("timeout"): vol.Any(float, int),
},
),
cv.key_value_schemas(
"start_stage",
{
PipelineStage.STT: vol.Schema(
{vol.Required("input"): {vol.Required("sample_rate"): int}},
extra=vol.ALLOW_EXTRA,
),
PipelineStage.INTENT: vol.Schema(
{vol.Required("input"): {"text": str}},
extra=vol.ALLOW_EXTRA,
),
PipelineStage.TTS: vol.Schema(
{vol.Required("input"): {"text": str}},
extra=vol.ALLOW_EXTRA,
),
},
),
),
)
@websocket_api.async_response
async def websocket_run(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Run a pipeline."""
pipeline_id = msg.get("pipeline")
pipeline = async_get_pipeline(hass, pipeline_id=pipeline_id)
if pipeline is None:
connection.send_error(
msg["id"],
"pipeline-not-found",
f"Pipeline not found: id={pipeline_id}",
)
return
timeout = msg.get("timeout", DEFAULT_TIMEOUT)
start_stage = PipelineStage(msg["start_stage"])
end_stage = PipelineStage(msg["end_stage"])
handler_id: int | None = None
unregister_handler: Callable[[], None] | None = None
# Arguments to PipelineInput
input_args: dict[str, Any] = {
"conversation_id": msg.get("conversation_id"),
}
if start_stage == PipelineStage.STT:
# Audio pipeline that will receive audio as binary websocket messages
audio_queue: "asyncio.Queue[bytes]" = asyncio.Queue()
incoming_sample_rate = msg["input"]["sample_rate"]
async def stt_stream() -> AsyncGenerator[bytes, None]:
state = None
segmenter = VoiceCommandSegmenter()
# Yield until we receive an empty chunk
while chunk := await audio_queue.get():
chunk, state = audioop.ratecv(
chunk, 2, 1, incoming_sample_rate, 16000, state
)
if not segmenter.process(chunk):
# Voice command is finished
break
yield chunk
def handle_binary(
_hass: HomeAssistant,
_connection: websocket_api.ActiveConnection,
data: bytes,
) -> None:
# Forward to STT audio stream
audio_queue.put_nowait(data)
handler_id, unregister_handler = connection.async_register_binary_handler(
handle_binary
)
# Audio input must be raw PCM at 16Khz with 16-bit mono samples
input_args["stt_metadata"] = stt.SpeechMetadata(
language=pipeline.stt_language or pipeline.language,
format=stt.AudioFormats.WAV,
codec=stt.AudioCodecs.PCM,
bit_rate=stt.AudioBitRates.BITRATE_16,
sample_rate=stt.AudioSampleRates.SAMPLERATE_16000,
channel=stt.AudioChannels.CHANNEL_MONO,
)
input_args["stt_stream"] = stt_stream()
elif start_stage == PipelineStage.INTENT:
# Input to conversation agent
input_args["intent_input"] = msg["input"]["text"]
elif start_stage == PipelineStage.TTS:
# Input to text to speech system
input_args["tts_input"] = msg["input"]["text"]
input_args["run"] = PipelineRun(
hass,
context=connection.context(msg),
pipeline=pipeline,
start_stage=start_stage,
end_stage=end_stage,
event_callback=lambda event: connection.send_event(msg["id"], event),
runner_data={
"stt_binary_handler_id": handler_id,
"timeout": timeout,
},
)
pipeline_input = PipelineInput(**input_args)
try:
await pipeline_input.validate()
except PipelineError as error:
# Report more specific error when possible
connection.send_error(msg["id"], error.code, error.message)
return
# Confirm subscription
connection.send_result(msg["id"])
run_task = hass.async_create_task(pipeline_input.execute())
# Cancel pipeline if user unsubscribes
connection.subscriptions[msg["id"]] = run_task.cancel
try:
# Task contains a timeout
async with async_timeout.timeout(timeout):
await run_task
except asyncio.TimeoutError:
pipeline_input.run.process_event(
PipelineEvent(
PipelineEventType.ERROR,
{"code": "timeout", "message": "Timeout running pipeline"},
)
)
finally:
if unregister_handler is not None:
# Unregister binary handler
unregister_handler()
@callback
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "assist_pipeline/pipeline_debug/list",
vol.Required("pipeline_id"): str,
}
)
def websocket_list_runs(
hass: HomeAssistant,
connection: websocket_api.connection.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""List pipeline runs for which debug data is available."""
pipeline_data: PipelineData = hass.data[DOMAIN]
pipeline_id = msg["pipeline_id"]
if pipeline_id not in pipeline_data.pipeline_runs:
connection.send_result(msg["id"], {"pipeline_runs": []})
return
pipeline_runs = pipeline_data.pipeline_runs[pipeline_id]
connection.send_result(
msg["id"],
{
"pipeline_runs": [
{"pipeline_run_id": id, "timestamp": pipeline_run.timestamp}
for id, pipeline_run in pipeline_runs.items()
]
},
)
@callback
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "assist_pipeline/pipeline_debug/get",
vol.Required("pipeline_id"): str,
vol.Required("pipeline_run_id"): str,
}
)
def websocket_get_run(
hass: HomeAssistant,
connection: websocket_api.connection.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Get debug data for a pipeline run."""
pipeline_data: PipelineData = hass.data[DOMAIN]
pipeline_id = msg["pipeline_id"]
pipeline_run_id = msg["pipeline_run_id"]
if pipeline_id not in pipeline_data.pipeline_runs:
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_FOUND,
f"pipeline_id {pipeline_id} not found",
)
return
pipeline_runs = pipeline_data.pipeline_runs[pipeline_id]
if pipeline_run_id not in pipeline_runs:
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_FOUND,
f"pipeline_run_id {pipeline_run_id} not found",
)
return
connection.send_result(
msg["id"],
{"events": pipeline_runs[pipeline_run_id].events},
)
@callback
@websocket_api.websocket_command(
{
vol.Required("type"): "assist_pipeline/language/list",
}
)
@websocket_api.async_response
async def websocket_list_languages(
hass: HomeAssistant,
connection: websocket_api.connection.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""List languages which are supported by a complete pipeline.
This will return a list of languages which are supported by at least one stt, tts
and conversation engine respectively.
"""
conv_language_tags = await conversation.async_get_conversation_languages(hass)
stt_language_tags = stt.async_get_speech_to_text_languages(hass)
tts_language_tags = tts.async_get_text_to_speech_languages(hass)
pipeline_languages: set[str] | None = None
if conv_language_tags and conv_language_tags != MATCH_ALL:
languages = set()
for language_tag in conv_language_tags:
dialect = language_util.Dialect.parse(language_tag)
languages.add(dialect.language)
pipeline_languages = languages
if stt_language_tags:
languages = set()
for language_tag in stt_language_tags:
dialect = language_util.Dialect.parse(language_tag)
languages.add(dialect.language)
if pipeline_languages is not None:
pipeline_languages &= languages
else:
pipeline_languages = languages
if tts_language_tags:
languages = set()
for language_tag in tts_language_tags:
dialect = language_util.Dialect.parse(language_tag)
languages.add(dialect.language)
if pipeline_languages is not None:
pipeline_languages &= languages
else:
pipeline_languages = languages
connection.send_result(
msg["id"],
{"languages": pipeline_languages},
)

View File

@@ -11,7 +11,7 @@
"password": "[%key:common::config_flow::data::password%]",
"ssh_key": "Path to your SSH key file (instead of password)",
"protocol": "Communication protocol to use",
"port": "[%key:common::config_flow::data::port%] (leave empty for protocol default)",
"port": "Port (leave empty for protocol default)",
"mode": "[%key:common::config_flow::data::mode%]"
}
}

View File

@@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==1.2.7", "yalexs-ble==2.1.14"]
"requirements": ["yalexs==1.3.2", "yalexs-ble==2.1.16"]
}

View File

@@ -659,7 +659,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
)
@dataclass
@dataclass(slots=True)
class AutomationEntityConfig:
"""Container for prepared automation entity configuration."""

View File

@@ -51,7 +51,6 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
if config_entry.version != 3:
# Home Assistant 2023.2
config_entry.version = 3
hass.config_entries.async_update_entry(config_entry)
_LOGGER.info("Migration to version %s successful", config_entry.version)

View File

@@ -4,8 +4,13 @@ from __future__ import annotations
import json
import logging
# pylint: disable-next=import-error, no-name-in-module
from azure.servicebus import ServiceBusMessage
# pylint: disable-next=import-error, no-name-in-module
from azure.servicebus.aio import ServiceBusClient, ServiceBusSender
# pylint: disable-next=import-error, no-name-in-module
from azure.servicebus.exceptions import (
MessagingEntityNotFoundError,
ServiceBusConnectionError,

View File

@@ -23,8 +23,10 @@ from homeassistant.util.json import json_loads_object
from .const import DOMAIN, EXCLUDE_FROM_BACKUP, LOGGER
BUF_SIZE = 2**20 * 4 # 4MB
@dataclass
@dataclass(slots=True)
class Backup:
"""Backup class."""
@@ -99,7 +101,7 @@ class BackupManager:
backups: dict[str, Backup] = {}
for backup_path in self.backup_dir.glob("*.tar"):
try:
with tarfile.open(backup_path, "r:") as backup_file:
with tarfile.open(backup_path, "r:", bufsize=BUF_SIZE) as backup_file:
if data_file := backup_file.extractfile("./backup.json"):
data = json_loads_object(data_file.read())
backup = Backup(
@@ -227,7 +229,7 @@ class BackupManager:
self.backup_dir.mkdir()
with TemporaryDirectory() as tmp_dir, SecureTarFile(
tar_file_path, "w", gzip=False
tar_file_path, "w", gzip=False, bufsize=BUF_SIZE
) as tar_file:
tmp_dir_path = Path(tmp_dir)
save_json(
@@ -237,6 +239,7 @@ class BackupManager:
with SecureTarFile(
tmp_dir_path.joinpath("./homeassistant.tar.gz").as_posix(),
"w",
bufsize=BUF_SIZE,
) as core_tar:
atomic_contents_add(
tar_file=core_tar,

View File

@@ -7,5 +7,5 @@
"integration_type": "system",
"iot_class": "calculated",
"quality_scale": "internal",
"requirements": ["securetar==2022.2.0"]
"requirements": ["securetar==2023.3.0"]
}

View File

@@ -84,7 +84,7 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) -> FlowResult:
"""Handle zeroconf discovery."""
hass = self.hass
ipaddress = host_port(discovery_info.__dict__)
ipaddress = (discovery_info.host, discovery_info.port)
self.device_config["host"] = discovery_info.host
self.device_config["port"] = discovery_info.port

View File

@@ -31,7 +31,7 @@ from homeassistant.config_entries import (
ConfigEntry,
)
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, callback as hass_callback
from homeassistant.core import Event, HassJob, HomeAssistant, callback as hass_callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, discovery_flow
from homeassistant.helpers.debounce import Debouncer
@@ -198,10 +198,18 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
function=_async_rediscover_adapters,
)
async def _async_shutdown_debouncer(_: Event) -> None:
"""Shutdown debouncer."""
await discovery_debouncer.async_shutdown()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown_debouncer)
async def _async_call_debouncer(now: datetime.datetime) -> None:
"""Call the debouncer at a later time."""
await discovery_debouncer.async_call()
call_debouncer_job = HassJob(_async_call_debouncer, cancel_on_shutdown=True)
def _async_trigger_discovery() -> None:
# There are so many bluetooth adapter models that
# we check the bus whenever a usb device is plugged in
@@ -220,7 +228,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async_call_later(
hass,
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS + LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
_async_call_debouncer,
call_debouncer_job,
)
cancel = usb.async_register_scan_request_callback(hass, _async_trigger_discovery)

View File

@@ -169,3 +169,9 @@ class ActiveBluetoothDataUpdateCoordinator(
# possible after a device comes online or back in range, if a poll is due
if self.needs_poll(service_info):
self.hass.async_create_task(self._debounced_poll.async_call())
@callback
def _async_stop(self) -> None:
"""Cancel debouncer and stop the callbacks."""
self._debounced_poll.async_cancel()
super()._async_stop()

View File

@@ -158,3 +158,9 @@ class ActiveBluetoothProcessorCoordinator(
# possible after a device comes online or back in range, if a poll is due
if self.needs_poll(service_info):
self.hass.async_create_task(self._debounced_poll.async_call())
@callback
def _async_stop(self) -> None:
"""Cancel debouncer and stop the callbacks."""
self._debounced_poll.async_cancel()
super()._async_stop()

View File

@@ -39,7 +39,7 @@ MONOTONIC_TIME: Final = monotonic_time_coarse
_LOGGER = logging.getLogger(__name__)
@dataclass
@dataclass(slots=True)
class BluetoothScannerDevice:
"""Data for a bluetooth device from a given scanner."""
@@ -309,43 +309,28 @@ class BaseHaRemoteScanner(BaseHaScanner):
# merges the dicts on PropertiesChanged
prev_device = prev_discovery[0]
prev_advertisement = prev_discovery[1]
if (
local_name
and prev_device.name
and len(prev_device.name) > len(local_name)
):
local_name = prev_device.name
if service_uuids and service_uuids != prev_advertisement.service_uuids:
service_uuids = list(
set(service_uuids + prev_advertisement.service_uuids)
)
elif not service_uuids:
service_uuids = prev_advertisement.service_uuids
if service_data and service_data != prev_advertisement.service_data:
service_data = {**prev_advertisement.service_data, **service_data}
elif not service_data:
service_data = prev_advertisement.service_data
if (
manufacturer_data
and manufacturer_data != prev_advertisement.manufacturer_data
):
manufacturer_data = {
**prev_advertisement.manufacturer_data,
**manufacturer_data,
}
elif not manufacturer_data:
manufacturer_data = prev_advertisement.manufacturer_data
prev_service_uuids = prev_advertisement.service_uuids
prev_service_data = prev_advertisement.service_data
prev_manufacturer_data = prev_advertisement.manufacturer_data
prev_name = prev_device.name
advertisement_data = AdvertisementData(
local_name=None if local_name == "" else local_name,
manufacturer_data=manufacturer_data,
service_data=service_data,
service_uuids=service_uuids,
rssi=rssi,
tx_power=NO_RSSI_VALUE if tx_power is None else tx_power,
platform_data=(),
)
if prev_discovery:
if local_name and prev_name and len(prev_name) > len(local_name):
local_name = prev_name
if service_uuids and service_uuids != prev_service_uuids:
service_uuids = list(set(service_uuids + prev_service_uuids))
elif not service_uuids:
service_uuids = prev_service_uuids
if service_data and service_data != prev_service_data:
service_data = prev_service_data | service_data
elif not service_data:
service_data = prev_service_data
if manufacturer_data and manufacturer_data != prev_manufacturer_data:
manufacturer_data = prev_manufacturer_data | manufacturer_data
elif not manufacturer_data:
manufacturer_data = prev_manufacturer_data
#
# Bleak updates the BLEDevice via create_or_update_device.
# We need to do the same to ensure integrations that already
@@ -366,6 +351,16 @@ class BaseHaRemoteScanner(BaseHaScanner):
details=self._details | details,
rssi=rssi, # deprecated, will be removed in newer bleak
)
advertisement_data = AdvertisementData(
local_name=None if local_name == "" else local_name,
manufacturer_data=manufacturer_data,
service_data=service_data,
service_uuids=service_uuids,
tx_power=NO_RSSI_VALUE if tx_power is None else tx_power,
rssi=rssi,
platform_data=(),
)
self._discovered_device_advertisement_datas[address] = (
device,
advertisement_data,
@@ -373,12 +368,12 @@ class BaseHaRemoteScanner(BaseHaScanner):
self._discovered_device_timestamps[address] = now
self._new_info_callback(
BluetoothServiceInfoBleak(
name=advertisement_data.local_name or device.name or device.address,
address=device.address,
name=local_name or address,
address=address,
rssi=rssi,
manufacturer_data=advertisement_data.manufacturer_data,
service_data=advertisement_data.service_data,
service_uuids=advertisement_data.service_uuids,
manufacturer_data=manufacturer_data,
service_data=service_data,
service_uuids=service_uuids,
source=self.source,
device=device,
advertisement=advertisement_data,

View File

@@ -19,7 +19,7 @@
"bleak-retry-connector==3.0.2",
"bluetooth-adapters==0.15.3",
"bluetooth-auto-recovery==1.0.3",
"bluetooth-data-tools==0.3.1",
"dbus-fast==1.84.2"
"bluetooth-data-tools==0.4.0",
"dbus-fast==1.85.0"
]
}

View File

@@ -61,7 +61,7 @@ class BluetoothCallbackMatcherWithCallback(
"""Callback matcher for the bluetooth integration that stores the callback."""
@dataclass(frozen=False)
@dataclass(slots=True, frozen=False)
class IntegrationMatchHistory:
"""Track which fields have been seen."""

View File

@@ -20,7 +20,7 @@ MANAGER: BluetoothManager | None = None
MONOTONIC_TIME: Final = monotonic_time_coarse
@dataclass
@dataclass(slots=True)
class HaBluetoothConnector:
"""Data for how to connect a BLEDevice from a given scanner."""

View File

@@ -20,7 +20,7 @@ if TYPE_CHECKING:
from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak
@dataclasses.dataclass(frozen=True)
@dataclasses.dataclass(slots=True, frozen=True)
class PassiveBluetoothEntityKey:
"""Key for a passive bluetooth entity.
@@ -36,7 +36,7 @@ class PassiveBluetoothEntityKey:
_T = TypeVar("_T")
@dataclasses.dataclass(frozen=True)
@dataclasses.dataclass(slots=True, frozen=True)
class PassiveBluetoothDataUpdate(Generic[_T]):
"""Generic bluetooth data."""

View File

@@ -10,9 +10,10 @@ from .wrappers import HaBleakClientWrapper, HaBleakScannerWrapper
ORIGINAL_BLEAK_SCANNER = bleak.BleakScanner
ORIGINAL_BLEAK_CLIENT = bleak.BleakClient
ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT = (
ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT_WITH_SERVICE_CACHE = (
bleak_retry_connector.BleakClientWithServiceCache
)
ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT = bleak_retry_connector.BleakClient
def install_multiple_bleak_catcher() -> None:
@@ -23,6 +24,7 @@ def install_multiple_bleak_catcher() -> None:
bleak.BleakScanner = HaBleakScannerWrapper # type: ignore[misc, assignment]
bleak.BleakClient = HaBleakClientWrapper # type: ignore[misc]
bleak_retry_connector.BleakClientWithServiceCache = HaBleakClientWithServiceCache # type: ignore[misc,assignment] # noqa: E501
bleak_retry_connector.BleakClient = HaBleakClientWrapper # type: ignore[misc] # noqa: E501
def uninstall_multiple_bleak_catcher() -> None:
@@ -30,6 +32,9 @@ def uninstall_multiple_bleak_catcher() -> None:
bleak.BleakScanner = ORIGINAL_BLEAK_SCANNER # type: ignore[misc]
bleak.BleakClient = ORIGINAL_BLEAK_CLIENT # type: ignore[misc]
bleak_retry_connector.BleakClientWithServiceCache = ( # type: ignore[misc]
ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT_WITH_SERVICE_CACHE
)
bleak_retry_connector.BleakClient = ( # type: ignore[misc]
ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT
)

View File

@@ -34,7 +34,7 @@ if TYPE_CHECKING:
from .manager import BluetoothManager
@dataclass
@dataclass(slots=True)
class _HaWrappedBleakBackend:
"""Wrap bleak backend to make it usable by Home Assistant."""
@@ -251,8 +251,10 @@ class HaBleakClientWrapper(BleakClient):
assert models.MANAGER is not None
manager = models.MANAGER
wrapped_backend = self._async_get_best_available_backend_and_device(manager)
device = wrapped_backend.device
scanner = wrapped_backend.scanner
self._backend = wrapped_backend.client(
wrapped_backend.device,
device,
disconnected_callback=self._make_disconnected_callback(
self.__disconnected_callback
),
@@ -261,8 +263,9 @@ class HaBleakClientWrapper(BleakClient):
)
if debug_logging := _LOGGER.isEnabledFor(logging.DEBUG):
# Only lookup the description if we are going to log it
description = ble_device_description(wrapped_backend.device)
rssi = wrapped_backend.device.rssi
description = ble_device_description(device)
_, adv = scanner.discovered_devices_and_advertisement_data[device.address]
rssi = adv.rssi
_LOGGER.debug("%s: Connecting (last rssi: %s)", description, rssi)
connected = None
try:
@@ -271,11 +274,11 @@ class HaBleakClientWrapper(BleakClient):
# If we failed to connect and its a local adapter (no source)
# we release the connection slot
if not connected:
self.__connect_failures[wrapped_backend.scanner] = (
self.__connect_failures.get(wrapped_backend.scanner, 0) + 1
self.__connect_failures[scanner] = (
self.__connect_failures.get(scanner, 0) + 1
)
if not wrapped_backend.source:
manager.async_release_connection_slot(wrapped_backend.device)
manager.async_release_connection_slot(device)
if debug_logging:
_LOGGER.debug("%s: Connected (last rssi: %s)", description, rssi)

View File

@@ -41,6 +41,7 @@ PLATFORMS = [
Platform.DEVICE_TRACKER,
Platform.LOCK,
Platform.NOTIFY,
Platform.SELECT,
Platform.SENSOR,
]

View File

@@ -0,0 +1,139 @@
"""Select platform for BMW."""
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
import logging
from typing import Any
from bimmer_connected.vehicle import MyBMWVehicle
from bimmer_connected.vehicle.charging_profile import ChargingMode
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfElectricCurrent
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BMWBaseEntity
from .const import DOMAIN
from .coordinator import BMWDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@dataclass
class BMWRequiredKeysMixin:
"""Mixin for required keys."""
current_option: Callable[[MyBMWVehicle], str]
remote_service: Callable[[MyBMWVehicle, str], Coroutine[Any, Any, Any]]
@dataclass
class BMWSelectEntityDescription(SelectEntityDescription, BMWRequiredKeysMixin):
"""Describes BMW sensor entity."""
is_available: Callable[[MyBMWVehicle], bool] = lambda _: False
dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None
SELECT_TYPES: dict[str, BMWSelectEntityDescription] = {
# --- Generic ---
"target_soc": BMWSelectEntityDescription(
key="target_soc",
name="Target SoC",
is_available=lambda v: v.is_remote_set_target_soc_enabled,
options=[str(i * 5 + 20) for i in range(17)],
current_option=lambda v: str(v.fuel_and_battery.charging_target),
remote_service=lambda v, o: v.remote_services.trigger_charging_settings_update(
target_soc=int(o)
),
icon="mdi:battery-charging-medium",
unit_of_measurement=PERCENTAGE,
),
"ac_limit": BMWSelectEntityDescription(
key="ac_limit",
name="AC Charging Limit",
is_available=lambda v: v.is_remote_set_ac_limit_enabled,
dynamic_options=lambda v: [
str(lim) for lim in v.charging_profile.ac_available_limits # type: ignore[union-attr]
],
current_option=lambda v: str(v.charging_profile.ac_current_limit), # type: ignore[union-attr]
remote_service=lambda v, o: v.remote_services.trigger_charging_settings_update(
ac_limit=int(o)
),
icon="mdi:current-ac",
unit_of_measurement=UnitOfElectricCurrent.AMPERE,
),
"charging_mode": BMWSelectEntityDescription(
key="charging_mode",
name="Charging Mode",
is_available=lambda v: v.is_charging_plan_supported,
options=[c.value for c in ChargingMode if c != ChargingMode.UNKNOWN],
current_option=lambda v: str(v.charging_profile.charging_mode.value), # type: ignore[union-attr]
remote_service=lambda v, o: v.remote_services.trigger_charging_profile_update(
charging_mode=ChargingMode(o)
),
icon="mdi:vector-point-select",
),
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the MyBMW lock from config entry."""
coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
entities: list[BMWSelect] = []
for vehicle in coordinator.account.vehicles:
if not coordinator.read_only:
entities.extend(
[
BMWSelect(coordinator, vehicle, description)
for description in SELECT_TYPES.values()
if description.is_available(vehicle)
]
)
async_add_entities(entities)
class BMWSelect(BMWBaseEntity, SelectEntity):
"""Representation of BMW select entity."""
entity_description: BMWSelectEntityDescription
def __init__(
self,
coordinator: BMWDataUpdateCoordinator,
vehicle: MyBMWVehicle,
description: BMWSelectEntityDescription,
) -> None:
"""Initialize an BMW select."""
super().__init__(coordinator, vehicle)
self.entity_description = description
self._attr_unique_id = f"{vehicle.vin}-{description.key}"
if description.dynamic_options:
self._attr_options = description.dynamic_options(vehicle)
self._attr_current_option = description.current_option(vehicle)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
_LOGGER.debug(
"Updating select '%s' of %s", self.entity_description.key, self.vehicle.name
)
self._attr_current_option = self.entity_description.current_option(self.vehicle)
super()._handle_coordinator_update()
async def async_select_option(self, option: str) -> None:
"""Update to the vehicle."""
_LOGGER.debug(
"Executing '%s' on vehicle '%s' to value '%s'",
self.entity_description.key,
self.vehicle.vin,
option,
)
await self.entity_description.remote_service(self.vehicle, option)

View File

@@ -17,9 +17,9 @@ from homeassistant.const import (
ATTR_SW_VERSION,
ATTR_VIA_DEVICE,
)
from homeassistant.core import callback
from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.event import async_call_later
from .const import DOMAIN
from .utils import BondDevice, BondHub
@@ -27,6 +27,7 @@ from .utils import BondDevice, BondHub
_LOGGER = logging.getLogger(__name__)
_FALLBACK_SCAN_INTERVAL = timedelta(seconds=10)
_BPUP_ALIVE_SCAN_INTERVAL = timedelta(seconds=60)
class BondEntity(Entity):
@@ -65,6 +66,7 @@ class BondEntity(Entity):
self._attr_name = device.name
self._attr_assumed_state = self._hub.is_bridge and not self._device.trust_state
self._apply_state()
self._bpup_polling_fallback: CALLBACK_TYPE | None = None
@property
def device_info(self) -> DeviceInfo:
@@ -100,12 +102,13 @@ class BondEntity(Entity):
return device_info
async def async_update(self) -> None:
"""Fetch assumed state of the cover from the hub using API."""
"""Perform a manual update from API."""
await self._async_update_from_api()
@callback
def _async_update_if_bpup_not_alive(self, now: datetime) -> None:
"""Fetch via the API if BPUP is not alive."""
self._async_schedule_bpup_alive_or_poll()
if (
self.hass.is_stopping
or self._bpup_subs.alive
@@ -172,16 +175,22 @@ class BondEntity(Entity):
"""Subscribe to BPUP and start polling."""
await super().async_added_to_hass()
self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback)
self.async_on_remove(
async_track_time_interval(
self.hass,
self._async_update_if_bpup_not_alive,
_FALLBACK_SCAN_INTERVAL,
name=f"Bond {self.entity_id} fallback polling",
)
self._async_schedule_bpup_alive_or_poll()
@callback
def _async_schedule_bpup_alive_or_poll(self) -> None:
"""Schedule the BPUP alive or poll."""
alive = self._bpup_subs.alive
self._bpup_polling_fallback = async_call_later(
self.hass,
_BPUP_ALIVE_SCAN_INTERVAL if alive else _FALLBACK_SCAN_INTERVAL,
self._async_update_if_bpup_not_alive,
)
async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from BPUP data on remove."""
await super().async_will_remove_from_hass()
self._bpup_subs.unsubscribe(self._device_id, self._async_bpup_callback)
if self._bpup_polling_fallback:
self._bpup_polling_fallback()
self._bpup_polling_fallback = None

View File

@@ -16,7 +16,7 @@ from .heartbeat import BroadlinkHeartbeat
class BroadlinkData:
"""Class for sharing data within the Broadlink integration."""
devices: dict = field(default_factory=dict)
devices: dict[str, BroadlinkDevice] = field(default_factory=dict)
platforms: dict = field(default_factory=dict)
heartbeat: BroadlinkHeartbeat | None = None
@@ -29,7 +29,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a Broadlink device from a config entry."""
data = hass.data[DOMAIN]
data: BroadlinkData = hass.data[DOMAIN]
if data.heartbeat is None:
data.heartbeat = BroadlinkHeartbeat(hass)
@@ -41,12 +41,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
data = hass.data[DOMAIN]
data: BroadlinkData = hass.data[DOMAIN]
device = data.devices.pop(entry.entry_id)
result = await device.async_unload()
if not data.devices:
if data.heartbeat and not data.devices:
await data.heartbeat.async_unload()
data.heartbeat = None

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