From 0f3e6efe759f892d4a88205cf88d4958a1ef882a Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Fri, 31 Jan 2025 12:55:02 +0100 Subject: [PATCH 1/8] Show the new errors from the MotionMount --- .../components/motionmount/sensor.py | 28 +++++++++++++------ .../components/motionmount/strings.json | 3 ++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/motionmount/sensor.py b/homeassistant/components/motionmount/sensor.py index 8e55fad4a8b67f..f2d41bf81b7d64 100644 --- a/homeassistant/components/motionmount/sensor.py +++ b/homeassistant/components/motionmount/sensor.py @@ -25,7 +25,14 @@ class MotionMountErrorStatusSensor(MotionMountEntity, SensorEntity): """The error status sensor of a MotionMount.""" _attr_device_class = SensorDeviceClass.ENUM - _attr_options = ["none", "motor", "internal"] + _attr_options = [ + "none", + "motor", + "hdmi_cec", + "obstruction", + "tv_width_constraint", + "internal", + ] _attr_translation_key = "motionmount_error_status" def __init__( @@ -38,13 +45,16 @@ def __init__( @property def native_value(self) -> str: """Return error status.""" - errors = self.mm.error_status or 0 - - if errors & (1 << 31): - # Only when but 31 is set are there any errors active at this moment - if errors & (1 << 10): - return "motor" - + status = self.mm.system_status + + if motionmount.MotionMountSystemError.MotorError in status: + return "motor" + if motionmount.MotionMountSystemError.ObstructionDetected in status: + return "obstruction" + if motionmount.MotionMountSystemError.TVWidthConstraintError in status: + return "tv_width_constraint" + if motionmount.MotionMountSystemError.HDMICECError in status: + return "hdmi_cec" + if motionmount.MotionMountSystemError.InternalError in status: return "internal" - return "none" diff --git a/homeassistant/components/motionmount/strings.json b/homeassistant/components/motionmount/strings.json index 1fcb6c47c99670..75fd0773322f96 100644 --- a/homeassistant/components/motionmount/strings.json +++ b/homeassistant/components/motionmount/strings.json @@ -72,6 +72,9 @@ "state": { "none": "None", "motor": "Motor", + "hdmi_cec": "HDMI CEC", + "obstruction": "Obstruction", + "tv_width_constraint": "TV width constraint", "internal": "Internal" } } From 965424c02c3d94de2ec6ba36a53d57feb87d8500 Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Mon, 3 Feb 2025 13:39:19 +0100 Subject: [PATCH 2/8] Add tests --- tests/components/motionmount/test_sensor.py | 139 ++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tests/components/motionmount/test_sensor.py diff --git a/tests/components/motionmount/test_sensor.py b/tests/components/motionmount/test_sensor.py new file mode 100644 index 00000000000000..1c91c9dda125f4 --- /dev/null +++ b/tests/components/motionmount/test_sensor.py @@ -0,0 +1,139 @@ +"""Tests for the MotionMount Sensor platform.""" + +from unittest.mock import MagicMock, PropertyMock + +import motionmount + +from homeassistant.core import HomeAssistant + +from . import ZEROCONF_NAME + +from tests.common import MockConfigEntry + +MAC = bytes.fromhex("c4dd57f8a55f") + + +async def test_error_status_sensor_none( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount_config_flow: MagicMock, +) -> None: + """Tests the state attributes.""" + mock_config_entry.add_to_hass(hass) + + type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) + type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) + type(mock_motionmount_config_flow).is_authenticated = PropertyMock( + return_value=True + ) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert hass.states.get("sensor.my_motionmount_error_status").state == "none" + + +async def test_error_status_sensor_motor( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount_config_flow: MagicMock, +) -> None: + """Tests the state attributes.""" + mock_config_entry.add_to_hass(hass) + + type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) + type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) + type(mock_motionmount_config_flow).is_authenticated = PropertyMock( + return_value=True + ) + type(mock_motionmount_config_flow).system_status = PropertyMock( + return_value=[motionmount.MotionMountSystemError.MotorError] + ) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert hass.states.get("sensor.my_motionmount_error_status").state == "motor" + + +async def test_error_status_sensor_obstruction( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount_config_flow: MagicMock, +) -> None: + """Tests the state attributes.""" + mock_config_entry.add_to_hass(hass) + + type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) + type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) + type(mock_motionmount_config_flow).is_authenticated = PropertyMock( + return_value=True + ) + type(mock_motionmount_config_flow).system_status = PropertyMock( + return_value=[motionmount.MotionMountSystemError.ObstructionDetected] + ) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert hass.states.get("sensor.my_motionmount_error_status").state == "obstruction" + + +async def test_error_status_sensor_tv_width_constraint( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount_config_flow: MagicMock, +) -> None: + """Tests the state attributes.""" + mock_config_entry.add_to_hass(hass) + + type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) + type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) + type(mock_motionmount_config_flow).is_authenticated = PropertyMock( + return_value=True + ) + type(mock_motionmount_config_flow).system_status = PropertyMock( + return_value=[motionmount.MotionMountSystemError.TVWidthConstraintError] + ) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert ( + hass.states.get("sensor.my_motionmount_error_status").state + == "tv_width_constraint" + ) + + +async def test_error_status_sensor_hdmi_cec( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount_config_flow: MagicMock, +) -> None: + """Tests the state attributes.""" + mock_config_entry.add_to_hass(hass) + + type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) + type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) + type(mock_motionmount_config_flow).is_authenticated = PropertyMock( + return_value=True + ) + type(mock_motionmount_config_flow).system_status = PropertyMock( + return_value=[motionmount.MotionMountSystemError.HDMICECError] + ) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert hass.states.get("sensor.my_motionmount_error_status").state == "hdmi_cec" + + +async def test_error_status_sensor_internal( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount_config_flow: MagicMock, +) -> None: + """Tests the state attributes.""" + mock_config_entry.add_to_hass(hass) + + type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) + type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) + type(mock_motionmount_config_flow).is_authenticated = PropertyMock( + return_value=True + ) + type(mock_motionmount_config_flow).system_status = PropertyMock( + return_value=[motionmount.MotionMountSystemError.InternalError] + ) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert hass.states.get("sensor.my_motionmount_error_status").state == "internal" From d61a9be358f7a14a9c0821bbd96e04246e9872ca Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Mon, 3 Feb 2025 16:34:49 +0100 Subject: [PATCH 3/8] Iterate over dict to get error message --- .../components/motionmount/sensor.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/motionmount/sensor.py b/homeassistant/components/motionmount/sensor.py index f2d41bf81b7d64..1a591153001e30 100644 --- a/homeassistant/components/motionmount/sensor.py +++ b/homeassistant/components/motionmount/sensor.py @@ -47,14 +47,16 @@ def native_value(self) -> str: """Return error status.""" status = self.mm.system_status - if motionmount.MotionMountSystemError.MotorError in status: - return "motor" - if motionmount.MotionMountSystemError.ObstructionDetected in status: - return "obstruction" - if motionmount.MotionMountSystemError.TVWidthConstraintError in status: - return "tv_width_constraint" - if motionmount.MotionMountSystemError.HDMICECError in status: - return "hdmi_cec" - if motionmount.MotionMountSystemError.InternalError in status: - return "internal" + errorMessages = { + motionmount.MotionMountSystemError.MotorError: "motor", + motionmount.MotionMountSystemError.ObstructionDetected: "obstruction", + motionmount.MotionMountSystemError.TVWidthConstraintError: "tv_width_constraint", + motionmount.MotionMountSystemError.HDMICECError: "hdmi_cec", + motionmount.MotionMountSystemError.InternalError: "internal", + } + + for error, message in errorMessages.items(): + if error in status: + return message + return "none" From a3aa454ef0ee4b6eadf781a07f7769849e0840bd Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Mon, 3 Feb 2025 16:37:27 +0100 Subject: [PATCH 4/8] Reduce namespace prefixes --- homeassistant/components/motionmount/sensor.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/motionmount/sensor.py b/homeassistant/components/motionmount/sensor.py index 1a591153001e30..d788b15630a13b 100644 --- a/homeassistant/components/motionmount/sensor.py +++ b/homeassistant/components/motionmount/sensor.py @@ -1,6 +1,7 @@ """Support for MotionMount sensors.""" import motionmount +from motionmount import MotionMountSystemError from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.core import HomeAssistant @@ -48,11 +49,11 @@ def native_value(self) -> str: status = self.mm.system_status errorMessages = { - motionmount.MotionMountSystemError.MotorError: "motor", - motionmount.MotionMountSystemError.ObstructionDetected: "obstruction", - motionmount.MotionMountSystemError.TVWidthConstraintError: "tv_width_constraint", - motionmount.MotionMountSystemError.HDMICECError: "hdmi_cec", - motionmount.MotionMountSystemError.InternalError: "internal", + MotionMountSystemError.MotorError: "motor", + MotionMountSystemError.ObstructionDetected: "obstruction", + MotionMountSystemError.TVWidthConstraintError: "tv_width_constraint", + MotionMountSystemError.HDMICECError: "hdmi_cec", + MotionMountSystemError.InternalError: "internal", } for error, message in errorMessages.items(): From a5a07fd93d1fb846057c4fe567249492eeb38703 Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Mon, 3 Feb 2025 16:45:03 +0100 Subject: [PATCH 5/8] Fix casing --- homeassistant/components/motionmount/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/motionmount/sensor.py b/homeassistant/components/motionmount/sensor.py index d788b15630a13b..7003399b59c021 100644 --- a/homeassistant/components/motionmount/sensor.py +++ b/homeassistant/components/motionmount/sensor.py @@ -48,7 +48,7 @@ def native_value(self) -> str: """Return error status.""" status = self.mm.system_status - errorMessages = { + error_messages = { MotionMountSystemError.MotorError: "motor", MotionMountSystemError.ObstructionDetected: "obstruction", MotionMountSystemError.TVWidthConstraintError: "tv_width_constraint", @@ -56,7 +56,7 @@ def native_value(self) -> str: MotionMountSystemError.InternalError: "internal", } - for error, message in errorMessages.items(): + for error, message in error_messages.items(): if error in status: return message From 7c47cdf7dc66ad58828861e56d9123fcf80bdcd5 Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Tue, 4 Feb 2025 09:34:09 +0100 Subject: [PATCH 6/8] Move errors to module level --- homeassistant/components/motionmount/sensor.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/motionmount/sensor.py b/homeassistant/components/motionmount/sensor.py index 7003399b59c021..33bddb2d76b14c 100644 --- a/homeassistant/components/motionmount/sensor.py +++ b/homeassistant/components/motionmount/sensor.py @@ -10,6 +10,14 @@ from . import MotionMountConfigEntry from .entity import MotionMountEntity +error_messages = { + MotionMountSystemError.MotorError: "motor", + MotionMountSystemError.ObstructionDetected: "obstruction", + MotionMountSystemError.TVWidthConstraintError: "tv_width_constraint", + MotionMountSystemError.HDMICECError: "hdmi_cec", + MotionMountSystemError.InternalError: "internal", +} + async def async_setup_entry( hass: HomeAssistant, @@ -48,14 +56,6 @@ def native_value(self) -> str: """Return error status.""" status = self.mm.system_status - error_messages = { - MotionMountSystemError.MotorError: "motor", - MotionMountSystemError.ObstructionDetected: "obstruction", - MotionMountSystemError.TVWidthConstraintError: "tv_width_constraint", - MotionMountSystemError.HDMICECError: "hdmi_cec", - MotionMountSystemError.InternalError: "internal", - } - for error, message in error_messages.items(): if error in status: return message From 61d3c726d7d3953a42a90557d71a0625e210760c Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Tue, 4 Feb 2025 10:12:32 +0100 Subject: [PATCH 7/8] Use parameter for testing various system states --- tests/components/motionmount/test_sensor.py | 126 +++----------------- 1 file changed, 18 insertions(+), 108 deletions(-) diff --git a/tests/components/motionmount/test_sensor.py b/tests/components/motionmount/test_sensor.py index 1c91c9dda125f4..5b08054e5cfb62 100644 --- a/tests/components/motionmount/test_sensor.py +++ b/tests/components/motionmount/test_sensor.py @@ -2,7 +2,8 @@ from unittest.mock import MagicMock, PropertyMock -import motionmount +from motionmount import MotionMountSystemError +import pytest from homeassistant.core import HomeAssistant @@ -13,117 +14,26 @@ MAC = bytes.fromhex("c4dd57f8a55f") -async def test_error_status_sensor_none( +@pytest.mark.parametrize( + "system_status", + [ + (None, "none"), + (MotionMountSystemError.MotorError, "motor"), + (MotionMountSystemError.ObstructionDetected, "obstruction"), + (MotionMountSystemError.TVWidthConstraintError, "tv_width_constraint"), + (MotionMountSystemError.HDMICECError, "hdmi_cec"), + (MotionMountSystemError.InternalError, "internal"), + ], +) +async def test_error_status_sensor_states( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_motionmount_config_flow: MagicMock, + system_status: (MotionMountSystemError, str), ) -> None: """Tests the state attributes.""" - mock_config_entry.add_to_hass(hass) - - type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) - type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) - type(mock_motionmount_config_flow).is_authenticated = PropertyMock( - return_value=True - ) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - - assert hass.states.get("sensor.my_motionmount_error_status").state == "none" - - -async def test_error_status_sensor_motor( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_motionmount_config_flow: MagicMock, -) -> None: - """Tests the state attributes.""" - mock_config_entry.add_to_hass(hass) - - type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) - type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) - type(mock_motionmount_config_flow).is_authenticated = PropertyMock( - return_value=True - ) - type(mock_motionmount_config_flow).system_status = PropertyMock( - return_value=[motionmount.MotionMountSystemError.MotorError] - ) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - - assert hass.states.get("sensor.my_motionmount_error_status").state == "motor" - - -async def test_error_status_sensor_obstruction( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_motionmount_config_flow: MagicMock, -) -> None: - """Tests the state attributes.""" - mock_config_entry.add_to_hass(hass) - - type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) - type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) - type(mock_motionmount_config_flow).is_authenticated = PropertyMock( - return_value=True - ) - type(mock_motionmount_config_flow).system_status = PropertyMock( - return_value=[motionmount.MotionMountSystemError.ObstructionDetected] - ) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + (status, state) = system_status - assert hass.states.get("sensor.my_motionmount_error_status").state == "obstruction" - - -async def test_error_status_sensor_tv_width_constraint( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_motionmount_config_flow: MagicMock, -) -> None: - """Tests the state attributes.""" - mock_config_entry.add_to_hass(hass) - - type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) - type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) - type(mock_motionmount_config_flow).is_authenticated = PropertyMock( - return_value=True - ) - type(mock_motionmount_config_flow).system_status = PropertyMock( - return_value=[motionmount.MotionMountSystemError.TVWidthConstraintError] - ) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - - assert ( - hass.states.get("sensor.my_motionmount_error_status").state - == "tv_width_constraint" - ) - - -async def test_error_status_sensor_hdmi_cec( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_motionmount_config_flow: MagicMock, -) -> None: - """Tests the state attributes.""" - mock_config_entry.add_to_hass(hass) - - type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) - type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) - type(mock_motionmount_config_flow).is_authenticated = PropertyMock( - return_value=True - ) - type(mock_motionmount_config_flow).system_status = PropertyMock( - return_value=[motionmount.MotionMountSystemError.HDMICECError] - ) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - - assert hass.states.get("sensor.my_motionmount_error_status").state == "hdmi_cec" - - -async def test_error_status_sensor_internal( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_motionmount_config_flow: MagicMock, -) -> None: - """Tests the state attributes.""" mock_config_entry.add_to_hass(hass) type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) @@ -132,8 +42,8 @@ async def test_error_status_sensor_internal( return_value=True ) type(mock_motionmount_config_flow).system_status = PropertyMock( - return_value=[motionmount.MotionMountSystemError.InternalError] + return_value=[status] ) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - assert hass.states.get("sensor.my_motionmount_error_status").state == "internal" + assert hass.states.get("sensor.my_motionmount_error_status").state == state From 344d3a997d3577369968082c77a258bd127c8721 Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Tue, 4 Feb 2025 13:10:52 +0100 Subject: [PATCH 8/8] Use patch to create mock --- tests/components/motionmount/test_sensor.py | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/motionmount/test_sensor.py b/tests/components/motionmount/test_sensor.py index 5b08054e5cfb62..da094042e00253 100644 --- a/tests/components/motionmount/test_sensor.py +++ b/tests/components/motionmount/test_sensor.py @@ -1,6 +1,6 @@ """Tests for the MotionMount Sensor platform.""" -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import patch from motionmount import MotionMountSystemError import pytest @@ -28,22 +28,22 @@ async def test_error_status_sensor_states( hass: HomeAssistant, mock_config_entry: MockConfigEntry, - mock_motionmount_config_flow: MagicMock, system_status: (MotionMountSystemError, str), ) -> None: """Tests the state attributes.""" (status, state) = system_status - mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.motionmount.motionmount.MotionMount", + autospec=True, + ) as motionmount_mock: + motionmount_mock.return_value.name = ZEROCONF_NAME + motionmount_mock.return_value.mac = MAC + motionmount_mock.return_value.is_authenticated = True + motionmount_mock.return_value.system_status = [status] - type(mock_motionmount_config_flow).name = PropertyMock(return_value=ZEROCONF_NAME) - type(mock_motionmount_config_flow).mac = PropertyMock(return_value=MAC) - type(mock_motionmount_config_flow).is_authenticated = PropertyMock( - return_value=True - ) - type(mock_motionmount_config_flow).system_status = PropertyMock( - return_value=[status] - ) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + mock_config_entry.add_to_hass(hass) - assert hass.states.get("sensor.my_motionmount_error_status").state == state + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + assert hass.states.get("sensor.my_motionmount_error_status").state == state