node-red-contrib-tplink-tapo-connect-api
Version:
This unofficial node-RED node allows connection to TP-Link Tapo devices. This project has been enhanced with AI support to enable new features. Starting with v0.50, we have added support for the KLAP protocol. To prioritize the operation of this node, we
168 lines • 6.14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlugDevice = void 0;
const base_device_1 = require("./base-device");
const protocol_selector_1 = require("../core/protocol-selector");
const auth_1 = require("../core/auth");
const klap_auth_1 = require("../core/klap-auth");
/**
* Plug device implementation using composition pattern
* Single responsibility: Plug-specific device behavior
*/
class PlugDevice extends base_device_1.BaseDevice {
constructor(ip, credentials, deviceModel) {
super(ip, credentials, deviceModel);
}
/**
* Initialize session based on selected protocol
*/
async initializeSession(protocol) {
try {
if (protocol === protocol_selector_1.ProtocolType.KLAP) {
await this.initializeKlapSession();
}
else {
await this.initializePassthroughSession();
}
}
catch (error) {
throw new Error(`Failed to initialize ${protocol} session: ${error.message}`);
}
}
/**
* Initialize KLAP session
*/
async initializeKlapSession() {
var _a, _b, _c, _d;
if (!this.klapAuth) {
this.klapAuth = new klap_auth_1.KlapAuth(this.ip, this.credentials);
}
await this.klapAuth.authenticate();
// Add small delay after authentication to prevent immediate -1012 errors
await new Promise(resolve => setTimeout(resolve, 500));
// Configure request executor to use KLAP auth
this.setRequestExecutor(async (request) => {
if (!this.klapAuth) {
throw new Error('KLAP authentication not initialized');
}
return this.klapAuth.secureRequest(request);
});
// Get session data from KLAP auth
const sessionData = {
sessionId: ((_b = (_a = this.klapAuth).getSessionId) === null || _b === void 0 ? void 0 : _b.call(_a)) || undefined,
token: ((_d = (_c = this.klapAuth).getToken) === null || _d === void 0 ? void 0 : _d.call(_c)) || undefined,
expiresAt: Date.now() + 1800000 // 30 minutes
};
await this.sessionManager.initializeSession(sessionData);
}
/**
* Initialize Passthrough session
*/
async initializePassthroughSession() {
var _a, _b;
if (!this.auth) {
this.auth = new auth_1.TapoAuth(this.ip, this.credentials);
}
const session = await this.auth.authenticate();
// Configure request executor to use Passthrough auth
this.setRequestExecutor(async (request) => {
if (!this.auth) {
throw new Error('Passthrough authentication not initialized');
}
return this.auth.secureRequest(request);
});
// Get session data from passthrough auth
const sessionData = {
sessionId: session.sessionId,
token: session.token,
cookies: ((_b = (_a = this.auth).getCookies) === null || _b === void 0 ? void 0 : _b.call(_a)) || undefined,
expiresAt: Date.now() + 1800000 // 30 minutes
};
await this.sessionManager.initializeSession(sessionData);
}
/**
* Get child devices (for multi-outlet plugs like P300)
*/
async getChildDevices() {
var _a;
if (!this.supportsChildDevices()) {
return [];
}
try {
const response = await this.sendRequest({
method: 'get_child_device_list',
params: {}
});
if (!response) {
throw new Error('Failed to get child devices: empty response');
}
if (response.error_code !== 0) {
throw new Error(`Failed to get child devices: ${response.error_code}`);
}
return ((_a = response.result) === null || _a === void 0 ? void 0 : _a.child_device_list) || [];
}
catch (error) {
console.warn('Failed to get child devices:', error);
return [];
}
}
/**
* Control child device (for multi-outlet plugs)
*/
async controlChildDevice(deviceId, turnOn) {
if (!this.supportsChildDevices()) {
throw new Error('Child device control not supported by this device');
}
const response = await this.sendRequest({
method: 'set_child_device_info',
params: {
device_id: deviceId,
device_on: turnOn
}
});
if (!response) {
throw new Error('Failed to control child device: empty response');
}
if (response.error_code !== 0) {
throw new Error(`Failed to control child device: ${response.error_code}`);
}
}
/**
* Check if device supports child devices
*/
supportsChildDevices() {
const supportedModels = ['P300', 'P304', 'KP303', 'KP400'];
return supportedModels.some(model => this.deviceModel.toUpperCase().includes(model.toUpperCase()));
}
/**
* Get plug-specific status
*/
async getPlugStatus() {
const deviceInfo = await this.getDeviceInfo();
const status = {
isOn: deviceInfo.device_on || false,
hasEnergyMonitoring: this.capabilities.hasEnergyMonitoring
};
// Get current power if supported
if (this.capabilities.hasEnergyMonitoring && this.energyController) {
try {
status.currentPower = await this.energyController.getCurrentPower();
}
catch (error) {
// Ignore energy reading errors
}
}
// Get child devices if supported
if (this.supportsChildDevices()) {
try {
status.childDevices = await this.getChildDevices();
}
catch (error) {
// Ignore child device errors
}
}
return status;
}
}
exports.PlugDevice = PlugDevice;
//# sourceMappingURL=plug-device.js.map