mirror of
https://github.com/home-assistant/core.git
synced 2025-08-06 14:15:12 +02:00
2023.7.2 (#96487)
This commit is contained in:
@@ -182,7 +182,6 @@ omit =
|
|||||||
homeassistant/components/crownstone/listeners.py
|
homeassistant/components/crownstone/listeners.py
|
||||||
homeassistant/components/cups/sensor.py
|
homeassistant/components/cups/sensor.py
|
||||||
homeassistant/components/currencylayer/sensor.py
|
homeassistant/components/currencylayer/sensor.py
|
||||||
homeassistant/components/daikin/__init__.py
|
|
||||||
homeassistant/components/daikin/climate.py
|
homeassistant/components/daikin/climate.py
|
||||||
homeassistant/components/daikin/sensor.py
|
homeassistant/components/daikin/sensor.py
|
||||||
homeassistant/components/daikin/switch.py
|
homeassistant/components/daikin/switch.py
|
||||||
|
@@ -42,6 +42,7 @@ class BlinkSyncModule(AlarmControlPanelEntity):
|
|||||||
_attr_icon = ICON
|
_attr_icon = ICON
|
||||||
_attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
|
_attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(self, data, name, sync):
|
def __init__(self, data, name, sync):
|
||||||
"""Initialize the alarm control panel."""
|
"""Initialize the alarm control panel."""
|
||||||
|
@@ -58,13 +58,14 @@ async def async_setup_entry(
|
|||||||
class BlinkBinarySensor(BinarySensorEntity):
|
class BlinkBinarySensor(BinarySensorEntity):
|
||||||
"""Representation of a Blink binary sensor."""
|
"""Representation of a Blink binary sensor."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, data, camera, description: BinarySensorEntityDescription
|
self, data, camera, description: BinarySensorEntityDescription
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.data = data
|
self.data = data
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_name = f"{DOMAIN} {camera} {description.name}"
|
|
||||||
self._camera = data.cameras[camera]
|
self._camera = data.cameras[camera]
|
||||||
self._attr_unique_id = f"{self._camera.serial}-{description.key}"
|
self._attr_unique_id = f"{self._camera.serial}-{description.key}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
|
@@ -38,6 +38,7 @@ async def async_setup_entry(
|
|||||||
class BlinkCamera(Camera):
|
class BlinkCamera(Camera):
|
||||||
"""An implementation of a Blink Camera."""
|
"""An implementation of a Blink Camera."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, data, name, camera):
|
def __init__(self, data, name, camera):
|
||||||
|
@@ -20,5 +20,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/bthome",
|
"documentation": "https://www.home-assistant.io/integrations/bthome",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"requirements": ["bthome-ble==2.12.0"]
|
"requirements": ["bthome-ble==2.12.1"]
|
||||||
}
|
}
|
||||||
|
@@ -301,55 +301,55 @@
|
|||||||
"name": "Condition 1d",
|
"name": "Condition 1d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"condition_2d": {
|
"condition_2d": {
|
||||||
"name": "Condition 2d",
|
"name": "Condition 2d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"condition_3d": {
|
"condition_3d": {
|
||||||
"name": "Condition 3d",
|
"name": "Condition 3d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"condition_4d": {
|
"condition_4d": {
|
||||||
"name": "Condition 4d",
|
"name": "Condition 4d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"condition_5d": {
|
"condition_5d": {
|
||||||
"name": "Condition 5d",
|
"name": "Condition 5d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditioncode_1d": {
|
"conditioncode_1d": {
|
||||||
@@ -371,76 +371,76 @@
|
|||||||
"name": "Detailed condition 1d",
|
"name": "Detailed condition 1d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditiondetailed_2d": {
|
"conditiondetailed_2d": {
|
||||||
"name": "Detailed condition 2d",
|
"name": "Detailed condition 2d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditiondetailed_3d": {
|
"conditiondetailed_3d": {
|
||||||
"name": "Detailed condition 3d",
|
"name": "Detailed condition 3d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditiondetailed_4d": {
|
"conditiondetailed_4d": {
|
||||||
"name": "Detailed condition 4d",
|
"name": "Detailed condition 4d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||||
@@ -455,21 +455,21 @@
|
|||||||
"name": "Detailed condition 5d",
|
"name": "Detailed condition 5d",
|
||||||
"state": {
|
"state": {
|
||||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditionexact_1d": {
|
"conditionexact_1d": {
|
||||||
|
@@ -15,8 +15,9 @@ from homeassistant.const import (
|
|||||||
CONF_UUID,
|
CONF_UUID,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||||
@@ -52,6 +53,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
if not daikin_api:
|
if not daikin_api:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
await async_migrate_unique_id(hass, entry, daikin_api)
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api})
|
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api})
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
@@ -67,7 +70,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
async def daikin_api_setup(hass, host, key, uuid, password):
|
async def daikin_api_setup(hass: HomeAssistant, host, key, uuid, password):
|
||||||
"""Create a Daikin instance only once."""
|
"""Create a Daikin instance only once."""
|
||||||
|
|
||||||
session = async_get_clientsession(hass)
|
session = async_get_clientsession(hass)
|
||||||
@@ -127,3 +130,82 @@ class DaikinApi:
|
|||||||
name=info.get("name"),
|
name=info.get("name"),
|
||||||
sw_version=info.get("ver", "").replace("_", "."),
|
sw_version=info.get("ver", "").replace("_", "."),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_unique_id(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry, api: DaikinApi
|
||||||
|
) -> None:
|
||||||
|
"""Migrate old entry."""
|
||||||
|
dev_reg = dr.async_get(hass)
|
||||||
|
old_unique_id = config_entry.unique_id
|
||||||
|
new_unique_id = api.device.mac
|
||||||
|
new_name = api.device.values["name"]
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
|
||||||
|
"""Update unique ID of entity entry."""
|
||||||
|
return update_unique_id(entity_entry, new_unique_id)
|
||||||
|
|
||||||
|
if new_unique_id == old_unique_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Migrate devices
|
||||||
|
for device_entry in dr.async_entries_for_config_entry(
|
||||||
|
dev_reg, config_entry.entry_id
|
||||||
|
):
|
||||||
|
for connection in device_entry.connections:
|
||||||
|
if connection[1] == old_unique_id:
|
||||||
|
new_connections = {
|
||||||
|
(CONNECTION_NETWORK_MAC, dr.format_mac(new_unique_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrating device %s connections to %s",
|
||||||
|
device_entry.name,
|
||||||
|
new_connections,
|
||||||
|
)
|
||||||
|
dev_reg.async_update_device(
|
||||||
|
device_entry.id,
|
||||||
|
merge_connections=new_connections,
|
||||||
|
)
|
||||||
|
|
||||||
|
if device_entry.name is None:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrating device name to %s",
|
||||||
|
new_name,
|
||||||
|
)
|
||||||
|
dev_reg.async_update_device(
|
||||||
|
device_entry.id,
|
||||||
|
name=new_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Migrate entities
|
||||||
|
await er.async_migrate_entries(hass, config_entry.entry_id, _update_unique_id)
|
||||||
|
|
||||||
|
new_data = {**config_entry.data, KEY_MAC: dr.format_mac(new_unique_id)}
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
config_entry, unique_id=new_unique_id, data=new_data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_unique_id(
|
||||||
|
entity_entry: er.RegistryEntry, unique_id: str
|
||||||
|
) -> dict[str, str] | None:
|
||||||
|
"""Update unique ID of entity entry."""
|
||||||
|
if entity_entry.unique_id.startswith(unique_id):
|
||||||
|
# Already correct, nothing to do
|
||||||
|
return None
|
||||||
|
|
||||||
|
unique_id_parts = entity_entry.unique_id.split("-")
|
||||||
|
unique_id_parts[0] = unique_id
|
||||||
|
entity_new_unique_id = "-".join(unique_id_parts)
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrating entity %s from %s to new id %s",
|
||||||
|
entity_entry.entity_id,
|
||||||
|
entity_entry.unique_id,
|
||||||
|
entity_new_unique_id,
|
||||||
|
)
|
||||||
|
return {"new_unique_id": entity_new_unique_id}
|
||||||
|
@@ -7,6 +7,6 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pydaikin"],
|
"loggers": ["pydaikin"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pydaikin==2.9.0"],
|
"requirements": ["pydaikin==2.10.5"],
|
||||||
"zeroconf": ["_dkapi._tcp.local."]
|
"zeroconf": ["_dkapi._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ async def async_setup_entry(
|
|||||||
[
|
[
|
||||||
DaikinZoneSwitch(daikin_api, zone_id)
|
DaikinZoneSwitch(daikin_api, zone_id)
|
||||||
for zone_id, zone in enumerate(zones)
|
for zone_id, zone in enumerate(zones)
|
||||||
if zone != ("-", "0")
|
if zone[0] != "-"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if daikin_api.device.support_advanced_modes:
|
if daikin_api.device.support_advanced_modes:
|
||||||
|
@@ -60,7 +60,6 @@ class ServiceDetails(NamedTuple):
|
|||||||
SERVICE_HANDLERS = {
|
SERVICE_HANDLERS = {
|
||||||
SERVICE_ENIGMA2: ServiceDetails("media_player", "enigma2"),
|
SERVICE_ENIGMA2: ServiceDetails("media_player", "enigma2"),
|
||||||
"yamaha": ServiceDetails("media_player", "yamaha"),
|
"yamaha": ServiceDetails("media_player", "yamaha"),
|
||||||
"openhome": ServiceDetails("media_player", "openhome"),
|
|
||||||
"bluesound": ServiceDetails("media_player", "bluesound"),
|
"bluesound": ServiceDetails("media_player", "bluesound"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +86,7 @@ MIGRATED_SERVICE_HANDLERS = [
|
|||||||
SERVICE_MOBILE_APP,
|
SERVICE_MOBILE_APP,
|
||||||
SERVICE_NETGEAR,
|
SERVICE_NETGEAR,
|
||||||
SERVICE_OCTOPRINT,
|
SERVICE_OCTOPRINT,
|
||||||
|
"openhome",
|
||||||
"philips_hue",
|
"philips_hue",
|
||||||
SERVICE_SAMSUNG_PRINTER,
|
SERVICE_SAMSUNG_PRINTER,
|
||||||
"sonos",
|
"sonos",
|
||||||
|
@@ -76,6 +76,7 @@ class ControllerEntity(ClimateEntity):
|
|||||||
|
|
||||||
_attr_fan_modes = list(_HA_FAN_TO_ESCEA)
|
_attr_fan_modes = list(_HA_FAN_TO_ESCEA)
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||||
_attr_icon = ICON
|
_attr_icon = ICON
|
||||||
_attr_precision = PRECISION_WHOLE
|
_attr_precision = PRECISION_WHOLE
|
||||||
|
@@ -388,6 +388,7 @@ async def async_setup_entry( # noqa: C901
|
|||||||
assert cli.api_version is not None
|
assert cli.api_version is not None
|
||||||
entry_data.api_version = cli.api_version
|
entry_data.api_version = cli.api_version
|
||||||
entry_data.available = True
|
entry_data.available = True
|
||||||
|
entry_data.expected_disconnect = True
|
||||||
if entry_data.device_info.name:
|
if entry_data.device_info.name:
|
||||||
reconnect_logic.name = entry_data.device_info.name
|
reconnect_logic.name = entry_data.device_info.name
|
||||||
|
|
||||||
|
@@ -34,7 +34,7 @@ from .const import (
|
|||||||
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS,
|
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from .dashboard import async_get_dashboard, async_set_dashboard_info
|
from .dashboard import async_get_or_create_dashboard_manager, async_set_dashboard_info
|
||||||
|
|
||||||
ERROR_REQUIRES_ENCRYPTION_KEY = "requires_encryption_key"
|
ERROR_REQUIRES_ENCRYPTION_KEY = "requires_encryption_key"
|
||||||
ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk"
|
ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk"
|
||||||
@@ -391,7 +391,9 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
self._device_name is None
|
self._device_name is None
|
||||||
or (dashboard := async_get_dashboard(self.hass)) is None
|
or (manager := await async_get_or_create_dashboard_manager(self.hass))
|
||||||
|
is None
|
||||||
|
or (dashboard := manager.async_get()) is None
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@@ -93,13 +93,6 @@ class ESPHomeDashboardManager:
|
|||||||
hass, addon_slug, url, async_get_clientsession(hass)
|
hass, addon_slug, url, async_get_clientsession(hass)
|
||||||
)
|
)
|
||||||
await dashboard.async_request_refresh()
|
await dashboard.async_request_refresh()
|
||||||
if not cur_dashboard and not dashboard.last_update_success:
|
|
||||||
# If there was no previous dashboard and the new one is not available,
|
|
||||||
# we skip setup and wait for discovery.
|
|
||||||
_LOGGER.error(
|
|
||||||
"Dashboard unavailable; skipping setup: %s", dashboard.last_exception
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._current_dashboard = dashboard
|
self._current_dashboard = dashboard
|
||||||
|
|
||||||
@@ -143,7 +136,14 @@ class ESPHomeDashboardManager:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_get_dashboard(hass: HomeAssistant) -> ESPHomeDashboard | None:
|
def async_get_dashboard(hass: HomeAssistant) -> ESPHomeDashboard | None:
|
||||||
"""Get an instance of the dashboard if set."""
|
"""Get an instance of the dashboard if set.
|
||||||
|
|
||||||
|
This is only safe to call after `async_setup` has been completed.
|
||||||
|
|
||||||
|
It should not be called from the config flow because there is a race
|
||||||
|
where manager can be an asyncio.Event instead of the actual manager
|
||||||
|
because the singleton decorator is not yet done.
|
||||||
|
"""
|
||||||
manager: ESPHomeDashboardManager | None = hass.data.get(KEY_DASHBOARD_MANAGER)
|
manager: ESPHomeDashboardManager | None = hass.data.get(KEY_DASHBOARD_MANAGER)
|
||||||
return manager.async_get() if manager else None
|
return manager.async_get() if manager else None
|
||||||
|
|
||||||
|
@@ -30,9 +30,6 @@ async def async_setup_entry(
|
|||||||
avm_wrapper.fritz_guest_wifi.get_info
|
avm_wrapper.fritz_guest_wifi.get_info
|
||||||
)
|
)
|
||||||
|
|
||||||
if not guest_wifi_info.get("NewEnable"):
|
|
||||||
return
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
FritzGuestWifiQRImage(
|
FritzGuestWifiQRImage(
|
||||||
|
@@ -16,5 +16,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["goalzero"],
|
"loggers": ["goalzero"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["goalzero==0.2.1"]
|
"requirements": ["goalzero==0.2.2"]
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiohomekit", "commentjson"],
|
"loggers": ["aiohomekit", "commentjson"],
|
||||||
"requirements": ["aiohomekit==2.6.5"],
|
"requirements": ["aiohomekit==2.6.7"],
|
||||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -43,6 +43,7 @@ class LEDBLEEntity(CoordinatorEntity, LightEntity):
|
|||||||
|
|
||||||
_attr_supported_color_modes = {ColorMode.RGB, ColorMode.WHITE}
|
_attr_supported_color_modes = {ColorMode.RGB, ColorMode.WHITE}
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
_attr_supported_features = LightEntityFeature.EFFECT
|
_attr_supported_features = LightEntityFeature.EFFECT
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@@ -22,6 +22,24 @@ PLATFORMS_BULB = [Platform.LIGHT]
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_get_device_state(
|
||||||
|
device: MyStromSwitch | MyStromBulb, ip_address: str
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
await device.get_state()
|
||||||
|
except MyStromConnectionError as err:
|
||||||
|
_LOGGER.error("No route to myStrom plug: %s", ip_address)
|
||||||
|
raise ConfigEntryNotReady() from err
|
||||||
|
|
||||||
|
|
||||||
|
def _get_mystrom_bulb(host: str, mac: str) -> MyStromBulb:
|
||||||
|
return MyStromBulb(host, mac)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_mystrom_switch(host: str) -> MyStromSwitch:
|
||||||
|
return MyStromSwitch(host)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up myStrom from a config entry."""
|
"""Set up myStrom from a config entry."""
|
||||||
host = entry.data[CONF_HOST]
|
host = entry.data[CONF_HOST]
|
||||||
@@ -32,14 +50,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
_LOGGER.error("No route to myStrom plug: %s", host)
|
_LOGGER.error("No route to myStrom plug: %s", host)
|
||||||
raise ConfigEntryNotReady() from err
|
raise ConfigEntryNotReady() from err
|
||||||
|
|
||||||
|
info.setdefault("type", 101)
|
||||||
|
|
||||||
device_type = info["type"]
|
device_type = info["type"]
|
||||||
if device_type in [101, 106, 107]:
|
if device_type in [101, 106, 107]:
|
||||||
device = MyStromSwitch(host)
|
device = _get_mystrom_switch(host)
|
||||||
platforms = PLATFORMS_SWITCH
|
platforms = PLATFORMS_SWITCH
|
||||||
elif device_type == 102:
|
await _async_get_device_state(device, info["ip"])
|
||||||
|
elif device_type in [102, 105]:
|
||||||
mac = info["mac"]
|
mac = info["mac"]
|
||||||
device = MyStromBulb(host, mac)
|
device = _get_mystrom_bulb(host, mac)
|
||||||
platforms = PLATFORMS_BULB
|
platforms = PLATFORMS_BULB
|
||||||
|
await _async_get_device_state(device, info["ip"])
|
||||||
if device.bulb_type not in ["rgblamp", "strip"]:
|
if device.bulb_type not in ["rgblamp", "strip"]:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Device %s (%s) is not a myStrom bulb nor myStrom LED Strip",
|
"Device %s (%s) is not a myStrom bulb nor myStrom LED Strip",
|
||||||
@@ -51,12 +73,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
_LOGGER.error("Unsupported myStrom device type: %s", device_type)
|
_LOGGER.error("Unsupported myStrom device type: %s", device_type)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
|
||||||
await device.get_state()
|
|
||||||
except MyStromConnectionError as err:
|
|
||||||
_LOGGER.error("No route to myStrom plug: %s", info["ip"])
|
|
||||||
raise ConfigEntryNotReady() from err
|
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = MyStromData(
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = MyStromData(
|
||||||
device=device,
|
device=device,
|
||||||
info=info,
|
info=info,
|
||||||
@@ -69,10 +85,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
device_type = hass.data[DOMAIN][entry.entry_id].info["type"]
|
device_type = hass.data[DOMAIN][entry.entry_id].info["type"]
|
||||||
|
platforms = []
|
||||||
if device_type in [101, 106, 107]:
|
if device_type in [101, 106, 107]:
|
||||||
platforms = PLATFORMS_SWITCH
|
platforms.extend(PLATFORMS_SWITCH)
|
||||||
elif device_type == 102:
|
elif device_type in [102, 105]:
|
||||||
platforms = PLATFORMS_BULB
|
platforms.extend(PLATFORMS_BULB)
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/python_script",
|
"documentation": "https://www.home-assistant.io/integrations/python_script",
|
||||||
"loggers": ["RestrictedPython"],
|
"loggers": ["RestrictedPython"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["RestrictedPython==6.0"]
|
"requirements": ["RestrictedPython==6.1"]
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pyrainbird.async_client import AsyncRainbirdClient, AsyncRainbirdController
|
from pyrainbird.async_client import AsyncRainbirdClient, AsyncRainbirdController
|
||||||
|
from pyrainbird.exceptions import RainbirdApiException
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import CONF_SERIAL_NUMBER
|
from .const import CONF_SERIAL_NUMBER
|
||||||
@@ -29,11 +31,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
entry.data[CONF_PASSWORD],
|
entry.data[CONF_PASSWORD],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
model_info = await controller.get_model_and_version()
|
||||||
|
except RainbirdApiException as err:
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
coordinator = RainbirdUpdateCoordinator(
|
coordinator = RainbirdUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
name=entry.title,
|
name=entry.title,
|
||||||
controller=controller,
|
controller=controller,
|
||||||
serial_number=entry.data[CONF_SERIAL_NUMBER],
|
serial_number=entry.data[CONF_SERIAL_NUMBER],
|
||||||
|
model_info=model_info,
|
||||||
)
|
)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ from typing import TypeVar
|
|||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from pyrainbird.async_client import AsyncRainbirdController, RainbirdApiException
|
from pyrainbird.async_client import AsyncRainbirdController, RainbirdApiException
|
||||||
|
from pyrainbird.data import ModelAndVersion
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
@@ -42,6 +43,7 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
|
|||||||
name: str,
|
name: str,
|
||||||
controller: AsyncRainbirdController,
|
controller: AsyncRainbirdController,
|
||||||
serial_number: str,
|
serial_number: str,
|
||||||
|
model_info: ModelAndVersion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ZoneStateUpdateCoordinator."""
|
"""Initialize ZoneStateUpdateCoordinator."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -54,6 +56,7 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
|
|||||||
self._controller = controller
|
self._controller = controller
|
||||||
self._serial_number = serial_number
|
self._serial_number = serial_number
|
||||||
self._zones: set[int] | None = None
|
self._zones: set[int] | None = None
|
||||||
|
self._model_info = model_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def controller(self) -> AsyncRainbirdController:
|
def controller(self) -> AsyncRainbirdController:
|
||||||
@@ -72,6 +75,8 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
|
|||||||
default_name=f"{MANUFACTURER} Controller",
|
default_name=f"{MANUFACTURER} Controller",
|
||||||
identifiers={(DOMAIN, self._serial_number)},
|
identifiers={(DOMAIN, self._serial_number)},
|
||||||
manufacturer=MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
|
model=self._model_info.model_name,
|
||||||
|
sw_version=f"{self._model_info.major}.{self._model_info.minor}",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_update_data(self) -> RainbirdDeviceState:
|
async def _async_update_data(self) -> RainbirdDeviceState:
|
||||||
|
@@ -62,6 +62,7 @@ class RainMachineUpdateEntity(RainMachineEntity, UpdateEntity):
|
|||||||
"""Define a RainMachine update entity."""
|
"""Define a RainMachine update entity."""
|
||||||
|
|
||||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||||
|
_attr_name = None
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
UpdateEntityFeature.INSTALL
|
UpdateEntityFeature.INSTALL
|
||||||
| UpdateEntityFeature.PROGRESS
|
| UpdateEntityFeature.PROGRESS
|
||||||
|
@@ -18,5 +18,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["reolink_aio"],
|
"loggers": ["reolink_aio"],
|
||||||
"requirements": ["reolink-aio==0.7.1"]
|
"requirements": ["reolink-aio==0.7.3"]
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,7 @@ SELECT_ENTITIES = (
|
|||||||
icon="mdi:spotlight-beam",
|
icon="mdi:spotlight-beam",
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
translation_key="floodlight_mode",
|
translation_key="floodlight_mode",
|
||||||
get_options=[mode.name for mode in SpotlightModeEnum],
|
get_options=lambda api, ch: api.whiteled_mode_list(ch),
|
||||||
supported=lambda api, ch: api.supported(ch, "floodLight"),
|
supported=lambda api, ch: api.supported(ch, "floodLight"),
|
||||||
value=lambda api, ch: SpotlightModeEnum(api.whiteled_mode(ch)).name,
|
value=lambda api, ch: SpotlightModeEnum(api.whiteled_mode(ch)).name,
|
||||||
method=lambda api, ch, name: api.set_whiteled(ch, mode=name),
|
method=lambda api, ch, name: api.set_whiteled(ch, mode=name),
|
||||||
|
@@ -62,7 +62,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"off": "Off",
|
"off": "Off",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"schedule": "Schedule"
|
"schedule": "Schedule",
|
||||||
|
"adaptive": "Adaptive",
|
||||||
|
"autoadaptive": "Auto adaptive"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"day_night_mode": {
|
"day_night_mode": {
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioridwell"],
|
"loggers": ["aioridwell"],
|
||||||
"requirements": ["aioridwell==2023.01.0"]
|
"requirements": ["aioridwell==2023.07.0"]
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["roborock"],
|
"loggers": ["roborock"],
|
||||||
"requirements": ["python-roborock==0.29.2"]
|
"requirements": ["python-roborock==0.30.0"]
|
||||||
}
|
}
|
||||||
|
@@ -90,6 +90,7 @@ class SlimProtoPlayer(MediaPlayerEntity):
|
|||||||
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||||
)
|
)
|
||||||
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
|
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, slimserver: SlimServer, player: SlimClient) -> None:
|
def __init__(self, slimserver: SlimServer, player: SlimClient) -> None:
|
||||||
"""Initialize MediaPlayer entity."""
|
"""Initialize MediaPlayer entity."""
|
||||||
|
@@ -33,6 +33,7 @@ class StookwijzerSensor(SensorEntity):
|
|||||||
_attr_attribution = "Data provided by stookwijzer.nu"
|
_attr_attribution = "Data provided by stookwijzer.nu"
|
||||||
_attr_device_class = SensorDeviceClass.ENUM
|
_attr_device_class = SensorDeviceClass.ENUM
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
_attr_translation_key = "stookwijzer"
|
_attr_translation_key = "stookwijzer"
|
||||||
|
|
||||||
def __init__(self, client: Stookwijzer, entry: ConfigEntry) -> None:
|
def __init__(self, client: Stookwijzer, entry: ConfigEntry) -> None:
|
||||||
|
@@ -122,6 +122,7 @@ class SwitchBotBlindTiltEntity(SwitchbotEntity, CoverEntity, RestoreEntity):
|
|||||||
| CoverEntityFeature.STOP_TILT
|
| CoverEntityFeature.STOP_TILT
|
||||||
| CoverEntityFeature.SET_TILT_POSITION
|
| CoverEntityFeature.SET_TILT_POSITION
|
||||||
)
|
)
|
||||||
|
_attr_name = None
|
||||||
_attr_translation_key = "cover"
|
_attr_translation_key = "cover"
|
||||||
CLOSED_UP_THRESHOLD = 80
|
CLOSED_UP_THRESHOLD = 80
|
||||||
CLOSED_DOWN_THRESHOLD = 20
|
CLOSED_DOWN_THRESHOLD = 20
|
||||||
|
@@ -49,9 +49,10 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
|
|
||||||
class UpbLight(UpbAttachedEntity, LightEntity):
|
class UpbLight(UpbAttachedEntity, LightEntity):
|
||||||
"""Representation of an UPB Light."""
|
"""Representation of a UPB Light."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, element, unique_id, upb):
|
def __init__(self, element, unique_id, upb):
|
||||||
"""Initialize an UpbLight."""
|
"""Initialize an UpbLight."""
|
||||||
|
@@ -47,7 +47,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
|
|
||||||
class UpbLink(UpbEntity, Scene):
|
class UpbLink(UpbEntity, Scene):
|
||||||
"""Representation of an UPB Link."""
|
"""Representation of a UPB Link."""
|
||||||
|
|
||||||
def __init__(self, element, unique_id, upb):
|
def __init__(self, element, unique_id, upb):
|
||||||
"""Initialize the base of all UPB devices."""
|
"""Initialize the base of all UPB devices."""
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pywemo"],
|
"loggers": ["pywemo"],
|
||||||
"requirements": ["pywemo==0.9.1"],
|
"requirements": ["pywemo==1.1.0"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Belkin International Inc."
|
"manufacturer": "Belkin International Inc."
|
||||||
|
@@ -43,6 +43,7 @@ class YaleAlarmDevice(YaleAlarmEntity, AlarmControlPanelEntity):
|
|||||||
AlarmControlPanelEntityFeature.ARM_HOME
|
AlarmControlPanelEntityFeature.ARM_HOME
|
||||||
| AlarmControlPanelEntityFeature.ARM_AWAY
|
| AlarmControlPanelEntityFeature.ARM_AWAY
|
||||||
)
|
)
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, coordinator: YaleDataUpdateCoordinator) -> None:
|
def __init__(self, coordinator: YaleDataUpdateCoordinator) -> None:
|
||||||
"""Initialize the Yale Alarm Device."""
|
"""Initialize the Yale Alarm Device."""
|
||||||
|
@@ -40,6 +40,8 @@ async def async_setup_entry(
|
|||||||
class YaleDoorlock(YaleEntity, LockEntity):
|
class YaleDoorlock(YaleEntity, LockEntity):
|
||||||
"""Representation of a Yale doorlock."""
|
"""Representation of a Yale doorlock."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, coordinator: YaleDataUpdateCoordinator, data: dict, code_format: int
|
self, coordinator: YaleDataUpdateCoordinator, data: dict, code_format: int
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@@ -29,6 +29,7 @@ class YaleXSBLELock(YALEXSBLEEntity, LockEntity):
|
|||||||
"""A yale xs ble lock."""
|
"""A yale xs ble lock."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_state(
|
def _async_update_state(
|
||||||
|
@@ -25,10 +25,10 @@
|
|||||||
"pyserial-asyncio==0.6",
|
"pyserial-asyncio==0.6",
|
||||||
"zha-quirks==0.0.101",
|
"zha-quirks==0.0.101",
|
||||||
"zigpy-deconz==0.21.0",
|
"zigpy-deconz==0.21.0",
|
||||||
"zigpy==0.56.1",
|
"zigpy==0.56.2",
|
||||||
"zigpy-xbee==0.18.1",
|
"zigpy-xbee==0.18.1",
|
||||||
"zigpy-zigate==0.11.0",
|
"zigpy-zigate==0.11.0",
|
||||||
"zigpy-znp==0.11.2"
|
"zigpy-znp==0.11.3"
|
||||||
],
|
],
|
||||||
"usb": [
|
"usb": [
|
||||||
{
|
{
|
||||||
|
@@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 7
|
MINOR_VERSION: Final = 7
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)
|
||||||
|
@@ -16,7 +16,6 @@ from collections.abc import (
|
|||||||
)
|
)
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from contextvars import ContextVar
|
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
import functools
|
import functools
|
||||||
@@ -156,8 +155,6 @@ MAX_EXPECTED_ENTITY_IDS = 16384
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
_cv_hass: ContextVar[HomeAssistant] = ContextVar("hass")
|
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(MAX_EXPECTED_ENTITY_IDS)
|
@functools.lru_cache(MAX_EXPECTED_ENTITY_IDS)
|
||||||
def split_entity_id(entity_id: str) -> tuple[str, str]:
|
def split_entity_id(entity_id: str) -> tuple[str, str]:
|
||||||
@@ -200,16 +197,27 @@ def is_callback(func: Callable[..., Any]) -> bool:
|
|||||||
return getattr(func, "_hass_callback", False) is True
|
return getattr(func, "_hass_callback", False) is True
|
||||||
|
|
||||||
|
|
||||||
|
class _Hass(threading.local):
|
||||||
|
"""Container which makes a HomeAssistant instance available to the event loop."""
|
||||||
|
|
||||||
|
hass: HomeAssistant | None = None
|
||||||
|
|
||||||
|
|
||||||
|
_hass = _Hass()
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_get_hass() -> HomeAssistant:
|
def async_get_hass() -> HomeAssistant:
|
||||||
"""Return the HomeAssistant instance.
|
"""Return the HomeAssistant instance.
|
||||||
|
|
||||||
Raises LookupError if no HomeAssistant instance is available.
|
Raises HomeAssistantError when called from the wrong thread.
|
||||||
|
|
||||||
This should be used where it's very cumbersome or downright impossible to pass
|
This should be used where it's very cumbersome or downright impossible to pass
|
||||||
hass to the code which needs it.
|
hass to the code which needs it.
|
||||||
"""
|
"""
|
||||||
return _cv_hass.get()
|
if not _hass.hass:
|
||||||
|
raise HomeAssistantError("async_get_hass called from the wrong thread")
|
||||||
|
return _hass.hass
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
@enum.unique
|
||||||
@@ -293,9 +301,9 @@ class HomeAssistant:
|
|||||||
config_entries: ConfigEntries = None # type: ignore[assignment]
|
config_entries: ConfigEntries = None # type: ignore[assignment]
|
||||||
|
|
||||||
def __new__(cls) -> HomeAssistant:
|
def __new__(cls) -> HomeAssistant:
|
||||||
"""Set the _cv_hass context variable."""
|
"""Set the _hass thread local data."""
|
||||||
hass = super().__new__(cls)
|
hass = super().__new__(cls)
|
||||||
_cv_hass.set(hass)
|
_hass.hass = hass
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -1760,7 +1768,7 @@ class ServiceRegistry:
|
|||||||
the context. Will return NONE if the service does not exist as there is
|
the context. Will return NONE if the service does not exist as there is
|
||||||
other error handling when calling the service if it does not exist.
|
other error handling when calling the service if it does not exist.
|
||||||
"""
|
"""
|
||||||
if not (handler := self._services[domain][service]):
|
if not (handler := self._services[domain.lower()][service.lower()]):
|
||||||
return SupportsResponse.NONE
|
return SupportsResponse.NONE
|
||||||
return handler.supports_response
|
return handler.supports_response
|
||||||
|
|
||||||
|
@@ -93,7 +93,7 @@ from homeassistant.core import (
|
|||||||
split_entity_id,
|
split_entity_id,
|
||||||
valid_entity_id,
|
valid_entity_id,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import HomeAssistantError, TemplateError
|
||||||
from homeassistant.generated import currencies
|
from homeassistant.generated import currencies
|
||||||
from homeassistant.generated.countries import COUNTRIES
|
from homeassistant.generated.countries import COUNTRIES
|
||||||
from homeassistant.generated.languages import LANGUAGES
|
from homeassistant.generated.languages import LANGUAGES
|
||||||
@@ -609,7 +609,7 @@ def template(value: Any | None) -> template_helper.Template:
|
|||||||
raise vol.Invalid("template value should be a string")
|
raise vol.Invalid("template value should be a string")
|
||||||
|
|
||||||
hass: HomeAssistant | None = None
|
hass: HomeAssistant | None = None
|
||||||
with contextlib.suppress(LookupError):
|
with contextlib.suppress(HomeAssistantError):
|
||||||
hass = async_get_hass()
|
hass = async_get_hass()
|
||||||
|
|
||||||
template_value = template_helper.Template(str(value), hass)
|
template_value = template_helper.Template(str(value), hass)
|
||||||
@@ -631,7 +631,7 @@ def dynamic_template(value: Any | None) -> template_helper.Template:
|
|||||||
raise vol.Invalid("template value does not contain a dynamic template")
|
raise vol.Invalid("template value does not contain a dynamic template")
|
||||||
|
|
||||||
hass: HomeAssistant | None = None
|
hass: HomeAssistant | None = None
|
||||||
with contextlib.suppress(LookupError):
|
with contextlib.suppress(HomeAssistantError):
|
||||||
hass = async_get_hass()
|
hass = async_get_hass()
|
||||||
|
|
||||||
template_value = template_helper.Template(str(value), hass)
|
template_value = template_helper.Template(str(value), hass)
|
||||||
@@ -1098,7 +1098,7 @@ def _no_yaml_config_schema(
|
|||||||
# pylint: disable-next=import-outside-toplevel
|
# pylint: disable-next=import-outside-toplevel
|
||||||
from .issue_registry import IssueSeverity, async_create_issue
|
from .issue_registry import IssueSeverity, async_create_issue
|
||||||
|
|
||||||
with contextlib.suppress(LookupError):
|
with contextlib.suppress(HomeAssistantError):
|
||||||
hass = async_get_hass()
|
hass = async_get_hass()
|
||||||
async_create_issue(
|
async_create_issue(
|
||||||
hass,
|
hass,
|
||||||
|
@@ -670,6 +670,9 @@ def async_set_service_schema(
|
|||||||
hass: HomeAssistant, domain: str, service: str, schema: dict[str, Any]
|
hass: HomeAssistant, domain: str, service: str, schema: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Register a description for a service."""
|
"""Register a description for a service."""
|
||||||
|
domain = domain.lower()
|
||||||
|
service = service.lower()
|
||||||
|
|
||||||
descriptions_cache: dict[
|
descriptions_cache: dict[
|
||||||
tuple[str, str], dict[str, Any] | None
|
tuple[str, str], dict[str, Any] | None
|
||||||
] = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {})
|
] = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {})
|
||||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.7.1"
|
version = "2023.7.2"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@@ -124,7 +124,7 @@ PyXiaomiGateway==0.14.3
|
|||||||
RachioPy==1.0.3
|
RachioPy==1.0.3
|
||||||
|
|
||||||
# homeassistant.components.python_script
|
# homeassistant.components.python_script
|
||||||
RestrictedPython==6.0
|
RestrictedPython==6.1
|
||||||
|
|
||||||
# homeassistant.components.remember_the_milk
|
# homeassistant.components.remember_the_milk
|
||||||
RtmAPI==0.7.2
|
RtmAPI==0.7.2
|
||||||
@@ -252,7 +252,7 @@ aioguardian==2022.07.0
|
|||||||
aioharmony==0.2.10
|
aioharmony==0.2.10
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==2.6.5
|
aiohomekit==2.6.7
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@@ -330,7 +330,7 @@ aioqsw==0.3.2
|
|||||||
aiorecollect==1.0.8
|
aiorecollect==1.0.8
|
||||||
|
|
||||||
# homeassistant.components.ridwell
|
# homeassistant.components.ridwell
|
||||||
aioridwell==2023.01.0
|
aioridwell==2023.07.0
|
||||||
|
|
||||||
# homeassistant.components.ruuvi_gateway
|
# homeassistant.components.ruuvi_gateway
|
||||||
aioruuvigateway==0.1.0
|
aioruuvigateway==0.1.0
|
||||||
@@ -565,7 +565,7 @@ brunt==1.2.0
|
|||||||
bt-proximity==0.2.1
|
bt-proximity==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.bthome
|
# homeassistant.components.bthome
|
||||||
bthome-ble==2.12.0
|
bthome-ble==2.12.1
|
||||||
|
|
||||||
# homeassistant.components.bt_home_hub_5
|
# homeassistant.components.bt_home_hub_5
|
||||||
bthomehub5-devicelist==0.1.1
|
bthomehub5-devicelist==0.1.1
|
||||||
@@ -864,7 +864,7 @@ gitterpy==0.1.7
|
|||||||
glances-api==0.4.3
|
glances-api==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.goalzero
|
# homeassistant.components.goalzero
|
||||||
goalzero==0.2.1
|
goalzero==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.goodwe
|
# homeassistant.components.goodwe
|
||||||
goodwe==0.2.31
|
goodwe==0.2.31
|
||||||
@@ -1615,7 +1615,7 @@ pycsspeechtts==1.0.8
|
|||||||
# pycups==1.9.73
|
# pycups==1.9.73
|
||||||
|
|
||||||
# homeassistant.components.daikin
|
# homeassistant.components.daikin
|
||||||
pydaikin==2.9.0
|
pydaikin==2.10.5
|
||||||
|
|
||||||
# homeassistant.components.danfoss_air
|
# homeassistant.components.danfoss_air
|
||||||
pydanfossair==0.1.0
|
pydanfossair==0.1.0
|
||||||
@@ -2139,7 +2139,7 @@ python-qbittorrent==0.4.3
|
|||||||
python-ripple-api==0.0.3
|
python-ripple-api==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==0.29.2
|
python-roborock==0.30.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.33
|
||||||
@@ -2213,7 +2213,7 @@ pyvolumio==0.1.5
|
|||||||
pywebpush==1.9.2
|
pywebpush==1.9.2
|
||||||
|
|
||||||
# homeassistant.components.wemo
|
# homeassistant.components.wemo
|
||||||
pywemo==0.9.1
|
pywemo==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.wilight
|
# homeassistant.components.wilight
|
||||||
pywilight==0.0.74
|
pywilight==0.0.74
|
||||||
@@ -2267,7 +2267,7 @@ renault-api==0.1.13
|
|||||||
renson-endura-delta==1.5.0
|
renson-endura-delta==1.5.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.7.1
|
reolink-aio==0.7.3
|
||||||
|
|
||||||
# homeassistant.components.idteck_prox
|
# homeassistant.components.idteck_prox
|
||||||
rfk101py==0.0.1
|
rfk101py==0.0.1
|
||||||
@@ -2759,10 +2759,10 @@ zigpy-xbee==0.18.1
|
|||||||
zigpy-zigate==0.11.0
|
zigpy-zigate==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-znp==0.11.2
|
zigpy-znp==0.11.3
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.56.1
|
zigpy==0.56.2
|
||||||
|
|
||||||
# homeassistant.components.zoneminder
|
# homeassistant.components.zoneminder
|
||||||
zm-py==0.5.2
|
zm-py==0.5.2
|
||||||
|
@@ -108,7 +108,7 @@ PyXiaomiGateway==0.14.3
|
|||||||
RachioPy==1.0.3
|
RachioPy==1.0.3
|
||||||
|
|
||||||
# homeassistant.components.python_script
|
# homeassistant.components.python_script
|
||||||
RestrictedPython==6.0
|
RestrictedPython==6.1
|
||||||
|
|
||||||
# homeassistant.components.remember_the_milk
|
# homeassistant.components.remember_the_milk
|
||||||
RtmAPI==0.7.2
|
RtmAPI==0.7.2
|
||||||
@@ -227,7 +227,7 @@ aioguardian==2022.07.0
|
|||||||
aioharmony==0.2.10
|
aioharmony==0.2.10
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==2.6.5
|
aiohomekit==2.6.7
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@@ -302,7 +302,7 @@ aioqsw==0.3.2
|
|||||||
aiorecollect==1.0.8
|
aiorecollect==1.0.8
|
||||||
|
|
||||||
# homeassistant.components.ridwell
|
# homeassistant.components.ridwell
|
||||||
aioridwell==2023.01.0
|
aioridwell==2023.07.0
|
||||||
|
|
||||||
# homeassistant.components.ruuvi_gateway
|
# homeassistant.components.ruuvi_gateway
|
||||||
aioruuvigateway==0.1.0
|
aioruuvigateway==0.1.0
|
||||||
@@ -469,7 +469,7 @@ brottsplatskartan==0.0.1
|
|||||||
brunt==1.2.0
|
brunt==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.bthome
|
# homeassistant.components.bthome
|
||||||
bthome-ble==2.12.0
|
bthome-ble==2.12.1
|
||||||
|
|
||||||
# homeassistant.components.buienradar
|
# homeassistant.components.buienradar
|
||||||
buienradar==1.0.5
|
buienradar==1.0.5
|
||||||
@@ -677,7 +677,7 @@ gios==3.1.0
|
|||||||
glances-api==0.4.3
|
glances-api==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.goalzero
|
# homeassistant.components.goalzero
|
||||||
goalzero==0.2.1
|
goalzero==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.goodwe
|
# homeassistant.components.goodwe
|
||||||
goodwe==0.2.31
|
goodwe==0.2.31
|
||||||
@@ -1197,7 +1197,7 @@ pycoolmasternet-async==0.1.5
|
|||||||
pycsspeechtts==1.0.8
|
pycsspeechtts==1.0.8
|
||||||
|
|
||||||
# homeassistant.components.daikin
|
# homeassistant.components.daikin
|
||||||
pydaikin==2.9.0
|
pydaikin==2.10.5
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==113
|
pydeconz==113
|
||||||
@@ -1565,7 +1565,7 @@ python-picnic-api==1.1.0
|
|||||||
python-qbittorrent==0.4.3
|
python-qbittorrent==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==0.29.2
|
python-roborock==0.30.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.33
|
||||||
@@ -1621,7 +1621,7 @@ pyvolumio==0.1.5
|
|||||||
pywebpush==1.9.2
|
pywebpush==1.9.2
|
||||||
|
|
||||||
# homeassistant.components.wemo
|
# homeassistant.components.wemo
|
||||||
pywemo==0.9.1
|
pywemo==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.wilight
|
# homeassistant.components.wilight
|
||||||
pywilight==0.0.74
|
pywilight==0.0.74
|
||||||
@@ -1660,7 +1660,7 @@ renault-api==0.1.13
|
|||||||
renson-endura-delta==1.5.0
|
renson-endura-delta==1.5.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.7.1
|
reolink-aio==0.7.3
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.65
|
rflink==0.0.65
|
||||||
@@ -2023,10 +2023,10 @@ zigpy-xbee==0.18.1
|
|||||||
zigpy-zigate==0.11.0
|
zigpy-zigate==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-znp==0.11.2
|
zigpy-znp==0.11.3
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.56.1
|
zigpy==0.56.2
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.49.0
|
zwave-js-server-python==0.49.0
|
||||||
|
128
tests/components/daikin/test_init.py
Normal file
128
tests/components/daikin/test_init.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"""Define tests for the Daikin init."""
|
||||||
|
import asyncio
|
||||||
|
from unittest.mock import AsyncMock, PropertyMock, patch
|
||||||
|
|
||||||
|
from aiohttp import ClientConnectionError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.daikin import update_unique_id
|
||||||
|
from homeassistant.components.daikin.const import DOMAIN, KEY_MAC
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_HOST
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
|
from .test_config_flow import HOST, MAC
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_daikin():
|
||||||
|
"""Mock pydaikin."""
|
||||||
|
|
||||||
|
async def mock_daikin_factory(*args, **kwargs):
|
||||||
|
"""Mock the init function in pydaikin."""
|
||||||
|
return Appliance
|
||||||
|
|
||||||
|
with patch("homeassistant.components.daikin.Appliance") as Appliance:
|
||||||
|
Appliance.factory.side_effect = mock_daikin_factory
|
||||||
|
type(Appliance).update_status = AsyncMock()
|
||||||
|
type(Appliance).inside_temperature = PropertyMock(return_value=22)
|
||||||
|
type(Appliance).target_temperature = PropertyMock(return_value=22)
|
||||||
|
type(Appliance).zones = PropertyMock(return_value=[("Zone 1", "0", 0)])
|
||||||
|
type(Appliance).fan_rate = PropertyMock(return_value=[])
|
||||||
|
type(Appliance).swing_modes = PropertyMock(return_value=[])
|
||||||
|
yield Appliance
|
||||||
|
|
||||||
|
|
||||||
|
DATA = {
|
||||||
|
"ver": "1_1_8",
|
||||||
|
"name": "DaikinAP00000",
|
||||||
|
"mac": MAC,
|
||||||
|
"model": "NOTSUPPORT",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
INVALID_DATA = {**DATA, "name": None, "mac": HOST}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unique_id_migrate(hass: HomeAssistant, mock_daikin) -> None:
|
||||||
|
"""Test unique id migration."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=HOST,
|
||||||
|
title=None,
|
||||||
|
data={CONF_HOST: HOST, KEY_MAC: HOST},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
|
type(mock_daikin).mac = PropertyMock(return_value=HOST)
|
||||||
|
type(mock_daikin).values = PropertyMock(return_value=INVALID_DATA)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.unique_id == HOST
|
||||||
|
|
||||||
|
assert device_registry.async_get_device({}, {(KEY_MAC, HOST)}).name is None
|
||||||
|
|
||||||
|
entity = entity_registry.async_get("climate.daikin_127_0_0_1")
|
||||||
|
assert entity.unique_id == HOST
|
||||||
|
assert update_unique_id(entity, MAC) is not None
|
||||||
|
|
||||||
|
assert entity_registry.async_get("switch.none_zone_1").unique_id.startswith(HOST)
|
||||||
|
|
||||||
|
type(mock_daikin).mac = PropertyMock(return_value=MAC)
|
||||||
|
type(mock_daikin).values = PropertyMock(return_value=DATA)
|
||||||
|
|
||||||
|
assert config_entry.unique_id != MAC
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_reload(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.unique_id == MAC
|
||||||
|
|
||||||
|
assert (
|
||||||
|
device_registry.async_get_device({}, {(KEY_MAC, MAC)}).name == "DaikinAP00000"
|
||||||
|
)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get("climate.daikin_127_0_0_1")
|
||||||
|
assert entity.unique_id == MAC
|
||||||
|
assert update_unique_id(entity, MAC) is None
|
||||||
|
|
||||||
|
assert entity_registry.async_get("switch.none_zone_1").unique_id.startswith(MAC)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_client_connection_error(hass: HomeAssistant, mock_daikin) -> None:
|
||||||
|
"""Test unique id migration."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=MAC,
|
||||||
|
data={CONF_HOST: HOST, KEY_MAC: MAC},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mock_daikin.factory.side_effect = ClientConnectionError
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_timeout_error(hass: HomeAssistant, mock_daikin) -> None:
|
||||||
|
"""Test unique id migration."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=MAC,
|
||||||
|
data={CONF_HOST: HOST, KEY_MAC: MAC},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mock_daikin.factory.side_effect = asyncio.TimeoutError
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
@@ -1308,3 +1308,45 @@ async def test_option_flow(
|
|||||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||||
assert result["data"] == {CONF_ALLOW_SERVICE_CALLS: option_value}
|
assert result["data"] == {CONF_ALLOW_SERVICE_CALLS: option_value}
|
||||||
assert len(mock_reload.mock_calls) == int(option_value)
|
assert len(mock_reload.mock_calls) == int(option_value)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_discovers_name_no_dashboard(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_client,
|
||||||
|
mock_zeroconf: None,
|
||||||
|
mock_setup_entry: None,
|
||||||
|
) -> None:
|
||||||
|
"""Test user step can discover the name and the there is not dashboard."""
|
||||||
|
mock_client.device_info.side_effect = [
|
||||||
|
RequiresEncryptionAPIError,
|
||||||
|
InvalidEncryptionKeyAPIError("Wrong key", "test"),
|
||||||
|
DeviceInfo(
|
||||||
|
uses_password=False,
|
||||||
|
name="test",
|
||||||
|
mac_address="11:22:33:44:55:AA",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
"esphome",
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "encryption_key"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "127.0.0.1",
|
||||||
|
CONF_PORT: 6053,
|
||||||
|
CONF_PASSWORD: "",
|
||||||
|
CONF_NOISE_PSK: VALID_NOISE_PSK,
|
||||||
|
CONF_DEVICE_NAME: "test",
|
||||||
|
}
|
||||||
|
assert mock_client.noise_psk == VALID_NOISE_PSK
|
||||||
|
@@ -58,7 +58,9 @@ async def test_setup_dashboard_fails(
|
|||||||
assert mock_config_entry.state == ConfigEntryState.LOADED
|
assert mock_config_entry.state == ConfigEntryState.LOADED
|
||||||
assert mock_get_devices.call_count == 1
|
assert mock_get_devices.call_count == 1
|
||||||
|
|
||||||
assert dashboard.STORAGE_KEY not in hass_storage
|
# The dashboard addon might recover later so we still
|
||||||
|
# allow it to be set up.
|
||||||
|
assert dashboard.STORAGE_KEY in hass_storage
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_dashboard_fails_when_already_setup(
|
async def test_setup_dashboard_fails_when_already_setup(
|
||||||
|
@@ -8,6 +8,9 @@
|
|||||||
# name: test_image_entity[fc_data0]
|
# name: test_image_entity[fc_data0]
|
||||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf5IDATx\xda\xedVQ\x0eC!\x0c"\xbb@\xef\x7fKn\xe0\x00\xfd\xdb\xcf6\xf9|\xc6\xc4\xc6\x0f\xd2\x02\xadb},\xe2\xb9\xfb\xe5\x0e\xc0(\x18\xf2\x84/|\xaeo\xef\x847\xda\x14\x1af\x1c\xde\xe3\x19(X\tKxN\xb2\x87\x17j9\x1d<m\x01)\xbbU\xe1\xcf\xa2\x9eU\xd1\xd7\xcbe.\xcc\xf6\xd05\x7f\x02\x82\x1d\xb8\x1c\xdd\xd7\x1b\xef\t\x90\x13an\xf1b\x13P\xb9\x01\xac\xd4k\xee\x04\xa5.\xd1.\xe8+\x90\x88\x1b\x0e\x0b\xfe\x03\xd3 \xd4Y\xe0\xef\x10\xa7z\xe3\xe9F\x7f(?;\xc6\x80\x95\xfc\xe2\x13\x1ddC\x0fZ\x07\xec6f\xc3/.\x94i\xddi\xf8\x8f\x9b9k<\x8d\xf9\xeci`\xfb\xed\xf1R\x99/g\x9e\xaei\xcc\x830\xb7\xf6\x83\xd4\xf1_\x9e\x0f\xf7.*\xf3\xc0\xf6\x1b\x86\xbf\x12\xde\xac\xed\x16\xb0\xf4\xbe\x9dO\x02\xd0\xe1\x8f\xee^\x0f|v\xf4\x15 \x13\xaf\x8e\xff\x9e\x7f\xe2\x9fwo\x06\xf4\x81v\xeb\xb3\xcc\xc3\x00\x00\x00\x00IEND\xaeB`\x82'
|
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf5IDATx\xda\xedVQ\x0eC!\x0c"\xbb@\xef\x7fKn\xe0\x00\xfd\xdb\xcf6\xf9|\xc6\xc4\xc6\x0f\xd2\x02\xadb},\xe2\xb9\xfb\xe5\x0e\xc0(\x18\xf2\x84/|\xaeo\xef\x847\xda\x14\x1af\x1c\xde\xe3\x19(X\tKxN\xb2\x87\x17j9\x1d<m\x01)\xbbU\xe1\xcf\xa2\x9eU\xd1\xd7\xcbe.\xcc\xf6\xd05\x7f\x02\x82\x1d\xb8\x1c\xdd\xd7\x1b\xef\t\x90\x13an\xf1b\x13P\xb9\x01\xac\xd4k\xee\x04\xa5.\xd1.\xe8+\x90\x88\x1b\x0e\x0b\xfe\x03\xd3 \xd4Y\xe0\xef\x10\xa7z\xe3\xe9F\x7f(?;\xc6\x80\x95\xfc\xe2\x13\x1ddC\x0fZ\x07\xec6f\xc3/.\x94i\xddi\xf8\x8f\x9b9k<\x8d\xf9\xeci`\xfb\xed\xf1R\x99/g\x9e\xaei\xcc\x830\xb7\xf6\x83\xd4\xf1_\x9e\x0f\xf7.*\xf3\xc0\xf6\x1b\x86\xbf\x12\xde\xac\xed\x16\xb0\xf4\xbe\x9dO\x02\xd0\xe1\x8f\xee^\x0f|v\xf4\x15 \x13\xaf\x8e\xff\x9e\x7f\xe2\x9fwo\x06\xf4\x81v\xeb\xb3\xcc\xc3\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_image_entity[fc_data1]
|
||||||
|
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf5IDATx\xda\xedVQ\x0eC!\x0c"\xbb@\xef\x7fKn\xe0\x00\xfd\xdb\xcf6\xf9|\xc6\xc4\xc6\x0f\xd2\x02\xadb},\xe2\xb9\xfb\xe5\x0e\xc0(\x18\xf2\x84/|\xaeo\xef\x847\xda\x14\x1af\x1c\xde\xe3\x19(X\tKxN\xb2\x87\x17j9\x1d<m\x01)\xbbU\xe1\xcf\xa2\x9eU\xd1\xd7\xcbe.\xcc\xf6\xd05\x7f\x02\x82\x1d\xb8\x1c\xdd\xd7\x1b\xef\t\x90\x13an\xf1b\x13P\xb9\x01\xac\xd4k\xee\x04\xa5.\xd1.\xe8+\x90\x88\x1b\x0e\x0b\xfe\x03\xd3 \xd4Y\xe0\xef\x10\xa7z\xe3\xe9F\x7f(?;\xc6\x80\x95\xfc\xe2\x13\x1ddC\x0fZ\x07\xec6f\xc3/.\x94i\xddi\xf8\x8f\x9b9k<\x8d\xf9\xeci`\xfb\xed\xf1R\x99/g\x9e\xaei\xcc\x830\xb7\xf6\x83\xd4\xf1_\x9e\x0f\xf7.*\xf3\xc0\xf6\x1b\x86\xbf\x12\xde\xac\xed\x16\xb0\xf4\xbe\x9dO\x02\xd0\xe1\x8f\xee^\x0f|v\xf4\x15 \x13\xaf\x8e\xff\x9e\x7f\xe2\x9fwo\x06\xf4\x81v\xeb\xb3\xcc\xc3\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||||
|
# ---
|
||||||
# name: test_image_update[fc_data0]
|
# name: test_image_update[fc_data0]
|
||||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf9IDATx\xda\xedV\xc1\r\xc40\x0cB\xb7\x80\xf7\xdf\x92\r\\\xb0\xfb\xeb\xe7\xaa\xf0l\xd4\xaaQ\x1e\xc8\x06L\x8a~,\xe2;{s\x06\xa0\xd8z9\xdb\xe6\x0f\xcf\xf5\xef\x99\xf0J\x0f\x85\x86*o\xcf\xf1\x04\x04\x1ak\xb6\x11<\x97\xa6\xa6\x83x&\xb32x\x86\xa4\xab\xeb\x08\x7f\x16\xf5^\x11}\xbd$\xb0\x80k=t\xcc\x9f\xfdg\xfa\xda\xe5\x1d\xe3\t\x8br_\xdb3\x85D}\x063u\x00\x03\xfd\xb6<\xe2\xeaL\xa2y<\xae\xcf\xe3!\x895\xbfL\xf07\x0eT]n7\xc3_{0\xd4\xefx:\xc0\x1f\xc6}\x9e\xb7\x84\x1e\xfb\x91\x0e\x12\x84\t=z\xd2t\x07\x8e\x1d\xc9\x03\xc7\xa9G\xb7\x12\xf3&0\x176\x19\x98\xc8g\x8b;\x88@\xc6\x7f\x93\xa9\xfbVD\xdf\x193\xde9\x1d\xd1\xc3\x9ev`E\xf2oo\xa3\xe1/\x847\xad\x8a?0t\xffN\xb4p\xf35\xf3\x7f\x80\xad\xafS\xf7\x1bD`D\x8f\xef\x9f\xf0\xe0\xec\x02\xa4\xc0\x83\x92\xcf\xf3\xf9a\x00\x00\x00\x00IEND\xaeB`\x82'
|
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf9IDATx\xda\xedV\xc1\r\xc40\x0cB\xb7\x80\xf7\xdf\x92\r\\\xb0\xfb\xeb\xe7\xaa\xf0l\xd4\xaaQ\x1e\xc8\x06L\x8a~,\xe2;{s\x06\xa0\xd8z9\xdb\xe6\x0f\xcf\xf5\xef\x99\xf0J\x0f\x85\x86*o\xcf\xf1\x04\x04\x1ak\xb6\x11<\x97\xa6\xa6\x83x&\xb32x\x86\xa4\xab\xeb\x08\x7f\x16\xf5^\x11}\xbd$\xb0\x80k=t\xcc\x9f\xfdg\xfa\xda\xe5\x1d\xe3\t\x8br_\xdb3\x85D}\x063u\x00\x03\xfd\xb6<\xe2\xeaL\xa2y<\xae\xcf\xe3!\x895\xbfL\xf07\x0eT]n7\xc3_{0\xd4\xefx:\xc0\x1f\xc6}\x9e\xb7\x84\x1e\xfb\x91\x0e\x12\x84\t=z\xd2t\x07\x8e\x1d\xc9\x03\xc7\xa9G\xb7\x12\xf3&0\x176\x19\x98\xc8g\x8b;\x88@\xc6\x7f\x93\xa9\xfbVD\xdf\x193\xde9\x1d\xd1\xc3\x9ev`E\xf2oo\xa3\xe1/\x847\xad\x8a?0t\xffN\xb4p\xf35\xf3\x7f\x80\xad\xafS\xf7\x1bD`D\x8f\xef\x9f\xf0\xe0\xec\x02\xa4\xc0\x83\x92\xcf\xf3\xf9a\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||||
# ---
|
# ---
|
||||||
|
@@ -60,11 +60,31 @@ GUEST_WIFI_CHANGED: dict[str, dict] = {
|
|||||||
|
|
||||||
GUEST_WIFI_DISABLED: dict[str, dict] = {
|
GUEST_WIFI_DISABLED: dict[str, dict] = {
|
||||||
"WLANConfiguration0": {},
|
"WLANConfiguration0": {},
|
||||||
"WLANConfiguration1": {"GetInfo": {"NewEnable": False}},
|
"WLANConfiguration1": {
|
||||||
|
"GetInfo": {
|
||||||
|
"NewEnable": False,
|
||||||
|
"NewStatus": "Up",
|
||||||
|
"NewSSID": "GuestWifi",
|
||||||
|
"NewBeaconType": "11iandWPA3",
|
||||||
|
"NewX_AVM-DE_PossibleBeaconTypes": "None,11i,11iandWPA3",
|
||||||
|
"NewStandard": "ax",
|
||||||
|
"NewBSSID": "1C:ED:6F:12:34:13",
|
||||||
|
},
|
||||||
|
"GetSSID": {
|
||||||
|
"NewSSID": "GuestWifi",
|
||||||
|
},
|
||||||
|
"GetSecurityKeys": {"NewKeyPassphrase": "1234567890"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(("fc_data"), [({**MOCK_FB_SERVICES, **GUEST_WIFI_ENABLED})])
|
@pytest.mark.parametrize(
|
||||||
|
("fc_data"),
|
||||||
|
[
|
||||||
|
({**MOCK_FB_SERVICES, **GUEST_WIFI_ENABLED}),
|
||||||
|
({**MOCK_FB_SERVICES, **GUEST_WIFI_DISABLED}),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_image_entity(
|
async def test_image_entity(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
@@ -150,23 +170,3 @@ async def test_image_update(
|
|||||||
|
|
||||||
assert resp_body != resp_body_new
|
assert resp_body != resp_body_new
|
||||||
assert resp_body_new == snapshot
|
assert resp_body_new == snapshot
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(("fc_data"), [({**MOCK_FB_SERVICES, **GUEST_WIFI_DISABLED})])
|
|
||||||
async def test_image_guest_wifi_disabled(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
hass_client: ClientSessionGenerator,
|
|
||||||
fc_class_mock,
|
|
||||||
fh_class_mock,
|
|
||||||
) -> None:
|
|
||||||
"""Test image entities."""
|
|
||||||
|
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
|
||||||
entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
|
||||||
|
|
||||||
images = hass.states.async_all(IMAGE_DOMAIN)
|
|
||||||
assert len(images) == 0
|
|
||||||
|
@@ -1 +1,174 @@
|
|||||||
"""Tests for the myStrom integration."""
|
"""Tests for the myStrom integration."""
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_device_response(device_type: int | None) -> dict[str, Any]:
|
||||||
|
"""Return default device response."""
|
||||||
|
response = {
|
||||||
|
"version": "2.59.32",
|
||||||
|
"mac": "6001940376EB",
|
||||||
|
"ssid": "personal",
|
||||||
|
"ip": "192.168.0.23",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gw": "192.168.0.1",
|
||||||
|
"dns": "192.168.0.1",
|
||||||
|
"static": False,
|
||||||
|
"connected": True,
|
||||||
|
"signal": 94,
|
||||||
|
}
|
||||||
|
if device_type is not None:
|
||||||
|
response["type"] = device_type
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_bulb_state() -> dict[str, Any]:
|
||||||
|
"""Get default bulb state."""
|
||||||
|
return {
|
||||||
|
"type": "rgblamp",
|
||||||
|
"battery": False,
|
||||||
|
"reachable": True,
|
||||||
|
"meshroot": True,
|
||||||
|
"on": False,
|
||||||
|
"color": "46;18;100",
|
||||||
|
"mode": "hsv",
|
||||||
|
"ramp": 10,
|
||||||
|
"power": 0.45,
|
||||||
|
"fw_version": "2.58.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_switch_state() -> dict[str, Any]:
|
||||||
|
"""Get default switch state."""
|
||||||
|
return {
|
||||||
|
"power": 1.69,
|
||||||
|
"Ws": 0.81,
|
||||||
|
"relay": True,
|
||||||
|
"temperature": 24.87,
|
||||||
|
"version": "2.59.32",
|
||||||
|
"mac": "6001940376EB",
|
||||||
|
"ssid": "personal",
|
||||||
|
"ip": "192.168.0.23",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gw": "192.168.0.1",
|
||||||
|
"dns": "192.168.0.1",
|
||||||
|
"static": False,
|
||||||
|
"connected": True,
|
||||||
|
"signal": 94,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MyStromDeviceMock:
|
||||||
|
"""Base device mock."""
|
||||||
|
|
||||||
|
def __init__(self, state: dict[str, Any]) -> None:
|
||||||
|
"""Initialize device mock."""
|
||||||
|
self._requested_state = False
|
||||||
|
self._state = state
|
||||||
|
|
||||||
|
async def get_state(self) -> None:
|
||||||
|
"""Set if state is requested."""
|
||||||
|
self._requested_state = True
|
||||||
|
|
||||||
|
|
||||||
|
class MyStromBulbMock(MyStromDeviceMock):
|
||||||
|
"""MyStrom Bulb mock."""
|
||||||
|
|
||||||
|
def __init__(self, mac: str, state: dict[str, Any]) -> None:
|
||||||
|
"""Initialize bulb mock."""
|
||||||
|
super().__init__(state)
|
||||||
|
self.mac = mac
|
||||||
|
|
||||||
|
@property
|
||||||
|
def firmware(self) -> Optional[str]:
|
||||||
|
"""Return current firmware."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["fw_version"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def consumption(self) -> Optional[float]:
|
||||||
|
"""Return current firmware."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["power"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color(self) -> Optional[str]:
|
||||||
|
"""Return current color settings."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["color"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self) -> Optional[str]:
|
||||||
|
"""Return current mode."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["mode"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transition_time(self) -> Optional[int]:
|
||||||
|
"""Return current transition time (ramp)."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["ramp"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bulb_type(self) -> Optional[str]:
|
||||||
|
"""Return the type of the bulb."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["type"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> Optional[bool]:
|
||||||
|
"""Return the current state of the bulb."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["on"]
|
||||||
|
|
||||||
|
|
||||||
|
class MyStromSwitchMock(MyStromDeviceMock):
|
||||||
|
"""MyStrom Switch mock."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relay(self) -> Optional[bool]:
|
||||||
|
"""Return the relay state."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["on"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def consumption(self) -> Optional[float]:
|
||||||
|
"""Return the current power consumption in mWh."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["power"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def consumedWs(self) -> Optional[float]:
|
||||||
|
"""The average of energy consumed per second since last report call."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["Ws"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def firmware(self) -> Optional[str]:
|
||||||
|
"""Return the current firmware."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["version"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mac(self) -> Optional[str]:
|
||||||
|
"""Return the MAC address."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["mac"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature(self) -> Optional[float]:
|
||||||
|
"""Return the current temperature in celsius."""
|
||||||
|
if not self._requested_state:
|
||||||
|
return None
|
||||||
|
return self._state["temperature"]
|
||||||
|
@@ -2,11 +2,19 @@
|
|||||||
from unittest.mock import AsyncMock, PropertyMock, patch
|
from unittest.mock import AsyncMock, PropertyMock, patch
|
||||||
|
|
||||||
from pymystrom.exceptions import MyStromConnectionError
|
from pymystrom.exceptions import MyStromConnectionError
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.mystrom.const import DOMAIN
|
from homeassistant.components.mystrom.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
MyStromBulbMock,
|
||||||
|
MyStromSwitchMock,
|
||||||
|
get_default_bulb_state,
|
||||||
|
get_default_device_response,
|
||||||
|
get_default_switch_state,
|
||||||
|
)
|
||||||
from .conftest import DEVICE_MAC
|
from .conftest import DEVICE_MAC
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@@ -16,36 +24,27 @@ async def init_integration(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
device_type: int,
|
device_type: int,
|
||||||
bulb_type: str = "strip",
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Inititialize integration for testing."""
|
"""Inititialize integration for testing."""
|
||||||
with patch(
|
with patch(
|
||||||
"pymystrom.get_device_info",
|
"pymystrom.get_device_info",
|
||||||
side_effect=AsyncMock(return_value={"type": device_type, "mac": DEVICE_MAC}),
|
side_effect=AsyncMock(return_value=get_default_device_response(device_type)),
|
||||||
), patch("pymystrom.switch.MyStromSwitch.get_state", return_value={}), patch(
|
|
||||||
"pymystrom.bulb.MyStromBulb.get_state", return_value={}
|
|
||||||
), patch(
|
), patch(
|
||||||
"pymystrom.bulb.MyStromBulb.bulb_type", bulb_type
|
"homeassistant.components.mystrom._get_mystrom_bulb",
|
||||||
|
return_value=MyStromBulbMock("6001940376EB", get_default_bulb_state()),
|
||||||
), patch(
|
), patch(
|
||||||
"pymystrom.switch.MyStromSwitch.mac",
|
"homeassistant.components.mystrom._get_mystrom_switch",
|
||||||
new_callable=PropertyMock,
|
return_value=MyStromSwitchMock(get_default_switch_state()),
|
||||||
return_value=DEVICE_MAC,
|
|
||||||
), patch(
|
|
||||||
"pymystrom.bulb.MyStromBulb.mac",
|
|
||||||
new_callable=PropertyMock,
|
|
||||||
return_value=DEVICE_MAC,
|
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert config_entry.state == ConfigEntryState.LOADED
|
|
||||||
|
|
||||||
|
|
||||||
async def test_init_switch_and_unload(
|
async def test_init_switch_and_unload(
|
||||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the initialization of a myStrom switch."""
|
"""Test the initialization of a myStrom switch."""
|
||||||
await init_integration(hass, config_entry, 101)
|
await init_integration(hass, config_entry, 106)
|
||||||
state = hass.states.get("switch.mystrom_device")
|
state = hass.states.get("switch.mystrom_device")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
@@ -56,12 +55,35 @@ async def test_init_switch_and_unload(
|
|||||||
assert not hass.data.get(DOMAIN)
|
assert not hass.data.get(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
async def test_init_bulb(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
@pytest.mark.parametrize(
|
||||||
|
("device_type", "platform", "entry_state", "entity_state_none"),
|
||||||
|
[
|
||||||
|
(None, "switch", ConfigEntryState.LOADED, False),
|
||||||
|
(102, "light", ConfigEntryState.LOADED, False),
|
||||||
|
(103, "button", ConfigEntryState.SETUP_ERROR, True),
|
||||||
|
(104, "button", ConfigEntryState.SETUP_ERROR, True),
|
||||||
|
(105, "light", ConfigEntryState.LOADED, False),
|
||||||
|
(106, "switch", ConfigEntryState.LOADED, False),
|
||||||
|
(107, "switch", ConfigEntryState.LOADED, False),
|
||||||
|
(110, "sensor", ConfigEntryState.SETUP_ERROR, True),
|
||||||
|
(113, "switch", ConfigEntryState.SETUP_ERROR, True),
|
||||||
|
(118, "button", ConfigEntryState.SETUP_ERROR, True),
|
||||||
|
(120, "switch", ConfigEntryState.SETUP_ERROR, True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_init_bulb(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
device_type: int,
|
||||||
|
platform: str,
|
||||||
|
entry_state: ConfigEntryState,
|
||||||
|
entity_state_none: bool,
|
||||||
|
) -> None:
|
||||||
"""Test the initialization of a myStrom bulb."""
|
"""Test the initialization of a myStrom bulb."""
|
||||||
await init_integration(hass, config_entry, 102)
|
await init_integration(hass, config_entry, device_type)
|
||||||
state = hass.states.get("light.mystrom_device")
|
state = hass.states.get(f"{platform}.mystrom_device")
|
||||||
assert state is not None
|
assert (state is None) == entity_state_none
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is entry_state
|
||||||
|
|
||||||
|
|
||||||
async def test_init_of_unknown_bulb(
|
async def test_init_of_unknown_bulb(
|
||||||
@@ -120,7 +142,7 @@ async def test_init_cannot_connect_because_of_get_state(
|
|||||||
"""Test error handling for failing get_state."""
|
"""Test error handling for failing get_state."""
|
||||||
with patch(
|
with patch(
|
||||||
"pymystrom.get_device_info",
|
"pymystrom.get_device_info",
|
||||||
side_effect=AsyncMock(return_value={"type": 101, "mac": DEVICE_MAC}),
|
side_effect=AsyncMock(return_value=get_default_device_response(101)),
|
||||||
), patch(
|
), patch(
|
||||||
"pymystrom.switch.MyStromSwitch.get_state", side_effect=MyStromConnectionError()
|
"pymystrom.switch.MyStromSwitch.get_state", side_effect=MyStromConnectionError()
|
||||||
), patch(
|
), patch(
|
||||||
@@ -129,4 +151,4 @@ async def test_init_cannot_connect_because_of_get_state(
|
|||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert config_entry.state == ConfigEntryState.SETUP_ERROR
|
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||||
|
@@ -35,6 +35,8 @@ SERIAL_NUMBER = 0x12635436566
|
|||||||
|
|
||||||
# Get serial number Command 0x85. Serial is 0x12635436566
|
# Get serial number Command 0x85. Serial is 0x12635436566
|
||||||
SERIAL_RESPONSE = "850000012635436566"
|
SERIAL_RESPONSE = "850000012635436566"
|
||||||
|
# Model and version command 0x82
|
||||||
|
MODEL_AND_VERSION_RESPONSE = "820006090C"
|
||||||
# Get available stations command 0x83
|
# Get available stations command 0x83
|
||||||
AVAILABLE_STATIONS_RESPONSE = "83017F000000" # Mask for 7 zones
|
AVAILABLE_STATIONS_RESPONSE = "83017F000000" # Mask for 7 zones
|
||||||
EMPTY_STATIONS_RESPONSE = "830000000000"
|
EMPTY_STATIONS_RESPONSE = "830000000000"
|
||||||
@@ -183,7 +185,13 @@ def mock_api_responses(
|
|||||||
|
|
||||||
These are returned in the order they are requested by the update coordinator.
|
These are returned in the order they are requested by the update coordinator.
|
||||||
"""
|
"""
|
||||||
return [stations_response, zone_state_response, rain_response, rain_delay_response]
|
return [
|
||||||
|
MODEL_AND_VERSION_RESPONSE,
|
||||||
|
stations_response,
|
||||||
|
zone_state_response,
|
||||||
|
rain_response,
|
||||||
|
rain_delay_response,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="responses")
|
@pytest.fixture(name="responses")
|
||||||
|
@@ -70,6 +70,8 @@ async def test_set_value(
|
|||||||
device = device_registry.async_get_device({(DOMAIN, SERIAL_NUMBER)})
|
device = device_registry.async_get_device({(DOMAIN, SERIAL_NUMBER)})
|
||||||
assert device
|
assert device
|
||||||
assert device.name == "Rain Bird Controller"
|
assert device.name == "Rain Bird Controller"
|
||||||
|
assert device.model == "ST8x-WiFi"
|
||||||
|
assert device.sw_version == "9.12"
|
||||||
|
|
||||||
aioclient_mock.mock_calls.clear()
|
aioclient_mock.mock_calls.clear()
|
||||||
responses.append(mock_response(ACK_ECHO))
|
responses.append(mock_response(ACK_ECHO))
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
"""Test zha siren."""
|
"""Test zha siren."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import patch
|
from unittest.mock import ANY, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zigpy.const import SIG_EP_PROFILE
|
from zigpy.const import SIG_EP_PROFILE
|
||||||
import zigpy.profiles.zha as zha
|
import zigpy.profiles.zha as zha
|
||||||
|
import zigpy.zcl
|
||||||
import zigpy.zcl.clusters.general as general
|
import zigpy.zcl.clusters.general as general
|
||||||
import zigpy.zcl.clusters.security as security
|
import zigpy.zcl.clusters.security as security
|
||||||
import zigpy.zcl.foundation as zcl_f
|
import zigpy.zcl.foundation as zcl_f
|
||||||
@@ -85,48 +86,76 @@ async def test_siren(hass: HomeAssistant, siren) -> None:
|
|||||||
|
|
||||||
# turn on from HA
|
# turn on from HA
|
||||||
with patch(
|
with patch(
|
||||||
"zigpy.zcl.Cluster.request",
|
"zigpy.device.Device.request",
|
||||||
return_value=mock_coro([0x00, zcl_f.Status.SUCCESS]),
|
return_value=mock_coro([0x00, zcl_f.Status.SUCCESS]),
|
||||||
|
), patch(
|
||||||
|
"zigpy.zcl.Cluster.request",
|
||||||
|
side_effect=zigpy.zcl.Cluster.request,
|
||||||
|
autospec=True,
|
||||||
):
|
):
|
||||||
# turn on via UI
|
# turn on via UI
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
SIREN_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True
|
SIREN_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True
|
||||||
)
|
)
|
||||||
assert len(cluster.request.mock_calls) == 1
|
assert cluster.request.mock_calls == [
|
||||||
assert cluster.request.call_args[0][0] is False
|
call(
|
||||||
assert cluster.request.call_args[0][1] == 0
|
cluster,
|
||||||
assert cluster.request.call_args[0][3] == 50 # bitmask for default args
|
False,
|
||||||
assert cluster.request.call_args[0][4] == 5 # duration in seconds
|
0,
|
||||||
assert cluster.request.call_args[0][5] == 0
|
ANY,
|
||||||
assert cluster.request.call_args[0][6] == 2
|
50, # bitmask for default args
|
||||||
|
5, # duration in seconds
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
manufacturer=None,
|
||||||
|
expect_reply=True,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# test that the state has changed to on
|
# test that the state has changed to on
|
||||||
assert hass.states.get(entity_id).state == STATE_ON
|
assert hass.states.get(entity_id).state == STATE_ON
|
||||||
|
|
||||||
# turn off from HA
|
# turn off from HA
|
||||||
with patch(
|
with patch(
|
||||||
"zigpy.zcl.Cluster.request",
|
"zigpy.device.Device.request",
|
||||||
return_value=mock_coro([0x01, zcl_f.Status.SUCCESS]),
|
return_value=mock_coro([0x01, zcl_f.Status.SUCCESS]),
|
||||||
|
), patch(
|
||||||
|
"zigpy.zcl.Cluster.request",
|
||||||
|
side_effect=zigpy.zcl.Cluster.request,
|
||||||
|
autospec=True,
|
||||||
):
|
):
|
||||||
# turn off via UI
|
# turn off via UI
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
SIREN_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True
|
SIREN_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True
|
||||||
)
|
)
|
||||||
assert len(cluster.request.mock_calls) == 1
|
assert cluster.request.mock_calls == [
|
||||||
assert cluster.request.call_args[0][0] is False
|
call(
|
||||||
assert cluster.request.call_args[0][1] == 0
|
cluster,
|
||||||
assert cluster.request.call_args[0][3] == 2 # bitmask for default args
|
False,
|
||||||
assert cluster.request.call_args[0][4] == 5 # duration in seconds
|
0,
|
||||||
assert cluster.request.call_args[0][5] == 0
|
ANY,
|
||||||
assert cluster.request.call_args[0][6] == 2
|
2, # bitmask for default args
|
||||||
|
5, # duration in seconds
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
manufacturer=None,
|
||||||
|
expect_reply=True,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# test that the state has changed to off
|
# test that the state has changed to off
|
||||||
assert hass.states.get(entity_id).state == STATE_OFF
|
assert hass.states.get(entity_id).state == STATE_OFF
|
||||||
|
|
||||||
# turn on from HA
|
# turn on from HA
|
||||||
with patch(
|
with patch(
|
||||||
"zigpy.zcl.Cluster.request",
|
"zigpy.device.Device.request",
|
||||||
return_value=mock_coro([0x00, zcl_f.Status.SUCCESS]),
|
return_value=mock_coro([0x00, zcl_f.Status.SUCCESS]),
|
||||||
|
), patch(
|
||||||
|
"zigpy.zcl.Cluster.request",
|
||||||
|
side_effect=zigpy.zcl.Cluster.request,
|
||||||
|
autospec=True,
|
||||||
):
|
):
|
||||||
# turn on via UI
|
# turn on via UI
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@@ -140,14 +169,21 @@ async def test_siren(hass: HomeAssistant, siren) -> None:
|
|||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert len(cluster.request.mock_calls) == 1
|
assert cluster.request.mock_calls == [
|
||||||
assert cluster.request.call_args[0][0] is False
|
call(
|
||||||
assert cluster.request.call_args[0][1] == 0
|
cluster,
|
||||||
assert cluster.request.call_args[0][3] == 97 # bitmask for passed args
|
False,
|
||||||
assert cluster.request.call_args[0][4] == 10 # duration in seconds
|
0,
|
||||||
assert cluster.request.call_args[0][5] == 0
|
ANY,
|
||||||
assert cluster.request.call_args[0][6] == 2
|
97, # bitmask for passed args
|
||||||
|
10, # duration in seconds
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
manufacturer=None,
|
||||||
|
expect_reply=True,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
]
|
||||||
# test that the state has changed to on
|
# test that the state has changed to on
|
||||||
assert hass.states.get(entity_id).state == STATE_ON
|
assert hass.states.get(entity_id).state == STATE_ON
|
||||||
|
|
||||||
|
@@ -490,17 +490,7 @@ def hass_fixture_setup() -> list[bool]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def hass(_hass: HomeAssistant) -> HomeAssistant:
|
async def hass(
|
||||||
"""Fixture to provide a test instance of Home Assistant."""
|
|
||||||
# This wraps the async _hass fixture inside a sync fixture, to ensure
|
|
||||||
# the `hass` context variable is set in the execution context in which
|
|
||||||
# the test itself is executed
|
|
||||||
ha._cv_hass.set(_hass)
|
|
||||||
return _hass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
async def _hass(
|
|
||||||
hass_fixture_setup: list[bool],
|
hass_fixture_setup: list[bool],
|
||||||
event_loop: asyncio.AbstractEventLoop,
|
event_loop: asyncio.AbstractEventLoop,
|
||||||
load_registries: bool,
|
load_registries: bool,
|
||||||
|
@@ -12,6 +12,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
import homeassistant
|
import homeassistant
|
||||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
issue_registry as ir,
|
issue_registry as ir,
|
||||||
@@ -383,7 +384,7 @@ def test_service() -> None:
|
|||||||
schema("homeassistant.turn_on")
|
schema("homeassistant.turn_on")
|
||||||
|
|
||||||
|
|
||||||
def test_service_schema() -> None:
|
def test_service_schema(hass: HomeAssistant) -> None:
|
||||||
"""Test service_schema validation."""
|
"""Test service_schema validation."""
|
||||||
options = (
|
options = (
|
||||||
{},
|
{},
|
||||||
@@ -1550,10 +1551,10 @@ def test_config_entry_only_schema_cant_find_module() -> None:
|
|||||||
def test_config_entry_only_schema_no_hass(
|
def test_config_entry_only_schema_no_hass(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test if the the hass context var is not set in our context."""
|
"""Test if the the hass context is not set in our context."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.config_validation.async_get_hass",
|
"homeassistant.helpers.config_validation.async_get_hass",
|
||||||
side_effect=LookupError,
|
side_effect=HomeAssistantError,
|
||||||
):
|
):
|
||||||
cv.config_entry_only_config_schema("test_domain")(
|
cv.config_entry_only_config_schema("test_domain")(
|
||||||
{"test_domain": {"foo": "bar"}}
|
{"test_domain": {"foo": "bar"}}
|
||||||
|
@@ -724,6 +724,27 @@ async def test_async_get_all_descriptions_dynamically_created_services(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_register_with_mixed_case(hass: HomeAssistant) -> None:
|
||||||
|
"""Test registering a service with mixed case.
|
||||||
|
|
||||||
|
For backwards compatibility, we have historically allowed mixed case,
|
||||||
|
and automatically converted it to lowercase.
|
||||||
|
"""
|
||||||
|
logger = hass.components.logger
|
||||||
|
logger_config = {logger.DOMAIN: {}}
|
||||||
|
await async_setup_component(hass, logger.DOMAIN, logger_config)
|
||||||
|
logger_domain_mixed = "LoGgEr"
|
||||||
|
hass.services.async_register(
|
||||||
|
logger_domain_mixed, "NeW_SeRVICE", lambda x: None, None
|
||||||
|
)
|
||||||
|
service.async_set_service_schema(
|
||||||
|
hass, logger_domain_mixed, "NeW_SeRVICE", {"description": "new service"}
|
||||||
|
)
|
||||||
|
descriptions = await service.async_get_all_descriptions(hass)
|
||||||
|
assert "description" in descriptions[logger.DOMAIN]["new_service"]
|
||||||
|
assert descriptions[logger.DOMAIN]["new_service"]["description"] == "new service"
|
||||||
|
|
||||||
|
|
||||||
async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -> None:
|
async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -> None:
|
||||||
"""Test service calls invoked only if entity has required features."""
|
"""Test service calls invoked only if entity has required features."""
|
||||||
test_service_mock = AsyncMock(return_value=None)
|
test_service_mock = AsyncMock(return_value=None)
|
||||||
|
@@ -9,10 +9,12 @@ import gc
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ from homeassistant.core import (
|
|||||||
ServiceResponse,
|
ServiceResponse,
|
||||||
State,
|
State,
|
||||||
SupportsResponse,
|
SupportsResponse,
|
||||||
|
callback,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import (
|
||||||
HomeAssistantError,
|
HomeAssistantError,
|
||||||
@@ -202,6 +205,184 @@ def test_async_run_hass_job_delegates_non_async() -> None:
|
|||||||
assert len(hass.async_add_hass_job.mock_calls) == 1
|
assert len(hass.async_add_hass_job.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_get_hass_can_be_called(hass: HomeAssistant) -> None:
|
||||||
|
"""Test calling async_get_hass via different paths.
|
||||||
|
|
||||||
|
The test asserts async_get_hass can be called from:
|
||||||
|
- Coroutines and callbacks
|
||||||
|
- Callbacks scheduled from callbacks, coroutines and threads
|
||||||
|
- Coroutines scheduled from callbacks, coroutines and threads
|
||||||
|
|
||||||
|
The test also asserts async_get_hass can not be called from threads
|
||||||
|
other than the event loop.
|
||||||
|
"""
|
||||||
|
task_finished = asyncio.Event()
|
||||||
|
|
||||||
|
def can_call_async_get_hass() -> bool:
|
||||||
|
"""Test if it's possible to call async_get_hass."""
|
||||||
|
try:
|
||||||
|
if ha.async_get_hass() is hass:
|
||||||
|
return True
|
||||||
|
raise Exception
|
||||||
|
except HomeAssistantError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
# Test scheduling a coroutine which calls async_get_hass via hass.async_create_task
|
||||||
|
async def _async_create_task() -> None:
|
||||||
|
task_finished.set()
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
|
||||||
|
hass.async_create_task(_async_create_task(), "create_task")
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a callback which calls async_get_hass via hass.async_add_job
|
||||||
|
@callback
|
||||||
|
def _add_job() -> None:
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
hass.async_add_job(_add_job)
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a callback which calls async_get_hass from a callback
|
||||||
|
@callback
|
||||||
|
def _schedule_callback_from_callback() -> None:
|
||||||
|
@callback
|
||||||
|
def _callback():
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
# Test the scheduled callback itself can call async_get_hass
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
hass.async_add_job(_callback)
|
||||||
|
|
||||||
|
_schedule_callback_from_callback()
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a coroutine which calls async_get_hass from a callback
|
||||||
|
@callback
|
||||||
|
def _schedule_coroutine_from_callback() -> None:
|
||||||
|
async def _coroutine():
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
# Test the scheduled callback itself can call async_get_hass
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
hass.async_add_job(_coroutine())
|
||||||
|
|
||||||
|
_schedule_coroutine_from_callback()
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a callback which calls async_get_hass from a coroutine
|
||||||
|
async def _schedule_callback_from_coroutine() -> None:
|
||||||
|
@callback
|
||||||
|
def _callback():
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
# Test the coroutine itself can call async_get_hass
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
hass.async_add_job(_callback)
|
||||||
|
|
||||||
|
await _schedule_callback_from_coroutine()
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a coroutine which calls async_get_hass from a coroutine
|
||||||
|
async def _schedule_callback_from_coroutine() -> None:
|
||||||
|
async def _coroutine():
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
# Test the coroutine itself can call async_get_hass
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
await hass.async_create_task(_coroutine())
|
||||||
|
|
||||||
|
await _schedule_callback_from_coroutine()
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a callback which calls async_get_hass from an executor
|
||||||
|
def _async_add_executor_job_add_job() -> None:
|
||||||
|
@callback
|
||||||
|
def _async_add_job():
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
# Test the executor itself can not call async_get_hass
|
||||||
|
assert not can_call_async_get_hass()
|
||||||
|
hass.add_job(_async_add_job)
|
||||||
|
|
||||||
|
await hass.async_add_executor_job(_async_add_executor_job_add_job)
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a coroutine which calls async_get_hass from an executor
|
||||||
|
def _async_add_executor_job_create_task() -> None:
|
||||||
|
async def _async_create_task() -> None:
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
# Test the executor itself can not call async_get_hass
|
||||||
|
assert not can_call_async_get_hass()
|
||||||
|
hass.create_task(_async_create_task())
|
||||||
|
|
||||||
|
await hass.async_add_executor_job(_async_add_executor_job_create_task)
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
|
||||||
|
# Test scheduling a callback which calls async_get_hass from a worker thread
|
||||||
|
class MyJobAddJob(threading.Thread):
|
||||||
|
@callback
|
||||||
|
def _my_threaded_job_add_job(self) -> None:
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
# Test the worker thread itself can not call async_get_hass
|
||||||
|
assert not can_call_async_get_hass()
|
||||||
|
hass.add_job(self._my_threaded_job_add_job)
|
||||||
|
|
||||||
|
my_job_add_job = MyJobAddJob()
|
||||||
|
my_job_add_job.start()
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
my_job_add_job.join()
|
||||||
|
|
||||||
|
# Test scheduling a coroutine which calls async_get_hass from a worker thread
|
||||||
|
class MyJobCreateTask(threading.Thread):
|
||||||
|
async def _my_threaded_job_create_task(self) -> None:
|
||||||
|
assert can_call_async_get_hass()
|
||||||
|
task_finished.set()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
# Test the worker thread itself can not call async_get_hass
|
||||||
|
assert not can_call_async_get_hass()
|
||||||
|
hass.create_task(self._my_threaded_job_create_task())
|
||||||
|
|
||||||
|
my_job_create_task = MyJobCreateTask()
|
||||||
|
my_job_create_task.start()
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
await task_finished.wait()
|
||||||
|
task_finished.clear()
|
||||||
|
my_job_create_task.join()
|
||||||
|
|
||||||
|
|
||||||
async def test_stage_shutdown(hass: HomeAssistant) -> None:
|
async def test_stage_shutdown(hass: HomeAssistant) -> None:
|
||||||
"""Simulate a shutdown, test calling stuff."""
|
"""Simulate a shutdown, test calling stuff."""
|
||||||
test_stop = async_capture_events(hass, EVENT_HOMEASSISTANT_STOP)
|
test_stop = async_capture_events(hass, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
Reference in New Issue
Block a user