@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
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import { describe, expect, test, beforeEach, jest } from '@jest/globals';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
|
-
import TuyaDeviceManager from '../src/cloud/device/
|
|
4
|
+
import TuyaDeviceManager from '../src/cloud/device/TuyaCloudDeviceManager';
|
|
5
5
|
import TuyaDevice from '../src/cloud/device/TuyaDevice';
|
|
6
|
+
import TuyaHomeDeviceManager from '../src/cloud/device/TuyaHomeDeviceManager';
|
|
7
|
+
import { TuyaPlatformHomeConfig } from '../src/config';
|
|
8
|
+
import { ExLogger, initLogger } from '../src/shared/util/Logger';
|
|
6
9
|
|
|
7
10
|
// Mock Logger
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
PrefixLogger: class PrefixLogger {
|
|
18
|
-
constructor(public log: any, public name: string, public debugEnabled: boolean) {}
|
|
19
|
-
info() {}
|
|
20
|
-
warn() {}
|
|
21
|
-
error() {}
|
|
22
|
-
debug() {}
|
|
23
|
-
},
|
|
24
|
-
}));
|
|
11
|
+
const mockLog: ExLogger = {
|
|
12
|
+
debug: jest.fn(),
|
|
13
|
+
info: jest.fn(),
|
|
14
|
+
warn: jest.fn(),
|
|
15
|
+
error: jest.fn(),
|
|
16
|
+
log: jest.fn(),
|
|
17
|
+
success: jest.fn(),
|
|
18
|
+
} as unknown as ExLogger;
|
|
25
19
|
|
|
26
20
|
// Mock TuyaOpenMQ
|
|
27
21
|
jest.mock('../src/cloud/api/TuyaOpenMQ', () => {
|
|
@@ -41,773 +35,776 @@ jest.mock('../src/cloud/api/TuyaOpenAPI', () => {
|
|
|
41
35
|
};
|
|
42
36
|
});
|
|
43
37
|
|
|
44
|
-
describe('TuyaDeviceManager', () =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
describe('TuyaDeviceManager', () =>
|
|
39
|
+
{
|
|
40
|
+
let manager: TuyaDeviceManager;
|
|
41
|
+
let mockAPI: any;
|
|
42
|
+
let mockConfig: TuyaPlatformHomeConfig;
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
initLogger(mockLog);
|
|
46
|
+
const getMock = jest.fn() as any;
|
|
47
|
+
getMock.mockImplementation((url: string) => {
|
|
48
|
+
if (url.includes('specifications')) {
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
success: true,
|
|
51
|
+
result: {
|
|
52
|
+
status: [],
|
|
53
|
+
functions: [],
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
52
57
|
return Promise.resolve({
|
|
53
58
|
success: true,
|
|
54
59
|
result: {
|
|
60
|
+
id: 'device_001',
|
|
61
|
+
name: 'Test Device',
|
|
62
|
+
product_id: 'prod_001',
|
|
63
|
+
category: 'dj',
|
|
55
64
|
status: [],
|
|
56
|
-
|
|
65
|
+
schema: [],
|
|
57
66
|
},
|
|
58
67
|
});
|
|
59
|
-
}
|
|
60
|
-
return Promise.resolve({
|
|
61
|
-
success: true,
|
|
62
|
-
result: {
|
|
63
|
-
id: 'device_001',
|
|
64
|
-
name: 'Test Device',
|
|
65
|
-
product_id: 'prod_001',
|
|
66
|
-
category: 'dj',
|
|
67
|
-
status: [],
|
|
68
|
-
schema: [],
|
|
69
|
-
},
|
|
70
68
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
manager = new TuyaDeviceManager(mockAPI, false);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('initialization', () => {
|
|
84
|
-
test('creates device manager instance', () => {
|
|
85
|
-
expect(manager).toBeInstanceOf(EventEmitter);
|
|
86
|
-
expect(manager).toBeDefined();
|
|
87
|
-
});
|
|
69
|
+
|
|
70
|
+
mockAPI = {
|
|
71
|
+
log: { log: console.log } as any,
|
|
72
|
+
accessId: 'test_access_id',
|
|
73
|
+
accessKey: 'test_access_key',
|
|
74
|
+
get: getMock,
|
|
75
|
+
};
|
|
88
76
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
mockConfig = {
|
|
78
|
+
projectType: '2',
|
|
79
|
+
accessId: 'test_id',
|
|
80
|
+
accessKey: 'test_key',
|
|
81
|
+
countryCode: 1,
|
|
82
|
+
username: 'user@example.com',
|
|
83
|
+
password: 'password',
|
|
84
|
+
appSchema: 'tuyaSmart',
|
|
85
|
+
generateWeatherAccessory: false,
|
|
86
|
+
weatherAPI: '',
|
|
87
|
+
forceIPv4: false,
|
|
88
|
+
} as TuyaPlatformHomeConfig;
|
|
96
89
|
|
|
97
|
-
|
|
98
|
-
const debugManager = new TuyaDeviceManager(mockAPI, true);
|
|
99
|
-
expect(debugManager.debug).toBe(true);
|
|
90
|
+
manager = new TuyaHomeDeviceManager(mockAPI, mockConfig, false);
|
|
100
91
|
});
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
describe('initialization', () => {
|
|
94
|
+
test('creates device manager instance', () => {
|
|
95
|
+
expect(manager).toBeInstanceOf(EventEmitter);
|
|
96
|
+
expect(manager).toBeDefined();
|
|
97
|
+
});
|
|
105
98
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
test('stores API reference', () => {
|
|
100
|
+
expect(manager.api).toBe(mockAPI);
|
|
101
|
+
});
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
103
|
+
test('initializes with debug disabled', () => {
|
|
104
|
+
expect(manager.debug).toBe(false);
|
|
105
|
+
});
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
107
|
+
test('initializes with debug enabled', () => {
|
|
108
|
+
const debugManager = new TuyaHomeDeviceManager(mockAPI, mockConfig, true);
|
|
109
|
+
expect(debugManager.debug).toBe(true);
|
|
110
|
+
});
|
|
120
111
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
112
|
+
test('initializes MQTT connection', () => {
|
|
113
|
+
expect(manager.mq).toBeDefined();
|
|
114
|
+
});
|
|
125
115
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
test('creates logger', () => {
|
|
117
|
+
expect(manager['log']).toBeDefined();
|
|
118
|
+
expect((manager['log'] as any).prefix).toEqual('TuyaHomeDeviceManager');
|
|
119
|
+
});
|
|
129
120
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
test('initializes empty owner IDs array', () => {
|
|
122
|
+
expect(manager.ownerIDs).toEqual([]);
|
|
123
|
+
expect(Array.isArray(manager.ownerIDs)).toBe(true);
|
|
124
|
+
});
|
|
133
125
|
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
test('initializes empty devices array', () => {
|
|
127
|
+
expect(manager.devices).toEqual([]);
|
|
128
|
+
expect(Array.isArray(manager.devices)).toBe(true);
|
|
129
|
+
});
|
|
136
130
|
});
|
|
137
|
-
});
|
|
138
131
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
id: 'base_device_001',
|
|
143
|
-
product_id: 'product_001',
|
|
144
|
-
category: 'dj',
|
|
145
|
-
name: 'Base Light',
|
|
146
|
-
schema: [],
|
|
147
|
-
status: [],
|
|
132
|
+
describe('static events', () => {
|
|
133
|
+
test('has DEVICE_ADD event', () => {
|
|
134
|
+
expect(TuyaDeviceManager.Events.DEVICE_ADD).toBe('DEVICE_ADD');
|
|
148
135
|
});
|
|
149
136
|
|
|
150
|
-
|
|
137
|
+
test('has DEVICE_INFO_UPDATE event', () => {
|
|
138
|
+
expect(TuyaDeviceManager.Events.DEVICE_INFO_UPDATE).toBe('DEVICE_INFO_UPDATE');
|
|
139
|
+
});
|
|
151
140
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
expect(virtualDevice.parent_id).toBe('base_device_001');
|
|
156
|
-
});
|
|
141
|
+
test('has DEVICE_STATUS_UPDATE event', () => {
|
|
142
|
+
expect(TuyaDeviceManager.Events.DEVICE_STATUS_UPDATE).toBe('DEVICE_STATUS_UPDATE');
|
|
143
|
+
});
|
|
157
144
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
id: 'base_001',
|
|
161
|
-
product_id: 'prod_001',
|
|
162
|
-
category: 'dj',
|
|
163
|
-
name: 'Base',
|
|
164
|
-
schema: [],
|
|
165
|
-
status: [],
|
|
145
|
+
test('has DEVICE_DELETE event', () => {
|
|
146
|
+
expect(TuyaDeviceManager.Events.DEVICE_DELETE).toBe('DEVICE_DELETE');
|
|
166
147
|
});
|
|
148
|
+
});
|
|
167
149
|
|
|
168
|
-
|
|
150
|
+
describe('virtual device creation', () => {
|
|
151
|
+
test('creates virtual device from base device', () => {
|
|
152
|
+
const baseDevice = new TuyaDevice({
|
|
153
|
+
id: 'base_device_001',
|
|
154
|
+
product_id: 'product_001',
|
|
155
|
+
category: 'dj',
|
|
156
|
+
name: 'Base Light',
|
|
157
|
+
schema: [],
|
|
158
|
+
status: [],
|
|
159
|
+
});
|
|
169
160
|
|
|
170
|
-
|
|
171
|
-
expect(virtualDevice.product_name).toBe('virtual product');
|
|
172
|
-
expect(virtualDevice.ip).toBe('');
|
|
173
|
-
expect(virtualDevice.remote_keys).toBeUndefined();
|
|
174
|
-
});
|
|
161
|
+
const virtualDevice = manager.createVirtualDevice(baseDevice, 'virtual_uuid');
|
|
175
162
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
category: 'kg',
|
|
181
|
-
name: 'Base',
|
|
182
|
-
schema: [],
|
|
183
|
-
status: [],
|
|
163
|
+
expect(virtualDevice).toBeDefined();
|
|
164
|
+
expect(virtualDevice).toBeInstanceOf(TuyaDevice);
|
|
165
|
+
expect(virtualDevice.sub).toBe(true);
|
|
166
|
+
expect(virtualDevice.parent_id).toBe('base_device_001');
|
|
184
167
|
});
|
|
185
168
|
|
|
186
|
-
|
|
169
|
+
test('sets virtual device properties', () => {
|
|
170
|
+
const baseDevice = new TuyaDevice({
|
|
171
|
+
id: 'base_001',
|
|
172
|
+
product_id: 'prod_001',
|
|
173
|
+
category: 'dj',
|
|
174
|
+
name: 'Base',
|
|
175
|
+
schema: [],
|
|
176
|
+
status: [],
|
|
177
|
+
});
|
|
187
178
|
|
|
188
|
-
|
|
189
|
-
expect(virtualDevice.id.length).toBeGreaterThan(0);
|
|
190
|
-
});
|
|
179
|
+
const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_uuid_123');
|
|
191
180
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
category: 'dj',
|
|
197
|
-
name: 'Base',
|
|
198
|
-
schema: [
|
|
199
|
-
{
|
|
200
|
-
code: 'switch',
|
|
201
|
-
mode: 'rw' as any,
|
|
202
|
-
type: 'Boolean' as any,
|
|
203
|
-
property: {} as any,
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
status: [],
|
|
181
|
+
expect(virtualDevice.name).toBe('Virtual Device');
|
|
182
|
+
expect(virtualDevice.product_name).toBe('virtual product');
|
|
183
|
+
expect(virtualDevice.ip).toBe('');
|
|
184
|
+
expect(virtualDevice.remote_keys).toBeUndefined();
|
|
207
185
|
});
|
|
208
186
|
|
|
209
|
-
|
|
187
|
+
test('generates UUID if not provided', () => {
|
|
188
|
+
const baseDevice = new TuyaDevice({
|
|
189
|
+
id: 'base_001',
|
|
190
|
+
product_id: 'prod_001',
|
|
191
|
+
category: 'kg',
|
|
192
|
+
name: 'Base',
|
|
193
|
+
schema: [],
|
|
194
|
+
status: [],
|
|
195
|
+
});
|
|
210
196
|
|
|
211
|
-
|
|
212
|
-
expect(virtualDevice.schema.length).toBeGreaterThan(0);
|
|
213
|
-
});
|
|
197
|
+
const virtualDevice = manager.createVirtualDevice(baseDevice, '');
|
|
214
198
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
id: 'base_001',
|
|
218
|
-
product_id: 'prod_001',
|
|
219
|
-
category: 'dj',
|
|
220
|
-
name: 'Base',
|
|
221
|
-
schema: [],
|
|
222
|
-
status: [{ code: 'switch_1', value: 1 }],
|
|
199
|
+
expect(virtualDevice.id).toBeDefined();
|
|
200
|
+
expect(virtualDevice.id.length).toBeGreaterThan(0);
|
|
223
201
|
});
|
|
224
202
|
|
|
225
|
-
|
|
203
|
+
test('clones base device schema', () => {
|
|
204
|
+
const baseDevice = new TuyaDevice({
|
|
205
|
+
id: 'base_001',
|
|
206
|
+
product_id: 'prod_001',
|
|
207
|
+
category: 'dj',
|
|
208
|
+
name: 'Base',
|
|
209
|
+
schema: [
|
|
210
|
+
{
|
|
211
|
+
code: 'switch',
|
|
212
|
+
mode: 'rw' as any,
|
|
213
|
+
type: 'Boolean' as any,
|
|
214
|
+
property: {} as any,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
status: [],
|
|
218
|
+
});
|
|
226
219
|
|
|
227
|
-
|
|
228
|
-
});
|
|
229
|
-
});
|
|
220
|
+
const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_uuid');
|
|
230
221
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const device1 = new TuyaDevice({
|
|
234
|
-
id: 'device_001',
|
|
235
|
-
product_id: 'prod_001',
|
|
236
|
-
category: 'dj',
|
|
237
|
-
name: 'Device 1',
|
|
238
|
-
schema: [],
|
|
239
|
-
status: [],
|
|
222
|
+
expect(virtualDevice.schema).toBeDefined();
|
|
223
|
+
expect(virtualDevice.schema.length).toBeGreaterThan(0);
|
|
240
224
|
});
|
|
241
225
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
226
|
+
test('clones base device status', () => {
|
|
227
|
+
const baseDevice = new TuyaDevice({
|
|
228
|
+
id: 'base_001',
|
|
229
|
+
product_id: 'prod_001',
|
|
230
|
+
category: 'dj',
|
|
231
|
+
name: 'Base',
|
|
232
|
+
schema: [],
|
|
233
|
+
status: [{ code: 'switch_1', value: 1 }],
|
|
234
|
+
});
|
|
247
235
|
|
|
248
|
-
|
|
249
|
-
manager.devices = [];
|
|
250
|
-
const found = manager.getDevice('non_existent');
|
|
236
|
+
const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_uuid');
|
|
251
237
|
|
|
252
|
-
|
|
238
|
+
expect(virtualDevice.status).toBeDefined();
|
|
239
|
+
});
|
|
253
240
|
});
|
|
254
241
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
242
|
+
describe('device retrieval', () => {
|
|
243
|
+
test('finds device by ID', () => {
|
|
244
|
+
const device1 = new TuyaDevice({
|
|
245
|
+
id: 'device_001',
|
|
246
|
+
product_id: 'prod_001',
|
|
247
|
+
category: 'dj',
|
|
248
|
+
name: 'Device 1',
|
|
249
|
+
schema: [],
|
|
250
|
+
status: [],
|
|
251
|
+
});
|
|
264
252
|
|
|
265
|
-
|
|
266
|
-
id: 'device_002',
|
|
267
|
-
product_id: 'prod_002',
|
|
268
|
-
category: 'kg',
|
|
269
|
-
name: 'Device 2',
|
|
270
|
-
schema: [],
|
|
271
|
-
status: [],
|
|
272
|
-
});
|
|
253
|
+
manager.devices.push(device1);
|
|
273
254
|
|
|
274
|
-
|
|
255
|
+
const found = manager.getDevice('device_001');
|
|
256
|
+
expect(found).toBe(device1);
|
|
257
|
+
});
|
|
275
258
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
280
|
-
});
|
|
259
|
+
test('returns undefined for non-existent device', () => {
|
|
260
|
+
manager.devices = [];
|
|
261
|
+
const found = manager.getDevice('non_existent');
|
|
281
262
|
|
|
282
|
-
|
|
283
|
-
test('can add devices to array', () => {
|
|
284
|
-
const device = new TuyaDevice({
|
|
285
|
-
id: 'dev_001',
|
|
286
|
-
product_id: 'prod_001',
|
|
287
|
-
category: 'dj',
|
|
288
|
-
name: 'Device',
|
|
289
|
-
schema: [],
|
|
290
|
-
status: [],
|
|
263
|
+
expect(found).toBeUndefined();
|
|
291
264
|
});
|
|
292
265
|
|
|
293
|
-
|
|
266
|
+
test('finds correct device among multiple', () => {
|
|
267
|
+
const device1 = new TuyaDevice({
|
|
268
|
+
id: 'device_001',
|
|
269
|
+
product_id: 'prod_001',
|
|
270
|
+
category: 'dj',
|
|
271
|
+
name: 'Device 1',
|
|
272
|
+
schema: [],
|
|
273
|
+
status: [],
|
|
274
|
+
});
|
|
294
275
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
276
|
+
const device2 = new TuyaDevice({
|
|
277
|
+
id: 'device_002',
|
|
278
|
+
product_id: 'prod_002',
|
|
279
|
+
category: 'kg',
|
|
280
|
+
name: 'Device 2',
|
|
281
|
+
schema: [],
|
|
282
|
+
status: [],
|
|
283
|
+
});
|
|
298
284
|
|
|
299
|
-
|
|
300
|
-
const device1 = new TuyaDevice({
|
|
301
|
-
id: 'dev_001',
|
|
302
|
-
product_id: 'prod_001',
|
|
303
|
-
category: 'dj',
|
|
304
|
-
name: 'Device 1',
|
|
305
|
-
schema: [],
|
|
306
|
-
status: [],
|
|
307
|
-
});
|
|
285
|
+
manager.devices = [device1, device2];
|
|
308
286
|
|
|
309
|
-
|
|
310
|
-
id
|
|
311
|
-
|
|
312
|
-
category: 'kg',
|
|
313
|
-
name: 'Device 2',
|
|
314
|
-
schema: [],
|
|
315
|
-
status: [],
|
|
287
|
+
const found = manager.getDevice('device_002');
|
|
288
|
+
expect(found?.id).toBe('device_002');
|
|
289
|
+
expect(found?.category).toBe('kg');
|
|
316
290
|
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('device array management', () => {
|
|
294
|
+
test('can add devices to array', () => {
|
|
295
|
+
const device = new TuyaDevice({
|
|
296
|
+
id: 'dev_001',
|
|
297
|
+
product_id: 'prod_001',
|
|
298
|
+
category: 'dj',
|
|
299
|
+
name: 'Device',
|
|
300
|
+
schema: [],
|
|
301
|
+
status: [],
|
|
302
|
+
});
|
|
317
303
|
|
|
318
|
-
|
|
319
|
-
manager.devices = manager.devices.filter(d => d.id !== 'dev_001');
|
|
304
|
+
manager.devices.push(device);
|
|
320
305
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
306
|
+
expect(manager.devices.length).toBe(1);
|
|
307
|
+
expect(manager.devices[0]).toBe(device);
|
|
308
|
+
});
|
|
324
309
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
product_id: 'prod_1',
|
|
310
|
+
test('can remove devices from array', () => {
|
|
311
|
+
const device1 = new TuyaDevice({
|
|
312
|
+
id: 'dev_001',
|
|
313
|
+
product_id: 'prod_001',
|
|
330
314
|
category: 'dj',
|
|
331
315
|
name: 'Device 1',
|
|
332
316
|
schema: [],
|
|
333
317
|
status: [],
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const device2 = new TuyaDevice({
|
|
321
|
+
id: 'dev_002',
|
|
322
|
+
product_id: 'prod_002',
|
|
338
323
|
category: 'kg',
|
|
339
324
|
name: 'Device 2',
|
|
340
325
|
schema: [],
|
|
341
326
|
status: [],
|
|
342
|
-
})
|
|
343
|
-
];
|
|
327
|
+
});
|
|
344
328
|
|
|
345
|
-
|
|
329
|
+
manager.devices = [device1, device2];
|
|
330
|
+
manager.devices = manager.devices.filter(d => d.id !== 'dev_001');
|
|
346
331
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
332
|
+
expect(manager.devices.length).toBe(1);
|
|
333
|
+
expect(manager.devices[0].id).toBe('dev_002');
|
|
334
|
+
});
|
|
350
335
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
336
|
+
test('can clear all devices', () => {
|
|
337
|
+
manager.devices = [
|
|
338
|
+
new TuyaDevice({
|
|
339
|
+
id: 'dev_1',
|
|
340
|
+
product_id: 'prod_1',
|
|
341
|
+
category: 'dj',
|
|
342
|
+
name: 'Device 1',
|
|
343
|
+
schema: [],
|
|
344
|
+
status: [],
|
|
345
|
+
}),
|
|
346
|
+
new TuyaDevice({
|
|
347
|
+
id: 'dev_2',
|
|
348
|
+
product_id: 'prod_2',
|
|
349
|
+
category: 'kg',
|
|
350
|
+
name: 'Device 2',
|
|
351
|
+
schema: [],
|
|
352
|
+
status: [],
|
|
353
|
+
}),
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
manager.devices = [];
|
|
355
357
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
expect(manager.devices.length).toBe(0);
|
|
359
|
+
});
|
|
358
360
|
});
|
|
359
361
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
362
|
+
describe('owner ID management', () => {
|
|
363
|
+
test('can add owner IDs', () => {
|
|
364
|
+
manager.ownerIDs.push('owner_123');
|
|
365
|
+
manager.ownerIDs.push('owner_456');
|
|
363
366
|
|
|
364
|
-
|
|
365
|
-
|
|
367
|
+
expect(manager.ownerIDs.length).toBe(2);
|
|
368
|
+
expect(manager.ownerIDs).toContain('owner_123');
|
|
369
|
+
});
|
|
366
370
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
);
|
|
371
|
+
test('can clear owner IDs', () => {
|
|
372
|
+
manager.ownerIDs = ['owner_1', 'owner_2'];
|
|
373
|
+
manager.ownerIDs = [];
|
|
371
374
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
});
|
|
375
|
+
expect(manager.ownerIDs.length).toBe(0);
|
|
376
|
+
});
|
|
375
377
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
378
|
+
test('prevents duplicate owner IDs', () => {
|
|
379
|
+
manager.ownerIDs = ['owner_123', 'owner_456'].filter(
|
|
380
|
+
(id, index, self) => self.indexOf(id) === index
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
expect(new Set(manager.ownerIDs).size).toBe(manager.ownerIDs.length);
|
|
384
|
+
});
|
|
379
385
|
});
|
|
380
386
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
expect(
|
|
384
|
-
done();
|
|
387
|
+
describe('event emitter behavior', () => {
|
|
388
|
+
test('is instance of EventEmitter', () => {
|
|
389
|
+
expect(manager instanceof EventEmitter).toBe(true);
|
|
385
390
|
});
|
|
386
391
|
|
|
387
|
-
|
|
388
|
-
|
|
392
|
+
test('can listen for events', (done) => {
|
|
393
|
+
manager.on('test_event', (data) => {
|
|
394
|
+
expect(data).toBe('test_data');
|
|
395
|
+
done();
|
|
396
|
+
});
|
|
389
397
|
|
|
390
|
-
|
|
391
|
-
manager.on(TuyaDeviceManager.Events.DEVICE_ADD, (device) => {
|
|
392
|
-
expect(device.id).toBe('device_001');
|
|
393
|
-
done();
|
|
398
|
+
manager.emit('test_event', 'test_data');
|
|
394
399
|
});
|
|
395
400
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
401
|
+
test('can emit DEVICE_ADD events', (done) => {
|
|
402
|
+
manager.on(TuyaDeviceManager.Events.DEVICE_ADD, (device) => {
|
|
403
|
+
expect(device.id).toBe('device_001');
|
|
404
|
+
done();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const device = new TuyaDevice({
|
|
408
|
+
id: 'device_001',
|
|
409
|
+
product_id: 'prod_001',
|
|
410
|
+
category: 'dj',
|
|
411
|
+
name: 'Device',
|
|
412
|
+
schema: [],
|
|
413
|
+
status: [],
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
manager.emit(TuyaDeviceManager.Events.DEVICE_ADD, device);
|
|
403
417
|
});
|
|
404
418
|
|
|
405
|
-
|
|
419
|
+
test('can emit DEVICE_STATUS_UPDATE events', (done) => {
|
|
420
|
+
manager.on(TuyaDeviceManager.Events.DEVICE_STATUS_UPDATE, (deviceId, status) => {
|
|
421
|
+
expect(deviceId).toBe('device_001');
|
|
422
|
+
expect(status).toBeDefined();
|
|
423
|
+
done();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
manager.emit(TuyaDeviceManager.Events.DEVICE_STATUS_UPDATE, 'device_001', {});
|
|
427
|
+
});
|
|
406
428
|
});
|
|
407
429
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
expect(
|
|
412
|
-
done();
|
|
430
|
+
describe('MQTT integration', () => {
|
|
431
|
+
test('adds message listener on init', () => {
|
|
432
|
+
const newManager = new TuyaHomeDeviceManager(mockAPI, mockConfig, false);
|
|
433
|
+
expect(newManager.mq.addMessageListener).toHaveBeenCalled();
|
|
413
434
|
});
|
|
414
435
|
|
|
415
|
-
|
|
436
|
+
test('MQTT is available through manager', () => {
|
|
437
|
+
expect(manager.mq).toBeDefined();
|
|
438
|
+
expect(manager.mq.addMessageListener).toBeDefined();
|
|
439
|
+
});
|
|
416
440
|
});
|
|
417
|
-
});
|
|
418
441
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
442
|
+
describe('device filtering', () => {
|
|
443
|
+
test('can filter devices by category', () => {
|
|
444
|
+
manager.devices = [
|
|
445
|
+
new TuyaDevice({
|
|
446
|
+
id: 'light_1',
|
|
447
|
+
product_id: 'prod_1',
|
|
448
|
+
category: 'dj',
|
|
449
|
+
name: 'Light',
|
|
450
|
+
schema: [],
|
|
451
|
+
status: [],
|
|
452
|
+
}),
|
|
453
|
+
new TuyaDevice({
|
|
454
|
+
id: 'switch_1',
|
|
455
|
+
product_id: 'prod_2',
|
|
456
|
+
category: 'kg',
|
|
457
|
+
name: 'Switch',
|
|
458
|
+
schema: [],
|
|
459
|
+
status: [],
|
|
460
|
+
}),
|
|
461
|
+
];
|
|
462
|
+
|
|
463
|
+
const lights = manager.devices.filter(d => d.category === 'dj');
|
|
464
|
+
expect(lights.length).toBe(1);
|
|
465
|
+
expect(lights[0].category).toBe('dj');
|
|
466
|
+
});
|
|
424
467
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
468
|
+
test('can filter virtual devices', () => {
|
|
469
|
+
const baseDevice = new TuyaDevice({
|
|
470
|
+
id: 'base_001',
|
|
471
|
+
product_id: 'prod_001',
|
|
472
|
+
category: 'dj',
|
|
473
|
+
name: 'Base',
|
|
474
|
+
schema: [],
|
|
475
|
+
status: [],
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_123');
|
|
479
|
+
manager.devices = [baseDevice, virtualDevice];
|
|
480
|
+
|
|
481
|
+
const virtuals = manager.devices.filter(d => d.sub === true);
|
|
482
|
+
expect(virtuals.length).toBe(1);
|
|
483
|
+
expect(virtuals[0].parent_id).toBe('base_001');
|
|
484
|
+
});
|
|
428
485
|
});
|
|
429
|
-
});
|
|
430
486
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
new
|
|
435
|
-
|
|
487
|
+
describe('multiple manager instances', () => {
|
|
488
|
+
test('maintain separate device lists', () => {
|
|
489
|
+
const manager1 = new TuyaHomeDeviceManager(mockAPI, mockConfig, false);
|
|
490
|
+
const manager2 = new TuyaHomeDeviceManager(mockAPI, mockConfig, false);
|
|
491
|
+
|
|
492
|
+
const device1 = new TuyaDevice({
|
|
493
|
+
id: 'dev_manager1',
|
|
436
494
|
product_id: 'prod_1',
|
|
437
495
|
category: 'dj',
|
|
438
|
-
name: '
|
|
496
|
+
name: 'Device 1',
|
|
439
497
|
schema: [],
|
|
440
498
|
status: [],
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const device2 = new TuyaDevice({
|
|
502
|
+
id: 'dev_manager2',
|
|
444
503
|
product_id: 'prod_2',
|
|
445
504
|
category: 'kg',
|
|
446
|
-
name: '
|
|
505
|
+
name: 'Device 2',
|
|
447
506
|
schema: [],
|
|
448
507
|
status: [],
|
|
449
|
-
})
|
|
450
|
-
];
|
|
508
|
+
});
|
|
451
509
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
expect(lights[0].category).toBe('dj');
|
|
455
|
-
});
|
|
510
|
+
manager1.devices.push(device1);
|
|
511
|
+
manager2.devices.push(device2);
|
|
456
512
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
id
|
|
460
|
-
product_id: 'prod_001',
|
|
461
|
-
category: 'dj',
|
|
462
|
-
name: 'Base',
|
|
463
|
-
schema: [],
|
|
464
|
-
status: [],
|
|
513
|
+
expect(manager1.devices.length).toBe(1);
|
|
514
|
+
expect(manager2.devices.length).toBe(1);
|
|
515
|
+
expect(manager1.devices[0].id).not.toBe(manager2.devices[0].id);
|
|
465
516
|
});
|
|
466
|
-
|
|
467
|
-
const virtualDevice = manager.createVirtualDevice(baseDevice, 'virt_123');
|
|
468
|
-
manager.devices = [baseDevice, virtualDevice];
|
|
469
|
-
|
|
470
|
-
const virtuals = manager.devices.filter(d => d.sub === true);
|
|
471
|
-
expect(virtuals.length).toBe(1);
|
|
472
|
-
expect(virtuals[0].parent_id).toBe('base_001');
|
|
473
517
|
});
|
|
474
|
-
});
|
|
475
518
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
519
|
+
describe('device type handling', () => {
|
|
520
|
+
test('handles light devices', () => {
|
|
521
|
+
const light = new TuyaDevice({
|
|
522
|
+
id: 'light_001',
|
|
523
|
+
product_id: 'dj_prod',
|
|
524
|
+
category: 'dj',
|
|
525
|
+
name: 'Smart Light',
|
|
526
|
+
schema: [],
|
|
527
|
+
status: [],
|
|
528
|
+
});
|
|
480
529
|
|
|
481
|
-
|
|
482
|
-
id: 'dev_manager1',
|
|
483
|
-
product_id: 'prod_1',
|
|
484
|
-
category: 'dj',
|
|
485
|
-
name: 'Device 1',
|
|
486
|
-
schema: [],
|
|
487
|
-
status: [],
|
|
488
|
-
});
|
|
530
|
+
manager.devices.push(light);
|
|
489
531
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
product_id: 'prod_2',
|
|
493
|
-
category: 'kg',
|
|
494
|
-
name: 'Device 2',
|
|
495
|
-
schema: [],
|
|
496
|
-
status: [],
|
|
532
|
+
const found = manager.getDevice('light_001');
|
|
533
|
+
expect(found?.category).toBe('dj');
|
|
497
534
|
});
|
|
498
535
|
|
|
499
|
-
|
|
500
|
-
|
|
536
|
+
test('handles switch devices', () => {
|
|
537
|
+
const sw = new TuyaDevice({
|
|
538
|
+
id: 'switch_001',
|
|
539
|
+
product_id: 'kg_prod',
|
|
540
|
+
category: 'kg',
|
|
541
|
+
name: 'Smart Switch',
|
|
542
|
+
schema: [],
|
|
543
|
+
status: [],
|
|
544
|
+
});
|
|
501
545
|
|
|
502
|
-
|
|
503
|
-
expect(manager2.devices.length).toBe(1);
|
|
504
|
-
expect(manager1.devices[0].id).not.toBe(manager2.devices[0].id);
|
|
505
|
-
});
|
|
506
|
-
});
|
|
546
|
+
manager.devices.push(sw);
|
|
507
547
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const light = new TuyaDevice({
|
|
511
|
-
id: 'light_001',
|
|
512
|
-
product_id: 'dj_prod',
|
|
513
|
-
category: 'dj',
|
|
514
|
-
name: 'Smart Light',
|
|
515
|
-
schema: [],
|
|
516
|
-
status: [],
|
|
548
|
+
const found = manager.getDevice('switch_001');
|
|
549
|
+
expect(found?.category).toBe('kg');
|
|
517
550
|
});
|
|
518
551
|
|
|
519
|
-
|
|
552
|
+
test('handles thermostat devices', () => {
|
|
553
|
+
const thermostat = new TuyaDevice({
|
|
554
|
+
id: 'thermo_001',
|
|
555
|
+
product_id: 'wk_prod',
|
|
556
|
+
category: 'wk',
|
|
557
|
+
name: 'Thermostat',
|
|
558
|
+
schema: [],
|
|
559
|
+
status: [],
|
|
560
|
+
});
|
|
520
561
|
|
|
521
|
-
|
|
522
|
-
expect(found?.category).toBe('dj');
|
|
523
|
-
});
|
|
562
|
+
manager.devices.push(thermostat);
|
|
524
563
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
id: 'switch_001',
|
|
528
|
-
product_id: 'kg_prod',
|
|
529
|
-
category: 'kg',
|
|
530
|
-
name: 'Smart Switch',
|
|
531
|
-
schema: [],
|
|
532
|
-
status: [],
|
|
564
|
+
const found = manager.getDevice('thermo_001');
|
|
565
|
+
expect(found?.category).toBe('wk');
|
|
533
566
|
});
|
|
567
|
+
});
|
|
534
568
|
|
|
535
|
-
|
|
569
|
+
describe('async operations', () => {
|
|
570
|
+
test('updateDevices returns array', async () => {
|
|
571
|
+
const result = await manager.updateDevices([]);
|
|
572
|
+
expect(Array.isArray(result)).toBe(true);
|
|
573
|
+
});
|
|
536
574
|
|
|
537
|
-
|
|
538
|
-
|
|
575
|
+
test('updateDevice can be called', async () => {
|
|
576
|
+
// Mock implementation since actual HTTP calls are not set up
|
|
577
|
+
const result = await manager.updateDevice('device_001');
|
|
578
|
+
expect(typeof result === 'object' || result === null).toBe(true);
|
|
579
|
+
});
|
|
539
580
|
});
|
|
540
581
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
category: 'wk',
|
|
546
|
-
name: 'Thermostat',
|
|
547
|
-
schema: [],
|
|
548
|
-
status: [],
|
|
582
|
+
describe('infrared device methods', () => {
|
|
583
|
+
test('getInfraredRemotes calls API with correct endpoint', async () => {
|
|
584
|
+
const result = await manager.getInfraredRemotes('ir_001');
|
|
585
|
+
expect(result).toBeDefined();
|
|
549
586
|
});
|
|
550
587
|
|
|
551
|
-
|
|
588
|
+
test('getInfraredKeys calls API with correct endpoint', async () => {
|
|
589
|
+
const result = await manager.getInfraredKeys('ir_001', 'remote_001');
|
|
590
|
+
expect(result).toBeDefined();
|
|
591
|
+
});
|
|
552
592
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
593
|
+
test('getInfraredACStatus calls API with correct endpoint', async () => {
|
|
594
|
+
const result = await manager.getInfraredACStatus('ir_001', 'remote_001');
|
|
595
|
+
expect(result).toBeDefined();
|
|
596
|
+
});
|
|
557
597
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
598
|
+
test('getInfraredDIYKeys calls API with correct endpoint', async () => {
|
|
599
|
+
const result = await manager.getInfraredDIYKeys('ir_001', 'remote_001');
|
|
600
|
+
expect(result).toBeDefined();
|
|
601
|
+
});
|
|
562
602
|
});
|
|
563
603
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
604
|
+
describe('infrared remote resolution', () => {
|
|
605
|
+
test('resolveInfraredRemotes returns empty array when no IR devices', async () => {
|
|
606
|
+
const parentDevice = new TuyaDevice({
|
|
607
|
+
id: 'parent_001',
|
|
608
|
+
name: 'Hub',
|
|
609
|
+
category: 'wifizigbee',
|
|
610
|
+
product_id: 'hub_prod',
|
|
611
|
+
schema: [],
|
|
612
|
+
status: [],
|
|
613
|
+
});
|
|
570
614
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
});
|
|
615
|
+
const result = await manager.resolveInfraredRemotes(parentDevice, [parentDevice]);
|
|
616
|
+
expect(Array.isArray(result)).toBe(true);
|
|
617
|
+
expect(result.length).toBe(0);
|
|
618
|
+
});
|
|
576
619
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
620
|
+
test('resolveInfraredRemotes finds IR devices with matching location', async () => {
|
|
621
|
+
const parentDevice = new TuyaDevice({
|
|
622
|
+
id: 'parent_001',
|
|
623
|
+
name: 'Hub',
|
|
624
|
+
category: 'wnykq',
|
|
625
|
+
product_id: 'hub_prod',
|
|
626
|
+
schema: [],
|
|
627
|
+
status: [],
|
|
628
|
+
lat: '40.7128',
|
|
629
|
+
lon: '-74.0060',
|
|
630
|
+
});
|
|
581
631
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
632
|
+
const irDevice = new TuyaDevice({
|
|
633
|
+
id: 'ir_001',
|
|
634
|
+
name: 'IR Remote',
|
|
635
|
+
category: 'infrared_ac',
|
|
636
|
+
product_id: 'ir_prod',
|
|
637
|
+
schema: [],
|
|
638
|
+
status: [],
|
|
639
|
+
sub: true,
|
|
640
|
+
lat: '40.7128',
|
|
641
|
+
lon: '-74.0060',
|
|
642
|
+
});
|
|
586
643
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
describe('infrared remote resolution', () => {
|
|
594
|
-
test('resolveInfraredRemotes returns empty array when no IR devices', () => {
|
|
595
|
-
const parentDevice = new TuyaDevice({
|
|
596
|
-
id: 'parent_001',
|
|
597
|
-
name: 'Hub',
|
|
598
|
-
category: 'wifizigbee',
|
|
599
|
-
product_id: 'hub_prod',
|
|
600
|
-
schema: [],
|
|
601
|
-
status: [],
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
const result = manager.resolveInfraredRemotes(parentDevice, [parentDevice]);
|
|
605
|
-
expect(Array.isArray(result)).toBe(true);
|
|
606
|
-
expect(result.length).toBe(0);
|
|
607
|
-
});
|
|
644
|
+
const result = await manager.resolveInfraredRemotes(parentDevice, [parentDevice, irDevice]);
|
|
645
|
+
expect(result.length).toBeGreaterThan(0);
|
|
646
|
+
});
|
|
608
647
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const irDevice = new TuyaDevice({
|
|
622
|
-
id: 'ir_001',
|
|
623
|
-
name: 'IR Remote',
|
|
624
|
-
category: 'infrared_ac',
|
|
625
|
-
product_id: 'ir_prod',
|
|
626
|
-
schema: [],
|
|
627
|
-
status: [],
|
|
628
|
-
sub: true,
|
|
629
|
-
lat: '40.7128',
|
|
630
|
-
lon: '-74.0060',
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
const result = manager.resolveInfraredRemotes(parentDevice, [parentDevice, irDevice]);
|
|
634
|
-
expect(result.length).toBeGreaterThan(0);
|
|
635
|
-
});
|
|
648
|
+
test('resolveInfraredRemotes finds IR devices with matching update_time', async () => {
|
|
649
|
+
const timestamp = 1234567890;
|
|
650
|
+
const parentDevice = new TuyaDevice({
|
|
651
|
+
id: 'parent_001',
|
|
652
|
+
name: 'Hub',
|
|
653
|
+
category: 'wnykq',
|
|
654
|
+
product_id: 'hub_prod',
|
|
655
|
+
schema: [],
|
|
656
|
+
status: [],
|
|
657
|
+
update_time: timestamp,
|
|
658
|
+
});
|
|
636
659
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
const irDevice = new TuyaDevice({
|
|
650
|
-
id: 'ir_001',
|
|
651
|
-
name: 'IR Remote',
|
|
652
|
-
category: 'infrared_light',
|
|
653
|
-
product_id: 'ir_prod',
|
|
654
|
-
schema: [],
|
|
655
|
-
status: [],
|
|
656
|
-
sub: true,
|
|
657
|
-
update_time: timestamp,
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
const result = manager.resolveInfraredRemotes(parentDevice, [parentDevice, irDevice]);
|
|
661
|
-
expect(result.length).toBeGreaterThan(0);
|
|
662
|
-
});
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
describe('infrared device fixing', () => {
|
|
666
|
-
test('fixInfraredDevice sets org_category_id', () => {
|
|
667
|
-
const device = new TuyaDevice({
|
|
668
|
-
id: 'ir_ac_001',
|
|
669
|
-
name: 'IR AC',
|
|
670
|
-
category: 'infrared_ac',
|
|
671
|
-
product_id: 'prsgoryjfdtb42r4',
|
|
672
|
-
schema: [],
|
|
673
|
-
status: [],
|
|
674
|
-
remote_keys: {
|
|
675
|
-
category_id: 5,
|
|
676
|
-
org_category_id: 5,
|
|
677
|
-
brand_id: 0,
|
|
678
|
-
remote_index: 0,
|
|
679
|
-
single_air: false,
|
|
680
|
-
duplicate_power: false,
|
|
681
|
-
key_list: [],
|
|
682
|
-
key_range: [],
|
|
683
|
-
},
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
manager.fixInfraredDevice(device);
|
|
687
|
-
expect(device.remote_keys?.org_category_id).toBe(5);
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
describe('HAP category resolution', () => {
|
|
692
|
-
test('resolveHAPCategoryID returns correct category for fan product', () => {
|
|
693
|
-
const device = new TuyaDevice({
|
|
694
|
-
id: 'ir_fan_001',
|
|
695
|
-
name: 'IR Fan',
|
|
696
|
-
category: 'infrared_fan',
|
|
697
|
-
product_id: 'prsgoryjfdtb42r4',
|
|
698
|
-
schema: [],
|
|
699
|
-
status: [],
|
|
700
|
-
remote_keys: {
|
|
701
|
-
category_id: 5,
|
|
702
|
-
org_category_id: 5,
|
|
703
|
-
brand_id: 0,
|
|
704
|
-
remote_index: 0,
|
|
705
|
-
single_air: false,
|
|
706
|
-
duplicate_power: false,
|
|
707
|
-
key_list: [],
|
|
708
|
-
key_range: [],
|
|
709
|
-
},
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
const result = manager.resolveHAPCategoryID(device);
|
|
713
|
-
expect(result).toBe(8); // Fan category
|
|
714
|
-
});
|
|
660
|
+
const irDevice = new TuyaDevice({
|
|
661
|
+
id: 'ir_001',
|
|
662
|
+
name: 'IR Remote',
|
|
663
|
+
category: 'infrared_light',
|
|
664
|
+
product_id: 'ir_prod',
|
|
665
|
+
schema: [],
|
|
666
|
+
status: [],
|
|
667
|
+
sub: true,
|
|
668
|
+
update_time: timestamp,
|
|
669
|
+
});
|
|
715
670
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
name: 'IR DIY',
|
|
720
|
-
category: 'infrared_diy',
|
|
721
|
-
product_id: 'k6ozylayfgnskuq6',
|
|
722
|
-
schema: [],
|
|
723
|
-
status: [],
|
|
724
|
-
remote_keys: {
|
|
725
|
-
category_id: 5,
|
|
726
|
-
org_category_id: 5,
|
|
727
|
-
brand_id: 0,
|
|
728
|
-
remote_index: 0,
|
|
729
|
-
single_air: false,
|
|
730
|
-
duplicate_power: false,
|
|
731
|
-
key_list: [],
|
|
732
|
-
key_range: [],
|
|
733
|
-
},
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
const result = manager.resolveHAPCategoryID(device);
|
|
737
|
-
expect(result).toBe(999); // DIY category
|
|
671
|
+
const result = await manager.resolveInfraredRemotes(parentDevice, [parentDevice, irDevice]);
|
|
672
|
+
expect(result.length).toBeGreaterThan(0);
|
|
673
|
+
});
|
|
738
674
|
});
|
|
739
675
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
676
|
+
describe('infrared device fixing', () => {
|
|
677
|
+
test('fixInfraredDevice sets org_category_id', () => {
|
|
678
|
+
const device = new TuyaDevice({
|
|
679
|
+
id: 'ir_ac_001',
|
|
680
|
+
name: 'IR AC',
|
|
681
|
+
category: 'infrared_ac',
|
|
682
|
+
product_id: 'prsgoryjfdtb42r4',
|
|
683
|
+
schema: [],
|
|
684
|
+
status: [],
|
|
685
|
+
remote_keys: {
|
|
686
|
+
category_id: 5,
|
|
687
|
+
org_category_id: 5,
|
|
688
|
+
brand_id: 0,
|
|
689
|
+
remote_index: 0,
|
|
690
|
+
single_air: false,
|
|
691
|
+
duplicate_power: false,
|
|
692
|
+
key_list: [],
|
|
693
|
+
key_range: [],
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
manager.fixInfraredDevice(device);
|
|
698
|
+
expect(device.remote_keys?.org_category_id).toBe(5);
|
|
699
|
+
});
|
|
762
700
|
});
|
|
763
701
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
702
|
+
describe('HAP category resolution', () => {
|
|
703
|
+
test('resolveHAPCategoryID returns correct category for fan product', () => {
|
|
704
|
+
const device = new TuyaDevice({
|
|
705
|
+
id: 'ir_fan_001',
|
|
706
|
+
name: 'IR Fan',
|
|
707
|
+
category: 'infrared_fan',
|
|
708
|
+
product_id: 'prsgoryjfdtb42r4',
|
|
709
|
+
schema: [],
|
|
710
|
+
status: [],
|
|
711
|
+
remote_keys: {
|
|
712
|
+
category_id: 5,
|
|
713
|
+
org_category_id: 5,
|
|
714
|
+
brand_id: 0,
|
|
715
|
+
remote_index: 0,
|
|
716
|
+
single_air: false,
|
|
717
|
+
duplicate_power: false,
|
|
718
|
+
key_list: [],
|
|
719
|
+
key_range: [],
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const result = manager.resolveHAPCategoryID(device);
|
|
724
|
+
expect(result).toBe(8); // Fan category
|
|
772
725
|
});
|
|
773
726
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
727
|
+
test('resolveHAPCategoryID returns DIY for DIY product', () => {
|
|
728
|
+
const device = new TuyaDevice({
|
|
729
|
+
id: 'ir_diy_001',
|
|
730
|
+
name: 'IR DIY',
|
|
731
|
+
category: 'infrared_diy',
|
|
732
|
+
product_id: 'k6ozylayfgnskuq6',
|
|
733
|
+
schema: [],
|
|
734
|
+
status: [],
|
|
735
|
+
remote_keys: {
|
|
736
|
+
category_id: 5,
|
|
737
|
+
org_category_id: 5,
|
|
738
|
+
brand_id: 0,
|
|
739
|
+
remote_index: 0,
|
|
740
|
+
single_air: false,
|
|
741
|
+
duplicate_power: false,
|
|
742
|
+
key_list: [],
|
|
743
|
+
key_range: [],
|
|
744
|
+
},
|
|
745
|
+
});
|
|
778
746
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
747
|
+
const result = manager.resolveHAPCategoryID(device);
|
|
748
|
+
expect(result).toBe(999); // DIY category
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test('resolveHAPCategoryID falls back to device category', () => {
|
|
752
|
+
const device = new TuyaDevice({
|
|
753
|
+
id: 'ir_other_001',
|
|
754
|
+
name: 'IR Other',
|
|
755
|
+
category: 'infrared_other',
|
|
756
|
+
product_id: 'other_prod',
|
|
787
757
|
schema: [],
|
|
788
758
|
status: [],
|
|
789
|
-
|
|
790
|
-
|
|
759
|
+
remote_keys: {
|
|
760
|
+
category_id: 7,
|
|
761
|
+
org_category_id: 7,
|
|
762
|
+
brand_id: 0,
|
|
763
|
+
remote_index: 0,
|
|
764
|
+
single_air: false,
|
|
765
|
+
duplicate_power: false,
|
|
766
|
+
key_list: [],
|
|
767
|
+
key_range: [],
|
|
768
|
+
},
|
|
769
|
+
});
|
|
791
770
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
771
|
+
const result = manager.resolveHAPCategoryID(device);
|
|
772
|
+
expect(result).toBe(7);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test('resolveHAPCategoryID defaults to DIY for device without remote_keys', () => {
|
|
776
|
+
const device = new TuyaDevice({
|
|
777
|
+
id: 'ir_001',
|
|
778
|
+
name: 'IR Device',
|
|
779
|
+
category: 'infrared_ac',
|
|
780
|
+
product_id: 'ir_prod',
|
|
781
|
+
schema: [],
|
|
782
|
+
status: [],
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
const result = manager.resolveHAPCategoryID(device);
|
|
786
|
+
expect(result).toBe(999);
|
|
787
|
+
});
|
|
804
788
|
});
|
|
805
789
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
790
|
+
describe('updateInfraredRemotes', () => {
|
|
791
|
+
test('updateInfraredRemotes skips non-IR devices', async () => {
|
|
792
|
+
const devices = [
|
|
793
|
+
new TuyaDevice({
|
|
794
|
+
id: 'light_001',
|
|
795
|
+
name: 'Light',
|
|
796
|
+
category: 'dj',
|
|
797
|
+
product_id: 'light_prod',
|
|
798
|
+
schema: [],
|
|
799
|
+
status: [],
|
|
800
|
+
}),
|
|
801
|
+
];
|
|
802
|
+
|
|
803
|
+
await manager.updateInfraredRemotes(devices);
|
|
804
|
+
// Should not throw
|
|
805
|
+
expect(true).toBe(true);
|
|
806
|
+
});
|
|
811
807
|
});
|
|
812
|
-
|
|
813
|
-
}
|
|
808
|
+
|
|
809
|
+
}
|
|
810
|
+
);
|