UNPKG

tsvesync

Version:

A TypeScript library for interacting with VeSync smart home devices

399 lines (355 loc) 13.9 kB
/** * VeSync Outlets */ import { VeSyncBaseDevice } from './vesyncBaseDevice'; import { VeSync } from './vesync'; import { Helpers } from './helpers'; import { logger } from './logger'; interface OutletConfig { [key: string]: { module: string; features: string[]; }; } // Outlet configuration export const outletConfig: OutletConfig = { 'wifi-switch-1.3': { module: 'VeSyncOutlet7A', features: ['energy'] }, 'ESW03-USA': { module: 'VeSyncOutlet10A', features: ['energy'] }, 'ESW01-EU': { module: 'VeSyncOutlet10A', features: ['energy'] }, 'ESW10-USA': { module: 'VeSyncOutlet10A', features: ['energy'] }, 'ESW15-USA': { module: 'VeSyncOutlet15A', features: ['energy', 'nightlight'] }, 'ESO15-TB': { module: 'VeSyncOutdoorPlug', features: ['energy'] } }; /** * VeSync Outlet Base Class */ export abstract class VeSyncOutlet extends VeSyncBaseDevice { protected energy: Record<string, any>; protected details: Record<string, any>; protected features: string[]; constructor(details: Record<string, any>, manager: VeSync) { super(details, manager); this.energy = {}; this.details = details; this.features = outletConfig[this.deviceType]?.features || []; } /** * Get outlet details */ async getDetails(): Promise<Boolean> { logger.debug(`[${this.deviceName}] Getting outlet details`); const body = { ...Helpers.reqBody(this.manager, 'devicedetail'), uuid: this.uuid }; let url: string; if (this.deviceType === 'wifi-switch-1.3') { url = '/v1/device/' + this.cid + '/detail'; } else if (this.deviceType.startsWith('ESO15')) { url = '/outdoorsocket15a/v1/device/devicedetail'; } else if (this.deviceType.startsWith('ESW15')) { url = '/15a/v1/device/devicedetail'; } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) { url = '/10a/v1/device/devicedetail'; } else { url = '/v1/device/devicedetail'; } const [response, statusCode] = await this.callApi( url, this.deviceType === 'wifi-switch-1.3' ? 'get' : 'post', body, Helpers.reqHeaders(this.manager) ); if (!response) { logger.debug(`[${this.deviceName}] No response received from API`); return false; } // Handle error responses if (response.error) { logger.error(`[${this.deviceName}] Failed to get outlet details for ${url}: ${JSON.stringify(response)}`); return false; } // Handle successful responses if (response.code === 0) { this.details = response; // Handle outdoor plugs with subdevices if (response.subDevices) { const subDevice = response.subDevices.find((dev: any) => { // Match by name (primary identifier) if (dev.subDeviceName === this.deviceName) { return true; } // Match by subDeviceNo if available if ('subDeviceNo' in this && (this as any).subDeviceNo === dev.subDeviceNo) { return true; } // Match by cid/uuid if available (cid might be in format parentCid_subDeviceNo) if (this.cid && this.cid.includes('_')) { const [_, subId] = this.cid.split('_'); if (subId && dev.subDeviceNo === parseInt(subId)) { return true; } } return false; }); if (subDevice) { this.deviceStatus = subDevice.subDeviceStatus; logger.debug(`[${this.deviceName}] Successfully retrieved sub-device status: ${this.deviceStatus}`); return true; } } // Handle regular devices if (response.deviceStatus !== undefined) { this.deviceStatus = response.deviceStatus; } else if (response.status !== undefined) { this.deviceStatus = response.status; } else if (response.power !== undefined) { this.deviceStatus = response.power === 'on' ? 'on' : 'off'; } else { logger.debug(`[${this.deviceName}] Device status not found in response: ${JSON.stringify(response)}`); return false; } logger.debug(`[${this.deviceName}] Successfully retrieved outlet details`); return true; } else { logger.debug(`[${this.deviceName}] Failed to get outlet details: ${JSON.stringify(response)}`); return false; } } /** * Update outlet energy data */ async updateEnergy(): Promise<void> { if (!this.features.includes('energy')) { logger.debug(`[${this.deviceName}] Energy monitoring not supported`); return; } logger.debug(`[${this.deviceName}] Updating energy data`); // Different endpoints for different device types let url: string; if (this.deviceType === 'wifi-switch-1.3') { url = `/v1/device/${this.deviceType}-${this.cid}/energy/detail`; } else if (this.deviceType.startsWith('ESO15')) { url = '/outdoorsocket15a/v1/device/energy'; } else if (this.deviceType.startsWith('ESW15')) { url = '/15a/v1/device/energy'; } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) { url = '/10a/v1/device/energy'; } else { url = '/v1/device/energy'; } const body = this.deviceType === 'wifi-switch-1.3' ? null : { ...Helpers.reqBody(this.manager, 'energy'), uuid: this.uuid }; const [response] = await this.callApi( url, this.deviceType === 'wifi-switch-1.3' ? 'get' : 'post', body, Helpers.reqHeaders(this.manager) ); // Handle different response formats if (response?.code === 0 && response.result) { this.energy = response.result; logger.info(`[${this.deviceName}] Successfully updated energy data`); } else if (response?.code === 0) { // Some devices return data directly in response this.energy = { power: response.power || '0', voltage: response.voltage || '0', energy: response.energy || '0', energyToday: response.energy || '0', energyWeek: response.energy || '0', energyMonth: response.energy || '0', energyYear: response.energy || '0' }; logger.info(`[${this.deviceName}] Successfully updated energy data`); } else { // For error responses, set defaults but don't log as error this.energy = { power: '0', voltage: '0', energy: '0', energyToday: '0', energyWeek: '0', energyMonth: '0', energyYear: '0' }; logger.debug(`[${this.deviceName}] Failed to update energy data: ${JSON.stringify(response)}`); } } /** * Get outlet energy usage */ async getEnergyUsage(): Promise<Record<string, any>> { if (!this.features.includes('energy')) { logger.debug(`[${this.deviceName}] Energy monitoring not supported`); return { power: 'Not supported', voltage: 'Not supported', energy_today: 'Not supported', energy_week: 'Not supported', energy_month: 'Not supported', energy_year: 'Not supported' }; } await this.updateEnergy(); return { power: this.energy.power || '0', voltage: this.energy.voltage || '0', energy_today: this.energy.energyToday || '0', energy_week: this.energy.energyWeek || '0', energy_month: this.energy.energyMonth || '0', energy_year: this.energy.energyYear || '0' }; } /** * Get outlet status */ async getStatus(): Promise<string> { logger.debug(`[${this.deviceName}] Getting outlet status`); await this.getDetails(); return this.deviceStatus; } /** * Get API prefix based on device type */ private getApiPrefix(): string { if (this.deviceType === 'wifi-switch-1.3') { return `v1/device/${this.deviceType}-${this.cid}`; } else if (this.deviceType.startsWith('ESW15') || this.deviceType.startsWith('ESO15')) { return '15a/v1/device'; } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) { return '10a/v1/device'; } return 'v1/device'; } /** * Get weekly energy data */ async getWeeklyEnergy(): Promise<void> { if (!this.features.includes('energy')) { logger.debug(`[${this.deviceName}] Energy monitoring not supported`); return; } logger.debug(`[${this.deviceName}] Getting weekly energy data`); const isLegacyDevice = this.deviceType === 'wifi-switch-1.3'; const body = isLegacyDevice ? null : { ...Helpers.reqBody(this.manager, 'energyweek'), uuid: this.uuid }; const [response] = await this.callApi( isLegacyDevice ? `/${this.getApiPrefix()}/energy/week` : `/${this.getApiPrefix()}/energyweek`, isLegacyDevice ? 'get' : 'post', body, Helpers.reqHeaders(this.manager) ); if (response?.code === 0) { this.energy.week = response.result || response.energyWeek || '0'; logger.debug(`[${this.deviceName}] Successfully retrieved weekly energy data`); } else { this.energy.week = '0'; logger.debug(`[${this.deviceName}] Failed to get weekly energy data: ${JSON.stringify(response)}`); } } /** * Get monthly energy data */ async getMonthlyEnergy(): Promise<void> { if (!this.features.includes('energy')) { logger.debug(`[${this.deviceName}] Energy monitoring not supported`); return; } logger.debug(`[${this.deviceName}] Getting monthly energy data`); const isLegacyDevice = this.deviceType === 'wifi-switch-1.3'; const body = isLegacyDevice ? null : { ...Helpers.reqBody(this.manager, 'energymonth'), uuid: this.uuid }; const [response] = await this.callApi( isLegacyDevice ? `/${this.getApiPrefix()}/energy/month` : `/${this.getApiPrefix()}/energymonth`, isLegacyDevice ? 'get' : 'post', body, Helpers.reqHeaders(this.manager) ); if (response?.code === 0) { this.energy.month = response.result || response.energyMonth || '0'; logger.debug(`[${this.deviceName}] Successfully retrieved monthly energy data`); } else { this.energy.month = '0'; logger.debug(`[${this.deviceName}] Failed to get monthly energy data: ${JSON.stringify(response)}`); } } /** * Get yearly energy data */ async getYearlyEnergy(): Promise<void> { if (!this.features.includes('energy')) { logger.debug(`[${this.deviceName}] Energy monitoring not supported`); return; } logger.debug(`[${this.deviceName}] Getting yearly energy data`); const isLegacyDevice = this.deviceType === 'wifi-switch-1.3'; const body = isLegacyDevice ? null : { ...Helpers.reqBody(this.manager, 'energyyear'), uuid: this.uuid }; const [response] = await this.callApi( isLegacyDevice ? `/${this.getApiPrefix()}/energy/year` : `/${this.getApiPrefix()}/energyyear`, isLegacyDevice ? 'get' : 'post', body, Helpers.reqHeaders(this.manager) ); if (response?.code === 0) { this.energy.year = response.result || response.energyYear || '0'; logger.debug(`[${this.deviceName}] Successfully retrieved yearly energy data`); } else { this.energy.year = '0'; logger.debug(`[${this.deviceName}] Failed to get yearly energy data: ${JSON.stringify(response)}`); } } /** * Update outlet details and energy info */ async update(): Promise<Boolean> { logger.debug(`[${this.deviceName}] Updating outlet information`); const success = await this.getDetails(); if (success && this.features.includes('energy')) { await this.updateEnergy(); } logger.info(`[${this.deviceName}] Successfully updated outlet information`); return success; } /** * Check if outlet has nightlight feature */ hasNightlight(): boolean { return this.features.includes('nightlight'); } /** * Turn outlet on */ abstract turnOn(): Promise<boolean>; /** * Turn outlet off */ abstract turnOff(): Promise<boolean>; }