diff --git a/lib/extension/devicePublish.js b/lib/extension/devicePublish.js index dcc57d9e..ea5df68d 100644 --- a/lib/extension/devicePublish.js +++ b/lib/extension/devicePublish.js @@ -12,15 +12,13 @@ const maxDepth = 20; const groupConverters = [ { from: (converted) => { - return {state: converted.cmd.toUpperCase()}; + if (converted.cid === 'genOnOff') { + return {state: converted.cmd.toUpperCase()}; + } else if (converted.cid === 'genLevelCtrl') { + return {state: 'ON', brightness: Number(converted.zclData.level)}; + } }, - to: zigbeeShepherdConverters.toZigbeeConverters.on_off, - }, - { - from: (converted) => { - return {brightness: Number(converted.zclData.level)}; - }, - to: zigbeeShepherdConverters.toZigbeeConverters.light_brightness, + to: zigbeeShepherdConverters.toZigbeeConverters.light_onoff_brightness, }, { from: (converted) => { @@ -47,16 +45,6 @@ const groupConverters = [ }, ]; -const stateConverters = [ - zigbeeShepherdConverters.toZigbeeConverters.on_off, - zigbeeShepherdConverters.toZigbeeConverters.livolo_switch_on_off, -]; - -const brightnessConverters = [ - zigbeeShepherdConverters.toZigbeeConverters.gledopto_light_brightness, - zigbeeShepherdConverters.toZigbeeConverters.light_brightness, -]; - class DevicePublish { constructor(zigbee, mqtt, state, publishEntityState) { this.zigbee = zigbee; @@ -114,18 +102,20 @@ class DevicePublish { handlePublished(entity, topic, converter, converted) { if (entity.type === 'device' && topic.type === 'set') { // Devices do not report when they go off, this ensures state (on/off) is always in sync. - // Brightness converters also control the state. (do a moveToLevelWithOnOff) - if (stateConverters.includes(converter) || brightnessConverters.includes(converter)) { - const msg = {}; - const _key = topic.postfix ? `state_${topic.postfix}` : 'state'; + // Brightness onoff converters also control the state. (do a moveToLevelWithOnOff) + const msg = {}; + const _key = topic.postfix ? `state_${topic.postfix}` : 'state'; - if (stateConverters.includes(converter)) { - msg[_key] = converted.cmd.toUpperCase(); - } else if (brightnessConverters.includes(converter)) { - msg[_key] = 'ON'; - msg['brightness'] = Number(converted.zclData.level); - } + if (converted.cid === 'genOnOff') { + msg[_key] = converted.cmd.toUpperCase(); + } else if (converted.cid === 'genLevelCtrl' && converted.cmd === 'moveToLevelWithOnOff') { + msg[_key] = 'ON'; + msg['brightness'] = Number(converted.zclData.level); + } else if (converted.cid === 'genLevelCtrl' && converted.cmd === 'moveToLevel') { + msg['brightness'] = Number(converted.zclData.level); + } + if (Object.keys(msg).length > 0) { this.publishEntityState(entity.ID, msg, true); } } else if (entity.type === 'group' && topic.type === 'set') { @@ -191,19 +181,20 @@ class DevicePublish { json = {state: message.toString()}; } - // When brightness is present skip state; brightness also handles state. - if (json.hasOwnProperty('brightness') && json.hasOwnProperty('state')) { - logger.debug(`Skipping 'state' because of 'brightness'`); - delete json.state; - } - // Ensure that state and brightness are executed before other commands. const keys = Object.keys(json); keys.sort((a, b) => (['state', 'brightness'].includes(a) ? -1 : 1)); // For each key in the JSON message find the matching converter. + const usedConverters = []; keys.forEach((key) => { const converter = converters.find((c) => c.key.includes(key)); + + if (usedConverters.includes(converter)) { + // Use a converter only once (e.g. light_onoff_brightness converters can convert state and brightness) + return; + } + if (!converter) { logger.error(`No converter available for '${key}' (${json[key]})`); return; @@ -251,6 +242,8 @@ class DevicePublish { ); }, converted.readAfterWriteTime); } + + usedConverters.push(converter); }); return true; diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 37df1c28..eba2ed90 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -548,6 +548,8 @@ const mapping = { 'E1525': [configurations.binary_sensor_occupancy, configurations.sensor_battery], 'ZYCT-202': [configurations.sensor_action], 'GR-ZB01-W': [configurations.cover_position], + '4090531P7': [configurations.light_brightness_colortemp_colorxy], + 'HGZB-42-UK': [configurations.switch], }; Object.keys(mapping).forEach((key) => { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 45b8b43b..fd75a1be 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -235,21 +235,22 @@ } }, "@sinonjs/commons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", - "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.1.tgz", + "integrity": "sha512-rgmZk5CrBGAMATk0HlHOFvo8V44/r+On6cKS80tqid0Eljd+fFBWBOXZp9H2/EB3faxdNdzXTx6QZIKLkbJ7mA==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/formatio": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", - "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.0.tgz", + "integrity": "sha512-hskkZG4qB0HgsxrPUlnk2EiIyBwntM+ETIxCha/gidl172MCfdosNezB5706ciS5P2VhueM7MoACWwMc4A4gMQ==", "dev": true, "requires": { - "@sinonjs/samsam": "^2 || ^3" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, "@sinonjs/samsam": { @@ -1978,9 +1979,9 @@ "dev": true }, "eslint": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.14.1.tgz", - "integrity": "sha512-CyUMbmsjxedx8B0mr79mNOqetvkbij/zrXnFeK2zc3pGRn3/tibjiNAv/3UxFEyfMDjh+ZqTrJrEGBFiGfD5Og==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.0.tgz", + "integrity": "sha512-xwG7SS5JLeqkiR3iOmVgtF8Y6xPdtr6AAsN6ph7Q6R/fv+3UlKYoika8SmNzmb35qdRF+RfTY35kMEdtbi+9wg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1989,7 +1990,7 @@ "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.2", "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", "espree": "^5.0.1", @@ -2063,9 +2064,9 @@ } }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.2.tgz", + "integrity": "sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -5264,13 +5265,13 @@ } }, "sinon": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.5.tgz", - "integrity": "sha512-1c2KK6g5NQr9XNYCEcUbeFtBpKZD1FXEw0VX7gNhWUBtkchguT2lNdS7XmS7y64OpQWfSNeeV/f8py3NNcQ63Q==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.6.tgz", + "integrity": "sha512-n5r5wZ/VyrP7oAJpGGGjSxvw2Yy8T+qdGjDBltK01KBqB33O60V/xcG0Hgqm6SbJEIyCGuICpaXg9uFzY5DOYw==", "dev": true, "requires": { "@sinonjs/commons": "^1.3.0", - "@sinonjs/formatio": "^3.1.0", + "@sinonjs/formatio": "^3.2.0", "@sinonjs/samsam": "^3.2.0", "diff": "^3.5.0", "lolex": "^3.1.0", @@ -6135,9 +6136,9 @@ } }, "zigbee-shepherd-converters": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/zigbee-shepherd-converters/-/zigbee-shepherd-converters-7.1.12.tgz", - "integrity": "sha512-36Guhlqtm1u2alAjc5WrWkMdIcOLCgYfISuC+g+Y667QXYIfsfAMh+r5Ur07kYwziaOIn0RvD9Q4PanGFV+Ciw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/zigbee-shepherd-converters/-/zigbee-shepherd-converters-7.2.0.tgz", + "integrity": "sha512-NQ78Zz7T7cVQYoodXUSXgwIi/JpLtltvPf3Y76vrq4vV98jqujRUyVbLJ1u9fvQUvYtokw+chIHDrsI4ACuapg==", "requires": { "chai": "*", "debounce": "*", diff --git a/package.json b/package.json index a8762770..eb911004 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "winston": "2.4.2", "ziee": "*", "zigbee-shepherd": "git+https://github.com/Koenkk/zigbee-shepherd.git#dcd93b4f6133a8e356971fcfbfa9ea92732c6db6", - "zigbee-shepherd-converters": "7.1.12", + "zigbee-shepherd-converters": "7.2.0", "zive": "*" }, "devDependencies": { diff --git a/test/devicePublish.test.js b/test/devicePublish.test.js index c829b2c0..9573a319 100644 --- a/test/devicePublish.test.js +++ b/test/devicePublish.test.js @@ -50,7 +50,7 @@ describe('DevicePublish', () => { chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); - chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: '200', transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 200, transtime: 0}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); chai.assert.isTrue(publishEntityState.calledOnce); @@ -61,6 +61,7 @@ describe('DevicePublish', () => { it('Should publish messages to zigbee devices when brightness is in %', () => { zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({brightness_percent: '92'})); chai.assert.isTrue(zigbee.publish.calledOnce); @@ -69,13 +70,18 @@ describe('DevicePublish', () => { chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); - chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: '235', transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 235, transtime: 0}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 235}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); }); it('Should publish messages to zigbee devices when brightness is in number', () => { zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({brightness: 230})); chai.assert.isTrue(zigbee.publish.calledOnce); @@ -87,6 +93,10 @@ describe('DevicePublish', () => { chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 230, transtime: 0}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 230}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); }); it('Should publish messages to zigbee devices with color_temp', () => { @@ -158,6 +168,7 @@ describe('DevicePublish', () => { it('Should publish messages to zigbee gledopto with [11,13]', () => { zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); zigbee.getDevice = sinon.fake.returns({modelId: 'GLEDOPTO', epList: [11, 13]}); devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({state: 'OFF'})); chai.assert.isTrue(zigbee.publish.calledOnce); @@ -169,12 +180,17 @@ describe('DevicePublish', () => { chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.strictEqual(zigbee.publish.getCall(0).args[7], 11); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'OFF'}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); }); it('Should publish messages to zigbee gledopto with [11,12,13]', () => { zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); zigbee.getDevice = sinon.fake.returns({modelId: 'GLEDOPTO', epList: [11, 12, 13]}); - devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({state: 'OFF'})); + devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({state: 'OFF', brightness: 50})); chai.assert.isTrue(zigbee.publish.calledOnce); chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], '0x12345678'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'device'); @@ -184,6 +200,10 @@ describe('DevicePublish', () => { chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.strictEqual(zigbee.publish.getCall(0).args[7], 12); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'OFF'}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); }); it('Should publish messages to zigbee devices with color xy', () => { @@ -201,6 +221,122 @@ describe('DevicePublish', () => { chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); }); + it('Should publish messages to zigbee devices with color xy and state', () => { + zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); + zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); + const payload = {color: {x: 100, y: 50}, state: 'ON'}; + devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify(payload)); + chai.assert.isTrue(zigbee.publish.calledTwice); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genOnOff'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'on'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[2], 'lightingColorCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[3], 'moveToColor'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[5], {colorx: 6553500, colory: 3276750, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[6], cfg.default); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON'}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); + }); + + it('Should publish messages to zigbee devices with color xy and brightness', () => { + zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); + zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); + const payload = {color: {x: 100, y: 50}, brightness: 20}; + devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify(payload)); + chai.assert.isTrue(zigbee.publish.calledTwice); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 20, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[2], 'lightingColorCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[3], 'moveToColor'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[5], {colorx: 6553500, colory: 3276750, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[6], cfg.default); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 20}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); + }); + + it('Should publish messages to zigbee devices with color xy, brightness and state on', () => { + zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); + zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); + const payload = {color: {x: 100, y: 50}, brightness: 20, state: 'oN'}; + devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify(payload)); + chai.assert.isTrue(zigbee.publish.calledTwice); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 20, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[2], 'lightingColorCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[3], 'moveToColor'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[5], {colorx: 6553500, colory: 3276750, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[6], cfg.default); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 20}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); + }); + + it('Should publish messages to zigbee devices with color xy, brightness and state off', () => { + zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); + zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); + const payload = {color: {x: 100, y: 50}, brightness: 20, state: 'oFF'}; + devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify(payload)); + chai.assert.isTrue(zigbee.publish.calledTwice); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genOnOff'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'off'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[0], '0x12345678'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[1], 'device'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[2], 'lightingColorCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[3], 'moveToColor'); + chai.assert.strictEqual(zigbee.publish.getCall(1).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[5], {colorx: 6553500, colory: 3276750, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[6], cfg.default); + chai.assert.deepEqual(zigbee.publish.getCall(1).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'OFF'}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); + }); + it('Should publish messages to zigbee devices with color rgb', () => { zigbee.publish.resetHistory(); zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); @@ -233,6 +369,7 @@ describe('DevicePublish', () => { it('Should publish 1 message when brightness with state is send', () => { zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 CWS opal 600lm'}); devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({state: 'ON', brightness: '50'})); chai.assert.isTrue(zigbee.publish.calledOnce); @@ -241,9 +378,13 @@ describe('DevicePublish', () => { chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); - chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: '50', transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 50, transtime: 0}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678'); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 50}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); }); it('Should publish messages to groups', () => { @@ -277,12 +418,52 @@ describe('DevicePublish', () => { chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); - chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: '127', transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 127, transtime: 0}); chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); chai.assert.isTrue(publishEntityState.calledOnce); chai.assert.strictEqual(publishEntityState.getCall(0).args[0], 1); - chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {brightness: 127}); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 127}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); + }); + + it('Should publish messages to groups with on and brightness', () => { + sandbox.stub(settings, 'getGroupIDByFriendlyName').callsFake(() => '1'); + zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); + devicePublish.onMQTTMessage('zigbee2mqtt/group/group_1/set', JSON.stringify({state: 'ON', brightness: 50})); + chai.assert.isTrue(zigbee.publish.calledOnce); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], 1); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'group'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genLevelCtrl'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'moveToLevelWithOnOff'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {level: 50, transtime: 0}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], 1); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'ON', brightness: 50}); + chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); + }); + + it('Should publish messages to groups with off and brightness', () => { + sandbox.stub(settings, 'getGroupIDByFriendlyName').callsFake(() => '1'); + zigbee.publish.resetHistory(); + publishEntityState.resetHistory(); + devicePublish.onMQTTMessage('zigbee2mqtt/group/group_1/set', JSON.stringify({state: 'OFF', brightness: 5})); + chai.assert.isTrue(zigbee.publish.calledOnce); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], 1); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[1], 'group'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[2], 'genOnOff'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[3], 'off'); + chai.assert.strictEqual(zigbee.publish.getCall(0).args[4], 'functional'); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {}); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[6], cfg.default); + chai.assert.deepEqual(zigbee.publish.getCall(0).args[7], null); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.strictEqual(publishEntityState.getCall(0).args[0], 1); + chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state: 'OFF'}); chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true); }); });