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
189 lines • 6.67 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtocolSelector = exports.ProtocolType = void 0;
var ProtocolType;
(function (ProtocolType) {
ProtocolType["KLAP"] = "klap";
ProtocolType["PASSTHROUGH"] = "passthrough";
})(ProtocolType || (exports.ProtocolType = ProtocolType = {}));
/**
* Manages protocol selection and fallback logic
* Single responsibility: Protocol detection and selection
*/
class ProtocolSelector {
constructor(ip, credentials, options = {}) {
var _a, _b, _c, _d;
this.ip = ip;
this.credentials = credentials;
this.protocols = new Map();
this.activeProtocol = null;
this.options = {
preferredProtocol: (_a = options.preferredProtocol) !== null && _a !== void 0 ? _a : ProtocolType.KLAP,
enableFallback: (_b = options.enableFallback) !== null && _b !== void 0 ? _b : true,
connectionTimeout: (_c = options.connectionTimeout) !== null && _c !== void 0 ? _c : 10000,
minRequestInterval: (_d = options.minRequestInterval) !== null && _d !== void 0 ? _d : 100
};
// Initialize protocol info
this.protocols.set(ProtocolType.KLAP, {
type: ProtocolType.KLAP,
isSupported: true,
priority: 1,
errorCount: 0
});
this.protocols.set(ProtocolType.PASSTHROUGH, {
type: ProtocolType.PASSTHROUGH,
isSupported: true,
priority: 2,
errorCount: 0
});
}
/**
* Select the best available protocol
*/
async selectProtocol() {
// If we already have an active protocol, use it
if (this.activeProtocol && this.isProtocolHealthy(this.activeProtocol)) {
return this.activeProtocol;
}
// Try preferred protocol first
if (await this.testProtocol(this.options.preferredProtocol)) {
this.activeProtocol = this.options.preferredProtocol;
return this.activeProtocol;
}
// If preferred protocol failed and fallback is enabled, try alternatives
if (this.options.enableFallback) {
const alternativeProtocols = this.getAlternativeProtocols();
for (const protocol of alternativeProtocols) {
if (await this.testProtocol(protocol)) {
this.activeProtocol = protocol;
return this.activeProtocol;
}
}
}
throw new Error('No suitable protocol found for device communication');
}
/**
* Get currently active protocol
*/
getActiveProtocol() {
return this.activeProtocol;
}
/**
* Test if a specific protocol works with the device
*/
async testProtocol(protocol) {
try {
const protocolInfo = this.protocols.get(protocol);
if (!protocolInfo || !protocolInfo.isSupported) {
return false;
}
// Perform actual protocol test (to be implemented by concrete protocols)
const isWorking = await this.performProtocolTest(protocol);
if (isWorking) {
protocolInfo.lastUsed = Date.now();
protocolInfo.errorCount = 0;
this.protocols.set(protocol, protocolInfo);
return true;
}
else {
this.recordProtocolError(protocol);
return false;
}
}
catch (error) {
this.recordProtocolError(protocol);
return false;
}
}
/**
* Record a protocol error and update its health status
*/
recordProtocolError(protocol) {
const protocolInfo = this.protocols.get(protocol);
if (protocolInfo) {
protocolInfo.errorCount++;
// Disable protocol if too many errors
if (protocolInfo.errorCount >= 5) {
protocolInfo.isSupported = false;
}
this.protocols.set(protocol, protocolInfo);
}
// Reset active protocol if it's the one with errors
if (this.activeProtocol === protocol) {
this.activeProtocol = null;
}
}
/**
* Reset protocol error counts (useful for recovery)
*/
resetProtocolErrors() {
for (const [type, info] of this.protocols) {
info.errorCount = 0;
info.isSupported = true;
this.protocols.set(type, info);
}
}
/**
* Get protocol information for debugging
*/
getProtocolInfo() {
return new Map(this.protocols);
}
/**
* Check if a protocol is healthy (low error count, recently used)
*/
isProtocolHealthy(protocol) {
const protocolInfo = this.protocols.get(protocol);
if (!protocolInfo || !protocolInfo.isSupported) {
return false;
}
// Consider unhealthy if too many recent errors
if (protocolInfo.errorCount >= 3) {
return false;
}
return true;
}
/**
* Get alternative protocols ordered by priority
*/
getAlternativeProtocols() {
const alternatives = Array.from(this.protocols.values())
.filter(info => info.type !== this.options.preferredProtocol && info.isSupported)
.sort((a, b) => a.priority - b.priority)
.map(info => info.type);
return alternatives;
}
/**
* Perform actual protocol test (to be implemented by subclasses or injected)
*/
async performProtocolTest(_protocol) {
// This should be implemented by concrete protocol implementations
// For now, return true as a placeholder
return true;
}
/**
* Force switch to a specific protocol
*/
forceProtocol(protocol) {
const protocolInfo = this.protocols.get(protocol);
if (!protocolInfo) {
throw new Error(`Unknown protocol: ${protocol}`);
}
this.activeProtocol = protocol;
protocolInfo.lastUsed = Date.now();
protocolInfo.errorCount = 0;
this.protocols.set(protocol, protocolInfo);
}
/**
* Get minimum request interval for the active protocol
*/
getMinRequestInterval() {
// KLAP might need longer intervals than passthrough
if (this.activeProtocol === ProtocolType.KLAP) {
return Math.max(this.options.minRequestInterval, 200);
}
return this.options.minRequestInterval;
}
}
exports.ProtocolSelector = ProtocolSelector;
//# sourceMappingURL=protocol-selector.js.map