From bafd342d5dfd741786b4c6e9feca7059dcfceca9 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:54:44 +0200 Subject: [PATCH] Add initial support for tuya cwjwq (#148420) --- homeassistant/components/tuya/const.py | 1 + homeassistant/components/tuya/select.py | 9 ++ homeassistant/components/tuya/sensor.py | 9 ++ homeassistant/components/tuya/strings.json | 16 +++ homeassistant/components/tuya/switch.py | 8 ++ tests/components/tuya/__init__.py | 6 ++ .../fixtures/cwjwq_smart_odor_eliminator.json | 66 ++++++++++++ .../tuya/snapshots/test_select.ambr | 57 ++++++++++ .../tuya/snapshots/test_sensor.ambr | 101 ++++++++++++++++++ .../tuya/snapshots/test_switch.ambr | 48 +++++++++ 10 files changed, 321 insertions(+) create mode 100644 tests/components/tuya/fixtures/cwjwq_smart_odor_eliminator.json diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 61da1239554..863ef451eaa 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -406,6 +406,7 @@ class DPCode(StrEnum): WIRELESS_ELECTRICITY = "wireless_electricity" WORK_MODE = "work_mode" # Working mode WORK_POWER = "work_power" + WORK_STATE_E = "work_state_e" @dataclass diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index 4ad4355f876..22229b3f6bf 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -55,6 +55,15 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { entity_category=EntityCategory.CONFIG, ), ), + # Smart Odor Eliminator-Pro + # Undocumented, see https://github.com/orgs/home-assistant/discussions/79 + "cwjwq": ( + SelectEntityDescription( + key=DPCode.WORK_MODE, + entity_category=EntityCategory.CONFIG, + translation_key="odor_elimination_mode", + ), + ), # Multi-functional Sensor # https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3 "dgnbj": ( diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index d1220e08728..a4e1e931a5f 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -220,6 +220,15 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { state_class=SensorStateClass.MEASUREMENT, ), ), + # Smart Odor Eliminator-Pro + # Undocumented, see https://github.com/orgs/home-assistant/discussions/79 + "cwjwq": ( + TuyaSensorEntityDescription( + key=DPCode.WORK_STATE_E, + translation_key="odor_elimination_status", + ), + *BATTERY_SENSORS, + ), # Smart Pet Feeder # https://developer.tuya.com/en/docs/iot/categorycwwsq?id=Kaiuz2b6vydld "cwwsq": ( diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index a5302b2e88b..d5ccfffb79c 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -485,6 +485,13 @@ "level_9": "Level 9", "level_10": "High" } + }, + "odor_elimination_mode": { + "name": "Odor elimination mode", + "state": { + "smart": "Smart", + "interim": "Interim" + } } }, "sensor": { @@ -697,6 +704,15 @@ }, "water_time": { "name": "Water usage duration" + }, + "odor_elimination_status": { + "name": "Status", + "state": { + "work": "Working", + "standby": "[%key:common::state::standby%]", + "charging": "[%key:common::state::charging%]", + "charge_done": "Charge done" + } } }, "switch": { diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index bfe80ec67bf..2cc7970d45a 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -85,6 +85,14 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { entity_category=EntityCategory.CONFIG, ), ), + # Smart Odor Eliminator-Pro + # Undocumented, see https://github.com/orgs/home-assistant/discussions/79 + "cwjwq": ( + SwitchEntityDescription( + key=DPCode.SWITCH, + translation_key="switch", + ), + ), # Smart Pet Feeder # https://developer.tuya.com/en/docs/iot/categorycwwsq?id=Kaiuz2b6vydld "cwwsq": ( diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 6427a69cdea..f0e2596fc81 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -45,6 +45,12 @@ DEVICE_MOCKS = { Platform.FAN, Platform.HUMIDIFIER, ], + "cwjwq_smart_odor_eliminator": [ + # https://github.com/orgs/home-assistant/discussions/79 + Platform.SELECT, + Platform.SENSOR, + Platform.SWITCH, + ], "cwwsq_cleverio_pf100": [ # https://github.com/home-assistant/core/issues/144745 Platform.NUMBER, diff --git a/tests/components/tuya/fixtures/cwjwq_smart_odor_eliminator.json b/tests/components/tuya/fixtures/cwjwq_smart_odor_eliminator.json new file mode 100644 index 00000000000..a4a9fc6aaff --- /dev/null +++ b/tests/components/tuya/fixtures/cwjwq_smart_odor_eliminator.json @@ -0,0 +1,66 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "1750837476328i3TNXQ", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf6574iutyikgwkx", + "name": "Smart Odor Eliminator-Pro", + "category": "cwjwq", + "product_id": "agwu93lr", + "product_name": "Smart Odor Eliminator-Pro", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2025-06-25T07:43:07+00:00", + "create_time": "2025-06-25T07:43:07+00:00", + "update_time": "2025-06-25T07:43:07+00:00", + "function": { + "switch": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["smart", "interim"] + } + } + }, + "status_range": { + "switch": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["smart", "interim"] + } + }, + "work_state_e": { + "type": "Enum", + "value": { + "range": ["work", "standby", "charging", "charge_done"] + } + }, + "battery_percentage": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "switch": false, + "work_mode": "smart", + "work_state_e": "work", + "battery_percentage": 43 + }, + "set_up": false, + "support_local": true +} diff --git a/tests/components/tuya/snapshots/test_select.ambr b/tests/components/tuya/snapshots/test_select.ambr index 2c5b0e86619..6f45f63dcfa 100644 --- a/tests/components/tuya/snapshots/test_select.ambr +++ b/tests/components/tuya/snapshots/test_select.ambr @@ -178,6 +178,63 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][select.smart_odor_eliminator_pro_odor_elimination_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'smart', + 'interim', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.smart_odor_eliminator_pro_odor_elimination_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Odor elimination mode', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'odor_elimination_mode', + 'unique_id': 'tuya.bf6574iutyikgwkxwork_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][select.smart_odor_eliminator_pro_odor_elimination_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Smart Odor Eliminator-Pro Odor elimination mode', + 'options': list([ + 'smart', + 'interim', + ]), + }), + 'context': , + 'entity_id': 'select.smart_odor_eliminator_pro_odor_elimination_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_platform_setup_and_discovery[kj_bladeless_tower_fan][select.bree_countdown-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index f0350f12c48..b637839333d 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -105,6 +105,107 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][sensor.smart_odor_eliminator_pro_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.smart_odor_eliminator_pro_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'battery', + 'unique_id': 'tuya.bf6574iutyikgwkxbattery_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][sensor.smart_odor_eliminator_pro_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Smart Odor Eliminator-Pro Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.smart_odor_eliminator_pro_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][sensor.smart_odor_eliminator_pro_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.smart_odor_eliminator_pro_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'odor_elimination_status', + 'unique_id': 'tuya.bf6574iutyikgwkxwork_state_e', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][sensor.smart_odor_eliminator_pro_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Smart Odor Eliminator-Pro Status', + }), + 'context': , + 'entity_id': 'sensor.smart_odor_eliminator_pro_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_platform_setup_and_discovery[cwwsq_cleverio_pf100][sensor.cleverio_pf100_last_amount-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_switch.ambr b/tests/components/tuya/snapshots/test_switch.ambr index 4e6af0fa7d3..1ed4e9fdc1b 100644 --- a/tests/components/tuya/snapshots/test_switch.ambr +++ b/tests/components/tuya/snapshots/test_switch.ambr @@ -146,6 +146,54 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][switch.smart_odor_eliminator_pro_switch-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.smart_odor_eliminator_pro_switch', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Switch', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'switch', + 'unique_id': 'tuya.bf6574iutyikgwkxswitch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[cwjwq_smart_odor_eliminator][switch.smart_odor_eliminator_pro_switch-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Smart Odor Eliminator-Pro Switch', + }), + 'context': , + 'entity_id': 'switch.smart_odor_eliminator_pro_switch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_platform_setup_and_discovery[cwysj_pixi_smart_drinking_fountain][switch.pixi_smart_drinking_fountain_filter_reset-entry] EntityRegistryEntrySnapshot({ 'aliases': set({