@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,833 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const TuyaDevice_1 = __importStar(require("../cloud/device/TuyaDevice"));
40
+ const TuyaOpenAPI_1 = __importDefault(require("../cloud/api/TuyaOpenAPI"));
41
+ const TuyaDeviceManager_1 = __importDefault(require("../cloud/device/TuyaDeviceManager"));
42
+ const TuyaDiscovery_1 = __importDefault(require("./TuyaDiscovery"));
43
+ const LocalDevice_1 = __importDefault(require("./LocalDevice"));
44
+ const Logger_1 = require("../shared/util/Logger");
45
+ const ConfigHash_1 = require("../shared/util/ConfigHash");
46
+ const ZigbeeGatewayDetection_1 = require("./ZigbeeGatewayDetection");
47
+ const DynamicChildDiscovery_1 = require("./DynamicChildDiscovery");
48
+ /**
49
+ * Default DP-to-code mapping used when the user hasn't supplied one.
50
+ * Keys are Tuya standard instruction codes; values are typical DP numbers.
51
+ * This covers the most common single-switch, dimmer and RGBTW combinations.
52
+ * Users can override via LocalDeviceConfig.dpMapping.
53
+ */
54
+ const DEFAULT_DP_MAP = {
55
+ switch_1: 1,
56
+ switch_2: 2,
57
+ switch_3: 3,
58
+ switch_4: 4,
59
+ bright_value: 2,
60
+ bright_value_v2: 22,
61
+ temp_value: 3,
62
+ temp_value_v2: 23,
63
+ work_mode: 2,
64
+ colour_data: 5,
65
+ colour_data_v2: 24,
66
+ switch_led: 1,
67
+ countdown_1: 9,
68
+ };
69
+ /** Inverse of DEFAULT_DP_MAP: dp number → code */
70
+ function buildDpToCodeMap(dpMapping) {
71
+ const inv = {};
72
+ for (const [code, dp] of Object.entries(dpMapping)) {
73
+ // Only map first code per dp (prefer explicit mappings)
74
+ if (!(dp in inv)) {
75
+ inv[dp] = code;
76
+ }
77
+ }
78
+ return inv;
79
+ }
80
+ /**
81
+ * LocalDeviceManager – extends TuyaDeviceManager so the existing platform code
82
+ * can treat it (and use its `devices` array / events) without any changes.
83
+ *
84
+ * Uses a dummy TuyaOpenAPI so the parent class initialises without connecting
85
+ * to the Tuya cloud. The MQTT client created by the parent is stopped
86
+ * immediately in the constructor.
87
+ *
88
+ * Local devices are represented as TuyaDevice objects with synthetic schemas
89
+ * derived from the user-supplied (or default) dpMapping. Status is tracked as
90
+ * `{code, value}` pairs, making them compatible with all existing accessory
91
+ * handlers.
92
+ */
93
+ class LocalDeviceManager extends TuyaDeviceManager_1.default {
94
+ // ──────────────────────────────────────────────────────────────────────────
95
+ constructor(localConfig, log, persistPath) {
96
+ // Create a minimal dummy API so the parent initialises without connecting
97
+ const dummyLog = new Logger_1.PrefixLogger(log, 'LocalDummy', false);
98
+ const dummyApi = new TuyaOpenAPI_1.default('', '', '', dummyLog);
99
+ super(dummyApi, false);
100
+ this.localDevices = [];
101
+ this.localConnections = new Map();
102
+ this.discoveryTimeout = null;
103
+ this.rediscoveryTimeout = null;
104
+ this.isDiscoveryActive = false;
105
+ this.discoveredInCurrentPhase = new Set();
106
+ // Maps deviceId → effective dpMapping (code → dp)
107
+ this.dpMaps = new Map();
108
+ // Maps deviceId → reverse dpMapping (dp → code)
109
+ this.reverseDpMaps = new Map();
110
+ // Maps discovered gwId → IP (kept even after TuyaDevice created)
111
+ this.discoveredIPs = new Map();
112
+ // Maps discovered gwId → version
113
+ this.discoveredVersions = new Map();
114
+ // Tracks which devices had config changes
115
+ this.configChanged = new Map();
116
+ // ── Zigbee gateway/child support ──────────────────────────────────────────
117
+ /** Parent gateway ID → GatewayRelationship (derived from config at init). */
118
+ this.gatewayRelationships = new Map();
119
+ /** Parent gateway device ID → its active LocalDevice connection. */
120
+ this.gatewayConnections = new Map();
121
+ // Stop the MQ that the parent started
122
+ this.mq.stop();
123
+ this.parentLog = log;
124
+ this.log = new Logger_1.PrefixLogger(log, 'LocalDeviceManager', false);
125
+ this.config = localConfig;
126
+ this.discovery = new TuyaDiscovery_1.default(log);
127
+ this.configHash = new ConfigHash_1.ConfigHash(persistPath || '', 'tuya-local-configs');
128
+ }
129
+ /**
130
+ * Initialise discovery and register manually configured devices.
131
+ * Returns immediately; discovery events fire asynchronously.
132
+ */
133
+ async initLocalDevices() {
134
+ // ── Zigbee: detect parent-child relationships before registering devices ──
135
+ if (this.config.devices && this.config.devices.length > 0) {
136
+ try {
137
+ this.gatewayRelationships = ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.detectFromDevices(this.config.devices);
138
+ if (this.gatewayRelationships.size > 0) {
139
+ for (const [parentId, rel] of this.gatewayRelationships) {
140
+ this.log.info(`Zigbee gateway detected: ${parentId} has ${rel.children.length} sub-device(s): ` +
141
+ rel.children.map(c => `${c.name} (CID=${c.cid})`).join(', '));
142
+ }
143
+ }
144
+ }
145
+ catch (err) {
146
+ const msg = err instanceof Error ? err.message : String(err);
147
+ this.log.error(`Zigbee configuration error: ${msg}`);
148
+ // Continue without Zigbee – non-fatal for non-Zigbee devices
149
+ }
150
+ }
151
+ // Register manual device entries first (non-child devices only at this stage)
152
+ if (this.config.devices) {
153
+ for (const cfg of this.config.devices) {
154
+ // Skip Zigbee children here – they are set up after their parent connects
155
+ if (ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isChild(cfg)) {
156
+ continue;
157
+ }
158
+ this._registerDeviceConfig(cfg);
159
+ }
160
+ // Validate devices have keys if auto-discovery is disabled
161
+ if (this.config.autoDiscoverDevices === false) {
162
+ for (const cfg of this.config.devices) {
163
+ if (!cfg.tuyaKey && !ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isChild(cfg)) {
164
+ const deviceName = cfg.name || cfg.tuyaDeviceId;
165
+ this.log.warn(`Local device "${deviceName}" (${cfg.tuyaDeviceId}) is missing tuyaKey ` +
166
+ 'and auto-discovery is disabled. This device will not be usable.');
167
+ }
168
+ }
169
+ }
170
+ }
171
+ // Start UDP discovery unless explicitly disabled
172
+ if (this.config.autoDiscoverDevices !== false) {
173
+ this.discovery.on('discover', (result) => {
174
+ this._onDiscovered(result);
175
+ });
176
+ this._startDiscoveryPhase();
177
+ // Schedule periodic rediscovery to handle devices that go offline and return
178
+ // (e.g., after WiFi reset or IP change)
179
+ const rediscoverIntervalSec = this.config.rediscoverInterval ?? 900; // Default 15 minutes
180
+ if (rediscoverIntervalSec > 0) {
181
+ this.log.info(`Scheduling periodic device rediscovery every ${rediscoverIntervalSec} seconds`);
182
+ this._schedulePeriodicRediscovery(rediscoverIntervalSec);
183
+ }
184
+ }
185
+ }
186
+ /** Stop discovery and disconnect all local TCP connections. */
187
+ stopLocalDevices() {
188
+ if (this.discoveryTimeout) {
189
+ clearTimeout(this.discoveryTimeout);
190
+ this.discoveryTimeout = null;
191
+ }
192
+ if (this.rediscoveryTimeout) {
193
+ clearTimeout(this.rediscoveryTimeout);
194
+ this.rediscoveryTimeout = null;
195
+ }
196
+ this.discovery.stop();
197
+ this.isDiscoveryActive = false;
198
+ for (const ld of this.localConnections.values()) {
199
+ ld.disconnect();
200
+ }
201
+ this.localConnections.clear();
202
+ // Gateway connections are separate from localConnections
203
+ for (const gw of this.gatewayConnections.values()) {
204
+ gw.disconnect();
205
+ }
206
+ this.gatewayConnections.clear();
207
+ }
208
+ /** Returns ALL devices (both local shadow entries from this manager). */
209
+ getDevice(deviceID) {
210
+ return this.localDevices.find(d => d.id === deviceID);
211
+ }
212
+ /**
213
+ * Send commands to a local device.
214
+ * `commands` is the same [{code, value}] format used by the cloud API so
215
+ * all existing accessory handlers work without modification.
216
+ */
217
+ async sendCommands(deviceID, commands) {
218
+ const dpMap = this.dpMaps.get(deviceID);
219
+ if (!dpMap) {
220
+ this.log.warn(`No dpMapping for local device ${deviceID}`);
221
+ return;
222
+ }
223
+ const dps = {};
224
+ for (const { code, value } of commands) {
225
+ const dp = dpMap[code];
226
+ if (dp === undefined) {
227
+ this.log.warn(`Unknown code "${code}" for device ${deviceID}`);
228
+ continue;
229
+ }
230
+ dps[String(dp)] = value;
231
+ }
232
+ if (Object.keys(dps).length === 0) {
233
+ return;
234
+ }
235
+ // Log the command for user visibility
236
+ const device = this.localDevices.find(d => d.id === deviceID);
237
+ const deviceName = device?.name || deviceID;
238
+ const commandStr = commands.map(c => `${c.code}=${c.value}`).join(', ');
239
+ this.log.info(`[${deviceName}] Sending command (local): ${commandStr}`);
240
+ // ── Zigbee child path ────────────────────────────────────────────────
241
+ const childConn = this.localConnections.get(deviceID);
242
+ if (childConn?.parentDevice) {
243
+ // Already set up as a Zigbee child – LocalDevice.update() delegates to parent
244
+ childConn.update(dps);
245
+ return true;
246
+ }
247
+ // ─────────────────────────────────────────────────────────────────────
248
+ let conn = this.localConnections.get(deviceID);
249
+ if (!conn) {
250
+ conn = this._createConnection(deviceID);
251
+ if (!conn) {
252
+ return;
253
+ }
254
+ }
255
+ conn.update(dps);
256
+ return true;
257
+ }
258
+ // ── Private helpers ────────────────────────────────────────────────────────
259
+ /**
260
+ * Start a discovery phase (e.g. initial startup or periodic rediscovery).
261
+ * Runs discovery for discoverTimeout seconds, then stops.
262
+ */
263
+ _startDiscoveryPhase() {
264
+ if (this.isDiscoveryActive) {
265
+ return; // Discovery already running
266
+ }
267
+ this.isDiscoveryActive = true;
268
+ this.discoveredInCurrentPhase.clear();
269
+ this.log.info('Starting discovery phase');
270
+ this.discovery.start();
271
+ // Stop after configurable timeout (config is in seconds, default 60s)
272
+ // Convert to milliseconds
273
+ const timeoutMs = (this.config.discoverTimeout ?? 60) * 1000;
274
+ if (this.discoveryTimeout) {
275
+ clearTimeout(this.discoveryTimeout);
276
+ }
277
+ this.discoveryTimeout = setTimeout(() => {
278
+ this.isDiscoveryActive = false;
279
+ this.discovery.stop();
280
+ // Report discovery results for clarity
281
+ if (this.discoveredInCurrentPhase.size > 0) {
282
+ this.log.info(`Ended discovery phase – rediscovered ${this.discoveredInCurrentPhase.size} device(s)`);
283
+ }
284
+ else {
285
+ // No devices found; they may be using cached IPs from previous discovery
286
+ const knownDevices = this.localDevices.filter(d => d.localKey);
287
+ if (knownDevices.length > 0) {
288
+ this.log.info(`Ended discovery phase – no new devices found (${knownDevices.length} configured device(s) using cached IP)`);
289
+ }
290
+ else {
291
+ this.log.info('Ended discovery phase – no devices found');
292
+ }
293
+ }
294
+ this.discoveryTimeout = null;
295
+ }, timeoutMs);
296
+ this.discoveryTimeout.unref();
297
+ }
298
+ /**
299
+ * Schedule periodic rediscovery to detect devices that go offline and return.
300
+ * This helps handle WiFi resets, IP changes, and network reconnections.
301
+ */
302
+ _schedulePeriodicRediscovery(intervalSec) {
303
+ if (this.rediscoveryTimeout) {
304
+ clearTimeout(this.rediscoveryTimeout);
305
+ }
306
+ const intervalMs = intervalSec * 1000;
307
+ this.rediscoveryTimeout = setTimeout(() => {
308
+ this.log.debug('Triggering periodic device rediscovery');
309
+ this._startDiscoveryPhase();
310
+ // Reschedule for the next cycle
311
+ this._schedulePeriodicRediscovery(intervalSec);
312
+ }, intervalMs);
313
+ this.rediscoveryTimeout.unref();
314
+ }
315
+ _registerDeviceConfig(cfg) {
316
+ const effectiveMap = Object.assign({}, DEFAULT_DP_MAP, cfg.dpMapping ?? {});
317
+ this.dpMaps.set(cfg.tuyaDeviceId, effectiveMap);
318
+ this.reverseDpMaps.set(cfg.tuyaDeviceId, buildDpToCodeMap(effectiveMap));
319
+ const existing = this.localDevices.find(d => d.id === cfg.tuyaDeviceId);
320
+ // Check if config has changed since last run
321
+ // Hash the config fields that affect device behavior
322
+ const configToHash = {
323
+ tuyaDeviceId: cfg.tuyaDeviceId,
324
+ name: cfg.name,
325
+ tuyaKey: cfg.tuyaKey,
326
+ dpMapping: cfg.dpMapping,
327
+ switchCount: cfg.switchCount,
328
+ outletCount: cfg.outletCount,
329
+ protocolVersion: cfg.protocolVersion,
330
+ category: cfg.category,
331
+ };
332
+ const { changed: configChanged } = this.configHash.hasConfigChanged(cfg.tuyaDeviceId, configToHash);
333
+ this.configChanged.set(cfg.tuyaDeviceId, configChanged);
334
+ if (configChanged && existing) {
335
+ this.log.info(`Device ${cfg.tuyaDeviceId}: config changed, rebuilding schema`);
336
+ }
337
+ if (!existing) {
338
+ const device = this._buildTuyaDevice(cfg, configChanged);
339
+ this.localDevices.push(device);
340
+ this.devices.push(device); // keep parent's devices array in sync
341
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_ADD, device);
342
+ }
343
+ }
344
+ /**
345
+ * Set up auto-detection listener to count switches from device status updates.
346
+ * Called BEFORE conn.connect() to avoid race conditions.
347
+ * Uses time-based accumulation to capture all switches across multiple updates.
348
+ */
349
+ _setupAutoDetectionListener(deviceId, device, conn) {
350
+ const cfg = this.config.devices?.find(d => d.tuyaDeviceId === deviceId);
351
+ if (!cfg || (cfg.switchCount || cfg.outletCount)) {
352
+ return; // Already has explicit count
353
+ }
354
+ this.log.debug(`[AutoDetect] Setting up auto-detection listener for ${deviceId}`);
355
+ const detectedSwitches = new Set();
356
+ let detectionTimer = null;
357
+ const onStatusChange = (changes) => {
358
+ this.log.debug(`[AutoDetect] Status change received for ${deviceId}: ${JSON.stringify(Object.keys(changes))}`);
359
+ // Collect switch numbers from incoming changes
360
+ const dpMap = this.dpMaps.get(deviceId) ?? DEFAULT_DP_MAP;
361
+ const reverseMap = buildDpToCodeMap(dpMap);
362
+ for (const dpStr of Object.keys(changes)) {
363
+ const dpNum = parseInt(dpStr, 10);
364
+ const code = reverseMap[dpNum];
365
+ const switchMatch = code?.match(/^switch_(\d+)$/);
366
+ if (switchMatch) {
367
+ const switchNum = parseInt(switchMatch[1], 10);
368
+ detectedSwitches.add(switchNum);
369
+ this.log.debug(`[AutoDetect] Found switch_${switchNum} (dp=${dpNum}, code=${code})`);
370
+ }
371
+ }
372
+ // Restart the detection timer
373
+ if (detectionTimer) {
374
+ clearTimeout(detectionTimer);
375
+ }
376
+ detectionTimer = setTimeout(() => {
377
+ // After 2 seconds of no new switches, finalize auto-detection
378
+ if (detectedSwitches.size > 0) {
379
+ const detectedCount = Math.max(...Array.from(detectedSwitches));
380
+ const switchList = Array.from(detectedSwitches).sort((a, b) => a - b).join(', ');
381
+ this.log.info(`[AutoDetect] Auto-detected ${detectedCount} switch(es) for "${cfg.name}" (${deviceId}): ` +
382
+ `switches=[${switchList}]`);
383
+ // Update device schema with detected switch count
384
+ device.schema = this._buildSyntheticSchema(this.dpMaps.get(deviceId) ?? DEFAULT_DP_MAP, detectedCount);
385
+ device.switchCount = detectedCount;
386
+ // Auto-detection is now complete
387
+ device.isAutoDetecting = false;
388
+ // Recalculate config hash since the schema has changed
389
+ // This ensures SwitchAccessory knows to rebuild services
390
+ const updatedConfigToHash = {
391
+ tuyaDeviceId: cfg.tuyaDeviceId,
392
+ name: cfg.name,
393
+ tuyaKey: cfg.tuyaKey,
394
+ dpMapping: cfg.dpMapping,
395
+ switchCount: detectedCount,
396
+ outletCount: cfg.outletCount,
397
+ protocolVersion: cfg.protocolVersion,
398
+ category: cfg.category,
399
+ };
400
+ const { changed: schemaChanged } = this.configHash.hasConfigChanged(deviceId, updatedConfigToHash);
401
+ if (schemaChanged) {
402
+ this.log.debug(`[AutoDetect] Schema changed after auto-detection, marking config as changed`);
403
+ device.configChanged = true;
404
+ this.configChanged.set(deviceId, true);
405
+ }
406
+ // Notify the platform so configureServices() is re-run and switch_2+ services are added.
407
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, device, { schemaUpdated: true });
408
+ }
409
+ else {
410
+ this.log.debug(`[AutoDetect] No switches detected after 2 seconds for ${deviceId}`);
411
+ }
412
+ // Remove listener after detection is complete
413
+ conn.removeListener('change', onStatusChange);
414
+ }, 2000);
415
+ };
416
+ // Listen for status changes (multiple events to capture all switches)
417
+ conn.on('change', onStatusChange);
418
+ this.log.debug(`[AutoDetect] Listener attached to connection for ${deviceId}`);
419
+ }
420
+ _onDiscovered(result) {
421
+ this.discoveredIPs.set(result.id, result.ip);
422
+ this.discoveredVersions.set(result.id, result.version);
423
+ this.discoveredInCurrentPhase.add(result.id);
424
+ // If we already have a TuyaDevice for this ID (from manual config), update its IP
425
+ const existing = this.localDevices.find(d => d.id === result.id);
426
+ if (existing) {
427
+ this.log.info(`Device ${result.id} (v${result.version}) rediscovered @ ${result.ip} ` +
428
+ '– updating connection');
429
+ existing.ip = result.ip;
430
+ // Update protocol version if not user-configured
431
+ const typed = existing;
432
+ if (!typed.cfgVersion) {
433
+ typed.localVersion = result.version;
434
+ }
435
+ // If a connection exists, reconnect with updated IP/version
436
+ const conn = this.localConnections.get(result.id);
437
+ if (conn) {
438
+ conn.disconnect();
439
+ this.localConnections.delete(result.id);
440
+ }
441
+ // Try to establish a connection if the device has a local key
442
+ const localKey = existing.localKey;
443
+ if (localKey) {
444
+ this._createConnection(result.id);
445
+ }
446
+ return;
447
+ }
448
+ // New device discovered via UDP – create a placeholder TuyaDevice
449
+ // (it won't be controllable until the user provides a tuyaKey via config)
450
+ const device = this._buildDiscoveredDevice(result);
451
+ this.localDevices.push(device);
452
+ this.devices.push(device);
453
+ const productInfo = result.productKey ? ` productKey=${result.productKey}` : '';
454
+ this.log.info(`Local device discovered: ${result.id} @ ${result.ip} (v${result.version}${productInfo}) ` +
455
+ '– add to config with tuyaKey to control');
456
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_ADD, device);
457
+ }
458
+ _buildTuyaDevice(cfg, configChanged = false) {
459
+ const device = new TuyaDevice_1.default({});
460
+ device.id = cfg.tuyaDeviceId;
461
+ device.uuid = cfg.tuyaDeviceId;
462
+ device.name = cfg.name ?? `LocalDevice-${cfg.tuyaDeviceId.slice(0, 8)}`;
463
+ device.category = cfg.category ?? 'unknown';
464
+ device.ip = cfg.ip ?? (this.discoveredIPs.get(cfg.tuyaDeviceId) ?? '');
465
+ device.online = false;
466
+ device.product_id = '';
467
+ device.product_name = 'Local Device';
468
+ device.model = '';
469
+ device.icon = '';
470
+ device.owner_id = '';
471
+ device.lat = '';
472
+ device.lon = '';
473
+ device.time_zone = '';
474
+ device.create_time = 0;
475
+ device.active_time = 0;
476
+ device.update_time = 0;
477
+ device.status = [];
478
+ const switchCount = cfg.switchCount ?? cfg.outletCount ?? 1;
479
+ const autoDetectNote = !cfg.switchCount && !cfg.outletCount && cfg.tuyaKey ? ' (auto-detecting on connect)' : '';
480
+ const isAutoDetecting = !!(cfg.tuyaKey && !cfg.switchCount && !cfg.outletCount);
481
+ this.log.info(`Building synthetic schema for ${cfg.name} with switchCount=${switchCount}${autoDetectNote}`);
482
+ device.schema = this._buildSyntheticSchema(Object.assign({}, DEFAULT_DP_MAP, cfg.dpMapping ?? {}), switchCount);
483
+ this.log.info(`Device "${cfg.name}" schema includes ${device.schema.length} codes: ${device.schema.map(s => s.code).join(', ')}`);
484
+ // Store local-specific metadata directly on the device object for later use
485
+ device.localKey = cfg.tuyaKey;
486
+ device.localIp = cfg.ip;
487
+ device.localVersion =
488
+ cfg.protocolVersion ?? this.discoveredVersions.get(cfg.tuyaDeviceId);
489
+ // Track whether version was user-configured (prevents overwrite on re-discovery)
490
+ device.cfgVersion = cfg.protocolVersion;
491
+ device.isLocal = true;
492
+ device.switchCount = switchCount;
493
+ device.isAutoDetecting = isAutoDetecting;
494
+ // Track whether config changed (used by SwitchAccessory to decide whether to remove cached extras)
495
+ device.configChanged = configChanged;
496
+ return device;
497
+ }
498
+ _buildDiscoveredDevice(result) {
499
+ const device = new TuyaDevice_1.default({});
500
+ device.id = result.id;
501
+ device.uuid = result.id;
502
+ device.name = `Local-${result.id.slice(0, 8)}`;
503
+ device.category = 'unknown';
504
+ device.ip = result.ip;
505
+ device.online = false;
506
+ device.product_id = result.productKey ?? '';
507
+ device.product_name = 'Local Device';
508
+ device.model = '';
509
+ device.icon = '';
510
+ device.owner_id = '';
511
+ device.lat = '';
512
+ device.lon = '';
513
+ device.time_zone = '';
514
+ device.create_time = 0;
515
+ device.active_time = 0;
516
+ device.update_time = 0;
517
+ device.status = [];
518
+ device.schema = [];
519
+ device.isLocal = true;
520
+ device.localVersion = result.version;
521
+ return device;
522
+ }
523
+ _buildSyntheticSchema(dpMapping, switchCount = 1) {
524
+ // If switchCount is limited, filter switch-related entries to only the needed ones
525
+ let filteredMapping = dpMapping;
526
+ if (switchCount > 0) {
527
+ // Only include switch_1 through switch_N where N = switchCount
528
+ // Also exclude other switch variants (like switch_led, switch_inching) if limited
529
+ filteredMapping = {};
530
+ const switchCodes = [];
531
+ for (const [code, dp] of Object.entries(dpMapping)) {
532
+ // Keep non-switch DPs (brightness, temp, color, etc.)
533
+ if (!code.startsWith('switch')) {
534
+ filteredMapping[code] = dp;
535
+ }
536
+ // For switches, only include switch_1..switch_N
537
+ const switchMatch = code.match(/^switch_(\d+)$/);
538
+ if (switchMatch) {
539
+ const switchNum = parseInt(switchMatch[1], 10);
540
+ if (switchNum <= switchCount) {
541
+ filteredMapping[code] = dp;
542
+ switchCodes.push(code);
543
+ }
544
+ }
545
+ }
546
+ if (switchCodes.length < Object.keys(dpMapping).filter(k => k.startsWith('switch')).length) {
547
+ this.log.debug(`Filtered switches: kept [${switchCodes.join(', ')}], excluded others`);
548
+ }
549
+ }
550
+ return Object.keys(filteredMapping).map(code => ({
551
+ code,
552
+ mode: TuyaDevice_1.TuyaDeviceSchemaMode.READ_WRITE,
553
+ type: TuyaDevice_1.TuyaDeviceSchemaType.Boolean, // best-effort default; handlers may override
554
+ property: {},
555
+ }));
556
+ }
557
+ _createConnection(deviceID) {
558
+ const device = this.localDevices.find(d => d.id === deviceID);
559
+ if (!device) {
560
+ return undefined;
561
+ }
562
+ const localKey = device.localKey;
563
+ if (!localKey) {
564
+ this.log.warn(`No tuyaKey configured for local device ${deviceID} — cannot connect.`);
565
+ return undefined;
566
+ }
567
+ const ip = device.ip || device.localIp;
568
+ if (!ip) {
569
+ this.log.warn(`No IP known for local device ${deviceID} — cannot connect.`);
570
+ return undefined;
571
+ }
572
+ const version = device.localVersion
573
+ ?? this.discoveredVersions.get(deviceID)
574
+ ?? '3.3';
575
+ const conn = new LocalDevice_1.default({
576
+ id: deviceID,
577
+ key: Buffer.from(localKey, 'utf8'),
578
+ ip,
579
+ version,
580
+ name: device.name,
581
+ }, this.parentLog);
582
+ conn.on('connect', () => {
583
+ device.online = true;
584
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, device, { online: true });
585
+ // ── Zigbee: when a gateway connects, set up its children ──
586
+ if (this.gatewayRelationships.has(deviceID)) {
587
+ this.gatewayConnections.set(deviceID, conn);
588
+ this._setupZigbeeChildren(deviceID, conn);
589
+ }
590
+ });
591
+ conn.on('disconnect', () => {
592
+ device.online = false;
593
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, device, { online: false });
594
+ });
595
+ conn.on('change', (changes) => {
596
+ const reverseMap = this.reverseDpMaps.get(deviceID) ?? {};
597
+ const statusUpdates = [];
598
+ for (const [dp, val] of Object.entries(changes)) {
599
+ const code = reverseMap[parseInt(dp, 10)] ?? dp;
600
+ // Update device.status array
601
+ const existing = device.status.find(s => s.code === code);
602
+ if (existing) {
603
+ existing.value = val;
604
+ }
605
+ else {
606
+ device.status.push({ code, value: val });
607
+ }
608
+ statusUpdates.push({ code, value: val });
609
+ }
610
+ if (statusUpdates.length > 0) {
611
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, device, statusUpdates);
612
+ }
613
+ });
614
+ conn.on('error', (err) => {
615
+ this.log.warn(`Error for local device ${deviceID}: ${err.message}`);
616
+ });
617
+ // Set up auto-detection listener BEFORE connecting to avoid race conditions
618
+ this._setupAutoDetectionListener(deviceID, device, conn);
619
+ conn.connect();
620
+ this.localConnections.set(deviceID, conn);
621
+ return conn;
622
+ }
623
+ /**
624
+ * After a Zigbee gateway connects, configure all its child sub-devices.
625
+ * Each child gets a lightweight LocalDevice whose update() call delegates
626
+ * to the parent, with the CID embedded in the payload.
627
+ *
628
+ * If a child connection already exists (e.g. after a gateway reconnect),
629
+ * re-link it to the parent and restore its connected state.
630
+ */
631
+ _setupZigbeeChildren(parentId, parentConn) {
632
+ const rel = this.gatewayRelationships.get(parentId);
633
+ if (!rel)
634
+ return;
635
+ for (const entry of rel.children) {
636
+ const existingConn = this.localConnections.get(entry.deviceId);
637
+ if (existingConn && existingConn.parentDevice) {
638
+ // Child connection already exists – re-bind to new parent connection
639
+ this._reconnectZigbeeChild(entry.deviceId, entry.cid, existingConn, parentConn);
640
+ }
641
+ else {
642
+ // New child – set up from scratch
643
+ this._setupZigbeeChild(entry.deviceId, entry.cid, parentConn);
644
+ }
645
+ }
646
+ }
647
+ /**
648
+ * Re-bind an existing Zigbee child connection to a newly reconnected parent gateway.
649
+ * Preserves the child's state and event listeners while updating the parent reference.
650
+ */
651
+ _reconnectZigbeeChild(childDeviceId, cid, childConn, newParentConn) {
652
+ const childTuyaDevice = this.localDevices.find(d => d.id === childDeviceId);
653
+ if (!childTuyaDevice) {
654
+ this.log.warn(`Zigbee child ${childDeviceId}: TuyaDevice not found during reconnect`);
655
+ return;
656
+ }
657
+ // Update parent reference and CID
658
+ childConn.parentDevice = newParentConn;
659
+ childConn.childId = cid;
660
+ newParentConn.children.set(cid, childConn);
661
+ // Mark child as connected (mirrors parent state)
662
+ childConn.connected = true;
663
+ this.log.info(`Zigbee child ${childDeviceId} (CID=${cid}): re-linked to parent on reconnect`);
664
+ // Notify that device is now online
665
+ childTuyaDevice.online = true;
666
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, childTuyaDevice, { online: true });
667
+ // Request fresh state from child via parent
668
+ newParentConn.queryStateChild(cid);
669
+ }
670
+ /**
671
+ * Set up a single Zigbee child device.
672
+ * Registers the child's TuyaDevice (if not already done), links it to the
673
+ * parent LocalDevice connection, and subscribes to status updates.
674
+ */
675
+ _setupZigbeeChild(childDeviceId, cid, parentConn) {
676
+ const cfg = this.config.devices?.find(d => d.tuyaDeviceId === childDeviceId);
677
+ if (!cfg) {
678
+ this.log.warn(`Zigbee child config not found for device ${childDeviceId}`);
679
+ return;
680
+ }
681
+ // Check if child has per-child overrides for DP mapping or category
682
+ const effectiveChildCfg = { ...cfg };
683
+ if (cfg.childDpMapping) {
684
+ effectiveChildCfg.dpMapping = cfg.childDpMapping;
685
+ this.log.debug(`Zigbee child ${childDeviceId}: using per-child DP mapping override`);
686
+ }
687
+ if (cfg.childCategory) {
688
+ effectiveChildCfg.category = cfg.childCategory;
689
+ this.log.debug(`Zigbee child ${childDeviceId}: using per-child category override`);
690
+ }
691
+ // Register TuyaDevice if not already present
692
+ if (!this.localDevices.find(d => d.id === childDeviceId)) {
693
+ this._registerDeviceConfig(effectiveChildCfg);
694
+ }
695
+ const childTuyaDevice = this.localDevices.find(d => d.id === childDeviceId);
696
+ if (!childTuyaDevice) {
697
+ this.log.warn(`Could not build TuyaDevice for Zigbee child ${childDeviceId}`);
698
+ return;
699
+ }
700
+ // Create a lightweight LocalDevice for the child (no socket of its own)
701
+ const childConn = new LocalDevice_1.default({
702
+ id: childDeviceId,
703
+ key: parentConn['context'].key,
704
+ ip: parentConn['context'].ip,
705
+ version: parentConn['context'].version,
706
+ name: cfg.name ?? childDeviceId,
707
+ }, this.parentLog);
708
+ // Link parent ↔ child
709
+ childConn.parentDevice = parentConn;
710
+ childConn.childId = cid;
711
+ parentConn.children.set(cid, childConn);
712
+ // Mark child as connected immediately (it's online whenever parent is)
713
+ childConn.connected = true;
714
+ childConn.on('disconnect', () => {
715
+ childTuyaDevice.online = false;
716
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, childTuyaDevice, { online: false });
717
+ });
718
+ childConn.on('change', (changes) => {
719
+ const reverseMap = this.reverseDpMaps.get(childDeviceId) ?? {};
720
+ const statusUpdates = [];
721
+ for (const [dp, val] of Object.entries(changes)) {
722
+ const code = reverseMap[parseInt(dp, 10)] ?? dp;
723
+ const existing = childTuyaDevice.status.find(s => s.code === code);
724
+ if (existing) {
725
+ existing.value = val;
726
+ }
727
+ else {
728
+ childTuyaDevice.status.push({ code, value: val });
729
+ }
730
+ statusUpdates.push({ code, value: val });
731
+ }
732
+ if (statusUpdates.length > 0) {
733
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, childTuyaDevice, statusUpdates);
734
+ }
735
+ });
736
+ // Also handle the 'connect' event that parent propagation may emit
737
+ parentConn.on('connect', () => {
738
+ if (!childConn.connected) {
739
+ childConn.connected = true;
740
+ childTuyaDevice.online = true;
741
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, childTuyaDevice, { online: true });
742
+ // Refresh child state after gateway reconnects
743
+ parentConn.queryStateChild(cid);
744
+ }
745
+ });
746
+ this.localConnections.set(childDeviceId, childConn);
747
+ childTuyaDevice.online = true;
748
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, childTuyaDevice, { online: true });
749
+ // Request child's current state immediately
750
+ parentConn.queryStateChild(cid);
751
+ this.log.info(`Zigbee child "${cfg.name ?? childDeviceId}" (${childDeviceId}, CID=${cid}) ` +
752
+ `connected via gateway ${parentConn['context'].id}`);
753
+ }
754
+ /**
755
+ * Discover and register Zigbee children from Tuya Cloud device list.
756
+ * If cloud API provides gateway_id field, we can auto-detect parent-child relationships
757
+ * and create child configs without manual config.
758
+ *
759
+ * @param cloudDeviceList - Optional array of cloud devices with id and gateway_id fields
760
+ */
761
+ discoverChildrenFromCloud(cloudDeviceList) {
762
+ if (!cloudDeviceList || cloudDeviceList.length === 0) {
763
+ return;
764
+ }
765
+ // Find parent-child relationships from cloud metadata
766
+ const gatewayToChildren = (0, DynamicChildDiscovery_1.discoverFromCloudList)(cloudDeviceList);
767
+ if (gatewayToChildren.size === 0) {
768
+ return;
769
+ }
770
+ // For each discovered parent-child relationship, ensure child config exists
771
+ for (const [gatewayDeviceId, childDeviceIds] of gatewayToChildren) {
772
+ const parentCfg = this.config.devices?.find(d => d.tuyaDeviceId === gatewayDeviceId);
773
+ if (!parentCfg) {
774
+ this.log.debug(`Cloud parent ${gatewayDeviceId}: no local config found, skipping auto-discovery`);
775
+ continue;
776
+ }
777
+ // Only auto-discover for gateways that support it
778
+ if (!(0, DynamicChildDiscovery_1.supportsChildDiscovery)(parentCfg)) {
779
+ continue;
780
+ }
781
+ for (const childDeviceId of childDeviceIds) {
782
+ // Check if child config already exists
783
+ const existingChildCfg = this.config.devices?.find(d => d.tuyaDeviceId === childDeviceId);
784
+ if (existingChildCfg) {
785
+ continue; // Already configured manually
786
+ }
787
+ // Auto-create child config from cloud metadata
788
+ // Use a placeholder CID (will be replaced by LAN discovery when gateway connects)
789
+ const placeholderCid = `00000000${childDeviceId.substring(0, 8).toLowerCase().padEnd(8, '0')}`;
790
+ const autoChildCfg = (0, DynamicChildDiscovery_1.buildDiscoveredChildConfig)(gatewayDeviceId, placeholderCid, undefined, parentCfg.ip, parentCfg.tuyaKey);
791
+ autoChildCfg.zigbeeChildId = undefined; // Will be discovered dynamically
792
+ if (!this.config.devices) {
793
+ this.config.devices = [];
794
+ }
795
+ this.config.devices.push(autoChildCfg);
796
+ this.log.info(`Zigbee child auto-discovery: registered ${childDeviceId} as child of ${gatewayDeviceId} ` +
797
+ '(from cloud metadata)');
798
+ }
799
+ }
800
+ // Re-detect Zigbee relationships now that we've added auto-discovered children
801
+ if (this.config.devices && this.config.devices.length > 0) {
802
+ try {
803
+ this.gatewayRelationships = ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.detectFromDevices(this.config.devices);
804
+ }
805
+ catch (err) {
806
+ const msg = err instanceof Error ? err.message : String(err);
807
+ this.log.warn(`Re-detection of Zigbee relationships after auto-discovery failed: ${msg}`);
808
+ }
809
+ }
810
+ }
811
+ /** Connect all configured local devices eagerly (called after init). */
812
+ connectAllDevices() {
813
+ for (const device of this.localDevices) {
814
+ const localKey = device.localKey;
815
+ if (!localKey) {
816
+ continue; // Skip if no local key configured
817
+ }
818
+ // Only connect if device has an explicit IP in config
819
+ // Devices relying on auto-discovery will be connected later after discovery completes
820
+ const localIp = device.localIp;
821
+ if (!localIp) {
822
+ this.log.debug(`Skipping initial connection for ${device.name} (${device.id}): ` +
823
+ 'waiting for auto-discovery to find IP address');
824
+ continue;
825
+ }
826
+ if (device.ip) {
827
+ this._createConnection(device.id);
828
+ }
829
+ }
830
+ }
831
+ }
832
+ exports.default = LocalDeviceManager;
833
+ //# sourceMappingURL=LocalDeviceManager.js.map