@homebridge-plugins/homebridge-tuya 3.0.0-beta.2 → 3.0.0-beta.21

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 (298) hide show
  1. package/README.md +354 -142
  2. package/config.schema.json +1221 -893
  3. package/dist/cloud/api/TuyaOpenAPI.js +108 -60
  4. package/dist/cloud/api/TuyaOpenAPI.js.map +1 -1
  5. package/dist/cloud/api/TuyaOpenMQ.js +45 -22
  6. package/dist/cloud/api/TuyaOpenMQ.js.map +1 -1
  7. package/dist/cloud/device/{TuyaDeviceManager.js → TuyaCloudDeviceManager.js} +146 -96
  8. package/dist/cloud/device/TuyaCloudDeviceManager.js.map +1 -0
  9. package/dist/cloud/device/TuyaCustomDeviceManager.js +75 -5
  10. package/dist/cloud/device/TuyaCustomDeviceManager.js.map +1 -1
  11. package/dist/cloud/device/TuyaDevice.js.map +1 -1
  12. package/dist/cloud/device/TuyaHomeDeviceManager.js +81 -13
  13. package/dist/cloud/device/TuyaHomeDeviceManager.js.map +1 -1
  14. package/dist/config.js +26 -5
  15. package/dist/config.js.map +1 -1
  16. package/dist/local/DynamicChildDiscovery.js +2 -1
  17. package/dist/local/DynamicChildDiscovery.js.map +1 -1
  18. package/dist/local/LocalDevice.js +16 -3
  19. package/dist/local/LocalDevice.js.map +1 -1
  20. package/dist/local/LocalDeviceManager.js +361 -164
  21. package/dist/local/LocalDeviceManager.js.map +1 -1
  22. package/dist/local/TuyaDiscovery.js +16 -7
  23. package/dist/local/TuyaDiscovery.js.map +1 -1
  24. package/dist/local/ZigbeeGatewayDetection.js +7 -4
  25. package/dist/local/ZigbeeGatewayDetection.js.map +1 -1
  26. package/dist/local/protocol/ProtocolUtilities.js +34 -17
  27. package/dist/local/protocol/ProtocolUtilities.js.map +1 -1
  28. package/dist/local/protocol/ProtocolV31V32.js +4 -2
  29. package/dist/local/protocol/ProtocolV31V32.js.map +1 -1
  30. package/dist/local/protocol/ProtocolV33.js +4 -2
  31. package/dist/local/protocol/ProtocolV33.js.map +1 -1
  32. package/dist/local/protocol/ProtocolV34.js +10 -5
  33. package/dist/local/protocol/ProtocolV34.js.map +1 -1
  34. package/dist/local/protocol/ProtocolV35.js +8 -4
  35. package/dist/local/protocol/ProtocolV35.js.map +1 -1
  36. package/dist/platform.js +253 -493
  37. package/dist/platform.js.map +1 -1
  38. package/dist/settings.js +0 -2
  39. package/dist/settings.js.map +1 -1
  40. package/dist/shared/TuyaDeviceManager.js +147 -0
  41. package/dist/shared/TuyaDeviceManager.js.map +1 -0
  42. package/dist/shared/TuyaHybridDeviceManager.js +215 -0
  43. package/dist/shared/TuyaHybridDeviceManager.js.map +1 -0
  44. package/dist/shared/accessory/AccessoryFactory.js +350 -0
  45. package/dist/shared/accessory/AccessoryFactory.js.map +1 -0
  46. package/dist/shared/{accessories → accessory}/AirConditionerAccessory.js +21 -15
  47. package/dist/shared/accessory/AirConditionerAccessory.js.map +1 -0
  48. package/dist/shared/{accessories → accessory}/AirPurifierAccessory.js.map +1 -1
  49. package/dist/shared/accessory/AirQualitySensorAccessory.js.map +1 -0
  50. package/dist/shared/{accessories → accessory}/BaseAccessory.js +16 -95
  51. package/dist/shared/accessory/BaseAccessory.js.map +1 -0
  52. package/dist/shared/{accessories → accessory}/BlindsAccessory.js.map +1 -1
  53. package/dist/shared/{accessories → accessory}/CameraAccessory.js +2 -2
  54. package/dist/shared/accessory/CameraAccessory.js.map +1 -0
  55. package/dist/shared/{accessories → accessory}/CarbonDioxideSensorAccessory.js.map +1 -1
  56. package/dist/shared/{accessories → accessory}/CarbonMonoxideSensorAccessory.js.map +1 -1
  57. package/dist/shared/{accessories → accessory}/ContactSensorAccessory.js.map +1 -1
  58. package/dist/shared/{accessories → accessory}/DehumidifierAccessory.js.map +1 -1
  59. package/dist/shared/{accessories → accessory}/DiffuserAccessory.js.map +1 -1
  60. package/dist/shared/{accessories → accessory}/DimmerAccessory.js.map +1 -1
  61. package/dist/shared/{accessories → accessory}/DoorbellAccessory.js.map +1 -1
  62. package/dist/shared/{accessories → accessory}/ExtractionHoodAccessory.js.map +1 -1
  63. package/dist/shared/{accessories → accessory}/FanAccessory.js.map +1 -1
  64. package/dist/shared/{accessories → accessory}/GarageDoorAccessory.js +19 -2
  65. package/dist/shared/accessory/GarageDoorAccessory.js.map +1 -0
  66. package/dist/shared/{accessories → accessory}/HeaterAccessory.js.map +1 -1
  67. package/dist/shared/{accessories → accessory}/HeaterAccessory_old.js.map +1 -1
  68. package/dist/shared/accessory/HumanPresenceSensorAccessory.js.map +1 -0
  69. package/dist/shared/{accessories → accessory}/HumidifierAccessory.js.map +1 -1
  70. package/dist/shared/accessory/IRAdapter.js +167 -0
  71. package/dist/shared/accessory/IRAdapter.js.map +1 -0
  72. package/dist/shared/accessory/IRAirConditionerAccessory.js.map +1 -0
  73. package/dist/shared/accessory/IRControlHubAccessory.js.map +1 -0
  74. package/dist/shared/accessory/IRControlHubSubAccessory.js.map +1 -0
  75. package/dist/shared/accessory/IRFanAccessory.js +90 -0
  76. package/dist/shared/accessory/IRFanAccessory.js.map +1 -0
  77. package/dist/shared/accessory/IRGenericAccessory.js.map +1 -0
  78. package/dist/shared/{accessories → accessory}/LeakSensorAccessory.js.map +1 -1
  79. package/dist/shared/{accessories → accessory}/LightAccessory.js.map +1 -1
  80. package/dist/shared/{accessories → accessory}/LightSensorAccessory.js.map +1 -1
  81. package/dist/shared/{accessories → accessory}/LocationWeatherAccessory.js +1 -1
  82. package/dist/shared/accessory/LocationWeatherAccessory.js.map +1 -0
  83. package/dist/shared/{accessories → accessory}/LockAccessory.js +3 -3
  84. package/dist/shared/{accessories → accessory}/LockAccessory.js.map +1 -1
  85. package/dist/shared/accessory/MotionSensorAccessory.js.map +1 -0
  86. package/dist/shared/accessory/OutletAccessory.js.map +1 -0
  87. package/dist/shared/{accessories → accessory}/PetFeederAccessory.js.map +1 -1
  88. package/dist/shared/{accessories → accessory}/SceneAccessory.js +1 -1
  89. package/dist/shared/accessory/SceneAccessory.js.map +1 -0
  90. package/dist/shared/accessory/SceneSwitchAccessory.js.map +1 -0
  91. package/dist/shared/accessory/SecuritySystemAccessory.js.map +1 -0
  92. package/dist/shared/{accessories → accessory}/SmokeSensorAccessory.js.map +1 -1
  93. package/dist/shared/{accessories → accessory}/SwitchAccessory.js +1 -45
  94. package/dist/shared/accessory/SwitchAccessory.js.map +1 -0
  95. package/dist/shared/accessory/TemperatureHumiditySensorAccessory.js.map +1 -0
  96. package/dist/shared/{accessories → accessory}/ThermostatAccessory.js.map +1 -1
  97. package/dist/shared/{accessories → accessory}/TowerRackAccessory.js.map +1 -1
  98. package/dist/shared/{accessories → accessory}/ValveAccessory.js.map +1 -1
  99. package/dist/shared/{accessories → accessory}/VibrationSensorAccessory.js.map +1 -1
  100. package/dist/shared/accessory/WeatherStationAccessory.js.map +1 -0
  101. package/dist/shared/accessory/WetBulbGlobeTemperatureAccessory.js.map +1 -0
  102. package/dist/shared/{accessories → accessory}/WhiteNoiseLightAccessory.js +1 -0
  103. package/dist/shared/accessory/WhiteNoiseLightAccessory.js.map +1 -0
  104. package/dist/shared/accessory/WindowAccessory.js.map +1 -0
  105. package/dist/shared/{accessories → accessory}/WindowCoveringAccessory.js.map +1 -1
  106. package/dist/shared/{accessories → accessory}/WirelessSwitchAccessory.js.map +1 -1
  107. package/dist/shared/accessory/characteristic/Active.js.map +1 -0
  108. package/dist/shared/accessory/characteristic/AirQuality.js.map +1 -0
  109. package/dist/shared/{accessories → accessory}/characteristic/CurrentRelativeHumidity.js +3 -0
  110. package/dist/shared/accessory/characteristic/CurrentRelativeHumidity.js.map +1 -0
  111. package/dist/shared/accessory/characteristic/CurrentTemperature.js.map +1 -0
  112. package/dist/shared/accessory/characteristic/CurrentWeather.js.map +1 -0
  113. package/dist/shared/accessory/characteristic/CurrentWeatherByOpenMeteo.js.map +1 -0
  114. package/dist/shared/{accessories → accessory}/characteristic/CurrentWetBulbGlobeTemperature.js +3 -2
  115. package/dist/shared/accessory/characteristic/CurrentWetBulbGlobeTemperature.js.map +1 -0
  116. package/dist/shared/{accessories → accessory}/characteristic/EnergyUsage.js +0 -5
  117. package/dist/shared/accessory/characteristic/EnergyUsage.js.map +1 -0
  118. package/dist/shared/{accessories → accessory}/characteristic/Light.js +28 -5
  119. package/dist/shared/accessory/characteristic/Light.js.map +1 -0
  120. package/dist/shared/accessory/characteristic/LightSensor.js.map +1 -0
  121. package/dist/shared/{accessories → accessory}/characteristic/LockPhysicalControls.js +3 -0
  122. package/dist/shared/accessory/characteristic/LockPhysicalControls.js.map +1 -0
  123. package/dist/shared/accessory/characteristic/MotionDetected.js.map +1 -0
  124. package/dist/shared/accessory/characteristic/Name.js.map +1 -0
  125. package/dist/shared/accessory/characteristic/OccupancyDetected.js.map +1 -0
  126. package/dist/shared/{accessories → accessory}/characteristic/On.js +1 -0
  127. package/dist/shared/accessory/characteristic/On.js.map +1 -0
  128. package/dist/shared/accessory/characteristic/OutletInUse.js.map +1 -0
  129. package/dist/shared/accessory/characteristic/ProgrammableSwitchEvent.js.map +1 -0
  130. package/dist/shared/{accessories → accessory}/characteristic/RelativeHumidityDehumidifierThreshold.js +3 -0
  131. package/dist/shared/accessory/characteristic/RelativeHumidityDehumidifierThreshold.js.map +1 -0
  132. package/dist/shared/accessory/characteristic/RotationSpeed.js.map +1 -0
  133. package/dist/shared/accessory/characteristic/SecuritySystemState.js.map +1 -0
  134. package/dist/shared/{accessories → accessory}/characteristic/SwingMode.js +3 -0
  135. package/dist/shared/accessory/characteristic/SwingMode.js.map +1 -0
  136. package/dist/shared/accessory/characteristic/TargetTemperature.js.map +1 -0
  137. package/dist/shared/accessory/characteristic/TemperatureDisplayUnits.js.map +1 -0
  138. package/dist/shared/util/ConfigHash.js +2 -2
  139. package/dist/shared/util/ConfigHash.js.map +1 -1
  140. package/dist/shared/util/ConfigMigrator.js +222 -0
  141. package/dist/shared/util/ConfigMigrator.js.map +1 -0
  142. package/dist/shared/util/FfmpegStreamingProcess.js +62 -0
  143. package/dist/shared/util/FfmpegStreamingProcess.js.map +1 -1
  144. package/dist/shared/util/InfraredTool.js +0 -2
  145. package/dist/shared/util/InfraredTool.js.map +1 -1
  146. package/dist/shared/util/Logger.js +85 -10
  147. package/dist/shared/util/Logger.js.map +1 -1
  148. package/dist/shared/util/TuyaStreamDelegate.js +194 -97
  149. package/dist/shared/util/TuyaStreamDelegate.js.map +1 -1
  150. package/dist/shared/util/util.js +59 -4
  151. package/dist/shared/util/util.js.map +1 -1
  152. package/docs/ADVANCED_OPTIONS.md +603 -0
  153. package/docs/CONFIG_GUIDE.md +616 -0
  154. package/docs/Get-Local-Keys-for-Your-Devices.md +246 -0
  155. package/{SUPPORTED_DEVICES.md → docs/SUPPORTED_DEVICES.md} +24 -3
  156. package/eslint.config.mjs +0 -1
  157. package/{jest.config.js → jest.config.ts} +5 -2
  158. package/package.json +12 -9
  159. package/test/ConfigMigrator.test.ts +1060 -0
  160. package/test/Logger.test.ts +482 -0
  161. package/test/accessoryFactory.data.test.ts +172 -0
  162. package/test/accessoryFactory.test.ts +64 -35
  163. package/test/airConditioner.test.ts +742 -0
  164. package/test/config.test.ts +17 -13
  165. package/test/custom.test.ts +2 -2
  166. package/test/errorHandling.test.ts +12 -12
  167. package/test/home.test.ts +1 -1
  168. package/test/hybridDeviceManager.test.ts +808 -0
  169. package/test/light.test.ts +250 -0
  170. package/test/localDevice.test.ts +21 -50
  171. package/test/localDeviceManager.behavior.test.ts +196 -31
  172. package/test/localDeviceManager.test.ts +124 -41
  173. package/test/mqttMessages.test.ts +33 -14
  174. package/test/platform.test.ts +337 -814
  175. package/test/platform.validation.local.test.ts +58 -119
  176. package/test/tuyaCustomDeviceManager.test.ts +106 -16
  177. package/test/tuyaDeviceManager.test.ts +636 -639
  178. package/test/tuyaDiscovery.test.ts +21 -33
  179. package/test/tuyaHomeDeviceManager.test.ts +109 -18
  180. package/test/tuyaOpenAPI.test.ts +25 -44
  181. package/test/tuyaOpenMQ.test.ts +15 -23
  182. package/test/util.test.ts +107 -7
  183. package/test/zigbee/LocalDevice.zigbee.test.ts +14 -18
  184. package/test/zigbee/LocalDeviceManager.cloud-discovery.test.ts +19 -20
  185. package/test/zigbee/LocalDeviceManager.zigbee.reconnect.test.ts +19 -20
  186. package/test/zigbee/LocalDeviceManager.zigbee.test.ts +73 -35
  187. package/test/zigbee/ZigbeeGatewayDetection.test.ts +2 -2
  188. package/ADVANCED_OPTIONS.md +0 -267
  189. package/dist/cloud/device/TuyaDeviceManager.js.map +0 -1
  190. package/dist/local/CloudLocalReconciliation.js +0 -180
  191. package/dist/local/CloudLocalReconciliation.js.map +0 -1
  192. package/dist/shared/AccessoryFactory.js +0 -276
  193. package/dist/shared/AccessoryFactory.js.map +0 -1
  194. package/dist/shared/accessories/AccessoryFactory.js +0 -305
  195. package/dist/shared/accessories/AccessoryFactory.js.map +0 -1
  196. package/dist/shared/accessories/AirConditionerAccessory.js.map +0 -1
  197. package/dist/shared/accessories/AirQualitySensorAccessory.js.map +0 -1
  198. package/dist/shared/accessories/BaseAccessory.js.map +0 -1
  199. package/dist/shared/accessories/CameraAccessory.js.map +0 -1
  200. package/dist/shared/accessories/GarageDoorAccessory.js.map +0 -1
  201. package/dist/shared/accessories/HumanPresenceSensorAccessory.js.map +0 -1
  202. package/dist/shared/accessories/IRAirConditionerAccessory.js.map +0 -1
  203. package/dist/shared/accessories/IRControlHubAccessory.js.map +0 -1
  204. package/dist/shared/accessories/IRControlHubSubAccessory.js.map +0 -1
  205. package/dist/shared/accessories/IRGenericAccessory.js.map +0 -1
  206. package/dist/shared/accessories/LocationWeatherAccessory.js.map +0 -1
  207. package/dist/shared/accessories/MotionSensorAccessory.js.map +0 -1
  208. package/dist/shared/accessories/OutletAccessory.js.map +0 -1
  209. package/dist/shared/accessories/SceneAccessory.js.map +0 -1
  210. package/dist/shared/accessories/SceneSwitchAccessory.js.map +0 -1
  211. package/dist/shared/accessories/SecuritySystemAccessory.js.map +0 -1
  212. package/dist/shared/accessories/SwitchAccessory.js.map +0 -1
  213. package/dist/shared/accessories/TemperatureHumiditySensorAccessory.js.map +0 -1
  214. package/dist/shared/accessories/WeatherStationAccessory.js.map +0 -1
  215. package/dist/shared/accessories/WetBulbGlobeTemperatureAccessory.js.map +0 -1
  216. package/dist/shared/accessories/WhiteNoiseLightAccessory.js.map +0 -1
  217. package/dist/shared/accessories/WindowAccessory.js.map +0 -1
  218. package/dist/shared/accessories/characteristic/Active.js.map +0 -1
  219. package/dist/shared/accessories/characteristic/AirQuality.js.map +0 -1
  220. package/dist/shared/accessories/characteristic/CurrentRelativeHumidity.js.map +0 -1
  221. package/dist/shared/accessories/characteristic/CurrentTemperature.js.map +0 -1
  222. package/dist/shared/accessories/characteristic/CurrentWeather.js.map +0 -1
  223. package/dist/shared/accessories/characteristic/CurrentWeatherByOpenMeteo.js.map +0 -1
  224. package/dist/shared/accessories/characteristic/CurrentWetBulbGlobeTemperature.js.map +0 -1
  225. package/dist/shared/accessories/characteristic/EnergyUsage.js.map +0 -1
  226. package/dist/shared/accessories/characteristic/Light.js.map +0 -1
  227. package/dist/shared/accessories/characteristic/LightSensor.js.map +0 -1
  228. package/dist/shared/accessories/characteristic/LockPhysicalControls.js.map +0 -1
  229. package/dist/shared/accessories/characteristic/MotionDetected.js.map +0 -1
  230. package/dist/shared/accessories/characteristic/Name.js.map +0 -1
  231. package/dist/shared/accessories/characteristic/OccupancyDetected.js.map +0 -1
  232. package/dist/shared/accessories/characteristic/On.js.map +0 -1
  233. package/dist/shared/accessories/characteristic/OutletInUse.js.map +0 -1
  234. package/dist/shared/accessories/characteristic/ProgrammableSwitchEvent.js.map +0 -1
  235. package/dist/shared/accessories/characteristic/RelativeHumidityDehumidifierThreshold.js.map +0 -1
  236. package/dist/shared/accessories/characteristic/RotationSpeed.js.map +0 -1
  237. package/dist/shared/accessories/characteristic/SecuritySystemState.js.map +0 -1
  238. package/dist/shared/accessories/characteristic/SwingMode.js.map +0 -1
  239. package/dist/shared/accessories/characteristic/TargetTemperature.js.map +0 -1
  240. package/dist/shared/accessories/characteristic/TemperatureDisplayUnits.js.map +0 -1
  241. package/test/zigbee/CloudLocalReconciliation.test.ts +0 -284
  242. package/tuya-local-configs/dev-1.hash +0 -1
  243. package/tuya-local-configs/device1.hash +0 -1
  244. package/wiki/Get-Local-Keys-for-Your-Devices.md +0 -85
  245. /package/dist/shared/{accessories → accessory}/AirPurifierAccessory.js +0 -0
  246. /package/dist/shared/{accessories → accessory}/AirQualitySensorAccessory.js +0 -0
  247. /package/dist/shared/{accessories → accessory}/BlindsAccessory.js +0 -0
  248. /package/dist/shared/{accessories → accessory}/CarbonDioxideSensorAccessory.js +0 -0
  249. /package/dist/shared/{accessories → accessory}/CarbonMonoxideSensorAccessory.js +0 -0
  250. /package/dist/shared/{accessories → accessory}/ContactSensorAccessory.js +0 -0
  251. /package/dist/shared/{accessories → accessory}/DehumidifierAccessory.js +0 -0
  252. /package/dist/shared/{accessories → accessory}/DiffuserAccessory.js +0 -0
  253. /package/dist/shared/{accessories → accessory}/DimmerAccessory.js +0 -0
  254. /package/dist/shared/{accessories → accessory}/DoorbellAccessory.js +0 -0
  255. /package/dist/shared/{accessories → accessory}/ExtractionHoodAccessory.js +0 -0
  256. /package/dist/shared/{accessories → accessory}/FanAccessory.js +0 -0
  257. /package/dist/shared/{accessories → accessory}/HeaterAccessory.js +0 -0
  258. /package/dist/shared/{accessories → accessory}/HeaterAccessory_old.js +0 -0
  259. /package/dist/shared/{accessories → accessory}/HumanPresenceSensorAccessory.js +0 -0
  260. /package/dist/shared/{accessories → accessory}/HumidifierAccessory.js +0 -0
  261. /package/dist/shared/{accessories → accessory}/IRAirConditionerAccessory.js +0 -0
  262. /package/dist/shared/{accessories → accessory}/IRControlHubAccessory.js +0 -0
  263. /package/dist/shared/{accessories → accessory}/IRControlHubSubAccessory.js +0 -0
  264. /package/dist/shared/{accessories → accessory}/IRGenericAccessory.js +0 -0
  265. /package/dist/shared/{accessories → accessory}/LeakSensorAccessory.js +0 -0
  266. /package/dist/shared/{accessories → accessory}/LightAccessory.js +0 -0
  267. /package/dist/shared/{accessories → accessory}/LightSensorAccessory.js +0 -0
  268. /package/dist/shared/{accessories → accessory}/MotionSensorAccessory.js +0 -0
  269. /package/dist/shared/{accessories → accessory}/OutletAccessory.js +0 -0
  270. /package/dist/shared/{accessories → accessory}/PetFeederAccessory.js +0 -0
  271. /package/dist/shared/{accessories → accessory}/SceneSwitchAccessory.js +0 -0
  272. /package/dist/shared/{accessories → accessory}/SecuritySystemAccessory.js +0 -0
  273. /package/dist/shared/{accessories → accessory}/SmokeSensorAccessory.js +0 -0
  274. /package/dist/shared/{accessories → accessory}/TemperatureHumiditySensorAccessory.js +0 -0
  275. /package/dist/shared/{accessories → accessory}/ThermostatAccessory.js +0 -0
  276. /package/dist/shared/{accessories → accessory}/TowerRackAccessory.js +0 -0
  277. /package/dist/shared/{accessories → accessory}/ValveAccessory.js +0 -0
  278. /package/dist/shared/{accessories → accessory}/VibrationSensorAccessory.js +0 -0
  279. /package/dist/shared/{accessories → accessory}/WeatherStationAccessory.js +0 -0
  280. /package/dist/shared/{accessories → accessory}/WetBulbGlobeTemperatureAccessory.js +0 -0
  281. /package/dist/shared/{accessories → accessory}/WindowAccessory.js +0 -0
  282. /package/dist/shared/{accessories → accessory}/WindowCoveringAccessory.js +0 -0
  283. /package/dist/shared/{accessories → accessory}/WirelessSwitchAccessory.js +0 -0
  284. /package/dist/shared/{accessories → accessory}/characteristic/Active.js +0 -0
  285. /package/dist/shared/{accessories → accessory}/characteristic/AirQuality.js +0 -0
  286. /package/dist/shared/{accessories → accessory}/characteristic/CurrentTemperature.js +0 -0
  287. /package/dist/shared/{accessories → accessory}/characteristic/CurrentWeather.js +0 -0
  288. /package/dist/shared/{accessories → accessory}/characteristic/CurrentWeatherByOpenMeteo.js +0 -0
  289. /package/dist/shared/{accessories → accessory}/characteristic/LightSensor.js +0 -0
  290. /package/dist/shared/{accessories → accessory}/characteristic/MotionDetected.js +0 -0
  291. /package/dist/shared/{accessories → accessory}/characteristic/Name.js +0 -0
  292. /package/dist/shared/{accessories → accessory}/characteristic/OccupancyDetected.js +0 -0
  293. /package/dist/shared/{accessories → accessory}/characteristic/OutletInUse.js +0 -0
  294. /package/dist/shared/{accessories → accessory}/characteristic/ProgrammableSwitchEvent.js +0 -0
  295. /package/dist/shared/{accessories → accessory}/characteristic/RotationSpeed.js +0 -0
  296. /package/dist/shared/{accessories → accessory}/characteristic/SecuritySystemState.js +0 -0
  297. /package/dist/shared/{accessories → accessory}/characteristic/TargetTemperature.js +0 -0
  298. /package/dist/shared/{accessories → accessory}/characteristic/TemperatureDisplayUnits.js +0 -0
