From fb4a4528723e38797f9f905b8c8e2444a6f5fc48 Mon Sep 17 00:00:00 2001 From: yufeng Date: Tue, 12 Aug 2025 17:02:03 +0800 Subject: [PATCH] Add supply frequency sensors to Tuya energy monitoring devices (#149320) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/components/tuya/const.py | 1 + homeassistant/components/tuya/sensor.py | 14 + homeassistant/components/tuya/strings.json | 3 + tests/components/tuya/__init__.py | 1 + .../tuya/fixtures/dlq_cnpkf4xdmd9v49iq.json | 158 ++++++++ .../tuya/snapshots/test_sensor.ambr | 336 ++++++++++++++++++ .../tuya/snapshots/test_switch.ambr | 96 +++++ 7 files changed, 609 insertions(+) create mode 100644 tests/components/tuya/fixtures/dlq_cnpkf4xdmd9v49iq.json diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 752911f98f6..6c4b1c02b22 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -328,6 +328,7 @@ class DPCode(StrEnum): STATUS = "status" STERILIZATION = "sterilization" # Sterilization SUCTION = "suction" + SUPPLY_FREQUENCY = "supply_frequency" SWING = "swing" # Swing mode SWITCH = "switch" # Switch SWITCH_1 = "switch_1" # Switch 1 diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index a22eba6cc35..82ddb1ee0ab 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -363,6 +363,13 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), + TuyaSensorEntityDescription( + key=DPCode.SUPPLY_FREQUENCY, + translation_key="supply_frequency", + device_class=SensorDeviceClass.FREQUENCY, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), TuyaSensorEntityDescription( key=DPCode.PHASE_A, translation_key="phase_a_current", @@ -1346,6 +1353,13 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { complex_type=ElectricityTypeData, subkey="power", ), + TuyaSensorEntityDescription( + key=DPCode.SUPPLY_FREQUENCY, + translation_key="supply_frequency", + device_class=SensorDeviceClass.FREQUENCY, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), TuyaSensorEntityDescription( key=DPCode.PHASE_A, translation_key="phase_a_current", diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index fab93b52fc1..f185e8d5489 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -743,6 +743,9 @@ }, "liquid_level": { "name": "Liquid level" + }, + "supply_frequency": { + "name": "Supply frequency" } }, "switch": { diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 5daa3d77427..a9087b92211 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -96,6 +96,7 @@ DEVICE_MOCKS = [ "dj_zav1pa32pyxray78", # https://github.com/home-assistant/core/issues/149704 "dj_zputiamzanuk6yky", # https://github.com/home-assistant/core/issues/149704 "dlq_0tnvg2xaisqdadcf", # https://github.com/home-assistant/core/issues/102769 + "dlq_cnpkf4xdmd9v49iq", # https://github.com/home-assistant/core/pull/149320 "dlq_jdj6ccklup7btq3a", # https://github.com/home-assistant/core/issues/143209 "dlq_kxdr6su0c55p7bbo", # https://github.com/home-assistant/core/issues/143499 "dlq_r9kg2g1uhhyicycb", # https://github.com/home-assistant/core/issues/149650 diff --git a/tests/components/tuya/fixtures/dlq_cnpkf4xdmd9v49iq.json b/tests/components/tuya/fixtures/dlq_cnpkf4xdmd9v49iq.json new file mode 100644 index 00000000000..aa42fc0f568 --- /dev/null +++ b/tests/components/tuya/fixtures/dlq_cnpkf4xdmd9v49iq.json @@ -0,0 +1,158 @@ +{ + "endpoint": "https://apigw.tuyacn.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "\u65ad\u8def\u5668HA", + "category": "dlq", + "product_id": "cnpkf4xdmd9v49iq", + "product_name": "Breaker", + "online": true, + "sub": false, + "time_zone": "+08:00", + "active_time": "2025-07-03T10:19:11+00:00", + "create_time": "2025-07-03T10:19:11+00:00", + "update_time": "2025-07-03T10:19:11+00:00", + "function": { + "switch": { + "type": "Boolean", + "value": {} + }, + "alarm_set_1": { + "type": "Raw", + "value": {} + }, + "alarm_set_2": { + "type": "Raw", + "value": {} + }, + "leakagecurr_test": { + "type": "Boolean", + "value": {} + }, + "online_state": { + "type": "Enum", + "value": { + "range": ["online", "offline"] + } + }, + "child_lock": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "forward_energy_total": { + "type": "Integer", + "value": { + "unit": "kW\u00b7h", + "min": 0, + "max": 99999999, + "scale": 3, + "step": 1 + } + }, + "phase_a": { + "type": "Raw", + "value": {} + }, + "fault": { + "type": "Bitmap", + "value": { + "label": [ + "short_circuit_alarm", + "surge_alarm", + "overload_alarm", + "leakagecurr_alarm", + "temp_dif_fault", + "fire_alarm", + "high_power_alarm", + "self_test_alarm", + "ov_cr", + "unbalance_alarm", + "ov_vol", + "undervoltage_alarm", + "miss_phase_alarm", + "outage_alarm", + "magnetism_alarm", + "credit_alarm", + "no_balance_alarm", + "leakage_early_warning", + "overcur_early_warning", + "overvol_early_warning", + "overpow_early_warning", + "undvol_early_warning", + "higtemp_early_warning" + ] + } + }, + "leakage_current": { + "type": "Integer", + "value": { + "unit": "mA", + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "switch": { + "type": "Boolean", + "value": {} + }, + "alarm_set_1": { + "type": "Raw", + "value": {} + }, + "alarm_set_2": { + "type": "Raw", + "value": {} + }, + "breaker_number": { + "type": "String", + "value": { + "maxlen": 255 + } + }, + "leakagecurr_test": { + "type": "Boolean", + "value": {} + }, + "supply_frequency": { + "type": "Integer", + "value": { + "unit": "Hz", + "min": 0, + "max": 9999, + "scale": 2, + "step": 1 + } + }, + "online_state": { + "type": "Enum", + "value": { + "range": ["online", "offline"] + } + }, + "child_lock": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "forward_energy_total": 120, + "phase_a": "Ag8JJQAASAAACAAAAAAACGME", + "fault": 0, + "leakage_current": 0, + "switch": false, + "alarm_set_1": "", + "alarm_set_2": "", + "breaker_number": "", + "leakagecurr_test": false, + "supply_frequency": 0, + "online_state": "online", + "child_lock": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index 88731f34f10..53003ac095a 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -2818,6 +2818,230 @@ 'state': '222.4', }) # --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_current-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': None, + 'entity_id': 'sensor.duan_lu_qi_ha_phase_a_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase A current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_a_current', + 'unique_id': 'tuya.qi94v9dmdx4fkpncqldphase_aelectriccurrent', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': '断路器HA Phase A current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.duan_lu_qi_ha_phase_a_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '599.296', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_power-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': None, + 'entity_id': 'sensor.duan_lu_qi_ha_phase_a_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase A power', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_a_power', + 'unique_id': 'tuya.qi94v9dmdx4fkpncqldphase_apower', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': '断路器HA Phase A power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.duan_lu_qi_ha_phase_a_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '18.432', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_voltage-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': None, + 'entity_id': 'sensor.duan_lu_qi_ha_phase_a_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase A voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_a_voltage', + 'unique_id': 'tuya.qi94v9dmdx4fkpncqldphase_avoltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': '断路器HA Phase A voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.duan_lu_qi_ha_phase_a_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '52.7', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_supply_frequency-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.duan_lu_qi_ha_supply_frequency', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Supply frequency', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'supply_frequency', + 'unique_id': 'tuya.qi94v9dmdx4fkpncqldsupply_frequency', + 'unit_of_measurement': 'Hz', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_supply_frequency-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'frequency', + 'friendly_name': '断路器HA Supply frequency', + 'state_class': , + 'unit_of_measurement': 'Hz', + }), + 'context': , + 'entity_id': 'sensor.duan_lu_qi_ha_supply_frequency', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_platform_setup_and_discovery[sensor.eau_chaude_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -7641,6 +7865,62 @@ 'state': '220.4', }) # --- +# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_supply_frequency-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.metering_3pn_wifi_stable_supply_frequency', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Supply frequency', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'supply_frequency', + 'unique_id': 'tuya.obb7p55c0us6rdxkqldsupply_frequency', + 'unit_of_measurement': 'Hz', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_supply_frequency-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'frequency', + 'friendly_name': 'Metering_3PN_WiFi_stable Supply frequency', + 'state_class': , + 'unit_of_measurement': 'Hz', + }), + 'context': , + 'entity_id': 'sensor.metering_3pn_wifi_stable_supply_frequency', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '50.0', + }) +# --- # name: test_platform_setup_and_discovery[sensor.motion_sensor_lidl_zigbee_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -12542,6 +12822,62 @@ 'state': '52.7', }) # --- +# name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_supply_frequency-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.xoca_dac212xc_v2_s1_supply_frequency', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Supply frequency', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'supply_frequency', + 'unique_id': 'tuya.9oh1h1uyalfykgg4bdnzsupply_frequency', + 'unit_of_measurement': 'Hz', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_supply_frequency-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'frequency', + 'friendly_name': 'XOCA-DAC212XC V2-S1 Supply frequency', + 'state_class': , + 'unit_of_measurement': 'Hz', + }), + 'context': , + 'entity_id': 'sensor.xoca_dac212xc_v2_s1_supply_frequency', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_total_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_switch.ambr b/tests/components/tuya/snapshots/test_switch.ambr index bff8c3e392a..d33c91118ef 100644 --- a/tests/components/tuya/snapshots/test_switch.ambr +++ b/tests/components/tuya/snapshots/test_switch.ambr @@ -2565,6 +2565,102 @@ 'state': 'on', }) # --- +# name: test_platform_setup_and_discovery[switch.duan_lu_qi_ha_child_lock-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': , + 'entity_id': 'switch.duan_lu_qi_ha_child_lock', + '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': 'Child lock', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': 'tuya.qi94v9dmdx4fkpncqldchild_lock', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.duan_lu_qi_ha_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': '断路器HA Child lock', + }), + 'context': , + 'entity_id': 'switch.duan_lu_qi_ha_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[switch.duan_lu_qi_ha_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.duan_lu_qi_ha_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.qi94v9dmdx4fkpncqldswitch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.duan_lu_qi_ha_switch-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': '断路器HA Switch', + }), + 'context': , + 'entity_id': 'switch.duan_lu_qi_ha_switch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.eau_chaude_child_lock-entry] EntityRegistryEntrySnapshot({ 'aliases': set({