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