tsvesync
Version:
A TypeScript library for interacting with VeSync smart home devices
409 lines (408 loc) • 14.8 kB
JavaScript
"use strict";
/**
* VeSync Bulb Base Class
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VeSyncBulb = exports.bulbConfig = void 0;
const vesyncBaseDevice_1 = require("./vesyncBaseDevice");
const helpers_1 = require("./helpers");
const logger_1 = require("./logger");
// Bulb configuration
exports.bulbConfig = {
'ESL100': {
module: 'VeSyncBulbESL100',
features: ['dimmable'],
colorModel: 'none'
},
'ESL100CW': {
module: 'VeSyncBulbESL100CW',
features: ['dimmable', 'color_temp'],
colorModel: 'none'
},
'XYD0001': {
module: 'VeSyncBulbXYD0001',
features: ['dimmable', 'color_temp', 'rgb_shift'],
colorModel: 'hsv'
},
'ESL100MC': {
module: 'VeSyncBulbESL100MC',
features: ['dimmable', 'rgb_shift'],
colorModel: 'rgb'
}
};
/**
* VeSync Bulb Base Class
*/
class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
constructor(details, manager) {
var _a;
super(details, manager);
this.brightness = 0;
this.colorTemp = 0;
this.colorValue = 0;
this.colorHue = 0;
this.colorSaturation = 0;
this.colorMode = '';
this.features = ((_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.features) || [];
this.rgbValues = {
red: 0,
green: 0,
blue: 0
};
}
hasFeature(feature) {
return this.features.includes(feature);
}
getColorModel() {
var _a, _b;
return (_b = (_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.colorModel) !== null && _b !== void 0 ? _b : 'none';
}
getBrightness() {
return this.brightness;
}
/**
* Get bulb details
*/
async getDetails() {
logger_1.logger.debug(`[${this.deviceName}] Getting bulb details`);
const isV2Device = this.deviceType === 'XYD0001';
const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
const body = isV2Device ? {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {},
method: 'getLightStatusV2',
source: 'APP'
}
} : {
...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
cid: this.cid,
configModule: this.configModule,
jsonCmd: {
getLightStatus: 'get'
}
};
const [response, statusCode] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
return this.processStandardBypassDetails(response, statusCode);
}
/**
* Update bulb details
*/
async update() {
logger_1.logger.debug(`[${this.deviceName}] Updating bulb information`);
const success = await this.getDetails();
logger_1.logger.info(`[${this.deviceName}] Successfully updated bulb information`);
return success;
}
/**
* Turn bulb on
*/
async turnOn() {
logger_1.logger.debug(`[${this.deviceName}] Turning bulb on`);
const isV2Device = this.deviceType === 'XYD0001';
const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
const body = isV2Device ? {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
enabled: true,
id: 0
},
method: 'setSwitch',
source: 'APP'
}
} : {
...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
cid: this.cid,
configModule: this.configModule,
jsonCmd: {
light: {
action: 'on'
}
}
};
const [response] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
this.deviceStatus = 'on';
logger_1.logger.info(`[${this.deviceName}] Successfully turned bulb on`);
return true;
}
logger_1.logger.error(`[${this.deviceName}] Failed to turn bulb on: ${JSON.stringify(response)}`);
return false;
}
/**
* Turn bulb off
*/
async turnOff() {
logger_1.logger.debug(`[${this.deviceName}] Turning bulb off`);
const isV2Device = this.deviceType === 'XYD0001';
const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
const body = isV2Device ? {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
enabled: false,
id: 0
},
method: 'setSwitch',
source: 'APP'
}
} : {
...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
cid: this.cid,
configModule: this.configModule,
jsonCmd: {
light: {
action: 'off'
}
}
};
const [response] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
this.deviceStatus = 'off';
logger_1.logger.info(`[${this.deviceName}] Successfully turned bulb off`);
return true;
}
logger_1.logger.error(`[${this.deviceName}] Failed to turn bulb off: ${JSON.stringify(response)}`);
return false;
}
/**
* Set bulb brightness
*/
async setBrightness(brightness) {
if (!this.features.includes('dimmable')) {
logger_1.logger.error(`[${this.deviceName}] Dimming not supported`);
return false;
}
const brightnessLevel = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
logger_1.logger.debug(`[${this.deviceName}] Setting brightness to ${brightnessLevel}`);
const isV2Device = this.deviceType === 'XYD0001';
const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
const body = isV2Device ? {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
brightness: brightnessLevel,
colorMode: '',
colorTemp: '',
force: 0,
hue: '',
saturation: '',
value: ''
},
method: 'setLightStatusV2',
source: 'APP'
}
} : {
...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
cid: this.cid,
configModule: this.configModule,
jsonCmd: {
light: {
brightness: brightnessLevel
}
}
};
const [response] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
this.brightness = brightnessLevel;
logger_1.logger.info(`[${this.deviceName}] Successfully set brightness to ${brightnessLevel}`);
return true;
}
logger_1.logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
return false;
}
/**
* Get bulb brightness
*/
/**
* Get color temperature in Kelvin
*/
getColorTempKelvin() {
if (!this.features.includes('color_temp')) {
return 0;
}
return ((6500 - 2700) * this.colorTemp / 100) + 2700;
}
/**
* Get color temperature in percent
*/
getColorTempPercent() {
if (!this.features.includes('color_temp')) {
return 0;
}
return this.colorTemp;
}
/**
* Get color hue
*/
getColorHue() {
if (!this.features.includes('rgb_shift')) {
return 0;
}
return this.colorHue;
}
/**
* Get color saturation
*/
getColorSaturation() {
if (!this.features.includes('rgb_shift')) {
return 0;
}
return this.colorSaturation;
}
/**
* Get color value
*/
getColorValue() {
if (!this.features.includes('rgb_shift')) {
return 0;
}
return this.colorValue;
}
/**
* Get RGB values
*/
getRGBValues() {
if (!this.features.includes('rgb_shift') || exports.bulbConfig[this.deviceType].colorModel !== 'rgb') {
return { red: 0, green: 0, blue: 0 };
}
return this.rgbValues;
}
/**
* Set color temperature wrapper to maintain compatibility with consumers
*/
async setColorTemperature(colorTemp) {
return this.setColorTemp(colorTemp);
}
/**
* Set color via hue/saturation/value when supported
*/
async setColor(hue, saturation, value = 100) {
var _a;
if (!this.features.includes('rgb_shift')) {
logger_1.logger.error(`[${this.deviceName}] Color control not supported`);
return false;
}
const colorModel = (_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.colorModel;
if (colorModel === 'rgb' && typeof this.setRgb === 'function') {
const rgb = VeSyncBulb.hsvToRgb(hue, saturation, value);
return this.setRgb(rgb.red, rgb.green, rgb.blue);
}
if (typeof this.setHsv === 'function') {
return this.setHsv(hue, saturation, value);
}
logger_1.logger.error(`[${this.deviceName}] No compatible color handler found`);
return false;
}
/**
* Process standard bypass (v1) detail responses shared by multiple bulbs
*/
processStandardBypassDetails(response, statusCode) {
var _a, _b, _c, _d, _e, _f, _g, _h;
const success = this.checkResponse([response, statusCode], 'getDetails');
if (success && (response === null || response === void 0 ? void 0 : response.result)) {
const innerResult = ((_a = response.result) === null || _a === void 0 ? void 0 : _a.result) || response.result;
if (innerResult) {
if (typeof innerResult.enabled === 'boolean') {
this.deviceStatus = innerResult.enabled ? 'on' : 'off';
}
else if (innerResult.action) {
this.deviceStatus = innerResult.action === 'on' ? 'on' : 'off';
}
if (innerResult.connectionStatus) {
this.connectionStatus = innerResult.connectionStatus;
}
if (this.features.includes('dimmable') && innerResult.brightness !== undefined) {
this.brightness = Number(innerResult.brightness);
}
if (this.features.includes('color_temp')) {
if (innerResult.colorTemp !== undefined) {
this.colorTemp = Number(innerResult.colorTemp);
}
else if (innerResult.colorTempe !== undefined) {
this.colorTemp = Number(innerResult.colorTempe);
}
}
if (this.features.includes('rgb_shift')) {
if (((_b = exports.bulbConfig[this.deviceType]) === null || _b === void 0 ? void 0 : _b.colorModel) === 'rgb') {
this.rgbValues = {
red: Number((_c = innerResult.red) !== null && _c !== void 0 ? _c : this.rgbValues.red),
green: Number((_d = innerResult.green) !== null && _d !== void 0 ? _d : this.rgbValues.green),
blue: Number((_e = innerResult.blue) !== null && _e !== void 0 ? _e : this.rgbValues.blue)
};
}
else {
this.colorHue = Number((_f = innerResult.hue) !== null && _f !== void 0 ? _f : this.colorHue);
this.colorSaturation = Number((_g = innerResult.saturation) !== null && _g !== void 0 ? _g : this.colorSaturation);
this.colorValue = Number((_h = innerResult.value) !== null && _h !== void 0 ? _h : this.colorValue);
}
}
}
logger_1.logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
}
return success;
}
static clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
static hsvToRgb(h, s, v) {
const saturation = VeSyncBulb.clamp(s, 0, 100) / 100;
const value = VeSyncBulb.clamp(v, 0, 100) / 100;
const hue = ((h % 360) + 360) % 360 / 60;
const i = Math.floor(hue);
const f = hue - i;
const p = value * (1 - saturation);
const q = value * (1 - saturation * f);
const t = value * (1 - saturation * (1 - f));
let r = 0;
let g = 0;
let b = 0;
switch (i) {
case 0:
r = value;
g = t;
b = p;
break;
case 1:
r = q;
g = value;
b = p;
break;
case 2:
r = p;
g = value;
b = t;
break;
case 3:
r = p;
g = q;
b = value;
break;
case 4:
r = t;
g = p;
b = value;
break;
default:
r = value;
g = p;
b = q;
break;
}
return {
red: Math.round(r * 255),
green: Math.round(g * 255),
blue: Math.round(b * 255)
};
}
}
exports.VeSyncBulb = VeSyncBulb;