device_config.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. """
  2. Config parser for Tuya Local devices.
  3. """
  4. import logging
  5. from os import walk
  6. from os.path import join, dirname
  7. from fnmatch import fnmatch
  8. from homeassistant.util.yaml import load_yaml
  9. import custom_components.tuya_local.devices
  10. _CONFIG_DIR = dirname(custom_components.tuya_local.devices.__file__)
  11. _LOGGER = logging.getLogger("tuya_local")
  12. def _typematch(type, value):
  13. if isinstance(value, type):
  14. return True
  15. # Allow values embedded in strings if they can be converted
  16. # But not for bool, as everything can be converted to bool
  17. elif isinstance(value, str) and type is not bool:
  18. try:
  19. type(value)
  20. return True
  21. except ValueError:
  22. return False
  23. return False
  24. class TuyaDeviceConfig:
  25. """Representation of a device config for Tuya Local devices."""
  26. def __init__(self, fname):
  27. """Initialize the device config.
  28. Args:
  29. fname (string): The filename of the yaml config to load."""
  30. self.__fname = fname
  31. filename = join(_CONFIG_DIR, fname)
  32. self.__config = load_yaml(filename)
  33. _LOGGER.debug("Loaded device config %s", fname)
  34. @property
  35. def name(self):
  36. """Return the friendly name for this device."""
  37. return self.__config["name"]
  38. @property
  39. def primary_entity(self):
  40. """Return the primary type of entity for this device."""
  41. return TuyaEntityConfig(self, self.__config["primary_entity"])
  42. def secondary_entities(self):
  43. """Iterate through entites for any secondary entites supported."""
  44. if "secondary_entities" in self.__config.keys():
  45. for conf in self.__config["secondary_entities"]:
  46. yield TuyaEntityConfig(self, conf)
  47. def matches(self, dps):
  48. """Determine if this device matches the provided dps map."""
  49. for d in self.primary_entity.dps():
  50. if d.id not in dps.keys() or not _typematch(d.type, dps[d.id]):
  51. return False
  52. for dev in self.secondary_entities():
  53. for d in dev.dps():
  54. if d.id not in dps.keys() or not _typematch(d.type, dps[d.id]):
  55. return False
  56. _LOGGER.debug("Matched config for %s", self.name)
  57. return True
  58. class TuyaEntityConfig:
  59. """Representation of an entity config for a supported entity."""
  60. def __init__(self, device, config):
  61. self.__device = device
  62. self.__config = config
  63. @property
  64. def name(self):
  65. """The friendly name for this entity."""
  66. if "name" in self.__config:
  67. return self.__config["name"]
  68. else:
  69. return self.__device.name
  70. @property
  71. def legacy_device(self):
  72. """Return the legacy device corresponding to this config."""
  73. if "legacy_class" in self.__config:
  74. return self.__config["legacy_class"]
  75. else:
  76. return None
  77. @property
  78. def entity(self):
  79. """The entity type of this entity."""
  80. return self.__config["entity"]
  81. def dps(self):
  82. """Iterate through the list of dps for this entity."""
  83. for d in self.__config["dps"]:
  84. yield TuyaDpsConfig(self, d)
  85. class TuyaDpsConfig:
  86. """Representation of a dps config."""
  87. def __init__(self, entity, config):
  88. self.__entity = entity
  89. self.__config = config
  90. @property
  91. def id(self):
  92. return str(self.__config["id"])
  93. @property
  94. def type(self):
  95. t = self.__config["type"]
  96. types = {
  97. "boolean": bool,
  98. "integer": int,
  99. "string": str,
  100. "float": float,
  101. "bitfield": int,
  102. }
  103. return types.get(t, None)
  104. @property
  105. def name(self):
  106. return self.__config["name"]
  107. @property
  108. def isreadonly(self):
  109. return "readonly" in self.__config.keys() and self.__config["readonly"] is True
  110. def map_from_dps(self, value):
  111. result = value
  112. if "mapping" in self.__config.keys():
  113. for map in self.__config["mapping"]:
  114. if map["dps_val"] == value and "value" in map:
  115. result = map["value"]
  116. _LOGGER.debug(
  117. "%s: Mapped dps %d value from %s to %s",
  118. self.__entity.__device.name,
  119. self.id,
  120. value,
  121. result,
  122. )
  123. return result
  124. def map_to_dps(self, value):
  125. result = value
  126. if "mapping" in self.__config.keys():
  127. for map in self.__config["mapping"]:
  128. if "value" in map and map["value"] == value:
  129. result = map["dps_val"]
  130. _LOGGER.debug(
  131. "%s: Mapped dps %d to %s from %s",
  132. self.__entity.__device.name,
  133. self.id,
  134. result,
  135. value,
  136. )
  137. return result
  138. def available_configs():
  139. """List the available config files."""
  140. for (path, dirs, files) in walk(_CONFIG_DIR):
  141. for basename in sorted(files):
  142. if fnmatch(basename, "*.yaml"):
  143. yield basename