Merge branch 'main' of github.com:koush/scrypted

This commit is contained in:
Koushik Dutta
2022-08-25 10:59:08 -07:00
5 changed files with 48 additions and 45 deletions

View File

@@ -8,7 +8,7 @@ The plugin will discover all the cameras within Tuya Cloud IoT project and repor
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)
Follow this [guide](https://developer.tuya.com/en/docs/iot/Configuration_Guide_custom?id=Kamcfx6g5uyot&_source=bdc927ff355af92156074d47e00d6191)
- [If you're using custom development](https://developer.tuya.com/en/docs/iot/Configuration_Guide_custom?id=Kamcfx6g5uyot&_source=bdc927ff355af92156074d47e00d6191)
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.

View File

@@ -43,5 +43,5 @@
"@types/uuid": "^8.3.4",
"@types/ws": "^8.5.3"
},
"version": "0.0.3"
"version": "0.0.4"
}

View File

@@ -24,10 +24,16 @@ export class TuyaCameraLight extends ScryptedDeviceBase implements OnOff {
private async setLightSwitch(on: boolean) {
const camera = this.camera.findCamera();
if (!camera) {
this.log.w(`Camera was not found for ${this.name}`);
return;
}
const lightSwitchStatus = TuyaDevice.getLightSwitchStatus(camera);
if (camera.online && lightSwitchStatus) {
await this.camera.controller.api.updateDevice(camera, [
await this.camera.controller.cloud?.updateDevice(camera, [
{
code: lightSwitchStatus.code,
value: on
@@ -56,11 +62,6 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
config: TuyaDeviceConfig
) {
super(nativeId);
if (this.interfaces.includes(ScryptedInterface.BinarySensor)) {
this.binaryState = false;
}
this.updateState(config);
}
@@ -86,10 +87,15 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
private async setStatusIndicator(on: boolean) {
const camera = this.findCamera();
if (!camera) {
this.log.w(`Camera was not found for ${this.name}`);
return;
}
const statusIndicator = TuyaDevice.getStatusIndicator(camera);
if (statusIndicator) {
await this.controller.api.updateDevice(camera, [
await this.controller.cloud?.updateDevice(camera, [
{
code: statusIndicator.code,
value: on
@@ -120,7 +126,7 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
throw new Error(`Failed to stream ${this.name}: Camera is offline.`);
}
const rtsps = await this.controller.api.getRTSPS(camera);
const rtsps = await this.controller.cloud?.getRTSPS(camera);
if (!rtsps) {
this.logger.w("There was an error retreiving camera's rtsps for streamimg.");
@@ -183,7 +189,7 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
}
findCamera() {
return this.controller.api.cameras.find(device => device.id === this.nativeId);
return this.controller.cloud?.cameras?.find(device => device.id === this.nativeId);
}
updateState(camera?: TuyaDeviceConfig) {

View File

@@ -11,8 +11,8 @@ import { TuyaPulsar, TuyaPulsarMessage } from './tuya/pulsar';
const { deviceManager } = sdk;
export class TuyaController extends ScryptedDeviceBase implements DeviceProvider, DeviceDiscovery, Settings {
api: TuyaCloud;
pulsar: TuyaPulsar;
cloud?: TuyaCloud;
pulsar?: TuyaPulsar;
cameras: Map<string, TuyaCamera> = new Map();
settingsStorage = new StorageSettings(this, {
@@ -61,20 +61,18 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
throw new Error('User Id, access Id, access key, and country info are missing.');
}
this.api = new TuyaCloud(
this.cloud = new TuyaCloud(
userId,
accessId,
accessKey,
country
);
const success = await this.api.login();
const success = await this.cloud.login();
if (!success) {
this.log.e("Failed to log in with credentials.");
this.api = undefined;
this.pulsar?.stop();
this.pulsar = undefined;
this.cloud = undefined;
throw new Error("Failed to log in with credentials, please check if everything is correct.");
}
@@ -89,7 +87,7 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
});
this.pulsar.message((ws, message) => {
this.pulsar.ackMessage(message.messageId);
this.pulsar?.ackMessage(message.messageId);
this.log.i(`TuyaPulse: message received: ${message}`);
const tuyaDevice = handleMessage(message);
if (!tuyaDevice)
@@ -115,21 +113,15 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
const data = message.payload.data;
const { devId, productKey } = data;
const device = this.api.cameras?.find(c => c.id === devId);
let returnDevice = false;
const device = this.cloud?.cameras?.find(c => c.id === devId);
if (data.bizCode) {
if (!device && data.bizCode !== 'add') {
return;
}
if (data.bizCode === 'online' || data.bizCode === 'offline') {
if (device && (data.bizCode === 'online' || data.bizCode === 'offline')) {
// Device status changed
const isOnline = data.bizCode === 'online';
device.online = isOnline;
returnDevice = true;
} else if (data.bizCode === 'delete') {
return this.cameras.get(devId);
} else if (device && data.bizCode === 'delete') {
// Device needs to be deleted
// - devId
// - uid
@@ -152,10 +144,6 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
}
});
returnDevice = true;
}
if (returnDevice) {
return this.cameras.get(devId);
}
}
@@ -175,7 +163,13 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
this.log.clearAlerts();
this.log.a("Successsfully logged in with credentials! Now discovering devices.");
if (!await this.api.fetchDevices()) {
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.");
}
@@ -184,7 +178,7 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
// Camera Setup
for (const camera of this.api.cameras || []) {
for (const camera of cloud.cameras || []) {
const nativeId = camera.id;
const device: Device = {
@@ -200,7 +194,7 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
? ScryptedDeviceType.Doorbell
: ScryptedDeviceType.Camera,
interfaces: [
ScryptedInterface.VideoCamera,
ScryptedInterface.VideoCamera
]
};
@@ -230,15 +224,9 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
devices
});
// Update devices with new state
for (const device of devices) {
this.getDevice(device.nativeId).then(device => device?.updateState());
}
// Handle any camera device that have a light switch
for (const camera of this.api.cameras) {
for (const camera of cloud.cameras || []) {
if (!TuyaDevice.hasLightSwitch(camera)) {
continue;
}
@@ -263,14 +251,20 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
devices: [device]
});
}
// Update devices with new state
for (const device of devices) {
await this.getDevice(device.nativeId).then(device => device?.updateState());
}
}
async getDevice(nativeId: string): Promise<TuyaCamera> {
async getDevice(nativeId: string) {
if (this.cameras.has(nativeId)) {
return this.cameras.get(nativeId);
}
const camera = this.api.cameras.find(camera => camera.id === nativeId);
const camera = this.cloud?.cameras?.find(camera => camera.id === nativeId);
if (camera) {
const ret = new TuyaCamera(this, nativeId, camera);
this.cameras.set(nativeId, ret);

View File

@@ -145,6 +145,9 @@ export class TuyaCloud {
body: { [k: string]: any } = {}
): Promise<TuyaResponse<T>> {
await this.refreshAccessTokenIfNeeded();
if (!this.session) {
throw new Error(`Token session not available for TuyaCloud.`);
}
const timestamp = Date.now().toString();
const headers = { client_id: this.clientId };