@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
package/jest.config.js CHANGED
@@ -2,4 +2,10 @@
2
2
  module.exports = {
3
3
  preset: 'ts-jest',
4
4
  testEnvironment: 'node',
5
+ moduleNameMapper: {
6
+ '^(\\.{1,2}/)*package\\.json$': '<rootDir>/package.json',
7
+ '^color-convert$': '<rootDir>/test/__mocks__/color-convert.js',
8
+ '^kelvin-to-rgb$': '<rootDir>/test/__mocks__/kelvin-to-rgb.js',
9
+ '^@homebridge/camera-utils$': '<rootDir>/test/__mocks__/@homebridge-camera-utils.js',
10
+ },
5
11
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@homebridge-plugins/homebridge-tuya",
3
3
  "displayName": "Homebridge Tuya",
4
- "version": "2.2.1",
5
- "description": "Forked from 0x5e/homebridge-tuya-platform, with a focus on fixing bugs and adding new device support.",
4
+ "version": "3.0.0-beta.1",
5
+ "description": "Unified Homebridge Tuya plugin supporting both Tuya Cloud (REST/MQTT) and Tuya Local (direct LAN TCP) communication.",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -27,8 +27,11 @@
27
27
  },
28
28
  "main": "dist/index.js",
29
29
  "scripts": {
30
- "lint": "eslint src/**/*.ts --max-warnings=0",
30
+ "check": "npm install && npm outdated",
31
+ "lint": "eslint src/**/*.ts",
32
+ "lint:fix": "eslint src/**/*.ts --fix",
31
33
  "test": "jest",
34
+ "coverage": "npm test -- --coverage",
32
35
  "watch": "npm run build && npm link && nodemon",
33
36
  "launch": "tsc && homebridge -I -D",
34
37
  "build": "rimraf ./dist && tsc",
@@ -40,35 +43,27 @@
40
43
  "tuya"
41
44
  ],
42
45
  "dependencies": {
43
- "@homebridge/camera-utils": "^2.2.0",
44
- "async-await-retry": "^2.0.1",
45
- "color-convert": "^2.0.1",
46
- "crypto-js": "^4.1.1",
47
- "debounce": "^1.2.1",
48
- "jsonschema": "^1.4.1",
46
+ "@homebridge/camera-utils": "^3.0.0",
47
+ "color-convert": "^3.1.3",
48
+ "jsonschema": "^1.5.0",
49
49
  "kelvin-to-rgb": "^1.0.2",
50
- "lodash.isequal": "^4.5.0",
51
- "mqtt": "^4.2.6",
52
- "uuid": "^9.0.0"
50
+ "mqtt": "^5.15.1"
53
51
  },
54
52
  "devDependencies": {
55
- "@types/color-convert": "^2.0.0",
56
- "@types/crypto-js": "^4.1.1",
57
- "@types/debounce": "^1.2.1",
58
- "@types/jest": "^29.1.2",
59
- "@types/lodash.isequal": "^4.5.6",
60
- "@types/node": "^22.0.0",
61
- "@types/uuid": "^8.3.4",
62
- "@typescript-eslint/eslint-plugin": "^5.0.0",
63
- "@typescript-eslint/parser": "^5.0.0",
64
- "eslint": "8.57.1",
53
+ "@eslint/js": "^10.0.1",
54
+ "@types/color-convert": "^2.0.4",
55
+ "@types/jest": "^30.0.0",
56
+ "@types/node": "^25.5.0",
57
+ "@typescript-eslint/eslint-plugin": "^8.57.2",
58
+ "@typescript-eslint/parser": "^8.57.2",
59
+ "eslint": "^10.1.0",
65
60
  "homebridge": "^2.0.0-beta.0",
66
- "jest": "^29.1.2",
67
- "nodemon": "^3.1.10",
68
- "rimraf": "^5.0.0",
61
+ "jest": "^30.3.0",
62
+ "nodemon": "^3.1.14",
63
+ "rimraf": "^6.1.3",
69
64
  "ts-jest": "^29.4.6",
70
- "ts-node": "^10.3.0",
71
- "typescript": "^5.0.0"
65
+ "ts-node": "^10.9.2",
66
+ "typescript": "^5.9.3"
72
67
  },
