Compare commits

..

45 Commits

Author SHA1 Message Date
epenet
21bbabc88e Merge branch 'dev' into block_pyserial_asyncio 2025-12-18 08:52:07 +01:00
epenet
e5a73fcf57 Disable blackbird integration (#157817) 2025-12-18 08:51:54 +01:00
Andre Lengwenus
6991e01489 Fix incorrect status updates for lcn (#159251) 2025-12-18 08:11:22 +01:00
Simone Chemelli
c8636ee6f3 Add missing strings for Shelly voltmeter sensor (#159332) 2025-12-18 08:05:46 +01:00
J. Nick Koston
52229dc5a8 Bump aioesphomeapi to 43.3.0 (#159141) 2025-12-17 20:22:38 -10:00
Andre Lengwenus
f013455843 Bump pypck to 0.9.8 (#159277) 2025-12-18 06:54:56 +01:00
Joost Lekkerkerker
cae5bca546 Add integration_type device to kostal_plenticore (#159288) 2025-12-17 21:00:27 +01:00
Joost Lekkerkerker
49299b06c6 Add integration_type device to kmtronic (#159286) 2025-12-17 20:58:58 +01:00
Paul Tarjan
8e39027ad5 Add guidance to not amend commits after review starts (#158804) 2025-12-17 20:58:43 +01:00
Joost Lekkerkerker
2a1ce2df61 Add integration_type service to kodi (#159287) 2025-12-17 20:57:47 +01:00
Joost Lekkerkerker
7a6d929150 Add integration_type device to kulersky (#159290) 2025-12-17 20:56:52 +01:00
Joost Lekkerkerker
6f4a112dbb Add integration_type hub to lacrosse_view (#159291) 2025-12-17 20:56:14 +01:00
Joost Lekkerkerker
2197b910fb Add integration_type device to landisgyr_heat_meter (#159293) 2025-12-17 20:55:11 +01:00
Joost Lekkerkerker
7e2a9cd7f9 Add integration_type hub to laundrify (#159295) 2025-12-17 20:54:20 +01:00
Joost Lekkerkerker
e7ed7a8ed2 Add integration_type hub to lcn (#159296) 2025-12-17 20:53:41 +01:00
Joost Lekkerkerker
9ba2d0defe Add integration_type device to leaone (#159297) 2025-12-17 20:52:35 +01:00
Joost Lekkerkerker
231300919c Add integration_type device to led_ble (#159298) 2025-12-17 20:51:45 +01:00
Joost Lekkerkerker
664c50586f Add integration_type device to lg_soundbar (#159299) 2025-12-17 20:51:00 +01:00
Joost Lekkerkerker
43b9ecfc2b Add integration_type device to lifx (#159302) 2025-12-17 20:48:33 +01:00
Joost Lekkerkerker
f1237ed52a Add integration_type hub to livisi (#159303) 2025-12-17 20:47:39 +01:00
Joost Lekkerkerker
ecf8f55cc4 Add integration_type device to loqed (#159305) 2025-12-17 20:45:23 +01:00
Joost Lekkerkerker
ff36693057 Add integration_type hub to lupusec (#159306) 2025-12-17 20:44:29 +01:00
Joost Lekkerkerker
005785997c Add integration_type hub to lutron (#159307) 2025-12-17 20:43:47 +01:00
Joost Lekkerkerker
9917b82b66 Add integration_type hub to lyric (#159309) 2025-12-17 20:42:30 +01:00
Joost Lekkerkerker
9c927406ac Add integration_type service to mailgun (#159310) 2025-12-17 20:41:43 +01:00
Joost Lekkerkerker
972d95602a Add integration_type hub to meater (#159311) 2025-12-17 20:41:10 +01:00
Joost Lekkerkerker
5e0549a18f Add integration_type device to medcom_ble (#159312) 2025-12-17 20:39:39 +01:00
Joost Lekkerkerker
bcbb159fb2 Add integration_type device to melnor (#159313) 2025-12-17 20:38:23 +01:00
Joost Lekkerkerker
0123ca656a Add integration_type hub to lg_thinq (#159300) 2025-12-17 20:34:25 +01:00
Joost Lekkerkerker
1f699c729c Add integration_type service to lastfm (#159294) 2025-12-17 20:33:49 +01:00
Joost Lekkerkerker
50c3fcfeba Add integration_type service to kraken (#159289) 2025-12-17 20:33:17 +01:00
Raphael Hehl
2af1e098cc Improve debug logging in UniFi Protect integration (#159318) 2025-12-17 20:31:33 +01:00
epenet
3a79fb273e Merge branch 'dev' into block_pyserial_asyncio 2025-10-16 12:08:06 +02:00
J. Nick Koston
162c27b92c Merge branch 'dev' into block_pyserial_asyncio 2025-05-26 10:40:34 -05:00
J. Nick Koston
e7e42dc318 Merge branch 'dev' into block_pyserial_asyncio 2024-08-16 17:48:47 -05:00
J. Nick Koston
9aa288ed44 Merge branch 'dev' into block_pyserial_asyncio 2024-06-22 16:36:10 -05:00
J. Nick Koston
5aacb6e1b8 Merge branch 'dev' into block_pyserial_asyncio 2024-06-22 15:18:40 -05:00
J. Nick Koston
1428ce4084 Merge branch 'dev' into block_pyserial_asyncio 2024-05-05 10:06:24 -05:00
J. Nick Koston
dba07ac90d Merge branch 'dev' into block_pyserial_asyncio 2024-05-03 02:12:40 -05:00
J. Nick Koston
264df97069 Merge branch 'dev' into block_pyserial_asyncio 2024-05-02 16:37:09 -05:00
J. Nick Koston
c3f493394a Merge branch 'drop_pyserial_zha' into block_pyserial_asyncio 2024-05-02 11:10:42 -05:00
J. Nick Koston
7e3e82746f Drop pyserial-asyncio from zha
This may not be possible yet, but the long term goal is to get
rid of pyserial-asyncio everywhere so we can prevent future
integrations from using it and than we have to go though the
effort of getting them to replace it with pyserial-asyncio-fast
to avoid the event loop being blocked

needed for https://github.com/home-assistant/core/pull/116635
2024-05-02 11:09:43 -05:00
J. Nick Koston
33724240d7 Merge branch 'serial' into block_pyserial_asyncio 2024-05-02 11:06:25 -05:00
J. Nick Koston
998a4eab9e Replace pyserial-asyncio with pyserial-asyncio-fast in serial
pyserial-asyncio is unmantained and does blocking I/O in the event loop
2024-05-02 11:04:45 -05:00
J. Nick Koston
9985262a53 Block pyserial-asyncio in favor of pyserial-asyncio-fast
pyserial-asyncio does blocking I/O in asyncio loop and is not maintained
2024-05-02 11:00:12 -05:00
49 changed files with 88 additions and 421 deletions

View File

@@ -51,6 +51,9 @@ rules:
- **Missing imports** - We use static analysis tooling to catch that
- **Code formatting** - We have ruff as a formatting tool that will catch those if needed (unless specifically instructed otherwise in these instructions)
**Git commit practices during review:**
- **Do NOT amend, squash, or rebase commits after review has started** - Reviewers need to see what changed since their last review
## Python Requirements
- **Compatibility**: Python 3.13+

View File

@@ -132,7 +132,6 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
"fan",
"lawn_mower",
"light",
"lock",
"media_player",
"switch",
"text",

View File

@@ -2,6 +2,7 @@
"domain": "blackbird",
"name": "Monoprice Blackbird Matrix Switch",
"codeowners": [],
"disabled": "This integration is disabled because it references pyserial-asyncio, which does blocking I/O in the asyncio loop and is not maintained.",
"documentation": "https://www.home-assistant.io/integrations/blackbird",
"iot_class": "local_polling",
"loggers": ["pyblackbird"],

View File

@@ -17,7 +17,7 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==43.0.0",
"aioesphomeapi==43.3.0",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==3.4.0"
],

View File

@@ -4,6 +4,7 @@
"codeowners": ["@dgomes"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/kmtronic",
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["pykmtronic"],
"requirements": ["pykmtronic==0.3.0"]

View File

@@ -5,6 +5,7 @@
"codeowners": ["@OnFreund"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/kodi",
"integration_type": "service",
"iot_class": "local_push",
"loggers": ["jsonrpc_async", "jsonrpc_base", "jsonrpc_websocket", "pykodi"],
"requirements": ["pykodi==0.2.7"],

View File

@@ -4,6 +4,7 @@
"codeowners": ["@stegm"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/kostal_plenticore",
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["kostal"],
"requirements": ["pykoplenti==1.3.0"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@eifinger"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/kraken",
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["krakenex", "pykrakenapi"],
"requirements": ["krakenex==2.2.2", "pykrakenapi==0.1.8"]

View File

@@ -10,6 +10,7 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/kulersky",
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["bleak", "pykulersky"],
"requirements": ["pykulersky==0.5.8"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@IceBotYT"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/lacrosse_view",
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["lacrosse_view"],
"requirements": ["lacrosse-view==1.1.1"]

View File

@@ -5,6 +5,7 @@
"config_flow": true,
"dependencies": ["usb"],
"documentation": "https://www.home-assistant.io/integrations/landisgyr_heat_meter",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["ultraheat-api==0.5.7"]
}

View File

@@ -4,6 +4,7 @@
"codeowners": ["@joostlek"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/lastfm",
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["pylast"],
"requirements": ["pylast==5.1.0"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@xLarry"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/laundrify",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["laundrify-aio==1.2.2"]
}

View File

@@ -19,8 +19,8 @@ from .const import CONF_DOMAIN_DATA
from .entity import LcnEntity
from .helpers import InputType, LcnConfigEntry
PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(minutes=1)
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(minutes=10)
def add_lcn_entities(

View File

@@ -36,7 +36,7 @@ from .const import (
from .entity import LcnEntity
from .helpers import InputType, LcnConfigEntry
PARALLEL_UPDATES = 0
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(minutes=1)

View File

@@ -27,7 +27,7 @@ from .const import (
from .entity import LcnEntity
from .helpers import InputType, LcnConfigEntry
PARALLEL_UPDATES = 0
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(minutes=1)

View File

@@ -33,8 +33,8 @@ from .helpers import InputType, LcnConfigEntry
BRIGHTNESS_SCALE = (1, 100)
PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(minutes=1)
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(minutes=10)
def add_lcn_entities(

View File

@@ -6,8 +6,9 @@
"config_flow": true,
"dependencies": ["http", "websocket_api"],
"documentation": "https://www.home-assistant.io/integrations/lcn",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["pypck"],
"quality_scale": "silver",
"requirements": ["pypck==0.9.7", "lcn-frontend==0.2.7"]
"requirements": ["pypck==0.9.8", "lcn-frontend==0.2.7"]
}

View File

@@ -22,7 +22,7 @@ from .const import (
from .entity import LcnEntity
from .helpers import LcnConfigEntry
PARALLEL_UPDATES = 0
PARALLEL_UPDATES = 2
def add_lcn_entities(

View File

@@ -40,7 +40,7 @@ from .const import (
from .entity import LcnEntity
from .helpers import InputType, LcnConfigEntry
PARALLEL_UPDATES = 0
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(minutes=1)

View File

@@ -17,8 +17,8 @@ from .const import CONF_DOMAIN_DATA, CONF_OUTPUT, OUTPUT_PORTS, RELAY_PORTS, SET
from .entity import LcnEntity
from .helpers import InputType, LcnConfigEntry
PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(minutes=1)
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(minutes=10)
def add_lcn_switch_entities(

View File

@@ -5,6 +5,7 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/leaone",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["leaone-ble==0.3.0"]
}

View File

@@ -34,6 +34,7 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/led_ble",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["bluetooth-data-tools==1.28.4", "led-ble==1.1.7"]
}

View File

@@ -4,6 +4,7 @@
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/lg_soundbar",
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["temescal"],
"requirements": ["temescal==0.5"]

View File

@@ -3,8 +3,13 @@
"name": "LG ThinQ",
"codeowners": ["@LG-ThinQ-Integration"],
"config_flow": true,
"dhcp": [{ "macaddress": "34E6E6*" }],
"dhcp": [
{
"macaddress": "34E6E6*"
}
],
"documentation": "https://www.home-assistant.io/integrations/lg_thinq",
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["thinqconnect"],
"requirements": ["thinqconnect==1.0.9"]

View File

@@ -49,6 +49,7 @@
"LIFX Z"
]
},
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["aiolifx", "aiolifx_effects", "bitstring"],
"requirements": [

View File

@@ -4,6 +4,7 @@
"codeowners": ["@StefanIacobLivisi", "@planbnet"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/livisi",
"integration_type": "hub",
"iot_class": "local_polling",
"requirements": ["livisi==0.0.25"]
}

View File

@@ -22,19 +22,5 @@
"unlock": {
"service": "mdi:lock-open-variant"
}
},
"triggers": {
"jammed": {
"trigger": "mdi:lock-alert"
},
"locked": {
"trigger": "mdi:lock"
},
"opened": {
"trigger": "mdi:lock-open-variant"
},
"unlocked": {
"trigger": "mdi:lock-open-variant"
}
}
}

View File

@@ -1,8 +1,4 @@
{
"common": {
"trigger_behavior_description": "The behavior of the targeted locks to trigger on.",
"trigger_behavior_name": "Behavior"
},
"device_automation": {
"action_type": {
"lock": "Lock {entity_name}",
@@ -54,15 +50,6 @@
"message": "The code for {entity_id} doesn't match pattern {code_format}."
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"lock": {
"description": "Locks a lock.",
@@ -95,47 +82,5 @@
"name": "Unlock"
}
},
"title": "Lock",
"triggers": {
"jammed": {
"description": "Triggers after one or more locks jam.",
"fields": {
"behavior": {
"description": "[%key:component::lock::common::trigger_behavior_description%]",
"name": "[%key:component::lock::common::trigger_behavior_name%]"
}
},
"name": "Lock jammed"
},
"locked": {
"description": "Triggers after one or more locks lock.",
"fields": {
"behavior": {
"description": "[%key:component::lock::common::trigger_behavior_description%]",
"name": "[%key:component::lock::common::trigger_behavior_name%]"
}
},
"name": "Lock locked"
},
"opened": {
"description": "Triggers after one or more locks open.",
"fields": {
"behavior": {
"description": "[%key:component::lock::common::trigger_behavior_description%]",
"name": "[%key:component::lock::common::trigger_behavior_name%]"
}
},
"name": "Lock opened"
},
"unlocked": {
"description": "Triggers after one or more locks unlock.",
"fields": {
"behavior": {
"description": "[%key:component::lock::common::trigger_behavior_description%]",
"name": "[%key:component::lock::common::trigger_behavior_name%]"
}
},
"name": "Lock unlocked"
}
}
"title": "Lock"
}

View File

@@ -1,18 +0,0 @@
"""Provides triggers for locks."""
from homeassistant.core import HomeAssistant
from homeassistant.helpers.trigger import Trigger, make_entity_target_state_trigger
from .const import DOMAIN, LockState
TRIGGERS: dict[str, type[Trigger]] = {
"jammed": make_entity_target_state_trigger(DOMAIN, LockState.JAMMED),
"locked": make_entity_target_state_trigger(DOMAIN, LockState.LOCKED),
"opened": make_entity_target_state_trigger(DOMAIN, LockState.OPEN),
"unlocked": make_entity_target_state_trigger(DOMAIN, LockState.UNLOCKED),
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for locks."""
return TRIGGERS

View File

@@ -1,20 +0,0 @@
.trigger_common: &trigger_common
target:
entity:
domain: lock
fields:
behavior:
required: true
default: any
selector:
select:
options:
- first
- last
- any
translation_key: trigger_behavior
jammed: *trigger_common
locked: *trigger_common
opened: *trigger_common
unlocked: *trigger_common

View File

@@ -6,6 +6,7 @@
"config_flow": true,
"dependencies": ["webhook"],
"documentation": "https://www.home-assistant.io/integrations/loqed",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["loqedAPI==2.1.10"],
"zeroconf": [

View File

@@ -4,6 +4,7 @@
"codeowners": ["@majuss", "@suaveolent"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/lupusec",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["lupupy"],
"requirements": ["lupupy==0.3.2"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@cdheiser", "@wilburCForce"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/lutron",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["pylutron"],
"requirements": ["pylutron==0.2.18"],

View File

@@ -19,6 +19,7 @@
}
],
"documentation": "https://www.home-assistant.io/integrations/lyric",
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["aiolyric"],
"requirements": ["aiolyric==2.0.2"]

View File

@@ -5,6 +5,7 @@
"config_flow": true,
"dependencies": ["webhook"],
"documentation": "https://www.home-assistant.io/integrations/mailgun",
"integration_type": "service",
"iot_class": "cloud_push",
"loggers": ["pymailgunner"],
"requirements": ["pymailgunner==1.4"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@Sotolotl", "@emontnemery"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/meater",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["meater-python==0.0.8"]
}

View File

@@ -10,6 +10,7 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/medcom_ble",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["medcom-ble==0.1.1"]
}

View File

@@ -11,6 +11,7 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/melnor",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["melnor-bluetooth==0.0.25"]
}

View File

@@ -537,6 +537,12 @@
"voltmeter_value": {
"name": "Voltmeter value"
},
"voltmeter_value_with_channel_name": {
"name": "Voltmeter value {channel_name}"
},
"voltmeter_with_channel_name": {
"name": "Voltmeter {channel_name}"
},
"water_consumption": {
"name": "Water consumption"
},

View File

@@ -83,7 +83,7 @@ def _async_device_entities(
_LOGGER.debug(
"Adding %s entity %s for %s",
klass.__name__,
description.name,
description.key,
device.display_name,
)
continue
@@ -111,7 +111,7 @@ def _async_device_entities(
_LOGGER.debug(
"Adding %s entity %s for %s",
klass.__name__,
description.name,
description.key,
device.display_name,
)
@@ -252,16 +252,11 @@ class BaseProtectEntity(Entity):
if changed:
if _LOGGER.isEnabledFor(logging.DEBUG):
device_name = device.name or ""
if hasattr(self, "entity_description") and self.entity_description.name:
device_name += f" {self.entity_description.name}"
_LOGGER.debug(
"Updating state [%s (%s)] %s -> %s",
device_name,
device.mac,
"Updating state [%s] %s -> %s",
self.entity_id,
previous_attrs,
tuple((getattr(self, attr)) for attr in self._state_attrs),
tuple(getter() for getter in self._state_getters),
)
self.async_write_ha_state()

View File

@@ -377,9 +377,7 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity):
entity_description.entity_category is not None
and entity_description.ufp_options_fn is not None
):
_LOGGER.debug(
"Updating dynamic select options for %s", entity_description.name
)
_LOGGER.debug("Updating dynamic select options for %s", self.entity_id)
self._async_set_options(self.data, entity_description)
if (unifi_value := entity_description.get_ufp_value(device)) is None:
unifi_value = TYPE_EMPTY_VALUE

View File

@@ -3344,7 +3344,7 @@
},
"kmtronic": {
"name": "KMtronic",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_push"
},
@@ -3363,7 +3363,7 @@
},
"kodi": {
"name": "Kodi",
"integration_type": "hub",
"integration_type": "service",
"config_flow": true,
"iot_class": "local_push"
},
@@ -3386,13 +3386,13 @@
},
"kostal_plenticore": {
"name": "Kostal Plenticore Solar Inverter",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
"kraken": {
"name": "Kraken",
"integration_type": "hub",
"integration_type": "service",
"config_flow": true,
"iot_class": "cloud_polling"
},
@@ -3403,7 +3403,7 @@
},
"kulersky": {
"name": "Kuler Sky",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
@@ -3439,7 +3439,7 @@
},
"landisgyr_heat_meter": {
"name": "Landis+Gyr Heat Meter",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
@@ -3451,7 +3451,7 @@
},
"lastfm": {
"name": "Last.fm",
"integration_type": "hub",
"integration_type": "service",
"config_flow": true,
"iot_class": "cloud_polling"
},
@@ -3482,13 +3482,13 @@
},
"leaone": {
"name": "LeaOne",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_push"
},
"led_ble": {
"name": "LED BLE",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
@@ -3536,7 +3536,7 @@
"name": "LG Netcast"
},
"lg_soundbar": {
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling",
"name": "LG Soundbars"
@@ -3569,7 +3569,7 @@
},
"lifx": {
"name": "LIFX",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
@@ -3727,7 +3727,7 @@
},
"loqed": {
"name": "LOQED Touch Smart Lock",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_push"
},
@@ -3796,7 +3796,7 @@
},
"mailgun": {
"name": "Mailgun",
"integration_type": "hub",
"integration_type": "service",
"config_flow": true,
"iot_class": "cloud_push"
},
@@ -3866,7 +3866,7 @@
},
"medcom_ble": {
"name": "Medcom Bluetooth",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
@@ -3899,7 +3899,7 @@
"name": "Melnor",
"integrations": {
"melnor": {
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling",
"name": "Melnor Bluetooth"

View File

@@ -190,6 +190,10 @@ scapy>=2.6.1
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
tuf>=4.0.0
# pyserial-asyncio does blocking I/O in asyncio loop, use pyserial-asyncio-fast
# instead as pyserial-asyncio is not maintained
pyserial-asyncio==1000000000.0.0
# https://github.com/jd/tenacity/issues/471
tenacity!=8.4.0

7
requirements_all.txt generated
View File

@@ -252,7 +252,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==43.0.0
aioesphomeapi==43.3.0
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -1918,9 +1918,6 @@ pybalboa==1.1.3
# homeassistant.components.bbox
pybbox==0.0.5-alpha
# homeassistant.components.blackbird
pyblackbird==0.6
# homeassistant.components.bluesound
pyblu==2.0.5
@@ -2300,7 +2297,7 @@ pypaperless==4.1.1
pypca==0.0.7
# homeassistant.components.lcn
pypck==0.9.7
pypck==0.9.8
# homeassistant.components.pglab
pypglab==0.0.5

View File

@@ -243,7 +243,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==43.0.0
aioesphomeapi==43.3.0
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -1637,9 +1637,6 @@ pyaussiebb==0.1.5
# homeassistant.components.balboa
pybalboa==1.1.3
# homeassistant.components.blackbird
pyblackbird==0.6
# homeassistant.components.bluesound
pyblu==2.0.5
@@ -1941,7 +1938,7 @@ pypalazzetti==0.1.20
pypaperless==4.1.1
# homeassistant.components.lcn
pypck==0.9.7
pypck==0.9.8
# homeassistant.components.pglab
pypglab==0.0.5

View File

@@ -181,6 +181,10 @@ scapy>=2.6.1
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
tuf>=4.0.0
# pyserial-asyncio does blocking I/O in asyncio loop, use pyserial-asyncio-fast
# instead as pyserial-asyncio is not maintained
pyserial-asyncio==1000000000.0.0
# https://github.com/jd/tenacity/issues/471
tenacity!=8.4.0

View File

@@ -0,0 +1,3 @@
"""Fixtures for component."""
collect_ignore_glob = ["test_*.py"]

View File

@@ -1,261 +0,0 @@
"""Test lock triggers."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.components.lock import DOMAIN, LockState
from homeassistant.const import ATTR_LABEL_ID, CONF_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall
from tests.components import (
StateDescription,
arm_trigger,
other_states,
parametrize_target_entities,
parametrize_trigger_states,
set_or_remove_state,
target_entities,
)
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_locks(hass: HomeAssistant) -> list[str]:
"""Create multiple lock entities associated with different targets."""
return (await target_entities(hass, DOMAIN))["included"]
@pytest.mark.parametrize(
"trigger_key",
[
"lock.jammed",
"lock.locked",
"lock.opened",
"lock.unlocked",
],
)
async def test_lock_triggers_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
) -> None:
"""Test the lock triggers are gated by the labs flag."""
await arm_trigger(hass, trigger_key, None, {ATTR_LABEL_ID: "test_label"})
assert (
"Unnamed automation failed to setup triggers and has been disabled: Trigger "
f"'{trigger_key}' requires the experimental 'New triggers and conditions' "
"feature to be enabled in Home Assistant Labs settings (feature flag: "
"'new_triggers_conditions')"
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
)
@pytest.mark.parametrize(
("trigger", "states"),
[
*parametrize_trigger_states(
trigger="lock.jammed",
target_states=[LockState.JAMMED],
other_states=other_states(LockState.JAMMED),
),
*parametrize_trigger_states(
trigger="lock.locked",
target_states=[LockState.LOCKED],
other_states=other_states(LockState.LOCKED),
),
*parametrize_trigger_states(
trigger="lock.opened",
target_states=[LockState.OPEN],
other_states=other_states(LockState.OPEN),
),
*parametrize_trigger_states(
trigger="lock.unlocked",
target_states=[LockState.UNLOCKED],
other_states=other_states(LockState.UNLOCKED),
),
],
)
async def test_lock_state_trigger_behavior_any(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_locks: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the lock state trigger fires when any lock state changes to a specific state."""
other_entity_ids = set(target_locks) - {entity_id}
# Set all locks, including the tested one, to the initial state
for eid in target_locks:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
await arm_trigger(hass, trigger, {}, trigger_target_config)
for state in states[1:]:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Check if changing other locks also triggers
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == (entities_in_target - 1) * state["count"]
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
)
@pytest.mark.parametrize(
("trigger", "states"),
[
*parametrize_trigger_states(
trigger="lock.jammed",
target_states=[LockState.JAMMED],
other_states=other_states(LockState.JAMMED),
),
*parametrize_trigger_states(
trigger="lock.locked",
target_states=[LockState.LOCKED],
other_states=other_states(LockState.LOCKED),
),
*parametrize_trigger_states(
trigger="lock.opened",
target_states=[LockState.OPEN],
other_states=other_states(LockState.OPEN),
),
*parametrize_trigger_states(
trigger="lock.unlocked",
target_states=[LockState.UNLOCKED],
other_states=other_states(LockState.UNLOCKED),
),
],
)
async def test_lock_state_trigger_behavior_first(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_locks: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the lock state trigger fires when the first lock changes to a specific state."""
other_entity_ids = set(target_locks) - {entity_id}
# Set all locks, including the tested one, to the initial state
for eid in target_locks:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
await arm_trigger(hass, trigger, {"behavior": "first"}, trigger_target_config)
for state in states[1:]:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Triggering other locks should not cause the trigger to fire again
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
)
@pytest.mark.parametrize(
("trigger", "states"),
[
*parametrize_trigger_states(
trigger="lock.jammed",
target_states=[LockState.JAMMED],
other_states=other_states(LockState.JAMMED),
),
*parametrize_trigger_states(
trigger="lock.locked",
target_states=[LockState.LOCKED],
other_states=other_states(LockState.LOCKED),
),
*parametrize_trigger_states(
trigger="lock.opened",
target_states=[LockState.OPEN],
other_states=other_states(LockState.OPEN),
),
*parametrize_trigger_states(
trigger="lock.unlocked",
target_states=[LockState.UNLOCKED],
other_states=other_states(LockState.UNLOCKED),
),
],
)
async def test_lock_state_trigger_behavior_last(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_locks: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the lock state trigger fires when the last lock changes to a specific state."""
other_entity_ids = set(target_locks) - {entity_id}
# Set all locks, including the tested one, to the initial state
for eid in target_locks:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
for state in states[1:]:
included_state = state["included"]
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == 0
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()