@homebridge-plugins/homebridge-tuya 2.2.1 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +161 -144
  3. package/config.schema.json +976 -11
  4. package/dist/{core → cloud/api}/TuyaOpenAPI.js +6 -7
  5. package/dist/cloud/api/TuyaOpenAPI.js.map +1 -0
  6. package/dist/{core → cloud/api}/TuyaOpenMQ.js +13 -11
  7. package/dist/cloud/api/TuyaOpenMQ.js.map +1 -0
  8. package/dist/cloud/device/TuyaCustomDeviceManager.js.map +1 -0
  9. package/dist/{device → cloud/device}/TuyaDevice.js +14 -1
  10. package/dist/cloud/device/TuyaDevice.js.map +1 -0
  11. package/dist/{device → cloud/device}/TuyaDeviceManager.js +8 -3
  12. package/dist/cloud/device/TuyaDeviceManager.js.map +1 -0
  13. package/dist/cloud/device/TuyaHomeDeviceManager.js.map +1 -0
  14. package/dist/config.js +1 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/local/CloudLocalReconciliation.js +180 -0
  17. package/dist/local/CloudLocalReconciliation.js.map +1 -0
  18. package/dist/local/DynamicChildDiscovery.js +154 -0
  19. package/dist/local/DynamicChildDiscovery.js.map +1 -0
  20. package/dist/local/LocalDevice.js +419 -0
  21. package/dist/local/LocalDevice.js.map +1 -0
  22. package/dist/local/LocalDeviceManager.js +833 -0
  23. package/dist/local/LocalDeviceManager.js.map +1 -0
  24. package/dist/local/TuyaDiscovery.js +248 -0
  25. package/dist/local/TuyaDiscovery.js.map +1 -0
  26. package/dist/local/ZigbeeEventDetection.js +213 -0
  27. package/dist/local/ZigbeeEventDetection.js.map +1 -0
  28. package/dist/local/ZigbeeGatewayDetection.js +109 -0
  29. package/dist/local/ZigbeeGatewayDetection.js.map +1 -0
  30. package/dist/local/config.js +6 -0
  31. package/dist/local/config.js.map +1 -0
  32. package/dist/local/protocol/ChildPayloadUtility.js +94 -0
  33. package/dist/local/protocol/ChildPayloadUtility.js.map +1 -0
  34. package/dist/local/protocol/Protocol.js +3 -0
  35. package/dist/local/protocol/Protocol.js.map +1 -0
  36. package/dist/local/protocol/ProtocolFactory.js +27 -0
  37. package/dist/local/protocol/ProtocolFactory.js.map +1 -0
  38. package/dist/local/protocol/ProtocolUtilities.js +329 -0
  39. package/dist/local/protocol/ProtocolUtilities.js.map +1 -0
  40. package/dist/local/protocol/ProtocolV31V32.js +60 -0
  41. package/dist/local/protocol/ProtocolV31V32.js.map +1 -0
  42. package/dist/local/protocol/ProtocolV33.js +52 -0
  43. package/dist/local/protocol/ProtocolV33.js.map +1 -0
  44. package/dist/local/protocol/ProtocolV34.js +104 -0
  45. package/dist/local/protocol/ProtocolV34.js.map +1 -0
  46. package/dist/local/protocol/ProtocolV35.js +77 -0
  47. package/dist/local/protocol/ProtocolV35.js.map +1 -0
  48. package/dist/platform.js +149 -69
  49. package/dist/platform.js.map +1 -1
  50. package/dist/shared/AccessoryFactory.js +276 -0
  51. package/dist/shared/AccessoryFactory.js.map +1 -0
  52. package/dist/{accessory → shared/accessories}/AccessoryFactory.js +2 -2
  53. package/dist/shared/accessories/AccessoryFactory.js.map +1 -0
  54. package/dist/shared/accessories/AirConditionerAccessory.js.map +1 -0
  55. package/dist/{accessory → shared/accessories}/AirPurifierAccessory.js +1 -1
  56. package/dist/shared/accessories/AirPurifierAccessory.js.map +1 -0
  57. package/dist/shared/accessories/AirQualitySensorAccessory.js.map +1 -0
  58. package/dist/{accessory → shared/accessories}/BaseAccessory.js +99 -30
  59. package/dist/shared/accessories/BaseAccessory.js.map +1 -0
  60. package/dist/shared/accessories/BlindsAccessory.js +199 -0
  61. package/dist/shared/accessories/BlindsAccessory.js.map +1 -0
  62. package/dist/{accessory → shared/accessories}/CameraAccessory.js +7 -3
  63. package/dist/shared/accessories/CameraAccessory.js.map +1 -0
  64. package/dist/shared/accessories/CarbonDioxideSensorAccessory.js.map +1 -0
  65. package/dist/shared/accessories/CarbonMonoxideSensorAccessory.js.map +1 -0
  66. package/dist/shared/accessories/ContactSensorAccessory.js.map +1 -0
  67. package/dist/shared/accessories/DehumidifierAccessory.js.map +1 -0
  68. package/dist/shared/accessories/DiffuserAccessory.js.map +1 -0
  69. package/dist/shared/accessories/DimmerAccessory.js.map +1 -0
  70. package/dist/{accessory → shared/accessories}/DoorbellAccessory.js +3 -1
  71. package/dist/shared/accessories/DoorbellAccessory.js.map +1 -0
  72. package/dist/{accessory → shared/accessories}/ExtractionHoodAccessory.js +4 -2
  73. package/dist/shared/accessories/ExtractionHoodAccessory.js.map +1 -0
  74. package/dist/{accessory → shared/accessories}/FanAccessory.js +10 -4
  75. package/dist/shared/accessories/FanAccessory.js.map +1 -0
  76. package/dist/shared/accessories/GarageDoorAccessory.js.map +1 -0
  77. package/dist/shared/accessories/HeaterAccessory.js.map +1 -0
  78. package/dist/shared/accessories/HeaterAccessory_old.js.map +1 -0
  79. package/dist/shared/accessories/HumanPresenceSensorAccessory.js.map +1 -0
  80. package/dist/{accessory → shared/accessories}/HumidifierAccessory.js +3 -1
  81. package/dist/shared/accessories/HumidifierAccessory.js.map +1 -0
  82. package/dist/{accessory → shared/accessories}/IRAirConditionerAccessory.js +2 -2
  83. package/dist/shared/accessories/IRAirConditionerAccessory.js.map +1 -0
  84. package/dist/shared/accessories/IRControlHubAccessory.js.map +1 -0
  85. package/dist/shared/accessories/IRControlHubSubAccessory.js.map +1 -0
  86. package/dist/shared/accessories/IRGenericAccessory.js.map +1 -0
  87. package/dist/shared/accessories/LeakSensorAccessory.js.map +1 -0
  88. package/dist/shared/accessories/LightAccessory.js.map +1 -0
  89. package/dist/shared/accessories/LightSensorAccessory.js.map +1 -0
  90. package/dist/shared/accessories/LocationWeatherAccessory.js.map +1 -0
  91. package/dist/shared/accessories/LockAccessory.js.map +1 -0
  92. package/dist/shared/accessories/MotionSensorAccessory.js.map +1 -0
  93. package/dist/shared/accessories/OutletAccessory.js.map +1 -0
  94. package/dist/shared/accessories/PetFeederAccessory.js.map +1 -0
  95. package/dist/shared/accessories/SceneAccessory.js.map +1 -0
  96. package/dist/{accessory → shared/accessories}/SceneSwitchAccessory.js +1 -1
  97. package/dist/shared/accessories/SceneSwitchAccessory.js.map +1 -0
  98. package/dist/shared/accessories/SecuritySystemAccessory.js.map +1 -0
  99. package/dist/shared/accessories/SmokeSensorAccessory.js.map +1 -0
  100. package/dist/shared/accessories/SwitchAccessory.js +148 -0
  101. package/dist/shared/accessories/SwitchAccessory.js.map +1 -0
  102. package/dist/shared/accessories/TemperatureHumiditySensorAccessory.js.map +1 -0
  103. package/dist/shared/accessories/ThermostatAccessory.js.map +1 -0
  104. package/dist/shared/accessories/TowerRackAccessory.js.map +1 -0
  105. package/dist/{accessory → shared/accessories}/ValveAccessory.js +1 -1
  106. package/dist/shared/accessories/ValveAccessory.js.map +1 -0
  107. package/dist/{accessory → shared/accessories}/VibrationSensorAccessory.js +7 -3
  108. package/dist/shared/accessories/VibrationSensorAccessory.js.map +1 -0
  109. package/dist/shared/accessories/WeatherStationAccessory.js.map +1 -0
  110. package/dist/shared/accessories/WetBulbGlobeTemperatureAccessory.js.map +1 -0
  111. package/dist/shared/accessories/WhiteNoiseLightAccessory.js.map +1 -0
  112. package/dist/shared/accessories/WindowAccessory.js.map +1 -0
  113. package/dist/shared/accessories/WindowCoveringAccessory.js.map +1 -0
  114. package/dist/shared/accessories/WirelessSwitchAccessory.js.map +1 -0
  115. package/dist/shared/accessories/characteristic/Active.js.map +1 -0
  116. package/dist/{accessory → shared/accessories}/characteristic/AirQuality.js +1 -1
  117. package/dist/shared/accessories/characteristic/AirQuality.js.map +1 -0
  118. package/dist/shared/accessories/characteristic/CurrentRelativeHumidity.js.map +1 -0
  119. package/dist/shared/accessories/characteristic/CurrentTemperature.js.map +1 -0
  120. package/dist/shared/accessories/characteristic/CurrentWeather.js.map +1 -0
  121. package/dist/shared/accessories/characteristic/CurrentWeatherByOpenMeteo.js.map +1 -0
  122. package/dist/shared/accessories/characteristic/CurrentWetBulbGlobeTemperature.js.map +1 -0
  123. package/dist/shared/accessories/characteristic/EnergyUsage.js.map +1 -0
  124. package/dist/shared/accessories/characteristic/Light.js.map +1 -0
  125. package/dist/shared/accessories/characteristic/LightSensor.js.map +1 -0
  126. package/dist/shared/accessories/characteristic/LockPhysicalControls.js.map +1 -0
  127. package/dist/{accessory → shared/accessories}/characteristic/MotionDetected.js +1 -1
  128. package/dist/shared/accessories/characteristic/MotionDetected.js.map +1 -0
  129. package/dist/shared/accessories/characteristic/Name.js.map +1 -0
  130. package/dist/shared/accessories/characteristic/OccupancyDetected.js.map +1 -0
  131. package/dist/{accessory → shared/accessories}/characteristic/On.js +1 -1
  132. package/dist/shared/accessories/characteristic/On.js.map +1 -0
  133. package/dist/shared/accessories/characteristic/OutletInUse.js.map +1 -0
  134. package/dist/{accessory → shared/accessories}/characteristic/ProgrammableSwitchEvent.js +2 -2
  135. package/dist/shared/accessories/characteristic/ProgrammableSwitchEvent.js.map +1 -0
  136. package/dist/shared/accessories/characteristic/RelativeHumidityDehumidifierThreshold.js.map +1 -0
  137. package/dist/shared/accessories/characteristic/RotationSpeed.js.map +1 -0
  138. package/dist/shared/accessories/characteristic/SecuritySystemState.js.map +1 -0
  139. package/dist/shared/accessories/characteristic/SwingMode.js.map +1 -0
  140. package/dist/shared/accessories/characteristic/TargetTemperature.js.map +1 -0
  141. package/dist/shared/accessories/characteristic/TemperatureDisplayUnits.js.map +1 -0
  142. package/dist/shared/util/ConfigHash.js +79 -0
  143. package/dist/shared/util/ConfigHash.js.map +1 -0
  144. package/dist/shared/util/FfmpegStreamingProcess.js.map +1 -0
  145. package/dist/shared/util/InfraredTool.js.map +1 -0
  146. package/dist/shared/util/Logger.js.map +1 -0
  147. package/dist/shared/util/TuyaRecordingDelegate.js.map +1 -0
  148. package/dist/shared/util/TuyaStreamDelegate.js.map +1 -0
  149. package/dist/shared/util/color.js.map +1 -0
  150. package/dist/shared/util/util.js +135 -0
  151. package/dist/shared/util/util.js.map +1 -0
  152. package/eslint.config.mjs +52 -0
  153. package/jest.config.js +6 -0
  154. package/package.json +22 -27
  155. package/test/__mocks__/@homebridge-camera-utils.js +10 -0
  156. package/test/__mocks__/color-convert.js +24 -0
  157. package/test/__mocks__/kelvin-to-rgb.js +21 -0
  158. package/test/accessoryFactory.test.ts +522 -0
  159. package/test/config.test.ts +430 -0
  160. package/test/custom.test.ts +4 -4
  161. package/test/errorHandling.test.ts +570 -0
  162. package/test/errorHandlingCoverage.test.ts +410 -0
  163. package/test/home.test.ts +4 -4
  164. package/test/localDevice.test.ts +616 -0
  165. package/test/localDeviceManager.behavior.test.ts +185 -0
  166. package/test/localDeviceManager.test.ts +268 -0
  167. package/test/mqttMessages.test.ts +495 -0
  168. package/test/platform.test.ts +515 -0
  169. package/test/platform.validation.local.test.ts +250 -0
  170. package/test/protocol.test.ts +446 -0
  171. package/test/protocolUtilities.test.ts +351 -0
  172. package/test/sanitizeName.test.ts +2 -1
  173. package/test/tuyaCustomDeviceManager.test.ts +469 -0
  174. package/test/tuyaDevice.test.ts +425 -0
  175. package/test/tuyaDeviceManager.test.ts +813 -0
  176. package/test/tuyaDiscovery.test.ts +439 -0
  177. package/test/tuyaHomeDeviceManager.test.ts +540 -0
  178. package/test/tuyaOpenAPI.test.ts +404 -0
  179. package/test/tuyaOpenMQ.test.ts +444 -0
  180. package/test/util.test.ts +318 -0
  181. package/test/util.ts +4 -3
  182. package/test/zigbee/ChildPayloadUtility.test.ts +199 -0
  183. package/test/zigbee/CloudLocalReconciliation.test.ts +284 -0
  184. package/test/zigbee/DynamicChildDiscovery.test.ts +333 -0
  185. package/test/zigbee/LocalDevice.zigbee.test.ts +274 -0
  186. package/test/zigbee/LocalDeviceManager.cloud-discovery.test.ts +163 -0
  187. package/test/zigbee/LocalDeviceManager.zigbee.reconnect.test.ts +200 -0
  188. package/test/zigbee/LocalDeviceManager.zigbee.test.ts +344 -0
  189. package/test/zigbee/ZigbeeEventDetection.test.ts +418 -0
  190. package/test/zigbee/ZigbeeGatewayDetection.test.ts +222 -0
  191. package/tuya-local-configs/dev-1.hash +1 -0
  192. package/tuya-local-configs/device1.hash +1 -0
  193. package/wiki/Get-Local-Keys-for-Your-Devices.md +85 -0
  194. package/dist/accessory/AccessoryFactory.js.map +0 -1
  195. package/dist/accessory/AirConditionerAccessory.js.map +0 -1
  196. package/dist/accessory/AirPurifierAccessory.js.map +0 -1
  197. package/dist/accessory/AirQualitySensorAccessory.js.map +0 -1
  198. package/dist/accessory/BaseAccessory.js.map +0 -1
  199. package/dist/accessory/CameraAccessory.js.map +0 -1
  200. package/dist/accessory/CarbonDioxideSensorAccessory.js.map +0 -1
  201. package/dist/accessory/CarbonMonoxideSensorAccessory.js.map +0 -1
  202. package/dist/accessory/ContactSensorAccessory.js.map +0 -1
  203. package/dist/accessory/DehumidifierAccessory.js.map +0 -1
  204. package/dist/accessory/DiffuserAccessory.js.map +0 -1
  205. package/dist/accessory/DimmerAccessory.js.map +0 -1
  206. package/dist/accessory/DoorbellAccessory.js.map +0 -1
  207. package/dist/accessory/ExtractionHoodAccessory.js.map +0 -1
  208. package/dist/accessory/FanAccessory.js.map +0 -1
  209. package/dist/accessory/GarageDoorAccessory.js.map +0 -1
  210. package/dist/accessory/HeaterAccessory.js.map +0 -1
  211. package/dist/accessory/HeaterAccessory_old.js.map +0 -1
  212. package/dist/accessory/HumanPresenceSensorAccessory.js.map +0 -1
  213. package/dist/accessory/HumidifierAccessory.js.map +0 -1
  214. package/dist/accessory/IRAirConditionerAccessory.js.map +0 -1
  215. package/dist/accessory/IRControlHubAccessory.js.map +0 -1
  216. package/dist/accessory/IRControlHubSubAccessory.js.map +0 -1
  217. package/dist/accessory/IRGenericAccessory.js.map +0 -1
  218. package/dist/accessory/LeakSensorAccessory.js.map +0 -1
  219. package/dist/accessory/LightAccessory.js.map +0 -1
  220. package/dist/accessory/LightSensorAccessory.js.map +0 -1
  221. package/dist/accessory/LocationWeatherAccessory.js.map +0 -1
  222. package/dist/accessory/LockAccessory.js.map +0 -1
  223. package/dist/accessory/MotionSensorAccessory.js.map +0 -1
  224. package/dist/accessory/OutletAccessory.js.map +0 -1
  225. package/dist/accessory/PetFeederAccessory.js.map +0 -1
  226. package/dist/accessory/SceneAccessory.js.map +0 -1
  227. package/dist/accessory/SceneSwitchAccessory.js.map +0 -1
  228. package/dist/accessory/SecuritySystemAccessory.js.map +0 -1
  229. package/dist/accessory/SmokeSensorAccessory.js.map +0 -1
  230. package/dist/accessory/SwitchAccessory.js +0 -85
  231. package/dist/accessory/SwitchAccessory.js.map +0 -1
  232. package/dist/accessory/TemperatureHumiditySensorAccessory.js.map +0 -1
  233. package/dist/accessory/ThermostatAccessory.js.map +0 -1
  234. package/dist/accessory/TowerRackAccessory.js.map +0 -1
  235. package/dist/accessory/ValveAccessory.js.map +0 -1
  236. package/dist/accessory/VibrationSensorAccessory.js.map +0 -1
  237. package/dist/accessory/WeatherStationAccessory.js.map +0 -1
  238. package/dist/accessory/WetBulbGlobeTemperatureAccessory.js.map +0 -1
  239. package/dist/accessory/WhiteNoiseLightAccessory.js.map +0 -1
  240. package/dist/accessory/WindowAccessory.js.map +0 -1
  241. package/dist/accessory/WindowCoveringAccessory.js.map +0 -1
  242. package/dist/accessory/WirelessSwitchAccessory.js.map +0 -1
  243. package/dist/accessory/characteristic/Active.js.map +0 -1
  244. package/dist/accessory/characteristic/AirQuality.js.map +0 -1
  245. package/dist/accessory/characteristic/CurrentRelativeHumidity.js.map +0 -1
  246. package/dist/accessory/characteristic/CurrentTemperature.js.map +0 -1
  247. package/dist/accessory/characteristic/CurrentWeather.js.map +0 -1
  248. package/dist/accessory/characteristic/CurrentWeatherByOpenMeteo.js.map +0 -1
  249. package/dist/accessory/characteristic/CurrentWetBulbGlobeTemperature.js.map +0 -1
  250. package/dist/accessory/characteristic/EnergyUsage.js.map +0 -1
  251. package/dist/accessory/characteristic/Light.js.map +0 -1
  252. package/dist/accessory/characteristic/LightSensor.js.map +0 -1
  253. package/dist/accessory/characteristic/LockPhysicalControls.js.map +0 -1
  254. package/dist/accessory/characteristic/MotionDetected.js.map +0 -1
  255. package/dist/accessory/characteristic/Name.js.map +0 -1
  256. package/dist/accessory/characteristic/OccupancyDetected.js.map +0 -1
  257. package/dist/accessory/characteristic/On.js.map +0 -1
  258. package/dist/accessory/characteristic/OutletInUse.js.map +0 -1
  259. package/dist/accessory/characteristic/ProgrammableSwitchEvent.js.map +0 -1
  260. package/dist/accessory/characteristic/RelativeHumidityDehumidifierThreshold.js.map +0 -1
  261. package/dist/accessory/characteristic/RotationSpeed.js.map +0 -1
  262. package/dist/accessory/characteristic/SecuritySystemState.js.map +0 -1
  263. package/dist/accessory/characteristic/SwingMode.js.map +0 -1
  264. package/dist/accessory/characteristic/TargetTemperature.js.map +0 -1
  265. package/dist/accessory/characteristic/TemperatureDisplayUnits.js.map +0 -1
  266. package/dist/core/TuyaOpenAPI.js.map +0 -1
  267. package/dist/core/TuyaOpenMQ.js.map +0 -1
  268. package/dist/device/TuyaCustomDeviceManager.js.map +0 -1
  269. package/dist/device/TuyaDevice.js.map +0 -1
  270. package/dist/device/TuyaDeviceManager.js.map +0 -1
  271. package/dist/device/TuyaHomeDeviceManager.js.map +0 -1
  272. package/dist/util/FfmpegStreamingProcess.js.map +0 -1
  273. package/dist/util/InfraredTool.js.map +0 -1
  274. package/dist/util/Logger.js.map +0 -1
  275. package/dist/util/TuyaRecordingDelegate.js.map +0 -1
  276. package/dist/util/TuyaStreamDelegate.js.map +0 -1
  277. package/dist/util/color.js.map +0 -1
  278. package/dist/util/util.js +0 -63
  279. package/dist/util/util.js.map +0 -1
  280. /package/dist/{device → cloud/device}/TuyaCustomDeviceManager.js +0 -0
  281. /package/dist/{device → cloud/device}/TuyaHomeDeviceManager.js +0 -0
  282. /package/dist/{accessory → shared/accessories}/AirConditionerAccessory.js +0 -0
  283. /package/dist/{accessory → shared/accessories}/AirQualitySensorAccessory.js +0 -0
  284. /package/dist/{accessory → shared/accessories}/CarbonDioxideSensorAccessory.js +0 -0
  285. /package/dist/{accessory → shared/accessories}/CarbonMonoxideSensorAccessory.js +0 -0
  286. /package/dist/{accessory → shared/accessories}/ContactSensorAccessory.js +0 -0
  287. /package/dist/{accessory → shared/accessories}/DehumidifierAccessory.js +0 -0
  288. /package/dist/{accessory → shared/accessories}/DiffuserAccessory.js +0 -0
  289. /package/dist/{accessory → shared/accessories}/DimmerAccessory.js +0 -0
  290. /package/dist/{accessory → shared/accessories}/GarageDoorAccessory.js +0 -0
  291. /package/dist/{accessory → shared/accessories}/HeaterAccessory.js +0 -0
  292. /package/dist/{accessory → shared/accessories}/HeaterAccessory_old.js +0 -0
  293. /package/dist/{accessory → shared/accessories}/HumanPresenceSensorAccessory.js +0 -0
  294. /package/dist/{accessory → shared/accessories}/IRControlHubAccessory.js +0 -0
  295. /package/dist/{accessory → shared/accessories}/IRControlHubSubAccessory.js +0 -0
  296. /package/dist/{accessory → shared/accessories}/IRGenericAccessory.js +0 -0
  297. /package/dist/{accessory → shared/accessories}/LeakSensorAccessory.js +0 -0
  298. /package/dist/{accessory → shared/accessories}/LightAccessory.js +0 -0
  299. /package/dist/{accessory → shared/accessories}/LightSensorAccessory.js +0 -0
  300. /package/dist/{accessory → shared/accessories}/LocationWeatherAccessory.js +0 -0
  301. /package/dist/{accessory → shared/accessories}/LockAccessory.js +0 -0
  302. /package/dist/{accessory → shared/accessories}/MotionSensorAccessory.js +0 -0
  303. /package/dist/{accessory → shared/accessories}/OutletAccessory.js +0 -0
  304. /package/dist/{accessory → shared/accessories}/PetFeederAccessory.js +0 -0
  305. /package/dist/{accessory → shared/accessories}/SceneAccessory.js +0 -0
  306. /package/dist/{accessory → shared/accessories}/SecuritySystemAccessory.js +0 -0
  307. /package/dist/{accessory → shared/accessories}/SmokeSensorAccessory.js +0 -0
  308. /package/dist/{accessory → shared/accessories}/TemperatureHumiditySensorAccessory.js +0 -0
  309. /package/dist/{accessory → shared/accessories}/ThermostatAccessory.js +0 -0
  310. /package/dist/{accessory → shared/accessories}/TowerRackAccessory.js +0 -0
  311. /package/dist/{accessory → shared/accessories}/WeatherStationAccessory.js +0 -0
  312. /package/dist/{accessory → shared/accessories}/WetBulbGlobeTemperatureAccessory.js +0 -0
  313. /package/dist/{accessory → shared/accessories}/WhiteNoiseLightAccessory.js +0 -0
  314. /package/dist/{accessory → shared/accessories}/WindowAccessory.js +0 -0
  315. /package/dist/{accessory → shared/accessories}/WindowCoveringAccessory.js +0 -0
  316. /package/dist/{accessory → shared/accessories}/WirelessSwitchAccessory.js +0 -0
  317. /package/dist/{accessory → shared/accessories}/characteristic/Active.js +0 -0
  318. /package/dist/{accessory → shared/accessories}/characteristic/CurrentRelativeHumidity.js +0 -0
  319. /package/dist/{accessory → shared/accessories}/characteristic/CurrentTemperature.js +0 -0
  320. /package/dist/{accessory → shared/accessories}/characteristic/CurrentWeather.js +0 -0
  321. /package/dist/{accessory → shared/accessories}/characteristic/CurrentWeatherByOpenMeteo.js +0 -0
  322. /package/dist/{accessory → shared/accessories}/characteristic/CurrentWetBulbGlobeTemperature.js +0 -0
  323. /package/dist/{accessory → shared/accessories}/characteristic/EnergyUsage.js +0 -0
  324. /package/dist/{accessory → shared/accessories}/characteristic/Light.js +0 -0
  325. /package/dist/{accessory → shared/accessories}/characteristic/LightSensor.js +0 -0
  326. /package/dist/{accessory → shared/accessories}/characteristic/LockPhysicalControls.js +0 -0
  327. /package/dist/{accessory → shared/accessories}/characteristic/Name.js +0 -0
  328. /package/dist/{accessory → shared/accessories}/characteristic/OccupancyDetected.js +0 -0
  329. /package/dist/{accessory → shared/accessories}/characteristic/OutletInUse.js +0 -0
  330. /package/dist/{accessory → shared/accessories}/characteristic/RelativeHumidityDehumidifierThreshold.js +0 -0
  331. /package/dist/{accessory → shared/accessories}/characteristic/RotationSpeed.js +0 -0
  332. /package/dist/{accessory → shared/accessories}/characteristic/SecuritySystemState.js +0 -0
  333. /package/dist/{accessory → shared/accessories}/characteristic/SwingMode.js +0 -0
  334. /package/dist/{accessory → shared/accessories}/characteristic/TargetTemperature.js +0 -0
  335. /package/dist/{accessory → shared/accessories}/characteristic/TemperatureDisplayUnits.js +0 -0
  336. /package/dist/{util → shared/util}/FfmpegStreamingProcess.js +0 -0
  337. /package/dist/{util → shared/util}/InfraredTool.js +0 -0
  338. /package/dist/{util → shared/util}/Logger.js +0 -0
  339. /package/dist/{util → shared/util}/TuyaRecordingDelegate.js +0 -0
  340. /package/dist/{util → shared/util}/TuyaStreamDelegate.js +0 -0
  341. /package/dist/{util → shared/util}/color.js +0 -0
