diff --git a/docs/USAGE.md b/docs/USAGE.md index 0f44d6be..1e133a58 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -66,17 +66,17 @@ Dimmer { ga="Light" } Color { ga="Light" [ colorTemperatureRange="2000,9000" ] } ``` -#### `Light as Group with separate Color and Brightness` +#### `Light as Group with separate Controls` | | | |---|---| | **Device Type** | [Light](https://developers.google.com/assistant/smarthome/guides/light) | | **Supported Traits** | [OnOff](https://developers.google.com/assistant/smarthome/traits/onoff), [ColorSetting](https://developers.google.com/assistant/smarthome/traits/colorsetting), [Brightness](https://developers.google.com/assistant/smarthome/traits/brightness) | -| **Supported Items** | Group as `light` with the following members: (optional) Number or Dimmer as `lightBrightness`, (optional) Number or Dimmer as `lightColorTemperature`, (optional) Color as `lightColor`, (optional) Switch as `lightPower` | -| **Configuration** | (optional) `useKelvin=true/false`
(optional) `checkState=true/false`
(optional) `colorTemperatureRange="minK,maxK"`
_Hint: if you want to use `lightColorTemperature` you either need to set `useKelvin=true` or `colorTemperatureRange`_ | +| **Supported Items** | Group as `SpecialColorLight` with the following members: (optional) Number or Dimmer as `lightBrightness`, (optional) Number or Dimmer as `lightColorTemperature`, (optional) Color as `lightColor`, (optional) Switch as `lightPower` | +| **Configuration** | (optional) `colorUnit=percent/kelvin/mired`
(optional) `checkState=true/false`
(optional) `colorTemperatureRange="minK,maxK"`
_Hint: if you want to use `lightColorTemperature` you either need to set `colorUnit` to `kelvin` or `mired` or define a `colorTemperatureRange` as `colorUnit` defaults to `percent`_ | ```shell -Group lightGroup { ga="Light" [ useKelvin=true, colorTemperatureRange="2000,9000" ] } +Group lightGroup { ga="SpecialColorLight" [ colorUnit="kelvin", colorTemperatureRange="2000,9000" ] } Switch powerItem (lightGroup) { ga="lightPower" } Dimmer brightnessItem (lightGroup) { ga="lightBrightness" } Color colorItem (lightGroup) { ga="lightColor" } diff --git a/functions/commands/colorabsolutetemperature.js b/functions/commands/colorabsolutetemperature.js index 93298381..45476642 100644 --- a/functions/commands/colorabsolutetemperature.js +++ b/functions/commands/colorabsolutetemperature.js @@ -1,7 +1,6 @@ const DefaultCommand = require('./default.js'); const SpecialColorLight = require('../devices/specialcolorlight.js'); -const rgb2hsv = require('../utilities.js').rgb2hsv; -const kelvin2rgb = require('../utilities.js').kelvin2rgb; +const { convertMired, convertRgbToHsv, convertKelvinToRgb } = require('../utilities.js'); class ColorAbsoluteTemperature extends DefaultCommand { static get type() { @@ -35,9 +34,13 @@ class ColorAbsoluteTemperature extends DefaultCommand { static convertParamsToValue(params, item, device) { if (this.getDeviceType(device) === 'SpecialColorLight') { try { - if (SpecialColorLight.useKelvin(item)) { + const colorUnit = SpecialColorLight.getColorUnit(item); + if (colorUnit === 'kelvin') { return params.color.temperature.toString(); } + if (colorUnit === 'mired') { + return convertMired(params.color.temperature).toString(); + } const { temperatureMinK, temperatureMaxK } = SpecialColorLight.getAttributes(item).colorTemperatureRange; return ( 100 - @@ -47,7 +50,7 @@ class ColorAbsoluteTemperature extends DefaultCommand { return '0'; } } - const hsv = rgb2hsv(kelvin2rgb(params.color.temperature)); + const hsv = convertRgbToHsv(convertKelvinToRgb(params.color.temperature)); const hsvArray = item.state.split(',').map((val) => Number(val)); return [Math.round(hsv.hue * 100) / 100, Math.round(hsv.saturation * 1000) / 10, hsvArray[2]].join(','); } diff --git a/functions/commands/thermostattemperaturesetpoint.js b/functions/commands/thermostattemperaturesetpoint.js index 7fba7407..ffff3131 100644 --- a/functions/commands/thermostattemperaturesetpoint.js +++ b/functions/commands/thermostattemperaturesetpoint.js @@ -1,6 +1,6 @@ const DefaultCommand = require('./default.js'); const Thermostat = require('../devices/thermostat.js'); -const convertToFahrenheit = require('../utilities.js').convertToFahrenheit; +const convertCelsiusToFahrenheit = require('../utilities.js').convertCelsiusToFahrenheit; class ThermostatTemperatureSetpoint extends DefaultCommand { static get type() { @@ -26,7 +26,7 @@ class ThermostatTemperatureSetpoint extends DefaultCommand { static convertParamsToValue(params, item) { let value = params.thermostatTemperatureSetpoint; if (Thermostat.useFahrenheit(item)) { - value = convertToFahrenheit(value); + value = convertCelsiusToFahrenheit(value); } return value.toString(); } diff --git a/functions/commands/thermostattemperaturesetpointhigh.js b/functions/commands/thermostattemperaturesetpointhigh.js index ec659bda..610563e8 100644 --- a/functions/commands/thermostattemperaturesetpointhigh.js +++ b/functions/commands/thermostattemperaturesetpointhigh.js @@ -1,6 +1,6 @@ const DefaultCommand = require('./default.js'); const Thermostat = require('../devices/thermostat.js'); -const convertToFahrenheit = require('../utilities.js').convertToFahrenheit; +const convertCelsiusToFahrenheit = require('../utilities.js').convertCelsiusToFahrenheit; class ThermostatTemperatureSetpointHigh extends DefaultCommand { static get type() { @@ -28,7 +28,7 @@ class ThermostatTemperatureSetpointHigh extends DefaultCommand { static convertParamsToValue(params, item) { let value = params.thermostatTemperatureSetpointHigh; if (Thermostat.useFahrenheit(item)) { - value = convertToFahrenheit(value); + value = convertCelsiusToFahrenheit(value); } return value.toString(); } diff --git a/functions/commands/thermostattemperaturesetpointlow.js b/functions/commands/thermostattemperaturesetpointlow.js index 1327fa9f..cb36a7a7 100644 --- a/functions/commands/thermostattemperaturesetpointlow.js +++ b/functions/commands/thermostattemperaturesetpointlow.js @@ -1,6 +1,6 @@ const DefaultCommand = require('./default.js'); const Thermostat = require('../devices/thermostat.js'); -const convertToFahrenheit = require('../utilities.js').convertToFahrenheit; +const convertCelsiusToFahrenheit = require('../utilities.js').convertCelsiusToFahrenheit; class ThermostatTemperatureSetpointLow extends DefaultCommand { static get type() { @@ -26,7 +26,7 @@ class ThermostatTemperatureSetpointLow extends DefaultCommand { static convertParamsToValue(params, item) { let value = params.thermostatTemperatureSetpointLow; if (Thermostat.useFahrenheit(item)) { - value = convertToFahrenheit(value); + value = convertCelsiusToFahrenheit(value); } return value.toString(); } diff --git a/functions/devices/specialcolorlight.js b/functions/devices/specialcolorlight.js index 24d48246..c765244e 100644 --- a/functions/devices/specialcolorlight.js +++ b/functions/devices/specialcolorlight.js @@ -1,4 +1,5 @@ const DefaultDevice = require('./default.js'); +const convertMired = require('../utilities.js').convertMired; class SpecialColorLight extends DefaultDevice { static get type() { @@ -9,13 +10,17 @@ class SpecialColorLight extends DefaultDevice { return ['action.devices.traits.OnOff', 'action.devices.traits.Brightness', 'action.devices.traits.ColorSetting']; } + static isCompatible(item = {}) { + return item.metadata && item.metadata.ga && item.metadata.ga.value.toLowerCase() == 'specialcolorlight'; + } + static matchesItemType(item) { const members = this.getMembers(item); return ( item.type === 'Group' && Object.keys(members).length > 1 && (!('lightColorTemperature' in members) || - this.useKelvin(item) || + this.getColorUnit(item) !== 'percent' || !!this.getAttributes(item).colorTemperatureRange) ); } @@ -30,9 +35,10 @@ class SpecialColorLight extends DefaultDevice { if ('colorTemperatureRange' in config) { const [min, max] = config.colorTemperatureRange.split(',').map((s) => Number(s.trim())); if (!isNaN(min) && !isNaN(max)) { + const colorUnit = this.getColorUnit(item); attributes.colorTemperatureRange = { - temperatureMinK: min, - temperatureMaxK: max + temperatureMinK: colorUnit === 'mired' ? convertMired(max) : min, + temperatureMaxK: colorUnit === 'mired' ? convertMired(min) : max }; } } @@ -74,10 +80,15 @@ class SpecialColorLight extends DefaultDevice { break; } try { - if (this.useKelvin(item)) { + const colorUnit = this.getColorUnit(item); + if (colorUnit === 'kelvin') { state.color = { temperatureK: Number(members[member].state) }; + } else if (colorUnit === 'mired') { + state.color = { + temperatureK: convertMired(Number(members[member].state)) + }; } else { const { temperatureMinK, temperatureMaxK } = this.getAttributes(item).colorTemperatureRange; state.color = { @@ -111,8 +122,9 @@ class SpecialColorLight extends DefaultDevice { return members; } - static useKelvin(item) { - return this.getConfig(item).useKelvin === true; + static getColorUnit(item) { + const colorUnit = this.getConfig(item).colorUnit || 'percent'; + return colorUnit.toLowerCase(); } } diff --git a/functions/devices/temperaturesensor.js b/functions/devices/temperaturesensor.js index 7e58d911..84e9f467 100644 --- a/functions/devices/temperaturesensor.js +++ b/functions/devices/temperaturesensor.js @@ -1,5 +1,5 @@ const DefaultDevice = require('./default.js'); -const convertToCelsius = require('../utilities.js').convertToCelsius; +const convertFahrenheitToCelsius = require('../utilities.js').convertFahrenheitToCelsius; class TemperatureSensor extends DefaultDevice { static get type() { @@ -28,7 +28,7 @@ class TemperatureSensor extends DefaultDevice { static getState(item) { let state = Number(parseFloat(item.state).toFixed(1)); if (this.getConfig(item).useFahrenheit === true) { - state = convertToCelsius(state); + state = convertFahrenheitToCelsius(state); } return { temperatureSetpointCelsius: state, diff --git a/functions/devices/thermostat.js b/functions/devices/thermostat.js index a70931e1..a9aba5df 100644 --- a/functions/devices/thermostat.js +++ b/functions/devices/thermostat.js @@ -1,5 +1,5 @@ const DefaultDevice = require('./default.js'); -const convertToCelsius = require('../utilities.js').convertToCelsius; +const convertFahrenheitToCelsius = require('../utilities.js').convertFahrenheitToCelsius; class Thermostat extends DefaultDevice { static get type() { @@ -50,7 +50,7 @@ class Thermostat extends DefaultDevice { } else { state[member] = Number(parseFloat(members[member].state).toFixed(1)); if (member.indexOf('Temperature') > 0 && this.useFahrenheit(item)) { - state[member] = convertToCelsius(state[member]); + state[member] = convertFahrenheitToCelsius(state[member]); } } } diff --git a/functions/utilities.js b/functions/utilities.js index 5b981312..2f439ccb 100644 --- a/functions/utilities.js +++ b/functions/utilities.js @@ -23,22 +23,22 @@ module.exports = { * @param {number} value temperature in Fahrenheit * @returns {number} temperature value converted to Celsius */ - convertToCelsius: (value) => { + convertFahrenheitToCelsius: (value) => { return Number((((value - 32) * 5) / 9).toFixed(1)); }, /** * @param {number} value temperature in Celsius * @returns {number} temperature value converted to Fahrenheit */ - convertToFahrenheit: (value) => { + convertCelsiusToFahrenheit: (value) => { return Math.round((value * 9) / 5 + 32); }, /** - * @param {number} kelvin color temperature as Kelvin + * @param {number} value color temperature as Kelvin * @returns {object} color temperature value converted to RGB */ - kelvin2rgb: (kelvin) => { - const temp = kelvin / 100; + convertKelvinToRgb: (value) => { + const temp = value / 100; const r = temp <= 66 ? 255 : 329.698727446 * Math.pow(temp - 60, -0.1332047592); const g = temp <= 66 @@ -51,11 +51,18 @@ module.exports = { b: b < 0 ? 0 : b > 255 ? 255 : Math.round(b) }; }, + /** + * @param {number} value color temperature as Kelvin or Mired + * @returns {number} color temperature value converted to Mired or Kelvin + */ + convertMired: (value) => { + return Math.round(Math.pow(10, 6) / value); + }, /** * @param {object} rgb color as RGB * @returns {object} color value converted to HSV */ - rgb2hsv: ({ r, g, b }) => { + convertRgbToHsv: ({ r, g, b }) => { r = r / 255; g = g / 255; b = b / 255; diff --git a/tests/commands/colorabsolutetemperature.test.js b/tests/commands/colorabsolutetemperature.test.js index 3e94b700..6083e6b5 100644 --- a/tests/commands/colorabsolutetemperature.test.js +++ b/tests/commands/colorabsolutetemperature.test.js @@ -63,7 +63,7 @@ describe('ColorAbsoluteTemperature Command', () => { metadata: { ga: { config: { - useKelvin: true + colorUnit: 'kelvin' } } } @@ -71,6 +71,20 @@ describe('ColorAbsoluteTemperature Command', () => { const device = { customData: { deviceType: 'SpecialColorLight' } }; expect(Command.convertParamsToValue(params, item, device)).toBe('2000'); }); + + test('convertParamsToValue SpecialColorLight Mired', () => { + const item = { + metadata: { + ga: { + config: { + colorUnit: 'mired' + } + } + } + }; + const device = { customData: { deviceType: 'SpecialColorLight' } }; + expect(Command.convertParamsToValue(params, item, device)).toBe('500'); + }); }); test('getResponseStates', () => { diff --git a/tests/devices/specialcolorlight.test.js b/tests/devices/specialcolorlight.test.js index 2bc697c3..1e861845 100644 --- a/tests/devices/specialcolorlight.test.js +++ b/tests/devices/specialcolorlight.test.js @@ -6,7 +6,7 @@ describe('SpecialColorLight Device', () => { Device.isCompatible({ metadata: { ga: { - value: 'LIGHT' + value: 'SpecialColorLight' } } }) @@ -14,7 +14,7 @@ describe('SpecialColorLight Device', () => { }); test('matchesItemType', () => { - const item = { + const item1 = { type: 'Group', metadata: { ga: { @@ -71,7 +71,7 @@ describe('SpecialColorLight Device', () => { ga: { value: 'LIGHT', config: { - useKelvin: true + colorUnit: 'kelvin' } } }, @@ -164,12 +164,40 @@ describe('SpecialColorLight Device', () => { } ] }; - expect(Device.matchesItemType(item)).toBe(true); + const item7 = { + type: 'Group', + metadata: { + ga: { + value: 'LIGHT', + config: { + colorUnit: 'mired' + } + } + }, + members: [ + { + metadata: { + ga: { + value: 'lightBrightness' + } + } + }, + { + metadata: { + ga: { + value: 'lightColorTemperature' + } + } + } + ] + }; + expect(Device.matchesItemType(item1)).toBe(true); expect(Device.matchesItemType(item2)).toBe(false); expect(Device.matchesItemType(item3)).toBe(true); expect(Device.matchesItemType(item4)).toBe(true); expect(Device.matchesItemType(item5)).toBe(true); expect(Device.matchesItemType(item6)).toBe(false); + expect(Device.matchesItemType(item7)).toBe(true); expect(Device.matchesItemType({ type: 'Color' })).toBe(false); expect(Device.matchesItemType({ type: 'Group', groupType: 'Color' })).toBe(false); expect(Device.matchesItemType({ type: 'Group', groupType: 'Dimmer' })).toBe(false); @@ -207,6 +235,25 @@ describe('SpecialColorLight Device', () => { expect(Device.getAttributes(item1)).toStrictEqual({}); }); + test('getAttributes colorTemperatureRange with mired', () => { + const item = { + metadata: { + ga: { + config: { + colorUnit: 'mired', + colorTemperatureRange: '250,454' + } + } + } + }; + expect(Device.getAttributes(item)).toStrictEqual({ + colorTemperatureRange: { + temperatureMinK: 2203, + temperatureMaxK: 4000 + } + }); + }); + test('getAttributes color', () => { const item = { metadata: { @@ -283,7 +330,7 @@ describe('SpecialColorLight Device', () => { ga: { value: 'LIGHT', config: { - useKelvin: true + colorUnit: 'kelvin' } } }, @@ -315,20 +362,20 @@ describe('SpecialColorLight Device', () => { }); }); - test('getState zero brightness', () => { + test('getState mired', () => { const item = { type: 'Group', metadata: { ga: { value: 'LIGHT', config: { - colorTemperatureRange: '1000,4000' + colorUnit: 'mired' } } }, members: [ { - state: '0', + state: '50', metadata: { ga: { value: 'lightBrightness' @@ -336,7 +383,7 @@ describe('SpecialColorLight Device', () => { } }, { - state: '20', + state: '200', metadata: { ga: { value: 'lightColorTemperature' @@ -346,29 +393,28 @@ describe('SpecialColorLight Device', () => { ] }; expect(Device.getState(item)).toStrictEqual({ - on: false, - brightness: 0, + on: true, + brightness: 50, color: { - temperatureK: 3400 + temperatureK: 5000 } }); }); - test('getState use kelvin', () => { + test('getState zero brightness', () => { const item = { type: 'Group', metadata: { ga: { value: 'LIGHT', config: { - colorTemperatureRange: '1000,4000', - useKelvin: true + colorTemperatureRange: '1000,4000' } } }, members: [ { - state: '50', + state: '0', metadata: { ga: { value: 'lightBrightness' @@ -376,7 +422,7 @@ describe('SpecialColorLight Device', () => { } }, { - state: '2000', + state: '20', metadata: { ga: { value: 'lightColorTemperature' @@ -386,10 +432,10 @@ describe('SpecialColorLight Device', () => { ] }; expect(Device.getState(item)).toStrictEqual({ - on: true, - brightness: 50, + on: false, + brightness: 0, color: { - temperatureK: 2000 + temperatureK: 3400 } }); }); diff --git a/tests/utilities.test.js b/tests/utilities.test.js index 549d7895..d008df8d 100644 --- a/tests/utilities.test.js +++ b/tests/utilities.test.js @@ -1,27 +1,36 @@ const Utilities = require('../functions/utilities.js'); describe('Utilities', () => { - test('convertToCelsius', () => { - expect(Utilities.convertToCelsius(10.0)).toEqual(-12.2); - expect(Utilities.convertToCelsius(0.0)).toEqual(-17.8); + test('convertFahrenheitToCelsius', () => { + expect(Utilities.convertFahrenheitToCelsius(10.0)).toEqual(-12.2); + expect(Utilities.convertFahrenheitToCelsius(0.0)).toEqual(-17.8); }); - test('convertToFahrenheit', () => { - expect(Utilities.convertToFahrenheit(10.0)).toEqual(50); - expect(Utilities.convertToFahrenheit(0.0)).toEqual(32); + test('convertCelsiusToFahrenheit', () => { + expect(Utilities.convertCelsiusToFahrenheit(10.0)).toEqual(50); + expect(Utilities.convertCelsiusToFahrenheit(0.0)).toEqual(32); }); - test('kelvin2rgb', () => { - expect(Utilities.kelvin2rgb(2000)).toStrictEqual({ b: 14, g: 137, r: 255 }); - expect(Utilities.kelvin2rgb(5900)).toEqual({ b: 234, g: 244, r: 255 }); - expect(Utilities.kelvin2rgb(9000)).toEqual({ b: 255, g: 223, r: 210 }); + test('convertKelvinToRgb', () => { + expect(Utilities.convertKelvinToRgb(2000)).toStrictEqual({ b: 14, g: 137, r: 255 }); + expect(Utilities.convertKelvinToRgb(5900)).toEqual({ b: 234, g: 244, r: 255 }); + expect(Utilities.convertKelvinToRgb(9000)).toEqual({ b: 255, g: 223, r: 210 }); }); - test('rgb2hsv', () => { - expect(Utilities.rgb2hsv({ r: 50, g: 10, b: 100 })).toStrictEqual({ hue: 266.67, saturation: 0.9, value: 0.39 }); - expect(Utilities.rgb2hsv({ r: 0, g: 0, b: 0 })).toStrictEqual({ hue: 0, saturation: 0, value: 0 }); - expect(Utilities.rgb2hsv({ r: 255, g: 0, b: 0 })).toStrictEqual({ hue: 0, saturation: 1, value: 1 }); - expect(Utilities.rgb2hsv({ r: 0, g: 255, b: 0 })).toStrictEqual({ hue: 120, saturation: 1, value: 1 }); - expect(Utilities.rgb2hsv({ r: 0, g: 0, b: 255 })).toStrictEqual({ hue: 240, saturation: 1, value: 1 }); + test('convertKelvinToMired', () => { + expect(Utilities.convertMired(3400)).toBe(294); + expect(Utilities.convertMired(294)).toBe(3401); + }); + + test('convertRgbToHsv', () => { + expect(Utilities.convertRgbToHsv({ r: 50, g: 10, b: 100 })).toStrictEqual({ + hue: 266.67, + saturation: 0.9, + value: 0.39 + }); + expect(Utilities.convertRgbToHsv({ r: 0, g: 0, b: 0 })).toStrictEqual({ hue: 0, saturation: 0, value: 0 }); + expect(Utilities.convertRgbToHsv({ r: 255, g: 0, b: 0 })).toStrictEqual({ hue: 0, saturation: 1, value: 1 }); + expect(Utilities.convertRgbToHsv({ r: 0, g: 255, b: 0 })).toStrictEqual({ hue: 120, saturation: 1, value: 1 }); + expect(Utilities.convertRgbToHsv({ r: 0, g: 0, b: 255 })).toStrictEqual({ hue: 240, saturation: 1, value: 1 }); }); });