@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,318 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import {
3
+ sanitizeName,
4
+ remap,
5
+ limit,
6
+ deepEqual,
7
+ debounce,
8
+ toHapProperty,
9
+ } from '../src/shared/util/util';
10
+ import { TuyaDeviceSchemaProperty } from '../src/cloud/device/TuyaDevice';
11
+
12
+ describe('sanitizeName', () => {
13
+ test('removes underscores and collapses to spaces', () => {
14
+ expect(sanitizeName('switch_1')).toBe('switch 1');
15
+ });
16
+
17
+ test('preserves ASCII alphanumeric and apostrophes', () => {
18
+ expect(sanitizeName("John's Lamp")).toBe("John's Lamp");
19
+ });
20
+
21
+ test('preserves accented and non-Latin letters (Unicode letters)', () => {
22
+ expect(sanitizeName('Café')).toBe('Café');
23
+ expect(sanitizeName('Тест123')).toBe('Тест123');
24
+ });
25
+
26
+ test('preserves CJK characters', () => {
27
+ expect(sanitizeName('中文测试')).toBe('中文测试');
28
+ });
29
+
30
+ test('returns undefined if sanitized name is empty or invalid', () => {
31
+ expect(sanitizeName('@@@')).toBeUndefined();
32
+ expect(sanitizeName(' !!! ')).toBeUndefined();
33
+ });
34
+
35
+ test('returns undefined for null or empty string', () => {
36
+ expect(sanitizeName('')).toBeUndefined();
37
+ expect(sanitizeName(undefined)).toBeUndefined();
38
+ });
39
+
40
+ test('handles mixed content correctly', () => {
41
+ expect(sanitizeName('device_123_name')).toBe('device 123 name');
42
+ expect(sanitizeName('My-Device-Name')).toBe('My Device Name');
43
+ });
44
+
45
+ test('collapses multiple consecutive spaces', () => {
46
+ expect(sanitizeName('name with spaces')).toBe('name with spaces');
47
+ });
48
+
49
+ test('trims leading and trailing spaces', () => {
50
+ expect(sanitizeName(' My Device ')).toBe('My Device');
51
+ });
52
+
53
+ test('rejects names that start or end with non-alphanumeric', () => {
54
+ expect(sanitizeName('_device')).toBeUndefined();
55
+ expect(sanitizeName('device_')).toBeUndefined();
56
+ expect(sanitizeName('_device_')).toBeUndefined();
57
+ });
58
+
59
+ test('handles single character names', () => {
60
+ expect(sanitizeName('A')).toBeUndefined(); // Single char fails the regex
61
+ expect(sanitizeName('AB')).toBe('AB');
62
+ });
63
+ });
64
+
65
+ describe('remap', () => {
66
+ test('remaps value from one range to another', () => {
67
+ // Remap 5 from [0, 10] to [0, 100]
68
+ const result = remap(5, 0, 10, 0, 100);
69
+ expect(result).toBe(50);
70
+ });
71
+
72
+ test('remaps 0 correctly', () => {
73
+ const result = remap(0, 0, 10, 0, 100);
74
+ expect(result).toBe(0);
75
+ });
76
+
77
+ test('remaps maximum value correctly', () => {
78
+ const result = remap(10, 0, 10, 0, 100);
79
+ expect(result).toBe(100);
80
+ });
81
+
82
+ test('remaps to negative range', () => {
83
+ const result = remap(5, 0, 10, -100, 0);
84
+ expect(result).toBe(-50);
85
+ });
86
+
87
+ test('remaps float values', () => {
88
+ const result = remap(2.5, 0, 5, 0, 100);
89
+ expect(result).toBe(50);
90
+ });
91
+
92
+ test('handles identical source and destination ranges', () => {
93
+ const result = remap(5, 0, 10, 0, 10);
94
+ expect(result).toBe(5);
95
+ });
96
+ });
97
+
98
+ describe('limit', () => {
99
+ test('limits value within range', () => {
100
+ expect(limit(50, 0, 100)).toBe(50);
101
+ });
102
+
103
+ test('clamps value below minimum', () => {
104
+ expect(limit(-10, 0, 100)).toBe(0);
105
+ });
106
+
107
+ test('clamps value above maximum', () => {
108
+ expect(limit(150, 0, 100)).toBe(100);
109
+ });
110
+
111
+ test('handles exact boundaries', () => {
112
+ expect(limit(0, 0, 100)).toBe(0);
113
+ expect(limit(100, 0, 100)).toBe(100);
114
+ });
115
+
116
+ test('handles negative ranges', () => {
117
+ expect(limit(-50, -100, 0)).toBe(-50);
118
+ expect(limit(-150, -100, 0)).toBe(-100);
119
+ expect(limit(50, -100, 0)).toBe(0);
120
+ });
121
+
122
+ test('handles float values', () => {
123
+ expect(limit(50.5, 0, 100)).toBe(50.5);
124
+ expect(limit(100.5, 0, 100)).toBe(100);
125
+ });
126
+ });
127
+
128
+ describe('deepEqual', () => {
129
+ test('returns true for equal primitives', () => {
130
+ expect(deepEqual(5, 5)).toBe(true);
131
+ expect(deepEqual('test', 'test')).toBe(true);
132
+ expect(deepEqual(true, true)).toBe(true);
133
+ });
134
+
135
+ test('returns false for different primitives', () => {
136
+ expect(deepEqual(5, 10)).toBe(false);
137
+ expect(deepEqual('test', 'other')).toBe(false);
138
+ });
139
+
140
+ test('returns true for equal objects', () => {
141
+ const obj1 = { a: 1, b: 2 };
142
+ const obj2 = { a: 1, b: 2 };
143
+ expect(deepEqual(obj1, obj2)).toBe(true);
144
+ });
145
+
146
+ test('returns false for different objects', () => {
147
+ const obj1 = { a: 1, b: 2 };
148
+ const obj2 = { a: 1, b: 3 };
149
+ expect(deepEqual(obj1, obj2)).toBe(false);
150
+ });
151
+
152
+ test('returns true for equal arrays', () => {
153
+ expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true);
154
+ });
155
+
156
+ test('returns false for different arrays', () => {
157
+ expect(deepEqual([1, 2, 3], [1, 2, 4])).toBe(false);
158
+ });
159
+
160
+ test('returns true for deeply nested equal structures', () => {
161
+ const obj1 = { a: { b: { c: 1 } } };
162
+ const obj2 = { a: { b: { c: 1 } } };
163
+ expect(deepEqual(obj1, obj2)).toBe(true);
164
+ });
165
+
166
+ test('handles null and undefined', () => {
167
+ expect(deepEqual(null, null)).toBe(true);
168
+ expect(deepEqual(undefined, undefined)).toBe(true);
169
+ expect(deepEqual(null, undefined)).toBe(false);
170
+ });
171
+ });
172
+
173
+ describe('debounce', () => {
174
+ test('debounces function execution', (done) => {
175
+ let callCount = 0;
176
+ const fn = jest.fn(() => {
177
+ callCount++;
178
+ });
179
+ const debounced = debounce(fn, 100);
180
+
181
+ debounced();
182
+ debounced();
183
+ debounced();
184
+
185
+ expect(fn).not.toHaveBeenCalled();
186
+
187
+ setTimeout(() => {
188
+ expect(fn).toHaveBeenCalledTimes(1);
189
+ expect(callCount).toBe(1);
190
+ done();
191
+ }, 150);
192
+ });
193
+
194
+ test('resets debounce timer on each call', (done) => {
195
+ let callCount = 0;
196
+ const fn = jest.fn(() => {
197
+ callCount++;
198
+ });
199
+ const debounced = debounce(fn, 50);
200
+
201
+ debounced();
202
+ setTimeout(() => debounced(), 25);
203
+ setTimeout(() => debounced(), 50);
204
+
205
+ expect(fn).not.toHaveBeenCalled();
206
+
207
+ setTimeout(() => {
208
+ expect(fn).toHaveBeenCalledTimes(1);
209
+ done();
210
+ }, 150);
211
+ });
212
+
213
+ test('preserves function return type (though debounced returns void)', () => {
214
+ const fn = (...args: unknown[]) => {
215
+ const x = args[0] as number;
216
+ return x * 2;
217
+ };
218
+ const debounced = debounce(fn, 100);
219
+
220
+ // Debounced functions return void
221
+ const result = debounced(5);
222
+ expect(result).toBeUndefined();
223
+ });
224
+ });
225
+
226
+ describe('toHapProperty', () => {
227
+ test('converts min property', () => {
228
+ const property: TuyaDeviceSchemaProperty = {
229
+ min: 0,
230
+ max: 100,
231
+ scale: 0,
232
+ };
233
+ const result = toHapProperty(property) as Record<string, any>;
234
+
235
+ expect(result.minValue).toBeDefined();
236
+ expect(result.minValue).toBe(0);
237
+ });
238
+
239
+ test('converts max property', () => {
240
+ const property: TuyaDeviceSchemaProperty = {
241
+ min: 0,
242
+ max: 100,
243
+ scale: 0,
244
+ };
245
+ const result = toHapProperty(property) as Record<string, any>;
246
+
247
+ expect(result.maxValue).toBeDefined();
248
+ expect(result.maxValue).toBe(100);
249
+ });
250
+
251
+ test('converts step property', () => {
252
+ const property: TuyaDeviceSchemaProperty = {
253
+ min: 0,
254
+ max: 100,
255
+ scale: 1,
256
+ step: 10,
257
+ };
258
+ const result = toHapProperty(property) as Record<string, any>;
259
+
260
+ expect(result.minStep).toBeDefined();
261
+ expect(result.minStep).toBe(1); // 10 / 10^1 = 1
262
+ });
263
+
264
+ test('converts range property to validValues', () => {
265
+ const property: TuyaDeviceSchemaProperty = {
266
+ range: ['off', 'on', 'auto'],
267
+ };
268
+ const result = toHapProperty(property) as Record<string, any>;
269
+
270
+ expect(result.validValues).toBeDefined();
271
+ expect(result.validValues).toEqual(['off', 'on', 'auto']);
272
+ });
273
+
274
+ test('applies scale factor correctly', () => {
275
+ const property: TuyaDeviceSchemaProperty = {
276
+ min: 0,
277
+ max: 10000,
278
+ scale: 2,
279
+ step: 100,
280
+ };
281
+ const result = toHapProperty(property) as Record<string, any>;
282
+
283
+ expect(result.minValue).toBe(0); // 0 / 100 = 0
284
+ expect(result.maxValue).toBe(100); // 10000 / 100 = 100
285
+ expect(result.minStep).toBe(1); // 100 / 100 = 1
286
+ });
287
+
288
+ test('clamps values to HAP limits', () => {
289
+ const property: TuyaDeviceSchemaProperty = {
290
+ min: -500,
291
+ max: 500,
292
+ scale: 0,
293
+ };
294
+ const result = toHapProperty(property) as Record<string, any>;
295
+
296
+ expect(result.minValue).toBeGreaterThanOrEqual(-273.15); // Absolute zero clamping
297
+ });
298
+
299
+ test('preserves other properties', () => {
300
+ const property: TuyaDeviceSchemaProperty = {
301
+ min: 0,
302
+ max: 100,
303
+ scale: 0,
304
+ unit: '°C',
305
+ };
306
+ const result = toHapProperty(property) as Record<string, any>;
307
+
308
+ expect(result.unit).toBe('°C');
309
+ });
310
+
311
+ test('handles empty properties object', () => {
312
+ const property: TuyaDeviceSchemaProperty = {};
313
+ const result = toHapProperty(property) as Record<string, any>;
314
+
315
+ expect(result).toBeDefined();
316
+ expect(typeof result).toBe('object');
317
+ });
318
+ });
package/test/util.ts CHANGED
@@ -1,13 +1,14 @@
1
+ import { expect } from '@jest/globals';
1
2
  import fs from 'fs';
