test_device_config.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """Test the config parser"""
  2. import unittest
  3. from warnings import warn
  4. from custom_components.tuya_local.const import (
  5. CONF_TYPE_DEHUMIDIFIER,
  6. CONF_TYPE_EUROM_600_HEATER,
  7. CONF_TYPE_FAN,
  8. CONF_TYPE_GARDENPAC_HEATPUMP,
  9. CONF_TYPE_GECO_HEATER,
  10. CONF_TYPE_GPCV_HEATER,
  11. CONF_TYPE_GPPH_HEATER,
  12. CONF_TYPE_GSH_HEATER,
  13. CONF_TYPE_KOGAN_HEATER,
  14. CONF_TYPE_KOGAN_SWITCH,
  15. CONF_TYPE_PURLINE_M100_HEATER,
  16. CONF_TYPE_REMORA_HEATPUMP,
  17. CONF_TYPE_EANONS_HUMIDIFIER,
  18. CONF_TYPE_INKBIRD_THERMOSTAT,
  19. )
  20. from custom_components.tuya_local.helpers.device_config import (
  21. available_configs,
  22. config_for_legacy_use,
  23. possible_matches,
  24. TuyaDeviceConfig,
  25. )
  26. from .const import (
  27. DEHUMIDIFIER_PAYLOAD,
  28. EUROM_600_HEATER_PAYLOAD,
  29. FAN_PAYLOAD,
  30. GARDENPAC_HEATPUMP_PAYLOAD,
  31. GECO_HEATER_PAYLOAD,
  32. GPCV_HEATER_PAYLOAD,
  33. GPPH_HEATER_PAYLOAD,
  34. GSH_HEATER_PAYLOAD,
  35. KOGAN_HEATER_PAYLOAD,
  36. KOGAN_SOCKET_PAYLOAD,
  37. KOGAN_SOCKET_PAYLOAD2,
  38. PURLINE_M100_HEATER_PAYLOAD,
  39. REMORA_HEATPUMP_PAYLOAD,
  40. EANONS_HUMIDIFIER_PAYLOAD,
  41. INKBIRD_THERMOSTAT_PAYLOAD,
  42. )
  43. class TestDeviceConfig(unittest.TestCase):
  44. """Test the device config parser"""
  45. def test_can_find_config_files(self):
  46. """Test that the config files can be found by the parser."""
  47. found = False
  48. for cfg in available_configs():
  49. found = True
  50. break
  51. self.assertTrue(found)
  52. def test_config_files_parse(self):
  53. for cfg in available_configs():
  54. parsed = TuyaDeviceConfig(cfg)
  55. self.assertIsNotNone(parsed.name)
  56. def test_config_files_have_legacy_link(self):
  57. """
  58. Initially, we require a link between the new style config, and the old
  59. classes so we can transition over to the new config. When the
  60. transition is complete, we will drop the requirement, as new devices
  61. will only be added as config files.
  62. """
  63. for cfg in available_configs():
  64. parsed = TuyaDeviceConfig(cfg)
  65. self.assertIsNotNone(parsed.legacy_type)
  66. self.assertIsNotNone(parsed.primary_entity)
  67. def _test_detect(self, payload, legacy_type, legacy_class):
  68. """Test that payload is detected as the correct type and class."""
  69. matched = False
  70. false_matches = []
  71. quality = 0
  72. for cfg in possible_matches(payload):
  73. self.assertTrue(cfg.matches(payload))
  74. if cfg.legacy_type == legacy_type:
  75. self.assertFalse(matched)
  76. matched = True
  77. quality = cfg.match_quality(payload)
  78. if legacy_class is not None:
  79. cfg_class = cfg.primary_entity.legacy_class
  80. if cfg_class is None:
  81. for e in cfg.secondary_entities():
  82. cfg_class = e.legacy_class
  83. if cfg_class is not None:
  84. break
  85. self.assertEqual(
  86. cfg_class.__name__,
  87. legacy_class,
  88. )
  89. else:
  90. false_matches.append(cfg)
  91. self.assertTrue(matched)
  92. if quality < 100:
  93. warn(f"{legacy_type} detected with imperfect quality {quality}%")
  94. best_q = 0
  95. for cfg in false_matches:
  96. q = cfg.match_quality(payload)
  97. if q > best_q:
  98. best_q = q
  99. warn(
  100. f"{legacy_type} also detectable as {cfg.legacy_type} with quality {q}%"
  101. )
  102. self.assertGreater(quality, best_q)
  103. # Ensure the same correct config is returned when looked up by type
  104. cfg = config_for_legacy_use(legacy_type)
  105. if legacy_class is not None:
  106. cfg_class = cfg.primary_entity.legacy_class
  107. if cfg_class is None:
  108. for e in cfg.secondary_entities():
  109. cfg_class = e.legacy_class
  110. if cfg_class is not None:
  111. break
  112. self.assertEqual(
  113. cfg_class.__name__,
  114. legacy_class,
  115. )
  116. def test_gpph_heater_detection(self):
  117. """Test that GPPH heater can be detected from its sample payload."""
  118. self._test_detect(GPPH_HEATER_PAYLOAD, CONF_TYPE_GPPH_HEATER, "GoldairHeater")
  119. def test_gpcv_heater_detection(self):
  120. """Test that GPCV heater can be detected from its sample payload."""
  121. self._test_detect(
  122. GPCV_HEATER_PAYLOAD,
  123. CONF_TYPE_GPCV_HEATER,
  124. None,
  125. )
  126. def test_eurom_heater_detection(self):
  127. """Test that Eurom heater can be detected from its sample payload."""
  128. self._test_detect(
  129. EUROM_600_HEATER_PAYLOAD,
  130. CONF_TYPE_EUROM_600_HEATER,
  131. None,
  132. )
  133. def test_geco_heater_detection(self):
  134. """Test that GECO heater can be detected from its sample payload."""
  135. self._test_detect(
  136. GECO_HEATER_PAYLOAD,
  137. CONF_TYPE_GECO_HEATER,
  138. None,
  139. )
  140. def test_kogan_heater_detection(self):
  141. """Test that Kogan heater can be detected from its sample payload."""
  142. self._test_detect(
  143. KOGAN_HEATER_PAYLOAD,
  144. CONF_TYPE_KOGAN_HEATER,
  145. None,
  146. )
  147. def test_goldair_dehumidifier_detection(self):
  148. """Test that Goldair dehumidifier can be detected from its sample payload."""
  149. self._test_detect(
  150. DEHUMIDIFIER_PAYLOAD,
  151. CONF_TYPE_DEHUMIDIFIER,
  152. "GoldairDehumidifier",
  153. )
  154. def test_goldair_fan_detection(self):
  155. """Test that Goldair fan can be detected from its sample payload."""
  156. self._test_detect(FAN_PAYLOAD, CONF_TYPE_FAN, "GoldairFan")
  157. def test_kogan_socket_detection(self):
  158. """Test that 1st gen Kogan Socket can be detected from its sample payload."""
  159. self._test_detect(
  160. KOGAN_SOCKET_PAYLOAD,
  161. CONF_TYPE_KOGAN_SWITCH,
  162. None,
  163. )
  164. def test_kogan_socket2_detection(self):
  165. """Test that 2nd gen Kogan Socket can be detected from its sample payload."""
  166. self._test_detect(
  167. KOGAN_SOCKET_PAYLOAD2,
  168. CONF_TYPE_KOGAN_SWITCH,
  169. None,
  170. )
  171. def test_gsh_heater_detection(self):
  172. """Test that GSH heater can be detected from its sample payload."""
  173. self._test_detect(
  174. GSH_HEATER_PAYLOAD,
  175. CONF_TYPE_GSH_HEATER,
  176. None,
  177. )
  178. def test_gardenpac_heatpump_detection(self):
  179. """Test that GardenPac heatpump can be detected from its sample payload."""
  180. self._test_detect(
  181. GARDENPAC_HEATPUMP_PAYLOAD,
  182. CONF_TYPE_GARDENPAC_HEATPUMP,
  183. None,
  184. )
  185. def test_purline_heater_detection(self):
  186. """Test that Purline heater can be detected from its sample payload."""
  187. self._test_detect(
  188. PURLINE_M100_HEATER_PAYLOAD,
  189. CONF_TYPE_PURLINE_M100_HEATER,
  190. None,
  191. )
  192. # Non-legacy devices start here.
  193. def test_remora_heatpump_detection(self):
  194. """Test that Remora heatpump can be detected from its sample payload."""
  195. self._test_detect(REMORA_HEATPUMP_PAYLOAD, CONF_TYPE_REMORA_HEATPUMP, None)
  196. def test_eanons_humidifier(self):
  197. """Test that Eanons humidifier can be detected from its sample payload."""
  198. self._test_detect(EANONS_HUMIDIFIER_PAYLOAD, CONF_TYPE_EANONS_HUMIDIFIER, None)
  199. def test_inkbird_thermostat(self):
  200. """Test that Inkbird thermostat can be detected from its sample payload."""
  201. self._test_detect(
  202. INKBIRD_THERMOSTAT_PAYLOAD, CONF_TYPE_INKBIRD_THERMOSTAT, None
  203. )