73
68
  "homebridge": {
74
69
  "plugin_type": "platform",
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ defaultFfmpegPath: '/usr/bin/ffmpeg',
3
+ reservePorts: async (count) => {
4
+ const ports = [];
5
+ for (let i = 0; i < count; i++) {
6
+ ports.push(5000 + i);
7
+ }
8
+ return ports;
9
+ },
10
+ };
@@ -0,0 +1,24 @@
1
+ module.exports = {
2
+ rgb: {
3
+ hsv: (r, g, b) => {
4
+ // Simple RGB to HSV conversion mock
5
+ const max = Math.max(r, g, b) / 255;
6
+ const min = Math.min(r, g, b) / 255;
7
+ const l = (max + min) / 2;
8
+ let h = 0, s = 0;
9
+
10
+ if (max !== min) {
11
+ const d = max - min;
12
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
13
+ switch (max) {
14
+ case r / 255: h = (g - b) / 255 / d + (g < b ? 6 : 0); break;
15
+ case g / 255: h = (b - r) / 255 / d + 2; break;
16
+ case b / 255: h = (r - g) / 255 / d + 4; break;
17
+ }
18
+ h /= 6;
19
+ }
20
+
21
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(max * 100)];
22
+ },
23
+ },
24
+ };
@@ -0,0 +1,21 @@
1
+ // Simple Kelvin to RGB conversion mock
2
+ module.exports = function kelvinToRgb(kelvin) {
3
+ let r = 255, g = 255, b = 255;
4
+
5
+ if (kelvin < 6600) {
6
+ r = 255;
7
+ g = Math.round(99.4708025861 * Math.log(kelvin) - 161.1195681661);
8
+
9
+ if (kelvin < 2000) {
10
+ b = 0;
11
+ } else {
12
+ b = Math.round(138.5177312231 * Math.log(kelvin - 10) - 305.0447927307);
13
+ }
14
+ }
15
+
16
+ return [
17
+ Math.max(0, Math.min(255, r)),
18
+ Math.max(0, Math.min(255, g)),
19
+ Math.max(0, Math.min(255, b)),
20
+ ];
21
+ };
@@ -0,0 +1,522 @@
1
+ /* eslint-disable no-console */
2
+ import { describe, expect, test, beforeEach } from '@jest/globals';
3
+ import { PlatformAccessory } from 'homebridge';
4
+ import AccessoryFactory from '../src/shared/AccessoryFactory';
5
+ import TuyaDevice, { TuyaDeviceSchemaMode, TuyaDeviceSchemaType } from '../src/cloud/device/TuyaDevice';
6
+ import { TuyaPlatform } from '../src/platform';
7
+
8
+ describe('AccessoryFactory', () => {
9
+ let mockPlatform: Partial<TuyaPlatform>;
10
+ let mockAccessory: Partial<PlatformAccessory>;
11
+
12
+ beforeEach(() => {
13
+ // Create proper HAP mock structure
14
+ class MockService {
15
+ static AccessoryInformation = class {};
16
+ static Lightbulb = class {};
17
+ static Switch = class {};
18
+ static Outlet = class {};
19
+ static Fan = class {};
20
+ static Thermostat = class {};
21
+ static WindowCovering = class {};
22
+ static GarageDoorOpener = class {};
23
+ static LockMechanism = class {};
24
+ static Valve = class {};
25
+ static HeaterCooler = class {};
26
+ static Microphone = class {};
27
+ static Speaker = class {};
28
+ }
29
+
30
+ class MockCharacteristic {
31
+ static On = { UUID: 'uuid-on' };
32
+ static Brightness = { UUID: 'uuid-brightness' };
33
+ static Name = { UUID: 'uuid-name' };
34
+ static Manufacturer = { UUID: 'uuid-manufacturer' };
35
+ static Model = { UUID: 'uuid-model' };
36
+ static SerialNumber = { UUID: 'uuid-sn' };
37
+ static AccessoryFlags = { UUID: 'uuid-flags' };
38
+ static Identify = { UUID: 'uuid-identify' };
39
+ static CurrentTemperature = { UUID: 'uuid-temp' };
40
+ static TargetTemperature = { UUID: 'uuid-target-temp' };
41
+ static CurrentRelativeHumidity = { UUID: 'uuid-humidity' };
42
+ static Saturation = { UUID: 'uuid-saturation' };
43
+ static Hue = { UUID: 'uuid-hue' };
44
+ static ColorTemperature = { UUID: 'uuid-color-temp' };
45
+ static RotationSpeed = { UUID: 'uuid-rotation' };
46
+ static CurrentPosition = { UUID: 'uuid-position' };
47
+ static TargetPosition = { UUID: 'uuid-target-pos' };
48
+ static LockCurrentState = { UUID: 'uuid-lock-state' };
49
+ static LockTargetState = { UUID: 'uuid-lock-target' };
50
+ static Active = { UUID: 'uuid-active', INACTIVE: 0, ACTIVE: 1 };
51
+ static CurrentHeatingCoolingState = { UUID: 'uuid-heating-cooling', OFF: 0, HEAT: 1, COOL: 2, AUTO: 3 };
52
+ static TargetHeatingCoolingState = { UUID: 'uuid-target-heating-cooling', OFF: 0, HEAT: 1, COOL: 2, AUTO: 3 };
53
+ static TemperatureDisplayUnits = { UUID: 'uuid-temp-units', CELSIUS: 0, FAHRENHEIT: 1 };
54
+ static CurrentHeaterCoolerState = { UUID: 'uuid-current-heater-cooler', IDLE: 0, HEATING: 1, COOLING: 2 };
55
+ static TargetHeaterCoolerState = { UUID: 'uuid-target-heater-cooler', OFF: 0, HEAT: 1, COOL: 2, AUTO: 3 };
56
+ }
57
+
58
+ mockPlatform = {
59
+ log: {
60
+ info: jest.fn(),
61
+ warn: jest.fn(),
62
+ error: jest.fn(),
63
+ debug: jest.fn(),
64
+ log: jest.fn(),
65
+ },
66
+ config: {} as any,
67
+ api: {
68
+ hap: {
69
+ Service: MockService as any,
70
+ Characteristic: MockCharacteristic as any,
71
+ CameraController: jest.fn(),
72
+ },
73
+ } as any,
74
+ accessories: [],
75
+ Service: MockService as any,
76
+ Characteristic: MockCharacteristic as any,
77
+ options: {
78
+ debug: false,
79
+ debugLevel: '',
80
+ },
81
+ deviceManager: {
82
+ getDevice: jest.fn().mockReturnValue({
83
+ id: 'test_device',
84
+ name: 'Test Device',
85
+ schema: [],
86
+ status: [],
87
+ product_id: 'test_product',
88
+ category: 'dj',
89
+ product_name: 'Light',
90
+ uuid: 'test-device-uuid',
91
+ model: 'Model X',
92
+ isVirtualDevice: jest.fn().mockReturnValue(false),
93
+ isIRControlHub: jest.fn().mockReturnValue(false),
94
+ }),
95
+ },
96
+ getDeviceSchemaConfig: jest.fn().mockReturnValue(null),
97
+ getDeviceStatusProperty: jest.fn().mockReturnValue(undefined),
98
+ } as any;
99
+
100
+ mockAccessory = {
101
+ UUID: 'test-uuid',
102
+ displayName: 'Test Device',
103
+ context: {
104
+ deviceID: 'test_device',
105
+ },
106
+ services: [],
107
+ getService: jest.fn().mockReturnValue(undefined),
108
+ addService: jest.fn().mockReturnValue({
109
+ setCharacteristic: jest.fn().mockReturnThis(),
110
+ getCharacteristic: jest.fn().mockReturnValue({
111
+ onGet: jest.fn(function() { return this; }),
112
+ onSet: jest.fn(function() { return this; }),
113
+ setProps: jest.fn().mockReturnThis(),
114
+ }),
115
+ }),
116
+ removeAllListeners: jest.fn(),
117
+ on: jest.fn(),
118
+ once: jest.fn(),
119
+ configureController: jest.fn(),
120
+ } as any;
121
+ });
122
+
123
+ describe('createAccessory', () => {
124
+ test('creates accessory for valid device', () => {
125
+ const device = new TuyaDevice({
126
+ id: 'test_device',
127
+ product_id: 'test_product',
128
+ category: 'light',
129
+ name: 'Test Light',
130
+ });
131
+
132
+ const accessory = AccessoryFactory.createAccessory(
133
+ mockPlatform as TuyaPlatform,
134
+ mockAccessory as PlatformAccessory,
135
+ device,
136
+ );
137
+
138
+ expect(accessory).toBeDefined();
139
+ expect(accessory).not.toBeNull();
140
+ });
141
+
142
+ test('creates accessory with category fallback', () => {
143
+ const device = new TuyaDevice({
144
+ id: 'test_device',
145
+ product_id: 'unknown_product',
146
+ category: 'switch',
147
+ name: 'Test Switch',
148
+ });
149
+
150
+ const accessory = AccessoryFactory.createAccessory(
151
+ mockPlatform as TuyaPlatform,
152
+ mockAccessory as PlatformAccessory,
153
+ device,
154
+ );
155
+
156
+ expect(accessory).toBeDefined();
157
+ });
158
+
159
+ test('handles unknown category gracefully', () => {
160
+ const device = new TuyaDevice(
161
+ {
162
+ id: 'test_device',
163
+ product_id: 'unknown_product',
164
+ category: 'unknown_category',
165
+ name: 'Unknown Device',
166
+ }
167
+ );
168
+
169
+ // Should not throw
170
+ expect(() => {
171
+ AccessoryFactory.createAccessory(
172
+ mockPlatform as TuyaPlatform,
173
+ mockAccessory as PlatformAccessory,
174
+ device,
175
+ );
176
+ }).not.toThrow();
177
+ });
178
+ });
179
+
180
+ describe('device categories', () => {
181
+ test('handles light category', () => {
182
+ const device = new TuyaDevice(
183
+ {
184
+ id: 'light_1',
185
+ product_id: 'prod_light',
186
+ category: 'dj',
187
+ name: 'Smart Light',
188
+ }
189
+ );
190
+
191
+ const accessory = AccessoryFactory.createAccessory(
192
+ mockPlatform as TuyaPlatform,
193
+ mockAccessory as PlatformAccessory,
194
+ device,
195
+ );
196
+
197
+ expect(accessory).toBeDefined();
198
+ });
199
+
200
+ test('handles switch category', () => {
201
+ const device = new TuyaDevice(
202
+ {
203
+ id: 'switch_1',
204
+ product_id: 'prod_switch',
205
+ category: 'kg',
206
+ name: 'Smart Switch',
207
+ }
208
+ );
209
+
210
+ const accessory = AccessoryFactory.createAccessory(
211
+ mockPlatform as TuyaPlatform,
212
+ mockAccessory as PlatformAccessory,
213
+ device,
214
+ );
215
+
216
+ expect(accessory).toBeDefined();
217
+ });
218
+
219
+ test('handles outlet category', () => {
220
+ const device = new TuyaDevice(
221
+ {
222
+ id: 'outlet_1',
223
+ product_id: 'prod_outlet',
224
+ category: 'cz',
225
+ name: 'Smart Outlet',
226
+ }
227
+ );
228
+
229
+ const accessory = AccessoryFactory.createAccessory(
230
+ mockPlatform as TuyaPlatform,
231
+ mockAccessory as PlatformAccessory,
232
+ device,
233
+ );
234
+
235
+ expect(accessory).toBeDefined();
236
+ });
237
+
238
+ test('handles fan category', () => {
239
+ const device = new TuyaDevice(
240
+ {
241
+ id: 'fan_1',
242
+ product_id: 'prod_fan',
243
+ category: 'fs',
244
+ name: 'Smart Fan',
245
+ }
246
+ );
247
+
248
+ const accessory = AccessoryFactory.createAccessory(
249
+ mockPlatform as TuyaPlatform,
250
+ mockAccessory as PlatformAccessory,
251
+ device,
252
+ );
253
+
254
+ expect(accessory).toBeDefined();
255
+ });
256
+
257
+ test('handles thermostat category', () => {
258
+ const device = new TuyaDevice(
259
+ {
260
+ id: 'thermostat_1',
261
+ product_id: 'prod_thermostat',
262
+ category: 'wk',
263
+ name: 'Smart Thermostat',
264
+ }
265
+ );
266
+
267
+ const accessory = AccessoryFactory.createAccessory(
268
+ mockPlatform as TuyaPlatform,
269
+ mockAccessory as PlatformAccessory,
270
+ device,
271
+ );
272
+
273
+ expect(accessory).toBeDefined();
274
+ });
275
+
276
+ test('handles lock category', () => {
277
+ const device = new TuyaDevice(
278
+ {
279
+ id: 'lock_1',
280
+ product_id: 'prod_lock',
281
+ category: 'jtmspro',
282
+ name: 'Smart Lock',
283
+ }
284
+ );
285
+
286
+ const accessory = AccessoryFactory.createAccessory(
287
+ mockPlatform as TuyaPlatform,
288
+ mockAccessory as PlatformAccessory,
289
+ device,
290
+ );
291
+
292
+ expect(accessory).toBeDefined();
293
+ });
294
+
295
+ test('handles security system category', () => {
296
+ const device = new TuyaDevice(
297
+ {
298
+ id: 'security_1',
299
+ product_id: 'prod_security',
300
+ category: 'zd_3in1',
301
+ name: 'Security Hub',
302
+ }
303
+ );
304
+
305
+ const accessory = AccessoryFactory.createAccessory(
306
+ mockPlatform as TuyaPlatform,
307
+ mockAccessory as PlatformAccessory,
308
+ device,
309
+ );
310
+
311
+ expect(accessory).toBeDefined();
312
+ });
313
+
314
+ test('handles camera category', () => {
315
+ const device = new TuyaDevice(
316
+ {
317
+ id: 'camera_1',
318
+ product_id: 'prod_camera',
319
+ category: 'sp',
320
+ name: 'Smart Camera',
321
+ }
322
+ );
323
+
324
+ const accessory = AccessoryFactory.createAccessory(
325
+ mockPlatform as TuyaPlatform,
326
+ mockAccessory as PlatformAccessory,
327
+ device,
328
+ );
329
+
330
+ expect(accessory).toBeDefined();
331
+ });
332
+ });
333
+
334
+ describe('device properties', () => {
335
+ test('handles device with standard schema', () => {
336
+ const device = new TuyaDevice(
337
+ {
338
+ id: 'device_with_schema',
339
+ product_id: 'prod_123',
340
+ category: 'kg',
341
+ name: 'Device',
342
+ status: [
343
+ { code: 'switch_1', value: true },
344
+ ],
345
+ schema: [
346
+ {
347
+ code: 'switch_1',
348
+ mode: TuyaDeviceSchemaMode.READ_WRITE,
349
+ type: TuyaDeviceSchemaType.Boolean,
350
+ property: {} as any,
351
+ },
352
+ ],
353
+ }
354
+ );
355
+
356
+ const accessory = AccessoryFactory.createAccessory(
357
+ mockPlatform as TuyaPlatform,
358
+ mockAccessory as PlatformAccessory,
359
+ device,
360
+ );
361
+
362
+ expect(accessory).toBeDefined();
363
+ });
364
+
365
+ test('handles device with minimal properties', () => {
366
+ const device = new TuyaDevice(
367
+ {
368
+ id: 'minimal_device',
369
+ product_id: 'prod_minimal',
370
+ category: 'kg',
371
+ name: 'Minimal',
372
+ }
373
+ );
374
+
375
+ const accessory = AccessoryFactory.createAccessory(
376
+ mockPlatform as TuyaPlatform,
377
+ mockAccessory as PlatformAccessory,
378
+ device,
379
+ );
380
+
381
+ expect(accessory).toBeDefined();
382
+ });
383
+
384
+ test('handles device with no product_id', () => {
385
+ const device = new TuyaDevice(
386
+ {
387
+ id: 'device_no_product',
388
+ product_id: '',
389
+ category: 'kg',
390
+ name: 'No Product ID',
391
+ }
392
+ );
393
+
394
+ const accessory = AccessoryFactory.createAccessory(
395
+ mockPlatform as TuyaPlatform,
396
+ mockAccessory as PlatformAccessory,
397
+ device,
398
+ );
399
+
400
+ expect(accessory).toBeDefined();
401
+ });
402
+ });
403
+
404
+ describe('IR Remote Control devices', () => {
405
+ test('handles IR remote control - generic', () => {
406
+ const device = new TuyaDevice(
407
+ {
408
+ id: 'ir_remote_1',
409
+ product_id: 'ir_prod',
410
+ category: 'ir',
411
+ name: 'IR Remote',
412
+ remote_keys: {
413
+ category_id: 99,
414
+ org_category_id: 99,
415
+ brand_id: 0,
416
+ remote_index: 0,
417
+ single_air: false,
418
+ duplicate_power: false,
419
+ key_list: [],
420
+ key_range: [],
421
+ },
422
+ }
423
+ );
424
+
425
+ const accessory = AccessoryFactory.createAccessory(
426
+ mockPlatform as TuyaPlatform,
427
+ mockAccessory as PlatformAccessory,
428
+ device,
429
+ );
430
+
431
+ expect(accessory).toBeDefined();
432
+ });
433
+
434
+ test('handles IR remote control - AC', () => {
435
+ const device = new TuyaDevice(
436
+ {
437
+ id: 'ir_ac_1',
438
+ product_id: 'ir_ac_prod',
439
+ category: 'ir',
440
+ name: 'IR AC Remote',
441
+ remote_keys: {
442
+ category_id: 5, // AC
443
+ org_category_id: 5,
444
+ brand_id: 0,
445
+ remote_index: 0,
446
+ single_air: false,
447
+ duplicate_power: false,
448
+ key_list: [],
449
+ key_range: [],
450
+ },
451
+ }
452
+ );
453
+
454
+ const accessory = AccessoryFactory.createAccessory(
455
+ mockPlatform as TuyaPlatform,
456
+ mockAccessory as PlatformAccessory,
457
+ device,
458
+ );
459
+
460
+ expect(accessory).toBeDefined();
461
+ });
462
+ });
463
+
464
+ describe('edge cases', () => {
465
+ test('handles device with special characters in name', () => {
466
+ const device = new TuyaDevice(
467
+ {
468
+ id: 'device_special',
469
+ product_id: 'prod_special',
470
+ category: 'kg',
471
+ name: 'Device "Test" & More!',
472
+ }
473
+ );
474
+
475
+ const accessory = AccessoryFactory.createAccessory(
476
+ mockPlatform as TuyaPlatform,
477
+ mockAccessory as PlatformAccessory,
478
+ device,
479
+ );
480
+
481
+ expect(accessory).toBeDefined();
482
+ });
483
+
484
+ test('handles device with empty name', () => {
485
+ const device = new TuyaDevice(
486
+ {
487
+ id: 'device_no_name',
488
+ product_id: 'prod_no_name',
489
+ category: 'kg',
490
+ name: '',
491
+ }
492
+ );
493
+
494
+ const accessory = AccessoryFactory.createAccessory(
495
+ mockPlatform as TuyaPlatform,
496
+ mockAccessory as PlatformAccessory,
497
+ device,
498
+ );
499
+
500
+ expect(accessory).toBeDefined();
501
+ });
502
+
503
+ test('handles device with unicode characters', () => {
504
+ const device = new TuyaDevice(
505
+ {
506
+ id: 'device_unicode',
507
+ product_id: 'prod_unicode',
508
+ category: 'kg',
509
+ name: '智能开关 中文设备',
510
+ }
511
+ );
512
+
513
+ const accessory = AccessoryFactory.createAccessory(
514
+ mockPlatform as TuyaPlatform,
515
+ mockAccessory as PlatformAccessory,
516
+ device,
517
+ );
518
+
519
+ expect(accessory).toBeDefined();
520
+ });
521
+ });
522
+ });