mirror of
https://github.com/home-assistant/core.git
synced 2026-05-06 08:36:42 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 813fa922e2 | |||
| 4b28928702 | |||
| 859ce55c96 | |||
| 9a9f19cb9e | |||
| d8b1bfb268 |
Generated
+2
-2
@@ -851,8 +851,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/input_select/ @home-assistant/core
|
||||
/homeassistant/components/input_text/ @home-assistant/core
|
||||
/tests/components/input_text/ @home-assistant/core
|
||||
/homeassistant/components/insteon/ @teharris1 @ssyrell
|
||||
/tests/components/insteon/ @teharris1 @ssyrell
|
||||
/homeassistant/components/insteon/ @teharris1
|
||||
/tests/components/insteon/ @teharris1
|
||||
/homeassistant/components/integration/ @dgomes
|
||||
/tests/components/integration/ @dgomes
|
||||
/homeassistant/components/intelliclima/ @dvdinth
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "sensereo",
|
||||
"name": "Sensereo",
|
||||
"iot_standards": ["matter"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "zunzunbee",
|
||||
"name": "Zunzunbee",
|
||||
"iot_standards": ["zigbee"]
|
||||
}
|
||||
@@ -147,7 +147,7 @@ class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
|
||||
@property
|
||||
def target_temperature(self) -> float:
|
||||
"""Return the target temperature."""
|
||||
return self._status.user_aircon_settings.current_setpoint
|
||||
return self._status.user_aircon_settings.temperature_setpoint_cool_c
|
||||
|
||||
@actron_air_command
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
@@ -239,7 +239,7 @@ class ActronZoneClimate(ActronAirZoneEntity, ActronAirClimateEntity):
|
||||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
"""Return the target temperature."""
|
||||
return self._zone.current_setpoint
|
||||
return self._zone.temperature_setpoint_cool_c
|
||||
|
||||
@actron_air_command
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
|
||||
@@ -36,7 +36,9 @@ def _make_detected_condition(
|
||||
) -> type[Condition]:
|
||||
"""Create a detected condition for a binary sensor device class."""
|
||||
return make_entity_state_condition(
|
||||
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)}, STATE_ON
|
||||
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)},
|
||||
STATE_ON,
|
||||
support_duration=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -45,7 +47,9 @@ def _make_cleared_condition(
|
||||
) -> type[Condition]:
|
||||
"""Create a cleared condition for a binary sensor device class."""
|
||||
return make_entity_state_condition(
|
||||
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)}, STATE_OFF
|
||||
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)},
|
||||
STATE_OFF,
|
||||
support_duration=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
|
||||
.condition_for: &condition_for
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
|
||||
# --- Unit lists for multi-unit pollutants ---
|
||||
|
||||
.co_units: &co_units
|
||||
@@ -252,7 +246,11 @@
|
||||
.condition_binary_common: &condition_binary_common
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
|
||||
is_gas_detected:
|
||||
<<: *condition_binary_common
|
||||
@@ -284,7 +282,6 @@ is_co_value:
|
||||
target: *target_co_sensor
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -299,7 +296,6 @@ is_ozone_value:
|
||||
target: *target_ozone
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -314,7 +310,6 @@ is_voc_value:
|
||||
target: *target_voc
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -329,7 +324,6 @@ is_voc_ratio_value:
|
||||
target: *target_voc_ratio
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -344,7 +338,6 @@ is_no_value:
|
||||
target: *target_no
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -359,7 +352,6 @@ is_no2_value:
|
||||
target: *target_no2
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -374,7 +366,6 @@ is_so2_value:
|
||||
target: *target_so2
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -391,7 +382,6 @@ is_co2_value:
|
||||
target: *target_co2
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -404,7 +394,6 @@ is_pm1_value:
|
||||
target: *target_pm1
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -417,7 +406,6 @@ is_pm25_value:
|
||||
target: *target_pm25
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -430,7 +418,6 @@ is_pm4_value:
|
||||
target: *target_pm4
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -443,7 +430,6 @@ is_pm10_value:
|
||||
target: *target_pm10
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -456,7 +442,6 @@ is_n2o_value:
|
||||
target: *target_n2o
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -53,9 +50,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -92,9 +86,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -107,9 +98,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -122,9 +110,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -137,9 +122,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -152,9 +134,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -167,9 +146,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -182,9 +158,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -197,9 +170,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -236,9 +206,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -251,9 +218,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -266,9 +230,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::air_quality::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.condition import (
|
||||
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR,
|
||||
Condition,
|
||||
EntityStateConditionBase,
|
||||
make_entity_state_condition,
|
||||
@@ -25,6 +26,7 @@ class EntityStateRequiredFeaturesCondition(EntityStateConditionBase):
|
||||
"""State condition."""
|
||||
|
||||
_required_features: int
|
||||
_schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR
|
||||
|
||||
def entity_filter(self, entities: set[str]) -> set[str]:
|
||||
"""Filter entities of this domain with the required features."""
|
||||
@@ -82,9 +84,11 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
AlarmControlPanelState.ARMED_VACATION,
|
||||
AlarmControlPanelEntityFeature.ARM_VACATION,
|
||||
),
|
||||
"is_disarmed": make_entity_state_condition(DOMAIN, AlarmControlPanelState.DISARMED),
|
||||
"is_disarmed": make_entity_state_condition(
|
||||
DOMAIN, AlarmControlPanelState.DISARMED, support_duration=True
|
||||
),
|
||||
"is_triggered": make_entity_state_condition(
|
||||
DOMAIN, AlarmControlPanelState.TRIGGERED
|
||||
DOMAIN, AlarmControlPanelState.TRIGGERED, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
.condition_common: &condition_common
|
||||
target:
|
||||
target: &condition_common_target
|
||||
entity:
|
||||
domain: alarm_control_panel
|
||||
fields: &condition_common_fields
|
||||
behavior:
|
||||
behavior: &condition_common_behavior
|
||||
required: true
|
||||
default: any
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
|
||||
.condition_common_for: &condition_common_for
|
||||
target: *condition_common_target
|
||||
fields: &condition_common_for_fields
|
||||
behavior: *condition_common_behavior
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
@@ -18,7 +23,7 @@
|
||||
is_armed: *condition_common
|
||||
|
||||
is_armed_away:
|
||||
fields: *condition_common_fields
|
||||
fields: *condition_common_for_fields
|
||||
target:
|
||||
entity:
|
||||
domain: alarm_control_panel
|
||||
@@ -26,7 +31,7 @@ is_armed_away:
|
||||
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
|
||||
is_armed_home:
|
||||
fields: *condition_common_fields
|
||||
fields: *condition_common_for_fields
|
||||
target:
|
||||
entity:
|
||||
domain: alarm_control_panel
|
||||
@@ -34,7 +39,7 @@ is_armed_home:
|
||||
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_HOME
|
||||
|
||||
is_armed_night:
|
||||
fields: *condition_common_fields
|
||||
fields: *condition_common_for_fields
|
||||
target:
|
||||
entity:
|
||||
domain: alarm_control_panel
|
||||
@@ -42,13 +47,13 @@ is_armed_night:
|
||||
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
|
||||
is_armed_vacation:
|
||||
fields: *condition_common_fields
|
||||
fields: *condition_common_for_fields
|
||||
target:
|
||||
entity:
|
||||
domain: alarm_control_panel
|
||||
supported_features:
|
||||
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
|
||||
is_disarmed: *condition_common
|
||||
is_disarmed: *condition_common_for
|
||||
|
||||
is_triggered: *condition_common
|
||||
is_triggered: *condition_common_for
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::alarm_control_panel::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Alarm is armed"
|
||||
|
||||
@@ -76,6 +76,7 @@ from .const import (
|
||||
ATTR_HEALTHY,
|
||||
ATTR_INTEGRATION_COUNT,
|
||||
ATTR_INTEGRATIONS,
|
||||
ATTR_ISSUE_TRACKER,
|
||||
ATTR_OPERATING_SYSTEM,
|
||||
ATTR_PROTECTED,
|
||||
ATTR_RECORDER,
|
||||
@@ -414,6 +415,7 @@ class Analytics:
|
||||
custom_integrations.append(
|
||||
{
|
||||
ATTR_DOMAIN: integration.domain,
|
||||
ATTR_ISSUE_TRACKER: integration.issue_tracker,
|
||||
ATTR_VERSION: integration.version,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -36,6 +36,7 @@ ATTR_HEALTHY = "healthy"
|
||||
ATTR_INSTALLATION_TYPE = "installation_type"
|
||||
ATTR_INTEGRATION_COUNT = "integration_count"
|
||||
ATTR_INTEGRATIONS = "integrations"
|
||||
ATTR_ISSUE_TRACKER = "issue_tracker"
|
||||
ATTR_ONBOARDED = "onboarded"
|
||||
ATTR_OPERATING_SYSTEM = "operating_system"
|
||||
ATTR_PREFERENCES = "preferences"
|
||||
|
||||
@@ -7,13 +7,17 @@ from .const import DOMAIN
|
||||
from .entity import AssistSatelliteState
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_idle": make_entity_state_condition(DOMAIN, AssistSatelliteState.IDLE),
|
||||
"is_listening": make_entity_state_condition(DOMAIN, AssistSatelliteState.LISTENING),
|
||||
"is_idle": make_entity_state_condition(
|
||||
DOMAIN, AssistSatelliteState.IDLE, support_duration=True
|
||||
),
|
||||
"is_listening": make_entity_state_condition(
|
||||
DOMAIN, AssistSatelliteState.LISTENING, support_duration=True
|
||||
),
|
||||
"is_processing": make_entity_state_condition(
|
||||
DOMAIN, AssistSatelliteState.PROCESSING
|
||||
DOMAIN, AssistSatelliteState.PROCESSING, support_duration=True
|
||||
),
|
||||
"is_responding": make_entity_state_condition(
|
||||
DOMAIN, AssistSatelliteState.RESPONDING
|
||||
DOMAIN, AssistSatelliteState.RESPONDING, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -901,13 +901,12 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Remove listeners when removing automation from Home Assistant."""
|
||||
await super().async_will_remove_from_hass()
|
||||
await self._async_disable()
|
||||
if self.registry_entry and self.registry_entry.entity_id != self.entity_id:
|
||||
# Entity ID change, do not unload the script or conditions as they will
|
||||
# be reused.
|
||||
await self._async_disable()
|
||||
return
|
||||
await self._async_disable(stop_actions=False)
|
||||
await self.action_script.async_unload()
|
||||
self.action_script.async_unload()
|
||||
if self._condition is not None:
|
||||
self._condition.async_unload()
|
||||
|
||||
|
||||
@@ -32,21 +32,25 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_low": make_entity_state_condition(
|
||||
BATTERY_DOMAIN_SPECS,
|
||||
STATE_ON,
|
||||
support_duration=True,
|
||||
primary_entities_only=False,
|
||||
),
|
||||
"is_not_low": make_entity_state_condition(
|
||||
BATTERY_DOMAIN_SPECS,
|
||||
STATE_OFF,
|
||||
support_duration=True,
|
||||
primary_entities_only=False,
|
||||
),
|
||||
"is_charging": make_entity_state_condition(
|
||||
BATTERY_CHARGING_DOMAIN_SPECS,
|
||||
STATE_ON,
|
||||
support_duration=True,
|
||||
primary_entities_only=False,
|
||||
),
|
||||
"is_not_charging": make_entity_state_condition(
|
||||
BATTERY_CHARGING_DOMAIN_SPECS,
|
||||
STATE_OFF,
|
||||
support_duration=True,
|
||||
primary_entities_only=False,
|
||||
),
|
||||
"is_level": make_entity_numerical_condition(
|
||||
|
||||
@@ -63,7 +63,6 @@ is_level:
|
||||
primary_entities_only: false
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::battery::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::battery::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::battery::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -85,9 +85,7 @@ class BleBoxCoverEntity(BleBoxEntity[blebox_uniapi.cover.Cover], CoverEntity):
|
||||
if position == -1: # possible for shutterBox
|
||||
return None
|
||||
|
||||
if position is None:
|
||||
return None
|
||||
return 100 - position if self._feature.is_position_inverted else position
|
||||
return None if position is None else 100 - position
|
||||
|
||||
@property
|
||||
def current_cover_tilt_position(self) -> int | None:
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["blebox_uniapi"],
|
||||
"requirements": ["blebox-uniapi==2.5.2"],
|
||||
"requirements": ["blebox-uniapi==2.5.1"],
|
||||
"zeroconf": ["_bbxsrv._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bring_api"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["bring-api==1.1.2"]
|
||||
"requirements": ["bring-api==1.1.1"]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ DOMAIN = "broadlink"
|
||||
|
||||
DOMAINS_AND_TYPES = {
|
||||
Platform.CLIMATE: {"HYS"},
|
||||
Platform.INFRARED: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
|
||||
Platform.LIGHT: {"LB1", "LB2"},
|
||||
Platform.RADIO_FREQUENCY: {"RM4PRO", "RMPRO"},
|
||||
Platform.REMOTE: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
"""Infrared platform for Broadlink remotes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from broadlink.exceptions import BroadlinkException
|
||||
from broadlink.remote import pulses_to_data as _bl_pulses_to_data
|
||||
|
||||
from homeassistant.components.infrared import InfraredCommand, InfraredEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import BroadlinkEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .device import BroadlinkDevice
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
def _timings_to_broadlink_packet(timings: list[int]) -> bytes:
|
||||
"""Convert signed microsecond timings to a Broadlink IR packet.
|
||||
|
||||
Positive values are pulse (high) durations; negative values are space
|
||||
(low) durations. The Broadlink library's encoder expects absolute
|
||||
durations.
|
||||
"""
|
||||
pulses = [abs(t) for t in timings]
|
||||
return _bl_pulses_to_data(pulses)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Broadlink infrared entity."""
|
||||
# Uses legacy hass.data[DOMAIN] pattern
|
||||
# pylint: disable-next=hass-use-runtime-data
|
||||
device = hass.data[DOMAIN].devices[config_entry.entry_id]
|
||||
async_add_entities([BroadlinkInfraredEntity(device)])
|
||||
|
||||
|
||||
class BroadlinkInfraredEntity(BroadlinkEntity, InfraredEntity):
|
||||
"""Broadlink infrared transmitter entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_translation_key = "infrared_emitter"
|
||||
|
||||
def __init__(self, device: BroadlinkDevice) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(device)
|
||||
self._attr_unique_id = f"{device.unique_id}-emitter"
|
||||
|
||||
async def async_send_command(self, command: InfraredCommand) -> None:
|
||||
"""Send an IR command via the Broadlink device."""
|
||||
packet = _timings_to_broadlink_packet(command.get_raw_timings())
|
||||
try:
|
||||
await self._device.async_request(self._device.api.send_data, packet)
|
||||
except (BroadlinkException, OSError) as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="send_command_failed",
|
||||
translation_placeholders={"error": str(err)},
|
||||
) from err
|
||||
@@ -49,11 +49,6 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"infrared": {
|
||||
"infrared_emitter": {
|
||||
"name": "IR emitter"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"day_of_week": {
|
||||
"name": "Day of week",
|
||||
@@ -87,9 +82,6 @@
|
||||
"frequency_not_supported": {
|
||||
"message": "Broadlink devices cannot transmit on {frequency} MHz"
|
||||
},
|
||||
"send_command_failed": {
|
||||
"message": "Failed to send IR command: {error}"
|
||||
},
|
||||
"transmit_failed": {
|
||||
"message": "Failed to transmit RF command: {error}"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from .const import DOMAIN
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_event_active": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
"is_event_active": make_entity_state_condition(
|
||||
DOMAIN, STATE_ON, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class ClimateTargetTemperatureCondition(EntityNumericalConditionWithUnitBase):
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_hvac_mode": ClimateHVACModeCondition,
|
||||
"is_off": make_entity_state_condition(DOMAIN, HVACMode.OFF),
|
||||
"is_off": make_entity_state_condition(DOMAIN, HVACMode.OFF, support_duration=True),
|
||||
"is_on": make_entity_state_condition(
|
||||
DOMAIN,
|
||||
{
|
||||
|
||||
@@ -9,11 +9,6 @@
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for: &condition_for
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
|
||||
.humidity_threshold_entity: &humidity_threshold_entity
|
||||
- domain: input_number
|
||||
@@ -41,7 +36,16 @@
|
||||
- domain: number
|
||||
device_class: temperature
|
||||
|
||||
is_off: *condition_common
|
||||
is_off:
|
||||
target: *condition_climate_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
|
||||
is_on: *condition_common
|
||||
is_cooling: *condition_common
|
||||
is_drying: *condition_common
|
||||
@@ -51,7 +55,6 @@ is_hvac_mode:
|
||||
target: *condition_climate_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
hvac_mode:
|
||||
context:
|
||||
filter_target: target
|
||||
@@ -67,7 +70,6 @@ target_humidity:
|
||||
target: *condition_climate_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
@@ -80,7 +82,6 @@ target_temperature:
|
||||
target: *condition_climate_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Thermostat is cooling"
|
||||
@@ -25,9 +22,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Thermostat is drying"
|
||||
@@ -37,9 +31,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Thermostat is heating"
|
||||
@@ -50,9 +41,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
},
|
||||
"hvac_mode": {
|
||||
"description": "The HVAC modes to test for.",
|
||||
"name": "Modes"
|
||||
@@ -77,9 +65,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Thermostat is on"
|
||||
@@ -90,9 +75,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::climate::common::condition_threshold_name%]"
|
||||
}
|
||||
@@ -105,9 +87,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::climate::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::climate::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::climate::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -18,12 +18,7 @@ from aiocomelit.const import (
|
||||
SCENARIO,
|
||||
VEDO,
|
||||
)
|
||||
from aiocomelit.exceptions import (
|
||||
CannotAuthenticate,
|
||||
CannotConnect,
|
||||
CannotRetrieveData,
|
||||
DeviceStorageFailureError,
|
||||
)
|
||||
from aiocomelit.exceptions import CannotAuthenticate, CannotConnect, CannotRetrieveData
|
||||
from aiohttp import ClientSession
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -117,11 +112,6 @@ class ComelitBaseCoordinator(DataUpdateCoordinator[T]):
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_authenticate",
|
||||
) from err
|
||||
except DeviceStorageFailureError as err:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_storage_failure",
|
||||
) from err
|
||||
|
||||
@abstractmethod
|
||||
async def _async_update_system_data(self) -> T:
|
||||
|
||||
@@ -121,9 +121,6 @@
|
||||
"cannot_retrieve_data": {
|
||||
"message": "Error retrieving data: {error}"
|
||||
},
|
||||
"device_storage_failure": {
|
||||
"message": "Device SD card read failure. The card may be corrupted or failing; replacement is recommended."
|
||||
},
|
||||
"humidity_while_off": {
|
||||
"message": "Cannot change humidity while off"
|
||||
},
|
||||
|
||||
@@ -5,12 +5,7 @@ from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, Concatenate, Literal
|
||||
|
||||
from aiocomelit.api import ComelitSerialBridgeObject
|
||||
from aiocomelit.exceptions import (
|
||||
CannotAuthenticate,
|
||||
CannotConnect,
|
||||
CannotRetrieveData,
|
||||
DeviceStorageFailureError,
|
||||
)
|
||||
from aiocomelit.exceptions import CannotAuthenticate, CannotConnect, CannotRetrieveData
|
||||
from aiohttp import ClientSession, CookieJar
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -115,12 +110,6 @@ def bridge_api_call[_T: ComelitBridgeBaseEntity, **_P](
|
||||
translation_key="cannot_retrieve_data",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except DeviceStorageFailureError as err:
|
||||
self.coordinator.last_update_success = False
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_storage_failure",
|
||||
) from err
|
||||
except CannotAuthenticate:
|
||||
self.coordinator.last_update_success = False
|
||||
self.coordinator.config_entry.async_start_reauth(self.hass)
|
||||
|
||||
@@ -9,11 +9,6 @@ is_value:
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_for_name": "For at least",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_for_name": "For at least"
|
||||
},
|
||||
@@ -11,9 +10,6 @@
|
||||
"behavior": {
|
||||
"name": "Condition passes if"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::counter::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "Threshold type"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ from collections.abc import Mapping
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.condition import Condition, EntityConditionBase
|
||||
from homeassistant.helpers.condition import (
|
||||
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR,
|
||||
Condition,
|
||||
EntityConditionBase,
|
||||
)
|
||||
|
||||
from .const import ATTR_IS_CLOSED, DOMAIN, CoverDeviceClass
|
||||
from .models import CoverDomainSpec
|
||||
@@ -14,6 +18,7 @@ class CoverConditionBase(EntityConditionBase):
|
||||
"""Base condition for cover state checks."""
|
||||
|
||||
_domain_specs: Mapping[str, CoverDomainSpec]
|
||||
_schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR
|
||||
|
||||
def is_valid_state(self, entity_state: State) -> bool:
|
||||
"""Check if the state matches the expected cover state."""
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["duco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["python-duco-client==0.3.10"],
|
||||
"requirements": ["python-duco-client==0.3.9"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "duco [[][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][]].*",
|
||||
|
||||
@@ -143,7 +143,6 @@ async def async_setup_entry(
|
||||
|
||||
@callback
|
||||
def _async_add_new_entities() -> None:
|
||||
"""Add new sensor entities and remove stale ones on coordinator updates."""
|
||||
# Remove devices whose nodes have disappeared from the API.
|
||||
# The firmware removes deregistered RF/wired nodes automatically.
|
||||
# BSRH box sensors that are physically unplugged from the PCB are
|
||||
@@ -167,19 +166,14 @@ async def async_setup_entry(
|
||||
for node in coordinator.data.nodes.values():
|
||||
if node.node_id in known_nodes:
|
||||
continue
|
||||
known_nodes.add(node.node_id)
|
||||
if node.general.node_type == NodeType.UNKNOWN:
|
||||
# Do not add the node to known_nodes so that it is re-evaluated
|
||||
# on every coordinator update. This allows entities to be
|
||||
# created automatically once a firmware update or library
|
||||
# update adds support for the device type.
|
||||
_LOGGER.debug(
|
||||
"Duco node %s (%s) has an unsupported device type and will be "
|
||||
"retried on subsequent coordinator updates",
|
||||
_LOGGER.warning(
|
||||
"Duco node %s (%s) has an unsupported device type and will be ignored",
|
||||
node.node_id,
|
||||
node.general.name,
|
||||
)
|
||||
continue
|
||||
known_nodes.add(node.node_id)
|
||||
new_entities.extend(
|
||||
DucoSensorEntity(coordinator, node, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["sense_energy"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["sense-energy==0.14.1"]
|
||||
"requirements": ["sense-energy==0.14.0"]
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from . import DOMAIN
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF, support_duration=True),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON, support_duration=True),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from fritzconnection.core.exceptions import FritzConnectionException
|
||||
from fritzconnection.lib.fritzstatus import FritzStatus
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
@@ -146,7 +145,7 @@ def _is_suitable_cpu_temperature(status: FritzStatus) -> bool:
|
||||
"""Return whether the CPU temperature sensor is suitable."""
|
||||
try:
|
||||
cpu_temp = status.get_cpu_temperatures()[0]
|
||||
except RequestException, IndexError, FritzConnectionException:
|
||||
except RequestException, IndexError:
|
||||
_LOGGER.debug("CPU temperature not supported by the device")
|
||||
return False
|
||||
if cpu_temp == 0:
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "system",
|
||||
"preview_features": { "winter_mode": {} },
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20260429.2"]
|
||||
"requirements": ["home-assistant-frontend==20260429.0"]
|
||||
}
|
||||
|
||||
@@ -9,13 +9,12 @@ from aiogithubapi import GitHubAPI
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import (
|
||||
SERVER_SOFTWARE,
|
||||
async_get_clientsession,
|
||||
)
|
||||
|
||||
from .const import CONF_REPOSITORIES, CONF_REPOSITORY, DOMAIN, SUBENTRY_TYPE_REPOSITORY
|
||||
from .const import CONF_REPOSITORIES, CONF_REPOSITORY, SUBENTRY_TYPE_REPOSITORY
|
||||
from .coordinator import GithubConfigEntry, GitHubDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
@@ -69,7 +68,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: GithubConfigEntry) -> b
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: GithubConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
if entry.minor_version == 1:
|
||||
dev_reg = dr.async_get(hass)
|
||||
# In minor version 2 we migrated repositories from entry options to
|
||||
# subentries, so we need to convert the list from
|
||||
# entry.options[CONF_REPOSITORIES] into individual subentries.
|
||||
@@ -80,13 +78,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: GithubConfigEntry) ->
|
||||
title=repository,
|
||||
unique_id=repository,
|
||||
)
|
||||
|
||||
hass.config_entries.async_add_subentry(entry, subentry)
|
||||
if device := dev_reg.async_get_device({(DOMAIN, repository)}):
|
||||
dev_reg.async_update_device(
|
||||
device.id,
|
||||
remove_config_entry_id=entry.entry_id,
|
||||
add_config_subentry_id=subentry.subentry_id,
|
||||
add_config_entry_id=entry.entry_id,
|
||||
)
|
||||
|
||||
hass.config_entries.async_update_entry(entry, minor_version=2)
|
||||
return True
|
||||
|
||||
@@ -119,22 +119,9 @@ class HiveFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
if not errors:
|
||||
_LOGGER.debug("2FA successful")
|
||||
if self.source == SOURCE_REAUTH:
|
||||
try:
|
||||
device_registered = await self.hive_auth.is_device_registered()
|
||||
except HiveApiError as err:
|
||||
_LOGGER.debug(
|
||||
"Failed to check whether the Hive device is registered during reauthentication: %s",
|
||||
err,
|
||||
)
|
||||
errors["base"] = "no_internet_available"
|
||||
else:
|
||||
if device_registered:
|
||||
return await self.async_setup_hive_entry()
|
||||
self.device_registration = True
|
||||
return await self.async_step_configuration()
|
||||
else:
|
||||
self.device_registration = True
|
||||
return await self.async_step_configuration()
|
||||
return await self.async_setup_hive_entry()
|
||||
self.device_registration = True
|
||||
return await self.async_step_configuration()
|
||||
|
||||
schema = vol.Schema({vol.Required(CONF_CODE): str})
|
||||
return self.async_show_form(step_id="2fa", data_schema=schema, errors=errors)
|
||||
@@ -186,7 +173,6 @@ class HiveFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Re Authenticate a user."""
|
||||
self.data = dict(entry_data)
|
||||
data = {
|
||||
CONF_USERNAME: entry_data[CONF_USERNAME],
|
||||
CONF_PASSWORD: entry_data[CONF_PASSWORD],
|
||||
|
||||
@@ -70,8 +70,8 @@ class IsModeCondition(EntityStateConditionBase):
|
||||
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF, support_duration=True),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON, support_duration=True),
|
||||
"is_drying": make_entity_state_condition(
|
||||
{DOMAIN: DomainSpec(value_source=ATTR_ACTION)}, HumidifierAction.DRYING
|
||||
),
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for: &condition_for
|
||||
|
||||
.condition_common_for: &condition_common_for
|
||||
target: *condition_humidifier_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
@@ -29,8 +34,8 @@
|
||||
mode: box
|
||||
unit_of_measurement: "%"
|
||||
|
||||
is_off: *condition_common
|
||||
is_on: *condition_common
|
||||
is_off: *condition_common_for
|
||||
is_on: *condition_common_for
|
||||
is_drying: *condition_common
|
||||
is_humidifying: *condition_common
|
||||
|
||||
@@ -38,7 +43,6 @@ is_mode:
|
||||
target: *condition_humidifier_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
mode:
|
||||
context:
|
||||
filter_target: target
|
||||
@@ -52,7 +56,6 @@ is_target_humidity:
|
||||
target: *condition_humidifier_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::humidifier::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Humidifier is drying"
|
||||
@@ -24,9 +21,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::humidifier::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Humidifier is humidifying"
|
||||
@@ -37,9 +31,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::humidifier::common::condition_for_name%]"
|
||||
},
|
||||
"mode": {
|
||||
"description": "The operation modes to check for.",
|
||||
"name": "Mode"
|
||||
@@ -77,9 +68,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::humidifier::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::humidifier::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ is_value:
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_for_name": "For at least",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_for_name": "For at least",
|
||||
@@ -14,9 +13,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::humidity::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::humidity::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::humidity::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ ILLUMINANCE_VALUE_DOMAIN_SPECS = {
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_detected": make_entity_state_condition(
|
||||
ILLUMINANCE_DETECTED_DOMAIN_SPECS, STATE_ON
|
||||
ILLUMINANCE_DETECTED_DOMAIN_SPECS, STATE_ON, support_duration=True
|
||||
),
|
||||
"is_not_detected": make_entity_state_condition(
|
||||
ILLUMINANCE_DETECTED_DOMAIN_SPECS, STATE_OFF
|
||||
ILLUMINANCE_DETECTED_DOMAIN_SPECS, STATE_OFF, support_duration=True
|
||||
),
|
||||
"is_value": make_entity_numerical_condition(
|
||||
ILLUMINANCE_VALUE_DOMAIN_SPECS, LIGHT_LUX
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for: &condition_for
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
@@ -27,7 +27,6 @@ is_value:
|
||||
device_class: illuminance
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::illuminance::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::illuminance::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::illuminance::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["indevolt-api==1.6.4"]
|
||||
"requirements": ["indevolt-api==1.6.5"]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "insteon",
|
||||
"name": "Insteon",
|
||||
"after_dependencies": ["panel_custom"],
|
||||
"codeowners": ["@teharris1", "@ssyrell"],
|
||||
"codeowners": ["@teharris1"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http", "usb", "websocket_api"],
|
||||
"dhcp": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"loggers": ["pyinsteon", "pypubsub"],
|
||||
"requirements": [
|
||||
"pyinsteon==1.6.4",
|
||||
"insteon-frontend-home-assistant==0.6.2"
|
||||
"insteon-frontend-home-assistant==0.6.1"
|
||||
],
|
||||
"single_config_entry": true,
|
||||
"usb": [
|
||||
|
||||
@@ -79,9 +79,10 @@ async def async_reload(hass: HomeAssistant, service_call: ServiceCall) -> None:
|
||||
existing_intents = hass.data[DOMAIN]
|
||||
|
||||
for intent_type, conf in existing_intents.items():
|
||||
intent.async_remove(hass, intent_type)
|
||||
if isinstance(conf.get(CONF_ACTION), script.Script):
|
||||
await conf[CONF_ACTION].async_unload()
|
||||
await conf[CONF_ACTION].async_stop()
|
||||
conf[CONF_ACTION].async_unload()
|
||||
intent.async_remove(hass, intent_type)
|
||||
|
||||
if not new_config or DOMAIN not in new_config:
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"xknx==3.15.0",
|
||||
"xknxproject==3.9.0",
|
||||
"knx-frontend==2026.4.30.60856"
|
||||
"xknxproject==3.8.2",
|
||||
"knx-frontend==2026.4.25.155016"
|
||||
],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -6,13 +6,21 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from .const import DOMAIN, LawnMowerActivity
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_docked": make_entity_state_condition(DOMAIN, LawnMowerActivity.DOCKED),
|
||||
"is_docked": make_entity_state_condition(
|
||||
DOMAIN, LawnMowerActivity.DOCKED, support_duration=True
|
||||
),
|
||||
"is_encountering_an_error": make_entity_state_condition(
|
||||
DOMAIN, LawnMowerActivity.ERROR
|
||||
DOMAIN, LawnMowerActivity.ERROR, support_duration=True
|
||||
),
|
||||
"is_mowing": make_entity_state_condition(
|
||||
DOMAIN, LawnMowerActivity.MOWING, support_duration=True
|
||||
),
|
||||
"is_paused": make_entity_state_condition(
|
||||
DOMAIN, LawnMowerActivity.PAUSED, support_duration=True
|
||||
),
|
||||
"is_returning": make_entity_state_condition(
|
||||
DOMAIN, LawnMowerActivity.RETURNING, support_duration=True
|
||||
),
|
||||
"is_mowing": make_entity_state_condition(DOMAIN, LawnMowerActivity.MOWING),
|
||||
"is_paused": make_entity_state_condition(DOMAIN, LawnMowerActivity.PAUSED),
|
||||
"is_returning": make_entity_state_condition(DOMAIN, LawnMowerActivity.RETURNING),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ class BrightnessCondition(EntityNumericalConditionBase):
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_brightness": BrightnessCondition,
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF, support_duration=True),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON, support_duration=True),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for: &condition_for
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
@@ -36,7 +36,6 @@ is_brightness:
|
||||
target: *condition_light_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -47,9 +47,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::light::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::light::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::light::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Generic
|
||||
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot3, LitterRobot4, Robot
|
||||
from pylitterbot import LitterRobot, LitterRobot4, Robot
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
@@ -32,11 +32,8 @@ class RobotBinarySensorEntityDescription(
|
||||
is_on_fn: Callable[[_WhiskerEntityT], bool]
|
||||
|
||||
|
||||
BINARY_SENSOR_MAP: dict[
|
||||
type[Robot] | tuple[type[Robot], ...],
|
||||
tuple[RobotBinarySensorEntityDescription, ...],
|
||||
] = {
|
||||
LitterRobot: (
|
||||
BINARY_SENSOR_MAP: dict[type[Robot], tuple[RobotBinarySensorEntityDescription, ...]] = {
|
||||
LitterRobot: ( # type: ignore[type-abstract] # only used for isinstance check
|
||||
RobotBinarySensorEntityDescription[LitterRobot](
|
||||
key="sleeping",
|
||||
translation_key="sleeping",
|
||||
@@ -61,14 +58,14 @@ BINARY_SENSOR_MAP: dict[
|
||||
is_on_fn=lambda robot: not robot.is_hopper_removed,
|
||||
),
|
||||
),
|
||||
(FeederRobot, LitterRobot3, LitterRobot4): (
|
||||
RobotBinarySensorEntityDescription[FeederRobot | LitterRobot3 | LitterRobot4](
|
||||
Robot: ( # type: ignore[type-abstract] # only used for isinstance check
|
||||
RobotBinarySensorEntityDescription[Robot](
|
||||
key="power_status",
|
||||
translation_key="power_status",
|
||||
device_class=BinarySensorDeviceClass.PLUG,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
is_on_fn=lambda robot: robot.power_type == "AC",
|
||||
is_on_fn=lambda robot: robot.power_status == "AC",
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylitterbot"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylitterbot==2025.4.0"]
|
||||
"requirements": ["pylitterbot==2025.3.2"]
|
||||
}
|
||||
|
||||
@@ -6,10 +6,18 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from .const import DOMAIN, LockState
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_jammed": make_entity_state_condition(DOMAIN, LockState.JAMMED),
|
||||
"is_locked": make_entity_state_condition(DOMAIN, LockState.LOCKED),
|
||||
"is_open": make_entity_state_condition(DOMAIN, LockState.OPEN),
|
||||
"is_unlocked": make_entity_state_condition(DOMAIN, LockState.UNLOCKED),
|
||||
"is_jammed": make_entity_state_condition(
|
||||
DOMAIN, LockState.JAMMED, support_duration=True
|
||||
),
|
||||
"is_locked": make_entity_state_condition(
|
||||
DOMAIN, LockState.LOCKED, support_duration=True
|
||||
),
|
||||
"is_open": make_entity_state_condition(
|
||||
DOMAIN, LockState.OPEN, support_duration=True
|
||||
),
|
||||
"is_unlocked": make_entity_state_condition(
|
||||
DOMAIN, LockState.UNLOCKED, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from .const import DOMAIN, MediaPlayerState
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, MediaPlayerState.OFF),
|
||||
"is_off": make_entity_state_condition(
|
||||
DOMAIN, MediaPlayerState.OFF, support_duration=True
|
||||
),
|
||||
"is_on": make_entity_state_condition(
|
||||
DOMAIN,
|
||||
{
|
||||
@@ -27,8 +29,12 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
MediaPlayerState.PAUSED,
|
||||
},
|
||||
),
|
||||
"is_paused": make_entity_state_condition(DOMAIN, MediaPlayerState.PAUSED),
|
||||
"is_playing": make_entity_state_condition(DOMAIN, MediaPlayerState.PLAYING),
|
||||
"is_paused": make_entity_state_condition(
|
||||
DOMAIN, MediaPlayerState.PAUSED, support_duration=True
|
||||
),
|
||||
"is_playing": make_entity_state_condition(
|
||||
DOMAIN, MediaPlayerState.PLAYING, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
.condition_common: &condition_common
|
||||
target:
|
||||
target: &condition_media_player_target
|
||||
entity:
|
||||
domain: media_player
|
||||
fields:
|
||||
behavior:
|
||||
behavior: &condition_behavior
|
||||
required: true
|
||||
default: any
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
|
||||
.condition_common_with_for: &condition_common_with_for
|
||||
target: *condition_media_player_target
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
|
||||
is_off: *condition_common
|
||||
is_off: *condition_common_with_for
|
||||
is_on: *condition_common
|
||||
is_not_playing: *condition_common
|
||||
is_paused: *condition_common
|
||||
is_playing: *condition_common
|
||||
is_paused: *condition_common_with_for
|
||||
is_playing: *condition_common_with_for
|
||||
|
||||
@@ -123,9 +123,6 @@
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"muted": {
|
||||
"trigger": "mdi:volume-mute"
|
||||
},
|
||||
"paused_playing": {
|
||||
"trigger": "mdi:pause"
|
||||
},
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::media_player::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::media_player::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Media player is not playing"
|
||||
@@ -35,9 +32,6 @@
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::media_player::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::media_player::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Media player is on"
|
||||
@@ -437,18 +431,6 @@
|
||||
},
|
||||
"title": "Media player",
|
||||
"triggers": {
|
||||
"muted": {
|
||||
"description": "Triggers after one or more media players are muted.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::media_player::common::trigger_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Media player muted"
|
||||
},
|
||||
"paused_playing": {
|
||||
"description": "Triggers after one or more media players pause playing.",
|
||||
"fields": {
|
||||
|
||||
@@ -1,85 +1,12 @@
|
||||
"""Provides triggers for media players."""
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import (
|
||||
EntityTriggerBase,
|
||||
Trigger,
|
||||
make_entity_transition_trigger,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.trigger import Trigger, make_entity_transition_trigger
|
||||
|
||||
from . import ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, MediaPlayerState
|
||||
from . import MediaPlayerState
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class MediaPlayerMutedTrigger(EntityTriggerBase):
|
||||
"""Class for media player muted triggers."""
|
||||
|
||||
_domain_specs = {DOMAIN: DomainSpec()}
|
||||
|
||||
def _has_volume_attributes(self, state: State) -> bool:
|
||||
"""Check if the state has volume muted or volume level attributes."""
|
||||
return (
|
||||
state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is not None
|
||||
or state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) is not None
|
||||
)
|
||||
|
||||
def _should_include(self, state: State) -> bool:
|
||||
"""Check if an entity should participate in all/count checks.
|
||||
|
||||
Entities without volume attributes cannot be muted, so they are
|
||||
excluded from the check - otherwise an "all" check would never
|
||||
pass when there are media players without volume support.
|
||||
"""
|
||||
return state.state not in self._excluded_states and self._has_volume_attributes(
|
||||
state
|
||||
)
|
||||
|
||||
def check_all_match(self, entity_ids: set[str]) -> bool:
|
||||
"""Check if all mutable entity states match."""
|
||||
return all(
|
||||
self.is_valid_state(state)
|
||||
for entity_id in entity_ids
|
||||
if (state := self._hass.states.get(entity_id)) is not None
|
||||
and self._should_include(state)
|
||||
)
|
||||
|
||||
def count_matches(self, entity_ids: set[str]) -> int:
|
||||
"""Count matching mutable entities."""
|
||||
return sum(
|
||||
self.is_valid_state(state)
|
||||
for entity_id in entity_ids
|
||||
if (state := self._hass.states.get(entity_id)) is not None
|
||||
and self._should_include(state)
|
||||
)
|
||||
|
||||
def is_muted(self, state: State) -> bool:
|
||||
"""Check if the media player is muted."""
|
||||
return (
|
||||
state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is True
|
||||
or state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 0
|
||||
)
|
||||
|
||||
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
|
||||
"""Check if the origin state is valid and the state has changed."""
|
||||
if from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
return False
|
||||
|
||||
if not self._has_volume_attributes(to_state):
|
||||
return False
|
||||
|
||||
return self.is_muted(from_state) != self.is_muted(to_state)
|
||||
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the new state matches the expected state."""
|
||||
if not self._has_volume_attributes(state):
|
||||
return False
|
||||
return self.is_muted(state)
|
||||
|
||||
|
||||
TRIGGERS: dict[str, type[Trigger]] = {
|
||||
"muted": MediaPlayerMutedTrigger,
|
||||
"paused_playing": make_entity_transition_trigger(
|
||||
DOMAIN,
|
||||
from_states={
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
selector:
|
||||
duration:
|
||||
|
||||
muted: *trigger_common
|
||||
paused_playing: *trigger_common
|
||||
started_playing: *trigger_common
|
||||
stopped_playing: *trigger_common
|
||||
|
||||
@@ -479,7 +479,6 @@ class WashingMachineProgramId(MieleEnum, missing_to_none=True):
|
||||
down_filled_items = 129
|
||||
cottons_eco = 133
|
||||
quick_power_wash = 146, 10031
|
||||
quick_intense = 177
|
||||
eco_40_60 = 190, 10007
|
||||
bed_linen = 10047
|
||||
easy_care = 10016
|
||||
|
||||
@@ -27,9 +27,11 @@ _MOISTURE_NUMERICAL_DOMAIN_SPECS = {
|
||||
}
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_detected": make_entity_state_condition(_MOISTURE_BINARY_DOMAIN_SPECS, STATE_ON),
|
||||
"is_detected": make_entity_state_condition(
|
||||
_MOISTURE_BINARY_DOMAIN_SPECS, STATE_ON, support_duration=True
|
||||
),
|
||||
"is_not_detected": make_entity_state_condition(
|
||||
_MOISTURE_BINARY_DOMAIN_SPECS, STATE_OFF
|
||||
_MOISTURE_BINARY_DOMAIN_SPECS, STATE_OFF, support_duration=True
|
||||
),
|
||||
"is_value": make_entity_numerical_condition(
|
||||
_MOISTURE_NUMERICAL_DOMAIN_SPECS, PERCENTAGE
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for: &condition_for
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
@@ -41,7 +41,6 @@ is_value:
|
||||
device_class: moisture
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::moisture::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::moisture::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::moisture::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -15,8 +15,12 @@ _MOTION_DOMAIN_SPECS = {
|
||||
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_detected": make_entity_state_condition(_MOTION_DOMAIN_SPECS, STATE_ON),
|
||||
"is_not_detected": make_entity_state_condition(_MOTION_DOMAIN_SPECS, STATE_OFF),
|
||||
"is_detected": make_entity_state_condition(
|
||||
_MOTION_DOMAIN_SPECS, STATE_ON, support_duration=True
|
||||
),
|
||||
"is_not_detected": make_entity_state_condition(
|
||||
_MOTION_DOMAIN_SPECS, STATE_OFF, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"data_description": {
|
||||
"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 broker's certificate.",
|
||||
"certificate": "The custom CA certificate file to validate your MQTT brokers certificate.",
|
||||
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
||||
"client_id": "The unique ID to identify the Home Assistant MQTT API as MQTT client. It is recommended to leave this option blank.",
|
||||
"client_key": "The private key file that belongs to your client certificate.",
|
||||
@@ -57,7 +57,7 @@
|
||||
"password": "The password to log in to your MQTT broker.",
|
||||
"port": "The port your MQTT broker listens to. For example 1883.",
|
||||
"protocol": "The MQTT protocol your broker operates at. For example 3.1.1.",
|
||||
"set_ca_cert": "Select **Auto** for automatic CA validation, or **Custom** and select **Next** to set a custom CA certificate, to allow validating your MQTT broker's certificate.",
|
||||
"set_ca_cert": "Select **Auto** for automatic CA validation, or **Custom** and select **Next** to set a custom CA certificate, to allow validating your MQTT brokers certificate.",
|
||||
"set_client_cert": "Enable and select **Next** to set a client certificate and private key to authenticate against your MQTT broker.",
|
||||
"tls_insecure": "Option to ignore validation of your MQTT broker's certificate.",
|
||||
"transport": "The transport to be used for the connection to your MQTT broker.",
|
||||
@@ -83,7 +83,7 @@
|
||||
"password": "[%key:component::mqtt::config::step::broker::data_description::password%]",
|
||||
"username": "[%key:component::mqtt::config::step::broker::data_description::username%]"
|
||||
},
|
||||
"description": "The MQTT broker reported an authentication error. Please confirm the broker's correct username and password.",
|
||||
"description": "The MQTT broker reported an authentication error. Please confirm the brokers correct username and password.",
|
||||
"title": "Re-authentication required with the MQTT broker"
|
||||
},
|
||||
"start_addon": {
|
||||
@@ -162,7 +162,7 @@
|
||||
"component": "Entity"
|
||||
},
|
||||
"data_description": {
|
||||
"component": "Select the entity you want to delete. At least one entity is required."
|
||||
"component": "Select the entity you want to delete. Minimal one entity is required."
|
||||
},
|
||||
"description": "Delete an entity. The entity will be removed from the device. Removing an entity will break any automations or scripts that depend on it.",
|
||||
"title": "Delete entity"
|
||||
|
||||
@@ -8,7 +8,6 @@ from google_nest_sdm.event import EventMessage, EventType
|
||||
from google_nest_sdm.traits import TraitType
|
||||
|
||||
from homeassistant.components.event import (
|
||||
DoorbellEventType,
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
@@ -43,7 +42,7 @@ ENTITY_DESCRIPTIONS = [
|
||||
key=EVENT_DOORBELL_CHIME,
|
||||
translation_key="chime",
|
||||
device_class=EventDeviceClass.DOORBELL,
|
||||
event_types=[DoorbellEventType.RING],
|
||||
event_types=[EVENT_DOORBELL_CHIME],
|
||||
trait_types=[TraitType.DOORBELL_CHIME],
|
||||
api_event_types=[EventType.DOORBELL_CHIME],
|
||||
),
|
||||
@@ -81,7 +80,7 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
class NestTraitEventEntity(EventEntity):
|
||||
"""Nest event entity for event entity descriptions."""
|
||||
"""Nest doorbell event entity."""
|
||||
|
||||
entity_description: NestEventEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
@@ -114,9 +113,6 @@ class NestTraitEventEntity(EventEntity):
|
||||
# This event is a duplicate message in the same thread
|
||||
return
|
||||
|
||||
if event_type == EVENT_DOORBELL_CHIME:
|
||||
event_type = DoorbellEventType.RING
|
||||
|
||||
self._trigger_event(
|
||||
event_type,
|
||||
{"nest_event_id": nest_event_id},
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"ring": "[%key:component::event::entity_component::doorbell::state_attributes::event_type::state::ring%]"
|
||||
"doorbell_chime": "[%key:component::nest::entity::event::chime::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,6 @@ DATA_SCHEMA = vol.Schema(
|
||||
|
||||
async def test_api(hass: HomeAssistant, user_input: dict[str, Any]) -> dict[str, str]:
|
||||
"""Test fetch data from Nord Pool."""
|
||||
if not user_input.get(CONF_AREAS):
|
||||
return {CONF_AREAS: "no_areas"}
|
||||
client = NordPoolClient(async_get_clientsession(hass))
|
||||
try:
|
||||
await client.async_get_delivery_period(
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"no_areas": "No area(s) selected",
|
||||
"no_data": "API connected but the response was empty"
|
||||
},
|
||||
"step": {
|
||||
|
||||
@@ -15,8 +15,12 @@ _OCCUPANCY_DOMAIN_SPECS = {
|
||||
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_detected": make_entity_state_condition(_OCCUPANCY_DOMAIN_SPECS, STATE_ON),
|
||||
"is_not_detected": make_entity_state_condition(_OCCUPANCY_DOMAIN_SPECS, STATE_OFF),
|
||||
"is_detected": make_entity_state_condition(
|
||||
_OCCUPANCY_DOMAIN_SPECS, STATE_ON, support_duration=True
|
||||
),
|
||||
"is_not_detected": make_entity_state_condition(
|
||||
_OCCUPANCY_DOMAIN_SPECS, STATE_OFF, support_duration=True
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["ohme==1.9.0"]
|
||||
"requirements": ["ohme==1.7.1"]
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ from .const import (
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_STORE_RESPONSES,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TOP_P,
|
||||
@@ -60,7 +59,6 @@ from .const import (
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_REASONING_SUMMARY,
|
||||
RECOMMENDED_STORE_RESPONSES,
|
||||
RECOMMENDED_STT_OPTIONS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
@@ -492,25 +490,6 @@ async def async_migrate_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) ->
|
||||
_add_stt_subentry(hass, entry)
|
||||
hass.config_entries.async_update_entry(entry, minor_version=6)
|
||||
|
||||
if entry.version == 2 and entry.minor_version == 6:
|
||||
for subentry in entry.subentries.values():
|
||||
if subentry.subentry_type in ("conversation", "ai_task_data"):
|
||||
data = dict(subentry.data)
|
||||
updated = False
|
||||
if data.get(CONF_REASONING_SUMMARY) == "short":
|
||||
data[CONF_REASONING_SUMMARY] = "concise"
|
||||
updated = True
|
||||
if data.get(CONF_REASONING_SUMMARY) == "concise" and not data.get(
|
||||
CONF_CHAT_MODEL, ""
|
||||
).startswith("gpt-5"):
|
||||
data[CONF_REASONING_SUMMARY] = RECOMMENDED_REASONING_SUMMARY
|
||||
updated = True
|
||||
if updated:
|
||||
hass.config_entries.async_update_subentry(
|
||||
entry, subentry, data=data
|
||||
)
|
||||
hass.config_entries.async_update_entry(entry, minor_version=7)
|
||||
|
||||
LOGGER.debug(
|
||||
"Migration to version %s:%s successful", entry.version, entry.minor_version
|
||||
)
|
||||
|
||||
@@ -127,7 +127,7 @@ class OpenAIConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for OpenAI Conversation."""
|
||||
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 7
|
||||
MINOR_VERSION = 6
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -435,37 +435,23 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
elif CONF_VERBOSITY in options:
|
||||
options.pop(CONF_VERBOSITY)
|
||||
|
||||
if model.startswith(("o", "gpt-5")):
|
||||
reasoning_summary_options = ["off", "auto", "concise", "detailed"]
|
||||
if model.startswith("o"):
|
||||
reasoning_summary_options.remove("concise")
|
||||
stored_summary = options.get(
|
||||
CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY
|
||||
)
|
||||
if stored_summary not in reasoning_summary_options:
|
||||
stored_summary = RECOMMENDED_REASONING_SUMMARY
|
||||
options[CONF_REASONING_SUMMARY] = stored_summary
|
||||
step_schema.update(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_REASONING_SUMMARY,
|
||||
default=stored_summary,
|
||||
default=RECOMMENDED_REASONING_SUMMARY,
|
||||
): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=reasoning_summary_options,
|
||||
options=["off", "auto", "short", "detailed"],
|
||||
translation_key=CONF_REASONING_SUMMARY,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
elif CONF_REASONING_SUMMARY in options:
|
||||
options.pop(CONF_REASONING_SUMMARY)
|
||||
elif CONF_VERBOSITY in options:
|
||||
options.pop(CONF_VERBOSITY)
|
||||
if CONF_REASONING_SUMMARY in options:
|
||||
if not model.startswith("gpt-5"):
|
||||
options.pop(CONF_REASONING_SUMMARY)
|
||||
|
||||
service_tiers = self._get_service_tiers(model)
|
||||
if "flex" in service_tiers or "priority" in service_tiers:
|
||||
|
||||
@@ -43,10 +43,7 @@ from openai.types.responses import (
|
||||
ToolParam,
|
||||
WebSearchToolParam,
|
||||
)
|
||||
from openai.types.responses.response_create_params import (
|
||||
Reasoning,
|
||||
ResponseCreateParamsStreaming,
|
||||
)
|
||||
from openai.types.responses.response_create_params import ResponseCreateParamsStreaming
|
||||
from openai.types.responses.response_input_param import (
|
||||
FunctionCallOutput,
|
||||
ImageGenerationCall as ImageGenerationCallParam,
|
||||
@@ -523,19 +520,16 @@ class OpenAIBaseLLMEntity(Entity):
|
||||
)
|
||||
|
||||
if model_args["model"].startswith(("o", "gpt-5")):
|
||||
reasoning: Reasoning = {
|
||||
model_args["reasoning"] = {
|
||||
"effort": options.get(
|
||||
CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT
|
||||
)
|
||||
if not model_args["model"].startswith("gpt-5-pro")
|
||||
else "high", # GPT-5 pro only supports reasoning.effort: high
|
||||
"summary": options.get(
|
||||
CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY
|
||||
),
|
||||
}
|
||||
reasoning_summary = options.get(
|
||||
CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY
|
||||
)
|
||||
if reasoning_summary != "off":
|
||||
reasoning["summary"] = reasoning_summary
|
||||
model_args["reasoning"] = reasoning
|
||||
model_args["include"] = ["reasoning.encrypted_content"]
|
||||
|
||||
if (
|
||||
|
||||
@@ -242,9 +242,9 @@
|
||||
"reasoning_summary": {
|
||||
"options": {
|
||||
"auto": "[%key:common::state::auto%]",
|
||||
"concise": "Concise",
|
||||
"detailed": "Detailed",
|
||||
"off": "[%key:common::state::off%]"
|
||||
"off": "[%key:common::state::off%]",
|
||||
"short": "Short"
|
||||
}
|
||||
},
|
||||
"search_context_size": {
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from openai import OpenAIError
|
||||
from propcache.api import cached_property
|
||||
@@ -166,15 +166,14 @@ class OpenAITTSEntity(TextToSpeechEntity, OpenAIBaseLLMEntity):
|
||||
client = self.entry.runtime_data
|
||||
|
||||
response_format = options[ATTR_PREFERRED_FORMAT]
|
||||
if response_format in ("ogg", "oga"):
|
||||
codec: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] = "opus"
|
||||
elif response_format == "raw":
|
||||
response_format = codec = "pcm"
|
||||
elif response_format not in self._supported_formats:
|
||||
response_format = self.default_options[ATTR_PREFERRED_FORMAT]
|
||||
codec = response_format
|
||||
else:
|
||||
codec = response_format
|
||||
if response_format not in self._supported_formats:
|
||||
# common aliases
|
||||
if response_format == "ogg":
|
||||
response_format = "opus"
|
||||
elif response_format == "raw":
|
||||
response_format = "pcm"
|
||||
else:
|
||||
response_format = self.default_options[ATTR_PREFERRED_FORMAT]
|
||||
|
||||
try:
|
||||
async with client.audio.speech.with_streaming_response.create(
|
||||
@@ -183,7 +182,7 @@ class OpenAITTSEntity(TextToSpeechEntity, OpenAIBaseLLMEntity):
|
||||
input=message,
|
||||
instructions=str(options.get(CONF_PROMPT)),
|
||||
speed=options.get(CONF_TTS_SPEED, RECOMMENDED_TTS_SPEED),
|
||||
response_format=codec,
|
||||
response_format=response_format,
|
||||
) as response:
|
||||
response_data = bytearray()
|
||||
async for chunk in response.iter_bytes():
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["opower"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["opower==0.18.2"]
|
||||
"requirements": ["opower==0.18.1"]
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
|
||||
.condition_for: &condition_for
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
|
||||
.power_units: &power_units
|
||||
- "mW"
|
||||
- "W"
|
||||
@@ -35,7 +29,6 @@ is_value:
|
||||
device_class: power
|
||||
fields:
|
||||
behavior: *condition_behavior
|
||||
for: *condition_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"condition_for_name": "For at least",
|
||||
"condition_threshold_name": "Threshold type",
|
||||
"trigger_behavior_name": "Trigger when",
|
||||
"trigger_for_name": "For at least",
|
||||
@@ -14,9 +13,6 @@
|
||||
"behavior": {
|
||||
"name": "[%key:component::power::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::power::common::condition_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::power::common::condition_threshold_name%]"
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["prowl"],
|
||||
"requirements": ["prowlpy==1.1.5"]
|
||||
"requirements": ["prowlpy==1.1.1"]
|
||||
}
|
||||
|
||||
@@ -97,19 +97,6 @@ def _get_nodes_data(data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
verify_ssl=data.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL),
|
||||
**auth_kwargs,
|
||||
)
|
||||
except AuthenticationError as err:
|
||||
raise ProxmoxAuthenticationError from err
|
||||
except SSLError as err:
|
||||
raise ProxmoxSSLError from err
|
||||
except ConnectTimeout as err:
|
||||
raise ProxmoxConnectTimeout from err
|
||||
except ResourceException as err:
|
||||
_LOGGER.debug("Error during Proxmox client initialisation", exc_info=True)
|
||||
raise ProxmoxInitFailed from err
|
||||
except requests.exceptions.ConnectionError as err:
|
||||
raise ProxmoxConnectionError from err
|
||||
|
||||
try:
|
||||
nodes = client.nodes.get()
|
||||
except AuthenticationError as err:
|
||||
raise ProxmoxAuthenticationError from err
|
||||
@@ -118,7 +105,6 @@ def _get_nodes_data(data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
except ConnectTimeout as err:
|
||||
raise ProxmoxConnectTimeout from err
|
||||
except ResourceException as err:
|
||||
_LOGGER.debug("Error fetching nodes", exc_info=True)
|
||||
raise ProxmoxNoNodesFound from err
|
||||
except requests.exceptions.ConnectionError as err:
|
||||
raise ProxmoxConnectionError from err
|
||||
@@ -129,10 +115,7 @@ def _get_nodes_data(data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
vms = client.nodes(node["node"]).qemu.get()
|
||||
containers = client.nodes(node["node"]).lxc.get()
|
||||
except ResourceException as err:
|
||||
_LOGGER.debug(
|
||||
"Error fetching VMs/LXC for node %s", node["node"], exc_info=True
|
||||
)
|
||||
raise ProxmoxNoVMLXCFound from err
|
||||
raise ProxmoxNoNodesFound from err
|
||||
except requests.exceptions.ConnectionError as err:
|
||||
raise ProxmoxConnectionError from err
|
||||
|
||||
@@ -315,15 +298,9 @@ class ProxmoxveConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except ProxmoxSSLError as exc:
|
||||
errors["base"] = "ssl_error"
|
||||
err = exc
|
||||
except ProxmoxInitFailed as exc:
|
||||
errors["base"] = "api_error_no_details"
|
||||
err = exc
|
||||
except ProxmoxNoNodesFound as exc:
|
||||
errors["base"] = "no_nodes_found"
|
||||
err = exc
|
||||
except ProxmoxNoVMLXCFound as exc:
|
||||
errors["base"] = "no_vmlxc_found"
|
||||
err = exc
|
||||
except ProxmoxConnectionError as exc:
|
||||
errors["base"] = "cannot_connect"
|
||||
err = exc
|
||||
@@ -393,14 +370,6 @@ class ProxmoxNoNodesFound(ProxmoxError):
|
||||
"""Error to indicate no nodes found."""
|
||||
|
||||
|
||||
class ProxmoxNoVMLXCFound(ProxmoxError):
|
||||
"""Error to indicate no LXC or VM found."""
|
||||
|
||||
|
||||
class ProxmoxInitFailed(ProxmoxError):
|
||||
"""Error to indicate API initialisation failure."""
|
||||
|
||||
|
||||
class ProxmoxConnectTimeout(ProxmoxError):
|
||||
"""Error to indicate a connection timeout."""
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"error": {
|
||||
"api_error_no_details": "An error occurred while communicating with the Proxmox VE instance.",
|
||||
"cannot_connect": "Cannot connect to Proxmox VE server",
|
||||
"connect_timeout": "[%key:common::config_flow::error::timeout_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"no_nodes_found": "No active nodes were found on the Proxmox VE server.",
|
||||
"no_vmlxc_found": "No LXC or VM were found on the Proxmox VE server.",
|
||||
"no_nodes_found": "No active nodes found",
|
||||
"ssl_error": "SSL check failed. Check the SSL settings"
|
||||
},
|
||||
"step": {
|
||||
@@ -326,9 +324,6 @@
|
||||
"no_permission_vm_lxc_power": {
|
||||
"message": "The configured Proxmox VE user does not have permission to manage the power state of VMs and containers. Please grant the user the 'VM.PowerMgmt' permission and try again."
|
||||
},
|
||||
"no_vmlxc_found": {
|
||||
"message": "No LXC or VM were found on the Proxmox VE server."
|
||||
},
|
||||
"permissions_error": {
|
||||
"message": "Failed to retrieve Proxmox VE permissions. Please check your credentials and try again."
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ from .coordinator import (
|
||||
PrusaLinkConfigEntry,
|
||||
PrusaLinkUpdateCoordinator,
|
||||
StatusCoordinator,
|
||||
VersionUpdateCoordinator,
|
||||
)
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
@@ -54,6 +55,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: PrusaLinkConfigEntry) ->
|
||||
"status": StatusCoordinator(hass, entry, api),
|
||||
"job": JobUpdateCoordinator(hass, entry, api),
|
||||
"info": InfoUpdateCoordinator(hass, entry, api),
|
||||
"version": VersionUpdateCoordinator(hass, entry, api),
|
||||
}
|
||||
for coordinator in coordinators.values():
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -16,6 +16,7 @@ from pyprusalink import (
|
||||
PrinterInfo,
|
||||
PrinterStatus,
|
||||
PrusaLink,
|
||||
VersionInfo,
|
||||
)
|
||||
from pyprusalink.types import InvalidAuth, PrusaLinkError
|
||||
|
||||
@@ -32,7 +33,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# rapidly-changing metrics.
|
||||
_MINIMUM_REFRESH_INTERVAL = 1.0
|
||||
|
||||
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo)
|
||||
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo, PrinterInfo, VersionInfo)
|
||||
|
||||
|
||||
type PrusaLinkConfigEntry = ConfigEntry[dict[str, PrusaLinkUpdateCoordinator]]
|
||||
@@ -124,3 +125,11 @@ class InfoUpdateCoordinator(PrusaLinkUpdateCoordinator[PrinterInfo]):
|
||||
async def _fetch_data(self) -> PrinterInfo:
|
||||
"""Fetch the printer data."""
|
||||
return await self.api.get_info()
|
||||
|
||||
|
||||
class VersionUpdateCoordinator(PrusaLinkUpdateCoordinator[VersionInfo]):
|
||||
"""Version update coordinator."""
|
||||
|
||||
async def _fetch_data(self) -> VersionInfo:
|
||||
"""Fetch the version data."""
|
||||
return await self.api.get_version()
|
||||
|
||||
@@ -17,9 +17,14 @@ class PrusaLinkEntity(CoordinatorEntity[PrusaLinkUpdateCoordinator]):
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device information about this PrusaLink device."""
|
||||
coordinators = self.coordinator.config_entry.runtime_data
|
||||
info_data = coordinators["info"].data or {}
|
||||
version_data = coordinators["version"].data or {}
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.coordinator.config_entry.entry_id)},
|
||||
name=self.coordinator.config_entry.title,
|
||||
manufacturer="Prusa",
|
||||
serial_number=info_data.get("serial"),
|
||||
sw_version=version_data.get("firmware"),
|
||||
configuration_url=self.coordinator.api.client.host,
|
||||
)
|
||||
|
||||
@@ -7,8 +7,8 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from . import DOMAIN
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF, support_duration=True),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON, support_duration=True),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
},
|
||||
"error": {
|
||||
"auth_missing": "[%key:component::samsungtv::config::abort::auth_missing%]",
|
||||
"invalid_host": "[%key:common::config_flow::error::invalid_host%]",
|
||||
"invalid_pin": "The PIN is invalid. Please try again."
|
||||
"invalid_host": "Host is invalid, please try again.",
|
||||
"invalid_pin": "PIN is invalid, please try again."
|
||||
},
|
||||
"flow_title": "{device}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Do you want to set up {device}? If you have never connected Home Assistant before, you should see a popup on your TV asking for authorization."
|
||||
"description": "Do you want to set up {device}? If you never connected Home Assistant before you should see a popup on your TV asking for authorization."
|
||||
},
|
||||
"encrypted_pairing": {
|
||||
"data": {
|
||||
@@ -62,7 +62,7 @@
|
||||
"host": "The hostname or IP address of your TV.",
|
||||
"name": "The name of your TV. This will be used to identify the device in Home Assistant."
|
||||
},
|
||||
"description": "Enter your Samsung TV information. If you have never connected Home Assistant before, you should see a popup on your TV asking for authorization."
|
||||
"description": "Enter your Samsung TV information. If you never connected Home Assistant before you should see a popup on your TV asking for authorization."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["satel_integra"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["satel-integra==1.3.0"]
|
||||
"requirements": ["satel-integra==1.2.2"]
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ from homeassistant.helpers.condition import Condition, make_entity_state_conditi
|
||||
from .const import DOMAIN
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF, support_duration=True),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON, support_duration=True),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
entity_domain=LOCK_DOMAIN,
|
||||
schema={
|
||||
vol.Required("name"): cv.string,
|
||||
vol.Required("code"): cv.matches_regex(r"^\d{4,8}$"),
|
||||
vol.Required("code"): vol.All(cv.string, cv.matches_regex(r"^\d{4,8}$")),
|
||||
vol.Optional("notify_on_use", default=True): cv.boolean,
|
||||
},
|
||||
func=SERVICE_ADD_CODE,
|
||||
|
||||
@@ -62,7 +62,6 @@ async def async_setup_platform(
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=None,
|
||||
name="schluter",
|
||||
update_method=async_update_data,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
|
||||
@@ -768,13 +768,13 @@ class ScriptEntity(BaseScriptEntity, RestoreEntity):
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Stop script and remove service when it will be removed from HA."""
|
||||
self.hass.services.async_remove(DOMAIN, self._attr_unique_id)
|
||||
await self.script.async_stop()
|
||||
if not self.registry_entry or self.registry_entry.entity_id == self.entity_id:
|
||||
# Entity ID not changed, unload the script as it will not be reused.
|
||||
self.script.async_unload()
|
||||
|
||||
if self.registry_entry and self.registry_entry.entity_id != self.entity_id:
|
||||
# Entity ID change, do not unload the script as it will be reused.
|
||||
await self.script.async_stop()
|
||||
return
|
||||
await self.script.async_unload()
|
||||
# remove service
|
||||
self.hass.services.async_remove(DOMAIN, self._attr_unique_id)
|
||||
|
||||
|
||||
@websocket_api.websocket_command({"type": "script/config", "entity_id": str})
|
||||
|
||||
@@ -10,11 +10,6 @@ is_option_selected:
|
||||
selector:
|
||||
automation_behavior:
|
||||
mode: condition
|
||||
for:
|
||||
required: true
|
||||
default: 00:00:00
|
||||
selector:
|
||||
duration:
|
||||
option:
|
||||
context:
|
||||
filter_target: target
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user