mirror of
https://github.com/koush/scrypted.git
synced 2026-02-11 09:34:27 +00:00
[Tuya Plugin] Fixed issue with devices not loading (#355)
* Fixed issue with devices not loading - Updated readme with requirements. - fixed race conditions - reorganized main.ts - improved cloud login verification - fixed issue where cameras with no light switch was forced to add a light, whic caused to crash plugin. * updated version to v0.0.5
This commit is contained in:
@@ -4,7 +4,7 @@ This is a Tuya controller that integrates Tuya devices, specifically cameras, in
|
||||
|
||||
The plugin will discover all the cameras within Tuya Cloud IoT project and report them to Scrypted, including motion events, for the ones that are supported.
|
||||
|
||||
## Retrieving Keys
|
||||
## Requirements
|
||||
In order to retrieve `Access Id` and `Access Key`, you must follow the guide below:
|
||||
- [Using Smart Home PaaS (TuyaSmart, SmartLife, ect...)](https://developer.tuya.com/en/docs/iot/Platform_Configuration_smarthome?id=Kamcgamwoevrx&_source=6435717a3be1bc67fdd1f6699a1a59ac)
|
||||
|
||||
@@ -12,6 +12,12 @@ In order to retrieve `Access Id` and `Access Key`, you must follow the guide bel
|
||||
|
||||
Once you have retreived both the `Access Id` and `Access Key` from the project, you can get the `User Id` by going to Tuya Cloud IoT -> Select the Project -> Devices -> Link Tuya App Account -> and then get the UID.
|
||||
|
||||
You also need to enable Messages Service in your project in order to receive real time notifications to Scrypted. (motion events, online/offline, light switch ect...) The way this is achieved is by following this [guide](https://developer.tuya.com/en/docs/iot/subscribe-mq?id=Kavqcrvckbh9h).
|
||||
|
||||
- You do not need to set an alert notification of your phone.
|
||||
- This might not be necessary in the future if I believe MQTT is the way to go, but in the mean time, TuyaPulse is required for this project.
|
||||
|
||||
|
||||
## TODOs
|
||||
- Fix 2-way talk for supported platforms (Can only work with WebRTC since we only get one stream with RTSPS)
|
||||
- Add support for camera doorbells (Just need to implement doorbell notification)
|
||||
4
plugins/tuya/package-lock.json
generated
4
plugins/tuya/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/tuya",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/tuya",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.5",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
|
||||
@@ -43,5 +43,5 @@
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.5.3"
|
||||
},
|
||||
"version": "0.0.4"
|
||||
"version": "0.0.5"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export class TuyaCameraLight extends ScryptedDeviceBase implements OnOff {
|
||||
nativeId: string
|
||||
) {
|
||||
super(nativeId);
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
async turnOff(): Promise<void> {
|
||||
@@ -52,27 +51,32 @@ export class TuyaCameraLight extends ScryptedDeviceBase implements OnOff {
|
||||
}
|
||||
|
||||
export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, VideoCamera, BinarySensor, MotionSensor, OnOff {
|
||||
cameraLight?: TuyaCameraLight
|
||||
private cameraLightSwitch?: TuyaCameraLight
|
||||
private previousMotion?: any;
|
||||
private motionTimeout?: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
public controller: TuyaController,
|
||||
nativeId: string,
|
||||
config: TuyaDeviceConfig
|
||||
nativeId: string
|
||||
) {
|
||||
super(nativeId);
|
||||
this.updateState(config);
|
||||
}
|
||||
|
||||
// Camera Light Provider
|
||||
// Camera Light Device Provider.
|
||||
|
||||
getDevice(nativeId: string) {
|
||||
if (!this.cameraLight) {
|
||||
this.cameraLight = new TuyaCameraLight(this, nativeId);
|
||||
// Find created devices
|
||||
if (this.cameraLightSwitch?.id === nativeId) {
|
||||
return this.cameraLightSwitch;
|
||||
}
|
||||
|
||||
return this.cameraLight;
|
||||
// Create devices if not found.
|
||||
if (nativeId === this.nativeLightSwitchId) {
|
||||
this.cameraLightSwitch = new TuyaCameraLight(this, nativeId);
|
||||
return this.cameraLightSwitch;
|
||||
}
|
||||
|
||||
throw new Error("This Camera Device Provider has not been implemented of type: " + nativeId.split('-')[1]);
|
||||
}
|
||||
|
||||
// OnOff Status Indicator
|
||||
@@ -153,8 +157,7 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
|
||||
codec: 'pcm_ulaw'
|
||||
},
|
||||
source: 'cloud',
|
||||
tool: 'scrypted',
|
||||
userConfigurable: false
|
||||
tool: 'scrypted'
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -199,24 +202,32 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
|
||||
return;
|
||||
}
|
||||
|
||||
this.on = TuyaDevice.getStatusIndicator(camera)?.value;
|
||||
this.online = camera.online;
|
||||
|
||||
const hasMotionSwitchStatus = TuyaDevice.getMotionSwitch(camera) !== undefined;
|
||||
if (hasMotionSwitchStatus) {
|
||||
const movementDetectedStatus = TuyaDevice.getMotionDetectionStatus(camera);
|
||||
if (movementDetectedStatus) {
|
||||
if (TuyaDevice.hasStatusIndicator(camera)) {
|
||||
this.on = TuyaDevice.getStatusIndicator(camera)?.value;
|
||||
}
|
||||
|
||||
if (TuyaDevice.hasMotionDetection(camera)) {
|
||||
const motionDetectedStatus = TuyaDevice.getMotionDetectionStatus(camera);
|
||||
if (motionDetectedStatus) {
|
||||
if (!this.previousMotion) {
|
||||
this.previousMotion = movementDetectedStatus.value;
|
||||
} else if (this.previousMotion !== movementDetectedStatus.value) {
|
||||
this.previousMotion = movementDetectedStatus.value;
|
||||
this.previousMotion = motionDetectedStatus.value;
|
||||
} else if (this.previousMotion !== motionDetectedStatus.value) {
|
||||
this.previousMotion = motionDetectedStatus.value;
|
||||
this.triggerMotion();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.getDevice(this.nativeLightId).updateState(camera);
|
||||
|
||||
// By the time this is called, scrypted would have already reported the device
|
||||
// Only set light switch on cameras that have a status light indicator.
|
||||
if (TuyaDevice.hasLightSwitch(camera)) {
|
||||
this.getDevice(this.nativeLightSwitchId)?.updateState(camera);
|
||||
}
|
||||
}
|
||||
|
||||
private get nativeLightId(): string {
|
||||
private get nativeLightSwitchId(): string {
|
||||
return `${this.nativeId}-light`;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,69 +11,103 @@ import { TuyaPulsar, TuyaPulsarMessage } from './tuya/pulsar';
|
||||
const { deviceManager } = sdk;
|
||||
|
||||
export class TuyaController extends ScryptedDeviceBase implements DeviceProvider, DeviceDiscovery, Settings {
|
||||
cloud?: TuyaCloud;
|
||||
pulsar?: TuyaPulsar;
|
||||
cloud: TuyaCloud;
|
||||
pulsar: TuyaPulsar;
|
||||
cameras: Map<string, TuyaCamera> = new Map();
|
||||
|
||||
settingsStorage = new StorageSettings(this, {
|
||||
userId: {
|
||||
title: 'User Id',
|
||||
description: 'Required: You can find this information in Tuya IoT -> Cloud -> Devices -> Linked Devices.',
|
||||
onPut: async () => this.discoverDevices(0),
|
||||
},
|
||||
accessId: {
|
||||
title: 'Access Id',
|
||||
description: 'Requirerd: This is located on the main project.',
|
||||
onPut: async () => this.discoverDevices(0),
|
||||
},
|
||||
accessKey: {
|
||||
title: 'Access Key/Secret',
|
||||
description: 'Requirerd: This is located on the main project.',
|
||||
type: 'password',
|
||||
onPut: async () => this.discoverDevices(0),
|
||||
},
|
||||
country: {
|
||||
title: 'Country',
|
||||
description: 'Required: This is the country where you registered your devices.',
|
||||
type: 'string',
|
||||
choices: TUYA_COUNTRIES.map(value => value.country),
|
||||
onPut: async () => this.discoverDevices(0)
|
||||
}
|
||||
});
|
||||
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
this.discoverDevices(0);
|
||||
}
|
||||
|
||||
async tryLogin() {
|
||||
const userId = this.settingsStorage.getItem('userId');
|
||||
const accessId = this.settingsStorage.getItem('accessId');
|
||||
const accessKey = this.settingsStorage.getItem('accessKey');
|
||||
const country = TUYA_COUNTRIES.find(value => value.country == this.settingsStorage.getItem('country'));
|
||||
private handlePulsarMessage(message: TuyaPulsarMessage) {
|
||||
const data = message.payload.data;
|
||||
const { devId, productKey } = data;
|
||||
|
||||
if (!userId ||
|
||||
!accessId ||
|
||||
!accessKey ||
|
||||
!country
|
||||
) {
|
||||
this.log.a('Enter your Tuya User Id, access Id, access key, and country to complete the setup.');
|
||||
throw new Error('User Id, access Id, access key, and country info are missing.');
|
||||
const device = this.cloud?.cameras?.find(c => c.id === devId);
|
||||
|
||||
if (data.bizCode) {
|
||||
if (device && (data.bizCode === 'online' || data.bizCode === 'offline')) {
|
||||
// Device status changed
|
||||
const isOnline = data.bizCode === 'online';
|
||||
device.online = isOnline;
|
||||
return this.cameras.get(devId);
|
||||
} else if (device && data.bizCode === 'delete') {
|
||||
// Device needs to be deleted
|
||||
// - devId
|
||||
// - uid
|
||||
|
||||
const { uid } = data.bizData;
|
||||
// TODO: delete device
|
||||
} else if (data.bizCode === 'add') {
|
||||
// TODO: There is a new device added, refetch
|
||||
}
|
||||
} else {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newStatus = data.status || [];
|
||||
|
||||
newStatus.forEach(item => {
|
||||
const index = device.status.findIndex(status => status.code == item.code);
|
||||
if (index !== -1) {
|
||||
device.status[index].value = item.value
|
||||
}
|
||||
});
|
||||
|
||||
return this.cameras.get(devId);
|
||||
}
|
||||
}
|
||||
|
||||
async discoverDevices(duration: number) {
|
||||
const userId = this.getSetting('userId');
|
||||
const accessId = this.getSetting('accessId');
|
||||
const accessKey = this.getSetting('accessKey');
|
||||
const country = TUYA_COUNTRIES.find(value => value.country == this.getSetting('country'));
|
||||
|
||||
this.log.clearAlerts();
|
||||
|
||||
let missingItems: string[] = [];
|
||||
|
||||
if (!userId)
|
||||
missingItems.push('User Id');
|
||||
|
||||
if (!accessId)
|
||||
missingItems.push('Access Id');
|
||||
|
||||
if (!accessKey)
|
||||
missingItems.push('Access Key');
|
||||
|
||||
if (!country)
|
||||
missingItems.push('Country');
|
||||
|
||||
if (missingItems.length > 0) {
|
||||
this.log.a(`You must provide your ${missingItems.join(', ')}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cloud = new TuyaCloud(
|
||||
userId,
|
||||
accessId,
|
||||
accessKey,
|
||||
country
|
||||
);
|
||||
if (!this.cloud) {
|
||||
this.cloud = new TuyaCloud(
|
||||
userId,
|
||||
accessId,
|
||||
accessKey,
|
||||
country
|
||||
);
|
||||
}
|
||||
|
||||
const success = await this.cloud.login();
|
||||
// If it cannot fetch devices, then that means it's permission denied.
|
||||
// For some reason, when generating a token does not validate authorization.
|
||||
if (!await this.cloud.fetchDevices()) {
|
||||
this.log.a("Failed to log in with credentials. Please try again.");
|
||||
this.cloud = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
this.log.e("Failed to log in with credentials.");
|
||||
this.cloud = undefined;
|
||||
throw new Error("Failed to log in with credentials, please check if everything is correct.");
|
||||
this.log.a("Successsfully logged in with credentials! Now discovering devices.");
|
||||
|
||||
if (this.pulsar) {
|
||||
this.pulsar.stop();
|
||||
}
|
||||
|
||||
this.pulsar = new TuyaPulsar({
|
||||
@@ -83,13 +117,13 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
|
||||
});
|
||||
|
||||
this.pulsar.open(() => {
|
||||
this.log.i(`TulsaPulse: opening connection.`)
|
||||
this.log.i(`TulsaPulse: opened connection.`)
|
||||
});
|
||||
|
||||
this.pulsar.message((ws, message) => {
|
||||
this.pulsar?.ackMessage(message.messageId);
|
||||
this.log.i(`TuyaPulse: message received: ${message}`);
|
||||
const tuyaDevice = handleMessage(message);
|
||||
const tuyaDevice = this.handlePulsarMessage(message);
|
||||
if (!tuyaDevice)
|
||||
return;
|
||||
tuyaDevice.updateState();
|
||||
@@ -107,78 +141,19 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
|
||||
this.log.e(`TuyaPulse: ${error}`);
|
||||
});
|
||||
|
||||
this.pulsar.maxRetries(() => {
|
||||
this.log.e("There was an error trying to connect to Message Service (TuyaPulse). Connection Max Reconnection Timed Out");
|
||||
});
|
||||
|
||||
this.pulsar.start();
|
||||
|
||||
const handleMessage = (message: TuyaPulsarMessage) => {
|
||||
const data = message.payload.data;
|
||||
const { devId, productKey } = data;
|
||||
|
||||
const device = this.cloud?.cameras?.find(c => c.id === devId);
|
||||
|
||||
if (data.bizCode) {
|
||||
if (device && (data.bizCode === 'online' || data.bizCode === 'offline')) {
|
||||
// Device status changed
|
||||
const isOnline = data.bizCode === 'online';
|
||||
device.online = isOnline;
|
||||
return this.cameras.get(devId);
|
||||
} else if (device && data.bizCode === 'delete') {
|
||||
// Device needs to be deleted
|
||||
// - devId
|
||||
// - uid
|
||||
|
||||
const { uid } = data.bizData;
|
||||
} else if (data.bizCode === 'add') {
|
||||
// TODO: There is a new device added, refetch
|
||||
}
|
||||
} else {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newStatus = data.status || [];
|
||||
|
||||
newStatus.forEach(item => {
|
||||
const index = device.status.findIndex(status => status.code == item.code);
|
||||
if (index !== -1) {
|
||||
device.status[index].value = item.value
|
||||
}
|
||||
});
|
||||
|
||||
return this.cameras.get(devId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSettings(): Promise<Setting[]> {
|
||||
return this.settingsStorage.getSettings();
|
||||
}
|
||||
|
||||
putSetting(key: string, value: SettingValue): Promise<void> {
|
||||
return this.settingsStorage.putSetting(key, value);
|
||||
}
|
||||
|
||||
async discoverDevices(duration: number) {
|
||||
await this.tryLogin();
|
||||
|
||||
this.log.clearAlerts();
|
||||
this.log.a("Successsfully logged in with credentials! Now discovering devices.");
|
||||
|
||||
const cloud = this.cloud;
|
||||
|
||||
if (!cloud) {
|
||||
throw new Error("There was an error: TuyaCloud not initialized");
|
||||
}
|
||||
|
||||
if (!await cloud.fetchDevices()) {
|
||||
this.log.e("Could not fetch devices.");
|
||||
throw new Error("There was an error fetching devices.");
|
||||
}
|
||||
// Find devices
|
||||
|
||||
const devices: Device[] = [];
|
||||
|
||||
// Camera Setup
|
||||
|
||||
for (const camera of cloud.cameras || []) {
|
||||
for (const camera of this.cloud.cameras || []) {
|
||||
const nativeId = camera.id;
|
||||
|
||||
const device: Device = {
|
||||
@@ -226,10 +201,9 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
|
||||
|
||||
// Handle any camera device that have a light switch
|
||||
|
||||
for (const camera of cloud.cameras || []) {
|
||||
if (!TuyaDevice.hasLightSwitch(camera)) {
|
||||
for (const camera of this.cloud.cameras || []) {
|
||||
if (!TuyaDevice.hasLightSwitch(camera))
|
||||
continue;
|
||||
}
|
||||
const nativeId = camera.id + '-light';
|
||||
const device: Device = {
|
||||
providerNativeId: camera.id,
|
||||
@@ -252,7 +226,7 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
|
||||
});
|
||||
}
|
||||
|
||||
// Update devices with new state
|
||||
// Update devices with new state
|
||||
|
||||
for (const device of devices) {
|
||||
await this.getDevice(device.nativeId).then(device => device?.updateState());
|
||||
@@ -266,13 +240,56 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
|
||||
|
||||
const camera = this.cloud?.cameras?.find(camera => camera.id === nativeId);
|
||||
if (camera) {
|
||||
const ret = new TuyaCamera(this, nativeId, camera);
|
||||
const ret = new TuyaCamera(this, nativeId);
|
||||
this.cameras.set(nativeId, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
throw new Error('device not found?');
|
||||
}
|
||||
|
||||
// Settings
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'userId',
|
||||
title: 'User Id',
|
||||
description: 'Required: You can find this information in Tuya IoT -> Cloud -> Devices -> Linked Devices.',
|
||||
value: this.getSetting('userId')
|
||||
},
|
||||
{
|
||||
key: 'accessId',
|
||||
title: 'Access Id',
|
||||
description: 'Requirerd: This is located on the main project.',
|
||||
value: this.getSetting('accessId')
|
||||
},
|
||||
{
|
||||
key: 'accessKey',
|
||||
title: 'Access Key/Secret',
|
||||
description: 'Requirerd: This is located on the main project.',
|
||||
type: 'password',
|
||||
value: this.getSetting('accessKey')
|
||||
},
|
||||
{
|
||||
key: 'country',
|
||||
title: 'Country',
|
||||
description: 'Required: This is the country where you registered your devices.',
|
||||
type: 'string',
|
||||
choices: TUYA_COUNTRIES.map(value => value.country),
|
||||
value: this.getSetting('country')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
getSetting(key: string): string | null {
|
||||
return this.storage.getItem(key);
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: string): Promise<void> {
|
||||
this.storage.setItem(key, value);
|
||||
this.discoverDevices(0);
|
||||
}
|
||||
}
|
||||
|
||||
export default createInstanceableProviderPlugin("Tuya", nativeId => new TuyaController(nativeId));
|
||||
|
||||
@@ -40,7 +40,6 @@ export class TuyaCloud {
|
||||
|
||||
public async login(): Promise<boolean> {
|
||||
await this.refreshAccessTokenIfNeeded();
|
||||
|
||||
return this.isLoggedIn();
|
||||
}
|
||||
|
||||
@@ -144,9 +143,12 @@ export class TuyaCloud {
|
||||
query: { [k: string]: any } = {},
|
||||
body: { [k: string]: any } = {}
|
||||
): Promise<TuyaResponse<T>> {
|
||||
await this.refreshAccessTokenIfNeeded();
|
||||
if (!this.session) {
|
||||
throw new Error(`Token session not available for TuyaCloud.`);
|
||||
if (!await this.login()) {
|
||||
return {
|
||||
result: undefined,
|
||||
success: false,
|
||||
t: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const timestamp = Date.now().toString();
|
||||
@@ -229,15 +231,25 @@ export class TuyaCloud {
|
||||
{ headers }
|
||||
);
|
||||
|
||||
let objData = JSON.parse(data);
|
||||
interface Token {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expire_time: number;
|
||||
uid: string;
|
||||
}
|
||||
|
||||
const newExpiration = new Date(Date.now() + objData.result.expire_time * 1000);
|
||||
let response: TuyaResponse<Token> = JSON.parse(data);
|
||||
|
||||
this.session = {
|
||||
accessToken: objData.result.access_token,
|
||||
refreshToken: objData.result.refresh_token,
|
||||
tokenExpiresAt: newExpiration,
|
||||
uid: objData.result.uid
|
||||
};
|
||||
if (!response.success) {
|
||||
this.session = undefined;
|
||||
} else {
|
||||
const newExpiration = new Date(Date.now() + response.result.expire_time * 1000);
|
||||
this.session = {
|
||||
accessToken: response.result.access_token,
|
||||
refreshToken: response.result.refresh_token,
|
||||
tokenExpiresAt: newExpiration,
|
||||
uid: response.result.uid
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,15 +40,12 @@ export namespace TuyaDevice {
|
||||
// MARK: Motion Detection
|
||||
|
||||
export function hasMotionDetection(camera: TuyaDeviceConfig): boolean {
|
||||
return getMotionSwitch(camera) !== undefined;
|
||||
}
|
||||
|
||||
export function getMotionSwitch(camera: TuyaDeviceConfig) {
|
||||
const motionSwitchCodes = [
|
||||
'motion_switch',
|
||||
'pir_sensitivity'
|
||||
]
|
||||
return getStatus(camera, motionSwitchCodes);
|
||||
|
||||
return getStatus(camera, motionSwitchCodes) !== undefined;
|
||||
}
|
||||
|
||||
export function getMotionDetectionStatus(camera: TuyaDeviceConfig) {
|
||||
|
||||
@@ -51,6 +51,7 @@ export class TuyaPulsar {
|
||||
static reconnect = 'TUYA_RECONNECT';
|
||||
static ping = 'TUYA_PING';
|
||||
static pong = 'TUYA_PONG';
|
||||
static maxRetries = 'TUYA_MAXRETRIES';
|
||||
|
||||
private config: IConfig;
|
||||
private server?: WebSocket;
|
||||
@@ -64,7 +65,7 @@ export class TuyaPulsar {
|
||||
ackTimeoutMillis: 3000,
|
||||
subscriptionType: 'Failover',
|
||||
retryTimeout: 1000,
|
||||
maxRetryTimes: 100,
|
||||
maxRetryTimes: 10,
|
||||
timeout: 30000,
|
||||
logger: console.log,
|
||||
},
|
||||
@@ -114,6 +115,10 @@ export class TuyaPulsar {
|
||||
this.event.on(TuyaPulsar.close, cb);
|
||||
}
|
||||
|
||||
public maxRetries(cb: () => void) {
|
||||
this.event.on(TuyaPulsar.maxRetries, cb);
|
||||
}
|
||||
|
||||
private _reconnect() {
|
||||
if (this.config.maxRetryTimes && this.retryTimes < this.config.maxRetryTimes) {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -121,6 +126,9 @@ export class TuyaPulsar {
|
||||
this.retryTimes++;
|
||||
this._connect(false);
|
||||
}, this.config.retryTimeout);
|
||||
} else {
|
||||
this.clearKeepAlive();
|
||||
this.event.emit(TuyaPulsar.maxRetries);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user