-
-
Notifications
You must be signed in to change notification settings - Fork 34.4k
Fix Tuya support for climate fan modes which use "windspeed" function #148646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
8b0c670
d6900de
2807218
cca5585
688ddfd
9d098f2
e744753
c7ad29b
4b3f421
aa60390
65056d8
5591909
0a4c352
256ba4b
5d67249
d602590
f45bf89
5dd0e69
e9c4074
ae779ba
8b8f4d0
91ee468
96edadc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,6 +97,10 @@ | |
Platform.SELECT, | ||
Platform.SWITCH, | ||
], | ||
"kt_serenelife_slpac905wuk_air_conditioner": [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep in alphabetical order (after ks_tower_fan) |
||
# https://github.com/home-assistant/core/pull/148646 | ||
Platform.CLIMATE, | ||
], | ||
"ks_tower_fan": [ | ||
# https://github.com/orgs/home-assistant/discussions/329 | ||
Platform.FAN, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
{ | ||
"endpoint": "https://apigw.tuyaeu.com", | ||
"terminal_id": "mock_terminal_id", | ||
"mqtt_connected": true, | ||
"disabled_by": null, | ||
"disabled_polling": false, | ||
"id": "mock_device_id", | ||
"name": "Air Conditioner", | ||
"category": "kt", | ||
"product_id": "5wnlzekkstwcdsvm", | ||
"product_name": "\u79fb\u52a8\u7a7a\u8c03 YPK--\uff08\u53cc\u6a21+\u84dd\u7259\uff09\u4f4e\u529f\u8017", | ||
"online": true, | ||
"sub": false, | ||
"time_zone": "+01:00", | ||
"active_time": "2025-07-06T10:10:44+00:00", | ||
"create_time": "2025-07-06T10:10:44+00:00", | ||
"update_time": "2025-07-06T10:10:44+00:00", | ||
"function": { | ||
"switch": { | ||
"type": "Boolean", | ||
"value": {} | ||
}, | ||
"temp_set": { | ||
"type": "Integer", | ||
"value": { | ||
"unit": "\u2103 \u2109", | ||
"min": 16, | ||
"max": 86, | ||
"scale": 0, | ||
"step": 1 | ||
} | ||
}, | ||
"windspeed": { | ||
"type": "Enum", | ||
"value": { | ||
"range": ["1", "2"] | ||
} | ||
} | ||
}, | ||
"status_range": { | ||
"switch": { | ||
"type": "Boolean", | ||
"value": {} | ||
}, | ||
"temp_set": { | ||
"type": "Integer", | ||
"value": { | ||
"unit": "\u2103 \u2109", | ||
"min": 16, | ||
"max": 86, | ||
"scale": 0, | ||
"step": 1 | ||
} | ||
}, | ||
"temp_current": { | ||
"type": "Integer", | ||
"value": { | ||
"unit": "\u2103 \u2109", | ||
"min": -7, | ||
"max": 98, | ||
"scale": 0, | ||
"step": 1 | ||
} | ||
}, | ||
"windspeed": { | ||
"type": "Enum", | ||
"value": { | ||
"range": ["1", "2"] | ||
} | ||
} | ||
}, | ||
"status": { | ||
"switch": false, | ||
"temp_set": 23, | ||
"temp_current": 22, | ||
"windspeed": 1 | ||
}, | ||
"set_up": true, | ||
"support_local": true | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,22 +2,99 @@ | |||||||||
|
||||||||||
from __future__ import annotations | ||||||||||
|
||||||||||
from typing import cast | ||||||||||
from unittest.mock import patch | ||||||||||
|
||||||||||
import pytest | ||||||||||
from syrupy.assertion import SnapshotAssertion | ||||||||||
from tuya_sharing import CustomerDevice | ||||||||||
from tuya_sharing import CustomerDevice, Manager | ||||||||||
|
||||||||||
from homeassistant.components.climate import HVACMode | ||||||||||
from homeassistant.components.tuya import ManagerCompat | ||||||||||
timmo001 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
from homeassistant.const import Platform | ||||||||||
from homeassistant.components.tuya.climate import ( | ||||||||||
TuyaClimateEntity, | ||||||||||
TuyaClimateEntityDescription, | ||||||||||
) | ||||||||||
from homeassistant.components.tuya.const import DPCode | ||||||||||
from homeassistant.const import Platform, UnitOfTemperature | ||||||||||
from homeassistant.core import HomeAssistant | ||||||||||
from homeassistant.exceptions import HomeAssistantError | ||||||||||
from homeassistant.helpers import entity_registry as er | ||||||||||
|
||||||||||
from . import DEVICE_MOCKS, initialize_entry | ||||||||||
|
||||||||||
from tests.common import MockConfigEntry, snapshot_platform | ||||||||||
|
||||||||||
|
||||||||||
class DummyDevice: | ||||||||||
"""Dummy device for testing.""" | ||||||||||
|
||||||||||
def __init__(self, function, status) -> None: | ||||||||||
"""Initialize the dummy device.""" | ||||||||||
self.function = function | ||||||||||
self.status = status | ||||||||||
self.id = "dummy" | ||||||||||
self.name = "Dummy" | ||||||||||
self.product_name = "Dummy" | ||||||||||
self.product_id = "dummy" | ||||||||||
self.status_range = {} | ||||||||||
self.online = True | ||||||||||
|
||||||||||
|
||||||||||
class DummyManager: | ||||||||||
"""Dummy manager for testing.""" | ||||||||||
|
||||||||||
def send_commands(self, device_id: str, commands: list) -> None: | ||||||||||
"""Send commands to the device.""" | ||||||||||
|
||||||||||
|
||||||||||
class DummyFunction: | ||||||||||
"""Dummy function for testing.""" | ||||||||||
|
||||||||||
def __init__(self, type_: str, values: str) -> None: | ||||||||||
"""Initialize the dummy function.""" | ||||||||||
self.type = type_ | ||||||||||
self.values = values | ||||||||||
|
||||||||||
|
||||||||||
def make_climate_entity(function, status): | ||||||||||
"""Make a dummy climate entity for testing.""" | ||||||||||
return TuyaClimateEntity( | ||||||||||
cast("CustomerDevice", DummyDevice(function, status)), | ||||||||||
cast("Manager", DummyManager()), | ||||||||||
TuyaClimateEntityDescription(key="kt", switch_only_hvac_mode=HVACMode.COOL), | ||||||||||
UnitOfTemperature.CELSIUS, | ||||||||||
) | ||||||||||
|
||||||||||
|
||||||||||
def test_fan_mode_windspeed() -> None: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please move the tests to the bottom of the file, after the setup tests. Also, please look at core/tests/components/tuya/test_cover.py Lines 60 to 63 in fe83847
|
||||||||||
"""Test fan mode with windspeed.""" | ||||||||||
entity = make_climate_entity( | ||||||||||
{"windspeed": DummyFunction("Enum", '{"range": ["1", "2"]}')}, | ||||||||||
{"windspeed": "2"}, | ||||||||||
) | ||||||||||
assert entity.fan_mode == "2" | ||||||||||
entity.set_fan_mode("1") | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
|
||||||||||
|
||||||||||
def test_fan_mode_fan_speed_enum() -> None: | ||||||||||
"""Test fan mode with fan speed enum.""" | ||||||||||
entity = make_climate_entity( | ||||||||||
{DPCode.FAN_SPEED_ENUM: DummyFunction("Enum", '{"range": ["1", "2"]}')}, | ||||||||||
{DPCode.FAN_SPEED_ENUM: "1"}, | ||||||||||
) | ||||||||||
assert entity.fan_mode == "1" | ||||||||||
entity.set_fan_mode("2") | ||||||||||
|
||||||||||
|
||||||||||
def test_fan_mode_no_valid_code() -> None: | ||||||||||
"""Test fan mode with no valid code.""" | ||||||||||
entity = make_climate_entity({}, {}) | ||||||||||
assert entity.fan_mode is None | ||||||||||
with pytest.raises(HomeAssistantError): | ||||||||||
entity.set_fan_mode("1") | ||||||||||
|
||||||||||
|
||||||||||
@pytest.mark.parametrize( | ||||||||||
"mock_device_code", | ||||||||||
[k for k, v in DEVICE_MOCKS.items() if Platform.CLIMATE in v], | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick]
set_fan_mode
raisesHomeAssistantError
while other setter methods useRuntimeError
; consider unifying the exception type for consistency.Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
50/50 on this one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to keep aligned with other platforms for now.
There is a probably a case for migrating all to HomeAssistantError, but I think having them all aligned is best for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually not possible to get to this state as
self._fan_dp_code
setsfan_mode
to None so aServiceNotSupported
exception is raised before this can occur. Would it be safe to remove this check?