@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.
- package/README.md +354 -142
- package/config.schema.json +1221 -893
- package/dist/cloud/api/TuyaOpenAPI.js +108 -60
- package/dist/cloud/api/TuyaOpenAPI.js.map +1 -1
- package/dist/cloud/api/TuyaOpenMQ.js +45 -22
- package/dist/cloud/api/TuyaOpenMQ.js.map +1 -1
- package/dist/cloud/device/{TuyaDeviceManager.js → TuyaCloudDeviceManager.js} +146 -96
- package/dist/cloud/device/TuyaCloudDeviceManager.js.map +1 -0
- package/dist/cloud/device/TuyaCustomDeviceManager.js +75 -5
- package/dist/cloud/device/TuyaCustomDeviceManager.js.map +1 -1
- package/dist/cloud/device/TuyaDevice.js.map +1 -1
- package/dist/cloud/device/TuyaHomeDeviceManager.js +81 -13
- package/dist/cloud/device/TuyaHomeDeviceManager.js.map +1 -1
- package/dist/config.js +26 -5
- package/dist/config.js.map +1 -1
- package/dist/local/DynamicChildDiscovery.js +2 -1
- package/dist/local/DynamicChildDiscovery.js.map +1 -1
- package/dist/local/LocalDevice.js +16 -3
- package/dist/local/LocalDevice.js.map +1 -1
- package/dist/local/LocalDeviceManager.js +361 -164
- package/dist/local/LocalDeviceManager.js.map +1 -1
- package/dist/local/TuyaDiscovery.js +16 -7
- package/dist/local/TuyaDiscovery.js.map +1 -1
- package/dist/local/ZigbeeGatewayDetection.js +7 -4
- package/dist/local/ZigbeeGatewayDetection.js.map +1 -1
- package/dist/local/protocol/ProtocolUtilities.js +34 -17
- package/dist/local/protocol/ProtocolUtilities.js.map +1 -1
- package/dist/local/protocol/ProtocolV31V32.js +4 -2
- package/dist/local/protocol/ProtocolV31V32.js.map +1 -1
- package/dist/local/protocol/ProtocolV33.js +4 -2
- package/dist/local/protocol/ProtocolV33.js.map +1 -1
- package/dist/local/protocol/ProtocolV34.js +10 -5
- package/dist/local/protocol/ProtocolV34.js.map +1 -1
- package/dist/local/protocol/ProtocolV35.js +8 -4
- package/dist/local/protocol/ProtocolV35.js.map +1 -1
- package/dist/platform.js +253 -493
- package/dist/platform.js.map +1 -1
- package/dist/settings.js +0 -2
- package/dist/settings.js.map +1 -1
- package/dist/shared/TuyaDeviceManager.js +147 -0
- package/dist/shared/TuyaDeviceManager.js.map +1 -0
- package/dist/shared/TuyaHybridDeviceManager.js +215 -0
- package/dist/shared/TuyaHybridDeviceManager.js.map +1 -0
- package/dist/shared/accessory/AccessoryFactory.js +350 -0
- package/dist/shared/accessory/AccessoryFactory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/AirConditionerAccessory.js +21 -15
- package/dist/shared/accessory/AirConditionerAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/AirPurifierAccessory.js.map +1 -1
- package/dist/shared/accessory/AirQualitySensorAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/BaseAccessory.js +16 -95
- package/dist/shared/accessory/BaseAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/BlindsAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/CameraAccessory.js +2 -2
- package/dist/shared/accessory/CameraAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/CarbonDioxideSensorAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/CarbonMonoxideSensorAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/ContactSensorAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/DehumidifierAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/DiffuserAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/DimmerAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/DoorbellAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/ExtractionHoodAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/FanAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/GarageDoorAccessory.js +19 -2
- package/dist/shared/accessory/GarageDoorAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/HeaterAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/HeaterAccessory_old.js.map +1 -1
- package/dist/shared/accessory/HumanPresenceSensorAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/HumidifierAccessory.js.map +1 -1
- package/dist/shared/accessory/IRAdapter.js +167 -0
- package/dist/shared/accessory/IRAdapter.js.map +1 -0
- package/dist/shared/accessory/IRAirConditionerAccessory.js.map +1 -0
- package/dist/shared/accessory/IRControlHubAccessory.js.map +1 -0
- package/dist/shared/accessory/IRControlHubSubAccessory.js.map +1 -0
- package/dist/shared/accessory/IRFanAccessory.js +90 -0
- package/dist/shared/accessory/IRFanAccessory.js.map +1 -0
- package/dist/shared/accessory/IRGenericAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/LeakSensorAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/LightAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/LightSensorAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/LocationWeatherAccessory.js +1 -1
- package/dist/shared/accessory/LocationWeatherAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/LockAccessory.js +3 -3
- package/dist/shared/{accessories → accessory}/LockAccessory.js.map +1 -1
- package/dist/shared/accessory/MotionSensorAccessory.js.map +1 -0
- package/dist/shared/accessory/OutletAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/PetFeederAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/SceneAccessory.js +1 -1
- package/dist/shared/accessory/SceneAccessory.js.map +1 -0
- package/dist/shared/accessory/SceneSwitchAccessory.js.map +1 -0
- package/dist/shared/accessory/SecuritySystemAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/SmokeSensorAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/SwitchAccessory.js +1 -45
- package/dist/shared/accessory/SwitchAccessory.js.map +1 -0
- package/dist/shared/accessory/TemperatureHumiditySensorAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/ThermostatAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/TowerRackAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/ValveAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/VibrationSensorAccessory.js.map +1 -1
- package/dist/shared/accessory/WeatherStationAccessory.js.map +1 -0
- package/dist/shared/accessory/WetBulbGlobeTemperatureAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/WhiteNoiseLightAccessory.js +1 -0
- package/dist/shared/accessory/WhiteNoiseLightAccessory.js.map +1 -0
- package/dist/shared/accessory/WindowAccessory.js.map +1 -0
- package/dist/shared/{accessories → accessory}/WindowCoveringAccessory.js.map +1 -1
- package/dist/shared/{accessories → accessory}/WirelessSwitchAccessory.js.map +1 -1
- package/dist/shared/accessory/characteristic/Active.js.map +1 -0
- package/dist/shared/accessory/characteristic/AirQuality.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/CurrentRelativeHumidity.js +3 -0
- package/dist/shared/accessory/characteristic/CurrentRelativeHumidity.js.map +1 -0
- package/dist/shared/accessory/characteristic/CurrentTemperature.js.map +1 -0
- package/dist/shared/accessory/characteristic/CurrentWeather.js.map +1 -0
- package/dist/shared/accessory/characteristic/CurrentWeatherByOpenMeteo.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/CurrentWetBulbGlobeTemperature.js +3 -2
- package/dist/shared/accessory/characteristic/CurrentWetBulbGlobeTemperature.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/EnergyUsage.js +0 -5
- package/dist/shared/accessory/characteristic/EnergyUsage.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/Light.js +28 -5
- package/dist/shared/accessory/characteristic/Light.js.map +1 -0
- package/dist/shared/accessory/characteristic/LightSensor.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/LockPhysicalControls.js +3 -0
- package/dist/shared/accessory/characteristic/LockPhysicalControls.js.map +1 -0
- package/dist/shared/accessory/characteristic/MotionDetected.js.map +1 -0
- package/dist/shared/accessory/characteristic/Name.js.map +1 -0
- package/dist/shared/accessory/characteristic/OccupancyDetected.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/On.js +1 -0
- package/dist/shared/accessory/characteristic/On.js.map +1 -0
- package/dist/shared/accessory/characteristic/OutletInUse.js.map +1 -0
- package/dist/shared/accessory/characteristic/ProgrammableSwitchEvent.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/RelativeHumidityDehumidifierThreshold.js +3 -0
- package/dist/shared/accessory/characteristic/RelativeHumidityDehumidifierThreshold.js.map +1 -0
- package/dist/shared/accessory/characteristic/RotationSpeed.js.map +1 -0
- package/dist/shared/accessory/characteristic/SecuritySystemState.js.map +1 -0
- package/dist/shared/{accessories → accessory}/characteristic/SwingMode.js +3 -0
- package/dist/shared/accessory/characteristic/SwingMode.js.map +1 -0
- package/dist/shared/accessory/characteristic/TargetTemperature.js.map +1 -0
- package/dist/shared/accessory/characteristic/TemperatureDisplayUnits.js.map +1 -0
- package/dist/shared/util/ConfigHash.js +2 -2
- package/dist/shared/util/ConfigHash.js.map +1 -1
- package/dist/shared/util/ConfigMigrator.js +222 -0
- package/dist/shared/util/ConfigMigrator.js.map +1 -0
- package/dist/shared/util/FfmpegStreamingProcess.js +62 -0
- package/dist/shared/util/FfmpegStreamingProcess.js.map +1 -1
- package/dist/shared/util/InfraredTool.js +0 -2
- package/dist/shared/util/InfraredTool.js.map +1 -1
- package/dist/shared/util/Logger.js +85 -10
- package/dist/shared/util/Logger.js.map +1 -1
- package/dist/shared/util/TuyaStreamDelegate.js +194 -97
- package/dist/shared/util/TuyaStreamDelegate.js.map +1 -1
- package/dist/shared/util/util.js +59 -4
- package/dist/shared/util/util.js.map +1 -1
- package/docs/ADVANCED_OPTIONS.md +603 -0
- package/docs/CONFIG_GUIDE.md +616 -0
- package/docs/Get-Local-Keys-for-Your-Devices.md +246 -0
- package/{SUPPORTED_DEVICES.md → docs/SUPPORTED_DEVICES.md} +24 -3
- package/eslint.config.mjs +0 -1
- package/{jest.config.js → jest.config.ts} +5 -2
- package/package.json +12 -9
- package/test/ConfigMigrator.test.ts +1060 -0
- package/test/Logger.test.ts +482 -0
- package/test/accessoryFactory.data.test.ts +172 -0
- package/test/accessoryFactory.test.ts +64 -35
- package/test/airConditioner.test.ts +742 -0
- package/test/config.test.ts +17 -13
- package/test/custom.test.ts +2 -2
- package/test/errorHandling.test.ts +12 -12
- package/test/home.test.ts +1 -1
- package/test/hybridDeviceManager.test.ts +808 -0
- package/test/light.test.ts +250 -0
- package/test/localDevice.test.ts +21 -50
- package/test/localDeviceManager.behavior.test.ts +196 -31
- package/test/localDeviceManager.test.ts +124 -41
- package/test/mqttMessages.test.ts +33 -14
- package/test/platform.test.ts +337 -814
- package/test/platform.validation.local.test.ts +58 -119
- package/test/tuyaCustomDeviceManager.test.ts +106 -16
- package/test/tuyaDeviceManager.test.ts +636 -639
- package/test/tuyaDiscovery.test.ts +21 -33
- package/test/tuyaHomeDeviceManager.test.ts +109 -18
- package/test/tuyaOpenAPI.test.ts +25 -44
- package/test/tuyaOpenMQ.test.ts +15 -23
- package/test/util.test.ts +107 -7
- package/test/zigbee/LocalDevice.zigbee.test.ts +14 -18
- package/test/zigbee/LocalDeviceManager.cloud-discovery.test.ts +19 -20
- package/test/zigbee/LocalDeviceManager.zigbee.reconnect.test.ts +19 -20
- package/test/zigbee/LocalDeviceManager.zigbee.test.ts +73 -35
- package/test/zigbee/ZigbeeGatewayDetection.test.ts +2 -2
- package/ADVANCED_OPTIONS.md +0 -267
- package/dist/cloud/device/TuyaDeviceManager.js.map +0 -1
- package/dist/local/CloudLocalReconciliation.js +0 -180
- package/dist/local/CloudLocalReconciliation.js.map +0 -1
- package/dist/shared/AccessoryFactory.js +0 -276
- package/dist/shared/AccessoryFactory.js.map +0 -1
- package/dist/shared/accessories/AccessoryFactory.js +0 -305
- package/dist/shared/accessories/AccessoryFactory.js.map +0 -1
- package/dist/shared/accessories/AirConditionerAccessory.js.map +0 -1
- package/dist/shared/accessories/AirQualitySensorAccessory.js.map +0 -1
- package/dist/shared/accessories/BaseAccessory.js.map +0 -1
- package/dist/shared/accessories/CameraAccessory.js.map +0 -1
- package/dist/shared/accessories/GarageDoorAccessory.js.map +0 -1
- package/dist/shared/accessories/HumanPresenceSensorAccessory.js.map +0 -1
- package/dist/shared/accessories/IRAirConditionerAccessory.js.map +0 -1
- package/dist/shared/accessories/IRControlHubAccessory.js.map +0 -1
- package/dist/shared/accessories/IRControlHubSubAccessory.js.map +0 -1
- package/dist/shared/accessories/IRGenericAccessory.js.map +0 -1
- package/dist/shared/accessories/LocationWeatherAccessory.js.map +0 -1
- package/dist/shared/accessories/MotionSensorAccessory.js.map +0 -1
- package/dist/shared/accessories/OutletAccessory.js.map +0 -1
- package/dist/shared/accessories/SceneAccessory.js.map +0 -1
- package/dist/shared/accessories/SceneSwitchAccessory.js.map +0 -1
- package/dist/shared/accessories/SecuritySystemAccessory.js.map +0 -1
- package/dist/shared/accessories/SwitchAccessory.js.map +0 -1
- package/dist/shared/accessories/TemperatureHumiditySensorAccessory.js.map +0 -1
- package/dist/shared/accessories/WeatherStationAccessory.js.map +0 -1
- package/dist/shared/accessories/WetBulbGlobeTemperatureAccessory.js.map +0 -1
- package/dist/shared/accessories/WhiteNoiseLightAccessory.js.map +0 -1
- package/dist/shared/accessories/WindowAccessory.js.map +0 -1
- package/dist/shared/accessories/characteristic/Active.js.map +0 -1
- package/dist/shared/accessories/characteristic/AirQuality.js.map +0 -1
- package/dist/shared/accessories/characteristic/CurrentRelativeHumidity.js.map +0 -1
- package/dist/shared/accessories/characteristic/CurrentTemperature.js.map +0 -1
- package/dist/shared/accessories/characteristic/CurrentWeather.js.map +0 -1
- package/dist/shared/accessories/characteristic/CurrentWeatherByOpenMeteo.js.map +0 -1
- package/dist/shared/accessories/characteristic/CurrentWetBulbGlobeTemperature.js.map +0 -1
- package/dist/shared/accessories/characteristic/EnergyUsage.js.map +0 -1
- package/dist/shared/accessories/characteristic/Light.js.map +0 -1
- package/dist/shared/accessories/characteristic/LightSensor.js.map +0 -1
- package/dist/shared/accessories/characteristic/LockPhysicalControls.js.map +0 -1
- package/dist/shared/accessories/characteristic/MotionDetected.js.map +0 -1
- package/dist/shared/accessories/characteristic/Name.js.map +0 -1
- package/dist/shared/accessories/characteristic/OccupancyDetected.js.map +0 -1
- package/dist/shared/accessories/characteristic/On.js.map +0 -1
- package/dist/shared/accessories/characteristic/OutletInUse.js.map +0 -1
- package/dist/shared/accessories/characteristic/ProgrammableSwitchEvent.js.map +0 -1
- package/dist/shared/accessories/characteristic/RelativeHumidityDehumidifierThreshold.js.map +0 -1
- package/dist/shared/accessories/characteristic/RotationSpeed.js.map +0 -1
- package/dist/shared/accessories/characteristic/SecuritySystemState.js.map +0 -1
- package/dist/shared/accessories/characteristic/SwingMode.js.map +0 -1
- package/dist/shared/accessories/characteristic/TargetTemperature.js.map +0 -1
- package/dist/shared/accessories/characteristic/TemperatureDisplayUnits.js.map +0 -1
- package/test/zigbee/CloudLocalReconciliation.test.ts +0 -284
- package/tuya-local-configs/dev-1.hash +0 -1
- package/tuya-local-configs/device1.hash +0 -1
- package/wiki/Get-Local-Keys-for-Your-Devices.md +0 -85
- /package/dist/shared/{accessories → accessory}/AirPurifierAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/AirQualitySensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/BlindsAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/CarbonDioxideSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/CarbonMonoxideSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/ContactSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/DehumidifierAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/DiffuserAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/DimmerAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/DoorbellAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/ExtractionHoodAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/FanAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/HeaterAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/HeaterAccessory_old.js +0 -0
- /package/dist/shared/{accessories → accessory}/HumanPresenceSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/HumidifierAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/IRAirConditionerAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/IRControlHubAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/IRControlHubSubAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/IRGenericAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/LeakSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/LightAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/LightSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/MotionSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/OutletAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/PetFeederAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/SceneSwitchAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/SecuritySystemAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/SmokeSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/TemperatureHumiditySensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/ThermostatAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/TowerRackAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/ValveAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/VibrationSensorAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/WeatherStationAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/WetBulbGlobeTemperatureAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/WindowAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/WindowCoveringAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/WirelessSwitchAccessory.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/Active.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/AirQuality.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/CurrentTemperature.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/CurrentWeather.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/CurrentWeatherByOpenMeteo.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/LightSensor.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/MotionDetected.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/Name.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/OccupancyDetected.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/OutletInUse.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/ProgrammableSwitchEvent.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/RotationSpeed.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/SecuritySystemState.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/TargetTemperature.js +0 -0
- /package/dist/shared/{accessories → accessory}/characteristic/TemperatureDisplayUnits.js +0 -0
package/dist/platform.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -40,18 +7,23 @@ exports.TuyaPlatform = void 0;
|
|
|
40
7
|
const jsonschema_1 = require("jsonschema");
|
|
41
8
|
const path_1 = __importDefault(require("path"));
|
|
42
9
|
const fs_1 = __importDefault(require("fs"));
|
|
43
|
-
const
|
|
10
|
+
const uuid_1 = require("uuid");
|
|
11
|
+
// Cloud imports (from src/cloud/)
|
|
12
|
+
const TuyaDevice_1 = __importDefault(require("./cloud/device/TuyaDevice"));
|
|
44
13
|
const TuyaCustomDeviceManager_1 = __importDefault(require("./cloud/device/TuyaCustomDeviceManager"));
|
|
45
14
|
const TuyaHomeDeviceManager_1 = __importDefault(require("./cloud/device/TuyaHomeDeviceManager"));
|
|
46
|
-
const TuyaOpenAPI_1 =
|
|
15
|
+
const TuyaOpenAPI_1 = __importDefault(require("./cloud/api/TuyaOpenAPI"));
|
|
47
16
|
// Local imports (from src/local/)
|
|
48
17
|
const LocalDeviceManager_1 = __importDefault(require("./local/LocalDeviceManager"));
|
|
49
18
|
// Shared imports (from src/shared/)
|
|
50
|
-
const
|
|
19
|
+
const TuyaDeviceManager_1 = __importDefault(require("./shared/TuyaDeviceManager"));
|
|
20
|
+
const ConfigMigrator_1 = __importDefault(require("./shared/util/ConfigMigrator"));
|
|
21
|
+
const AccessoryFactory_1 = __importDefault(require("./shared/accessory/AccessoryFactory"));
|
|
51
22
|
const util_1 = require("./shared/util/util");
|
|
52
|
-
const ConfigHash_1 = require("./shared/util/ConfigHash");
|
|
53
23
|
const settings_1 = require("./settings");
|
|
54
24
|
const config_1 = require("./config");
|
|
25
|
+
const Logger_1 = require("./shared/util/Logger");
|
|
26
|
+
const TuyaHybridDeviceManager_1 = __importDefault(require("./shared/TuyaHybridDeviceManager"));
|
|
55
27
|
/**
|
|
56
28
|
* TuyaPlatform — unified Homebridge platform supporting both Tuya Cloud (REST/MQTT)
|
|
57
29
|
* and Tuya Local (direct LAN TCP) device communication.
|
|
@@ -62,9 +34,10 @@ const config_1 = require("./config");
|
|
|
62
34
|
* "both" — cloud + local simultaneously
|
|
63
35
|
*/
|
|
64
36
|
class TuyaPlatform {
|
|
37
|
+
static { this.PLATFORM_UUID = '8CC2405F-4DB0-4586-B32F-A38CC156164D'; }
|
|
65
38
|
validate() {
|
|
66
39
|
// Local-only mode does not need cloud options
|
|
67
|
-
if (this.mode ===
|
|
40
|
+
if (this.mode === config_1.TuyaPluginMode.local) {
|
|
68
41
|
if (!this.platformConfig.local) {
|
|
69
42
|
this.log.error('mode is "local" but no "local" config block found.');
|
|
70
43
|
return false;
|
|
@@ -72,41 +45,42 @@ class TuyaPlatform {
|
|
|
72
45
|
return true;
|
|
73
46
|
}
|
|
74
47
|
// Both mode requires a local block in addition to cloud options
|
|
75
|
-
if (this.mode ===
|
|
48
|
+
if (this.mode === config_1.TuyaPluginMode.both && !this.platformConfig.local) {
|
|
76
49
|
this.log.error('mode is "both" but no "local" config block found.');
|
|
77
50
|
return false;
|
|
78
51
|
}
|
|
79
52
|
// Cloud or "both" mode requires cloud options
|
|
80
53
|
let result;
|
|
81
|
-
if (!this.
|
|
82
|
-
this.log.error('Not configured — "
|
|
54
|
+
if (!this.platformConfig.cloud) {
|
|
55
|
+
this.log.error('Not configured — "cloud" block is required for cloud mode, exit.');
|
|
83
56
|
return false;
|
|
84
57
|
}
|
|
85
|
-
else if (this.
|
|
86
|
-
result = new jsonschema_1.Validator().validate(this.
|
|
58
|
+
else if (this.platformConfig.cloud.projectType === '1') {
|
|
59
|
+
result = new jsonschema_1.Validator().validate(this.platformConfig.cloud, config_1.customOptionsSchema);
|
|
87
60
|
}
|
|
88
|
-
else if (this.
|
|
89
|
-
result = new jsonschema_1.Validator().validate(this.
|
|
61
|
+
else if (this.platformConfig.cloud.projectType === '2') {
|
|
62
|
+
result = new jsonschema_1.Validator().validate(this.platformConfig.cloud, config_1.homeOptionsSchema);
|
|
90
63
|
}
|
|
91
64
|
else {
|
|
92
|
-
this.log.error(`Unsupported projectType: ${this.
|
|
65
|
+
this.log.error(`Unsupported projectType: ${this.platformConfig.cloud['projectType']}, exit.`);
|
|
93
66
|
return false;
|
|
94
67
|
}
|
|
95
68
|
result.errors.forEach(error => this.log.error(error.stack));
|
|
96
69
|
if (result.errors.length > 0) {
|
|
97
70
|
return false;
|
|
98
71
|
}
|
|
99
|
-
if (!this.validateDeviceOverrides(
|
|
72
|
+
if (!this.validateDeviceOverrides(this.platformConfig.cloud.deviceOverrides)
|
|
73
|
+
|| !this.validateSchema(this.platformConfig.cloud.deviceOverrides)) {
|
|
100
74
|
return false;
|
|
101
75
|
}
|
|
102
76
|
return true;
|
|
103
77
|
}
|
|
104
|
-
validateDeviceOverrides() {
|
|
105
|
-
if (!
|
|
78
|
+
validateDeviceOverrides(deviceOverrides) {
|
|
79
|
+
if (!deviceOverrides) {
|
|
106
80
|
return true;
|
|
107
81
|
}
|
|
108
82
|
const idMap = new Map();
|
|
109
|
-
for (const item of
|
|
83
|
+
for (const item of deviceOverrides) {
|
|
110
84
|
if (idMap.has(item.id)) {
|
|
111
85
|
idMap.get(item.id)?.push(item);
|
|
112
86
|
}
|
|
@@ -122,11 +96,11 @@ class TuyaPlatform {
|
|
|
122
96
|
}
|
|
123
97
|
return true;
|
|
124
98
|
}
|
|
125
|
-
validateSchema() {
|
|
126
|
-
if (!
|
|
99
|
+
validateSchema(deviceOverrides) {
|
|
100
|
+
if (!deviceOverrides) {
|
|
127
101
|
return true;
|
|
128
102
|
}
|
|
129
|
-
for (const deviceOverride of
|
|
103
|
+
for (const deviceOverride of deviceOverrides) {
|
|
130
104
|
if (!deviceOverride.schema) {
|
|
131
105
|
continue;
|
|
132
106
|
}
|
|
@@ -154,29 +128,53 @@ class TuyaPlatform {
|
|
|
154
128
|
this.api = api;
|
|
155
129
|
this.Service = this.api.hap.Service;
|
|
156
130
|
this.Characteristic = this.api.hap.Characteristic;
|
|
131
|
+
this.platformAccessories = new Map();
|
|
132
|
+
/** platform ID */
|
|
133
|
+
this.platformID = (0, uuid_1.v5)(this.config.name ?? 'TuyaPlatform', TuyaPlatform.PLATFORM_UUID);
|
|
157
134
|
/** Cast config to our typed shape for easy access. */
|
|
158
135
|
this.platformConfig = this.config;
|
|
159
|
-
/** Cloud credentials block (options.projectType, accessId, etc.) */
|
|
160
|
-
this.options = this.config.options;
|
|
161
136
|
/** Active communication mode. Defaults to "cloud" for backward compatibility. */
|
|
162
|
-
this.mode = this.config.mode ??
|
|
163
|
-
// this is used to track restored cached accessories
|
|
164
|
-
this.cachedAccessories = [];
|
|
137
|
+
this.mode = this.config.mode ?? config_1.TuyaPluginMode.cloud;
|
|
165
138
|
/** All active accessory handler instances. */
|
|
166
139
|
this.accessoryHandlers = [];
|
|
140
|
+
/** for debug */
|
|
141
|
+
this.debug = false;
|
|
142
|
+
(0, Logger_1.initLogger)(log);
|
|
143
|
+
this.platformConfig = new ConfigMigrator_1.default(!!this.platformConfig.common?.debug).migrate(config);
|
|
144
|
+
this.debug = this.platformConfig.common?.debug ?? false;
|
|
145
|
+
this.debugLevel = this.platformConfig.common?.debugLevel ?? undefined;
|
|
146
|
+
this.mode = this.platformConfig.mode;
|
|
167
147
|
if (!this.validate()) {
|
|
168
148
|
return;
|
|
169
149
|
}
|
|
150
|
+
// Error/Exception handling.
|
|
151
|
+
const formatError = (e) => {
|
|
152
|
+
if (e instanceof Error) {
|
|
153
|
+
return `${e.message}\n${e.stack}`;
|
|
154
|
+
}
|
|
155
|
+
return JSON.stringify(e);
|
|
156
|
+
};
|
|
157
|
+
process.on('uncaughtException', (err) => {
|
|
158
|
+
this.log.error('Uncaught exception: %o', formatError(err));
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
|
161
|
+
process.on('unhandledRejection', (reason) => {
|
|
162
|
+
this.log.error('Unhandled rejection: %o', formatError(reason));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
});
|
|
170
165
|
this.log.debug('Finished initializing platform');
|
|
171
166
|
// When this event is fired it means Homebridge has restored all cached accessories from disk.
|
|
172
167
|
// Dynamic Platform plugins should only register new accessories after this event was fired,
|
|
173
168
|
// in order to ensure they weren't added to homebridge already. This event can also be used
|
|
174
169
|
// to start discovery of new accessories.
|
|
175
|
-
this.api.on(
|
|
170
|
+
this.api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, async () => {
|
|
176
171
|
this.log.debug('Executed didFinishLaunching callback');
|
|
177
172
|
// run the method to discover / register your devices as accessories
|
|
178
173
|
await this.initDevices();
|
|
179
174
|
});
|
|
175
|
+
this.api.on("shutdown" /* APIEvent.SHUTDOWN */, async () => {
|
|
176
|
+
this.log.debug('Executed shutdown callback');
|
|
177
|
+
});
|
|
180
178
|
}
|
|
181
179
|
/**
|
|
182
180
|
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
|
@@ -184,474 +182,150 @@ class TuyaPlatform {
|
|
|
184
182
|
*/
|
|
185
183
|
configureAccessory(accessory) {
|
|
186
184
|
this.log.info('Loading accessory from cache:', accessory.displayName);
|
|
187
|
-
AccessoryFactory_1.default.configAccessory(this, accessory);
|
|
188
185
|
// add the restored accessory to the accessories cache so we can track if it has already been registered
|
|
189
|
-
this.
|
|
186
|
+
this.platformAccessories.set(accessory.UUID, accessory);
|
|
190
187
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
* must not be registered again to prevent "duplicate UUID" errors.
|
|
195
|
-
*/
|
|
196
|
-
async initDevices() {
|
|
197
|
-
const cloudEnabled = this.mode === 'cloud' || this.mode === 'both';
|
|
198
|
-
const localEnabled = this.mode === 'local' || this.mode === 'both';
|
|
199
|
-
// For "both" mode, initialize cloud FIRST to fetch device details for enrichment
|
|
200
|
-
let cloudDevices;
|
|
201
|
-
if (cloudEnabled && this.options && this.mode === 'both') {
|
|
202
|
-
this.log.info('[Cloud] Initializing cloud device manager for hybrid mode enrichment…');
|
|
203
|
-
if (this.options.projectType === '1') {
|
|
204
|
-
cloudDevices = await this.initCustomProject();
|
|
205
|
-
}
|
|
206
|
-
else if (this.options.projectType === '2') {
|
|
207
|
-
cloudDevices = await this.initHomeProject();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// ── Local devices ────────────────────────────────────────────────────────
|
|
211
|
-
if (localEnabled && this.platformConfig.local) {
|
|
212
|
-
// If "both" mode and we successfully got cloud devices, enrich local config
|
|
213
|
-
if (this.mode === 'both' && cloudDevices && this.deviceManager) {
|
|
214
|
-
this.log.info('[Local] Enriching local config with cloud device details…');
|
|
215
|
-
await this.enrichLocalConfigFromCloud(cloudDevices);
|
|
216
|
-
}
|
|
217
|
-
this.log.info('[Local] Initialising local device manager…');
|
|
218
|
-
this.localDeviceManager = new LocalDeviceManager_1.default(this.platformConfig.local, this.log, this.api.user.storagePath());
|
|
219
|
-
await this.localDeviceManager.initLocalDevices();
|
|
220
|
-
this.localDeviceManager.connectAllDevices();
|
|
221
|
-
this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, (device) => this.addAccessory(device, 'local'));
|
|
222
|
-
this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
|
|
223
|
-
this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
|
|
224
|
-
this.localDeviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
|
|
225
|
-
const localDevices = [...this.localDeviceManager.devices.values()];
|
|
226
|
-
this.log.info(`[Local] Registered ${localDevices.length} local device(s).`);
|
|
227
|
-
for (const device of localDevices) {
|
|
228
|
-
this.addAccessory(device, 'local');
|
|
229
|
-
}
|
|
188
|
+
async saveDeviceList() {
|
|
189
|
+
if (!this.deviceManager) {
|
|
190
|
+
return;
|
|
230
191
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (this.options.projectType === '1') {
|
|
235
|
-
devices = await this.initCustomProject();
|
|
236
|
-
}
|
|
237
|
-
else if (this.options.projectType === '2') {
|
|
238
|
-
devices = await this.initHomeProject();
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
this.log.warn(`Unsupported projectType: ${this.options['projectType']}.`);
|
|
242
|
-
}
|
|
243
|
-
if (devices && this.deviceManager) {
|
|
244
|
-
// Initialize config hash tracker for cloud devices
|
|
245
|
-
this.configHash = new ConfigHash_1.ConfigHash(this.api.user.storagePath(), 'tuya-cloud-configs');
|
|
246
|
-
// Apply device config overrides
|
|
247
|
-
for (const device of devices) {
|
|
248
|
-
const deviceConfig = this.getDeviceConfig(device, 'cloud');
|
|
249
|
-
if (deviceConfig?.category) {
|
|
250
|
-
this.log.warn('Override %o category from %o to %o', device.name, device.category, deviceConfig.category);
|
|
251
|
-
device.category = deviceConfig.category;
|
|
252
|
-
}
|
|
253
|
-
if (deviceConfig?.unbridged) {
|
|
254
|
-
this.log.warn('Unbridge %o category %o', device.name, device.category);
|
|
255
|
-
device.unbridged = deviceConfig.unbridged;
|
|
256
|
-
}
|
|
257
|
-
// Check if config has changed since last run
|
|
258
|
-
// Hash the device override fields that affect accessory structure
|
|
259
|
-
const configToHash = {
|
|
260
|
-
deviceId: device.id,
|
|
261
|
-
customCategory: deviceConfig?.category,
|
|
262
|
-
unbridged: deviceConfig?.unbridged ?? false,
|
|
263
|
-
schemaOverrides: deviceConfig?.schema ? JSON.stringify(deviceConfig.schema) : undefined,
|
|
264
|
-
adaptiveLighting: deviceConfig?.adaptiveLighting ?? false,
|
|
265
|
-
};
|
|
266
|
-
const { changed: configChanged } = this.configHash.hasConfigChanged(device.id, configToHash);
|
|
267
|
-
device.configChanged = configChanged;
|
|
268
|
-
if (configChanged) {
|
|
269
|
-
this.log.info(`[Cloud] Device config changed for "${device.name}" (${device.id}), will rebuild services`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
await this.deviceManager.updateInfraredRemotes(devices);
|
|
273
|
-
this.log.info(`[Cloud] Got ${devices.length} device(s) and scene(s).`);
|
|
274
|
-
const file = path_1.default.join(this.api.user.persistPath(), `TuyaDeviceList.${this.deviceManager.api.tokenInfo.uid}.json`);
|
|
275
|
-
this.log.info('Device list saved at %s', file);
|
|
276
|
-
if (!fs_1.default.existsSync(this.api.user.persistPath())) {
|
|
277
|
-
await fs_1.default.promises.mkdir(this.api.user.persistPath());
|
|
278
|
-
}
|
|
279
|
-
await fs_1.default.promises.writeFile(file, JSON.stringify(devices, null, 2));
|
|
280
|
-
for (const device of devices) {
|
|
281
|
-
this.addAccessory(device, 'cloud');
|
|
282
|
-
}
|
|
283
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, (device) => this.addAccessory(device, 'cloud'));
|
|
284
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
|
|
285
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
|
|
286
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
|
|
287
|
-
}
|
|
192
|
+
if (this.tid !== undefined) {
|
|
193
|
+
clearTimeout(this.tid);
|
|
194
|
+
this.tid = undefined;
|
|
288
195
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
this.configHash = new ConfigHash_1.ConfigHash(this.api.user.storagePath(), 'tuya-cloud-configs');
|
|
292
|
-
// Apply device config overrides
|
|
293
|
-
for (const device of cloudDevices) {
|
|
294
|
-
const deviceConfig = this.getDeviceConfig(device, 'cloud');
|
|
295
|
-
if (deviceConfig?.category) {
|
|
296
|
-
this.log.warn('Override %o category from %o to %o', device.name, device.category, deviceConfig.category);
|
|
297
|
-
device.category = deviceConfig.category;
|
|
298
|
-
}
|
|
299
|
-
if (deviceConfig?.unbridged) {
|
|
300
|
-
this.log.warn('Unbridge %o category %o', device.name, device.category);
|
|
301
|
-
device.unbridged = deviceConfig.unbridged;
|
|
302
|
-
}
|
|
303
|
-
// Check if config has changed since last run
|
|
304
|
-
const configToHash = {
|
|
305
|
-
deviceId: device.id,
|
|
306
|
-
customCategory: deviceConfig?.category,
|
|
307
|
-
unbridged: deviceConfig?.unbridged ?? false,
|
|
308
|
-
schemaOverrides: deviceConfig?.schema ? JSON.stringify(deviceConfig.schema) : undefined,
|
|
309
|
-
adaptiveLighting: deviceConfig?.adaptiveLighting ?? false,
|
|
310
|
-
};
|
|
311
|
-
const { changed: configChanged } = this.configHash.hasConfigChanged(device.id, configToHash);
|
|
312
|
-
device.configChanged = configChanged;
|
|
313
|
-
if (configChanged) {
|
|
314
|
-
this.log.info(`[Cloud] Device config changed for "${device.name}" (${device.id}), will rebuild services`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
await this.deviceManager.updateInfraredRemotes(cloudDevices);
|
|
318
|
-
this.log.info(`[Cloud] Got ${cloudDevices.length} device(s) and scene(s).`);
|
|
319
|
-
const file = path_1.default.join(this.api.user.persistPath(), `TuyaDeviceList.${this.deviceManager.api.tokenInfo.uid}.json`);
|
|
196
|
+
this.tid = setTimeout(async () => {
|
|
197
|
+
const file = path_1.default.join(this.api.user.persistPath(), `TuyaDeviceList.${this.platformID}-${this.mode}.json`);
|
|
320
198
|
this.log.info('Device list saved at %s', file);
|
|
321
199
|
if (!fs_1.default.existsSync(this.api.user.persistPath())) {
|
|
322
200
|
await fs_1.default.promises.mkdir(this.api.user.persistPath());
|
|
323
201
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, (device) => this.addAccessory(device, 'cloud'));
|
|
329
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
|
|
330
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
|
|
331
|
-
if (this.localDeviceManager) {
|
|
332
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, (device, status) => {
|
|
333
|
-
this.localDeviceManager?.handleCloudStatusUpdate(device.id, status);
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
this.deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
|
|
337
|
-
}
|
|
338
|
-
// Remove stale cached accessories not claimed by any device
|
|
339
|
-
for (const cachedAccessory of this.cachedAccessories) {
|
|
340
|
-
this.log.warn('Removing unused accessory from cache:', cachedAccessory.displayName);
|
|
341
|
-
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [cachedAccessory]);
|
|
342
|
-
}
|
|
343
|
-
this.cachedAccessories = [];
|
|
344
|
-
}
|
|
345
|
-
getDeviceConfig(device, source) {
|
|
346
|
-
if (!this.options.deviceOverrides) {
|
|
347
|
-
return undefined;
|
|
348
|
-
}
|
|
349
|
-
// Find matching override, respecting source filtering
|
|
350
|
-
// Since deviceOverrides are in the cloud config, they default to cloud-only
|
|
351
|
-
const effectiveSource = source || 'cloud';
|
|
352
|
-
const matches = this.options.deviceOverrides.filter(config => {
|
|
353
|
-
const sourceMatch = config.source
|
|
354
|
-
? (config.source === 'both' || config.source === effectiveSource) // explicit source
|
|
355
|
-
: effectiveSource === 'cloud'; // default: cloud-only
|
|
356
|
-
const idMatch = config.id === device.id || config.id === device.uuid ||
|
|
357
|
-
config.id === device.product_id || config.id === 'global';
|
|
358
|
-
return sourceMatch && idMatch;
|
|
359
|
-
});
|
|
360
|
-
// Return device-specific config, then product, then global
|
|
361
|
-
return matches.find(config => config.id === device.id || config.id === device.uuid) ||
|
|
362
|
-
matches.find(config => config.id === device.product_id) ||
|
|
363
|
-
matches.find(config => config.id === 'global');
|
|
364
|
-
}
|
|
365
|
-
getDeviceSchemaConfig(device, code, source) {
|
|
366
|
-
const deviceConfig = this.getDeviceConfig(device, source);
|
|
367
|
-
if (!deviceConfig || !deviceConfig.schema) {
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
// migrate old config
|
|
371
|
-
deviceConfig.schema.forEach(item => {
|
|
372
|
-
if (item['oldCode']) {
|
|
373
|
-
item.newCode = item.code;
|
|
374
|
-
item.code = item['oldCode'];
|
|
375
|
-
item['oldCode'] = undefined;
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
// ignore case - allow both old (code) and migrated (newCode) names
|
|
379
|
-
const schemaConfig = deviceConfig.schema.find(item => {
|
|
380
|
-
if (!code) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
const target = code.toString().toLowerCase();
|
|
384
|
-
const legacyCode = item.code?.toString().toLowerCase();
|
|
385
|
-
const migratedCode = item.newCode?.toString().toLowerCase();
|
|
386
|
-
return legacyCode === target || migratedCode === target;
|
|
387
|
-
});
|
|
388
|
-
if (!schemaConfig) {
|
|
389
|
-
return undefined;
|
|
390
|
-
}
|
|
391
|
-
return schemaConfig;
|
|
202
|
+
const devices = [...this.platformAccessories.values()].map(accessory => this.deviceManager.getDevice(accessory.context.deviceID));
|
|
203
|
+
await fs_1.default.promises.writeFile(file, JSON.stringify(devices, null, 2));
|
|
204
|
+
this.tid = undefined;
|
|
205
|
+
}, 5000);
|
|
392
206
|
}
|
|
393
207
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
208
|
+
* This is an example method showing how to register discovered accessories.
|
|
209
|
+
* Accessories must only be registered once, previously created accessories
|
|
210
|
+
* must not be registered again to prevent "duplicate UUID" errors.
|
|
396
211
|
*/
|
|
397
|
-
async
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// Create a map of cloud devices by ID for quick lookup
|
|
402
|
-
const cloudDeviceMap = new Map(devices.map(d => [d.id, d]));
|
|
403
|
-
for (const device of devices) {
|
|
404
|
-
try {
|
|
405
|
-
// Skip if already manually configured in local section
|
|
406
|
-
const existingConfig = this.platformConfig.local.devices.find(cfg => cfg.tuyaDeviceId === device.id || cfg.tuyaDeviceId === device.uuid);
|
|
407
|
-
if (existingConfig && existingConfig.tuyaKey) {
|
|
408
|
-
this.log.debug(`[Hybrid] Device ${device.name} (${device.id}) already has manual local config, skipping cloud enrichment`);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
// Fetch device details from cloud API (includes local_key)
|
|
412
|
-
const detailRes = await this.deviceManager.getDeviceDetails(device.id);
|
|
413
|
-
if (!detailRes.success || !detailRes.result) {
|
|
414
|
-
this.log.debug(`[Hybrid] Could not fetch device details for ${device.name} (${device.id}) from cloud API`);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
const details = detailRes.result;
|
|
418
|
-
const deviceId = device.id || device.uuid;
|
|
419
|
-
const localKey = details.local_key || details.localKey;
|
|
420
|
-
const ip = details.ip || details.address;
|
|
421
|
-
if (!deviceId) {
|
|
422
|
-
this.log.warn(`[Hybrid] Skipping enrichment for ${device.name} because cloud device ID is missing`);
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
if (!localKey) {
|
|
426
|
-
this.log.debug(`[Hybrid] No local_key available for ${device.name} (${deviceId}), will use cloud-only`);
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
if (existingConfig) {
|
|
430
|
-
// Update existing manual config with cloud-provided local_key and optional IP
|
|
431
|
-
existingConfig.tuyaKey = localKey;
|
|
432
|
-
if (ip) {
|
|
433
|
-
existingConfig.ip = ip;
|
|
434
|
-
}
|
|
435
|
-
this.log.info(`[Hybrid] Enriched local config for ${device.name} (${deviceId}) with cloud-provided local_key and ip`);
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
// Create new local device config entry from cloud device details
|
|
439
|
-
const localDeviceConfig = {
|
|
440
|
-
tuyaDeviceId: deviceId,
|
|
441
|
-
tuyaKey: localKey,
|
|
442
|
-
name: device.name || `LocalDevice-${deviceId.slice(0, 8)}`,
|
|
443
|
-
category: device.category,
|
|
444
|
-
};
|
|
445
|
-
if (ip) {
|
|
446
|
-
localDeviceConfig.ip = ip;
|
|
447
|
-
}
|
|
448
|
-
if (device.product_id) {
|
|
449
|
-
localDeviceConfig.productId = device.product_id;
|
|
450
|
-
}
|
|
451
|
-
if (!this.platformConfig.local.devices) {
|
|
452
|
-
this.platformConfig.local.devices = [];
|
|
453
|
-
}
|
|
454
|
-
this.platformConfig.local.devices.push(localDeviceConfig);
|
|
455
|
-
this.log.info(`[Hybrid] Added cloud device ${device.name} (${deviceId}) to local config with local_key`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
460
|
-
this.log.warn(`[Hybrid] Error enriching device ${device.name} (${device.id}): ${msg}`);
|
|
461
|
-
// Continue with next device - this is non-fatal
|
|
462
|
-
}
|
|
212
|
+
async initDevices() {
|
|
213
|
+
let deviceManager;
|
|
214
|
+
if (this.mode === config_1.TuyaPluginMode.cloud) {
|
|
215
|
+
deviceManager = await this.initCloudMode(this.platformConfig.cloud);
|
|
463
216
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (this.options.projectType !== '1') {
|
|
467
|
-
return undefined;
|
|
468
|
-
}
|
|
469
|
-
const DEFAULT_USER = 'homebridge';
|
|
470
|
-
const DEFAULT_PASS = 'homebridge';
|
|
471
|
-
let res;
|
|
472
|
-
const { endpoint, accessId, accessKey, debug, debugLevel } = this.options;
|
|
473
|
-
const debugMode = debug && ((debugLevel ?? '').length > 0 ? debugLevel?.includes('api') : true);
|
|
474
|
-
const api = new TuyaOpenAPI_1.default(endpoint, accessId, accessKey, this.log, 'en', debugMode);
|
|
475
|
-
const deviceManager = new TuyaCustomDeviceManager_1.default(api, debugMode);
|
|
476
|
-
this.log.info('Get token.');
|
|
477
|
-
res = await api.getToken();
|
|
478
|
-
if (res.success === false) {
|
|
479
|
-
this.log.error(`Get token failed. code=${res.code}, msg=${res.msg}`);
|
|
480
|
-
return undefined;
|
|
481
|
-
}
|
|
482
|
-
this.log.info(`Search default user "${DEFAULT_USER}"`);
|
|
483
|
-
res = await api.customGetUserInfo(DEFAULT_USER);
|
|
484
|
-
if (res.success === false) {
|
|
485
|
-
this.log.error(`Search user failed. code=${res.code}, msg=${res.msg}`);
|
|
486
|
-
return undefined;
|
|
487
|
-
}
|
|
488
|
-
if (!res.result.user_name) {
|
|
489
|
-
this.log.info(`Default user "${DEFAULT_USER}" not exist.`);
|
|
490
|
-
this.log.info(`Creating default user "${DEFAULT_USER}".`);
|
|
491
|
-
res = await api.customCreateUser(DEFAULT_USER, DEFAULT_PASS);
|
|
492
|
-
if (res.success === false) {
|
|
493
|
-
this.log.error(`Create default user failed. code=${res.code}, msg=${res.msg}`);
|
|
494
|
-
return undefined;
|
|
495
|
-
}
|
|
217
|
+
else if (this.mode === config_1.TuyaPluginMode.local) {
|
|
218
|
+
deviceManager = await this.initLocalMode(this.platformConfig.local);
|
|
496
219
|
}
|
|
497
|
-
else {
|
|
498
|
-
this.
|
|
499
|
-
}
|
|
500
|
-
const uid = res.result.user_id;
|
|
501
|
-
this.log.info('Fetching asset list.');
|
|
502
|
-
res = await deviceManager.getAssetList();
|
|
503
|
-
if (res.success === false) {
|
|
504
|
-
this.log.error(`Fetching asset list failed. code=${res.code}, msg=${res.msg}`);
|
|
505
|
-
return undefined;
|
|
506
|
-
}
|
|
507
|
-
const assetIDList = [];
|
|
508
|
-
for (const { asset_id, asset_name } of res.result.list) {
|
|
509
|
-
this.log.info(`Got asset_id=${asset_id}, asset_name=${asset_name}`);
|
|
510
|
-
assetIDList.push(asset_id);
|
|
511
|
-
}
|
|
512
|
-
if (assetIDList.length === 0) {
|
|
513
|
-
this.log.warn('Asset list is empty. exit.');
|
|
514
|
-
return undefined;
|
|
515
|
-
}
|
|
516
|
-
this.log.info('Authorize asset list.');
|
|
517
|
-
res = await deviceManager.authorizeAssetList(uid, assetIDList, true);
|
|
518
|
-
if (res.success === false) {
|
|
519
|
-
this.log.error(`Authorize asset list failed. code=${res.code}, msg=${res.msg}`);
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
this.log.info(`Log in with user "${DEFAULT_USER}".`);
|
|
523
|
-
res = await api.customLogin(DEFAULT_USER, DEFAULT_USER);
|
|
524
|
-
if (res.success === false) {
|
|
525
|
-
this.log.error(`Login failed. code=${res.code}, msg=${res.msg}`);
|
|
526
|
-
if (res.code && TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]) {
|
|
527
|
-
this.log.error(TuyaOpenAPI_1.LOGIN_ERROR_MESSAGES[res.code]);
|
|
528
|
-
}
|
|
529
|
-
return undefined;
|
|
220
|
+
else if (this.mode === config_1.TuyaPluginMode.both) {
|
|
221
|
+
deviceManager = await this.initBothMode(this.platformConfig.cloud, this.platformConfig.local);
|
|
530
222
|
}
|
|
531
|
-
this.log.info('Start MQTT connection.');
|
|
532
|
-
deviceManager.mq.start();
|
|
533
|
-
this.log.info('Fetching device list.');
|
|
534
|
-
deviceManager.ownerIDs = assetIDList;
|
|
535
|
-
const devices = await deviceManager.updateDevices(assetIDList);
|
|
536
223
|
this.deviceManager = deviceManager;
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
this.log.info('Start MQTT connection.');
|
|
558
|
-
deviceManager.mq.start();
|
|
559
|
-
this.log.info('Fetching home list.');
|
|
560
|
-
res = await deviceManager.getHomeList();
|
|
561
|
-
if (res.success === false) {
|
|
562
|
-
this.log.error(`Fetching home list failed. code=${res.code}, msg=${res.msg}`);
|
|
563
|
-
return undefined;
|
|
564
|
-
}
|
|
565
|
-
const homeIDList = [];
|
|
566
|
-
for (const { home_id, name } of res.result) {
|
|
567
|
-
this.log.info(`Got home_id=${home_id}, name=${name}`);
|
|
568
|
-
if (this.options.homeWhitelist) {
|
|
569
|
-
if (this.options.homeWhitelist.includes(home_id)) {
|
|
570
|
-
this.log.info(`Found home_id=${home_id} in whitelist; including devices from this home.`);
|
|
571
|
-
homeIDList.push(home_id);
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
this.log.info(`Did not find home_id=${home_id} in whitelist; excluding devices from this home.`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
homeIDList.push(home_id);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
if (homeIDList.length === 0) {
|
|
582
|
-
this.log.warn('Home list is empty.');
|
|
583
|
-
}
|
|
584
|
-
this.log.info('Fetching device list.');
|
|
585
|
-
deviceManager.ownerIDs = homeIDList.map(homeID => homeID.toString());
|
|
586
|
-
const devices = await deviceManager.updateDevices(homeIDList);
|
|
587
|
-
this.log.info('Fetching scene list.');
|
|
588
|
-
for (const homeID of homeIDList) {
|
|
589
|
-
const scenes = await deviceManager.getSceneList(homeID);
|
|
590
|
-
for (const scene of scenes) {
|
|
591
|
-
this.log.info(`Got scene_id=${scene.id}, name=${scene.name}`);
|
|
592
|
-
}
|
|
593
|
-
devices.push(...scenes);
|
|
224
|
+
const devices = await this.deviceManager.pullDevices();
|
|
225
|
+
devices.forEach(device => this.log.debug(`${device.id}: ${device.name}`));
|
|
226
|
+
for (const device of devices) {
|
|
227
|
+
this.addAccessory(device);
|
|
228
|
+
}
|
|
229
|
+
// add RTSP camera accessories that are not present in either Cloud or Local (non-Tuya devices).
|
|
230
|
+
this.platformConfig.local?.cameras?.
|
|
231
|
+
filter(cconfig => !devices.map(device => device.id).includes(cconfig.deviceId)).forEach(cconfig => {
|
|
232
|
+
this.log.info(`adding non-Tuya device: ${cconfig.deviceName}`);
|
|
233
|
+
const device = this.deviceManager.createRTSPCameraDevice(cconfig);
|
|
234
|
+
this.deviceManager.devices.push(device);
|
|
235
|
+
this.addAccessory(device);
|
|
236
|
+
});
|
|
237
|
+
deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_ADD, this.addAccessory.bind(this));
|
|
238
|
+
deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_INFO_UPDATE, this.updateAccessoryInfo.bind(this));
|
|
239
|
+
deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_STATUS_UPDATE, this.updateAccessoryStatus.bind(this));
|
|
240
|
+
deviceManager.on(TuyaDeviceManager_1.default.Events.DEVICE_DELETE, this.removeAccessory.bind(this));
|
|
241
|
+
if (this.mode !== config_1.TuyaPluginMode.cloud) {
|
|
242
|
+
// When running in local mode, a short delay is added before detecting the change.
|
|
243
|
+
setTimeout(this.finishDeviceInitializing.bind(this), (this.platformConfig.local?.discoverTimeout ?? 5) * 1000);
|
|
594
244
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const targetDevice = devices.find(device => device.lat && device.lon);
|
|
598
|
-
if (targetDevice) {
|
|
599
|
-
devices.push(this.createWeatherDevice(targetDevice, res.result));
|
|
600
|
-
}
|
|
245
|
+
else {
|
|
246
|
+
this.finishDeviceInitializing();
|
|
601
247
|
}
|
|
602
|
-
return devices;
|
|
603
248
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
device.category = deviceConfig.category;
|
|
249
|
+
async initCloudMode(cloudConfig) {
|
|
250
|
+
this.log.info('[Cloud] Initializing cloud device manager…');
|
|
251
|
+
let deviceManager;
|
|
252
|
+
if (cloudConfig.projectType === '1') {
|
|
253
|
+
deviceManager = await this.initCustomProject(cloudConfig);
|
|
610
254
|
}
|
|
611
|
-
if (
|
|
612
|
-
|
|
613
|
-
device.unbridged = deviceConfig.unbridged;
|
|
255
|
+
else if (cloudConfig.projectType === '2') {
|
|
256
|
+
deviceManager = await this.initHomeProject(cloudConfig);
|
|
614
257
|
}
|
|
258
|
+
return deviceManager;
|
|
259
|
+
}
|
|
260
|
+
async initLocalMode(localConfig) {
|
|
261
|
+
this.log.info('[Local] Initializing local device manager…');
|
|
262
|
+
return new LocalDeviceManager_1.default(localConfig, !!this.debug);
|
|
263
|
+
}
|
|
264
|
+
async initBothMode(cloudConfig, localConfig) {
|
|
265
|
+
this.log.debug('[Hybrid] Initializing...');
|
|
266
|
+
const { accessId, accessKey, endpoint, forceIPv4 } = cloudConfig;
|
|
267
|
+
const debugMode = !!this.debugLevel ? this.debugLevel.includes('api') : this.debug;
|
|
268
|
+
const _endpoint = (endpoint && endpoint.length > 0) ? endpoint : TuyaOpenAPI_1.default.getDefaultEndpoint(cloudConfig['countryCode']);
|
|
269
|
+
const api = new TuyaOpenAPI_1.default(_endpoint, accessId, accessKey, 'en', debugMode, forceIPv4);
|
|
270
|
+
return new TuyaHybridDeviceManager_1.default(api, cloudConfig, localConfig, !!this.debug);
|
|
271
|
+
}
|
|
272
|
+
async initCustomProject(config) {
|
|
273
|
+
const { endpoint, accessId, accessKey, forceIPv4 } = config;
|
|
274
|
+
const debugMode = !!this.debugLevel ? this.debugLevel.includes('api') : this.debug;
|
|
275
|
+
const api = new TuyaOpenAPI_1.default(endpoint, accessId, accessKey, 'en', debugMode, forceIPv4);
|
|
276
|
+
return new TuyaCustomDeviceManager_1.default(api, config, debugMode);
|
|
277
|
+
}
|
|
278
|
+
async initHomeProject(config) {
|
|
279
|
+
const { accessId, accessKey, countryCode, endpoint, forceIPv4 } = config;
|
|
280
|
+
const debugMode = !!this.debugLevel ? this.debugLevel.includes('api') : this.debug;
|
|
281
|
+
const _endpoint = (endpoint && endpoint.length > 0) ? endpoint : TuyaOpenAPI_1.default.getDefaultEndpoint(countryCode);
|
|
282
|
+
const api = new TuyaOpenAPI_1.default(_endpoint, accessId, accessKey, 'en', debugMode, forceIPv4);
|
|
283
|
+
return new TuyaHomeDeviceManager_1.default(api, config, debugMode);
|
|
284
|
+
}
|
|
285
|
+
addAccessory(device) {
|
|
615
286
|
if (device.category === 'hidden') {
|
|
616
287
|
this.log.info('Hide Accessory:', device.name);
|
|
617
|
-
return;
|
|
288
|
+
return null;
|
|
618
289
|
}
|
|
619
290
|
const uuid = this.api.hap.uuid.generate(device.id);
|
|
620
|
-
const existingAccessory = this.
|
|
291
|
+
const existingAccessory = this.platformAccessories.get(uuid);
|
|
621
292
|
if (existingAccessory && !device.unbridged) {
|
|
622
|
-
this.log.
|
|
623
|
-
// Update context
|
|
624
|
-
if (!existingAccessory.context || !existingAccessory.context.deviceID) {
|
|
625
|
-
this.log.info('Update accessory context:', existingAccessory.displayName);
|
|
626
|
-
existingAccessory.context.deviceID = device.id;
|
|
627
|
-
this.api.updatePlatformAccessories([existingAccessory]);
|
|
628
|
-
}
|
|
293
|
+
this.log.debug('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
629
294
|
// create the accessory handler for the restored accessory
|
|
630
295
|
const handler = AccessoryFactory_1.default.createAccessory(this, existingAccessory, device);
|
|
631
296
|
this.accessoryHandlers.push(handler);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
297
|
+
// Always apply the user settings to ensure that any changes made after the cache was created are properly reflected.
|
|
298
|
+
AccessoryFactory_1.default.configAccessory(this, existingAccessory);
|
|
299
|
+
this.syncDeviceAndAccessory(device, existingAccessory);
|
|
300
|
+
this.saveDeviceList();
|
|
301
|
+
return existingAccessory;
|
|
636
302
|
}
|
|
637
303
|
else {
|
|
638
304
|
// the accessory does not yet exist, so we need to create it
|
|
639
|
-
this.log.
|
|
305
|
+
this.log.debug('Adding new accessory:', device.name);
|
|
640
306
|
// create a new accessory (sanitize name to conform to HAP rules)
|
|
641
307
|
const safeName = (0, util_1.sanitizeName)(device.name) ?? (device.id || 'Tuya Device');
|
|
642
308
|
const accessory = new this.api.platformAccessory(safeName, uuid);
|
|
643
309
|
accessory.context.deviceID = device.id;
|
|
310
|
+
// Initialize config hash tracker for device
|
|
311
|
+
const currentHash = this.deviceManager.createDeviceConfigHash(device);
|
|
312
|
+
accessory.context.deviceConfigHash = currentHash;
|
|
644
313
|
// create the accessory handler for the newly create accessory
|
|
645
314
|
const handler = AccessoryFactory_1.default.createAccessory(this, accessory, device);
|
|
646
315
|
this.accessoryHandlers.push(handler);
|
|
316
|
+
AccessoryFactory_1.default.configAccessory(this, accessory);
|
|
647
317
|
// link the accessory to your platform
|
|
648
318
|
if (device.unbridged) {
|
|
319
|
+
this.log.success(`(unbridged) publish accessory: ${accessory.displayName}`);
|
|
649
320
|
this.api.publishExternalAccessories(settings_1.PLUGIN_NAME, [accessory]);
|
|
650
321
|
}
|
|
651
322
|
else {
|
|
323
|
+
this.log.success(`publish accessory: ${accessory.displayName}`);
|
|
652
324
|
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
|
|
653
325
|
}
|
|
654
|
-
|
|
326
|
+
this.platformAccessories.set(accessory.UUID, accessory);
|
|
327
|
+
this.saveDeviceList();
|
|
328
|
+
return accessory;
|
|
655
329
|
}
|
|
656
330
|
}
|
|
657
331
|
updateAccessoryInfo(device, info) {
|
|
@@ -659,8 +333,11 @@ class TuyaPlatform {
|
|
|
659
333
|
if (!handler) {
|
|
660
334
|
return;
|
|
661
335
|
}
|
|
662
|
-
|
|
663
|
-
handler.onDeviceInfoUpdate(info)
|
|
336
|
+
const accessory = this.platformAccessories.get(this.api.hap.uuid.generate(device.id));
|
|
337
|
+
handler.onDeviceInfoUpdate(info).then(() => {
|
|
338
|
+
this.syncDeviceAndAccessory(device, accessory);
|
|
339
|
+
});
|
|
340
|
+
this.saveDeviceList();
|
|
664
341
|
}
|
|
665
342
|
updateAccessoryStatus(device, status) {
|
|
666
343
|
const handler = this.getAccessoryHandler(device.id);
|
|
@@ -669,6 +346,7 @@ class TuyaPlatform {
|
|
|
669
346
|
}
|
|
670
347
|
// this.log.debug('onDeviceStatusUpdate devId = %s, status = %o}', device.id, status);
|
|
671
348
|
handler.onDeviceStatusUpdate(status);
|
|
349
|
+
// Status updates occur frequently, so the saveDeviceList process is not performed.
|
|
672
350
|
}
|
|
673
351
|
removeAccessory(deviceID) {
|
|
674
352
|
const handler = this.getAccessoryHandler(deviceID);
|
|
@@ -679,21 +357,103 @@ class TuyaPlatform {
|
|
|
679
357
|
if (index >= 0) {
|
|
680
358
|
this.accessoryHandlers.splice(index, 1);
|
|
681
359
|
}
|
|
360
|
+
this.saveDeviceList();
|
|
682
361
|
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [handler.accessory]);
|
|
683
362
|
this.log.info('Removing existing accessory from cache:', handler.accessory.displayName);
|
|
363
|
+
this.platformAccessories.delete(handler.accessory.UUID);
|
|
364
|
+
}
|
|
365
|
+
async finishDeviceInitializing() {
|
|
366
|
+
// Remove stale cached accessories not claimed by any device
|
|
367
|
+
for (const [uuid, accessory] of this.platformAccessories) {
|
|
368
|
+
if (this.deviceManager.getDevice(accessory.context.deviceID) === undefined) {
|
|
369
|
+
this.log.warn('Removing unused accessory from cache:', accessory.displayName);
|
|
370
|
+
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
|
|
371
|
+
this.platformAccessories.delete(uuid);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
684
374
|
}
|
|
685
375
|
getAccessoryHandler(deviceID) {
|
|
686
376
|
return this.accessoryHandlers.find(handler => handler.device?.id === deviceID);
|
|
687
377
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
378
|
+
/**
|
|
379
|
+
* When device information is updated due to changes in the plugin settings,
|
|
380
|
+
* the updated data must be reflected in the accessory cache.
|
|
381
|
+
* @param device
|
|
382
|
+
* @param accessory
|
|
383
|
+
*/
|
|
384
|
+
async syncDeviceAndAccessory(device, accessory) {
|
|
385
|
+
this.log.debug(`sync device to accessory cache. device: ${device.name}, accessory cache: ${accessory.displayName}`);
|
|
386
|
+
// A code value used to detect changes in the plugin settings.
|
|
387
|
+
const currentHash = this.deviceManager.createDeviceConfigHash(device);
|
|
388
|
+
if (currentHash !== accessory.context.deviceConfigHash) {
|
|
389
|
+
this.log.info(`Device config changed for "${device.name}" (${device.id}), will rebuild services`);
|
|
390
|
+
accessory.context.deviceConfigHash = currentHash;
|
|
391
|
+
// To reflect configuration changes without discarding the existing
|
|
392
|
+
// cache instance as much as possible, the latest configuration data is created as a separate accessory.
|
|
393
|
+
const cloneDevice = Object.assign(new TuyaDevice_1.default({}), (0, util_1.deepClone)(device));
|
|
394
|
+
this.deviceManager.devices.push(cloneDevice);
|
|
395
|
+
cloneDevice.id = 'dummyID' + cloneDevice.id;
|
|
396
|
+
cloneDevice.name = 'dummy';
|
|
397
|
+
let cloudConfig = this.platformConfig.cloud?.deviceOverrides?.find(i => i.id === device.id);
|
|
398
|
+
let localConfig = this.platformConfig.local?.devices?.find(i => i.tuyaDeviceId === device.id);
|
|
399
|
+
let serviceConfig = this.platformConfig.cloud?.serviceInformationOverrides?.find(i => i.device_id === device.id);
|
|
400
|
+
if (cloudConfig) {
|
|
401
|
+
cloudConfig = (0, util_1.deepClone)(cloudConfig);
|
|
402
|
+
cloudConfig.id = cloneDevice.id;
|
|
403
|
+
this.platformConfig.cloud.deviceOverrides.push(cloudConfig);
|
|
404
|
+
}
|
|
405
|
+
if (localConfig) {
|
|
406
|
+
localConfig = (0, util_1.deepClone)(localConfig);
|
|
407
|
+
localConfig.tuyaDeviceId = cloneDevice.id;
|
|
408
|
+
this.platformConfig.local.devices.push(localConfig);
|
|
409
|
+
}
|
|
410
|
+
if (serviceConfig) {
|
|
411
|
+
serviceConfig = (0, util_1.deepClone)(serviceConfig);
|
|
412
|
+
serviceConfig.device_id = cloneDevice.id;
|
|
413
|
+
this.platformConfig.cloud.serviceInformationOverrides.push(serviceConfig);
|
|
414
|
+
}
|
|
415
|
+
const freshAccessory = this.addAccessory(cloneDevice);
|
|
416
|
+
// The dummy item should be removed immediately.
|
|
417
|
+
this.removeAccessory(cloneDevice.id);
|
|
418
|
+
this.deviceManager.devices.splice(this.deviceManager.devices.indexOf(cloneDevice), 1);
|
|
419
|
+
if (cloudConfig) {
|
|
420
|
+
this.platformConfig.cloud.deviceOverrides.splice(this.platformConfig.cloud.deviceOverrides.indexOf(cloudConfig), 1);
|
|
421
|
+
}
|
|
422
|
+
if (localConfig) {
|
|
423
|
+
localConfig.tuyaDeviceId = cloneDevice.id;
|
|
424
|
+
this.platformConfig.local.devices.splice(this.platformConfig.local.devices.indexOf(localConfig), 1);
|
|
425
|
+
}
|
|
426
|
+
if (serviceConfig) {
|
|
427
|
+
serviceConfig.device_id = cloudConfig.id;
|
|
428
|
+
this.platformConfig.cloud.serviceInformationOverrides.splice(this.platformConfig.cloud.serviceInformationOverrides.indexOf(serviceConfig), 1);
|
|
429
|
+
}
|
|
430
|
+
if (freshAccessory === null) {
|
|
431
|
+
// hidden
|
|
432
|
+
this.removeAccessory(device.id);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const newServices = freshAccessory.services;
|
|
436
|
+
const oldServices = accessory.services;
|
|
437
|
+
for (const oldService of oldServices) {
|
|
438
|
+
const existsInNew = newServices.some(newService => newService.UUID === oldService.UUID);
|
|
439
|
+
if (!existsInNew) {
|
|
440
|
+
accessory.removeService(oldService);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const newService = newServices.find(s => s.UUID === oldService.UUID);
|
|
444
|
+
if (!newService) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
for (const oldChar of oldService.characteristics) {
|
|
448
|
+
const existsCharInNew = newService.characteristics.some(newChar => newChar.UUID === oldChar.UUID);
|
|
449
|
+
if (!existsCharInNew) {
|
|
450
|
+
oldService.removeCharacteristic(oldChar);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
this.log.success(`(update) publish accessory: ${accessory.displayName}`);
|
|
456
|
+
this.api.updatePlatformAccessories([accessory]);
|
|
697
457
|
}
|
|
698
458
|
}
|
|
699
459
|
exports.TuyaPlatform = TuyaPlatform;
|