homebridge-homeassistant-mbmccormick
Version:
Homebridge plugin for Home Assistant: https://home-assistant.io
472 lines (414 loc) • 14.5 kB
JavaScript
'use strict';
let Service;
let Characteristic;
let communicationError;
/* eslint-disable */
const LightUtil = {
hsvToRgb(h, s, v) {
let r;
let g;
let b;
let i;
let f;
let p;
let q;
let t;
if (arguments.length === 1) {
s = h.s, v = h.v, h = h.h;
}
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
},
rgbToHsv(r, g, b) {
if (arguments.length === 1) {
g = r.g, b = r.b, r = r.r;
}
let max = Math.max(r, g, b),
min = Math.min(r, g, b),
d = max - min,
h,
s = (max === 0 ? 0 : d / max),
v = max / 255;
switch (max) {
case min: h = 0; break;
case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break;
case g: h = (b - r) + d * 2; h /= 6 * d; break;
case b: h = (r - g) + d * 4; h /= 6 * d; break;
}
return {
h,
s,
v,
};
},
rgbToCie(red, green, blue) {
// Apply a gamma correction to the RGB values, which makes the color more vivid and more the like the color displayed on the screen of your device
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
// RGB values to XYZ using the Wide RGB D65 conversion formula
const X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
const Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
const Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
// Calculate the xy values from the XYZ values
let x = (X / (X + Y + Z)).toFixed(4);
let y = (Y / (X + Y + Z)).toFixed(4);
if (isNaN(x)) {
x = 0;
}
if (isNaN(y)) { y = 0; }
return [x, y];
},
};
/* eslint-enable */
function HomeAssistantLight(log, data, client, firmware) {
// device info
this.domain = 'light';
this.data = data;
this.entity_id = data.entity_id;
this.uuid_base = data.entity_id;
this.firmware = firmware;
if (data.attributes && data.attributes.friendly_name) {
this.name = data.attributes.friendly_name;
} else {
this.name = data.entity_id.split('.').pop().replace(/_/g, ' ');
}
if (data.attributes && data.attributes.homebridge_manufacturer) {
this.manufacturer = String(data.attributes.homebridge_manufacturer);
} else {
this.manufacturer = 'Home Assistant';
}
if (data.attributes && data.attributes.homebridge_model) {
this.model = String(data.attributes.homebridge_model);
} else {
this.model = 'Light';
}
if (data.attributes && data.attributes.homebridge_serial) {
this.serial = String(data.attributes.homebridge_serial);
} else {
this.serial = data.entity_id;
}
this.client = client;
this.log = log;
this.maxTemp = 400;
this.minTemp = 50;
if (data.attributes.homebridge_max_mireds) {
this.maxTemp = data.attributes.homebridge_max_mireds;
}
if (data.attributes.homebridge_min_mireds) {
this.minTemp = data.attributes.homebridge_min_mireds;
}
this.cachedColor = false;
}
HomeAssistantLight.prototype = {
features: Object.freeze({
BRIGHTNESS: 1,
COLOR_TEMP: 2,
EFFECT: 4,
FLASH: 8,
RGB_COLOR: 16,
TRANSITION: 32,
XY_COLOR: 64,
}),
is_supported(feature) {
// If the supported_features attribute doesn't exist, assume not supported
if (this.data.attributes.supported_features === undefined) {
return false;
}
return (this.data.attributes.supported_features & feature) > 0;
},
onEvent(oldState, newState) {
if (newState.state) {
this.lightbulbService.getCharacteristic(Characteristic.On)
.setValue(newState.state === 'on', null, 'internal');
if (this.is_supported(this.features.BRIGHTNESS)) {
const brightness = Math.round(((newState.attributes.brightness || 0) / 255) * 100);
this.lightbulbService.getCharacteristic(Characteristic.Brightness)
.setValue(brightness, null, 'internal');
this.data.attributes.brightness = newState.attributes.brightness;
}
if (this.is_supported(this.features.RGB_COLOR) &&
newState.attributes.rgb_color !== undefined) {
const rgbColor = newState.attributes.rgb_color;
const hsv = LightUtil.rgbToHsv(rgbColor[0], rgbColor[1], rgbColor[2]);
const hue = hsv.h * 360;
const saturation = hsv.s * 100;
this.lightbulbService.getCharacteristic(Characteristic.Hue)
.setValue(hue, null, 'internal');
this.lightbulbService.getCharacteristic(Characteristic.Saturation)
.setValue(saturation, null, 'internal');
this.data.attributes.hue = hue;
this.data.attributes.saturation = saturation;
}
if (this.is_supported(this.features.COLOR_TEMP)) {
const colorTemperature = Math.round(newState.attributes.color_temp) || this.minTemp;
this.lightbulbService.getCharacteristic(Characteristic.ColorTemperature)
.setValue(colorTemperature, null, 'internal');
}
}
},
identify(callback) {
this.log(`identifying: ${this.name}`);
const that = this;
const serviceData = {};
serviceData.entity_id = this.entity_id;
let service = 'toggle';
if (this.is_supported(this.features.FLASH)) {
service = 'turn_on';
serviceData.flash = 'short';
}
this.client.callService(this.domain, service, serviceData, (data) => {
if (data) {
that.log(`Successfully identified '${that.name}'`);
}
callback();
});
},
getPowerState(callback) {
this.log(`fetching power state for: ${this.name}`);
this.client.fetchState(this.entity_id, (data) => {
if (data) {
const powerState = data.state === 'on';
callback(null, powerState);
} else {
callback(communicationError);
}
});
},
getBrightness(callback) {
this.log(`fetching brightness for: ${this.name}`);
this.client.fetchState(this.entity_id, (data) => {
if (data) {
const brightness = ((data.attributes.brightness || 0) / 255) * 100;
callback(null, brightness);
} else {
callback(communicationError);
}
});
},
getHue(callback) {
const that = this;
this.client.fetchState(this.entity_id, (data) => {
if (data) {
const rgb = data.attributes.rgb_color || [0, 0, 0];
const hsv = LightUtil.rgbToHsv(rgb[0], rgb[1], rgb[2]);
const hue = hsv.h * 360;
that.data.attributes.hue = hue;
callback(null, hue);
} else {
callback(communicationError);
}
});
},
getSaturation(callback) {
const that = this;
this.client.fetchState(this.entity_id, (data) => {
if (data) {
const rgb = data.attributes.rgb_color || [0, 0, 0];
const hsv = LightUtil.rgbToHsv(rgb[0], rgb[1], rgb[2]);
const saturation = hsv.s * 100;
that.data.attributes.saturation = saturation;
callback(null, saturation);
} else {
callback(communicationError);
}
});
},
getColorTemperature(callback) {
this.client.fetchState(this.entity_id, (data) => {
if (data) {
const colorTemp = Math.round(data.attributes.color_temp) || this.minTemp;
callback(null, colorTemp);
} else {
callback(communicationError);
}
});
},
setPowerState(powerOn, callback, context) {
if (context === 'internal') {
callback();
return;
}
const that = this;
const serviceData = {};
serviceData.entity_id = this.entity_id;
if (powerOn) {
this.log(`Setting power state on the '${this.name}' to on`);
this.client.callService(this.domain, 'turn_on', serviceData, (data) => {
if (data) {
that.log(`Successfully set power state on the '${that.name}' to on`);
callback();
} else {
callback(communicationError);
}
});
} else {
this.log(`Setting power state on the '${this.name}' to off`);
this.client.callService(this.domain, 'turn_off', serviceData, (data) => {
if (data) {
that.log(`Successfully set power state on the '${that.name}' to off`);
callback();
} else {
callback(communicationError);
}
});
}
},
setBrightness(level, callback, context) {
if (context === 'internal') {
callback();
return;
}
const that = this;
const serviceData = {};
serviceData.entity_id = this.entity_id;
serviceData.brightness = 255 * (level / 100.0);
this.log(`Setting brightness on the '${this.name}' to ${level}`);
this.client.callService(this.domain, 'turn_on', serviceData, (data) => {
if (data) {
that.log(`Successfully set brightness on the '${that.name}' to ${level}`);
callback();
} else {
callback(communicationError);
}
});
},
setHue(level, callback, context) {
if (context === 'internal') {
callback();
return;
}
this.data.attributes.hue = level;
if (this.cachedColor) {
this._setColor(callback);
} else {
this.cachedColor = true;
callback();
}
},
setSaturation(level, callback, context) {
if (context === 'internal') {
callback();
return;
}
this.data.attributes.saturation = level;
if (this.cachedColor) {
this._setColor(callback);
} else {
this.cachedColor = true;
callback();
}
},
_setColor(callback) {
const that = this;
this.cachedColor = false;
const serviceData = {};
serviceData.entity_id = this.entity_id;
const rgb = LightUtil.hsvToRgb(
(this.data.attributes.hue || 0) / 360,
(this.data.attributes.saturation || 0) / 100,
(this.data.attributes.brightness || 0) / 255
);
if (this.data.attributes.hue !== undefined) {
if (this.is_supported(this.features.XY_COLOR)) {
serviceData.xy_color = LightUtil.rgbToCie(rgb.r, rgb.g, rgb.b);
} else {
serviceData.rgb_color = [rgb.r, rgb.g, rgb.b];
}
}
this.client.callService(this.domain, 'turn_on', serviceData, (data) => {
if (data) {
if (that.is_supported(that.features.XY_COLOR)) {
that.log(`Successfully set xy on the '${that.name}' to ${serviceData.xy_color}`);
} else {
that.log(`Successfully set rgb on the '${that.name}' to ${serviceData.rgb_color}`);
}
callback();
} else {
callback(communicationError);
}
});
},
setColorTemperature(level, callback, context) {
if (context === 'internal') {
callback();
return;
}
const that = this;
const serviceData = {};
serviceData.entity_id = this.entity_id;
serviceData.color_temp = Math.round(level);
this.client.callService(this.domain, 'turn_on', serviceData, (data) => {
if (data) {
that.log(`Successfully set color temperature on the '${that.name}' to ${serviceData.color_temp}`);
callback();
} else {
callback(communicationError);
}
});
},
getServices() {
this.lightbulbService = new Service.Lightbulb();
const informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.serial)
.setCharacteristic(Characteristic.FirmwareRevision, this.firmware);
informationService
.setCharacteristic(Characteristic.Identify)
.on('set', this.identify.bind(this));
this.lightbulbService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
if (this.is_supported(this.features.BRIGHTNESS)) {
this.lightbulbService
.addCharacteristic(Characteristic.Brightness)
.on('get', this.getBrightness.bind(this))
.on('set', this.setBrightness.bind(this));
}
if (this.is_supported(this.features.RGB_COLOR)) {
this.lightbulbService
.addCharacteristic(Characteristic.Hue)
.on('get', this.getHue.bind(this))
.on('set', this.setHue.bind(this));
this.lightbulbService
.addCharacteristic(Characteristic.Saturation)
.on('get', this.getSaturation.bind(this))
.on('set', this.setSaturation.bind(this));
}
if (this.is_supported(this.features.COLOR_TEMP)) {
this.lightbulbService
.addCharacteristic(Characteristic.ColorTemperature)
.setProps({ maxValue: this.maxTemp, minValue: this.minTemp })
.on('get', this.getColorTemperature.bind(this))
.on('set', this.setColorTemperature.bind(this));
}
return [informationService, this.lightbulbService];
},
};
function HomeAssistantLightPlatform(oService, oCharacteristic, oCommunicationError) {
Service = oService;
Characteristic = oCharacteristic;
communicationError = oCommunicationError;
return HomeAssistantLight;
}
module.exports = HomeAssistantLightPlatform;
module.exports.HomeAssistantLight = HomeAssistantLight;