From 932a3efd1ce933bdb26701ff8ca6ec914c9361ec Mon Sep 17 00:00:00 2001 From: ZaydSkaff Date: Tue, 24 Jun 2025 12:18:46 +0300 Subject: [PATCH] Sp 1780 be configure the curtain module device (#424) * task: add Cur new device configuration --- .../common/src/constants/product-type.enum.ts | 1 + src/device/commands/cur2-commands.ts | 28 ++ src/schedule/constants/device-function-map.ts | 32 ++ src/schedule/services/schedule.service.ts | 356 ++++++++---------- 4 files changed, 224 insertions(+), 193 deletions(-) create mode 100644 src/device/commands/cur2-commands.ts create mode 100644 src/schedule/constants/device-function-map.ts diff --git a/libs/common/src/constants/product-type.enum.ts b/libs/common/src/constants/product-type.enum.ts index 1bb1394..182f43c 100644 --- a/libs/common/src/constants/product-type.enum.ts +++ b/libs/common/src/constants/product-type.enum.ts @@ -15,6 +15,7 @@ export enum ProductType { WL = 'WL', GD = 'GD', CUR = 'CUR', + CUR_2 = 'CUR_2', PC = 'PC', FOUR_S = '4S', SIX_S = '6S', diff --git a/src/device/commands/cur2-commands.ts b/src/device/commands/cur2-commands.ts new file mode 100644 index 0000000..22301f8 --- /dev/null +++ b/src/device/commands/cur2-commands.ts @@ -0,0 +1,28 @@ +interface BaseCommand { + code: string; + value: any; +} +export interface ControlCur2Command extends BaseCommand { + code: 'control'; + value: 'open' | 'close' | 'stop'; +} +export interface ControlCur2PercentCommand extends BaseCommand { + code: 'percent_control'; + value: 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100; +} +export interface ControlCur2AccurateCalibrationCommand extends BaseCommand { + code: 'accurate_calibration'; + value: 'start' | 'end'; // Assuming this is a numeric value for calibration +} +export interface ControlCur2TDirectionConCommand extends BaseCommand { + code: 'control_t_direction_con'; + value: 'forward' | 'back'; +} +export interface ControlCur2QuickCalibrationCommand extends BaseCommand { + code: 'tr_timecon'; + value: number; // between 10 and 120 +} +export interface ControlCur2MotorModeCommand extends BaseCommand { + code: 'elec_machinery_mode'; + value: 'strong_power' | 'dry_contact'; +} diff --git a/src/schedule/constants/device-function-map.ts b/src/schedule/constants/device-function-map.ts new file mode 100644 index 0000000..7258f45 --- /dev/null +++ b/src/schedule/constants/device-function-map.ts @@ -0,0 +1,32 @@ +import { + ControlCur2AccurateCalibrationCommand, + ControlCur2Command, + ControlCur2PercentCommand, + ControlCur2QuickCalibrationCommand, + ControlCur2TDirectionConCommand, +} from 'src/device/commands/cur2-commands'; + +export enum ScheduleProductType { + CUR_2 = 'CUR_2', +} +export const DeviceFunctionMap: { + [T in ScheduleProductType]: (body: DeviceFunction[T]) => any; +} = { + [ScheduleProductType.CUR_2]: ({ code, value }) => { + return [ + { + code, + value, + }, + ]; + }, +}; + +type DeviceFunction = { + [ScheduleProductType.CUR_2]: + | ControlCur2Command + | ControlCur2PercentCommand + | ControlCur2AccurateCalibrationCommand + | ControlCur2TDirectionConCommand + | ControlCur2QuickCalibrationCommand; +}; diff --git a/src/schedule/services/schedule.service.ts b/src/schedule/services/schedule.service.ts index ee029cf..e4bd586 100644 --- a/src/schedule/services/schedule.service.ts +++ b/src/schedule/services/schedule.service.ts @@ -1,6 +1,6 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { AddScheduleDto, EnableScheduleDto, @@ -11,14 +11,14 @@ import { getDeviceScheduleInterface, } from '../interfaces/get.schedule.interface'; -import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductType } from '@app/common/constants/product-type.enum'; +import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertTimestampToDubaiTime } from '@app/common/helper/convertTimestampToDubaiTime'; import { getEnabledDays, getScheduleStatus, } from '@app/common/helper/getScheduleStatus'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; @Injectable() export class ScheduleService { @@ -49,22 +49,11 @@ export class ScheduleService { } // Corrected condition for supported device types - if ( - deviceDetails.productDevice.prodType !== ProductType.THREE_G && - deviceDetails.productDevice.prodType !== ProductType.ONE_G && - deviceDetails.productDevice.prodType !== ProductType.TWO_G && - deviceDetails.productDevice.prodType !== ProductType.WH && - deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && - deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && - deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && - deviceDetails.productDevice.prodType !== ProductType.GD - ) { - throw new HttpException( - 'This device is not supported for schedule', - HttpStatus.BAD_REQUEST, - ); - } - return await this.enableScheduleDeviceInTuya( + this.ensureProductTypeSupportedForSchedule( + ProductType[deviceDetails.productDevice.prodType], + ); + + return this.enableScheduleDeviceInTuya( deviceDetails.deviceTuyaUuid, enableScheduleDto, ); @@ -75,29 +64,6 @@ export class ScheduleService { ); } } - async enableScheduleDeviceInTuya( - deviceId: string, - enableScheduleDto: EnableScheduleDto, - ): Promise { - try { - const path = `/v2.0/cloud/timer/device/${deviceId}/state`; - const response = await this.tuya.request({ - method: 'PUT', - path, - body: { - enable: enableScheduleDto.enable, - timer_id: enableScheduleDto.scheduleId, - }, - }); - - return response as addScheduleDeviceInterface; - } catch (error) { - throw new HttpException( - 'Error while updating schedule from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async deleteDeviceSchedule(deviceUuid: string, scheduleId: string) { try { const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); @@ -107,21 +73,10 @@ export class ScheduleService { } // Corrected condition for supported device types - if ( - deviceDetails.productDevice.prodType !== ProductType.THREE_G && - deviceDetails.productDevice.prodType !== ProductType.ONE_G && - deviceDetails.productDevice.prodType !== ProductType.TWO_G && - deviceDetails.productDevice.prodType !== ProductType.WH && - deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && - deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && - deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && - deviceDetails.productDevice.prodType !== ProductType.GD - ) { - throw new HttpException( - 'This device is not supported for schedule', - HttpStatus.BAD_REQUEST, - ); - } + this.ensureProductTypeSupportedForSchedule( + ProductType[deviceDetails.productDevice.prodType], + ); + return await this.deleteScheduleDeviceInTuya( deviceDetails.deviceTuyaUuid, scheduleId, @@ -133,25 +88,6 @@ export class ScheduleService { ); } } - async deleteScheduleDeviceInTuya( - deviceId: string, - scheduleId: string, - ): Promise { - try { - const path = `/v2.0/cloud/timer/device/${deviceId}/batch?timer_ids=${scheduleId}`; - const response = await this.tuya.request({ - method: 'DELETE', - path, - }); - - return response as addScheduleDeviceInterface; - } catch (error) { - throw new HttpException( - 'Error while deleting schedule from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async addDeviceSchedule(deviceUuid: string, addScheduleDto: AddScheduleDto) { try { const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); @@ -160,22 +96,10 @@ export class ScheduleService { throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } - // Corrected condition for supported device types - if ( - deviceDetails.productDevice.prodType !== ProductType.THREE_G && - deviceDetails.productDevice.prodType !== ProductType.ONE_G && - deviceDetails.productDevice.prodType !== ProductType.TWO_G && - deviceDetails.productDevice.prodType !== ProductType.WH && - deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && - deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && - deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && - deviceDetails.productDevice.prodType !== ProductType.GD - ) { - throw new HttpException( - 'This device is not supported for schedule', - HttpStatus.BAD_REQUEST, - ); - } + this.ensureProductTypeSupportedForSchedule( + ProductType[deviceDetails.productDevice.prodType], + ); + await this.addScheduleDeviceInTuya( deviceDetails.deviceTuyaUuid, addScheduleDto, @@ -187,40 +111,6 @@ export class ScheduleService { ); } } - async addScheduleDeviceInTuya( - deviceId: string, - addScheduleDto: AddScheduleDto, - ): Promise { - try { - const convertedTime = convertTimestampToDubaiTime(addScheduleDto.time); - const loops = getScheduleStatus(addScheduleDto.days); - - const path = `/v2.0/cloud/timer/device/${deviceId}`; - const response = await this.tuya.request({ - method: 'POST', - path, - body: { - time: convertedTime.time, - timezone_id: 'Asia/Dubai', - loops: `${loops}`, - functions: [ - { - code: addScheduleDto.function.code, - value: addScheduleDto.function.value, - }, - ], - category: `category_${addScheduleDto.category}`, - }, - }); - - return response as addScheduleDeviceInterface; - } catch (error) { - throw new HttpException( - 'Error adding schedule from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async getDeviceScheduleByCategory(deviceUuid: string, category: string) { try { const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); @@ -229,21 +119,10 @@ export class ScheduleService { throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } // Corrected condition for supported device types - if ( - deviceDetails.productDevice.prodType !== ProductType.THREE_G && - deviceDetails.productDevice.prodType !== ProductType.ONE_G && - deviceDetails.productDevice.prodType !== ProductType.TWO_G && - deviceDetails.productDevice.prodType !== ProductType.WH && - deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && - deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && - deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && - deviceDetails.productDevice.prodType !== ProductType.GD - ) { - throw new HttpException( - 'This device is not supported for schedule', - HttpStatus.BAD_REQUEST, - ); - } + this.ensureProductTypeSupportedForSchedule( + ProductType[deviceDetails.productDevice.prodType], + ); + const schedules = await this.getScheduleDeviceInTuya( deviceDetails.deviceTuyaUuid, category, @@ -270,7 +149,82 @@ export class ScheduleService { ); } } - async getScheduleDeviceInTuya( + async updateDeviceSchedule( + deviceUuid: string, + updateScheduleDto: UpdateScheduleDto, + ) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + // Corrected condition for supported device types + this.ensureProductTypeSupportedForSchedule( + ProductType[deviceDetails.productDevice.prodType], + ); + + await this.updateScheduleDeviceInTuya( + deviceDetails.deviceTuyaUuid, + updateScheduleDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Error While Updating Schedule', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + return this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + isActive: true, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } + + private async addScheduleDeviceInTuya( + deviceId: string, + addScheduleDto: AddScheduleDto, + ): Promise { + try { + const convertedTime = convertTimestampToDubaiTime(addScheduleDto.time); + const loops = getScheduleStatus(addScheduleDto.days); + + const path = `/v2.0/cloud/timer/device/${deviceId}`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + time: convertedTime.time, + timezone_id: 'Asia/Dubai', + loops: `${loops}`, + functions: [ + { + ...addScheduleDto.function, + }, + ], + category: `category_${addScheduleDto.category}`, + }, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error adding schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async getScheduleDeviceInTuya( deviceId: string, category: string, ): Promise { @@ -291,57 +245,8 @@ export class ScheduleService { ); } } - async getDeviceByDeviceUuid( - deviceUuid: string, - withProductDevice: boolean = true, - ) { - return await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - isActive: true, - }, - ...(withProductDevice && { relations: ['productDevice'] }), - }); - } - async updateDeviceSchedule( - deviceUuid: string, - updateScheduleDto: UpdateScheduleDto, - ) { - try { - const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); - if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { - throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); - } - - // Corrected condition for supported device types - if ( - deviceDetails.productDevice.prodType !== ProductType.THREE_G && - deviceDetails.productDevice.prodType !== ProductType.ONE_G && - deviceDetails.productDevice.prodType !== ProductType.TWO_G && - deviceDetails.productDevice.prodType !== ProductType.WH && - deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && - deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && - deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && - deviceDetails.productDevice.prodType !== ProductType.GD - ) { - throw new HttpException( - 'This device is not supported for schedule', - HttpStatus.BAD_REQUEST, - ); - } - await this.updateScheduleDeviceInTuya( - deviceDetails.deviceTuyaUuid, - updateScheduleDto, - ); - } catch (error) { - throw new HttpException( - error.message || 'Error While Updating Schedule', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async updateScheduleDeviceInTuya( + private async updateScheduleDeviceInTuya( deviceId: string, updateScheduleDto: UpdateScheduleDto, ): Promise { @@ -376,4 +281,69 @@ export class ScheduleService { ); } } + + private async enableScheduleDeviceInTuya( + deviceId: string, + enableScheduleDto: EnableScheduleDto, + ): Promise { + try { + const path = `/v2.0/cloud/timer/device/${deviceId}/state`; + const response = await this.tuya.request({ + method: 'PUT', + path, + body: { + enable: enableScheduleDto.enable, + timer_id: enableScheduleDto.scheduleId, + }, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error while updating schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async deleteScheduleDeviceInTuya( + deviceId: string, + scheduleId: string, + ): Promise { + try { + const path = `/v2.0/cloud/timer/device/${deviceId}/batch?timer_ids=${scheduleId}`; + const response = await this.tuya.request({ + method: 'DELETE', + path, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error while deleting schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private ensureProductTypeSupportedForSchedule(deviceType: ProductType): void { + if ( + ![ + ProductType.THREE_G, + ProductType.ONE_G, + ProductType.TWO_G, + ProductType.WH, + ProductType.ONE_1TG, + ProductType.TWO_2TG, + ProductType.THREE_3TG, + ProductType.GD, + ProductType.CUR_2, + ].includes(deviceType) + ) { + throw new HttpException( + 'This device is not supported for schedule', + HttpStatus.BAD_REQUEST, + ); + } + } }