mirror of
https://github.com/home-assistant/core.git
synced 2025-12-13 19:38:10 +00:00
Compare commits
88 Commits
button_tri
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bed9c20b3 | ||
|
|
d3fb7a7b87 | ||
|
|
60dcca4143 | ||
|
|
01f498f239 | ||
|
|
15055b8e8e | ||
|
|
6826619e12 | ||
|
|
b50a8e04a8 | ||
|
|
c6c67c5357 | ||
|
|
c82803d1e2 | ||
|
|
732b30f181 | ||
|
|
0e2e57a657 | ||
|
|
f00b0080a9 | ||
|
|
ad970c1234 | ||
|
|
02ec56bffa | ||
|
|
8388c290bf | ||
|
|
576ee99faf | ||
|
|
8a3534c345 | ||
|
|
e1e91c5568 | ||
|
|
1e09bddb1d | ||
|
|
90e4340595 | ||
|
|
120b17349c | ||
|
|
8a26961304 | ||
|
|
407b675080 | ||
|
|
274844271b | ||
|
|
f11e4e7bda | ||
|
|
96f8c39c6f | ||
|
|
77b79fef8d | ||
|
|
a0d2f285f3 | ||
|
|
3aef05d1ec | ||
|
|
510e391ee4 | ||
|
|
54adfdd694 | ||
|
|
d45f920b4a | ||
|
|
3080ef9a4a | ||
|
|
51cebb52f3 | ||
|
|
7b0d4c47b7 | ||
|
|
a660ab3f97 | ||
|
|
dd8fc16788 | ||
|
|
2b0fab0468 | ||
|
|
3bb88ed433 | ||
|
|
984385cd98 | ||
|
|
09de108676 | ||
|
|
ebc7581718 | ||
|
|
e55162812d | ||
|
|
aa6ccaa024 | ||
|
|
e1b009a6de | ||
|
|
91ddc525b0 | ||
|
|
d7d7954ac2 | ||
|
|
e87c260df7 | ||
|
|
5185c6cd68 | ||
|
|
7599c918e2 | ||
|
|
fa7e22ec91 | ||
|
|
606519e51b | ||
|
|
8e39e010f7 | ||
|
|
dc01cf49a0 | ||
|
|
1f3ad382f1 | ||
|
|
2595c7dcb2 | ||
|
|
d445b320de | ||
|
|
7b6df1a8a0 | ||
|
|
2a151dcd19 | ||
|
|
adbab150af | ||
|
|
d20edf7928 | ||
|
|
7d6d37fe76 | ||
|
|
228e0453a7 | ||
|
|
1da31c0530 | ||
|
|
41ad15e577 | ||
|
|
421af881fe | ||
|
|
715a484f7e | ||
|
|
0a789f51b8 | ||
|
|
fa25d45123 | ||
|
|
6d255b2521 | ||
|
|
5ffb39f064 | ||
|
|
d642109436 | ||
|
|
10f6d8d14f | ||
|
|
a94678cb06 | ||
|
|
0d8d466003 | ||
|
|
8ddf3e1734 | ||
|
|
d88047a750 | ||
|
|
61c7ac81d6 | ||
|
|
bbe07bddb0 | ||
|
|
a3afc2beb1 | ||
|
|
374cd93d3d | ||
|
|
6e99411084 | ||
|
|
41d5415c86 | ||
|
|
052d56f358 | ||
|
|
0a676b5812 | ||
|
|
1f4cf67daa | ||
|
|
bb4ec229ce | ||
|
|
ff62b460d5 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -263,7 +263,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: &actions-cache actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: &actions-cache actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
|
||||
with:
|
||||
path: venv
|
||||
key: &key-pre-commit-venv >-
|
||||
@@ -304,7 +304,7 @@ jobs:
|
||||
- &cache-restore-pre-commit-venv
|
||||
name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: &actions-cache-restore actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: &actions-cache-restore actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -511,7 +511,7 @@ jobs:
|
||||
fi
|
||||
- name: Save apt cache
|
||||
if: steps.cache-apt-check.outputs.cache-hit != 'true'
|
||||
uses: &actions-cache-save actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: &actions-cache-save actions/cache/save@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
|
||||
with:
|
||||
path: *path-apt-cache
|
||||
key: *key-apt-cache
|
||||
|
||||
4
CODEOWNERS
generated
4
CODEOWNERS
generated
@@ -220,8 +220,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
|
||||
/homeassistant/components/blebox/ @bbx-a @swistakm
|
||||
/tests/components/blebox/ @bbx-a @swistakm
|
||||
/homeassistant/components/blink/ @fronzbot @mkmer
|
||||
/tests/components/blink/ @fronzbot @mkmer
|
||||
/homeassistant/components/blink/ @fronzbot
|
||||
/tests/components/blink/ @fronzbot
|
||||
/homeassistant/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
|
||||
/tests/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
|
||||
/homeassistant/components/bluemaestro/ @bdraco
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/actron_air",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["actron-neo-api==0.1.87"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@Bre77"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/advantage_air",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["advantage_air"],
|
||||
"requirements": ["advantage-air==0.4.4"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@Noltari"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aemet_opendata"],
|
||||
"requirements": ["AEMET-OpenData==0.6.4"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aftership",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["pyaftership==21.11.0"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@ispysoftware"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/agent_dvr",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["agent"],
|
||||
"requirements": ["agent-py==0.0.24"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@asymworks"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airnow",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyairnow"],
|
||||
"requirements": ["pyairnow==1.3.1"]
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/airthings",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["airthings"],
|
||||
"requirements": ["airthings-cloud==0.2.0"]
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["airthings-ble==1.2.0"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@samsinnamon"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airtouch4",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["airtouch4pyapi"],
|
||||
"requirements": ["airtouch4pyapi==1.0.5"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@danzel"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airtouch5",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["airtouch5py"],
|
||||
"requirements": ["airtouch5py==0.3.0"]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==1.0.4"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@Noltari"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.7.2"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@madpilot"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/amberelectric",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["amberelectric"],
|
||||
"requirements": ["amberelectric==2.0.12"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@engrbm87"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/android_ip_webcam",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["pydroid-ipcam==3.0.0"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@Lash-L"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/anova",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["anova_wifi"],
|
||||
"requirements": ["anova-wifi==0.17.0"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@hyralex"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/anthemav",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["anthemav"],
|
||||
"requirements": ["anthemav==1.4.1"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@bdr99"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aosmith",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["py-aosmith==1.0.15"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@elupus"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["arcam"],
|
||||
"requirements": ["arcam-fmj==1.8.2"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@ikalnyi"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/arve",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["asyncarve==0.1.1"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@milanmeu"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioaseko"],
|
||||
"requirements": ["aioaseko==1.0.0"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@MatsNL"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/atag",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyatag"],
|
||||
"requirements": ["pyatag==0.3.5.3"]
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==9.2.0", "yalexs-ble==3.2.2"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@djtimca"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aurora",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["auroranoaa"],
|
||||
"requirements": ["auroranoaa==0.0.5"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@nickw444", "@Bre77"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aussie_broadband",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aussiebb"],
|
||||
"requirements": ["pyaussiebb==0.1.5"]
|
||||
|
||||
@@ -6,10 +6,7 @@ rules:
|
||||
This integration does not provide additional actions.
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules:
|
||||
status: todo
|
||||
comment: |
|
||||
The entity.py file is not used in this integration.
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
|
||||
@@ -204,13 +204,25 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AutarcoBatterySensorEntity(
|
||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
class AutarcoSensorBase(CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity):
|
||||
"""Base class for Autarco sensors."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AutarcoDataUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Autarco sensor base."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
|
||||
|
||||
class AutarcoBatterySensorEntity(AutarcoSensorBase):
|
||||
"""Defines an Autarco battery sensor."""
|
||||
|
||||
entity_description: AutarcoBatterySensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -218,10 +230,8 @@ class AutarcoBatterySensorEntity(
|
||||
coordinator: AutarcoDataUpdateCoordinator,
|
||||
description: AutarcoBatterySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Autarco sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
"""Initialize Autarco battery sensor."""
|
||||
super().__init__(coordinator, description)
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.account_site.site_id}_battery_{description.key}"
|
||||
)
|
||||
@@ -239,13 +249,10 @@ class AutarcoBatterySensorEntity(
|
||||
return self.entity_description.value_fn(self.coordinator.data.battery)
|
||||
|
||||
|
||||
class AutarcoSolarSensorEntity(
|
||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
class AutarcoSolarSensorEntity(AutarcoSensorBase):
|
||||
"""Defines an Autarco solar sensor."""
|
||||
|
||||
entity_description: AutarcoSolarSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -253,10 +260,8 @@ class AutarcoSolarSensorEntity(
|
||||
coordinator: AutarcoDataUpdateCoordinator,
|
||||
description: AutarcoSolarSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Autarco sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
"""Initialize Autarco solar sensor."""
|
||||
super().__init__(coordinator, description)
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.account_site.site_id}_solar_{description.key}"
|
||||
)
|
||||
@@ -273,13 +278,10 @@ class AutarcoSolarSensorEntity(
|
||||
return self.entity_description.value_fn(self.coordinator.data.solar)
|
||||
|
||||
|
||||
class AutarcoInverterSensorEntity(
|
||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
class AutarcoInverterSensorEntity(AutarcoSensorBase):
|
||||
"""Defines an Autarco inverter sensor."""
|
||||
|
||||
entity_description: AutarcoInverterSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -288,10 +290,8 @@ class AutarcoInverterSensorEntity(
|
||||
description: AutarcoInverterSensorEntityDescription,
|
||||
serial_number: str,
|
||||
) -> None:
|
||||
"""Initialize Autarco sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
"""Initialize Autarco inverter sensor."""
|
||||
super().__init__(coordinator, description)
|
||||
self._serial_number = serial_number
|
||||
self._attr_unique_id = f"{serial_number}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@@ -131,6 +131,7 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
|
||||
"lawn_mower",
|
||||
"light",
|
||||
"media_player",
|
||||
"switch",
|
||||
"text",
|
||||
"vacuum",
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@kaareseras"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/azure_data_explorer",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["azure"],
|
||||
"requirements": ["azure-kusto-ingest==4.5.1", "azure-kusto-data[aio]==4.5.1"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@timmo001"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/azure_devops",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioazuredevops"],
|
||||
"requirements": ["aioazuredevops==2.2.2"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@eavanvalkenburg"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/azure_event_hub",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["azure"],
|
||||
"requirements": ["azure-eventhub==5.11.1"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@bdraco", "@jfroy"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/baf",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["aiobafi6==0.9.0"],
|
||||
"zeroconf": [
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/balboa",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pybalboa"],
|
||||
"requirements": ["pybalboa==1.1.3"]
|
||||
|
||||
@@ -22,6 +22,7 @@ class BeoSource:
|
||||
NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio")
|
||||
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
|
||||
TIDAL: Final[Source] = Source(name="Tidal", id="tidal")
|
||||
TV: Final[Source] = Source(name="TV", id="tv")
|
||||
UNKNOWN: Final[Source] = Source(name="Unknown Source", id="unknown")
|
||||
URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer")
|
||||
|
||||
@@ -55,12 +56,13 @@ BEO_REPEAT_TO_HA: dict[str, RepeatMode] = {
|
||||
class BeoMediaType(StrEnum):
|
||||
"""Bang & Olufsen specific media types."""
|
||||
|
||||
FAVOURITE = "favourite"
|
||||
DEEZER = "deezer"
|
||||
FAVOURITE = "favourite"
|
||||
OVERLAY_TTS = "overlay_tts"
|
||||
RADIO = "radio"
|
||||
TIDAL = "tidal"
|
||||
TTS = "provider"
|
||||
OVERLAY_TTS = "overlay_tts"
|
||||
TV = "tv"
|
||||
|
||||
|
||||
class BeoModel(StrEnum):
|
||||
|
||||
@@ -218,6 +218,7 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
||||
self._sources: dict[str, str] = {}
|
||||
self._state: str = MediaPlayerState.IDLE
|
||||
self._video_sources: dict[str, str] = {}
|
||||
self._video_source_id_map: dict[str, str] = {}
|
||||
self._sound_modes: dict[str, int] = {}
|
||||
|
||||
# Beolink compatible sources
|
||||
@@ -355,6 +356,9 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
||||
and menu_item.label != "TV"
|
||||
):
|
||||
self._video_sources[key] = menu_item.label
|
||||
self._video_source_id_map[
|
||||
menu_item.content.content_uri.removeprefix("tv://")
|
||||
] = menu_item.label
|
||||
|
||||
# Combine the source dicts
|
||||
self._sources = self._audio_sources | self._video_sources
|
||||
@@ -627,10 +631,11 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
||||
def media_content_type(self) -> MediaType | str | None:
|
||||
"""Return the current media type."""
|
||||
content_type = {
|
||||
BeoSource.URI_STREAMER.id: MediaType.URL,
|
||||
BeoSource.DEEZER.id: BeoMediaType.DEEZER,
|
||||
BeoSource.TIDAL.id: BeoMediaType.TIDAL,
|
||||
BeoSource.NET_RADIO.id: BeoMediaType.RADIO,
|
||||
BeoSource.TIDAL.id: BeoMediaType.TIDAL,
|
||||
BeoSource.TV.id: BeoMediaType.TV,
|
||||
BeoSource.URI_STREAMER.id: MediaType.URL,
|
||||
}
|
||||
# Hard to determine content type.
|
||||
if self._source_change.id in content_type:
|
||||
@@ -690,7 +695,11 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
||||
|
||||
@property
|
||||
def source(self) -> str | None:
|
||||
"""Return the current audio source."""
|
||||
"""Return the current audio/video source."""
|
||||
# Associate TV content ID with a video source
|
||||
if self.media_content_id in self._video_source_id_map:
|
||||
return self._video_source_id_map[self.media_content_id]
|
||||
|
||||
return self._source_change.name
|
||||
|
||||
@property
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@bbx-a", "@swistakm"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/blebox",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["blebox_uniapi"],
|
||||
"requirements": ["blebox-uniapi==2.5.0"],
|
||||
|
||||
@@ -64,6 +64,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BlinkConfigEntry) -> b
|
||||
if entry.version == 2:
|
||||
await _reauth_flow_wrapper(hass, entry, data)
|
||||
return False
|
||||
if entry.version == 3:
|
||||
# Migrate device_id to hardware_id for blinkpy 0.25.x OAuth2 compatibility
|
||||
if "device_id" in data:
|
||||
data["hardware_id"] = data.pop("device_id")
|
||||
hass.config_entries.async_update_entry(entry, data=data, version=4)
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DEVICE_ID, DOMAIN
|
||||
from .const import DOMAIN, HARDWARE_ID
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -43,7 +43,7 @@ async def _send_blink_2fa_pin(blink: Blink, pin: str | None) -> bool:
|
||||
class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a Blink config flow."""
|
||||
|
||||
VERSION = 3
|
||||
VERSION = 4
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the blink flow."""
|
||||
@@ -53,7 +53,7 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
async def _handle_user_input(self, user_input: dict[str, Any]):
|
||||
"""Handle user input."""
|
||||
self.auth = Auth(
|
||||
{**user_input, "device_id": DEVICE_ID},
|
||||
{**user_input, "hardware_id": HARDWARE_ID},
|
||||
no_prompt=True,
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "blink"
|
||||
DEVICE_ID = "Home Assistant"
|
||||
HARDWARE_ID = "Home Assistant"
|
||||
|
||||
CONF_MIGRATE = "migrate"
|
||||
CONF_CAMERA = "camera"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "blink",
|
||||
"name": "Blink",
|
||||
"codeowners": ["@fronzbot", "@mkmer"],
|
||||
"codeowners": ["@fronzbot"],
|
||||
"config_flow": true,
|
||||
"dhcp": [
|
||||
{
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/blink",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["blinkpy"],
|
||||
"requirements": ["blinkpy==0.25.1"]
|
||||
|
||||
@@ -13,32 +13,25 @@ from bluecurrent_api.exceptions import (
|
||||
RequestLimitReached,
|
||||
WebsocketError,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.const import CONF_API_TOKEN, CONF_DEVICE_ID, Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
BCU_APP,
|
||||
CHARGEPOINT_SETTINGS,
|
||||
CHARGEPOINT_STATUS,
|
||||
CHARGING_CARD_ID,
|
||||
DOMAIN,
|
||||
EVSE_ID,
|
||||
LOGGER,
|
||||
PLUG_AND_CHARGE,
|
||||
SERVICE_START_CHARGE_SESSION,
|
||||
VALUE,
|
||||
)
|
||||
from .services import async_setup_services
|
||||
|
||||
type BlueCurrentConfigEntry = ConfigEntry[Connector]
|
||||
|
||||
@@ -54,13 +47,12 @@ VALUE_TYPES = [CHARGEPOINT_STATUS, CHARGEPOINT_SETTINGS]
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
SERVICE_START_CHARGE_SESSION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DEVICE_ID): cv.string,
|
||||
# When no charging card is provided, use no charging card (BCU_APP = no charging card).
|
||||
vol.Optional(CHARGING_CARD_ID, default=BCU_APP): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Blue Current."""
|
||||
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -88,66 +80,6 @@ async def async_setup_entry(
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Blue Current."""
|
||||
|
||||
async def start_charge_session(service_call: ServiceCall) -> None:
|
||||
"""Start a charge session with the provided device and charge card ID."""
|
||||
# When no charge card is provided, use the default charge card set in the config flow.
|
||||
charging_card_id = service_call.data[CHARGING_CARD_ID]
|
||||
device_id = service_call.data[CONF_DEVICE_ID]
|
||||
|
||||
# Get the device based on the given device ID.
|
||||
device = dr.async_get(hass).devices.get(device_id)
|
||||
|
||||
if device is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN, translation_key="invalid_device_id"
|
||||
)
|
||||
|
||||
blue_current_config_entry: ConfigEntry | None = None
|
||||
|
||||
for config_entry_id in device.config_entries:
|
||||
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||
if not config_entry or config_entry.domain != DOMAIN:
|
||||
# Not the blue_current config entry.
|
||||
continue
|
||||
|
||||
if config_entry.state is not ConfigEntryState.LOADED:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN, translation_key="config_entry_not_loaded"
|
||||
)
|
||||
|
||||
blue_current_config_entry = config_entry
|
||||
break
|
||||
|
||||
if not blue_current_config_entry:
|
||||
# The device is not connected to a valid blue_current config entry.
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN, translation_key="no_config_entry"
|
||||
)
|
||||
|
||||
connector = blue_current_config_entry.runtime_data
|
||||
|
||||
# Get the evse_id from the identifier of the device.
|
||||
evse_id = next(
|
||||
identifier[1]
|
||||
for identifier in device.identifiers
|
||||
if identifier[0] == DOMAIN
|
||||
)
|
||||
|
||||
await connector.client.start_session(evse_id, charging_card_id)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_START_CHARGE_SESSION,
|
||||
start_charge_session,
|
||||
SERVICE_START_CHARGE_SESSION_SCHEMA,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, config_entry: BlueCurrentConfigEntry
|
||||
) -> bool:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@gleeuwen", "@NickKoepr", "@jtodorova23"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/blue_current",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["bluecurrent_api"],
|
||||
"requirements": ["bluecurrent-api==1.3.2"]
|
||||
|
||||
79
homeassistant/components/blue_current/services.py
Normal file
79
homeassistant/components/blue_current/services.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""The Blue Current integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.const import CONF_DEVICE_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
|
||||
from .const import BCU_APP, CHARGING_CARD_ID, DOMAIN, SERVICE_START_CHARGE_SESSION
|
||||
|
||||
SERVICE_START_CHARGE_SESSION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DEVICE_ID): cv.string,
|
||||
# When no charging card is provided, use no charging card (BCU_APP = no charging card).
|
||||
vol.Optional(CHARGING_CARD_ID, default=BCU_APP): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def start_charge_session(service_call: ServiceCall) -> None:
|
||||
"""Start a charge session with the provided device and charge card ID."""
|
||||
# When no charge card is provided, use the default charge card set in the config flow.
|
||||
charging_card_id = service_call.data[CHARGING_CARD_ID]
|
||||
device_id = service_call.data[CONF_DEVICE_ID]
|
||||
|
||||
# Get the device based on the given device ID.
|
||||
device = dr.async_get(service_call.hass).devices.get(device_id)
|
||||
|
||||
if device is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN, translation_key="invalid_device_id"
|
||||
)
|
||||
|
||||
blue_current_config_entry: ConfigEntry | None = None
|
||||
|
||||
for config_entry_id in device.config_entries:
|
||||
config_entry = service_call.hass.config_entries.async_get_entry(config_entry_id)
|
||||
if not config_entry or config_entry.domain != DOMAIN:
|
||||
# Not the blue_current config entry.
|
||||
continue
|
||||
|
||||
if config_entry.state is not ConfigEntryState.LOADED:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN, translation_key="config_entry_not_loaded"
|
||||
)
|
||||
|
||||
blue_current_config_entry = config_entry
|
||||
break
|
||||
|
||||
if not blue_current_config_entry:
|
||||
# The device is not connected to a valid blue_current config entry.
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN, translation_key="no_config_entry"
|
||||
)
|
||||
|
||||
connector = blue_current_config_entry.runtime_data
|
||||
|
||||
# Get the evse_id from the identifier of the device.
|
||||
evse_id = next(
|
||||
identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN
|
||||
)
|
||||
|
||||
await connector.client.start_session(evse_id, charging_card_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Register the services."""
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_START_CHARGE_SESSION,
|
||||
start_charge_session,
|
||||
SERVICE_START_CHARGE_SESSION_SCHEMA,
|
||||
)
|
||||
@@ -11,6 +11,7 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bluemaestro",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bluemaestro-ble==0.4.1"]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"codeowners": ["@thrawnarn", "@LouisChrist"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/bluesound",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["pyblu==2.0.5"],
|
||||
"zeroconf": [
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@gerard33", "@rikroe"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bimmer_connected"],
|
||||
"requirements": ["bimmer-connected[china]==0.17.3"]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bond",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["bond_async"],
|
||||
"requirements": ["bond-async==0.2.1"],
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"codeowners": ["@tschamm"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/bosch_shc",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["boschshcpy"],
|
||||
"requirements": ["boschshcpy==0.2.107"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@gjohansson-ST"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/brottsplatskartan",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["brottsplatskartan"],
|
||||
"requirements": ["brottsplatskartan==1.0.5"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@eavanvalkenburg"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/brunt",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["brunt"],
|
||||
"requirements": ["brunt==1.2.0"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@mjj4791", "@ties", "@Robbie1221"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/buienradar",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["buienradar", "vincenty"],
|
||||
"requirements": ["buienradar==1.0.6"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/caldav",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["caldav", "vobject"],
|
||||
"requirements": ["caldav==2.1.0", "icalendar==6.3.1", "vobject==0.9.9"]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["ffmpeg"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/canary",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["canary"],
|
||||
"requirements": ["py-canary==0.5.4"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@ocalvo"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/ccm15",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["py_ccm15==0.1.2"]
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"codeowners": ["@jjlawren"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cert_expiry",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@ludeeus", "@ctalkington"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cloudflare",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pycfdns"],
|
||||
"requirements": ["pycfdns==3.0.0"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@tombrien"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/coinbase",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["coinbase"],
|
||||
"requirements": ["coinbase-advanced-py==1.2.2"]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@lawtancool"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/control4",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyControl4"],
|
||||
"requirements": ["pyControl4==1.5.0"],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"codeowners": ["@ol-iver", "@starkillerOG"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["denonavr"],
|
||||
"requirements": ["denonavr==1.2.0"],
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["googleapiclient"],
|
||||
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==11.1.0"]
|
||||
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["google_air_quality_api"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["google_air_quality_api==2.0.0"]
|
||||
"requirements": ["google_air_quality_api==2.0.2"]
|
||||
}
|
||||
|
||||
@@ -88,16 +88,16 @@
|
||||
"1b_good_air_quality": "1B - Good air quality",
|
||||
"2_cyan": "2 - Cyan",
|
||||
"2_light_green": "2 - Light green",
|
||||
"2_orange": "4 - Orange",
|
||||
"2_red": "5 - Red",
|
||||
"2_yellow": "3 - Yellow",
|
||||
"2a_acceptable_air_quality": "2A - Acceptable air quality",
|
||||
"2b_acceptable_air_quality": "2B - Acceptable air quality",
|
||||
"3_green": "3 - Green",
|
||||
"3_yellow": "3 - Yellow",
|
||||
"3a_aggravated_air_quality": "3A - Aggravated air quality",
|
||||
"3b_bad_air_quality": "3B - Bad air quality",
|
||||
"4_orange": "4 - Orange",
|
||||
"4_yellow_watch": "4 - Yellow/Watch",
|
||||
"5_orange_alert": "5 - Orange/Alert",
|
||||
"5_red": "5 - Red",
|
||||
"6_red_alert": "6 - Red/Alert+",
|
||||
"10_33": "10-33% of guideline",
|
||||
"33_66": "33-66% of guideline",
|
||||
|
||||
@@ -27,6 +27,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eBatChargeToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_charge_lifetime",
|
||||
@@ -42,6 +43,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eBatDisChargeToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_discharge_lifetime",
|
||||
@@ -57,6 +59,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="epvToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_solar_generation_lifetime",
|
||||
@@ -72,6 +75,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pDischarge1",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_voltage",
|
||||
@@ -101,6 +105,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="elocalLoadToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption_lifetime",
|
||||
@@ -116,6 +121,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="etoGridToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_export_to_grid_lifetime",
|
||||
@@ -132,6 +138,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="chargePower",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption",
|
||||
@@ -139,6 +146,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pLocalLoad",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_wattage_pv_1",
|
||||
@@ -146,6 +154,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pPv1",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_wattage_pv_2",
|
||||
@@ -153,6 +162,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pPv2",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_wattage_pv_all",
|
||||
@@ -160,6 +170,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_export_to_grid",
|
||||
@@ -167,6 +178,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pactogrid",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_import_from_grid",
|
||||
@@ -174,6 +186,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pactouser",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_discharge_kw",
|
||||
@@ -181,6 +194,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pdisCharge1",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_grid_voltage",
|
||||
@@ -196,6 +210,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eCharge",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption_solar_today",
|
||||
@@ -203,6 +218,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eChargeToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_self_consumption_today",
|
||||
@@ -210,6 +226,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eChargeToday1",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption_battery_today",
|
||||
@@ -217,6 +234,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="echarge1",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_import_from_grid_today",
|
||||
@@ -224,6 +242,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="etouser",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
# This sensor is manually created using the most recent X-Axis value from the chartData
|
||||
GrowattSensorEntityDescription(
|
||||
|
||||
@@ -79,6 +79,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv1",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -122,6 +123,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv2",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -165,6 +167,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv3",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -208,6 +211,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv4",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -234,6 +238,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -258,6 +263,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pac",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -323,6 +329,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc1DischargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_1_discharge_total",
|
||||
@@ -339,6 +346,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc2DischargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_2_discharge_total",
|
||||
@@ -372,6 +380,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc1ChargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_1_charge_total",
|
||||
@@ -388,6 +397,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc2ChargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_2_charge_total",
|
||||
@@ -445,6 +455,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pacToLocalLoad",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -453,6 +464,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pacToUserTotal",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -461,6 +473,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pacToGridTotal",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -545,6 +558,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="psystem",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -553,6 +567,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pself",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -50,5 +50,6 @@ TOTAL_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="nominalPower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylamarzocco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylamarzocco==2.2.3"]
|
||||
"requirements": ["pylamarzocco==2.2.4"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Support for LCN climate control."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Iterable
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
@@ -172,14 +171,14 @@ class LcnClimate(LcnEntity, ClimateEntity):
|
||||
async def async_update(self) -> None:
|
||||
"""Update the state of the entity."""
|
||||
self._attr_available = any(
|
||||
await asyncio.gather(
|
||||
self.device_connection.request_status_variable(
|
||||
[
|
||||
await self.device_connection.request_status_variable(
|
||||
self.variable, SCAN_INTERVAL.seconds
|
||||
),
|
||||
self.device_connection.request_status_variable(
|
||||
await self.device_connection.request_status_variable(
|
||||
self.setpoint, SCAN_INTERVAL.seconds
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
def input_received(self, input_obj: InputType) -> None:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Support for LCN covers."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine, Iterable
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
@@ -134,14 +133,14 @@ class LcnOutputsCover(LcnEntity, CoverEntity):
|
||||
"""Update the state of the entity."""
|
||||
if not self.device_connection.is_group:
|
||||
self._attr_available = any(
|
||||
await asyncio.gather(
|
||||
self.device_connection.request_status_output(
|
||||
[
|
||||
await self.device_connection.request_status_output(
|
||||
pypck.lcn_defs.OutputPort["OUTPUTUP"], SCAN_INTERVAL.seconds
|
||||
),
|
||||
self.device_connection.request_status_output(
|
||||
await self.device_connection.request_status_output(
|
||||
pypck.lcn_defs.OutputPort["OUTPUTDOWN"], SCAN_INTERVAL.seconds
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
def input_received(self, input_obj: InputType) -> None:
|
||||
@@ -274,7 +273,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
|
||||
self.motor, self.positioning_mode, SCAN_INTERVAL.seconds
|
||||
)
|
||||
)
|
||||
self._attr_available = any(await asyncio.gather(*coros))
|
||||
self._attr_available = any([await coro for coro in coros])
|
||||
|
||||
def input_received(self, input_obj: InputType) -> None:
|
||||
"""Set cover states when LCN input object (command) is received."""
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["ical"],
|
||||
"requirements": ["ical==11.1.0"]
|
||||
"requirements": ["ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["ical==11.1.0"]
|
||||
"requirements": ["ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -499,4 +499,53 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.WindowCovering.Attributes.ConfigStatus,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="ThermostatRemoteSensing_LocalTemperature",
|
||||
translation_key="thermostat_remote_sensing_local_temperature",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
# LocalTemperature bit from RemoteSensing attribute
|
||||
device_to_ha=lambda x: bool(
|
||||
x
|
||||
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kLocalTemperature # Calculated Local Temperature is derived from a remote node
|
||||
),
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
|
||||
allow_multi=True,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="ThermostatRemoteSensing_OutdoorTemperature",
|
||||
translation_key="thermostat_remote_sensing_outdoor_temperature",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
# OutdoorTemperature bit from RemoteSensing attribute
|
||||
device_to_ha=lambda x: bool(
|
||||
x
|
||||
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kOutdoorTemperature # OutdoorTemperature is derived from a remote node
|
||||
),
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
|
||||
allow_multi=True,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="ThermostatRemoteSensing_Occupancy",
|
||||
translation_key="thermostat_remote_sensing_occupancy",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
# Occupancy bit from RemoteSensing attribute
|
||||
device_to_ha=lambda x: bool(
|
||||
x
|
||||
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kOccupancy # Occupancy is derived from a remote node
|
||||
),
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
|
||||
featuremap_contains=clusters.Thermostat.Bitmaps.Feature.kOccupancy,
|
||||
allow_multi=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -183,6 +183,48 @@ class MatterModeSelectEntity(MatterAttributeSelectEntity):
|
||||
self._attr_name = desc
|
||||
|
||||
|
||||
class MatterDoorLockOperatingModeSelectEntity(MatterAttributeSelectEntity):
|
||||
"""Representation of a Door Lock Operating Mode select entity.
|
||||
|
||||
This entity dynamically filters available operating modes based on the device's
|
||||
`SupportedOperatingModes` bitmap attribute. In this bitmap, bit=0 indicates a
|
||||
supported mode and bit=1 indicates unsupported (inverted from typical bitmap conventions).
|
||||
If the bitmap is unavailable, only mandatory modes are included. The mapping from
|
||||
bitmap bits to operating mode values is defined by the Matter specification.
|
||||
"""
|
||||
|
||||
entity_description: MatterMapSelectEntityDescription
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
# Get the bitmap of supported operating modes
|
||||
supported_modes_bitmap = self.get_matter_attribute_value(
|
||||
self.entity_description.list_attribute
|
||||
)
|
||||
|
||||
# Convert bitmap to list of supported mode values
|
||||
# NOTE: The Matter spec inverts the usual meaning: bit=0 means supported,
|
||||
# bit=1 means not supported, undefined bits must be 1. Mandatory modes are
|
||||
# bits 0 (Normal) and 3 (NoRemoteLockUnlock).
|
||||
num_mode_bits = supported_modes_bitmap.bit_length()
|
||||
supported_mode_values = [
|
||||
bit_position
|
||||
for bit_position in range(num_mode_bits)
|
||||
if not supported_modes_bitmap & (1 << bit_position)
|
||||
]
|
||||
|
||||
# Map supported mode values to their string representations
|
||||
self._attr_options = [
|
||||
mapped_value
|
||||
for mode_value in supported_mode_values
|
||||
if (mapped_value := self.entity_description.device_to_ha(mode_value))
|
||||
]
|
||||
|
||||
# Use base implementation to set the current option
|
||||
super()._update_from_device()
|
||||
|
||||
|
||||
class MatterListSelectEntity(MatterEntity, SelectEntity):
|
||||
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
|
||||
|
||||
@@ -594,15 +636,18 @@ DISCOVERY_SCHEMAS = [
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SELECT,
|
||||
entity_description=MatterSelectEntityDescription(
|
||||
entity_description=MatterMapSelectEntityDescription(
|
||||
key="DoorLockOperatingMode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
translation_key="door_lock_operating_mode",
|
||||
options=list(DOOR_LOCK_OPERATING_MODE_MAP.values()),
|
||||
list_attribute=clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||
device_to_ha=DOOR_LOCK_OPERATING_MODE_MAP.get,
|
||||
ha_to_device=DOOR_LOCK_OPERATING_MODE_MAP_REVERSE.get,
|
||||
),
|
||||
entity_class=MatterAttributeSelectEntity,
|
||||
required_attributes=(clusters.DoorLock.Attributes.OperatingMode,),
|
||||
entity_class=MatterDoorLockOperatingModeSelectEntity,
|
||||
required_attributes=(
|
||||
clusters.DoorLock.Attributes.OperatingMode,
|
||||
clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -89,6 +89,15 @@
|
||||
"test_in_progress": {
|
||||
"name": "Test in progress"
|
||||
},
|
||||
"thermostat_remote_sensing_local_temperature": {
|
||||
"name": "Local temperature remote sensing"
|
||||
},
|
||||
"thermostat_remote_sensing_occupancy": {
|
||||
"name": "Occupancy remote sensing"
|
||||
},
|
||||
"thermostat_remote_sensing_outdoor_temperature": {
|
||||
"name": "Outdoor temperature remote sensing"
|
||||
},
|
||||
"valve_fault_blocked": {
|
||||
"name": "Valve blocked"
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"ws_path": "WebSocket path"
|
||||
},
|
||||
"data_description": {
|
||||
"advanced_options": "Enable and select **Next** to set advanced options.",
|
||||
"advanced_options": "Enable and select **Submit** to set advanced options.",
|
||||
"broker": "The hostname or IP address of your MQTT broker.",
|
||||
"certificate": "The custom CA certificate file to validate your MQTT brokers certificate.",
|
||||
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
||||
|
||||
122
homeassistant/components/nederlandse_spoorwegen/diagnostics.py
Normal file
122
homeassistant/components/nederlandse_spoorwegen/diagnostics.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Diagnostics support for Nederlandse Spoorwegen."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NSConfigEntry
|
||||
|
||||
TO_REDACT = [
|
||||
CONF_API_KEY,
|
||||
]
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: NSConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinators_data = {}
|
||||
|
||||
# Collect data from all coordinators
|
||||
for subentry_id, coordinator in entry.runtime_data.items():
|
||||
coordinators_data[subentry_id] = {
|
||||
"coordinator_info": {
|
||||
"name": coordinator.name,
|
||||
"departure": coordinator.departure,
|
||||
"destination": coordinator.destination,
|
||||
"via": coordinator.via,
|
||||
"departure_time": coordinator.departure_time,
|
||||
},
|
||||
"route_data": {
|
||||
"trips_count": len(coordinator.data.trips) if coordinator.data else 0,
|
||||
"has_first_trip": coordinator.data.first_trip is not None
|
||||
if coordinator.data
|
||||
else False,
|
||||
"has_next_trip": coordinator.data.next_trip is not None
|
||||
if coordinator.data
|
||||
else False,
|
||||
}
|
||||
if coordinator.data
|
||||
else None,
|
||||
}
|
||||
|
||||
return {
|
||||
"entry_data": async_redact_data(entry.data, TO_REDACT),
|
||||
"coordinators": coordinators_data,
|
||||
}
|
||||
|
||||
|
||||
async def async_get_device_diagnostics(
|
||||
hass: HomeAssistant, entry: NSConfigEntry, device: DeviceEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a route."""
|
||||
# Find the coordinator for this device
|
||||
coordinator = None
|
||||
subentry_id = None
|
||||
|
||||
# Each device has an identifier (DOMAIN, subentry_id)
|
||||
for identifier in device.identifiers:
|
||||
if identifier[0] == DOMAIN:
|
||||
subentry_id = identifier[1]
|
||||
coordinator = entry.runtime_data.get(subentry_id)
|
||||
break
|
||||
|
||||
# Collect detailed diagnostics for this specific route
|
||||
device_data = {
|
||||
"device_info": {
|
||||
"subentry_id": subentry_id,
|
||||
"device_name": device.name,
|
||||
"manufacturer": device.manufacturer,
|
||||
"model": device.model,
|
||||
},
|
||||
"coordinator_info": {
|
||||
"name": coordinator.name,
|
||||
"departure": coordinator.departure,
|
||||
"destination": coordinator.destination,
|
||||
"via": coordinator.via,
|
||||
"departure_time": coordinator.departure_time,
|
||||
}
|
||||
if coordinator
|
||||
else None,
|
||||
}
|
||||
|
||||
# Add detailed trip data if available
|
||||
if coordinator and coordinator.data:
|
||||
device_data["trip_details"] = {
|
||||
"trips_count": len(coordinator.data.trips),
|
||||
"has_first_trip": coordinator.data.first_trip is not None,
|
||||
"has_next_trip": coordinator.data.next_trip is not None,
|
||||
}
|
||||
|
||||
# Add first trip details if available
|
||||
if coordinator.data.first_trip:
|
||||
first_trip = coordinator.data.first_trip
|
||||
device_data["first_trip"] = {
|
||||
"departure_time_planned": str(first_trip.departure_time_planned)
|
||||
if first_trip.departure_time_planned
|
||||
else None,
|
||||
"departure_time_actual": str(first_trip.departure_time_actual)
|
||||
if first_trip.departure_time_actual
|
||||
else None,
|
||||
"arrival_time_planned": str(first_trip.arrival_time_planned)
|
||||
if first_trip.arrival_time_planned
|
||||
else None,
|
||||
"arrival_time_actual": str(first_trip.arrival_time_actual)
|
||||
if first_trip.arrival_time_actual
|
||||
else None,
|
||||
"departure_platform_planned": first_trip.departure_platform_planned,
|
||||
"departure_platform_actual": first_trip.departure_platform_actual,
|
||||
"arrival_platform_planned": first_trip.arrival_platform_planned,
|
||||
"arrival_platform_actual": first_trip.arrival_platform_actual,
|
||||
"status": str(first_trip.status) if first_trip.status else None,
|
||||
"nr_transfers": first_trip.nr_transfers,
|
||||
"going": first_trip.going,
|
||||
}
|
||||
|
||||
return device_data
|
||||
@@ -27,6 +27,8 @@ from .const import (
|
||||
DATA_CAMERAS,
|
||||
DATA_EVENTS,
|
||||
DOMAIN,
|
||||
EVENT_TYPE_CONNECTION,
|
||||
EVENT_TYPE_DISCONNECTION,
|
||||
EVENT_TYPE_LIGHT_MODE,
|
||||
EVENT_TYPE_OFF,
|
||||
EVENT_TYPE_ON,
|
||||
@@ -123,7 +125,13 @@ class NetatmoCamera(NetatmoModuleEntity, Camera):
|
||||
"""Entity created."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
for event_type in (EVENT_TYPE_LIGHT_MODE, EVENT_TYPE_OFF, EVENT_TYPE_ON):
|
||||
for event_type in (
|
||||
EVENT_TYPE_LIGHT_MODE,
|
||||
EVENT_TYPE_OFF,
|
||||
EVENT_TYPE_ON,
|
||||
EVENT_TYPE_CONNECTION,
|
||||
EVENT_TYPE_DISCONNECTION,
|
||||
):
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
@@ -146,12 +154,19 @@ class NetatmoCamera(NetatmoModuleEntity, Camera):
|
||||
data["home_id"] == self.home.entity_id
|
||||
and data["camera_id"] == self.device.entity_id
|
||||
):
|
||||
if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
|
||||
if data[WEBHOOK_PUSH_TYPE] in (
|
||||
"NACamera-off",
|
||||
"NOCamera-off",
|
||||
"NACamera-disconnection",
|
||||
"NOCamera-disconnection",
|
||||
):
|
||||
self._attr_is_streaming = False
|
||||
self._monitoring = False
|
||||
elif data[WEBHOOK_PUSH_TYPE] in (
|
||||
"NACamera-on",
|
||||
"NOCamera-on",
|
||||
WEBHOOK_NACAMERA_CONNECTION,
|
||||
"NOCamera-connection",
|
||||
):
|
||||
self._attr_is_streaming = True
|
||||
self._monitoring = True
|
||||
|
||||
@@ -127,6 +127,9 @@ EVENT_TYPE_ALARM_STARTED = "alarm_started"
|
||||
EVENT_TYPE_DOOR_TAG_BIG_MOVE = "tag_big_move"
|
||||
EVENT_TYPE_DOOR_TAG_OPEN = "tag_open"
|
||||
EVENT_TYPE_DOOR_TAG_SMALL_MOVE = "tag_small_move"
|
||||
# Generic events
|
||||
EVENT_TYPE_CONNECTION = "connection"
|
||||
EVENT_TYPE_DISCONNECTION = "disconnection"
|
||||
EVENT_TYPE_OFF = "off"
|
||||
EVENT_TYPE_ON = "on"
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pynintendoauth", "pynintendoparental"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pynintendoauth==1.0.0", "pynintendoparental==2.1.0"]
|
||||
"requirements": ["pynintendoauth==1.0.0", "pynintendoparental==2.1.1"]
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["openai==2.9.0", "python-open-router==0.3.3"]
|
||||
"requirements": ["openai==2.11.0", "python-open-router==0.3.3"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/openai_conversation",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["openai==2.9.0"]
|
||||
"requirements": ["openai==2.11.0"]
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["python-overseerr==0.7.1"]
|
||||
"requirements": ["python-overseerr==0.8.0"]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from plugwise.constants import GwEntityData
|
||||
from plugwise import GwEntityData
|
||||
|
||||
from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST
|
||||
from homeassistant.helpers.device_registry import (
|
||||
@@ -30,37 +30,43 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseDataUpdateCoordinator]):
|
||||
super().__init__(coordinator)
|
||||
self._dev_id = device_id
|
||||
|
||||
configuration_url: str | None = None
|
||||
if entry := self.coordinator.config_entry:
|
||||
configuration_url = f"http://{entry.data[CONF_HOST]}"
|
||||
api = coordinator.api
|
||||
gateway_id = api.gateway_id
|
||||
entry = coordinator.config_entry
|
||||
|
||||
data = coordinator.data[device_id]
|
||||
# Link configuration-URL for the gateway device
|
||||
configuration_url = (
|
||||
f"http://{entry.data[CONF_HOST]}"
|
||||
if device_id == gateway_id and entry
|
||||
else None
|
||||
)
|
||||
|
||||
# Build connections set
|
||||
connections = set()
|
||||
if mac := data.get("mac_address"):
|
||||
if mac := self.device.get("mac_address"):
|
||||
connections.add((CONNECTION_NETWORK_MAC, mac))
|
||||
if mac := data.get("zigbee_mac_address"):
|
||||
connections.add((CONNECTION_ZIGBEE, mac))
|
||||
if zigbee_mac := self.device.get("zigbee_mac_address"):
|
||||
connections.add((CONNECTION_ZIGBEE, zigbee_mac))
|
||||
|
||||
# Set base device info
|
||||
self._attr_device_info = DeviceInfo(
|
||||
configuration_url=configuration_url,
|
||||
identifiers={(DOMAIN, device_id)},
|
||||
connections=connections,
|
||||
manufacturer=data.get("vendor"),
|
||||
model=data.get("model"),
|
||||
model_id=data.get("model_id"),
|
||||
name=coordinator.api.smile.name,
|
||||
sw_version=data.get("firmware"),
|
||||
hw_version=data.get("hardware"),
|
||||
manufacturer=self.device.get("vendor"),
|
||||
model=self.device.get("model"),
|
||||
model_id=self.device.get("model_id"),
|
||||
name=api.smile.name,
|
||||
sw_version=self.device.get("firmware"),
|
||||
hw_version=self.device.get("hardware"),
|
||||
)
|
||||
|
||||
if device_id != coordinator.api.gateway_id:
|
||||
# Add extra info if not the gateway device
|
||||
if device_id != gateway_id:
|
||||
self._attr_device_info.update(
|
||||
{
|
||||
ATTR_NAME: data.get(ATTR_NAME),
|
||||
ATTR_VIA_DEVICE: (
|
||||
DOMAIN,
|
||||
str(self.coordinator.api.gateway_id),
|
||||
),
|
||||
ATTR_NAME: self.device.get(ATTR_NAME),
|
||||
ATTR_VIA_DEVICE: (DOMAIN, gateway_id),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ SELECT_TYPES = (
|
||||
PlugwiseSelectEntityDescription(
|
||||
key=SELECT_SCHEDULE,
|
||||
translation_key=SELECT_SCHEDULE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
options_key="available_schedules",
|
||||
),
|
||||
PlugwiseSelectEntityDescription(
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["ical"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["ical==11.1.0"]
|
||||
"requirements": ["ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -394,7 +394,14 @@ class RoborockWashingMachineUpdateCoordinator(
|
||||
async def _async_update_data(
|
||||
self,
|
||||
) -> dict[RoborockZeoProtocol, StateType]:
|
||||
try:
|
||||
return await self.api.query_values(self.request_protocols)
|
||||
except RoborockException as ex:
|
||||
_LOGGER.debug("Failed to update washing machine data: %s", ex)
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_data_fail",
|
||||
) from ex
|
||||
|
||||
|
||||
class RoborockWetDryVacUpdateCoordinator(
|
||||
@@ -425,4 +432,11 @@ class RoborockWetDryVacUpdateCoordinator(
|
||||
async def _async_update_data(
|
||||
self,
|
||||
) -> dict[RoborockDyadDataProtocol, StateType]:
|
||||
try:
|
||||
return await self.api.query_values(self.request_protocols)
|
||||
except RoborockException as ex:
|
||||
_LOGGER.debug("Failed to update wet dry vac data: %s", ex)
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_data_fail",
|
||||
) from ex
|
||||
|
||||
@@ -416,8 +416,8 @@ def warn_dip(
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Entity %s %shas state class total_increasing, but its state is not"
|
||||
" strictly increasing. Triggered by state %s (%s) with last_updated set"
|
||||
" to %s. Please %s"
|
||||
" strictly increasing. Triggered by state %s (previous state: %s) with"
|
||||
" last_updated set to %s. Please %s"
|
||||
),
|
||||
entity_id,
|
||||
f"from integration {domain} " if domain else "",
|
||||
|
||||
@@ -63,6 +63,7 @@ from .repairs import (
|
||||
async_manage_open_wifi_ap_issue,
|
||||
async_manage_outbound_websocket_incorrectly_enabled_issue,
|
||||
)
|
||||
from .services import async_setup_services
|
||||
from .utils import (
|
||||
async_create_issue_unsupported_firmware,
|
||||
async_migrate_rpc_virtual_components_unique_ids,
|
||||
@@ -117,6 +118,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
if (conf := config.get(DOMAIN)) is not None:
|
||||
hass.data[DOMAIN] = {CONF_COAP_PORT: conf[CONF_COAP_PORT]}
|
||||
|
||||
async_setup_services(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -343,3 +343,6 @@ MODEL_FRANKEVER_IRRIGATION_CONTROLLER = "Irrigation"
|
||||
ROLE_GENERIC = "generic"
|
||||
|
||||
TRV_CHANNEL = 0
|
||||
|
||||
ATTR_KEY = "key"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
@@ -105,5 +105,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_kvs_value": {
|
||||
"service": "mdi:import"
|
||||
},
|
||||
"set_kvs_value": {
|
||||
"service": "mdi:export"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
action-setup: done
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
docs-actions: done
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
@@ -24,9 +20,7 @@ rules:
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: done
|
||||
docs-installation-parameters: done
|
||||
|
||||
170
homeassistant/components/shelly/services.py
Normal file
170
homeassistant/components/shelly/services.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""Support for services."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from aioshelly.const import RPC_GENERATIONS
|
||||
from aioshelly.exceptions import DeviceConnectionError, RpcCallError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_DEVICE_ID
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
ServiceResponse,
|
||||
SupportsResponse,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.util.json import JsonValueType
|
||||
|
||||
from .const import ATTR_KEY, ATTR_VALUE, CONF_SLEEP_PERIOD, DOMAIN
|
||||
from .coordinator import ShellyConfigEntry
|
||||
from .utils import get_device_entry_gen
|
||||
|
||||
SERVICE_GET_KVS_VALUE = "get_kvs_value"
|
||||
SERVICE_SET_KVS_VALUE = "set_kvs_value"
|
||||
SERVICE_GET_KVS_VALUE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): cv.string,
|
||||
vol.Required(ATTR_KEY): str,
|
||||
}
|
||||
)
|
||||
SERVICE_SET_KVS_VALUE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): cv.string,
|
||||
vol.Required(ATTR_KEY): str,
|
||||
vol.Required(ATTR_VALUE): vol.Any(str, int, float, bool, dict, list, None),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_config_entry_for_service_call(
|
||||
call: ServiceCall,
|
||||
) -> ShellyConfigEntry:
|
||||
"""Get the config entry related to a service call (by device ID)."""
|
||||
device_registry = dr.async_get(call.hass)
|
||||
device_id = call.data[ATTR_DEVICE_ID]
|
||||
|
||||
if (device_entry := device_registry.async_get(device_id)) is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_device_id",
|
||||
translation_placeholders={"device_id": device_id},
|
||||
)
|
||||
|
||||
for entry_id in device_entry.config_entries:
|
||||
config_entry = call.hass.config_entries.async_get_entry(entry_id)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert config_entry
|
||||
|
||||
if config_entry.domain != DOMAIN:
|
||||
continue
|
||||
if config_entry.state is not ConfigEntryState.LOADED:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="entry_not_loaded",
|
||||
translation_placeholders={"device": config_entry.title},
|
||||
)
|
||||
if get_device_entry_gen(config_entry) not in RPC_GENERATIONS:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="kvs_not_supported",
|
||||
translation_placeholders={"device": config_entry.title},
|
||||
)
|
||||
if config_entry.data.get(CONF_SLEEP_PERIOD, 0) > 0:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="kvs_not_supported",
|
||||
translation_placeholders={"device": config_entry.title},
|
||||
)
|
||||
return config_entry
|
||||
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_not_found",
|
||||
translation_placeholders={"device_id": device_id},
|
||||
)
|
||||
|
||||
|
||||
async def _async_execute_action(
|
||||
call: ServiceCall, method: str, args: tuple
|
||||
) -> dict[str, Any]:
|
||||
"""Execute action on the device."""
|
||||
config_entry = async_get_config_entry_for_service_call(call)
|
||||
|
||||
runtime_data = config_entry.runtime_data
|
||||
|
||||
if not runtime_data.rpc:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_not_initialized",
|
||||
translation_placeholders={"device": config_entry.title},
|
||||
)
|
||||
|
||||
action_method = getattr(runtime_data.rpc.device, method)
|
||||
|
||||
try:
|
||||
response = await action_method(*args)
|
||||
except RpcCallError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="rpc_call_error",
|
||||
translation_placeholders={"device": config_entry.title},
|
||||
) from err
|
||||
except DeviceConnectionError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_communication_error",
|
||||
translation_placeholders={"device": config_entry.title},
|
||||
) from err
|
||||
else:
|
||||
return cast(dict[str, Any], response)
|
||||
|
||||
|
||||
async def async_get_kvs_value(call: ServiceCall) -> ServiceResponse:
|
||||
"""Handle the get_kvs_value service call."""
|
||||
key = call.data[ATTR_KEY]
|
||||
|
||||
response = await _async_execute_action(call, "kvs_get", (key,))
|
||||
|
||||
result: dict[str, JsonValueType] = {}
|
||||
result[ATTR_VALUE] = response[ATTR_VALUE]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def async_set_kvs_value(call: ServiceCall) -> None:
|
||||
"""Handle the set_kvs_value service call."""
|
||||
await _async_execute_action(
|
||||
call, "kvs_set", (call.data[ATTR_KEY], call.data[ATTR_VALUE])
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up the services for Shelly integration."""
|
||||
for service, method, schema, response in (
|
||||
(
|
||||
SERVICE_GET_KVS_VALUE,
|
||||
async_get_kvs_value,
|
||||
SERVICE_GET_KVS_VALUE_SCHEMA,
|
||||
SupportsResponse.ONLY,
|
||||
),
|
||||
(
|
||||
SERVICE_SET_KVS_VALUE,
|
||||
async_set_kvs_value,
|
||||
SERVICE_SET_KVS_VALUE_SCHEMA,
|
||||
SupportsResponse.NONE,
|
||||
),
|
||||
):
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
service,
|
||||
method,
|
||||
schema=schema,
|
||||
supports_response=response,
|
||||
)
|
||||
27
homeassistant/components/shelly/services.yaml
Normal file
27
homeassistant/components/shelly/services.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
get_kvs_value:
|
||||
fields:
|
||||
device_id:
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: shelly
|
||||
key:
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
|
||||
set_kvs_value:
|
||||
fields:
|
||||
device_id:
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: shelly
|
||||
key:
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
required: true
|
||||
selector:
|
||||
object:
|
||||
@@ -603,6 +603,9 @@
|
||||
"auth_error": {
|
||||
"message": "Authentication failed for {device}, please update your credentials"
|
||||
},
|
||||
"config_entry_not_found": {
|
||||
"message": "Config entry for device ID {device_id} not found"
|
||||
},
|
||||
"device_communication_action_error": {
|
||||
"message": "Device communication error occurred while calling action for {entity} of {device}"
|
||||
},
|
||||
@@ -612,12 +615,24 @@
|
||||
"device_not_found": {
|
||||
"message": "{device} not found while configuring device automation triggers"
|
||||
},
|
||||
"device_not_initialized": {
|
||||
"message": "{device} not initialized"
|
||||
},
|
||||
"entry_not_loaded": {
|
||||
"message": "Config entry not loaded for {device}"
|
||||
},
|
||||
"firmware_unsupported": {
|
||||
"message": "{device} is running an unsupported firmware, please update the firmware"
|
||||
},
|
||||
"invalid_device_id": {
|
||||
"message": "Invalid device ID specified: {device_id}"
|
||||
},
|
||||
"invalid_trigger": {
|
||||
"message": "Invalid device automation trigger (type, subtype): {trigger}"
|
||||
},
|
||||
"kvs_not_supported": {
|
||||
"message": "{device} does not support KVS"
|
||||
},
|
||||
"ota_update_connection_error": {
|
||||
"message": "Device communication error occurred while triggering OTA update for {device}"
|
||||
},
|
||||
@@ -627,6 +642,9 @@
|
||||
"rpc_call_action_error": {
|
||||
"message": "RPC call error occurred while calling action for {entity} of {device}"
|
||||
},
|
||||
"rpc_call_error": {
|
||||
"message": "RPC call error occurred for {device}"
|
||||
},
|
||||
"update_error": {
|
||||
"message": "An error occurred while retrieving data from {device}"
|
||||
},
|
||||
@@ -748,5 +766,39 @@
|
||||
"manual": "Enter address manually"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_kvs_value": {
|
||||
"description": "Get a value from the device's Key-Value Storage.",
|
||||
"fields": {
|
||||
"device_id": {
|
||||
"description": "The ID of the Shelly device to get the KVS value from.",
|
||||
"name": "Device"
|
||||
},
|
||||
"key": {
|
||||
"description": "The name of the key for which the KVS value will be retrieved.",
|
||||
"name": "Key"
|
||||
}
|
||||
},
|
||||
"name": "Get KVS value"
|
||||
},
|
||||
"set_kvs_value": {
|
||||
"description": "Set a value in the device's Key-Value Storage.",
|
||||
"fields": {
|
||||
"device_id": {
|
||||
"description": "The ID of the Shelly device to set the KVS value.",
|
||||
"name": "Device"
|
||||
},
|
||||
"key": {
|
||||
"description": "The name of the key under which the KVS value will be stored.",
|
||||
"name": "Key"
|
||||
},
|
||||
"value": {
|
||||
"description": "Value to set.",
|
||||
"name": "Value"
|
||||
}
|
||||
},
|
||||
"name": "Set KVS value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysmartthings==3.5.0"]
|
||||
"requirements": ["pysmartthings==3.5.1"]
|
||||
}
|
||||
|
||||
@@ -155,7 +155,10 @@ class SqueezeboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.exception("Unknown exception while validating connection")
|
||||
return "unknown"
|
||||
|
||||
if "uuid" in status:
|
||||
if "uuid" not in status:
|
||||
_LOGGER.exception("Discovered server did not provide a uuid")
|
||||
return "missing_uuid"
|
||||
|
||||
await self.async_set_unique_id(status["uuid"])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"missing_uuid": "Your LMS did not provide a unique identifier and is not compatible with this integration. Please check and update your LMS version.",
|
||||
"no_server_found": "Could not automatically discover server.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
|
||||
@@ -29,5 +29,13 @@
|
||||
"turn_on": {
|
||||
"service": "mdi:toggle-switch-variant"
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"turned_off": {
|
||||
"trigger": "mdi:toggle-switch-variant-off"
|
||||
},
|
||||
"turned_on": {
|
||||
"trigger": "mdi:toggle-switch-variant"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"common": {
|
||||
"trigger_behavior_description": "The behavior of the targeted switches to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
},
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"toggle": "[%key:common::device_automation::action_type::toggle%]",
|
||||
@@ -41,6 +45,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"trigger_behavior": {
|
||||
"options": {
|
||||
"any": "Any",
|
||||
"first": "First",
|
||||
"last": "Last"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"toggle": {
|
||||
"description": "Toggles a switch on/off.",
|
||||
@@ -55,5 +68,27 @@
|
||||
"name": "[%key:common::action::turn_on%]"
|
||||
}
|
||||
},
|
||||
"title": "Switch"
|
||||
"title": "Switch",
|
||||
"triggers": {
|
||||
"turned_off": {
|
||||
"description": "Triggers after one or more switches turn off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::switch::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::switch::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Switch turned off"
|
||||
},
|
||||
"turned_on": {
|
||||
"description": "Triggers after one or more switches turn on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::switch::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::switch::common::trigger_behavior_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Switch turned on"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
homeassistant/components/switch/trigger.py
Normal file
17
homeassistant/components/switch/trigger.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Provides triggers for switch platform."""
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.trigger import Trigger, make_entity_state_trigger
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
TRIGGERS: dict[str, type[Trigger]] = {
|
||||
"turned_on": make_entity_state_trigger(DOMAIN, STATE_ON),
|
||||
"turned_off": make_entity_state_trigger(DOMAIN, STATE_OFF),
|
||||
}
|
||||
|
||||
|
||||
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
|
||||
"""Return the triggers for switch platform."""
|
||||
return TRIGGERS
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user