diff --git a/homeassistant/components/tuya/humidifier.py b/homeassistant/components/tuya/humidifier.py index f8f3a70c412..06fdc1545c5 100644 --- a/homeassistant/components/tuya/humidifier.py +++ b/homeassistant/components/tuya/humidifier.py @@ -14,14 +14,14 @@ from homeassistant.components.humidifier import ( HumidifierEntityFeature, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import TuyaConfigEntry -from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode, DPType +from .const import TUYA_DISCOVERY_NEW, DPCode, DPType from .entity import TuyaEntity from .models import IntegerTypeData +from .util import ActionDPCodeNotFoundError @dataclass(frozen=True) @@ -171,27 +171,27 @@ class TuyaHumidifierEntity(TuyaEntity, HumidifierEntity): def turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" if self._switch_dpcode is None: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="action_dpcode_not_found", + raise ActionDPCodeNotFoundError( + self.device, + self.entity_description.dpcode or self.entity_description.key, ) self._send_command([{"code": self._switch_dpcode, "value": True}]) def turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" if self._switch_dpcode is None: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="action_dpcode_not_found", + raise ActionDPCodeNotFoundError( + self.device, + self.entity_description.dpcode or self.entity_description.key, ) self._send_command([{"code": self._switch_dpcode, "value": False}]) def set_humidity(self, humidity: int) -> None: """Set new target humidity.""" if self._set_humidity is None: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="action_dpcode_not_found", + raise ActionDPCodeNotFoundError( + self.device, + self.entity_description.humidity, ) self._send_command( diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index 8eb564737e4..ea06e185ba7 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -12,7 +12,6 @@ from homeassistant.components.number import ( ) from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -27,6 +26,7 @@ from .const import ( ) from .entity import TuyaEntity from .models import IntegerTypeData +from .util import ActionDPCodeNotFoundError # All descriptions can be found here. Mostly the Integer data types in the # default instructions set of each category end up being a number. @@ -464,10 +464,7 @@ class TuyaNumberEntity(TuyaEntity, NumberEntity): def set_native_value(self, value: float) -> None: """Set new value.""" if self._number is None: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="action_dpcode_not_found", - ) + raise ActionDPCodeNotFoundError(self.device, self.entity_description.key) self._send_command( [ diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 0e3a26d53d9..d3332ac2e2f 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -945,7 +945,7 @@ }, "exceptions": { "action_dpcode_not_found": { - "message": "Unable to process action as the device does not provide corresponding DPCode." + "message": "Unable to process action as the device does not provide corresponding DPCode (expected one of {expected} in {available})." } } } diff --git a/homeassistant/components/tuya/util.py b/homeassistant/components/tuya/util.py index c1615b89c2d..916a7cfddf4 100644 --- a/homeassistant/components/tuya/util.py +++ b/homeassistant/components/tuya/util.py @@ -2,6 +2,12 @@ from __future__ import annotations +from tuya_sharing import CustomerDevice + +from homeassistant.exceptions import ServiceValidationError + +from .const import DOMAIN, DPCode + def remap_value( value: float, @@ -15,3 +21,25 @@ def remap_value( if reverse: value = from_max - value + from_min return ((value - from_min) / (from_max - from_min)) * (to_max - to_min) + to_min + + +class ActionDPCodeNotFoundError(ServiceValidationError): + """Custom exception for action DP code not found errors.""" + + def __init__( + self, device: CustomerDevice, expected: str | DPCode | tuple[DPCode, ...] | None + ) -> None: + """Initialize the error with device and expected DP codes.""" + if expected is None: + expected = () # empty tuple for no expected codes + elif isinstance(expected, str): + expected = (DPCode(expected),) + + super().__init__( + translation_domain=DOMAIN, + translation_key="action_dpcode_not_found", + translation_placeholders={ + "expected": str(sorted([dp.value for dp in expected])), + "available": str(sorted(device.function.keys())), + }, + ) diff --git a/tests/components/tuya/test_humidifier.py b/tests/components/tuya/test_humidifier.py index db64b4c0e60..d4996bcd32a 100644 --- a/tests/components/tuya/test_humidifier.py +++ b/tests/components/tuya/test_humidifier.py @@ -171,6 +171,10 @@ async def test_turn_on_unsupported( blocking=True, ) assert err.value.translation_key == "action_dpcode_not_found" + assert err.value.translation_placeholders == { + "expected": "['switch', 'switch_spray']", + "available": ("[]"), + } @pytest.mark.parametrize( @@ -197,6 +201,10 @@ async def test_turn_off_unsupported( blocking=True, ) assert err.value.translation_key == "action_dpcode_not_found" + assert err.value.translation_placeholders == { + "expected": "['switch', 'switch_spray']", + "available": ("[]"), + } @pytest.mark.parametrize( @@ -226,3 +234,7 @@ async def test_set_humidity_unsupported( blocking=True, ) assert err.value.translation_key == "action_dpcode_not_found" + assert err.value.translation_placeholders == { + "expected": "['dehumidify_set_value']", + "available": ("[]"), + } diff --git a/tests/components/tuya/test_number.py b/tests/components/tuya/test_number.py index 26b853c2879..b6c7b1f6de5 100644 --- a/tests/components/tuya/test_number.py +++ b/tests/components/tuya/test_number.py @@ -119,3 +119,12 @@ async def test_set_value_no_function( blocking=True, ) assert err.value.translation_key == "action_dpcode_not_found" + assert err.value.translation_placeholders == { + "expected": "['delay_set']", + "available": ( + "['alarm_delay_time', 'alarm_time', 'master_mode', 'master_state', " + "'muffling', 'sub_admin', 'sub_class', 'switch_alarm_light', " + "'switch_alarm_propel', 'switch_alarm_sound', 'switch_kb_light', " + "'switch_kb_sound', 'switch_mode_sound']" + ), + }