@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
@@ -37,14 +37,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const TuyaDevice_1 = __importStar(require("../cloud/device/TuyaDevice"));
40
- const TuyaOpenAPI_1 = __importDefault(require("../cloud/api/TuyaOpenAPI"));
41
- const TuyaDeviceManager_1 = __importDefault(require("../cloud/device/TuyaDeviceManager"));
40
+ const TuyaDeviceManager_1 = __importDefault(require("../shared/TuyaDeviceManager"));
42
41
  const TuyaDiscovery_1 = __importDefault(require("./TuyaDiscovery"));
43
42
  const LocalDevice_1 = __importDefault(require("./LocalDevice"));
44
- const Logger_1 = require("../shared/util/Logger");
45
43
  const ConfigHash_1 = require("../shared/util/ConfigHash");
46
44
  const ZigbeeGatewayDetection_1 = require("./ZigbeeGatewayDetection");
47
45
  const DynamicChildDiscovery_1 = require("./DynamicChildDiscovery");
46
+ const config_1 = require("../config");
48
47
  /**
49
48
  * Default DP-to-code mapping used when the user hasn't supplied one.
50
49
  * Keys are Tuya standard instruction codes; values are typical DP numbers.
@@ -92,12 +91,8 @@ function buildDpToCodeMap(dpMapping) {
92
91
  */