@@ -0,0 +1,813 @@
1
+ /* eslint-disable no-console */
2
+ import { describe, expect, test, beforeEach, jest } from '@jest/globals';
3
+ import EventEmitter from 'events';
4
+ import TuyaDeviceManager from '../src/cloud/device/TuyaDeviceManager';
5
+ import TuyaDevice from '../src/cloud/device/TuyaDevice';
6
+
7
+ // Mock Logger
8
+ jest.mock('../src/shared/util/Logger', () => ({
9
+ __esModule: true,
10
+ default: class Logger {
11
+ log() {}
12
+ info() {}
13
+ warn() {}
14
+ error() {}
15
+ debug() {}
16
+ },
17
+ PrefixLogger: class PrefixLogger {
18
+ constructor(public log: any, public name: string, public debugEnabled: boolean) {}
19
+ info() {}
20
+ warn() {}
21
+ error() {}
22
+ debug() {}
23
+ },
24
+ }));
25
+
26
+ // Mock TuyaOpenMQ
27
+ jest.mock('../src/cloud/api/TuyaOpenMQ', () => {
28
+ return class MockTuyaOpenMQ extends EventEmitter {
29
+ addMessageListener = jest.fn();
30
+ connect = jest.fn();
31
+ disconnect = jest.fn();
32
+ };
33
+ });
34
+
35
+ // Mock TuyaOpenAPI
36
+ jest.mock('../src/cloud/api/TuyaOpenAPI', () => {
37
+ return class MockTuyaOpenAPI {
38
+ log = { log: console.log };
39
+ accessId = 'test_id';
40
+ accessKey = 'test_key';
41
+ };
42
+ });
43
+
44
+ describe('TuyaDeviceManager', () => {
45
+ let manager: TuyaDeviceManager;
46
+ let mockAPI: any;
47
+
48
+ beforeEach(() => {
49
+ const getMock = jest.fn() as any;
50
+ getMock.mockImplementation((url: string) => {
51
+ if (url.includes('specifications')) {
52
+ return Promise.resolve({
53
+ success: true,
54
+ result: {
55
+ status: [],
56
+ functions: [],
57
+ },
58
+ });
59
+ }
60
+ return Promise.resolve({
61
+ success: true,
62
+ result: {
63
+ id: 'device_001',
64
+ name: 'Test Device',
65
+ product_id: 'prod_001',
66
+ category: 'dj',
67
+ status: [],
68
+ schema: [],
69
+ },
70
+ });
71
+ });
72
+
73
+ mockAPI = {
74
+ log: { log: console.log } as any,
75
+ accessId: 'test_access_id',
76
+ accessKey: 'test_access_key',
77
+ get: getMock,
78
+ };
79
+
80
+ manager = new TuyaDeviceManager(mockAPI, false);
81
+ });
82
+
83
+ describe('initialization', () => {
84
+ test('creates device manager instance', () => {
85
+ expect(manager).toBeInstanceOf(EventEmitter);
86
+ expect(manager).toBeDefined();
87
+ });
88
+
89
+ test('stores API reference', () => {
90
+ expect(manager.api).toBe(mockAPI);
91
+ });
92
+
93
+ test('initializes with debug disabled', () => {
94
+ expect(manager.debug).toBe(false);
95
+ });
96
+
97
+ test('initializes with debug enabled', () => {
98
+ const debugManager = new TuyaDeviceManager(mockAPI, true);
99
+ expect(debugManager.debug).toBe(true);
100
+ });
101
+
102
+ test('initializes MQTT connection', () => {
103
+ expect(manager.mq).toBeDefined();
104
+ });
105
+
106
+ test('creates logger', () => {
107
+ expect(manager.log).toBeDefined();
108
+ });
109
+
110
+ test('initializes empty owner IDs array', () => {
111
+ expect(manager.ownerIDs).toEqual([]);
112
+ expect(Array.isArray(manager.ownerIDs)).toBe(true);
113
+ });
114
+
115
+ test('initializes empty devices array', () => {
116
+ expect(manager.devices).toEqual([]);
117
+ expect(Array.isArray(manager.devices)).toBe(true);
118
+ });
119
+ });
120
+
121
+ describe('static events', () => {
122
+ test('has DEVICE_ADD event', () => {
123
+ expect(TuyaDeviceManager.Events.DEVICE_ADD).toBe('DEVICE_ADD');
124
+ });
125
+
126
+ test('has DEVICE_INFO_UPDATE event', () => {
127
+ expect(TuyaDeviceManager.Events.DEVICE_INFO_UPDATE).toBe('DEVICE_INFO_UPDATE');
128
+ });
129
+
130
+ test('has DEVICE_STATUS_UPDATE event', () => {
131
+ expect(TuyaDeviceManager.Events.DEVICE_STATUS_UPDATE).toBe('DEVICE_STATUS_UPDATE');
132
+ });
133
+
134
+ test('has DEVICE_DELETE event', () => {
135
+ expect(TuyaDeviceManager.Events.DEVICE_DELETE).toBe('DEVICE_DELETE');
136
+ });
137
+ });
138
+
139
+ describe('virtual device creation', () => {
140
+ test('creates virtual device from base device', () => {
141
+ const baseDevice = new TuyaDevice({
142
+ id: 'base_device_001',
143
+ product_id: 'product_001',
144
+ category: 'dj',
145
+ name: 'Base Light',
146
+ schema: [],
147
+ status: [],
148
+ });
149
+
150
+ const virtualDevice = manager.createVirtualDevice(baseDevice, 'virtual_uuid');
151
+
152
+ expect(virtualDevice).toBeDefined();
153
+ expect(virtualDevice).toBeInstanceOf(TuyaDevice);
154
+ expect(virtualDevice.sub).toBe(true);
155
+ expect(virtualDevice.parent_id).toBe('base_device_001');
156
+ });
157
+
158
+ test('sets virtual device properties', () => {
159
+ const baseDevice = new TuyaDevice({
160
+ id: 'base_001',
161
+ product_id: 'prod_001',
162
+ category: 'dj',
163
+ name: 'Base',
164
+ schema: [],
165
+ status: [],
166
+ });
167
+
168
+ const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_uuid_123');
169
+
170
+ expect(virtualDevice.name).toBe('Virtual Device');
171
+ expect(virtualDevice.product_name).toBe('virtual product');
172
+ expect(virtualDevice.ip).toBe('');
173
+ expect(virtualDevice.remote_keys).toBeUndefined();
174
+ });
175
+
176
+ test('generates UUID if not provided', () => {
177
+ const baseDevice = new TuyaDevice({
178
+ id: 'base_001',
179
+ product_id: 'prod_001',
180
+ category: 'kg',
181
+ name: 'Base',
182
+ schema: [],
183
+ status: [],
184
+ });
185
+
186
+ const virtualDevice = manager.createVirtualDevice(baseDevice, '');
187
+
188
+ expect(virtualDevice.id).toBeDefined();
189
+ expect(virtualDevice.id.length).toBeGreaterThan(0);
190
+ });
191
+
192
+ test('clones base device schema', () => {
193
+ const baseDevice = new TuyaDevice({
194
+ id: 'base_001',
195
+ product_id: 'prod_001',
196
+ category: 'dj',
197
+ name: 'Base',
198
+ schema: [
199
+ {
200
+ code: 'switch',
201
+ mode: 'rw' as any,
202
+ type: 'Boolean' as any,
203
+ property: {} as any,
204
+ },
205
+ ],
206
+ status: [],
207
+ });
208
+
209
+ const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_uuid');
210
+
211
+ expect(virtualDevice.schema).toBeDefined();
212
+ expect(virtualDevice.schema.length).toBeGreaterThan(0);
213
+ });
214
+
215
+ test('clones base device status', () => {
216
+ const baseDevice = new TuyaDevice({
217
+ id: 'base_001',
218
+ product_id: 'prod_001',
219
+ category: 'dj',
220
+ name: 'Base',
221
+ schema: [],
222
+ status: [{ code: 'switch_1', value: 1 }],
223
+ });
224
+
225
+ const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_uuid');
226
+
227
+ expect(virtualDevice.status).toBeDefined();
228
+ });
229
+ });
230
+
231
+ describe('device retrieval', () => {
232
+ test('finds device by ID', () => {
233
+ const device1 = new TuyaDevice({
234
+ id: 'device_001',
235
+ product_id: 'prod_001',
236
+ category: 'dj',
237
+ name: 'Device 1',
238
+ schema: [],
239
+ status: [],
240
+ });
241
+
242
+ manager.devices.push(device1);
243
+
244
+ const found = manager.getDevice('device_001');
245
+ expect(found).toBe(device1);
246
+ });
247
+
248
+ test('returns undefined for non-existent device', () => {
249
+ manager.devices = [];
250
+ const found = manager.getDevice('non_existent');
251
+
252
+ expect(found).toBeUndefined();
253
+ });
254
+
255
+ test('finds correct device among multiple', () => {
256
+ const device1 = new TuyaDevice({
257
+ id: 'device_001',
258
+ product_id: 'prod_001',
259
+ category: 'dj',
260
+ name: 'Device 1',
261
+ schema: [],
262
+ status: [],
263
+ });
264
+
265
+ const device2 = new TuyaDevice({
266
+ id: 'device_002',
267
+ product_id: 'prod_002',
268
+ category: 'kg',
269
+ name: 'Device 2',
270
+ schema: [],
271
+ status: [],
272
+ });
273
+
274
+ manager.devices = [device1, device2];
275
+
276
+ const found = manager.getDevice('device_002');
277
+ expect(found?.id).toBe('device_002');
278
+ expect(found?.category).toBe('kg');
279
+ });
280
+ });
281
+
282
+ describe('device array management', () => {
283
+ test('can add devices to array', () => {
284
+ const device = new TuyaDevice({
285
+ id: 'dev_001',
286
+ product_id: 'prod_001',
287
+ category: 'dj',
288
+ name: 'Device',
289
+ schema: [],
290
+ status: [],
291
+ });
292
+
293
+ manager.devices.push(device);
294
+
295
+ expect(manager.devices.length).toBe(1);
296
+ expect(manager.devices[0]).toBe(device);
297
+ });
298
+
299
+ test('can remove devices from array', () => {
300
+ const device1 = new TuyaDevice({
301
+ id: 'dev_001',
302
+ product_id: 'prod_001',
303
+ category: 'dj',
304
+ name: 'Device 1',
305
+ schema: [],
306
+ status: [],
307
+ });
308
+
309
+ const device2 = new TuyaDevice({
310
+ id: 'dev_002',
311
+ product_id: 'prod_002',
312
+ category: 'kg',
313
+ name: 'Device 2',
314
+ schema: [],
315
+ status: [],
316
+ });
317
+
318
+ manager.devices = [device1, device2];
319
+ manager.devices = manager.devices.filter(d => d.id !== 'dev_001');
320
+
321
+ expect(manager.devices.length).toBe(1);
322
+ expect(manager.devices[0].id).toBe('dev_002');
323
+ });
324
+
325
+ test('can clear all devices', () => {
326
+ manager.devices = [
327
+ new TuyaDevice({
328
+ id: 'dev_1',
329
+ product_id: 'prod_1',
330
+ category: 'dj',
331
+ name: 'Device 1',
332
+ schema: [],
333
+ status: [],
334
+ }),
335
+ new TuyaDevice({
336
+ id: 'dev_2',
337
+ product_id: 'prod_2',
338
+ category: 'kg',
339
+ name: 'Device 2',
340
+ schema: [],
341
+ status: [],
342
+ }),
343
+ ];
344
+
345
+ manager.devices = [];
346
+
347
+ expect(manager.devices.length).toBe(0);
348
+ });
349
+ });
350
+
351
+ describe('owner ID management', () => {
352
+ test('can add owner IDs', () => {
353
+ manager.ownerIDs.push('owner_123');
354
+ manager.ownerIDs.push('owner_456');
355
+
356
+ expect(manager.ownerIDs.length).toBe(2);
357
+ expect(manager.ownerIDs).toContain('owner_123');
358
+ });
359
+
360
+ test('can clear owner IDs', () => {
361
+ manager.ownerIDs = ['owner_1', 'owner_2'];
362
+ manager.ownerIDs = [];
363
+
364
+ expect(manager.ownerIDs.length).toBe(0);
365
+ });
366
+
367
+ test('prevents duplicate owner IDs', () => {
368
+ manager.ownerIDs = ['owner_123', 'owner_456'].filter(
369
+ (id, index, self) => self.indexOf(id) === index
370
+ );
371
+
372
+ expect(new Set(manager.ownerIDs).size).toBe(manager.ownerIDs.length);
373
+ });
374
+ });
375
+
376
+ describe('event emitter behavior', () => {
377
+ test('is instance of EventEmitter', () => {
378
+ expect(manager instanceof EventEmitter).toBe(true);
379
+ });
380
+
381
+ test('can listen for events', (done) => {
382
+ manager.on('test_event', (data) => {
383
+ expect(data).toBe('test_data');
384
+ done();
385
+ });
386
+
387
+ manager.emit('test_event', 'test_data');
388
+ });
389
+
390
+ test('can emit DEVICE_ADD events', (done) => {
391
+ manager.on(TuyaDeviceManager.Events.DEVICE_ADD, (device) => {
392
+ expect(device.id).toBe('device_001');
393
+ done();
394
+ });
395
+
396
+ const device = new TuyaDevice({
397
+ id: 'device_001',
398
+ product_id: 'prod_001',
399
+ category: 'dj',
400
+ name: 'Device',
401
+ schema: [],
402
+ status: [],
403
+ });
404
+
405
+ manager.emit(TuyaDeviceManager.Events.DEVICE_ADD, device);
406
+ });
407
+
408
+ test('can emit DEVICE_STATUS_UPDATE events', (done) => {
409
+ manager.on(TuyaDeviceManager.Events.DEVICE_STATUS_UPDATE, (deviceId, status) => {
410
+ expect(deviceId).toBe('device_001');
411
+ expect(status).toBeDefined();
412
+ done();
413
+ });
414
+
415
+ manager.emit(TuyaDeviceManager.Events.DEVICE_STATUS_UPDATE, 'device_001', {});
416
+ });
417
+ });
418
+
419
+ describe('MQTT integration', () => {
420
+ test('adds message listener on init', () => {
421
+ const newManager = new TuyaDeviceManager(mockAPI, false);
422
+ expect(newManager.mq.addMessageListener).toHaveBeenCalled();
423
+ });
424
+
425
+ test('MQTT is available through manager', () => {
426
+ expect(manager.mq).toBeDefined();
427
+ expect(manager.mq.addMessageListener).toBeDefined();
428
+ });
429
+ });
430
+
431
+ describe('device filtering', () => {
432
+ test('can filter devices by category', () => {
433
+ manager.devices = [
434
+ new TuyaDevice({
435
+ id: 'light_1',
436
+ product_id: 'prod_1',
437
+ category: 'dj',
438
+ name: 'Light',
439
+ schema: [],
440
+ status: [],
441
+ }),
442
+ new TuyaDevice({
443
+ id: 'switch_1',
444
+ product_id: 'prod_2',
445
+ category: 'kg',
446
+ name: 'Switch',
447
+ schema: [],
448
+ status: [],
449
+ }),
450
+ ];
451
+
452
+ const lights = manager.devices.filter(d => d.category === 'dj');
453
+ expect(lights.length).toBe(1);
454
+ expect(lights[0].category).toBe('dj');
455
+ });
456
+
457
+ test('can filter virtual devices', () => {
458
+ const baseDevice = new TuyaDevice({
459
+ id: 'base_001',
460
+ product_id: 'prod_001',
461
+ category: 'dj',
462
+ name: 'Base',
463
+ schema: [],
464
+ status: [],
465
+ });
466
+
467
+ const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_123');
468
+ manager.devices = [baseDevice, virtualDevice];
469
+
470
+ const virtuals = manager.devices.filter(d => d.sub === true);
471
+ expect(virtuals.length).toBe(1);
472
+ expect(virtuals[0].parent_id).toBe('base_001');
473
+ });
474
+ });
475
+
476
+ describe('multiple manager instances', () => {
477
+ test('maintain separate device lists', () => {
478
+ const manager1 = new TuyaDeviceManager(mockAPI, false);
479
+ const manager2 = new TuyaDeviceManager(mockAPI, false);
480
+
481
+ const device1 = new TuyaDevice({
482
+ id: 'dev_manager1',
483
+ product_id: 'prod_1',
484
+ category: 'dj',
485
+ name: 'Device 1',
486
+ schema: [],
487
+ status: [],
488
+ });
489
+
490
+ const device2 = new TuyaDevice({
491
+ id: 'dev_manager2',
492
+ product_id: 'prod_2',
493
+ category: 'kg',
494
+ name: 'Device 2',
495
+ schema: [],
496
+ status: [],
497
+ });
498
+
499
+ manager1.devices.push(device1);
500
+ manager2.devices.push(device2);
501
+
502
+ expect(manager1.devices.length).toBe(1);
503
+ expect(manager2.devices.length).toBe(1);
504
+ expect(manager1.devices[0].id).not.toBe(manager2.devices[0].id);
505
+ });
506
+ });
507
+
508
+ describe('device type handling', () => {
509
+ test('handles light devices', () => {
510
+ const light = new TuyaDevice({
511
+ id: 'light_001',
512
+ product_id: 'dj_prod',
513
+ category: 'dj',
514
+ name: 'Smart Light',
515
+ schema: [],
516
+ status: [],
517
+ });
518
+
519
+ manager.devices.push(light);
520
+
521
+ const found = manager.getDevice('light_001');
522
+ expect(found?.category).toBe('dj');
523
+ });
524
+
525
+ test('handles switch devices', () => {
526
+ const sw = new TuyaDevice({
527
+ id: 'switch_001',
528
+ product_id: 'kg_prod',
529
+ category: 'kg',
530
+ name: 'Smart Switch',
531
+ schema: [],
532
+ status: [],
533
+ });
534
+
535
+ manager.devices.push(sw);
536
+
537
+ const found = manager.getDevice('switch_001');
538
+ expect(found?.category).toBe('kg');
539
+ });
540
+
541
+ test('handles thermostat devices', () => {
542
+ const thermostat = new TuyaDevice({
543
+ id: 'thermo_001',
544
+ product_id: 'wk_prod',
545
+ category: 'wk',
546
+ name: 'Thermostat',
547
+ schema: [],
548
+ status: [],
549
+ });
550
+
551
+ manager.devices.push(thermostat);
552
+
553
+ const found = manager.getDevice('thermo_001');
554
+ expect(found?.category).toBe('wk');
555
+ });
556
+ });
557
+
558
+ describe('async operations', () => {
559
+ test('updateDevices returns array', async () => {
560
+ const result = await manager.updateDevices([]);
561
+ expect(Array.isArray(result)).toBe(true);
562
+ });
563
+
564
+ test('updateDevice can be called', async () => {
565
+ // Mock implementation since actual HTTP calls are not set up
566
+ const result = await manager.updateDevice('device_001');
567
+ expect(typeof result === 'object' || result === null).toBe(true);
568
+ });
569
+ });
570
+
571
+ describe('infrared device methods', () => {
572
+ test('getInfraredRemotes calls API with correct endpoint', async () => {
573
+ const result = await manager.getInfraredRemotes('ir_001');
574
+ expect(result).toBeDefined();
575
+ });
576
+
577
+ test('getInfraredKeys calls API with correct endpoint', async () => {
578
+ const result = await manager.getInfraredKeys('ir_001', 'remote_001');
579
+ expect(result).toBeDefined();
580
+ });
581
+
582
+ test('getInfraredACStatus calls API with correct endpoint', async () => {
583
+ const result = await manager.getInfraredACStatus('ir_001', 'remote_001');
584
+ expect(result).toBeDefined();
585
+ });
586
+
587
+ test('getInfraredDIYKeys calls API with correct endpoint', async () => {
588
+ const result = await manager.getInfraredDIYKeys('ir_001', 'remote_001');
589
+ expect(result).toBeDefined();
590
+ });
591
+ });
592
+
593
+ describe('infrared remote resolution', () => {
594
+ test('resolveInfraredRemotes returns empty array when no IR devices', () => {
595
+ const parentDevice = new TuyaDevice({
596
+ id: 'parent_001',
597
+ name: 'Hub',
598
+ category: 'wifizigbee',
599
+ product_id: 'hub_prod',
600
+ schema: [],
601
+ status: [],
602
+ });
603
+
604
+ const result = manager.resolveInfraredRemotes(parentDevice, [parentDevice]);
605
+ expect(Array.isArray(result)).toBe(true);
606
+ expect(result.length).toBe(0);
607
+ });
608
+
609
+ test('resolveInfraredRemotes finds IR devices with matching location', () => {
610
+ const parentDevice = new TuyaDevice({
611
+ id: 'parent_001',
612
+ name: 'Hub',
613
+ category: 'wifizigbee',
614
+ product_id: 'hub_prod',
615
+ schema: [],
616
+ status: [],
617
+ lat: '40.7128',
618
+ lon: '-74.0060',
619
+ });
620
+
621
+ const irDevice = new TuyaDevice({
622
+ id: 'ir_001',
623
+ name: 'IR Remote',
624
+ category: 'infrared_ac',
625
+ product_id: 'ir_prod',
626
+ schema: [],
627
+ status: [],
628
+ sub: true,
629
+ lat: '40.7128',
630
+ lon: '-74.0060',
631
+ });
632
+
633
+ const result = manager.resolveInfraredRemotes(parentDevice, [parentDevice, irDevice]);
634
+ expect(result.length).toBeGreaterThan(0);
635
+ });
636
+
637
+ test('resolveInfraredRemotes finds IR devices with matching update_time', () => {
638
+ const timestamp = 1234567890;
639
+ const parentDevice = new TuyaDevice({
640
+ id: 'parent_001',
641
+ name: 'Hub',
642
+ category: 'wifizigbee',
643
+ product_id: 'hub_prod',
644
+ schema: [],
645
+ status: [],
646
+ update_time: timestamp,
647
+ });
648
+
649
+ const irDevice = new TuyaDevice({
650
+ id: 'ir_001',
651
+ name: 'IR Remote',
652
+ category: 'infrared_light',
653
+ product_id: 'ir_prod',
654
+ schema: [],
655
+ status: [],
656
+ sub: true,
657
+ update_time: timestamp,
658
+ });
659
+
660
+ const result = manager.resolveInfraredRemotes(parentDevice, [parentDevice, irDevice]);
661
+ expect(result.length).toBeGreaterThan(0);
662
+ });
663
+ });
664
+
665
+ describe('infrared device fixing', () => {
666
+ test('fixInfraredDevice sets org_category_id', () => {
667
+ const device = new TuyaDevice({
668
+ id: 'ir_ac_001',
669
+ name: 'IR AC',
670
+ category: 'infrared_ac',
671
+ product_id: 'prsgoryjfdtb42r4',
672
+ schema: [],
673
+ status: [],
674
+ remote_keys: {
675
+ category_id: 5,
676
+ org_category_id: 5,
677
+ brand_id: 0,
678
+ remote_index: 0,
679
+ single_air: false,
680
+ duplicate_power: false,
681
+ key_list: [],
682
+ key_range: [],
683
+ },
684
+ });
685
+
686
+ manager.fixInfraredDevice(device);
687
+ expect(device.remote_keys?.org_category_id).toBe(5);
688
+ });
689
+ });
690
+
691
+ describe('HAP category resolution', () => {
692
+ test('resolveHAPCategoryID returns correct category for fan product', () => {
693
+ const device = new TuyaDevice({
694
+ id: 'ir_fan_001',
695
+ name: 'IR Fan',
696
+ category: 'infrared_fan',
697
+ product_id: 'prsgoryjfdtb42r4',
698
+ schema: [],
699
+ status: [],
700
+ remote_keys: {
701
+ category_id: 5,
702
+ org_category_id: 5,
703
+ brand_id: 0,
704
+ remote_index: 0,
705
+ single_air: false,
706
+ duplicate_power: false,
707
+ key_list: [],
708
+ key_range: [],
709
+ },
710
+ });
711
+
712
+ const result = manager.resolveHAPCategoryID(device);
713
+ expect(result).toBe(8); // Fan category
714
+ });
715
+
716
+ test('resolveHAPCategoryID returns DIY for DIY product', () => {
717
+ const device = new TuyaDevice({
718
+ id: 'ir_diy_001',
719
+ name: 'IR DIY',
720
+ category: 'infrared_diy',
721
+ product_id: 'k6ozylayfgnskuq6',
722
+ schema: [],
723
+ status: [],
724
+ remote_keys: {
725
+ category_id: 5,
726
+ org_category_id: 5,
727
+ brand_id: 0,
728
+ remote_index: 0,
729
+ single_air: false,
730
+ duplicate_power: false,
731
+ key_list: [],
732
+ key_range: [],
733
+ },
734
+ });
735
+
736
+ const result = manager.resolveHAPCategoryID(device);
737
+ expect(result).toBe(999); // DIY category
738
+ });
739
+
740
+ test('resolveHAPCategoryID falls back to device category', () => {
741
+ const device = new TuyaDevice({
742
+ id: 'ir_other_001',
743
+ name: 'IR Other',
744
+ category: 'infrared_other',
745
+ product_id: 'other_prod',
746
+ schema: [],
747
+ status: [],
748
+ remote_keys: {
749
+ category_id: 7,
750
+ org_category_id: 7,
751
+ brand_id: 0,
752
+ remote_index: 0,
753
+ single_air: false,
754
+ duplicate_power: false,
755
+ key_list: [],
756
+ key_range: [],
757
+ },
758
+ });
759
+
760
+ const result = manager.resolveHAPCategoryID(device);
761
+ expect(result).toBe(7);
762
+ });
763
+
764
+ test('resolveHAPCategoryID defaults to DIY for device without remote_keys', () => {
765
+ const device = new TuyaDevice({
766
+ id: 'ir_001',
767
+ name: 'IR Device',
768
+ category: 'infrared_ac',
769
+ product_id: 'ir_prod',
770
+ schema: [],
771
+ status: [],
772
+ });
773
+
774
+ const result = manager.resolveHAPCategoryID(device);
775
+ expect(result).toBe(999);
776
+ });
777
+ });
778
+
779
+ describe('updateInfraredRemotes', () => {
780
+ test('updateInfraredRemotes skips non-IR devices', async () => {
781
+ const devices = [
782
+ new TuyaDevice({
783
+ id: 'light_001',
784
+ name: 'Light',
785
+ category: 'dj',
786
+ product_id: 'light_prod',
787
+ schema: [],
788
+ status: [],
789
+ }),
790
+ ];
791
+
792
+ await manager.updateInfraredRemotes(devices);
793
+ // Should not throw
794
+ expect(true).toBe(true);
795
+ });
796
+ });
797
+
798
+ describe('dump method', () => {
799
+ test('dump logs object properties without error', () => {
800
+ const testObj = { key1: 'value1', key2: 123 };
801
+ expect(() => {
802
+ manager.dump(testObj);
803
+ }).not.toThrow();
804
+ });
805
+
806
+ test('dump handles nested objects', () => {
807
+ const testObj = { outer: { inner: 'value' } };
808
+ expect(() => {
809
+ manager.dump(testObj);
810
+ }).not.toThrow();
811
+ });
812
+ });
813
+ });