2
3
  import { PLATFORM_NAME } from '../src/settings';
3
4
  import { TuyaPlatformConfig } from '../src/config';
4
- import TuyaDevice, { TuyaDeviceSchema } from '../src/device/TuyaDevice';
5
- import { TuyaOpenAPIResponse } from '../src/core/TuyaOpenAPI';
5
+ import TuyaDevice, { TuyaDeviceSchema } from '../src/cloud/device/TuyaDevice';
6
+ import { TuyaOpenAPIResponse } from '../src/cloud/api/TuyaOpenAPI';
6
7
 
7
8
  const file = fs.readFileSync(`${process.env.HOME}/.homebridge-dev/config.json`);
8
9
  const { platforms } = JSON.parse(file.toString());
9
10
 
10
- export const config: TuyaPlatformConfig = platforms.find(platform => platform.platform === PLATFORM_NAME);
11
+ export const config: TuyaPlatformConfig = platforms.find((platform: any) => platform.platform === PLATFORM_NAME);
11
12
 
12
13
  export function expectDevice(device: TuyaDevice) {
13
14
  // console.debug(JSON.stringify(device));
@@ -0,0 +1,199 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { ChildPayloadUtility } from '../../src/local/protocol/ChildPayloadUtility';
3
+
4
+ describe('ChildPayloadUtility', () => {
5
+
6
+ // ── isValidCid ─────────────────────────────────────────────────────────────
7
+
8
+ describe('isValidCid', () => {
9
+ it('accepts a valid lowercase 16-hex-char CID', () => {
10
+ expect(ChildPayloadUtility.isValidCid('0011223344556601')).toBe(true);
11
+ });
12
+
13
+ it('accepts a valid uppercase 16-hex-char CID', () => {
14
+ expect(ChildPayloadUtility.isValidCid('AABBCCDDEEFF0011')).toBe(true);
15
+ });
16
+
17
+ it('accepts a mixed-case 16-hex-char CID', () => {
18
+ expect(ChildPayloadUtility.isValidCid('0011aAbBcCdDeEfF')).toBe(true);
19
+ });
20
+
21
+ it('rejects a CID that is too short', () => {
22
+ expect(ChildPayloadUtility.isValidCid('001122334455660')).toBe(false);
23
+ });
24
+
25
+ it('rejects a CID that is too long', () => {
26
+ expect(ChildPayloadUtility.isValidCid('00112233445566011')).toBe(false);
27
+ });
28
+
29
+ it('rejects a CID containing non-hex characters', () => {
30
+ expect(ChildPayloadUtility.isValidCid('0011223344556GHI')).toBe(false);
31
+ });
32
+
33
+ it('rejects an empty string', () => {
34
+ expect(ChildPayloadUtility.isValidCid('')).toBe(false);
35
+ });
36
+ });
37
+
38
+ // ── prepareChildPayload ────────────────────────────────────────────────────
39
+
40
+ describe('prepareChildPayload', () => {
41
+ const cid = '0011223344556601';
42
+ const dps = { '1': true, '2': 100 };
43
+
44
+ it('v3.3: returns { cid, dps } envelope', () => {
45
+ const result = ChildPayloadUtility.prepareChildPayload(cid, dps, '3.3');
46
+ expect(result).toEqual({ cid, dps });
47
+ });
48
+
49
+ it('v3.4: returns { protocol:5, data:{ cid, dps } } envelope', () => {
50
+ const result = ChildPayloadUtility.prepareChildPayload(cid, dps, '3.4');
51
+ expect(result).toEqual({ protocol: 5, data: { cid, dps } });
52
+ });
53
+
54
+ it('v3.5: returns { protocol:5, data:{ cid, dps } } envelope', () => {
55
+ const result = ChildPayloadUtility.prepareChildPayload(cid, dps, '3.5');
56
+ expect(result).toEqual({ protocol: 5, data: { cid, dps } });
57
+ });
58
+
59
+ it('v3.3: preserves empty dps object', () => {
60
+ const result = ChildPayloadUtility.prepareChildPayload(cid, {}, '3.3');
61
+ expect(result).toEqual({ cid, dps: {} });
62
+ });
63
+
64
+ it('v3.4: preserves complex dps values', () => {
65
+ const complexDps = { '1': true, '24': 'colour', '25': '0064010200006464000000' };
66
+ const result = ChildPayloadUtility.prepareChildPayload(cid, complexDps, '3.4');
67
+ expect((result as any).data.dps).toEqual(complexDps);
68
+ });
69
+
70
+ it('throws on an invalid CID', () => {
71
+ expect(() => ChildPayloadUtility.prepareChildPayload('invalid', dps, '3.3'))
72
+ .toThrow('Invalid Zigbee CID');
73
+ });
74
+
75
+ it('throws on a CID that is too short', () => {
76
+ expect(() => ChildPayloadUtility.prepareChildPayload('0011223344', dps, '3.3'))
77
+ .toThrow('Invalid Zigbee CID');
78
+ });
79
+ });
80
+
81
+ // ── prepareChildQueryPayload ───────────────────────────────────────────────
82
+
83
+ describe('prepareChildQueryPayload', () => {
84
+ const cid = '0011223344556601';
85
+
86
+ it('v3.3: returns { cid, dps:{} }', () => {
87
+ const result = ChildPayloadUtility.prepareChildQueryPayload(cid, '3.3');
88
+ expect(result).toEqual({ cid, dps: {} });
89
+ });
90
+
91
+ it('v3.4: returns { protocol:5, data:{ cid, dps:{} } }', () => {
92
+ const result = ChildPayloadUtility.prepareChildQueryPayload(cid, '3.4');
93
+ expect(result).toEqual({ protocol: 5, data: { cid, dps: {} } });
94
+ });
95
+
96
+ it('v3.5: returns { protocol:5, data:{ cid, dps:{} } }', () => {
97
+ const result = ChildPayloadUtility.prepareChildQueryPayload(cid, '3.5');
98
+ expect(result).toEqual({ protocol: 5, data: { cid, dps: {} } });
99
+ });
100
+
101
+ it('throws on an invalid CID', () => {
102
+ expect(() => ChildPayloadUtility.prepareChildQueryPayload('bad!', '3.3'))
103
+ .toThrow('Invalid Zigbee CID');
104
+ });
105
+ });
106
+
107
+ // ── extractChildData ────────────────────────────────────────────────────────
108
+
109
+ describe('extractChildData', () => {
110
+ it('v3.3: extracts cid + dps from { gwId, cid, dps }', () => {
111
+ const payload = {
112
+ gwId: 'parent_gw',
113
+ cid: '0011223344556601',
114
+ dps: { '1': true },
115
+ };
116
+ const result = ChildPayloadUtility.extractChildData(payload);
117
+ expect(result).toEqual({
118
+ childId: '0011223344556601',
119
+ dps: { '1': true },
120
+ });
121
+ });
122
+
123
+ it('v3.3: extracts when gwId is absent (minimal format)', () => {
124
+ const payload = { cid: '0011223344556601', dps: { '2': 50 } };
125
+ const result = ChildPayloadUtility.extractChildData(payload);
126
+ expect(result).toEqual({ childId: '0011223344556601', dps: { '2': 50 } });
127
+ });
128
+
129
+ it('v3.4: extracts from { protocol:5, data:{ cid, dps } }', () => {
130
+ const payload = {
131
+ protocol: 5,
132
+ data: { cid: '0011223344556601', dps: { '1': false } },
133
+ };
134
+ expect(ChildPayloadUtility.extractChildData(payload)).toEqual({
135
+ childId: '0011223344556601',
136
+ dps: { '1': false },
137
+ });
138
+ });
139
+
140
+ it('v3.5: extracts from { protocol:5, data:{ cid, dps } }', () => {
141
+ const payload = {
142
+ protocol: 5,
143
+ data: { cid: 'aabbccddeeff0011', dps: { '24': 'colour' } },
144
+ };
145
+ expect(ChildPayloadUtility.extractChildData(payload)).toEqual({
146
+ childId: 'aabbccddeeff0011',
147
+ dps: { '24': 'colour' },
148
+ });
149
+ });
150
+
151
+ it('returns null for a bare { dps } payload (regular device update)', () => {
152
+ const payload = { devId: 'parent_id', dps: { '1': true } };
153
+ expect(ChildPayloadUtility.extractChildData(payload)).toBeNull();
154
+ });
155
+
156
+ it('returns null for a non-child v3.4+ update { protocol:5, data:{ devId, dps } }', () => {
157
+ // No cid field in data
158
+ const payload = {
159
+ protocol: 5,
160
+ data: { devId: 'some_device', dps: { '1': true } },
161
+ };
162
+ expect(ChildPayloadUtility.extractChildData(payload)).toBeNull();
163
+ });
164
+
165
+ it('returns null for an empty payload', () => {
166
+ expect(ChildPayloadUtility.extractChildData({})).toBeNull();
167
+ });
168
+
169
+ it('ignores protocol:4 envelope (not child routing)', () => {
170
+ const payload = { protocol: 4, data: { cid: '0011223344556601', dps: {} } };
171
+ expect(ChildPayloadUtility.extractChildData(payload)).toBeNull();
172
+ });
173
+ });
174
+
175
+ // ── round-trip: prepare then extract ──────────────────────────────────────
176
+
177
+ describe('round-trip consistency', () => {
178
+ const cid = 'aabbccddeeff0011';
179
+ const dps = { '1': true, '2': 255 };
180
+
181
+ it('v3.3 prepare → extract yields same cid and dps', () => {
182
+ const prepared = ChildPayloadUtility.prepareChildPayload(cid, dps, '3.3');
183
+ const extracted = ChildPayloadUtility.extractChildData(prepared);
184
+ expect(extracted).toEqual({ childId: cid, dps });
185
+ });
186
+
187
+ it('v3.4 prepare → extract yields same cid and dps', () => {
188
+ const prepared = ChildPayloadUtility.prepareChildPayload(cid, dps, '3.4');
189
+ const extracted = ChildPayloadUtility.extractChildData(prepared);
190
+ expect(extracted).toEqual({ childId: cid, dps });
191
+ });
192
+
193
+ it('v3.5 prepare → extract yields same cid and dps', () => {
194
+ const prepared = ChildPayloadUtility.prepareChildPayload(cid, dps, '3.5');
195
+ const extracted = ChildPayloadUtility.extractChildData(prepared);
196
+ expect(extracted).toEqual({ childId: cid, dps });
197
+ });
198
+ });
199
+ });