Add switch platform to Midea (#178802)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Simone Chemelli
2026-08-24 17:05:23 +02:00
committed by GitHub
co-authored by Claude Sonnet 5
parent 240f3f4ea3
commit c1952cddad
5 changed files with 1272 additions and 0 deletions
@@ -26,6 +26,7 @@ _PLATFORMS: list[Platform] = [
Platform.HUMIDIFIER,
Platform.NUMBER,
Platform.SELECT,
Platform.SWITCH,
]
@@ -263,6 +263,47 @@
"up_mid": "Up middle"
}
}
},
"switch": {
"anion": {
"name": "Anion"
},
"aux_heating": {
"name": "Auxiliary heating"
},
"disinfect": {
"name": "Disinfect"
},
"night_light": {
"name": "Night light"
},
"out_silent": {
"name": "Outdoor silent mode"
},
"prompt_tone": {
"name": "Prompt tone"
},
"screen_display": {
"name": "Screen display"
},
"self_clean": {
"name": "Self clean"
},
"smart_eye": {
"name": "Smart eye"
},
"sound": {
"name": "Sound"
},
"tbh": {
"name": "TBH"
},
"zone1_curve": {
"name": "Zone 1 curve"
},
"zone2_curve": {
"name": "Zone 2 curve"
}
}
},
"exceptions": {
+133
View File
@@ -0,0 +1,133 @@
"""Switch for Midea."""
from dataclasses import dataclass
from typing import Any, override
from midealocal.const import DeviceType
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .entity import MideaConfigEntry, MideaEntity, midea_api_call
PARALLEL_UPDATES = 0
@dataclass(kw_only=True, frozen=True)
class MideaSwitchEntityDescription(SwitchEntityDescription):
"""Description for a Midea switch entity."""
models: list[DeviceType]
SWITCHES: list[MideaSwitchEntityDescription] = [
MideaSwitchEntityDescription(
key="aux_heating",
translation_key="aux_heating",
models=[DeviceType.AC, DeviceType.CC, DeviceType.CF],
),
MideaSwitchEntityDescription(
key="prompt_tone",
translation_key="prompt_tone",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="screen_display",
translation_key="screen_display",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="out_silent",
translation_key="out_silent",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="smart_eye",
translation_key="smart_eye",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="anion",
translation_key="anion",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="sound",
translation_key="sound",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="self_clean",
translation_key="self_clean",
models=[DeviceType.AC],
),
MideaSwitchEntityDescription(
key="disinfect",
translation_key="disinfect",
models=[DeviceType.C3],
),
MideaSwitchEntityDescription(
key="tbh",
translation_key="tbh",
models=[DeviceType.C3],
),
MideaSwitchEntityDescription(
key="zone1_curve",
translation_key="zone1_curve",
models=[DeviceType.C3],
),
MideaSwitchEntityDescription(
key="zone2_curve",
translation_key="zone2_curve",
models=[DeviceType.C3],
),
MideaSwitchEntityDescription(
key="night_light",
translation_key="night_light",
models=[DeviceType.CC],
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MideaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switches for device."""
device = config_entry.runtime_data
async_add_entities(
MideaSwitch(device, description)
for description in SWITCHES
if device.device_type in description.models
and description.key in device.attributes
)
class MideaSwitch(MideaEntity, SwitchEntity):
"""Represent a Midea switch."""
entity_description: MideaSwitchEntityDescription
@property
@override
def is_on(self) -> bool | None:
"""Return true if switch is on."""
value = self._device.get_attribute(self.entity_description.key)
if not isinstance(value, bool):
return None
return value
@override
def turn_on(self, **kwargs: Any) -> None:
"""Turn on switch."""
with midea_api_call():
self._device.set_attribute(attr=self.entity_description.key, value=True)
@override
def turn_off(self, **kwargs: Any) -> None:
"""Turn off switch."""
with midea_api_call():
self._device.set_attribute(attr=self.entity_description.key, value=False)
@@ -0,0 +1,751 @@
# serializer version: 1
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_anion-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_anion',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Anion',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Anion',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'anion',
'unique_id': '12345678_anion',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_anion-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Anion',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_anion',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_auxiliary_heating-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_auxiliary_heating',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Auxiliary heating',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Auxiliary heating',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'aux_heating',
'unique_id': '12345678_aux_heating',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_auxiliary_heating-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Auxiliary heating',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_auxiliary_heating',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_outdoor_silent_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_outdoor_silent_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Outdoor silent mode',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Outdoor silent mode',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'out_silent',
'unique_id': '12345678_out_silent',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_outdoor_silent_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Outdoor silent mode',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_outdoor_silent_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_prompt_tone-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_prompt_tone',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Prompt tone',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Prompt tone',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'prompt_tone',
'unique_id': '12345678_prompt_tone',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_prompt_tone-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Prompt tone',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_prompt_tone',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_screen_display-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_screen_display',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Screen display',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Screen display',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'screen_display',
'unique_id': '12345678_screen_display',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_screen_display-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Screen display',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_screen_display',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_self_clean-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_self_clean',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Self clean',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Self clean',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'self_clean',
'unique_id': '12345678_self_clean',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_self_clean-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Self clean',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_self_clean',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_smart_eye-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_smart_eye',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Smart eye',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Smart eye',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'smart_eye',
'unique_id': '12345678_smart_eye',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_smart_eye-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Smart eye',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_smart_eye',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_sound-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_sound',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Sound',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Sound',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'sound',
'unique_id': '12345678_sound',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[ac][switch.bedroom_ac_sound-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Sound',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_sound',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_disinfect-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_disinfect',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Disinfect',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Disinfect',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'disinfect',
'unique_id': '12345678_disinfect',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_disinfect-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Disinfect',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_disinfect',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_tbh-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_tbh',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TBH',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TBH',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'tbh',
'unique_id': '12345678_tbh',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_tbh-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC TBH',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_tbh',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_zone_1_curve-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_zone_1_curve',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Zone 1 curve',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Zone 1 curve',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'zone1_curve',
'unique_id': '12345678_zone1_curve',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_zone_1_curve-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Zone 1 curve',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_zone_1_curve',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_zone_2_curve-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_zone_2_curve',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Zone 2 curve',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Zone 2 curve',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'zone2_curve',
'unique_id': '12345678_zone2_curve',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[c3][switch.bedroom_ac_zone_2_curve-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Zone 2 curve',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_zone_2_curve',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state_snapshot[cc][switch.bedroom_ac_auxiliary_heating-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_auxiliary_heating',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Auxiliary heating',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Auxiliary heating',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'aux_heating',
'unique_id': '12345678_aux_heating',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[cc][switch.bedroom_ac_auxiliary_heating-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Auxiliary heating',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_auxiliary_heating',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state_snapshot[cc][switch.bedroom_ac_night_light-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_night_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Night light',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Night light',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'night_light',
'unique_id': '12345678_night_light',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[cc][switch.bedroom_ac_night_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Night light',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_night_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state_snapshot[cf][switch.bedroom_ac_auxiliary_heating-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.bedroom_ac_auxiliary_heating',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Auxiliary heating',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Auxiliary heating',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'aux_heating',
'unique_id': '12345678_aux_heating',
'unit_of_measurement': None,
})
# ---
# name: test_switch_state_snapshot[cf][switch.bedroom_ac_auxiliary_heating-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom AC Auxiliary heating',
}),
'context': <ANY>,
'entity_id': 'switch.bedroom_ac_auxiliary_heating',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
+346
View File
@@ -0,0 +1,346 @@
"""Tests for midea switch.py."""
from collections.abc import Callable
from unittest.mock import patch
from midealocal.const import DeviceType
from midealocal.devices.ac import DeviceAttributes as ACAttributes
from midealocal.devices.c3 import DeviceAttributes as C3Attributes
from midealocal.devices.cc import DeviceAttributes as CCAttributes
from midealocal.devices.cf import DeviceAttributes as CFAttributes
from midealocal.exceptions import SocketException
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from .conftest import DummyDevice, entity_entries
from .const import TEST_DEVICE_ID
from tests.common import MockConfigEntry, snapshot_platform
async def _assert_service_call(
hass: HomeAssistant,
entity_id: str,
service: str,
expected_calls: list[tuple],
device: DummyDevice,
) -> None:
"""Call a switch service and assert the fake device recorded the right call."""
device.calls.clear()
await hass.services.async_call(
SWITCH_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert device.calls == expected_calls
@pytest.mark.parametrize(
"device",
[
pytest.param(
DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: True,
ACAttributes.breezeless: False,
ACAttributes.dry: False,
ACAttributes.indirect_wind: False,
ACAttributes.natural_wind: False,
ACAttributes.prompt_tone: True,
ACAttributes.screen_display: True,
ACAttributes.screen_display_alternate: False,
ACAttributes.out_silent: False,
ACAttributes.smart_eye: False,
ACAttributes.anion: False,
ACAttributes.sound: True,
ACAttributes.self_clean: False,
ACAttributes.comfort_mode: False,
ACAttributes.eco_mode: False,
ACAttributes.boost_mode: False,
ACAttributes.sleep_mode: False,
ACAttributes.frost_protect: False,
ACAttributes.swing_vertical: True,
ACAttributes.swing_horizontal: True,
},
),
id="ac",
),
pytest.param(
DummyDevice(
DeviceType.C3,
attributes={
C3Attributes.zone1_power: True,
C3Attributes.zone2_power: False,
C3Attributes.mode: 1,
C3Attributes.target_temperature: [22, 23],
C3Attributes.disinfect: False,
C3Attributes.dhw_power: True,
C3Attributes.eco_mode: False,
C3Attributes.fast_dhw: False,
C3Attributes.silent_mode: True,
C3Attributes.tbh: False,
C3Attributes.zone1_curve: False,
C3Attributes.zone2_curve: True,
},
),
id="c3",
),
pytest.param(
DummyDevice(
DeviceType.CC,
attributes={
CCAttributes.power: True,
CCAttributes.mode: 1,
CCAttributes.eco_mode: False,
CCAttributes.sleep_mode: False,
CCAttributes.swing: False,
CCAttributes.aux_heating: True,
CCAttributes.night_light: False,
},
),
id="cc",
),
pytest.param(
DummyDevice(DeviceType.CF, attributes={CFAttributes.aux_heating: True}),
id="cf",
),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_state_snapshot(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
device: DummyDevice,
) -> None:
"""Test async_setup_entry creates the right switch entities per device type."""
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
async def test_ac_switch_services(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test AC switch service calls reach the device."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: False,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_aux_heating"]
assert (state := hass.states.get(entity_entry.entity_id)) is not None
assert state.state == "off"
await _assert_service_call(
hass,
entity_entry.entity_id,
SERVICE_TURN_ON,
[("set_attribute", ACAttributes.aux_heating, True)],
device,
)
await _assert_service_call(
hass,
entity_entry.entity_id,
SERVICE_TURN_OFF,
[("set_attribute", ACAttributes.aux_heating, False)],
device,
)
async def test_switch_unknown_when_attribute_becomes_non_bool(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test is_on gracefully reports unknown if a later update clears it."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: False,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_aux_heating"]
assert (state := hass.states.get(entity_entry.entity_id))
assert state.state == "off"
device.attributes[ACAttributes.aux_heating] = True
device.notify_update({ACAttributes.aux_heating: True})
await hass.async_block_till_done()
assert (state := hass.states.get(entity_entry.entity_id))
assert state.state == "on"
device.attributes[ACAttributes.aux_heating] = None
device.notify_update({ACAttributes.aux_heating: None})
await hass.async_block_till_done()
assert (state := hass.states.get(entity_entry.entity_id))
assert state.state == "unknown"
async def test_switch_turn_on_raises_on_device_communication_error(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test a device communication failure surfaces as a HomeAssistantError."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: False,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_aux_heating"]
with (
patch.object(device, "set_attribute", side_effect=SocketException("offline")),
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)
async def test_fb_has_no_switch_entities(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test FB devices, which only expose the climate-owned power attribute, get no switches."""
device = DummyDevice(DeviceType.FB, attributes={"power": True})
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
assert entity_entries(hass, config_entry) == {}
async def test_switch_not_created_when_attribute_missing(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test a switch is not created when the device does not report the attribute."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
assert f"{TEST_DEVICE_ID}_aux_heating" not in entity_entries(hass, config_entry)
async def test_switch_not_created_for_climate_owned_attribute(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test no switch is created for attributes already controlled by climate."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.comfort_mode: False,
ACAttributes.eco_mode: False,
ACAttributes.boost_mode: False,
ACAttributes.sleep_mode: False,
ACAttributes.frost_protect: False,
ACAttributes.swing_vertical: True,
ACAttributes.swing_horizontal: True,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
created = entity_entries(hass, config_entry)
for key in (
"power",
"comfort_mode",
"eco_mode",
"boost_mode",
"sleep_mode",
"frost_protect",
"swing_vertical",
"swing_horizontal",
):
assert f"{TEST_DEVICE_ID}_{key}" not in created
async def test_switch_not_created_for_other_device_type(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test a switch scoped to one device type is not created for another."""
device = DummyDevice(
DeviceType.CC,
attributes={
CCAttributes.power: True,
CCAttributes.mode: 1,
# C3-only attribute name reused here to prove the CC device type
# is excluded from the description's `models` even when the
# attribute happens to be present.
"disinfect": True,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
assert f"{TEST_DEVICE_ID}_disinfect" not in entity_entries(hass, config_entry)