mirror of
https://github.com/SyncrowIOT/backend.git
synced 2026-10-25 01:17:54 +00:00
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
|
|
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
|
|
import { GetSpaceParam } from '../dtos';
|
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
|
|
|
import { ValidationService } from './space-validation.service';
|
|
import { ProductType } from '@app/common/constants/product-type.enum';
|
|
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
|
|
|
|
@Injectable()
|
|
export class SpaceDeviceService {
|
|
constructor(
|
|
private readonly tuyaService: TuyaService,
|
|
private readonly validationService: ValidationService,
|
|
) {}
|
|
|
|
async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> {
|
|
const { spaceUuid, communityUuid, projectUuid } = params;
|
|
|
|
try {
|
|
// Validate community, project, and fetch space including devices in a single query
|
|
const space = await this.validationService.fetchSpaceDevices(spaceUuid);
|
|
|
|
if (!space || !space.devices?.length) {
|
|
throw new HttpException(
|
|
'The space does not contain any devices.',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
const spaceHierarchy =
|
|
await this.validationService.getParentHierarchy(space);
|
|
|
|
const orderedHierarchy = [space, ...spaceHierarchy.reverse()];
|
|
|
|
// Fetch Tuya details for each device in parallel using Promise.allSettled
|
|
const deviceDetailsPromises = space.devices.map((device) =>
|
|
this.fetchDeviceDetails(device, orderedHierarchy),
|
|
);
|
|
|
|
const detailedDevices = (await Promise.allSettled(deviceDetailsPromises))
|
|
.filter((result) => result.status === 'fulfilled' && result.value)
|
|
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
|
|
|
return new SuccessResponseDto({
|
|
data: detailedDevices,
|
|
message: 'Successfully retrieved list of devices.',
|
|
});
|
|
} catch (error) {
|
|
console.error('Error listing devices in space:', error);
|
|
throw new HttpException(
|
|
error.message || 'Failed to retrieve devices in space',
|
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async fetchDeviceDetails(device: any, orderedHierarchy: any[]) {
|
|
try {
|
|
// Fetch Tuya details in parallel
|
|
const [tuyaDetails, tuyaDeviceStatusResponse] = await Promise.all([
|
|
this.getDeviceDetailsByDeviceIdTuya(device.deviceTuyaUuid),
|
|
this.tuyaService.getDevicesInstructionStatusTuya(device.deviceTuyaUuid),
|
|
]);
|
|
|
|
const tuyaStatusList =
|
|
tuyaDeviceStatusResponse?.result?.[0]?.status || [];
|
|
|
|
return {
|
|
spaces: orderedHierarchy.map((space) => ({
|
|
uuid: space.uuid,
|
|
spaceName: space.spaceName,
|
|
})),
|
|
uuid: device.uuid,
|
|
deviceTuyaUuid: device.deviceTuyaUuid,
|
|
productUuid: device.productDevice?.uuid || null,
|
|
productType: device.productDevice?.prodType || null,
|
|
isActive: device.isActive,
|
|
updatedAt: device.updatedAt,
|
|
deviceTag: device.tag,
|
|
subspace: device.subspace,
|
|
...tuyaDetails,
|
|
...(this.extractBatteryStatus(
|
|
device.productDevice?.prodType,
|
|
tuyaStatusList,
|
|
) !== null && {
|
|
battery: this.extractBatteryStatus(
|
|
device.productDevice?.prodType,
|
|
tuyaStatusList,
|
|
),
|
|
}),
|
|
status: tuyaStatusList,
|
|
};
|
|
} catch (error) {
|
|
console.warn(
|
|
`Skipping device ${device.deviceTuyaUuid} due to error: ${error.message}`,
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private extractBatteryStatus(
|
|
deviceType: string,
|
|
tuyaStatus: any[],
|
|
): number | null {
|
|
const batteryCodes = {
|
|
[ProductType.DL]: BatteryStatus.RESIDUAL_ELECTRICITY,
|
|
[ProductType.DS]: BatteryStatus.BATTERY_PERCENTAGE,
|
|
[ProductType.WL]: BatteryStatus.BATTERY_PERCENTAGE,
|
|
[ProductType.SOS]: BatteryStatus.BATTERY_PERCENTAGE,
|
|
};
|
|
|
|
const batteryCode = batteryCodes[deviceType];
|
|
if (!batteryCode) return null;
|
|
|
|
const batteryStatus = tuyaStatus.find(
|
|
(status) => status.code === batteryCode,
|
|
);
|
|
return batteryStatus ? batteryStatus.value : null;
|
|
}
|
|
|
|
private async getDeviceDetailsByDeviceIdTuya(
|
|
deviceId: string,
|
|
): Promise<GetDeviceDetailsInterface> {
|
|
try {
|
|
const tuyaDeviceDetails =
|
|
await this.tuyaService.getDeviceDetails(deviceId);
|
|
|
|
// Convert keys to camel case
|
|
const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails);
|
|
|
|
// Exclude specific keys and add `productUuid`
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const { uuid, ...rest } = camelCaseResponse;
|
|
return {
|
|
...rest,
|
|
} as GetDeviceDetailsInterface;
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
`Error fetching device details from Tuya for device id ${deviceId}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|