1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-26 12:55:16 +00:00

Fix Matter speaker mute toggle (#161128)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ludovic BOUÉ
2026-02-23 14:38:37 +01:00
committed by GitHub
parent 74a3f4bbb9
commit cdb92a54b0
2 changed files with 81 additions and 28 deletions

View File

@@ -46,30 +46,42 @@ async def async_setup_entry(
class MatterSwitchEntityDescription(SwitchEntityDescription, MatterEntityDescription):
"""Describe Matter Switch entities."""
inverted: bool = False
class MatterSwitch(MatterEntity, SwitchEntity):
"""Representation of a Matter switch."""
entity_description: MatterSwitchEntityDescription
_platform_translation_key = "switch"
def _get_command_for_value(self, value: bool) -> ClusterCommand:
"""Get the appropriate command for the desired value.
Applies inversion if needed (e.g., for inverted logic like mute).
"""
send_value = not value if self.entity_description.inverted else value
return (
clusters.OnOff.Commands.On()
if send_value
else clusters.OnOff.Commands.Off()
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
await self.send_device_command(
clusters.OnOff.Commands.On(),
)
await self.send_device_command(self._get_command_for_value(True))
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn switch off."""
await self.send_device_command(
clusters.OnOff.Commands.Off(),
)
await self.send_device_command(self._get_command_for_value(False))
@callback
def _update_from_device(self) -> None:
"""Update from device."""
self._attr_is_on = self.get_matter_attribute_value(
self._entity_info.primary_attribute
)
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
if self.entity_description.inverted:
value = not value
self._attr_is_on = value
class MatterGenericCommandSwitch(MatterSwitch):
@@ -121,9 +133,7 @@ class MatterGenericCommandSwitch(MatterSwitch):
@dataclass(frozen=True, kw_only=True)
class MatterGenericCommandSwitchEntityDescription(
SwitchEntityDescription, MatterEntityDescription
):
class MatterGenericCommandSwitchEntityDescription(MatterSwitchEntityDescription):
"""Describe Matter Generic command Switch entities."""
# command: a custom callback to create the command to send to the device
@@ -133,9 +143,7 @@ class MatterGenericCommandSwitchEntityDescription(
@dataclass(frozen=True, kw_only=True)
class MatterNumericSwitchEntityDescription(
SwitchEntityDescription, MatterEntityDescription
):
class MatterNumericSwitchEntityDescription(MatterSwitchEntityDescription):
"""Describe Matter Numeric Switch entities."""
@@ -146,11 +154,10 @@ class MatterNumericSwitch(MatterSwitch):
async def _async_set_native_value(self, value: bool) -> None:
"""Update the current value."""
send_value: Any = value
if value_convert := self.entity_description.ha_to_device:
send_value = value_convert(value)
await self.write_attribute(
value=send_value,
)
await self.write_attribute(value=send_value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
@@ -248,19 +255,12 @@ DISCOVERY_SCHEMAS = [
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterNumericSwitchEntityDescription(
entity_description=MatterSwitchEntityDescription(
key="MatterMuteToggle",
translation_key="speaker_mute",
device_to_ha={
True: False, # True means volume is on, so HA should show mute as off
False: True, # False means volume is off (muted), so HA should show mute as on
}.get,
ha_to_device={
False: True, # HA showing mute as off means volume is on, so send True
True: False, # HA showing mute as on means volume is off (muted), so send False
}.get,
inverted=True,
),
entity_class=MatterNumericSwitch,
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
device_type=(device_types.Speaker,),
),

View File

@@ -232,3 +232,56 @@ async def test_evse_sensor(
),
timed_request_timeout_ms=3000,
)
@pytest.mark.parametrize("node_fixture", ["mock_speaker"])
async def test_speaker_mute_uses_onoff_commands(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test speaker mute switch uses On/Off commands instead of attribute writes."""
state = hass.states.get("switch.mock_speaker_mute")
assert state
assert state.state == "off"
await hass.services.async_call(
"switch",
"turn_on",
{"entity_id": "switch.mock_speaker_mute"},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
assert matter_client.send_device_command.call_args == call(
node_id=matter_node.node_id,
endpoint_id=1,
command=clusters.OnOff.Commands.Off(),
)
set_node_attribute(matter_node, 1, 6, 0, False)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("switch.mock_speaker_mute")
assert state
assert state.state == "on"
await hass.services.async_call(
"switch",
"turn_off",
{"entity_id": "switch.mock_speaker_mute"},
blocking=True,
)
assert matter_client.send_device_command.call_count == 2
assert matter_client.send_device_command.call_args == call(
node_id=matter_node.node_id,
endpoint_id=1,
command=clusters.OnOff.Commands.On(),
)
set_node_attribute(matter_node, 1, 6, 0, True)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("switch.mock_speaker_mute")
assert state
assert state.state == "off"