diff --git a/libs/common/src/constants/product-type.enum.ts b/libs/common/src/constants/product-type.enum.ts new file mode 100644 index 0000000..f0d24ad --- /dev/null +++ b/libs/common/src/constants/product-type.enum.ts @@ -0,0 +1,8 @@ +export enum ProductType { + AC = 'AC', + GW = 'GW', + CPS = 'CPS', + DL = 'DL', + WPS = 'WPS', + TH_G = '3G', +} diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index bb8dae8..c9fc59c 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -179,4 +179,36 @@ export class DeviceController { ); } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post(':deviceUuid/firmware/:firmwareVersion') + async updateDeviceFirmware( + @Param('deviceUuid') deviceUuid: string, + @Param('firmwareVersion') firmwareVersion: number, + ) { + try { + return await this.deviceService.updateDeviceFirmware( + deviceUuid, + firmwareVersion, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('getaway/:gatewayUuid/devices') + async getDevicesInGetaway(@Param('gatewayUuid') gatewayUuid: string) { + try { + return await this.deviceService.getDevicesInGetaway(gatewayUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index f7012f7..526c199 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -59,3 +59,8 @@ export interface DeviceInstructionResponse { dataType: string; }[]; } +export interface updateDeviceFirmwareInterface { + success: boolean; + result: boolean; + msg: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 8e2e79c..a7cbaae 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -4,6 +4,7 @@ import { HttpException, HttpStatus, NotFoundException, + BadRequestException, } from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; @@ -14,6 +15,7 @@ import { GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, controlDeviceInterface, + updateDeviceFirmwareInterface, } from '../interfaces/get.device.interface'; import { GetDeviceByRoomUuidDto } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; @@ -21,6 +23,7 @@ import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PermissionType } from '@app/common/constants/permission-type.enum'; import { In } from 'typeorm'; +import { ProductType } from '@app/common/constants/product-type.enum'; @Injectable() export class DeviceService { @@ -38,7 +41,17 @@ export class DeviceService { secretKey, }); } - + private async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } async addDeviceUser(addDeviceDto: AddDeviceDto) { try { const device = await this.getDeviceDetailsByDeviceIdTuya( @@ -189,11 +202,7 @@ export class DeviceService { async controlDevice(controlDeviceDto: ControlDeviceDto, deviceUuid: string) { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false); if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { throw new NotFoundException('Device Not Found'); @@ -250,12 +259,7 @@ export class DeviceService { deviceUuid, ); - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['productDevice'], - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { throw new NotFoundException('Device Not Found'); @@ -298,7 +302,7 @@ export class DeviceService { }); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { productName, productId, ...rest } = camelCaseResponse.result; + const { productName, productId, id, ...rest } = camelCaseResponse.result; return { ...rest, @@ -316,12 +320,7 @@ export class DeviceService { deviceUuid: string, ): Promise { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['productDevice'], - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { throw new NotFoundException('Device Not Found'); @@ -366,12 +365,7 @@ export class DeviceService { } async getDevicesInstructionStatus(deviceUuid: string) { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['productDevice'], - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { throw new NotFoundException('Device Not Found'); @@ -417,9 +411,106 @@ export class DeviceService { const device = await this.deviceRepository.findOne({ where: { uuid: deviceUuid, + permission: { + userUuid: userUuid, + }, }, relations: ['permission', 'permission.permissionType'], }); return device.permission[0].permissionType.type; } + async getDevicesInGetaway(gatewayUuid: string) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(gatewayUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } else if (deviceDetails.productDevice.prodType !== ProductType.GW) { + throw new BadRequestException('This is not a gateway device'); + } + + const response = await this.getDevicesInGetawayTuya( + deviceDetails.deviceTuyaUuid, + ); + + return { + uuid: deviceDetails.uuid, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + device: response, + }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + HttpStatus.NOT_FOUND, + ); + } + } + async getDevicesInGetawayTuya( + deviceId: string, + ): Promise { + try { + const path = `/v1.0/devices/${deviceId}/sub-devices`; + const response: any = await this.tuya.request({ + method: 'GET', + path, + }); + const camelCaseResponse = response.result.map((device: any) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { product_id, category, id, ...rest } = device; + const camelCaseDevice = convertKeysToCamelCase({ ...rest }); + return camelCaseDevice as GetDeviceDetailsInterface[]; + }); + + return camelCaseResponse; + } catch (error) { + throw new HttpException( + 'Error fetching device details from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async updateDeviceFirmware(deviceUuid: string, firmwareVersion: number) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new NotFoundException('Device Not Found'); + } + const response = await this.updateDeviceFirmwareTuya( + deviceDetails.deviceTuyaUuid, + firmwareVersion, + ); + + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } catch (error) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + } + async updateDeviceFirmwareTuya( + deviceUuid: string, + firmwareVersion: number, + ): Promise { + try { + const path = `/v2.0/cloud/thing/${deviceUuid}/firmware/${firmwareVersion}`; + const response = await this.tuya.request({ + method: 'POST', + path, + }); + + return response as updateDeviceFirmwareInterface; + } catch (error) { + throw new HttpException( + 'Error updating device firmware from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } }