Skip to content

Commit

Permalink
Fix Philips Hue polling in color temperature only mode (#2081)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Gilles authored May 20, 2024
1 parent 70feeaa commit 1a4c6d1
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 5 deletions.
17 changes: 16 additions & 1 deletion server/services/philips-hue/lib/light/light.poll.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { EVENTS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants');
const { LIGHT_EXTERNAL_ID_BASE } = require('../utils/consts');
const { xyToInt, hsbToRgb, rgbToInt } = require('../../../../utils/colors');
const { xyToInt, hsbToRgb, rgbToInt, kelvinToRGB, miredToKelvin } = require('../../../../utils/colors');

const logger = require('../../../../utils/logger');
const { parseExternalId } = require('../utils/parseExternalId');
Expand Down Expand Up @@ -37,6 +37,8 @@ async function poll(device) {
let currentColorState;
switch (state.colormode) {
case 'ct':
currentColorState = rgbToInt(kelvinToRGB(miredToKelvin(state.ct)));
break;
case 'xy':
currentColorState = xyToInt(state.xy[0], state.xy[1]);
break;
Expand Down Expand Up @@ -72,6 +74,19 @@ async function poll(device) {
state: newBrightnessValue,
});
}

const colorTemperatureFeature = getDeviceFeature(
device,
DEVICE_FEATURE_CATEGORIES.LIGHT,
DEVICE_FEATURE_TYPES.LIGHT.TEMPERATURE,
);
if (colorTemperatureFeature && colorTemperatureFeature.last_value !== state.ct) {
logger.debug(`Polling Philips Hue ${lightId}, new color temperature value = ${state.ct}`);
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${lightId}:${DEVICE_FEATURE_TYPES.LIGHT.TEMPERATURE}`,
state: state.ct,
});
}
}

module.exports = {
Expand Down
19 changes: 16 additions & 3 deletions server/test/services/philips-hue/light/light.poll.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { expect } = require('chai');
const { assert, fake } = require('sinon');
const proxyquire = require('proxyquire').noCallThru();
const { MockedPhilipsHueClient, hueApiHsColorMode } = require('../mocks.test');
const { MockedPhilipsHueClient, hueApiHsColorMode, hueApiCtColorMode } = require('../mocks.test');
const { EVENTS } = require('../../../../utils/constants');

const PhilipsHueService = proxyquire('../../../../services/philips-hue/index', {
Expand Down Expand Up @@ -182,9 +182,14 @@ describe('PhilipsHueService Poll', () => {
state: 11534095,
});
});
it('should poll light with mode ct and use xy', async () => {
it('should poll light with mode ct and update color temperature', async () => {
// PREPARE
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
philipsHueService.device.hueClient.api = {
createLocal: () => ({
connect: () => hueApiCtColorMode,
}),
};
await philipsHueService.device.init();
// EXECUTE
await philipsHueService.device.poll({
Expand All @@ -194,12 +199,20 @@ describe('PhilipsHueService Poll', () => {
category: 'light',
type: 'color',
},
{
category: 'light',
type: 'temperature',
},
],
});
// ASSERT
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:color',
state: 16187362,
state: 16759424,
});
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:temperature',
state: 305,
});
});
it('should poll light and update bri', async () => {
Expand Down
19 changes: 19 additions & 0 deletions server/test/services/philips-hue/mocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ const hueApiHsColorMode = {
},
};

const hueApiCtColorMode = {
lights: {
getLightState: fake.resolves({
on: true,
bri: 90,
hue: 16203,
sat: 76,
effect: 'none',
xy: [0.4181, 0.3975],
ct: 305,
alert: 'select',
colormode: 'ct',
mode: 'homeautomation',
reachable: true,
}),
},
};

const MockedPhilipsHueClient = {
v3: {
lightStates: {
Expand Down Expand Up @@ -141,4 +159,5 @@ module.exports = {
fakes,
hueApi,
hueApiHsColorMode,
hueApiCtColorMode,
};
40 changes: 39 additions & 1 deletion server/test/utils/colors.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
const { expect } = require('chai');
const { intToHex, hexToInt, intToRgb, rgbToInt, xyToInt, hsbToRgb, rgbToHsb } = require('../../utils/colors');
const {
intToHex,
hexToInt,
intToRgb,
rgbToInt,
xyToInt,
hsbToRgb,
rgbToHsb,
kelvinToRGB,
} = require('../../utils/colors');

describe('colors', () => {
const matchingTable = {
Expand Down Expand Up @@ -67,3 +76,32 @@ describe('colors', () => {
}
});
});

describe('ColorTemperature', () => {
const kelvinInRgb = [
{ kelvin: 0, rgb: [255, 0, 0] },
{ kelvin: 1901, rgb: [255, 132, 0] },
{ kelvin: 2000, rgb: [255, 137, 14] },
{ kelvin: 5000, rgb: [255, 228, 206] },
{ kelvin: 8000, rgb: [221, 230, 255] },
{ kelvin: 6600, rgb: [255, 255, 255] },
];
kelvinInRgb.forEach((color) => {
it(`color ${color.kelvin}K should equal ${color.rgb} in RGB`, () => {
const value = kelvinToRGB(color.kelvin);
expect(value).to.deep.equal(color.rgb);
});
});
it('should try all Kelvin from 0 to 50k', function Test() {
this.timeout(10000);
for (let i = 0; i < 50000; i += 1) {
const [r, g, b] = kelvinToRGB(i);
expect(r).to.be.greaterThanOrEqual(0);
expect(g).to.be.greaterThanOrEqual(0);
expect(b).to.be.greaterThanOrEqual(0);
expect(r).to.be.lessThanOrEqual(255);
expect(g).to.be.lessThanOrEqual(255);
expect(b).to.be.lessThanOrEqual(255);
}
});
});
55 changes: 55 additions & 0 deletions server/utils/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,60 @@ function hexToInt(hexColor) {
return parseInt(hexColor, 16);
}

/**
* @description Convert Kelvin to RGB temp color.
* @param {number} kelvin - Color temperature in Kelvin.
* @returns {Array} - Return array of RGB.
* @example const [r, g, b] = kelvinToRGB(2500);
*/
function kelvinToRGB(kelvin) {
const temperature = kelvin / 100;
let red;
let green;
let blue;

// Calculate Red
if (temperature <= 66) {
red = 255;
} else {
red = temperature - 60;
red = 329.698727446 * red ** -0.1332047592;
if (red > 255) {
red = 255;
}
}

// Calculate Green
if (temperature <= 66) {
green = temperature;
green = 99.4708025861 * Math.log(green) - 161.1195681661;
if (green < 0) {
green = 0;
}
if (green > 255) {
green = 255;
}
} else {
green = temperature - 60;
green = 288.1221695283 * green ** -0.0755148492;
}

// Calculate Blue
if (temperature >= 66) {
blue = 255;
} else if (temperature <= 19) {
blue = 0;
} else {
blue = temperature - 10;
blue = 138.5177312231 * Math.log(blue) - 305.0447927307;
if (blue < 0) {
blue = 0;
}
}

return [Math.round(red), Math.round(green), Math.round(blue)];
}

/**
* @description Reverse Gamma correction applied in rgbToXy.
* @param {number} value - Color value.
Expand Down Expand Up @@ -191,4 +245,5 @@ module.exports = {
rgbToHsb,
miredToKelvin,
kelvinToMired,
kelvinToRGB,
};

0 comments on commit 1a4c6d1

Please sign in to comment.