93
92
  class LocalDeviceManager extends TuyaDeviceManager_1.default {
94
93
  // ──────────────────────────────────────────────────────────────────────────
95
- constructor(localConfig, log, persistPath) {
96
- // Create a minimal dummy API so the parent initialises without connecting
97
- const dummyLog = new Logger_1.PrefixLogger(log, 'LocalDummy', false);
98
- const dummyApi = new TuyaOpenAPI_1.default('', '', '', dummyLog);
99
- super(dummyApi, false);
100
- this.localDevices = [];
94
+ constructor(localConfig, debugMode = false) {
95
+ super(debugMode);
101
96
  this.localConnections = new Map();
102
97
  this.discoveryTimeout = null;
103
98
  this.rediscoveryTimeout = null;
@@ -113,20 +108,96 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
113
108
  this.discoveredVersions = new Map();
114
109
  // Pending local response watches to cancel superseded commands
115
110
  this.pendingLocalResponseWatchers = new Map();
116
- // Tracks which devices had config changes
117
- this.configChanged = new Map();
118
111
  // ── Zigbee gateway/child support ──────────────────────────────────────────
119
112
  /** Parent gateway ID → GatewayRelationship (derived from config at init). */
120
113
  this.gatewayRelationships = new Map();
121
114
  /** Parent gateway device ID → its active LocalDevice connection. */
122
115
  this.gatewayConnections = new Map();
123
- // Stop the MQ that the parent started
124
- this.mq.stop();
125
- this.parentLog = log;
126
- this.log = new Logger_1.PrefixLogger(log, 'LocalDeviceManager', false);
127
116
  this.config = localConfig;
128
- this.discovery = new TuyaDiscovery_1.default(log);
129
- this.configHash = new ConfigHash_1.ConfigHash(persistPath || '', 'tuya-local-configs');
117
+ this.discovery = new TuyaDiscovery_1.default();
118
+ }
119
+ async pullDevices() {
120
+ await this.initLocalDevices();
121
+ this.connectAllDevices();
122
+ const devices = [...this.devices];
123
+ this.log.info(`Registered ${devices.length} local device(s).`);
124
+ return devices;
125
+ }
126
+ createDeviceConfigHash(device) {
127
+ const cfg = this.config.devices?.find(config => config.tuyaDeviceId === device.id);
128
+ if (!cfg) {
129
+ return '';
130
+ }
131
+ const effectiveSwitchCount = this._getEffectiveSwitchCount(cfg, device);
132
+ // Check if config has changed since last run
133
+ // Hash the config fields that affect device behavior
134
+ const configToHash = {
135
+ tuyaDeviceId: cfg.tuyaDeviceId,
136
+ name: cfg.name,
137
+ tuyaKey: cfg.tuyaKey,
138
+ dpMapping: this._getDPMap(cfg.tuyaDeviceId),
139
+ switchCount: effectiveSwitchCount,
140
+ outletCount: effectiveSwitchCount,
141
+ protocolVersion: cfg.protocolVersion,
142
+ category: cfg.category,
143
+ };
144
+ return ConfigHash_1.ConfigHash.computeHash(configToHash);
145
+ }
146
+ configDevice(device) {
147
+ const deviceConfig = this.getDeviceConfig(device);
148
+ if (deviceConfig?.category) {
149
+ this.log.warn('Override %o category from %o to %o', device.name, device.category, deviceConfig.category);
150
+ device.category = deviceConfig.category;
151
+ }
152
+ if (deviceConfig?.unbridged) {
153
+ this.log.warn('Unbridge %o category %o', device.name, device.category);
154
+ device.unbridged = deviceConfig.unbridged;
155
+ }
156
+ }
157
+ getDeviceSchemaConfig(device, dpCode) {
158
+ const schema = this.getDeviceConfig(device)?.schema;
159
+ if (!schema || !dpCode) {
160
+ return undefined;
161
+ }
162
+ // ignore case - allow both the device code and its (optional) rename
163
+ const target = dpCode.toString().toLowerCase();
164
+ return schema.find(s => {
165
+ const code = s.code?.toString().toLowerCase();
166
+ const newCode = s.newCode?.toString().toLowerCase();
167
+ return code === target || newCode === target;
168
+ });
169
+ }
170
+ enableGarageDoorUseContactSensorForState(device) {
171
+ return this.getDeviceConfig(device)?.garageDoorUseContactSensorForState === true;
172
+ }
173
+ async retrieveDeviceRTSP(deviceID) {
174
+ const cameraConfig = this.config.cameras?.find(cameraConfig => cameraConfig.deviceId === deviceID);
175
+ if (cameraConfig) {
176
+ const rtspUrl = cameraConfig.rtspUrl;
177
+ if (rtspUrl.includes('@') || !cameraConfig.username) {
178
+ return rtspUrl;
179
+ }
180
+ else {
181
+ return `rtsp://${cameraConfig.username}:${cameraConfig.password}@${rtspUrl.substring('rtsp://'.length)}`;
182
+ }
183
+ }
184
+ return '';
185
+ }
186
+ getDeviceConfig(device) {
187
+ if (!this.config) {
188
+ return undefined;
189
+ }
190
+ // Find matching override, respecting source filtering
191
+ const matches = this.config.deviceOverrides?.filter(config => {
192
+ const sourceMatch = [config_1.TuyaPluginMode.local, config_1.TuyaPluginMode.both].includes(config.configFor);
193
+ const idMatch = config.id === device.id || config.id === device.uuid ||
194
+ config.id === device.product_id || config.id === 'global';
195
+ return sourceMatch && idMatch;
196
+ }) ?? [];
197
+ // Return device-specific config, then product, then global
198
+ return matches.find(config => config.id === device.id || config.id === device.uuid) ||
199
+ matches.find(config => config.id === device.product_id) ||
200
+ matches.find(config => config.id === 'global');
130
201
  }
131
202
  /**
132
203
  * Initialise discovery and register manually configured devices.
@@ -134,9 +205,10 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
134
205
  */
135
206
  async initLocalDevices() {
136
207
  // ── Zigbee: detect parent-child relationships before registering devices ──
137
- if (this.config.devices && this.config.devices.length > 0) {
208
+ const zigbeeDevices = this.config.devices?.filter(ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isZigbeeDevice);
209
+ if (zigbeeDevices && zigbeeDevices.length > 0) {
138
210
  try {
139
- this.gatewayRelationships = ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.detectFromDevices(this.config.devices);
211
+ this.gatewayRelationships = ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.detectFromDevices(zigbeeDevices);
140
212
  if (this.gatewayRelationships.size > 0) {
141
213
  for (const [parentId, rel] of this.gatewayRelationships) {
142
214
  this.log.info(`Zigbee gateway detected: ${parentId} has ${rel.children.length} sub-device(s): ` +
@@ -157,10 +229,13 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
157
229
  if (ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isChild(cfg)) {
158
230
  continue;
159
231
  }
160
- this._registerDeviceConfig(cfg);
232
+ const device = this._registerDeviceConfig(cfg);
233
+ if (!!device) {
234
+ this._syncSubDevices(device);
235
+ }
161
236
  }
162
237
  // Validate devices have keys if auto-discovery is disabled
163
- if (this.config.autoDiscoverDevices === false) {
238
+ if (!this.config.autoDiscoverDevices) {
164
239
  for (const cfg of this.config.devices) {
165
240
  if (!cfg.tuyaKey && !ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isChild(cfg)) {
166
241
  const deviceName = cfg.name || cfg.tuyaDeviceId;
@@ -172,7 +247,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
172
247
  }
173
248
  // Start UDP discovery unless explicitly disabled
174
249
  if (this.config.autoDiscoverDevices !== false) {
175
- this.discovery.on('discover', (result) => {
250
+ this.discovery.on(TuyaDiscovery_1.default.Events.DEVICE_DISCOVER, (result) => {
176
251
  this._onDiscovered(result);
177
252
  });
178
253
  this._startDiscoveryPhase();
@@ -185,6 +260,59 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
185
260
  }
186
261
  }
187
262
  }
263
+ /** sync connection info for sub devices */
264
+ _syncSubDevices(device) {
265
+ // We assume cases where only the parent device has settings and the sub‑device has none, as well as the opposite situation.
266
+ // or maybe a possibility that some cases may still be undetected.
267
+ if (device.sub) {
268
+ const parentDevice = this.devices.find(d => d.id === device.parent_id);
269
+ if (parentDevice) {
270
+ this._syncConnectionInfo(parentDevice, device);
271
+ }
272
+ else {
273
+ const dummyParentDevice = this._buildDiscoveredDevice({
274
+ id: device.parent_id,
275
+ ip: '',
276
+ version: '',
277
+ });
278
+ this._syncConnectionInfo(dummyParentDevice, device);
279
+ this.devices.push(dummyParentDevice);
280
+ }
281
+ }
282
+ else {
283
+ const subDevices = this.devices.filter(d => d.sub && d.parent_id === device.id);
284
+ subDevices.forEach(subDevice => {
285
+ this._syncConnectionInfo(device, subDevice);
286
+ });
287
+ // else no subDevices
288
+ }
289
+ }
290
+ /**
291
+ * parentDevice and subDevice must have same localIp, localKey, localVersion.
292
+ * priority: value exists > value not exists > subDevice > parentDevice
293
+ */
294
+ _syncConnectionInfo(parentDevice, subDevice) {
295
+ const connectionProps = ['localIp', 'localKey', 'localVersion'];
296
+ for (const prop of connectionProps) {
297
+ const parentValue = parentDevice[prop];
298
+ const subValue = subDevice[prop];
299
+ const resolvedValue = this._resolveConnectionInfoValue(parentValue, subValue);
300
+ if (resolvedValue === undefined) {
301
+ continue;
302
+ }
303
+ parentDevice[prop] = resolvedValue;
304
+ subDevice[prop] = resolvedValue;
305
+ }
306
+ }
307
+ _resolveConnectionInfoValue(parentValue, subValue) {
308
+ if (subValue !== undefined && subValue !== null && subValue !== '') {
309
+ return subValue;
310
+ }
311
+ if (parentValue !== undefined && parentValue !== null && parentValue !== '') {
312
+ return parentValue;
313
+ }
314
+ return undefined;
315
+ }
188
316
  /** Stop discovery and disconnect all local TCP connections. */
189
317
  stopLocalDevices() {
190
318
  if (this.discoveryTimeout) {
@@ -207,9 +335,24 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
207
335
  }
208
336
  this.gatewayConnections.clear();
209
337
  }
210
- /** Returns ALL devices (both local shadow entries from this manager). */
211
338
  getDevice(deviceID) {
212
- return this.localDevices.find(d => d.id === deviceID);
339
+ const device = this.devices.find(d => d.id === deviceID);
340
+ if (device) {
341
+ // local devices have synthetic schemas based on config, so enrich the device schema from config on retrieval
342
+ // Merge config schema with device schema, prioritizing config
343
+ device.schema.forEach(s => {
344
+ const configSchema = this.getDeviceConfig(device)?.schema?.find(cs => cs.code === s.code);
345
+ if (configSchema) {
346
+ Object.assign(s, configSchema);
347
+ }
348
+ });
349
+ this.getDeviceConfig(device)?.schema?.forEach(cs => {
350
+ if (!device.schema.some(s => s.code === cs.code)) {
351
+ device.schema.push(cs);
352
+ }
353
+ });
354
+ }
355
+ return device;
213
356
  }
214
357
  /**
215
358
  * Send commands to a local device.
@@ -217,11 +360,12 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
217
360
  * all existing accessory handlers work without modification.
218
361
  */
219
362
  async sendCommands(deviceID, commands) {
220
- const dpMap = this.dpMaps.get(deviceID);
363
+ const dpMap = this._getDPMap(deviceID);
221
364
  if (!dpMap) {
222
365
  this.log.warn(`No dpMapping for local device ${deviceID}`);
223
- return;
366
+ return false;
224
367
  }
368
+ this.log.error(`dpMap:${JSON.stringify(dpMap)}`);
225
369
  const dps = {};
226
370
  for (const { code, value } of commands) {
227
371
  const dp = dpMap[code];
@@ -232,10 +376,10 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
232
376
  dps[String(dp)] = value;
233
377
  }
234
378
  if (Object.keys(dps).length === 0) {
235
- return;
379
+ return false;
236
380
  }
237
381
  // Log the command for user visibility
238
- const device = this.localDevices.find(d => d.id === deviceID);
382
+ const device = this.getDevice(deviceID);
239
383
  const deviceName = device?.name || deviceID;
240
384
  const commandStr = commands.map(c => `${c.code}=${c.value}`).join(', ');
241
385
  this.log.info(`[${deviceName}] Sending command (local): ${commandStr}`);
@@ -251,7 +395,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
251
395
  if (!conn) {
252
396
  conn = this._createConnection(deviceID);
253
397
  if (!conn) {
254
- return;
398
+ return false;
255
399
  }
256
400
  }
257
401
  const expectedDps = new Set(Object.keys(dps));
@@ -312,12 +456,13 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
312
456
  return true;
313
457
  }
314
458
  try {
315
- await responsePromise;
316
- return true;
459
+ return await responsePromise;
317
460
  }
318
461
  catch (error) {
319
462
  this.log.warn(`Local command timeout or error for ${deviceName}: ${error instanceof Error ? error.message : error}`);
320
- throw error;
463
+ // To prevent forcing the caller to implement try‑catch handling, this function will not throw error.
464
+ // throw error;
465
+ return false;
321
466
  }
322
467
  }
323
468
  _cancelSupersededLocalResponseWatchers(deviceID, expectedDps) {
@@ -349,52 +494,11 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
349
494
  this.pendingLocalResponseWatchers.delete(deviceID);
350
495
  }
351
496
  }
352
- handleCloudStatusUpdate(deviceID, statusUpdates) {
353
- const device = this.localDevices.find(d => d.id === deviceID);
354
- if (!device) {
355
- return;
356
- }
357
- const dpMap = this.dpMaps.get(deviceID) ?? {};
358
- const statusDps = new Set();
359
- for (const status of statusUpdates) {
360
- const dp = dpMap[status.code];
361
- statusDps.add(dp !== undefined ? String(dp) : String(status.code));
362
- const existing = device.status.find(s => s.code === status.code);
363
- if (existing) {
364
- existing.value = status.value;
365
- }
366
- else {
367
- device.status.push({ code: status.code, value: status.value });
368
- }
369
- }
370
- const watchers = this.pendingLocalResponseWatchers.get(deviceID);
371
- if (!watchers || watchers.length === 0) {
372
- return;
373
- }
374
- const remaining = [];
375
- for (const watcher of watchers) {
376
- let matched = false;
377
- for (const dp of watcher.expectedDps) {
378
- if (statusDps.has(dp)) {
379
- matched = true;
380
- break;
381
- }
382
- }
383
- if (matched) {
384
- watcher.cleanup();
385
- watcher.resolve(true);
386
- }
387
- else {
388
- remaining.push(watcher);
389
- }
390
- }
391
- if (remaining.length > 0) {
392
- this.pendingLocalResponseWatchers.set(deviceID, remaining);
393
- }
394
- else {
395
- this.pendingLocalResponseWatchers.delete(deviceID);
396
- }
397
- this.emit(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, device, statusUpdates);
497
+ /**
498
+ * Logic to suppress unnecessary discovery processes.
499
+ */
500
+ static isTargetDevice(device) {
501
+ return !device.isVirtualDevice() || !device.sub;
398
502
  }
399
503
  // ── Private helpers ────────────────────────────────────────────────────────
400
504
  /**
@@ -407,7 +511,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
407
511
  }
408
512
  this.isDiscoveryActive = true;
409
513
  this.discoveredInCurrentPhase.clear();
410
- this.log.info('Starting discovery phase');
514
+ this.log.debug('Starting discovery phase');
411
515
  this.discovery.start();
412
516
  // Stop after configurable timeout (config is in seconds, default 60s)
413
517
  // Convert to milliseconds
@@ -420,16 +524,16 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
420
524
  this.discovery.stop();
421
525
  // Report discovery results for clarity
422
526
  if (this.discoveredInCurrentPhase.size > 0) {
423
- this.log.info(`Ended discovery phase – rediscovered ${this.discoveredInCurrentPhase.size} device(s)`);
527
+ this.log.debug(`Ended discovery phase – rediscovered ${this.discoveredInCurrentPhase.size} device(s)`);
424
528
  }
425
529
  else {
426
530
  // No devices found; they may be using cached IPs from previous discovery
427
- const knownDevices = this.localDevices.filter(d => d.localKey);
531
+ const knownDevices = this.devices.filter(d => d.localKey);
428
532
  if (knownDevices.length > 0) {
429
- this.log.info(`Ended discovery phase – no new devices found (${knownDevices.length} configured device(s) using cached IP)`);
533
+ this.log.debug(`Ended discovery phase – no new devices found (${knownDevices.length} configured device(s) using cached IP)`);
430
534
  }
431
535
  else {
432
- this.log.info('Ended discovery phase – no devices found');
536
+ this.log.debug('Ended discovery phase – no devices found');
433
537
  }
434
538
  }
435
539
  this.discoveryTimeout = null;
@@ -457,35 +561,19 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
457
561
  if (!cfg.tuyaDeviceId) {
458
562
  const name = cfg.name ?? 'unknown';
459
563
  this.log.warn(`Skipping invalid local config entry for ${name}: missing tuyaDeviceId`);
460
- return;
461
- }
462
- const effectiveMap = Object.assign({}, DEFAULT_DP_MAP, cfg.dpMapping ?? {});
463
- this.dpMaps.set(cfg.tuyaDeviceId, effectiveMap);
464
- this.reverseDpMaps.set(cfg.tuyaDeviceId, buildDpToCodeMap(effectiveMap));
465
- const existing = this.localDevices.find(d => d.id === cfg.tuyaDeviceId);
466
- // Check if config has changed since last run
467
- // Hash the config fields that affect device behavior
468
- const configToHash = {
469
- tuyaDeviceId: cfg.tuyaDeviceId,
470
- name: cfg.name,
471
- tuyaKey: cfg.tuyaKey,
472
- dpMapping: cfg.dpMapping,
473
- switchCount: cfg.switchCount,
474
- outletCount: cfg.outletCount,
475
- protocolVersion: cfg.protocolVersion,
476
- category: cfg.category,
477
- };
478
- const { changed: configChanged } = this.configHash.hasConfigChanged(cfg.tuyaDeviceId, configToHash);
479
- this.configChanged.set(cfg.tuyaDeviceId, configChanged);
480
- if (configChanged && existing) {
481
- this.log.info(`Device ${cfg.tuyaDeviceId}: config changed, rebuilding schema`);
564
+ return undefined;
482
565
  }
483
- if (!existing) {
484
- const device = this._buildTuyaDevice(cfg, configChanged);
485
- this.localDevices.push(device);
486
- this.devices.push(device); // keep parent's devices array in sync
487
- this.emit(TuyaDeviceManager_1.default.Events.DEVICE_ADD, device);
566
+ const existing = this.getDevice(cfg.tuyaDeviceId);
567
+ if (existing) {
568
+ return existing;
488
569
  }
570
+ const effectiveMap = this._createDPMap(cfg);
571
+ this._setDPMap(cfg.tuyaDeviceId, cfg.parentDeviceId, ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isZigbeeDevice(cfg), effectiveMap);
572
+ const device = this._buildTuyaDevice(cfg);
573
+ this.configDevice(device);
574
+ this.devices.push(device);
575
+ this.emit(TuyaDeviceManager_1.default.Events.DEVICE_ADD, device);
576
+ return device;
489
577
  }
490
578
  /**
491
579
  * Set up auto-detection listener to count switches from device status updates.
@@ -494,7 +582,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
494
582
  */
495
583
  _setupAutoDetectionListener(deviceId, device, conn) {
496
584
  const cfg = this.config.devices?.find(d => d.tuyaDeviceId === deviceId);
497
- if (!cfg || (cfg.switchCount || cfg.outletCount)) {
585
+ if (!cfg || this._getConfiguredSwitchCount(cfg) !== undefined) {
498
586
  return; // Already has explicit count
499
587
  }
500
588
  this.log.debug(`[AutoDetect] Setting up auto-detection listener for ${deviceId}`);
@@ -503,8 +591,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
503
591
  const onStatusChange = (changes) => {
504
592
  this.log.debug(`[AutoDetect] Status change received for ${deviceId}: ${JSON.stringify(Object.keys(changes))}`);
505
593
  // Collect switch numbers from incoming changes
506
- const dpMap = this.dpMaps.get(deviceId) ?? DEFAULT_DP_MAP;
507
- const reverseMap = buildDpToCodeMap(dpMap);
594
+ const reverseMap = this.reverseDpMaps.get(deviceId) ?? {};
508
595
  for (const dpStr of Object.keys(changes)) {
509
596
  const dpNum = parseInt(dpStr, 10);
510
597
  const code = reverseMap[dpNum];
@@ -527,28 +614,12 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
527
614
  this.log.info(`[AutoDetect] Auto-detected ${detectedCount} switch(es) for "${cfg.name}" (${deviceId}): ` +
528
615
  `switches=[${switchList}]`);
529
616
  // Update device schema with detected switch count
530
- device.schema = this._buildSyntheticSchema(this.dpMaps.get(deviceId) ?? DEFAULT_DP_MAP, detectedCount);
531
- device.switchCount = detectedCount;
617
+ device.schema = this._buildSyntheticSchema(device, this._getDPMap(deviceId), detectedCount);
618
+ device.switchCount = this._getEffectiveSwitchCount(cfg) ?? detectedCount;
532
619
  // Auto-detection is now complete
533
620
  device.isAutoDetecting = false;
534
- // Recalculate config hash since the schema has changed
535
- // This ensures SwitchAccessory knows to rebuild services
536
- const updatedConfigToHash = {
537
- tuyaDeviceId: cfg.tuyaDeviceId,
538
- name: cfg.name,
539
- tuyaKey: cfg.tuyaKey,
540
- dpMapping: cfg.dpMapping,
541
- switchCount: detectedCount,
542
- outletCount: cfg.outletCount,
543
- protocolVersion: cfg.protocolVersion,
544
- category: cfg.category,
545
- };
546
- const { changed: schemaChanged } = this.configHash.hasConfigChanged(deviceId, updatedConfigToHash);
547
- if (schemaChanged) {
548
- this.log.debug('[AutoDetect] Schema changed after auto-detection, marking config as changed');
549
- device.configChanged = true;
550
- this.configChanged.set(deviceId, true);
551
- }
621
+ // update config
622
+ cfg.switchCount = device.switchCount;
552
623
  // Notify the platform so configureServices() is re-run and switch_2+ services are added.
553
624
  this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, device, { schemaUpdated: true });
554
625
  }
@@ -568,7 +639,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
568
639
  this.discoveredVersions.set(result.id, result.version);
569
640
  this.discoveredInCurrentPhase.add(result.id);
570
641
  // If we already have a TuyaDevice for this ID (from manual config), update its IP
571
- const existing = this.localDevices.find(d => d.id === result.id);
642
+ const existing = this.getDevice(result.id);
572
643
  if (existing) {
573
644
  this.log.info(`Device ${result.id} (v${result.version}) rediscovered @ ${result.ip} ` +
574
645
  '– updating connection');
@@ -594,14 +665,14 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
594
665
  // New device discovered via UDP – create a placeholder TuyaDevice
595
666
  // (it won't be controllable until the user provides a tuyaKey via config)
596
667
  const device = this._buildDiscoveredDevice(result);
597
- this.localDevices.push(device);
668
+ this.configDevice(device);
598
669
  this.devices.push(device);
599
670
  const productInfo = result.productKey ? ` productKey=${result.productKey}` : '';
600
671
  this.log.info(`Local device discovered: ${result.id} @ ${result.ip} (v${result.version}${productInfo}) ` +
601
672
  '– add to config with tuyaKey to control');
602
673
  this.emit(TuyaDeviceManager_1.default.Events.DEVICE_ADD, device);
603
674
  }
604
- _buildTuyaDevice(cfg, configChanged = false) {
675
+ _buildTuyaDevice(cfg) {
605
676
  const device = new TuyaDevice_1.default({});
606
677
  const deviceId = cfg.tuyaDeviceId ?? '';
607
678
  device.id = deviceId;
@@ -622,11 +693,15 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
622
693
  device.active_time = 0;
623
694
  device.update_time = 0;
624
695
  device.status = [];
625
- const switchCount = cfg.switchCount ?? cfg.outletCount ?? 1;
626
- const autoDetectNote = !cfg.switchCount && !cfg.outletCount && cfg.tuyaKey ? ' (auto-detecting on connect)' : '';
627
- const isAutoDetecting = !!(cfg.tuyaKey && !cfg.switchCount && !cfg.outletCount);
696
+ device.parent_id = cfg.parentDeviceId;
697
+ device.remote_keys = cfg.remote_keys;
698
+ device.sub = !ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isChild(cfg) && !!cfg.parentDeviceId;
699
+ const configuredSwitchCount = this._getConfiguredSwitchCount(cfg);
700
+ const switchCount = configuredSwitchCount ?? 1;
701
+ const autoDetectNote = configuredSwitchCount === undefined && cfg.tuyaKey ? ' (auto-detecting on connect)' : '';
702
+ const isAutoDetecting = !!(cfg.tuyaKey && configuredSwitchCount === undefined);
628
703
  this.log.info(`Building synthetic schema for ${cfg.name} with switchCount=${switchCount}${autoDetectNote}`);
629
- device.schema = this._buildSyntheticSchema(Object.assign({}, DEFAULT_DP_MAP, cfg.dpMapping ?? {}), switchCount);
704
+ device.schema = this._buildSyntheticSchema(device, this._getDPMap(device.id), switchCount);
630
705
  this.log.info(`Device "${cfg.name}" schema includes ${device.schema.length} codes: ${device.schema.map(s => s.code).join(', ')}`);
631
706
  // Store local-specific metadata directly on the device object for later use
632
707
  device.localKey = cfg.tuyaKey;
@@ -638,8 +713,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
638
713
  device.isLocal = true;
639
714
  device.switchCount = switchCount;
640
715
  device.isAutoDetecting = isAutoDetecting;
641
- // Track whether config changed (used by SwitchAccessory to decide whether to remove cached extras)
642
- device.configChanged = configChanged;
716
+ this.log.debug(`_buildTuyaDevice local device:${JSON.stringify(device)}`);
643
717
  return device;
644
718
  }
645
719
  _buildDiscoveredDevice(result) {
@@ -665,11 +739,13 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
665
739
  device.schema = [];
666
740
  device.isLocal = true;
667
741
  device.localVersion = result.version;
742
+ this.log.info(`_buildDiscoveredDevice local device:${JSON.stringify(device)}`);
668
743
  return device;
669
744
  }
670
- _buildSyntheticSchema(dpMapping, switchCount = 1) {
745
+ _buildSyntheticSchema(device, dpMapping, switchCount = 1) {
671
746
  // If switchCount is limited, filter switch-related entries to only the needed ones
672
747
  let filteredMapping = dpMapping;
748
+ this.log.error(`deviceID:${device.id}, switchCount:${switchCount}`);
673
749
  if (switchCount > 0) {
674
750
  // Only include switch_1 through switch_N where N = switchCount
675
751
  // Also exclude other switch variants (like switch_led, switch_inching) if limited
@@ -694,45 +770,84 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
694
770
  this.log.debug(`Filtered switches: kept [${switchCodes.join(', ')}], excluded others`);
695
771
  }
696
772
  }
697
- return Object.keys(filteredMapping).map(code => ({
773
+ this.log.debug(`local device schema codes:${JSON.stringify(filteredMapping)}`);
774
+ const schemas = Object.keys(filteredMapping).map(code => ({
698
775
  code,
699
776
  mode: TuyaDevice_1.TuyaDeviceSchemaMode.READ_WRITE,
700
777
  type: TuyaDevice_1.TuyaDeviceSchemaType.Boolean, // best-effort default; handlers may override
701
778
  property: {},
702
779
  }));
780
+ // apply overrides. The synthetic schemas are keyed on the (possibly renamed)
781
+ // dp code, so match on newCode when a rename is set, otherwise on the device code.
782
+ this.getDeviceConfig(device)?.schema?.forEach(s => {
783
+ if (!s.code) {
784
+ return;
785
+ }
786
+ const targetCode = s.newCode?.trim() || s.code;
787
+ if (!targetCode) {
788
+ return;
789
+ }
790
+ const schema = schemas.find(t => t.code === targetCode);
791
+ if (!!schema) {
792
+ schema.type = s.type ?? schema.type;
793
+ schema.property = s.property ?? schema.property;
794
+ }
795
+ else {
796
+ schemas.push({
797
+ code: targetCode,
798
+ mode: TuyaDevice_1.TuyaDeviceSchemaMode.READ_WRITE,
799
+ type: s.type ?? TuyaDevice_1.TuyaDeviceSchemaType.Boolean,
800
+ property: s.property ?? {},
801
+ });
802
+ }
803
+ });
804
+ this.log.error('schema:%o', schemas);
805
+ return schemas;
703
806
  }
704
807
  _createConnection(deviceID) {
705
- const device = this.localDevices.find(d => d.id === deviceID);
808
+ let device = this.getDevice(deviceID);
706
809
  if (!device) {
707
810
  return undefined;
708
811
  }
709
- const localKey = device.localKey;
812
+ // Since the sub‑device’s actual entity is the parent device, no separate connection is established for the sub‑device.
813
+ if (device.sub) {
814
+ const parentDevice = this.getDevice(device.parent_id);
815
+ if (parentDevice) {
816
+ device = parentDevice;
817
+ }
818
+ else {
819
+ this.log.warn(`parent device not found. parentDeviceID: ${device.parent_id}, subDeviceID: ${deviceID}.`);
820
+ this.log.warn('sub device will be used as parent device.');
821
+ }
822
+ }
823
+ const connectDeviceID = device.id;
824
+ let localKey = device.localKey;
710
825
  if (!localKey) {
711
- this.log.warn(`No tuyaKey configured for local device ${deviceID} — cannot connect.`);
826
+ this.log.warn(`No tuyaKey configured for local device ${connectDeviceID} — cannot connect.`);
712
827
  return undefined;
713
828
  }
714
- const ip = device.ip || device.localIp;
829
+ const ip = device.localIp ?? device.ip;
715
830
  if (!ip) {
716
- this.log.warn(`No IP known for local device ${deviceID} — cannot connect.`);
831
+ this.log.warn(`No IP known for local device ${connectDeviceID} — cannot connect.`);
717
832
  return undefined;
718
833
  }
719
834
  const version = device.localVersion
720
- ?? this.discoveredVersions.get(deviceID)
835
+ ?? this.discoveredVersions.get(connectDeviceID)
721
836
  ?? '3.3';
722
837
  const conn = new LocalDevice_1.default({
723
- id: deviceID,
838
+ id: connectDeviceID,
724
839
  key: Buffer.from(localKey, 'utf8'),
725
840
  ip,
726
841
  version,
727
842
  name: device.name,
728
- }, this.parentLog);
843
+ });
729
844
  conn.on('connect', () => {
730
845
  device.online = true;
731
846
  this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, device, { online: true });
732
847
  // ── Zigbee: when a gateway connects, set up its children ──
733
- if (this.gatewayRelationships.has(deviceID)) {
734
- this.gatewayConnections.set(deviceID, conn);
735
- this._setupZigbeeChildren(deviceID, conn);
848
+ if (this.gatewayRelationships.has(connectDeviceID)) {
849
+ this.gatewayConnections.set(connectDeviceID, conn);
850
+ this._setupZigbeeChildren(connectDeviceID, conn);
736
851
  }
737
852
  });
738
853
  conn.on('disconnect', () => {
@@ -740,7 +855,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
740
855
  this.emit(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, device, { online: false });
741
856
  });
742
857
  conn.on('change', (changes) => {
743
- const reverseMap = this.reverseDpMaps.get(deviceID) ?? {};
858
+ const reverseMap = this.reverseDpMaps.get(connectDeviceID) ?? {};
744
859
  const statusUpdates = [];
745
860
  for (const [dp, val] of Object.entries(changes)) {
746
861
  const code = reverseMap[parseInt(dp, 10)] ?? dp;
@@ -759,12 +874,12 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
759
874
  }
760
875
  });
761
876
  conn.on('error', (err) => {
762
- this.log.warn(`Error for local device ${deviceID}: ${err.message}`);
877
+ this.log.warn(`Error for local device ${connectDeviceID}: ${err.message}`);
763
878
  });
764
879
  // Set up auto-detection listener BEFORE connecting to avoid race conditions
765
- this._setupAutoDetectionListener(deviceID, device, conn);
880
+ this._setupAutoDetectionListener(connectDeviceID, device, conn);
766
881
  conn.connect();
767
- this.localConnections.set(deviceID, conn);
882
+ this.localConnections.set(connectDeviceID, conn);
768
883
  return conn;
769
884
  }
770
885
  /**
@@ -797,7 +912,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
797
912
  * Preserves the child's state and event listeners while updating the parent reference.
798
913
  */
799
914
  _reconnectZigbeeChild(childDeviceId, cid, childConn, newParentConn) {
800
- const childTuyaDevice = this.localDevices.find(d => d.id === childDeviceId);
915
+ const childTuyaDevice = this.getDevice(childDeviceId);
801
916
  if (!childTuyaDevice) {
802
917
  this.log.warn(`Zigbee child ${childDeviceId}: TuyaDevice not found during reconnect`);
803
918
  return;
@@ -837,10 +952,10 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
837
952
  this.log.debug(`Zigbee child ${childDeviceId}: using per-child category override`);
838
953
  }
839
954
  // Register TuyaDevice if not already present
840
- if (!this.localDevices.find(d => d.id === childDeviceId)) {
955
+ if (!this.getDevice(childDeviceId)) {
841
956
  this._registerDeviceConfig(effectiveChildCfg);
842
957
  }
843
- const childTuyaDevice = this.localDevices.find(d => d.id === childDeviceId);
958
+ const childTuyaDevice = this.getDevice(childDeviceId);
844
959
  if (!childTuyaDevice) {
845
960
  this.log.warn(`Could not build TuyaDevice for Zigbee child ${childDeviceId}`);
846
961
  return;
@@ -852,7 +967,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
852
967
  ip: parentConn['context'].ip,
853
968
  version: parentConn['context'].version,
854
969
  name: cfg.name ?? childDeviceId,
855
- }, this.parentLog);
970
+ });
856
971
  // Link parent ↔ child
857
972
  childConn.parentDevice = parentConn;
858
973
  childConn.childId = cid;
@@ -958,7 +1073,7 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
958
1073
  }
959
1074
  /** Connect all configured local devices eagerly (called after init). */
960
1075
  connectAllDevices() {
961
- for (const device of this.localDevices) {
1076
+ for (const device of this.devices) {
962
1077
  const localKey = device.localKey;
963
1078
  if (!localKey) {
964
1079
  continue; // Skip if no local key configured
@@ -976,6 +1091,88 @@ class LocalDeviceManager extends TuyaDeviceManager_1.default {
976
1091
  }
977
1092
  }
978
1093
  }
1094
+ _getConfiguredSwitchCount(cfg) {
1095
+ if (!cfg) {
1096
+ return undefined;
1097
+ }
1098
+ if (cfg.switchCount !== undefined) {
1099
+ return cfg.switchCount;
1100
+ }
1101
+ if (cfg.outletCount !== undefined) {
1102
+ return cfg.outletCount;
1103
+ }
1104
+ return undefined;
1105
+ }
1106
+ _getEffectiveSwitchCount(cfg, device) {
1107
+ const configuredSwitchCount = this._getConfiguredSwitchCount(cfg);
1108
+ if (configuredSwitchCount !== undefined) {
1109
+ return configuredSwitchCount;
1110
+ }
1111
+ const deviceCount = device ? device.switchCount
1112
+ ?? device.outletCount
1113
+ : undefined;
1114
+ return deviceCount;
1115
+ }
1116
+ _createDPMap(cfg) {
1117
+ const effectiveMap = Object.assign({}, cfg.dpMapping, Object.fromEntries(cfg.dpMappingArray?.map(item => [item.dpCode, item.dpID]) ?? []));
1118
+ if (Object.keys(effectiveMap).length === 0) {
1119
+ Object.assign(effectiveMap, DEFAULT_DP_MAP);
1120
+ }
1121
+ // override dpcodes
1122
+ for (const [dpCode, dpID] of Object.entries(effectiveMap)) {
1123
+ const schemaConfig = this.getDeviceSchemaConfig({ id: cfg.tuyaDeviceId }, dpCode);
1124
+ if (!schemaConfig?.newCode) {
1125
+ continue;
1126
+ }
1127
+ // Only treat newCode as a rename when it actually differs from the device code.
1128
+ // Renaming to the same key would set-then-delete it, dropping the dp↔code mapping.
1129
+ if (schemaConfig.newCode && schemaConfig.newCode !== dpCode) {
1130
+ effectiveMap[schemaConfig.newCode] = dpID;
1131
+ delete effectiveMap[dpCode];
1132
+ }
1133
+ const targetCode = schemaConfig.newCode.trim();
1134
+ if (!targetCode || targetCode === dpCode) {
1135
+ continue;
1136
+ }
1137
+ effectiveMap[targetCode] = dpID;
1138
+ delete effectiveMap[dpCode];
1139
+ }
1140
+ return effectiveMap;
1141
+ }
1142
+ _setDPMap(deviceID, parentDeviceId, isZigbeeDevice, dpMap) {
1143
+ // Because sub‑devices have no actual entity, they are registered as part of the parent device’s information.
1144
+ // Since there is only one real device,
1145
+ // configuring each sub‑device individually is unnecessary—and even if you did, the settings would end up identical.
1146
+ // If any inconsistencies occur, the values are merged, with later definitions taking precedence.
1147
+ const isSubDevice = !isZigbeeDevice && !!parentDeviceId;
1148
+ if (isSubDevice) {
1149
+ const parentDPMap = this._getDPMap(parentDeviceId);
1150
+ const mergedDPMap = { ...parentDPMap, ...dpMap };
1151
+ this.dpMaps.set(parentDeviceId, mergedDPMap);
1152
+ this.reverseDpMaps.set(parentDeviceId, buildDpToCodeMap(mergedDPMap));
1153
+ }
1154
+ else {
1155
+ this.dpMaps.set(deviceID, dpMap);
1156
+ this.reverseDpMaps.set(deviceID, buildDpToCodeMap(dpMap));
1157
+ }
1158
+ }
1159
+ _getDPMap(deviceID) {
1160
+ let localConfig = this.config.devices?.find(config => config.tuyaDeviceId === deviceID);
1161
+ if (!localConfig) {
1162
+ // ZigbeeChildDevice or HubSubDevice
1163
+ localConfig = this.config.devices?.find(config => config.parentDeviceId === deviceID);
1164
+ }
1165
+ if (!localConfig) {
1166
+ // A Local device almost never operates without configuration, but this is just in case.
1167
+ return DEFAULT_DP_MAP;
1168
+ }
1169
+ if (!ZigbeeGatewayDetection_1.ZigbeeGatewayDetection.isChild(localConfig) && localConfig.parentDeviceId) {
1170
+ return this.dpMaps.get(localConfig.parentDeviceId) ?? DEFAULT_DP_MAP;
1171
+ }
1172
+ else {
1173
+ return this.dpMaps.get(deviceID) ?? DEFAULT_DP_MAP;
1174
+ }
1175
+ }
979
1176
  }
980
1177
  exports.default = LocalDeviceManager;
981
1178
  //# sourceMappingURL=LocalDeviceManager.js.map