Skip to content

Commit

Permalink
Philips Hue : Poll color/brightness, reduce poll time & update depend…
Browse files Browse the repository at this point in the history
…encies (#2071)

Co-authored-by: Cyril Beslay <[email protected]>
  • Loading branch information
Pierre-Gilles and cicoub13 authored May 9, 2024
1 parent 608146c commit 3854c30
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 35 deletions.
2 changes: 2 additions & 0 deletions server/lib/device/device.setupPoll.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ function setupPoll() {
setInterval(this.pollAll(DEVICE_POLL_FREQUENCIES.EVERY_MINUTES), DEVICE_POLL_FREQUENCIES.EVERY_MINUTES);
// poll devices who need to be polled every 30 seconds
setInterval(this.pollAll(DEVICE_POLL_FREQUENCIES.EVERY_30_SECONDS), DEVICE_POLL_FREQUENCIES.EVERY_30_SECONDS);
// poll devices who need to be polled every 15 seconds
setInterval(this.pollAll(DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS), DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS);
// poll devices who need to be polled every 10 seconds
setInterval(this.pollAll(DEVICE_POLL_FREQUENCIES.EVERY_10_SECONDS), DEVICE_POLL_FREQUENCIES.EVERY_10_SECONDS);
// poll devices who need to be polled every 2 seconds
Expand Down
2 changes: 1 addition & 1 deletion server/services/philips-hue/lib/light/light.getLights.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async function getLights() {
selector: `${LIGHT_EXTERNAL_ID_BASE}:${serialNumber}:${philipsHueLight.id}`,
should_poll: true,
model: philipsHueLight.modelid,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS,
features: [],
not_handled: true,
raw_philips_hue_device: philipsHueLight,
Expand Down
48 changes: 45 additions & 3 deletions server/services/philips-hue/lib/light/light.poll.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
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 logger = require('../../../../utils/logger');
const { parseExternalId } = require('../utils/parseExternalId');
const { NotFoundError } = require('../../../../utils/coreErrors');
const { getDeviceFeature } = require('../../../../utils/device');
const { getDeviceFeature, normalize } = require('../../../../utils/device');

/**
* @description Poll value of a Philips hue.
Expand All @@ -19,17 +20,58 @@ async function poll(device) {
throw new NotFoundError(`HUE_API_NOT_FOUND`);
}
const state = await hueApi.lights.getLightState(lightId);

// if the binary value is different from the value we have, save new state
const currentBinaryState = state.on ? 1 : 0;
const binaryFeature = getDeviceFeature(device, DEVICE_FEATURE_CATEGORIES.LIGHT, DEVICE_FEATURE_TYPES.LIGHT.BINARY);

// if the value is different from the value we have, save new state
if (binaryFeature && binaryFeature.last_value !== currentBinaryState) {
logger.debug(`Polling Philips Hue ${lightId}, new value = ${currentBinaryState}`);
logger.debug(`Polling Philips Hue ${lightId}, new binary value = ${currentBinaryState}`);
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${lightId}:${DEVICE_FEATURE_TYPES.LIGHT.BINARY}`,
state: currentBinaryState,
});
}

// if the color value is different from the value we have, save new state
let currentColorState;
switch (state.colormode) {
case 'ct':
case 'xy':
currentColorState = xyToInt(state.xy[0], state.xy[1]);
break;
case 'hs':
currentColorState = rgbToInt(hsbToRgb([state.hue, state.sat, state.bri]));
break;
default:
}
const colorFeature = getDeviceFeature(device, DEVICE_FEATURE_CATEGORIES.LIGHT, DEVICE_FEATURE_TYPES.LIGHT.COLOR);

if (colorFeature && colorFeature.last_value !== currentColorState) {
logger.debug(
`Polling Philips Hue ${lightId}, new color value = ${currentColorState} from color mode ${state.colormode}`,
);
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${lightId}:${DEVICE_FEATURE_TYPES.LIGHT.COLOR}`,
state: currentColorState,
});
}

// if the brightness value is different from the value we have, save new state
const brightnessColorState = state.bri;
const brightnessFeature = getDeviceFeature(
device,
DEVICE_FEATURE_CATEGORIES.LIGHT,
DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS,
);
const newBrightnessValue = Math.round(normalize(brightnessColorState, 0, 254, 0, 100));
if (brightnessFeature && brightnessFeature.last_value !== newBrightnessValue) {
logger.debug(`Polling Philips Hue ${lightId}, new brightness value = ${newBrightnessValue}`);
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${lightId}:${DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS}`,
state: newBrightnessValue,
});
}
}

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion server/services/philips-hue/lib/models/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getPhilipsHueColorLight = (philipsHueLight, bridgeSerialNumber, serviceId)
selector: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${philipsHueLight.id}`,
should_poll: true,
model: philipsHueLight.modelid,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS,
features: [
{
name: `${philipsHueLight.name} On/Off`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getPhilipsHueColorTemperatureLight = (philipsHueLight, bridgeSerialNumber,
selector: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${philipsHueLight.id}`,
should_poll: true,
model: philipsHueLight.modelid,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS,
features: [
{
name: `${philipsHueLight.name} On/Off`,
Expand Down
2 changes: 1 addition & 1 deletion server/services/philips-hue/lib/models/plugOnOff.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getPlugOnOff = (philipsHueLight, bridgeSerialNumber, serviceId) => ({
selector: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${philipsHueLight.id}`,
should_poll: true,
model: philipsHueLight.modelid,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS,
features: [
{
name: `${philipsHueLight.name} On/Off`,
Expand Down
2 changes: 1 addition & 1 deletion server/services/philips-hue/lib/models/white.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getPhilipsHueWhiteLight = (philipsHueLight, bridgeSerialNumber, serviceId)
selector: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${philipsHueLight.id}`,
should_poll: true,
model: philipsHueLight.modelid,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS,
features: [
{
name: `${philipsHueLight.name} On/Off`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getPhilipsHueWhiteTemperatureLight = (philipsHueLight, bridgeSerialNumber,
selector: `${LIGHT_EXTERNAL_ID_BASE}:${bridgeSerialNumber}:${philipsHueLight.id}`,
should_poll: true,
model: philipsHueLight.modelid,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_15_SECONDS,
features: [
{
name: `${philipsHueLight.name} On/Off`,
Expand Down
14 changes: 7 additions & 7 deletions server/services/philips-hue/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/services/philips-hue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"arm64"
],
"dependencies": {
"bluebird": "^3.7.0",
"bluebird": "^3.7.2",
"bottleneck": "^2.19.5",
"node-hue-api": "^4.0.11"
}
Expand Down
163 changes: 146 additions & 17 deletions server/test/services/philips-hue/light/light.poll.test.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
const { assert } = require('chai');
const { fake } = require('sinon');
const EventEmitter = require('events');
const { expect } = require('chai');
const { assert, fake } = require('sinon');
const proxyquire = require('proxyquire').noCallThru();
const { MockedPhilipsHueClient } = require('../mocks.test');
const { MockedPhilipsHueClient, hueApiHsColorMode } = require('../mocks.test');
const { EVENTS } = require('../../../../utils/constants');

const PhilipsHueService = proxyquire('../../../../services/philips-hue/index', {
'node-hue-api': MockedPhilipsHueClient,
});

const StateManager = require('../../../../lib/state');

const event = new EventEmitter();
const stateManager = new StateManager(event);
const deviceManager = {
get: fake.resolves([
{
Expand Down Expand Up @@ -59,13 +55,35 @@ const deviceManager = {
]),
};

const gladys = {
device: deviceManager,
stateManager,
event,
};
describe('PhilipsHueService Poll', () => {
let gladys;
let initialApi;

beforeEach(() => {
gladys = {
job: {
wrapper: (type, func) => {
return async () => {
return func();
};
},
},
event: {
emit: fake.resolves(null),
},
device: deviceManager,
stateManager: {
get: fake.resolves(true),
},
};
initialApi = MockedPhilipsHueClient.v3.api;
});

afterEach(() => {
// Reset API after each test to avoid bugs
MockedPhilipsHueClient.v3.api = initialApi;
});

describe('PhilipsHueService', () => {
it('should poll light', async () => {
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
await philipsHueService.device.init();
Expand All @@ -82,15 +100,126 @@ describe('PhilipsHueService', () => {
it('should return hue api not found', async () => {
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
await philipsHueService.device.init();
const promise = philipsHueService.device.poll({
external_id: 'light:not-found:1',
try {
await philipsHueService.device.poll({
external_id: 'light:not-found:1',
features: [
{
category: 'light',
type: 'binary',
},
],
});
expect.fail();
} catch (e) {
expect(e.message).eq('HUE_API_NOT_FOUND');
}
});
it('should poll light and update binary', async () => {
// PREPARE
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
await philipsHueService.device.init();
// EXECUTE
await philipsHueService.device.poll({
external_id: 'light:1234:1',
features: [
{
category: 'light',
type: 'binary',
},
],
});
return assert.isRejected(promise, 'HUE_API_NOT_FOUND');
// ASSERT
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:binary',
state: 0,
});
});
it('should poll light and update color for color mode xy', async () => {
// PREPARE
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
await philipsHueService.device.init();
// EXECUTE
await philipsHueService.device.poll({
external_id: 'light:1234:1',
features: [
{
category: 'light',
type: 'color',
},
],
});
// ASSERT
// Color is xy: [0.3321, 0.3605], so 16187362 in Int
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:color',
state: 16187362,
});
});
it('should poll light and update color for color mode hs', async () => {
// PREPARE
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
philipsHueService.device.hueClient.api = {
createLocal: () => ({
connect: () => hueApiHsColorMode,
}),
};
await philipsHueService.device.init();
// EXECUTE
await philipsHueService.device.poll({
external_id: 'light:1234:1',
features: [
{
category: 'light',
type: 'color',
},
],
});
// ASSERT
// Color is hsb: 67, so 16187362 in Int
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:color',
state: 11534095,
});
});
it('should poll light with mode ct and use xy', async () => {
// PREPARE
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
await philipsHueService.device.init();
// EXECUTE
await philipsHueService.device.poll({
external_id: 'light:1234:1',
features: [
{
category: 'light',
type: 'color',
},
],
});
// ASSERT
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:color',
state: 16187362,
});
});
it('should poll light and update bri', async () => {
// PREPARE
const philipsHueService = PhilipsHueService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279');
await philipsHueService.device.init();
// EXECUTE
await philipsHueService.device.poll({
external_id: 'light:1234:1',
features: [
{
category: 'light',
type: 'brightness',
},
],
});
// ASSERT
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'philips-hue-light:1234:1:brightness',
state: 22,
});
});
});
Loading

0 comments on commit 3854c30

Please sign in to comment.