@@ -0,0 +1,1060 @@
1
+ import { describe, expect, test, beforeEach, jest } from '@jest/globals';
2
+ import ConfigMigrator from '../src/shared/util/ConfigMigrator';
3
+ import { CommonConfig, TuyaPlatformHomeConfig, TuyaPluginMode } from '../src/config';
4
+ import { ExLogger, initLogger } from '../src/shared/util/Logger';
5
+
6
+ // モック用のダミーロガー
7
+ const mockInfo = jest.fn();
8
+
9
+ // Mock Logger
10
+ const mockLog: ExLogger = {
11
+ debug: jest.fn(),
12
+ info: mockInfo,
13
+ warn: jest.fn(),
14
+ error: jest.fn(),
15
+ log: jest.fn(),
16
+ success: jest.fn(),
17
+ } as unknown as ExLogger;
18
+
19
+ jest.mock('../config.schema.json', () => {
20
+ return {
21
+ schema: {
22
+ properties: {
23
+ mode: { default: 'cloud' },
24
+ name: { default: 'Tuya' },
25
+ options: {
26
+ properties: {
27
+ projectType: { default: '2' },
28
+ appSchema: { default: 'tuyaSmart' },
29
+ generateWeatherAccessory: { default: false },
30
+ weatherAPI: { default: 'Open-Meteo' }
31
+ },
32
+ },
33
+ local: {
34
+ properties: {
35
+ autoDiscoverDevices: { default: true },
36
+ discoverTimeout: { default: 5 },
37
+ rediscoverInterval: { default: 900 },
38
+ devices: { default: [] }
39
+ },
40
+ },
41
+ common: {
42
+ debug: { default: false}
43
+ }
44
+ },
45
+ },
46
+ };
47
+ }, { virtual: true });
48
+
49
+ describe('ConfigMigrator migrate', () => {
50
+ beforeEach(() => {
51
+ jest.clearAllMocks();
52
+ initLogger(mockLog);
53
+ });
54
+
55
+ test('migrate should call logger.info and return config with defaults applied for cloud mode', () => {
56
+ const migrator = new ConfigMigrator();
57
+
58
+ const inputConfig = {
59
+ mode: TuyaPluginMode.cloud,
60
+ } as any;
61
+
62
+ const result = migrator.migrate(inputConfig);
63
+
64
+ expect(mockInfo).toHaveBeenNthCalledWith(1, '[ConfigMigrator] Migrating config...');
65
+
66
+ expect(result.mode).toBe(TuyaPluginMode.cloud);
67
+
68
+ // cloud
69
+ const cloud = result.cloud as TuyaPlatformHomeConfig;
70
+ const common = result.common as CommonConfig;
71
+ expect(cloud).toBeDefined();
72
+ expect(cloud.projectType).toBe('2');
73
+ expect(cloud.appSchema).toBe('tuyaSmart');
74
+ expect(cloud.generateWeatherAccessory).toBe(false);
75
+ expect(cloud.weatherAPI).toBe('Open-Meteo');
76
+ expect(common.debug).toBe(false);
77
+
78
+ // local
79
+ expect(result.local).toBeUndefined();
80
+ });
81
+
82
+ test('migrate should apply local defaults when mode is local', () => {
83
+ const migrator = new ConfigMigrator();
84
+
85
+ const inputConfig = {
86
+ mode: TuyaPluginMode.local,
87
+ // cloud intentionally provided to ensure it is not overwritten for local-only mode
88
+ cloud: { projectType: '1' },
89
+ } as any;
90
+
91
+ const result = migrator.migrate(inputConfig);
92
+
93
+ // local defaults applied
94
+ const local = result.local;
95
+ expect(local).toBeDefined();
96
+ expect(local!.autoDiscoverDevices).toBe(true);
97
+ expect(local!.discoverTimeout).toBe(5);
98
+ expect(local!.rediscoverInterval).toBe(900);
99
+ expect(local!.devices).toBeDefined();
100
+ expect(local!.devices).toEqual([]);
101
+
102
+ // Unnecessary settings are removed.
103
+ expect(result.cloud).toBeUndefined();
104
+ });
105
+
106
+ test('migrate should apply both cloud and local defaults when mode is both', () => {
107
+ const migrator = new ConfigMigrator();
108
+
109
+ const inputConfig = {
110
+ mode: TuyaPluginMode.both,
111
+ // partially provided cloud/local to ensure merging with defaults
112
+ cloud: { projectType: '2', generateWeatherAccessory: true, accessId: 'testID', accessKey: 'testKey' },
113
+ local: { autoDiscoverDevices: true, discoverTimeout: 10 },
114
+ } as any;
115
+
116
+ const result = migrator.migrate(inputConfig);
117
+
118
+ // cloud: retries preserved, timeout default applied
119
+ const cloud = result.cloud as TuyaPlatformHomeConfig;
120
+ const common = result.common as CommonConfig;
121
+ expect(cloud).toBeDefined();
122
+ expect(cloud.projectType).toBe('2');
123
+ expect(cloud.appSchema).toBe('tuyaSmart');
124
+ expect(cloud.generateWeatherAccessory).toBe(true);
125
+ expect(cloud.weatherAPI).toBe('Open-Meteo');
126
+ expect(common.debug).toBe(false);
127
+ expect(cloud.accessId).toBe('testID');
128
+ expect(cloud.accessKey).toBe('testKey');
129
+
130
+ // local: port preserved, retryInterval default applied
131
+ const local = result.local;
132
+ expect(local).toBeDefined();
133
+ expect(local!.autoDiscoverDevices).toBe(true);
134
+ expect(local!.discoverTimeout).toBe(10);
135
+ expect(local!.rediscoverInterval).toBe(900);
136
+ expect(local!.devices).toBeDefined();
137
+ expect(local!.devices).toEqual([]);
138
+ });
139
+
140
+ test('migrate should apply local defaults when mode is local (including remote_keys)', () => {
141
+ const migrator = new ConfigMigrator();
142
+
143
+ const inputConfig = {
144
+ mode: TuyaPluginMode.local,
145
+ // cloud intentionally provided to ensure it is not overwritten for local-only mode
146
+ cloud: { projectType: '1' },
147
+ local: {
148
+ devices: [
149
+ {
150
+ tuyaDeviceId: 'localID',
151
+ name: 'test name',
152
+ remote_keys: {
153
+ brand_id: 1,
154
+ key_list: [
155
+ {
156
+ key_name: 'keyName_1',
157
+ learning_code: '0123456789',
158
+ },
159
+ {
160
+ key_name: ' ',
161
+ learning_code: '9876543210',
162
+ standard_key: true,
163
+ }
164
+ ]
165
+ }
166
+ }
167
+ ]
168
+ }
169
+ } as any;
170
+
171
+ const result = migrator.migrate(inputConfig);
172
+
173
+ // local defaults applied
174
+ const local = result.local;
175
+ expect(local).toBeDefined();
176
+ expect(local!.autoDiscoverDevices).toBe(true);
177
+ expect(local!.discoverTimeout).toBe(5);
178
+ expect(local!.rediscoverInterval).toBe(900);
179
+ expect(local!.devices).toBeDefined();
180
+ expect(local!.devices?.length).toEqual(1);
181
+ expect(local!.devices![0].remote_keys).toBeDefined();
182
+ expect(local!.devices![0].remote_keys?.brand_id).toBe(1);
183
+ expect(local!.devices![0].remote_keys?.category_id).toBe(999);
184
+ expect(local!.devices![0].remote_keys?.duplicate_power).toBe(false);
185
+ expect(local!.devices![0].remote_keys?.org_category_id).toBe(999);
186
+ expect(local!.devices![0].remote_keys?.remote_index).toBe(0);
187
+ expect(local!.devices![0].remote_keys?.single_air).toBe(false);
188
+ expect(local!.devices![0].remote_keys?.key_list.length).toBe(2);
189
+ expect(local!.devices![0].remote_keys?.key_list[0].key_name).toBe('keyName 1');
190
+ expect(local!.devices![0].remote_keys?.key_list[0].key).toBe('keyName 1');
191
+ expect(local!.devices![0].remote_keys?.key_list[0].key_id).toBe(1000000);
192
+ expect(local!.devices![0].remote_keys?.key_list[0].standard_key).toBe(false);
193
+ expect(local!.devices![0].remote_keys?.key_list[0].learning_code).toBe('0123456789');
194
+ expect(local!.devices![0].remote_keys?.key_list[1].key_name).toBe(' ');
195
+ expect(local!.devices![0].remote_keys?.key_list[1].key).toBe(' ');
196
+ expect(local!.devices![0].remote_keys?.key_list[1].key_id).toBe(1000001);
197
+ expect(local!.devices![0].remote_keys?.key_list[1].standard_key).toBe(true);
198
+ expect(local!.devices![0].remote_keys?.key_list[1].learning_code).toBe('9876543210');
199
+
200
+ // Unnecessary settings are removed.
201
+ expect(result.cloud).toBeUndefined();
202
+ });
203
+
204
+ test('migrate should apply local defaults when mode is local (without remote_keys)', () => {
205
+ const migrator = new ConfigMigrator();
206
+
207
+ const inputConfig = {
208
+ mode: TuyaPluginMode.local,
209
+ // cloud intentionally provided to ensure it is not overwritten for local-only mode
210
+ cloud: { projectType: '1' },
211
+ local: {
212
+ devices: [
213
+ {
214
+ tuyaDeviceId: 'localID',
215
+ name: 'test name',
216
+ }
217
+ ]
218
+ }
219
+ } as any;
220
+
221
+ const result = migrator.migrate(inputConfig);
222
+
223
+ // local defaults applied
224
+ const local = result.local;
225
+ expect(local).toBeDefined();
226
+ expect(local!.autoDiscoverDevices).toBe(true);
227
+ expect(local!.discoverTimeout).toBe(5);
228
+ expect(local!.rediscoverInterval).toBe(900);
229
+ expect(local!.devices).toBeDefined();
230
+ expect(local!.devices?.length).toEqual(1);
231
+ expect(local!.devices![0].remote_keys).toBeUndefined();
232
+
233
+ // Unnecessary settings are removed.
234
+ expect(result.cloud).toBeUndefined();
235
+ });
236
+
237
+ test('migrate should transform device schema oldCode -> code and set newCode', () => {
238
+ const migrator = new ConfigMigrator();
239
+
240
+ const deviceOverride = {
241
+ id: 'dev1',
242
+ schema: [
243
+ { code: 'currentCode', oldCode: 'legacyCode', name: 'switch' },
244
+ { code: 'onlyCurrent', name: 'sensor' },
245
+ ],
246
+ } as any;
247
+
248
+ const inputConfig = {
249
+ mode: TuyaPluginMode.cloud,
250
+ cloud: {
251
+ deviceOverrides: [deviceOverride],
252
+ },
253
+ } as any;
254
+
255
+ const result = migrator.migrate(inputConfig);
256
+
257
+ // deviceOverrides
258
+ expect(result.cloud?.deviceOverrides).toBeDefined();
259
+ const migrated = result.cloud?.deviceOverrides![0];
260
+
261
+ // For the first schema item, replace code using oldCode and store the original code in newCode.
262
+ const first = migrated?.schema![0];
263
+ expect(first!.code).toBe('legacyCode'); // oldCode が code に移動
264
+ expect(first!.newCode).toBe('currentCode'); // 元の code が newCode に保存
265
+ expect((first as any)['oldCode']).toBeUndefined();
266
+
267
+ // The second schema item remains unchanged.
268
+ const second = migrated?.schema![1];
269
+ expect(second!.code).toBe('onlyCurrent');
270
+ expect(second!.newCode).toBeUndefined();
271
+ });
272
+
273
+ test('migrate old schema with options to cloud and etc. (config.schema.json v2.x -> v3.x)', () => {
274
+ const migrator = new ConfigMigrator();
275
+
276
+ const v2config = {
277
+ name: 'Tuya',
278
+ options: {
279
+ projectType: '2',
280
+ appSchema: 'tuyaSmart',
281
+ generateWeatherAccessory: false,
282
+ weatherAPI: 'Open-Meteo',
283
+ deviceOverrides: [],
284
+ debug: false
285
+ }
286
+ } as any;
287
+
288
+ const v3config = {
289
+ mode: TuyaPluginMode.cloud,
290
+ name: 'Tuya',
291
+ cloud: {
292
+ projectType: '2',
293
+ appSchema: 'tuyaSmart',
294
+ generateWeatherAccessory: false,
295
+ weatherAPI: 'Open-Meteo',
296
+ deviceOverrides: []
297
+ },
298
+ common: {
299
+ debug: false
300
+ }
301
+ } as any;
302
+
303
+ const newConfig = migrator.migrate(v2config);
304
+
305
+ expect(newConfig).toEqual(v3config);
306
+ });
307
+
308
+ test('priority v3.x settings > v2.x settings', () => {
309
+ const migrator = new ConfigMigrator();
310
+
311
+ const mixConfig = {
312
+ mode: TuyaPluginMode.cloud,
313
+ name: 'Tuya',
314
+ options: {
315
+ projectType: '2',
316
+ appSchema: 'tuyaSmart',
317
+ generateWeatherAccessory: false,
318
+ weatherAPI: 'Open-Meteo',
319
+ deviceOverrides: [ { id: 'old' } ],
320
+ debug: true,
321
+ debugLevel: 'api'
322
+ },
323
+ cloud: {
324
+ projectType: '2',
325
+ appSchema: 'tuyaSmart',
326
+ generateWeatherAccessory: false,
327
+ weatherAPI: 'Open-Meteo',
328
+ deviceOverrides: [ { id: 'new', schema: [] } ]
329
+ },
330
+ common: {
331
+ debug: false,
332
+ debugLevel: undefined
333
+ }
334
+ } as any;
335
+
336
+ const v3config = {
337
+ mode: TuyaPluginMode.cloud,
338
+ name: 'Tuya',
339
+ cloud: {
340
+ projectType: '2',
341
+ appSchema: 'tuyaSmart',
342
+ generateWeatherAccessory: false,
343
+ weatherAPI: 'Open-Meteo',
344
+ deviceOverrides: [ { id: 'new', schema: [] } ]
345
+ },
346
+ common: {
347
+ debug: false,
348
+ debugLevel: undefined
349
+ }
350
+ } as any;
351
+
352
+ const newConfig = migrator.migrate(mixConfig);
353
+
354
+ expect(newConfig).toEqual(v3config);
355
+ });
356
+
357
+ test('Separated configuration options for local mode and cloud mode.', () => {
358
+ const migrator = new ConfigMigrator();
359
+
360
+ const bothConfig = {
361
+ mode: TuyaPluginMode.both,
362
+ name: 'Tuya',
363
+ cloud: {
364
+ projectType: '2',
365
+ appSchema: 'tuyaSmart',
366
+ generateWeatherAccessory: false,
367
+ weatherAPI: 'Open-Meteo',
368
+ deviceOverrides: [
369
+ { id: 'cloudID', schema: [], configFor: 'cloud' },
370
+ { id: 'localID', schema: [], configFor: 'local' },
371
+ { id: 'bothID', schema: [], configFor: 'both' },
372
+ { id: 'undefinedID', schema: [] }
373
+ ]
374
+ },
375
+ local: {
376
+ autoDiscoverDevices: false,
377
+ devices: [ { tuyaDeviceId: 'localID', name: 'test name' } ],
378
+ discoverTimeout: 1,
379
+ rediscoverInterval: 100,
380
+ },
381
+ common: {
382
+ debug: false
383
+ }
384
+ } as any;
385
+
386
+ const expected = {
387
+ mode: TuyaPluginMode.both,
388
+ name: 'Tuya',
389
+ cloud: {
390
+ projectType: '2',
391
+ appSchema: 'tuyaSmart',
392
+ generateWeatherAccessory: false,
393
+ weatherAPI: 'Open-Meteo',
394
+ deviceOverrides: [
395
+ { id: 'cloudID', schema: [], configFor: 'cloud' },
396
+ { id: 'bothID', schema: [], configFor: 'both' },
397
+ { id: 'undefinedID', schema: [] }
398
+ ]
399
+ },
400
+ local: {
401
+ autoDiscoverDevices: false,
402
+ devices: [ { tuyaDeviceId: 'localID', name: 'test name' } ],
403
+ discoverTimeout: 1,
404
+ rediscoverInterval: 100,
405
+ deviceOverrides: [
406
+ { id: 'localID', schema: [], configFor: 'local' },
407
+ { id: 'bothID', schema: [], configFor: 'both' },
408
+ ]
409
+ },
410
+ common: {
411
+ debug: false
412
+ }
413
+ } as any;
414
+
415
+ const newConfig = migrator.migrate(bothConfig);
416
+
417
+ expect(newConfig).toEqual(expected);
418
+ });
419
+
420
+ test('camera settings only used for local', () => {
421
+ const migrator = new ConfigMigrator();
422
+
423
+ const before = {
424
+ name: 'Tuya',
425
+ mode: 'both',
426
+ cloud: {
427
+ projectType: '2',
428
+ appSchema: 'tuyaSmart',
429
+ generateWeatherAccessory: false,
430
+ weatherAPI: 'Open-Meteo',
431
+ deviceOverrides: [],
432
+ debug: false
433
+ },
434
+ local: {
435
+ autoDiscoverDevices: false,
436
+ devices: [ { tuyaDeviceId: 'localID', name: 'test name' } ],
437
+ discoverTimeout: 1,
438
+ rediscoverInterval: 100,
439
+ deviceOverrides: [
440
+ { id: 'localID', schema: [], configFor: 'local' },
441
+ { id: 'bothID', schema: [], configFor: 'both' },
442
+ ]
443
+ },
444
+ cameras: [
445
+ {
446
+ deviceId: 'camera-device1',
447
+ deviceName: 'camera-deviceName1',
448
+ rtspUrl: 'rtsp://192.168.0.1:554/live',
449
+ username: 'username1',
450
+ password: 'password1'
451
+ }
452
+ ]
453
+ } as any;
454
+
455
+ const after = {
456
+ mode: TuyaPluginMode.both,
457
+ name: 'Tuya',
458
+ cloud: {
459
+ projectType: '2',
460
+ appSchema: 'tuyaSmart',
461
+ generateWeatherAccessory: false,
462
+ weatherAPI: 'Open-Meteo',
463
+ deviceOverrides: []
464
+ },
465
+ local: {
466
+ autoDiscoverDevices: false,
467
+ devices: [ { tuyaDeviceId: 'localID', name: 'test name' } ],
468
+ discoverTimeout: 1,
469
+ rediscoverInterval: 100,
470
+ deviceOverrides: [
471
+ { id: 'localID', schema: [], configFor: 'local' },
472
+ { id: 'bothID', schema: [], configFor: 'both' },
473
+ ],
474
+ cameras: [
475
+ {
476
+ deviceId: 'camera-device1',
477
+ deviceName: 'camera-deviceName1',
478
+ rtspUrl: 'rtsp://192.168.0.1:554/live',
479
+ username: 'username1',
480
+ password: 'password1'
481
+ }
482
+ ]
483
+ },
484
+ common: {
485
+ debug: false,
486
+ debugLevel: undefined
487
+ }
488
+ } as any;
489
+
490
+ const newConfig = migrator.migrate(before);
491
+
492
+ expect(newConfig).toEqual(after);
493
+ });
494
+
495
+ test('camera settings moved to local config. (config.schema.json v2.x -> v3.x)', () => {
496
+ const migrator = new ConfigMigrator();
497
+
498
+ const v2config = {
499
+ name: 'Tuya',
500
+ options: {
501
+ projectType: '2',
502
+ appSchema: 'tuyaSmart',
503
+ generateWeatherAccessory: false,
504
+ weatherAPI: 'Open-Meteo',
505
+ deviceOverrides: [],
506
+ debug: false
507
+ },
508
+ cameras: [
509
+ {
510
+ deviceId: 'camera-device1',
511
+ deviceName: 'camera-deviceName1',
512
+ rtspUrl: 'rtsp://192.168.0.1:554/live',
513
+ username: 'username1',
514
+ password: 'password1'
515
+ }
516
+ ]
517
+ } as any;
518
+
519
+ const v3config = {
520
+ mode: TuyaPluginMode.both,
521
+ name: 'Tuya',
522
+ cloud: {
523
+ projectType: '2',
524
+ appSchema: 'tuyaSmart',
525
+ generateWeatherAccessory: false,
526
+ weatherAPI: 'Open-Meteo',
527
+ deviceOverrides: []
528
+ },
529
+ local: {
530
+ autoDiscoverDevices: true,
531
+ discoverTimeout: 5,
532
+ rediscoverInterval: 900,
533
+ devices: [],
534
+ cameras: [
535
+ {
536
+ deviceId: 'camera-device1',
537
+ deviceName: 'camera-deviceName1',
538
+ rtspUrl: 'rtsp://192.168.0.1:554/live',
539
+ username: 'username1',
540
+ password: 'password1'
541
+ }
542
+ ]
543
+ },
544
+ common: {
545
+ debug: false,
546
+ debugLevel: undefined
547
+ }
548
+ } as any;
549
+
550
+ const newConfig = migrator.migrate(v2config);
551
+
552
+ expect(newConfig).toEqual(v3config);
553
+ });
554
+
555
+ test('no migration', () => {
556
+ const migrator = new ConfigMigrator();
557
+
558
+ const config = {
559
+ mode: TuyaPluginMode.cloud,
560
+ name: 'Tuya',
561
+ cloud: {
562
+ projectType: '2',
563
+ appSchema: 'tuyaSmart',
564
+ generateWeatherAccessory: false,
565
+ weatherAPI: 'Open-Meteo',
566
+ deviceOverrides: []
567
+ },
568
+ common: {
569
+ debug: true,
570
+ debugLevel: 'api'
571
+ }
572
+ } as any;
573
+
574
+ const newConfig = migrator.migrate(config);
575
+
576
+ expect(newConfig).toEqual(config);
577
+ });
578
+
579
+ describe('ConfigMigrator extractDefaultConfig', () => {
580
+ test('schema without properties', () => {
581
+ const migrator = new ConfigMigrator();
582
+ const schema = {}
583
+
584
+ const result = migrator['extractDefaultConfig'](schema);
585
+ expect(result).toEqual(schema);
586
+ });
587
+ });
588
+
589
+ describe('ConfigMigrator normalizeConfig', () => {
590
+ test('sanitize names', () => {
591
+ const migrator = new ConfigMigrator();
592
+ const inputConfig = {
593
+ cloud: {
594
+ serviceInformationOverrides: [ { configuredName: 'test_configuredname' } ]
595
+ },
596
+ local: {
597
+ devices: [ { name: 'test-name' } ]
598
+ }
599
+ };
600
+ const outputConfig = {
601
+ cloud: {
602
+ serviceInformationOverrides: [ { configuredName: 'test configuredname' } ]
603
+ },
604
+ local: {
605
+ devices: [ { name: 'test name' } ]
606
+ }
607
+ };
608
+
609
+
610
+ const result = migrator['normalizeConfig'](inputConfig as any);
611
+ expect(result).toEqual(outputConfig);
612
+ });
613
+ });
614
+
615
+ describe('ConfigMigrator cleanupConfig', () => {
616
+ test('remove config with undefined device id', () => {
617
+ const migrator = new ConfigMigrator();
618
+ const inputConfig = {
619
+ cloud: {
620
+ deviceOverrides: [ { } ]
621
+ },
622
+ local: {
623
+ devices: [ { } ]
624
+ }
625
+ };
626
+ const outputConfig = {
627
+ cloud: {
628
+ deviceOverrides: []
629
+ },
630
+ local: {
631
+ devices: []
632
+ }
633
+ };
634
+
635
+ const result = migrator['cleanupConfig'](inputConfig as any);
636
+ expect(result).toEqual(outputConfig);
637
+ });
638
+
639
+ test('undefined device list will be initialized with an empty list.', () => {
640
+ const migrator = new ConfigMigrator();
641
+ const inputConfig = {
642
+ cloud: {
643
+ },
644
+ local: {
645
+ }
646
+ };
647
+ const outputConfig = {
648
+ cloud: {
649
+ deviceOverrides: []
650
+ },
651
+ local: {
652
+ devices: []
653
+ }
654
+ };
655
+
656
+ const result = migrator['cleanupConfig'](inputConfig as any);
657
+ expect(result).toEqual(outputConfig);
658
+ });
659
+
660
+ test('delete bad ip.', () => {
661
+ const migrator = new ConfigMigrator();
662
+ const inputConfig = {
663
+ local: {
664
+ devices: [
665
+ { tuyaDeviceId: 'tuyaDeviceId1', ip: '192.168.0.1' },
666
+ { tuyaDeviceId: 'tuyaDeviceId2', ip: 'fe80::213:72ff:fe37:7f6e' },
667
+ { tuyaDeviceId: 'tuyaDeviceId3', ip: '192.168.1' },
668
+ { tuyaDeviceId: 'tuyaDeviceId4', ip: 'fe80::213::72ff:fe37:7f6e' },
669
+ ]
670
+ }
671
+ };
672
+ const outputConfig = {
673
+ local: {
674
+ devices: [
675
+ { tuyaDeviceId: 'tuyaDeviceId1', ip: '192.168.0.1' },
676
+ { tuyaDeviceId: 'tuyaDeviceId2', ip: 'fe80::213:72ff:fe37:7f6e' },
677
+ { tuyaDeviceId: 'tuyaDeviceId3', ip: undefined },
678
+ { tuyaDeviceId: 'tuyaDeviceId4', ip: undefined },
679
+ ]
680
+ }
681
+ };
682
+
683
+ const result = migrator['cleanupConfig'](inputConfig as any);
684
+ expect(result).toEqual(outputConfig);
685
+ });
686
+
687
+ test('delete unused debugLevel', () => {
688
+ const migrator = new ConfigMigrator();
689
+ const inputConfig = {
690
+ common: {
691
+ debug: false,
692
+ debugLevel: 'api'
693
+ }
694
+ };
695
+ const outputConfig = {
696
+ common: {
697
+ debug: false,
698
+ debugLevel: undefined
699
+ }
700
+ };
701
+
702
+ const result = migrator['cleanupConfig'](inputConfig as any);
703
+ expect(result).toEqual(outputConfig);
704
+ });
705
+
706
+ test('initialize undefined device schema', () => {
707
+ const migrator = new ConfigMigrator();
708
+ const inputConfig = {
709
+ cloud: {
710
+ deviceOverrides: [
711
+ {
712
+ id: 'cloudid1',
713
+ }
714
+ ]
715
+ },
716
+ local: {
717
+ devices: [],
718
+ deviceOverrides: [
719
+ {
720
+ id: 'localid1',
721
+ }
722
+ ]
723
+ }
724
+ };
725
+ const outputConfig = {
726
+ cloud: {
727
+ deviceOverrides: [
728
+ {
729
+ id: 'cloudid1',
730
+ schema: []
731
+ }
732
+ ]
733
+ },
734
+ local: {
735
+ devices: [],
736
+ deviceOverrides: [
737
+ {
738
+ id: 'localid1',
739
+ schema: []
740
+ }
741
+ ]
742
+ }
743
+ };
744
+
745
+ const result = migrator['cleanupConfig'](inputConfig as any);
746
+ expect(result).toEqual(outputConfig);
747
+ });
748
+
749
+ test('remove device schema with undefined dp code', () => {
750
+ const migrator = new ConfigMigrator();
751
+ const inputConfig = {
752
+ cloud: {
753
+ deviceOverrides: [
754
+ {
755
+ id: 'cloudid1',
756
+ schema: [
757
+ { code: 'code1' },
758
+ { newCode: 'newcode1' },
759
+ {},
760
+ ]
761
+ }
762
+ ]
763
+ },
764
+ local: {
765
+ devices: [],
766
+ deviceOverrides: [
767
+ {
768
+ id: 'localid1',
769
+ schema: [
770
+ { code: 'code2' },
771
+ { newCode: 'newcode2' },
772
+ {},
773
+ ]
774
+ }
775
+ ]
776
+ }
777
+ };
778
+ const outputConfig = {
779
+ cloud: {
780
+ deviceOverrides: [
781
+ {
782
+ id: 'cloudid1',
783
+ schema: [
784
+ { code: 'code1' },
785
+ ]
786
+ }
787
+ ]
788
+ },
789
+ local: {
790
+ devices: [],
791
+ deviceOverrides: [
792
+ {
793
+ id: 'localid1',
794
+ schema: [
795
+ { code: 'code2' },
796
+ ]
797
+ }
798
+ ]
799
+ }
800
+ };
801
+
802
+ const result = migrator['cleanupConfig'](inputConfig as any);
803
+ expect(result).toEqual(outputConfig);
804
+ });
805
+
806
+ test('remove unset dpMapping', () => {
807
+ const migrator = new ConfigMigrator();
808
+ const inputConfig = {
809
+ local: {
810
+ devices: [ {
811
+ tuyaDeviceId: 'deviceID',
812
+ dpMapping: {
813
+ 'dpCode1': undefined,
814
+ 'dpCode2': 1,
815
+ 'dpCode3': -1
816
+ }
817
+ } ]
818
+ }
819
+ };
820
+ const outputConfig = {
821
+ local: {
822
+ devices: [ {
823
+ tuyaDeviceId: 'deviceID',
824
+ dpMapping: {
825
+ 'dpCode2': 1,
826
+ }
827
+ } ]
828
+ }
829
+ };
830
+
831
+ const result = migrator['cleanupConfig'](inputConfig as any);
832
+ expect(result).toEqual(outputConfig);
833
+ });
834
+ });
835
+
836
+ describe('ConfigMigrator diffConfig', () => {
837
+ beforeEach(() => {
838
+ jest.clearAllMocks();
839
+ });
840
+
841
+ test('no difference. same values, same order.', () => {
842
+ const migrator = new ConfigMigrator();
843
+
844
+ const left = {
845
+ prop1: 'value1',
846
+ prop2: {
847
+ prop2_1: 'value2_1',
848
+ prop2_2: {
849
+ prop2_2_1: 'value2_2_1',
850
+ prop2_2_2: 'value2_2_2',
851
+ prop2_2_3: 'value2_2_3',
852
+ },
853
+ prop2_3: 'value2_3',
854
+ },
855
+ prop3: 'value3',
856
+ };
857
+ const right = {
858
+ prop1: 'value1',
859
+ prop2: {
860
+ prop2_1: 'value2_1',
861
+ prop2_2: {
862
+ prop2_2_1: 'value2_2_1',
863
+ prop2_2_2: 'value2_2_2',
864
+ prop2_2_3: 'value2_2_3',
865
+ },
866
+ prop2_3: 'value2_3',
867
+ },
868
+ prop3: 'value3',
869
+ };
870
+ const result = migrator['diffConfig'](left, right);
871
+ expect(result.length).toBe(0);
872
+ });
873
+
874
+ test('no difference. same values, different order.', () => {
875
+ const migrator = new ConfigMigrator();
876
+
877
+ const left = {
878
+ prop1: 'value1',
879
+ prop2: {
880
+ prop2_1: 'value2_1',
881
+ prop2_2: {
882
+ prop2_2_1: 'value2_2_1',
883
+ prop2_2_2: 'value2_2_2',
884
+ prop2_2_3: 'value2_2_3',
885
+ },
886
+ prop2_3: 'value2_3',
887
+ },
888
+ prop3: 'value3',
889
+ };
890
+ const right = {
891
+ prop2: {
892
+ prop2_2: {
893
+ prop2_2_2: 'value2_2_2',
894
+ prop2_2_3: 'value2_2_3',
895
+ prop2_2_1: 'value2_2_1',
896
+ },
897
+ prop2_3: 'value2_3',
898
+ prop2_1: 'value2_1',
899
+ },
900
+ prop3: 'value3',
901
+ prop1: 'value1',
902
+ };
903
+ const result = migrator['diffConfig'](left, right);
904
+ expect(result.length).toBe(0);
905
+ });
906
+
907
+ test('different values.', () => {
908
+ const migrator = new ConfigMigrator();
909
+
910
+ const left = {
911
+ prop1: 'value1',
912
+ prop2: {
913
+ prop2_1: 'value2_1',
914
+ prop2_2: {
915
+ prop2_2_1: 'value2_2_1',
916
+ prop2_2_2: 'value2_2_2',
917
+ prop2_2_3: 'value2_2_3',
918
+ },
919
+ prop2_3: 'value2_3',
920
+ },
921
+ prop3: 'value3',
922
+ };
923
+ const right = {
924
+ prop1: 'value1_diff',
925
+ prop2: {
926
+ prop2_1: null,
927
+ prop2_2: {
928
+ prop2_2_1: undefined,
929
+ prop2_2_2: 'value2_2_2',
930
+ prop2_2_3: 'value2_2_3',
931
+ },
932
+ prop2_3: 'value2_3',
933
+ },
934
+ prop3: 'value3',
935
+ };
936
+ const result = migrator['diffConfig'](left, right);
937
+ expect(result.length).toBe(3);
938
+ expect(result[0]).toBe("prop1: \"value1\" -> \"value1_diff\"");
939
+ expect(result[1]).toBe("prop2.prop2_1: \"value2_1\" -> null");
940
+ expect(result[2]).toBe("prop2.prop2_2.prop2_2_1: \"value2_2_1\" -> undefined");
941
+ });
942
+
943
+ test('different object.', () => {
944
+ const migrator = new ConfigMigrator();
945
+
946
+ const left = {
947
+ prop1: 'value1',
948
+ prop2: {
949
+ prop2_1: 'value2_1',
950
+ prop2_2: {
951
+ prop2_2_1: 'value2_2_1',
952
+ prop2_2_2: 'value2_2_2',
953
+ prop2_2_3: 'value2_2_3',
954
+ },
955
+ prop2_3: 'value2_3',
956
+ },
957
+ prop3: 'value3',
958
+ };
959
+ const right = {
960
+ prop1: { prop1_1: 'value1_1' },
961
+ prop2: {
962
+ prop2_1: 'value2_1',
963
+ prop2_2: 'value2_2',
964
+ prop2_3: 'value2_3',
965
+ },
966
+ prop3: 'value3',
967
+ };
968
+ const result = migrator['diffConfig'](left, right);
969
+ expect(result.length).toBe(2);
970
+ expect(result[0]).toBe("prop1: \"value1\" -> {\"prop1_1\":\"value1_1\"}");
971
+ expect(result[1]).toBe("prop2.prop2_2: {\"prop2_2_1\":\"value2_2_1\",\"prop2_2_2\":\"value2_2_2\",\"prop2_2_3\":\"value2_2_3\"} -> \"value2_2\"");
972
+ });
973
+
974
+ test('property added.', () => {
975
+ const migrator = new ConfigMigrator();
976
+
977
+ const left = {
978
+ prop1: 'value1',
979
+ prop2: {
980
+ prop2_1: 'value2_1',
981
+ prop2_2: {
982
+ prop2_2_1: 'value2_2_1',
983
+ prop2_2_2: 'value2_2_2',
984
+ prop2_2_3: 'value2_2_3',
985
+ },
986
+ prop2_3: 'value2_3',
987
+ },
988
+ prop3: 'value3',
989
+ };
990
+ const right = {
991
+ prop1: 'value1',
992
+ prop2: {
993
+ prop2_1: 'value2_1',
994
+ prop2_2: {
995
+ prop2_2_1: 'value2_2_1',
996
+ prop2_2_2: 'value2_2_2',
997
+ prop2_2_3: 'value2_2_3',
998
+ prop2_2_4: 'value2_2_4',
999
+ },
1000
+ prop2_3: 'value2_3',
1001
+ prop2_4: 'value2_4',
1002
+ },
1003
+ prop3: 'value3',
1004
+ prop4: 'value4',
1005
+ };
1006
+ const result = migrator['diffConfig'](left, right);
1007
+ expect(result.length).toBe(3);
1008
+ expect(result[0]).toBe("prop2.prop2_2.prop2_2_4: <<undefined>> -> \"value2_2_4\"");
1009
+ expect(result[1]).toBe("prop2.prop2_4: <<undefined>> -> \"value2_4\"");
1010
+ expect(result[2]).toBe("prop4: <<undefined>> -> \"value4\"");
1011
+ });
1012
+
1013
+ test('property deleted.', () => {
1014
+ const migrator = new ConfigMigrator();
1015
+
1016
+ const left = {
1017
+ prop1: 'value1',
1018
+ prop2: {
1019
+ prop2_1: 'value2_1',
1020
+ prop2_2: {
1021
+ prop2_2_1: 'value2_2_1',
1022
+ prop2_2_2: 'value2_2_2',
1023
+ prop2_2_3: 'value2_2_3',
1024
+ },
1025
+ prop2_3: 'value2_3',
1026
+ },
1027
+ prop3: 'value3',
1028
+ };
1029
+ const right = {
1030
+ prop1: 'value1',
1031
+ prop2: {
1032
+ prop2_1: 'value2_1',
1033
+ prop2_2: {
1034
+ prop2_2_1: 'value2_2_1',
1035
+ prop2_2_2: 'value2_2_2',
1036
+ },
1037
+ },
1038
+ };
1039
+ const result = migrator['diffConfig'](left, right);
1040
+ expect(result.length).toBe(3);
1041
+ expect(result[0]).toBe("prop2.prop2_2.prop2_2_3: \"value2_2_3\" -> <<ignored>>");
1042
+ expect(result[1]).toBe("prop2.prop2_3: \"value2_3\" -> <<ignored>>");
1043
+ expect(result[2]).toBe("prop3: \"value3\" -> <<ignored>>");
1044
+ });
1045
+
1046
+ test('undefined property on the right hand will be ignored.', () => {
1047
+ const migrator = new ConfigMigrator();
1048
+
1049
+ const left = {
1050
+ };
1051
+
1052
+ const right = {
1053
+ prop1: undefined
1054
+ };
1055
+ const result = migrator['diffConfig'](left, right);
1056
+ expect(result).toEqual([]);
1057
+ });
1058
+
1059
+ });
1060
+ });