@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,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProtocolV31V32 = void 0;
4
+ const ProtocolUtilities_1 = require("./ProtocolUtilities");
5
+ /** Version header bytes prepended to v3.1 plaintext payloads */
6
+ const VERSION_HEADER_31 = Buffer.from('3.1' + '\x00'.repeat(12), 'latin1');
7
+ class ProtocolV31V32 {
8
+ constructor(version = '3.1') {
9
+ this.isV31 = version === '3.1';
10
+ }
11
+ encodeFrame(cmd, data, seqNo, _sessionKey, deviceKey) {
12
+ let payload;
13
+ if (this.isV31) {
14
+ // v3.1 CONTROL cmd: prefix + MD5 hash + base64-encrypted data
15
+ // For the persistent-socket model used here we just send plaintext JSON
16
+ // (devices accept it; encryption is optional for local LAN control on v3.1)
17
+ payload = data;
18
+ }
19
+ else {
20
+ // v3.2: AES-128-ECB encrypt
21
+ payload = deviceKey ? (0, ProtocolUtilities_1.encryptECB)(data, deviceKey) : data;
22
+ // Prepend 3.x version header (15 bytes) for non-heartbeat payloads
23
+ if (payload.length > 0) {
24
+ const versionHeader = Buffer.from('3.2' + '\x00'.repeat(12), 'latin1');
25
+ payload = Buffer.concat([versionHeader, payload]);
26
+ }
27
+ }
28
+ return (0, ProtocolUtilities_1.packMessage55AA)(seqNo, cmd, payload);
29
+ }
30
+ isFrameComplete(buffer) {
31
+ return (0, ProtocolUtilities_1.isFrameComplete)(buffer);
32
+ }
33
+ extractFrame(buffer) {
34
+ return (0, ProtocolUtilities_1.extractFrame)(buffer);
35
+ }
36
+ decodeFrame(frame, deviceKey) {
37
+ const msg = (0, ProtocolUtilities_1.unpackMessage55AA)(frame, undefined, false);
38
+ if (!msg)
39
+ return null;
40
+ if (!msg.hmacOk)
41
+ return null;
42
+ let payload = msg.payload;
43
+ // Strip v3.x version header if present (first 15 bytes: e.g. "3.1\x00…")
44
+ if (payload.length >= 15 && payload[0] === 0x33 /* '3' */) {
45
+ payload = payload.subarray(15);
46
+ }
47
+ // Decrypt v3.2 ECB payload
48
+ if (!this.isV31 && payload.length > 0) {
49
+ try {
50
+ payload = (0, ProtocolUtilities_1.decryptECB)(payload, deviceKey);
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ return { cmd: msg.cmd, payload };
57
+ }
58
+ }
59
+ exports.ProtocolV31V32 = ProtocolV31V32;
60
+ //# sourceMappingURL=ProtocolV31V32.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProtocolV31V32.js","sourceRoot":"","sources":["../../../src/local/protocol/ProtocolV31V32.ts"],"names":[],"mappings":";;;AAgBA,2DAI6B;AAE7B,gEAAgE;AAChE,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE3E,MAAa,cAAc;IAGzB,YAAY,UAAyB,KAAK;QACxC,IAAI,CAAC,KAAK,GAAG,OAAO,KAAK,KAAK,CAAC;IACjC,CAAC;IAED,WAAW,CAAC,GAAW,EAAE,IAAY,EAAE,KAAa,EAAE,WAAoB,EAAE,SAAkB;QAC5F,IAAI,OAAe,CAAC;QAEpB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,8DAA8D;YAC9D,wEAAwE;YACxE,4EAA4E;YAC5E,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,4BAA4B;YAC5B,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAA,8BAAU,EAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACzD,mEAAmE;YACnE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACvE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;QAED,OAAO,IAAA,mCAAe,EAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,eAAe,CAAC,MAAc;QAC5B,OAAO,IAAA,mCAAe,EAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,OAAO,IAAA,gCAAY,EAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,WAAW,CAAC,KAAa,EAAE,SAAiB;QAC1C,MAAM,GAAG,GAAG,IAAA,qCAAiB,EAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAE7B,IAAI,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAE1B,yEAAyE;QACzE,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1D,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACjC,CAAC;QAED,2BAA2B;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,OAAO,GAAG,IAAA,8BAAU,EAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YAC3C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;CACF;AA3DD,wCA2DC"}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProtocolV33 = void 0;
4
+ const ProtocolUtilities_1 = require("./ProtocolUtilities");
5
+ /** 15-byte header prepended to v3.3 encrypted payloads */
6
+ const VERSION_HEADER_33 = Buffer.from('3.3' + '\x00'.repeat(12), 'latin1');
7
+ class ProtocolV33 {
8
+ encodeFrame(cmd, data, seqNo, _sessionKey, deviceKey) {
9
+ let payload = data;
10
+ if (deviceKey) {
11
+ payload = (0, ProtocolUtilities_1.encryptECB)(data, deviceKey);
12
+ }
13
+ // Prepend version header for commands that need it
14
+ if (!ProtocolUtilities_1.NO_VERSION_HEADER_CMDS.has(cmd) && payload.length > 0) {
15
+ payload = Buffer.concat([VERSION_HEADER_33, payload]);
16
+ }
17
+ // v3.3 uses CRC32 (no hmacKey)
18
+ return (0, ProtocolUtilities_1.packMessage55AA)(seqNo, cmd, payload);
19
+ }
20
+ isFrameComplete(buffer) {
21
+ return (0, ProtocolUtilities_1.isFrameComplete)(buffer);
22
+ }
23
+ extractFrame(buffer) {
24
+ return (0, ProtocolUtilities_1.extractFrame)(buffer);
25
+ }
26
+ decodeFrame(frame, deviceKey) {
27
+ const msg = (0, ProtocolUtilities_1.unpackMessage55AA)(frame, undefined, false);
28
+ if (!msg)
29
+ return null;
30
+ if (!msg.hmacOk)
31
+ return null;
32
+ let payload = msg.payload;
33
+ // Strip version header ("3.3\x00…", 15 bytes)
34
+ if (payload.length >= 15 && payload[0] === 0x33 /* '3' */) {
35
+ payload = payload.subarray(15);
36
+ }
37
+ if (payload.length === 0) {
38
+ // Heartbeat or empty ACK — return as-is
39
+ return { cmd: msg.cmd, payload };
40
+ }
41
+ // Decrypt ECB
42
+ try {
43
+ payload = (0, ProtocolUtilities_1.decryptECB)(payload, deviceKey);
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ return { cmd: msg.cmd, payload };
49
+ }
50
+ }
51
+ exports.ProtocolV33 = ProtocolV33;
52
+ //# sourceMappingURL=ProtocolV33.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProtocolV33.js","sourceRoot":"","sources":["../../../src/local/protocol/ProtocolV33.ts"],"names":[],"mappings":";;;AASA,2DAK6B;AAE7B,0DAA0D;AAC1D,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE3E,MAAa,WAAW;IACtB,WAAW,CAAC,GAAW,EAAE,IAAY,EAAE,KAAa,EAAE,WAAoB,EAAE,SAAkB;QAC5F,IAAI,OAAO,GAAG,IAAI,CAAC;QAEnB,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,GAAG,IAAA,8BAAU,EAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACxC,CAAC;QAED,mDAAmD;QACnD,IAAI,CAAC,0CAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3D,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,CAAC;QAED,+BAA+B;QAC/B,OAAO,IAAA,mCAAe,EAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,eAAe,CAAC,MAAc;QAC5B,OAAO,IAAA,mCAAe,EAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,OAAO,IAAA,gCAAY,EAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,WAAW,CAAC,KAAa,EAAE,SAAiB;QAC1C,MAAM,GAAG,GAAG,IAAA,qCAAiB,EAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAE7B,IAAI,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAE1B,8CAA8C;QAC9C,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1D,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,wCAAwC;YACxC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;QACnC,CAAC;QAED,cAAc;QACd,IAAI,CAAC;YACH,OAAO,GAAG,IAAA,8BAAU,EAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;CACF;AAnDD,kCAmDC"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProtocolV34 = void 0;
4
+ const ProtocolUtilities_1 = require("./ProtocolUtilities");
5
+ /** 15-byte version header prepended to v3.4 payloads before ECB encryption */
6
+ const VERSION_HEADER_34 = Buffer.from('3.4' + '\x00'.repeat(12), 'latin1');
7
+ class ProtocolV34 {
8
+ encodeFrame(cmd, data, seqNo, sessionKey, deviceKey) {
9
+ let payload = data;
10
+ if (sessionKey) {
11
+ // Post-exchange: encrypt with session key and sign with session key
12
+ if (!ProtocolUtilities_1.NO_VERSION_HEADER_CMDS.has(cmd)) {
13
+ payload = Buffer.concat([VERSION_HEADER_34, payload]);
14
+ }
15
+ payload = (0, ProtocolUtilities_1.encryptECB)(payload, sessionKey);
16
+ return (0, ProtocolUtilities_1.packMessage55AA)(seqNo, cmd, payload, sessionKey);
17
+ }
18
+ if (deviceKey) {
19
+ // During key exchange (cmd 3 / cmd 5): ECB-encrypt payload with real device key
20
+ // and HMAC-sign the frame with the real device key (mirrors tinytuya _encode_message)
21
+ payload = (0, ProtocolUtilities_1.encryptECB)(payload, deviceKey);
22
+ return (0, ProtocolUtilities_1.packMessage55AA)(seqNo, cmd, payload, deviceKey);
23
+ }
24
+ // Fallback: unencrypted + CRC32 (should not normally reach here for v3.4)
25
+ return (0, ProtocolUtilities_1.packMessage55AA)(seqNo, cmd, payload);
26
+ }
27
+ isFrameComplete(buffer) {
28
+ return (0, ProtocolUtilities_1.isFrameComplete)(buffer);
29
+ }
30
+ extractFrame(buffer) {
31
+ return (0, ProtocolUtilities_1.extractFrame)(buffer);
32
+ }
33
+ decodeFrame(frame, deviceKey, sessionKey) {
34
+ // v3.4 uses HMAC-SHA256 (sessionKey for normal frames, deviceKey during key exchange)
35
+ const hmacKey = sessionKey ?? deviceKey;
36
+ const msg = (0, ProtocolUtilities_1.unpackMessage55AA)(frame, hmacKey, false);
37
+ if (!msg)
38
+ return null;
39
+ if (!msg.hmacOk)
40
+ return null;
41
+ let payload = msg.payload;
42
+ if (payload.length === 0) {
43
+ return { cmd: msg.cmd, payload };
44
+ }
45
+ // Decrypt ECB with session key
46
+ const decryptKey = sessionKey ?? deviceKey;
47
+ try {
48
+ payload = (0, ProtocolUtilities_1.decryptECB)(payload, decryptKey);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ // Only strip version header for commands that include it.
54
+ // Key exchange cmds (3,4,5) and heartbeats (9) are in NO_VERSION_HEADER_CMDS
55
+ // – their payloads must NOT be sliced or the nonce/HMAC bytes get corrupted.
56
+ if (!ProtocolUtilities_1.NO_VERSION_HEADER_CMDS.has(msg.cmd) && payload.length >= 15 && payload[0] === 0x33 /* '3' */) {
57
+ payload = payload.subarray(15);
58
+ }
59
+ return { cmd: msg.cmd, payload };
60
+ }
61
+ /**
62
+ * Key exchange step 1: build the payload client sends to device (cmd 3).
63
+ * Returns an encrypted 16-byte nonce; stores localNonce for later.
64
+ *
65
+ * Matches TinyTuya _negotiate_session_key_generate_step_1:
66
+ * local_nonce = b'0123456789abcdef'
67
+ * payload = MessagePayload(SESS_KEY_NEG_START, local_nonce)
68
+ */
69
+ buildKeyExchangeStep1(localNonce, _deviceKey) {
70
+ // The nonce is sent as-is (no encryption at the payload level for cmd 3;
71
+ // the frame itself carries the real key's HMAC wrapper).
72
+ return localNonce;
73
+ }
74
+ /**
75
+ * Key exchange step 3 + finalize.
76
+ *
77
+ * @param step2Payload Decrypted 48-byte payload from device cmd-4 frame.
78
+ * @param localNonce The 16-byte nonce we sent in step 1.
79
+ * @param realKey The device's real local key (pre-exchange).
80
+ * @returns { step3Payload, sessionKey } or null on verification failure.
81
+ *
82
+ * Matches TinyTuya _negotiate_session_key_generate_step_3 + _generate_finalize.
83
+ */
84
+ processKeyExchangeStep2(step2Payload, localNonce, realKey) {
85
+ if (step2Payload.length < 48)
86
+ return null;
87
+ const remoteNonce = step2Payload.subarray(0, 16);
88
+ const receivedHmac = step2Payload.subarray(16, 48);
89
+ // Verify HMAC(localNonce, realKey)
90
+ const expectedHmac = (0, ProtocolUtilities_1.hmac)(localNonce, realKey);
91
+ if (!receivedHmac.equals(expectedHmac))
92
+ return null;
93
+ // Step 3 payload: HMAC(remoteNonce, realKey)
94
+ const step3Payload = (0, ProtocolUtilities_1.hmac)(remoteNonce, realKey);
95
+ // Finalise: sessionKey = AES-ECB-encrypt(localNonce XOR remoteNonce, realKey)
96
+ const xored = Buffer.allocUnsafe(16);
97
+ for (let i = 0; i < 16; i++)
98
+ xored[i] = localNonce[i] ^ remoteNonce[i];
99
+ const sessionKey = (0, ProtocolUtilities_1.encryptECBNoPad)(xored, realKey);
100
+ return { step3Payload, sessionKey };
101
+ }
102
+ }
103
+ exports.ProtocolV34 = ProtocolV34;
104
+ //# sourceMappingURL=ProtocolV34.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProtocolV34.js","sourceRoot":"","sources":["../../../src/local/protocol/ProtocolV34.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProtocolV35 = void 0;
4
+ const ProtocolUtilities_1 = require("./ProtocolUtilities");
5
+ /** 15-byte version header prepended to v3.5 payloads before GCM encryption */
6
+ const VERSION_HEADER_35 = Buffer.from('3.5' + '\x00'.repeat(12), 'latin1');
7
+ class ProtocolV35 {
8
+ encodeFrame(cmd, data, seqNo, sessionKey, _deviceKey) {
9
+ if (!sessionKey) {
10
+ // During key exchange (before session key exists), send as 0x55AA plain
11
+ return (0, ProtocolUtilities_1.packMessage55AA)(seqNo, cmd, data);
12
+ }
13
+ let plaintext = data;
14
+ if (!ProtocolUtilities_1.NO_VERSION_HEADER_CMDS.has(cmd)) {
15
+ plaintext = Buffer.concat([VERSION_HEADER_35, plaintext]);
16
+ }
17
+ return (0, ProtocolUtilities_1.packMessage6699)(seqNo, cmd, plaintext, sessionKey);
18
+ }
19
+ isFrameComplete(buffer) {
20
+ return (0, ProtocolUtilities_1.isFrameComplete)(buffer);
21
+ }
22
+ extractFrame(buffer) {
23
+ return (0, ProtocolUtilities_1.extractFrame)(buffer);
24
+ }
25
+ decodeFrame(frame, deviceKey, sessionKey) {
26
+ // Use deviceKey as fallback during key exchange (before session key is established)
27
+ const key = sessionKey ?? deviceKey;
28
+ const msg = (0, ProtocolUtilities_1.unpackMessage6699)(frame, key);
29
+ if (!msg)
30
+ return null;
31
+ let payload = msg.payload;
32
+ // Only strip version header for commands that include it.
33
+ // Key exchange cmds (3,4,5) and heartbeats are in NO_VERSION_HEADER_CMDS.
34
+ if (!ProtocolUtilities_1.NO_VERSION_HEADER_CMDS.has(msg.cmd) && payload.length >= 15 && payload[0] === 0x33 /* '3' */) {
35
+ payload = payload.subarray(15);
36
+ }
37
+ return { cmd: msg.cmd, payload };
38
+ }
39
+ /**
40
+ * Key exchange step 1 payload (cmd 3): just the local nonce.
41
+ * Matches TinyTuya _negotiate_session_key_generate_step_1.
42
+ */
43
+ buildKeyExchangeStep1(localNonce, _realKey) {
44
+ return localNonce;
45
+ }
46
+ /**
47
+ * Process step-2 response and derive session key.
48
+ * For v3.5 the finalize differs from v3.4: uses GCM encrypt.
49
+ * Matches TinyTuya _negotiate_session_key_generate_finalize for version 3.5.
50
+ */
51
+ processKeyExchangeStep2(step2Payload, localNonce, realKey) {
52
+ if (step2Payload.length < 48)
53
+ return null;
54
+ const remoteNonce = step2Payload.subarray(0, 16);
55
+ const receivedHmac = step2Payload.subarray(16, 48);
56
+ // Verify HMAC(localNonce, realKey)
57
+ const expectedHmac = (0, ProtocolUtilities_1.hmac)(localNonce, realKey);
58
+ if (!receivedHmac.equals(expectedHmac))
59
+ return null;
60
+ // Step 3: HMAC(remoteNonce, realKey)
61
+ const step3Payload = (0, ProtocolUtilities_1.hmac)(remoteNonce, realKey);
62
+ // Finalize (v3.5 path):
63
+ // xored = localNonce XOR remoteNonce
64
+ // iv = localNonce[:12]
65
+ // encrypted = GCM-encrypt(xored, realKey, iv=iv)
66
+ // sessionKey = encrypted[12:28] (first 16 bytes of ciphertext, after IV)
67
+ const xored = Buffer.allocUnsafe(16);
68
+ for (let i = 0; i < 16; i++)
69
+ xored[i] = localNonce[i] ^ remoteNonce[i];
70
+ const iv = localNonce.subarray(0, 12);
71
+ const { ciphertext } = (0, ProtocolUtilities_1.encryptGCM)(xored, realKey, iv);
72
+ const sessionKey = ciphertext.subarray(0, 16);
73
+ return { step3Payload, sessionKey };
74
+ }
75
+ }
76
+ exports.ProtocolV35 = ProtocolV35;
77
+ //# sourceMappingURL=ProtocolV35.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProtocolV35.js","sourceRoot":"","sources":["../../../src/local/protocol/ProtocolV35.ts"],"names":[],"mappings":"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"}
package/dist/platform.js CHANGED
@@ -40,24 +40,46 @@ exports.TuyaPlatform = void 0;
40
40
  const jsonschema_1 = require("jsonschema");
41
41
  const path_1 = __importDefault(require("path"));
42
42
  const fs_1 = __importDefault(require("fs"));
43
- const TuyaDeviceManager_1 = __importDefault(require("./device/TuyaDeviceManager"));
44
- const TuyaCustomDeviceManager_1 = __importDefault(require("./device/TuyaCustomDeviceManager"));
45
- const TuyaHomeDeviceManager_1 = __importDefault(require("./device/TuyaHomeDeviceManager"));
43
+ const TuyaDeviceManager_1 = __importDefault(require("./cloud/device/TuyaDeviceManager"));
44
+ const TuyaCustomDeviceManager_1 = __importDefault(require("./cloud/device/TuyaCustomDeviceManager"));
45
+ const TuyaHomeDeviceManager_1 = __importDefault(require("./cloud/device/TuyaHomeDeviceManager"));
46
+ const TuyaOpenAPI_1 = __importStar(require("./cloud/api/TuyaOpenAPI"));
47
+ // Local imports (from src/local/)
48
+ const LocalDeviceManager_1 = __importDefault(require("./local/LocalDeviceManager"));
49
+ // Shared imports (from src/shared/)
50
+ const AccessoryFactory_1 = __importDefault(require("./shared/accessories/AccessoryFactory"));
51
+ const util_1 = require("./shared/util/util");
52
+ const ConfigHash_1 = require("./shared/util/ConfigHash");
46
53
  const settings_1 = require("./settings");
47
54
  const config_1 = require("./config");
48
- const AccessoryFactory_1 = __importDefault(require("./accessory/AccessoryFactory"));
49
- const util_1 = require("./util/util");
50
- const TuyaOpenAPI_1 = __importStar(require("./core/TuyaOpenAPI"));
51
55
  /**
52
- * HomebridgePlatform
53
- * This class is the main constructor for your plugin, this is where you should
54
- * parse the user config and discover/register accessories with Homebridge.
56
+ * TuyaPlatform — unified Homebridge platform supporting both Tuya Cloud (REST/MQTT)
57
+ * and Tuya Local (direct LAN TCP) device communication.
58
+ *
59
+ * Set `mode` in your config to:
60
+ * "cloud" — cloud only (default, backward-compatible)
61
+ * "local" — local LAN only
62
+ * "both" — cloud + local simultaneously
55
63
  */
56
64
  class TuyaPlatform {
57
65
  validate() {
66
+ // Local-only mode does not need cloud options
67
+ if (this.mode === 'local') {
68
+ if (!this.platformConfig.local) {
69
+ this.log.error('mode is "local" but no "local" config block found.');
70
+ return false;
71
+ }
72
+ return true;
73
+ }
74
+ // Both mode requires a local block in addition to cloud options
75
+ if (this.mode === 'both' && !this.platformConfig.local) {
76
+ this.log.error('mode is "both" but no "local" config block found.');
77
+ return false;
78
+ }
79
+ // Cloud or "both" mode requires cloud options
58
80
  let result;
59
81
  if (!this.options) {
60
- this.log.error('Not configured, exit.');
82
+ this.log.error('Not configured — "options" block is required for cloud mode, exit.');
61
83
  return false;
62
84
  }
63
85
  else if (this.options.projectType === '1') {
@@ -132,9 +154,15 @@ class TuyaPlatform {
132
154
  this.api = api;
133
155
  this.Service = this.api.hap.Service;
134
156
  this.Characteristic = this.api.hap.Characteristic;
157
+ /** Cast config to our typed shape for easy access. */
158
+ this.platformConfig = this.config;
159
+ /** Cloud credentials block (options.projectType, accessId, etc.) */
135
160
  this.options = this.config.options;
161
+ /** Active communication mode. Defaults to "cloud" for backward compatibility. */
162
+ this.mode = this.config.mode ?? 'cloud';
136
163
  // this is used to track restored cached accessories
137
164
  this.cachedAccessories = [];
165
+ /** All active accessory handler instances. */
138
166
  this.accessoryHandlers = [];
139
167
  if (!this.validate()) {
140
168
  return;
@@ -166,71 +194,111 @@ class TuyaPlatform {
166
194
  * must not be registered again to prevent "duplicate UUID" errors.
167
195
  */
168
196
  async initDevices() {
169
- let devices;
170
- if (this.options.projectType === '1') {
171
- devices = await this.initCustomProject();
172
- }
173
- else if (this.options.projectType === '2') {
174
- devices = await this.initHomeProject();
175
- }
176
- else {
177
- this.log.warn(`Unsupported projectType: ${this.config.options.projectType}.`);
178
- }
179
- if (!devices || !this.deviceManager) {
180
- return;
181
- }
182
- // override device category
183
- for (const device of devices) {
184
- const deviceConfig = this.getDeviceConfig(device);
185
- if (!deviceConfig || !deviceConfig.category) {
186
- continue;
197
+ const cloudEnabled = this.mode === 'cloud' || this.mode === 'both';
198
+ const localEnabled = this.mode === 'local' || this.mode === 'both';
199
+ // ── Local devices ────────────────────────────────────────────────────────
200
+ if (localEnabled && this.platformConfig.local) {
201
+ this.log.info('[Local] Initialising local device manager…');
202
+ this.localDeviceManager = new LocalDeviceManager_1.default(this.platformConfig.local, this.log, this.api.user.storagePath());
203
+ await this.localDeviceManager.initLocalDevices();
204
+ this.localDeviceManager.connectAllDevices();
205
+ this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, (device) => this.addAccessory(device, 'local'));
206
+ this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
207
+ this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
208
+ this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
209
+ const localDevices = [...this.localDeviceManager.devices.values()];
210
+ this.log.info(`[Local] Registered ${localDevices.length} local device(s).`);
211
+ for (const device of localDevices) {
212
+ this.addAccessory(device, 'local');
187
213
  }
188
- this.log.warn('Override %o category from %o to %o', device.name, device.category, deviceConfig.category);
189
- device.category = deviceConfig.category;
190
214
  }
191
- // override device bridged
192
- for (const device of devices) {
193
- const deviceConfig = this.getDeviceConfig(device);
194
- if (!deviceConfig || !deviceConfig.unbridged) {
195
- continue;
215
+ // ── Cloud devices ────────────────────────────────────────────────────────
216
+ if (cloudEnabled && this.options) {
217
+ let devices;
218
+ if (this.options.projectType === '1') {
219
+ devices = await this.initCustomProject();
220
+ }
221
+ else if (this.options.projectType === '2') {
222
+ devices = await this.initHomeProject();
223
+ }
224
+ else {
225
+ this.log.warn(`Unsupported projectType: ${this.options['projectType']}.`);
226
+ }
227
+ if (devices && this.deviceManager) {
228
+ // Initialize config hash tracker for cloud devices
229
+ this.configHash = new ConfigHash_1.ConfigHash(this.api.user.storagePath(), 'tuya-cloud-configs');
230
+ // Apply device config overrides
231
+ for (const device of devices) {
232
+ const deviceConfig = this.getDeviceConfig(device, 'cloud');
233
+ if (deviceConfig?.category) {
234
+ this.log.warn('Override %o category from %o to %o', device.name, device.category, deviceConfig.category);
235
+ device.category = deviceConfig.category;
236
+ }
237
+ if (deviceConfig?.unbridged) {
238
+ this.log.warn('Unbridge %o category %o', device.name, device.category);
239
+ device.unbridged = deviceConfig.unbridged;
240
+ }
241
+ // Check if config has changed since last run
242
+ // Hash the device override fields that affect accessory structure
243
+ const configToHash = {
244
+ deviceId: device.id,
245
+ customCategory: deviceConfig?.category,
246
+ unbridged: deviceConfig?.unbridged ?? false,
247
+ schemaOverrides: deviceConfig?.schema ? JSON.stringify(deviceConfig.schema) : undefined,
248
+ adaptiveLighting: deviceConfig?.adaptiveLighting ?? false,
249
+ };
250
+ const { changed: configChanged } = this.configHash.hasConfigChanged(device.id, configToHash);
251
+ device.configChanged = configChanged;
252
+ if (configChanged) {
253
+ this.log.info(`[Cloud] Device config changed for "${device.name}" (${device.id}), will rebuild services`);
254
+ }
255
+ }
256
+ await this.deviceManager.updateInfraredRemotes(devices);
257
+ this.log.info(`[Cloud] Got ${devices.length} device(s) and scene(s).`);
258
+ const file = path_1.default.join(this.api.user.persistPath(), `TuyaDeviceList.${this.deviceManager.api.tokenInfo.uid}.json`);
259
+ this.log.info('Device list saved at %s', file);
260
+ if (!fs_1.default.existsSync(this.api.user.persistPath())) {
261
+ await fs_1.default.promises.mkdir(this.api.user.persistPath());
262
+ }
263
+ await fs_1.default.promises.writeFile(file, JSON.stringify(devices, null, 2));
264
+ for (const device of devices) {
265
+ this.addAccessory(device, 'cloud');
266
+ }
267
+ this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, (device) => this.addAccessory(device, 'cloud'));
268
+ this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
269
+ this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
270
+ this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
196
271
  }
197
- this.log.warn('Unbridge %o category %o', device.name, device.category);
198
- device.unbridged = deviceConfig.unbridged;
199
- }
200
- await this.deviceManager.updateInfraredRemotes(devices);
201
- this.log.info(`Got ${devices.length} device(s) and scene(s).`);
202
- const file = path_1.default.join(this.api.user.persistPath(), `TuyaDeviceList.${this.deviceManager.api.tokenInfo.uid}.json`);
203
- this.log.info('Device list saved at %s', file);
204
- if (!fs_1.default.existsSync(this.api.user.persistPath())) {
205
- await fs_1.default.promises.mkdir(this.api.user.persistPath());
206
- }
207
- await fs_1.default.promises.writeFile(file, JSON.stringify(devices, null, 2));
208
- // add accessories
209
- for (const device of devices) {
210
- this.addAccessory(device);
211
272
  }
212
- // remove unused accessories
273
+ // Remove stale cached accessories not claimed by any device
213
274
  for (const cachedAccessory of this.cachedAccessories) {
214
275
  this.log.warn('Removing unused accessory from cache:', cachedAccessory.displayName);
215
276
  this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [cachedAccessory]);
216
277
  }
217
278
  this.cachedAccessories = [];
218
- this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, this.addAccessory.bind(this));
219
- this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
220
- this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
221
- this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
222
279
  }
223
- getDeviceConfig(device) {
280
+ getDeviceConfig(device, source) {
224
281
  if (!this.options.deviceOverrides) {
225
282
  return undefined;
226
283
  }
227
- const deviceConfig = this.options.deviceOverrides.find(config => config.id === device.id || config.id === device.uuid);
228
- const productConfig = this.options.deviceOverrides.find(config => config.id === device.product_id);
229
- const globalConfig = this.options.deviceOverrides.find(config => config.id === 'global');
230
- return deviceConfig || productConfig || globalConfig;
284
+ // Find matching override, respecting source filtering
285
+ // Since deviceOverrides are in the cloud config, they default to cloud-only
286
+ const effectiveSource = source || 'cloud';
287
+ const matches = this.options.deviceOverrides.filter(config => {
288
+ const sourceMatch = config.source
289
+ ? (config.source === 'both' || config.source === effectiveSource) // explicit source
290
+ : effectiveSource === 'cloud'; // default: cloud-only
291
+ const idMatch = config.id === device.id || config.id === device.uuid ||
292
+ config.id === device.product_id || config.id === 'global';
293
+ return sourceMatch && idMatch;
294
+ });
295
+ // Return device-specific config, then product, then global
296
+ return matches.find(config => config.id === device.id || config.id === device.uuid) ||
297
+ matches.find(config => config.id === device.product_id) ||
298
+ matches.find(config => config.id === 'global');
231
299
  }
232
- getDeviceSchemaConfig(device, code) {
233
- const deviceConfig = this.getDeviceConfig(device);
300
+ getDeviceSchemaConfig(device, code, source) {
301
+ const deviceConfig = this.getDeviceConfig(device, source);
234
302
  if (!deviceConfig || !deviceConfig.schema) {
235
303
  return undefined;
236
304
  }
@@ -242,13 +310,15 @@ class TuyaPlatform {
242
310
  item['oldCode'] = undefined;
243
311
  }
244
312
  });
245
- // ignore case - guard against undefined codes
313
+ // ignore case - allow both old (code) and migrated (newCode) names
246
314
  const schemaConfig = deviceConfig.schema.find(item => {
247
- const itemCode = (item.newCode ?? item.code);
248
- if (!itemCode || !code) {
315
+ if (!code) {
249
316
  return false;
250
317
  }
251
- return itemCode.toString().toLowerCase() === code.toString().toLowerCase();
318
+ const target = code.toString().toLowerCase();
319
+ const legacyCode = item.code?.toString().toLowerCase();
320
+ const migratedCode = item.newCode?.toString().toLowerCase();
321
+ return legacyCode === target || migratedCode === target;
252
322
  });
253
323
  if (!schemaConfig) {
254
324
  return undefined;
@@ -316,7 +386,7 @@ class TuyaPlatform {
316
386
  res = await api.customLogin(DEFAULT_USER, DEFAULT_USER);
317
387
  if (res.success === false) {
318
388
  this.log.error(`Login failed. code=${res.code}, msg=${res.msg}`);
319
- if (TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]) {
389
+ if (res.code && TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]) {
320
390
  this.log.error(TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]);
321
391
  }
322
392
  return undefined;
@@ -342,7 +412,7 @@ class TuyaPlatform {
342
412
  res = await api.homeLogin(countryCode, username, password, appSchema);
343
413
  if (res.success === false) {
344
414
  this.log.error(`Login failed. code=${res.code}, msg=${res.msg}`);
345
- if (TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]) {
415
+ if (res.code && TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]) {
346
416
  this.log.error(TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]);
347
417
  }
348
418
  return undefined;
@@ -394,7 +464,17 @@ class TuyaPlatform {
394
464
  }
395
465
  return devices;
396
466
  }
397
- addAccessory(device) {
467
+ addAccessory(device, source) {
468
+ // Apply device override config before checking if hidden
469
+ const deviceConfig = this.getDeviceConfig(device, source);
470
+ if (deviceConfig?.category) {
471
+ this.log.warn('Override %o category from %o to %o', device.name, device.category, deviceConfig.category);
472
+ device.category = deviceConfig.category;
473
+ }
474
+ if (deviceConfig?.unbridged) {
475
+ this.log.warn('Unbridge %o category %o', device.name, device.category);
476
+ device.unbridged = deviceConfig.unbridged;
477
+ }
398
478
  if (device.category === 'hidden') {
399
479
  this.log.info('Hide Accessory:', device.name);
400
480
  return;
@@ -466,7 +546,7 @@ class TuyaPlatform {
466
546
  this.log.info('Removing existing accessory from cache:', handler.accessory.displayName);
467
547
  }
468
548
  getAccessoryHandler(deviceID) {
469
- return this.accessoryHandlers.find(handler => handler.device.id === deviceID);
549
+ return this.accessoryHandlers.find(handler => handler.device?.id === deviceID);
470
550
  }
471
551
  createWeatherDevice(device, result) {
472
552
  const key = `weather-${device.owner_id}`;