|
2 | 2 |
|
3 | 3 | import asyncio
|
4 | 4 | import datetime
|
5 |
| -import itertools |
6 | 5 | from unittest import mock
|
7 | 6 |
|
8 | 7 | import pytest
|
9 |
| -import zigpy |
10 | 8 | from zigpy.profiles import zha
|
11 | 9 | from zigpy.quirks import CustomDevice, get_device
|
12 | 10 | import zigpy.types as t
|
13 | 11 | from zigpy.zcl import foundation
|
14 |
| -from zigpy.zcl.clusters import CLUSTERS_BY_ID |
15 |
| -from zigpy.zcl.clusters.general import ( |
16 |
| - Basic, |
17 |
| - GreenPowerProxy, |
18 |
| - Groups, |
19 |
| - Identify, |
20 |
| - OnOff, |
21 |
| - Ota, |
22 |
| - PowerConfiguration, |
23 |
| -) |
24 |
| -from zigpy.zcl.clusters.lightlink import LightLink |
| 12 | +from zigpy.zcl.clusters.general import PowerConfiguration |
25 | 13 |
|
26 | 14 | import zhaquirks
|
27 | 15 | from zhaquirks.const import (
|
|
33 | 21 | ON,
|
34 | 22 | OUTPUT_CLUSTERS,
|
35 | 23 | PROFILE_ID,
|
36 |
| - SKIP_CONFIGURATION, |
37 | 24 | ZONE_STATUS_CHANGE_COMMAND,
|
38 | 25 | )
|
39 |
| -from zhaquirks.tuya import ( |
40 |
| - TUYA_QUERY_DATA, |
41 |
| - Data, |
42 |
| - EnchantedDevice, |
43 |
| - TuyaEnchantableCluster, |
44 |
| - TuyaManufClusterAttributes, |
45 |
| - TuyaNewManufCluster, |
46 |
| - TuyaZBOnOffAttributeCluster, |
47 |
| -) |
| 26 | +from zhaquirks.tuya import Data, TuyaManufClusterAttributes, TuyaNewManufCluster |
48 | 27 | import zhaquirks.tuya.sm0202_motion
|
49 | 28 | import zhaquirks.tuya.ts011f_plug
|
50 | 29 | import zhaquirks.tuya.ts0041
|
@@ -1610,188 +1589,3 @@ async def test_power_config_no_bind(zigpy_device_from_quirk, quirk):
|
1610 | 1589 |
|
1611 | 1590 | assert len(request_mock.mock_calls) == 0
|
1612 | 1591 | assert len(bind_mock.mock_calls) == 0
|
1613 |
| - |
1614 |
| - |
1615 |
| -class TuyaTestSpellDevice(EnchantedDevice): |
1616 |
| - """Tuya test spell device.""" |
1617 |
| - |
1618 |
| - tuya_spell_data_query = True # enable additional data query spell |
1619 |
| - |
1620 |
| - signature = { |
1621 |
| - MODELS_INFO: [("UjqjHq6ZErY23tgs", "zo9WD7q5dbvDj96y")], |
1622 |
| - ENDPOINTS: { |
1623 |
| - 1: { |
1624 |
| - PROFILE_ID: zha.PROFILE_ID, |
1625 |
| - DEVICE_TYPE: zha.DeviceType.SMART_PLUG, |
1626 |
| - INPUT_CLUSTERS: [ |
1627 |
| - Basic.cluster_id, |
1628 |
| - OnOff.cluster_id, |
1629 |
| - TuyaNewManufCluster.cluster_id, |
1630 |
| - ], |
1631 |
| - OUTPUT_CLUSTERS: [], |
1632 |
| - } |
1633 |
| - }, |
1634 |
| - } |
1635 |
| - |
1636 |
| - replacement = { |
1637 |
| - ENDPOINTS: { |
1638 |
| - 1: { |
1639 |
| - PROFILE_ID: zha.PROFILE_ID, |
1640 |
| - DEVICE_TYPE: zha.DeviceType.SMART_PLUG, |
1641 |
| - INPUT_CLUSTERS: [ |
1642 |
| - Basic.cluster_id, |
1643 |
| - TuyaZBOnOffAttributeCluster, |
1644 |
| - TuyaNewManufCluster, |
1645 |
| - ], |
1646 |
| - OUTPUT_CLUSTERS: [], |
1647 |
| - } |
1648 |
| - } |
1649 |
| - } |
1650 |
| - |
1651 |
| - |
1652 |
| -ENCHANTED_QUIRKS = [TuyaTestSpellDevice] |
1653 |
| -for manufacturer in zigpy.quirks._DEVICE_REGISTRY._registry.values(): |
1654 |
| - for model_quirk_list in manufacturer.values(): |
1655 |
| - for quirk_entry in model_quirk_list: |
1656 |
| - if quirk_entry in ENCHANTED_QUIRKS: |
1657 |
| - continue |
1658 |
| - if issubclass(quirk_entry, EnchantedDevice): |
1659 |
| - ENCHANTED_QUIRKS.append(quirk_entry) |
1660 |
| - |
1661 |
| -del quirk_entry, model_quirk_list, manufacturer |
1662 |
| - |
1663 |
| - |
1664 |
| -@mock.patch("zigpy.zcl.Cluster.bind", mock.AsyncMock()) |
1665 |
| -async def test_tuya_spell(zigpy_device_from_quirk): |
1666 |
| - """Test that enchanted Tuya devices have their spell applied when binding bindable cluster.""" |
1667 |
| - non_bindable_cluster_ids = [ |
1668 |
| - Basic.cluster_id, |
1669 |
| - Identify.cluster_id, |
1670 |
| - Groups.cluster_id, |
1671 |
| - Ota.cluster_id, |
1672 |
| - GreenPowerProxy.cluster_id, |
1673 |
| - LightLink.cluster_id, |
1674 |
| - ] |
1675 |
| - |
1676 |
| - request_patch = mock.patch("zigpy.zcl.Cluster.request", mock.AsyncMock()) |
1677 |
| - with request_patch as request_mock: |
1678 |
| - request_mock.return_value = (foundation.Status.SUCCESS, "done") |
1679 |
| - |
1680 |
| - for quirk in ENCHANTED_QUIRKS: |
1681 |
| - device = zigpy_device_from_quirk(quirk) |
1682 |
| - assert isinstance(device, EnchantedDevice) |
1683 |
| - |
1684 |
| - # fail if SKIP_CONFIGURATION is set, as that will cause ZHA to not call bind() |
1685 |
| - if getattr(device, SKIP_CONFIGURATION, False): |
1686 |
| - pytest.fail( |
1687 |
| - f"Enchanted quirk {quirk} has SKIP_CONFIGURATION set. " |
1688 |
| - f"This is not allowed for enchanted devices." |
1689 |
| - ) |
1690 |
| - |
1691 |
| - for cluster in itertools.chain( |
1692 |
| - device.endpoints[1].in_clusters.values(), |
1693 |
| - device.endpoints[1].out_clusters.values(), |
1694 |
| - ): |
1695 |
| - # emulate ZHA calling bind() on most default clusters with an unchanged ep_attribute |
1696 |
| - if ( |
1697 |
| - not isinstance(cluster, int) |
1698 |
| - and cluster.cluster_id not in non_bindable_cluster_ids |
1699 |
| - and cluster.cluster_id in CLUSTERS_BY_ID |
1700 |
| - and CLUSTERS_BY_ID[cluster.cluster_id].ep_attribute |
1701 |
| - == cluster.ep_attribute |
1702 |
| - ): |
1703 |
| - await cluster.bind() |
1704 |
| - |
1705 |
| - # the number of Tuya spells that are allowed to be cast, so the sum of enabled Tuya spells |
1706 |
| - enabled_tuya_spells_num = ( |
1707 |
| - device.tuya_spell_read_attributes + device.tuya_spell_data_query |
1708 |
| - ) |
1709 |
| - |
1710 |
| - # skip if no Tuya spells are enabled, |
1711 |
| - # this case is already handled in the test_tuya_spell_devices_valid() test |
1712 |
| - if enabled_tuya_spells_num == 0: |
1713 |
| - continue |
1714 |
| - |
1715 |
| - # check that exactly a Tuya spell was cast |
1716 |
| - if len(request_mock.mock_calls) == 0: |
1717 |
| - pytest.fail( |
1718 |
| - f"Enchanted quirk {quirk} did not cast a Tuya spell. " |
1719 |
| - f"One bindable cluster subclassing `TuyaEnchantableCluster` on endpoint 1 needs to be implemented. " |
1720 |
| - f"Also check that enchanted bindable clusters do not modify their `ep_attribute`, " |
1721 |
| - f"as ZHA will not call bind() in that case." |
1722 |
| - ) |
1723 |
| - # check that no more than one call was made for each enabled spell |
1724 |
| - elif len(request_mock.mock_calls) > enabled_tuya_spells_num: |
1725 |
| - pytest.fail( |
1726 |
| - f"Enchanted quirk {quirk} cast more than one Tuya spell. " |
1727 |
| - f"Make sure to only implement one cluster subclassing `TuyaEnchantableCluster` on endpoint 1." |
1728 |
| - ) |
1729 |
| - |
1730 |
| - # used to check list of mock calls below |
1731 |
| - messages = 0 |
1732 |
| - |
1733 |
| - # check 'attribute read spell' was cast correctly (if enabled) |
1734 |
| - if device.tuya_spell_read_attributes: |
1735 |
| - assert ( |
1736 |
| - request_mock.mock_calls[messages][1][1] |
1737 |
| - == foundation.GeneralCommand.Read_Attributes |
1738 |
| - ) |
1739 |
| - assert request_mock.mock_calls[messages][1][3] == [4, 0, 1, 5, 7, 65534] |
1740 |
| - messages += 1 |
1741 |
| - |
1742 |
| - # check 'query data spell' was cast correctly (if enabled) |
1743 |
| - if device.tuya_spell_data_query: |
1744 |
| - assert not request_mock.mock_calls[messages][1][0] |
1745 |
| - assert request_mock.mock_calls[messages][1][1] == TUYA_QUERY_DATA |
1746 |
| - messages += 1 |
1747 |
| - |
1748 |
| - request_mock.reset_mock() |
1749 |
| - |
1750 |
| - |
1751 |
| -def test_tuya_spell_devices_valid(): |
1752 |
| - """Test that all enchanted Tuya devices implement at least one enchanted cluster.""" |
1753 |
| - |
1754 |
| - for quirk in ENCHANTED_QUIRKS: |
1755 |
| - # check that at least one Tuya spell is enabled for an EnchantedDevice |
1756 |
| - if not quirk.tuya_spell_read_attributes and not quirk.tuya_spell_data_query: |
1757 |
| - pytest.fail( |
1758 |
| - f"Enchanted quirk {quirk} does not have any Tuya spells enabled. " |
1759 |
| - f"Enable at least one Tuya spell by setting `TUYA_SPELL_READ_ATTRIBUTES` or `TUYA_SPELL_DATA_QUERY` " |
1760 |
| - f"or inherit CustomDevice rather than EnchantedDevice." |
1761 |
| - ) |
1762 |
| - |
1763 |
| - enchanted_clusters = 0 # number of clusters subclassing TuyaEnchantableCluster |
1764 |
| - tuya_cluster_exists = False # cluster subclassing TuyaNewManufCluster existing |
1765 |
| - |
1766 |
| - # iterate over all clusters in the replacement |
1767 |
| - for endpoint_id, endpoint in quirk.replacement[ENDPOINTS].items(): |
1768 |
| - if endpoint_id != 1: # spell is only activated on endpoint 1 for now |
1769 |
| - continue |
1770 |
| - for cluster in endpoint[INPUT_CLUSTERS] + endpoint[OUTPUT_CLUSTERS]: |
1771 |
| - if not isinstance(cluster, int): |
1772 |
| - # count all clusters which would apply the spell on bind() |
1773 |
| - if issubclass(cluster, TuyaEnchantableCluster): |
1774 |
| - enchanted_clusters += 1 |
1775 |
| - # check if there's a valid Tuya cluster where the id wasn't modified |
1776 |
| - if ( |
1777 |
| - issubclass(cluster, TuyaNewManufCluster) |
1778 |
| - and cluster.cluster_id == TuyaNewManufCluster.cluster_id |
1779 |
| - ): |
1780 |
| - tuya_cluster_exists = True |
1781 |
| - |
1782 |
| - # an EnchantedDevice must have exactly one enchanted cluster on endpoint 1 |
1783 |
| - if enchanted_clusters == 0: |
1784 |
| - pytest.fail( |
1785 |
| - f"{quirk} does not have a cluster subclassing `TuyaEnchantableCluster` on endpoint 1 " |
1786 |
| - f"as required by the Tuya spell." |
1787 |
| - ) |
1788 |
| - elif enchanted_clusters > 1: |
1789 |
| - pytest.fail( |
1790 |
| - f"{quirk} has more than one cluster subclassing `TuyaEnchantableCluster` on endpoint 1" |
1791 |
| - ) |
1792 |
| - |
1793 |
| - # an EnchantedDevice with the data query spell must also have a cluster subclassing TuyaNewManufCluster |
1794 |
| - if quirk.tuya_spell_data_query and not tuya_cluster_exists: |
1795 |
| - pytest.fail( |
1796 |
| - f"{quirk} set Tuya data query spell but has no cluster subclassing `TuyaNewManufCluster` on endpoint 1" |
1797 |
| - ) |
0 commit comments