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