Publish group state. #764

This commit is contained in:
Koen Kanters
2019-02-14 18:13:51 +01:00
parent f171d54e4b
commit 17a6fff3ce
4 changed files with 122 additions and 15 deletions
+1 -2
View File
@@ -242,7 +242,6 @@ class Controller {
}
const entitySettings = entity.type === 'device' ? settings.getDevice(entityID) : settings.getGroup(entityID);
const friendlyName = entitySettings ? entitySettings.friendly_name : entityID;
const options = {
retain: entitySettings ? entitySettings.retain : false,
qos: entitySettings && entitySettings.qos ? entitySettings.qos : 0,
@@ -252,7 +251,7 @@ class Controller {
messagePayload.device = this.getDeviceInfoForMqtt(entityID);
}
this.mqtt.publish(friendlyName, JSON.stringify(messagePayload), options);
this.mqtt.publish(entity.friendlyName, JSON.stringify(messagePayload), options);
}
getDeviceInfoForMqtt(ieeeAddr) {
+71 -11
View File
@@ -9,11 +9,51 @@ const postfixes = ['left', 'right', 'center', 'bottom_left', 'bottom_right', 'to
const maxDepth = 20;
const groupConverters = [
{
from: (converted) => {
return {state: converted.cmd.toUpperCase()};
},
to: zigbeeShepherdConverters.toZigbeeConverters.on_off,
},
{
from: (converted) => {
return {brightness: Number(converted.zclData.level)};
},
to: zigbeeShepherdConverters.toZigbeeConverters.light_brightness,
},
{
from: (converted) => {
return {color_temp: Number(converted.zclData.colortemp)};
},
to: zigbeeShepherdConverters.toZigbeeConverters.light_colortemp,
},
{
from: (converted) => {
if (converted.zclData.hasOwnProperty('colorx') && converted.zclData.hasOwnProperty('colory')) {
return {
color: {
x: converted.zclData.colorx / 65535,
y: converted.zclData.colory / 65535,
},
};
}
},
to: zigbeeShepherdConverters.toZigbeeConverters.light_color,
},
{
from: () => null,
to: zigbeeShepherdConverters.toZigbeeConverters.ignore_transition,
},
];
const stateConverters = [
zigbeeShepherdConverters.toZigbeeConverters.on_off,
zigbeeShepherdConverters.toZigbeeConverters.livolo_switch_on_off,
];
const brightnessConverters = [
zigbeeShepherdConverters.toZigbeeConverters.gledopto_light_brightness,
zigbeeShepherdConverters.toZigbeeConverters.light_brightness,
zigbeeShepherdConverters.toZigbeeConverters.light_colortemp,
zigbeeShepherdConverters.toZigbeeConverters.light_color,
zigbeeShepherdConverters.toZigbeeConverters.ignore_transition,
];
class DevicePublish {
@@ -61,6 +101,31 @@ class DevicePublish {
return {type: type, ID: ID, postfix: postfix};
}
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';
if (stateConverters.includes(converter)) {
msg[_key] = converted.cmd.toUpperCase();
} else if (brightnessConverters.includes(converter)) {
msg[_key] = 'ON';
}
this.publishEntityState(entity.ID, msg, true);
}
} else if (entity.type === 'group' && topic.type === 'set') {
// As a group doesn't confirm it's state, we mock the state here.
const payload = groupConverters.find((g) => g.to === converter).from(converted);
if (payload) {
this.publishEntityState(entity.ID, payload, true);
}
}
}
onMQTTMessage(topic, message) {
topic = this.parseTopic(topic);
@@ -99,7 +164,7 @@ class DevicePublish {
converters = model.toZigbee;
} else if (entity.type === 'group') {
converters = groupConverters;
converters = groupConverters.map((g) => g.to);
}
// Convert the MQTT message to a Zigbee message.
@@ -145,13 +210,8 @@ class DevicePublish {
converted.cfg,
endpoint,
(error, rsp) => {
// Devices do not report when they go off, this ensures state (on/off) is always in sync.
if (entity.type === 'device' && topic.type === 'set' &&
!error && (key.startsWith('state') || key.startsWith('brightness'))) {
const msg = {};
const _key = topic.postfix ? `state_${topic.postfix}` : 'state';
msg[_key] = key.startsWith('brightness') ? 'ON' : json['state'];
this.publishEntityState(device.ieeeAddr, msg, true);
if (!error) {
this.handlePublished(entity, topic, converter, converted);
}
}
);
+9 -1
View File
@@ -7,21 +7,29 @@ const ikeaTradfriManufacturerID = [4476];
// An entity can be either a group or a device.
function resolveEntity(ID) {
let type = null;
let friendlyName = null;
if (settings.getIeeeAddrByFriendlyName(ID)) {
// Check if the ID is a friendly_name of a device.
friendlyName = ID;
ID = settings.getIeeeAddrByFriendlyName(ID);
type = 'device';
} else if (settings.getGroupIDByFriendlyName(ID)) {
// Check if the ID is a friendly_name of a group.
friendlyName = ID;
ID = Number(settings.getGroupIDByFriendlyName(ID));
type = 'group';
} else if (settings.getGroup(ID)) {
friendlyName = settings.getGroup(ID).friendly_name;
ID = Number(ID);
type = 'group';
} else {
// By default it is a device with ID as ID.
type = 'device';
friendlyName = ID;
}
return {ID: ID, type: type};
return {ID: ID, type: type, friendlyName: friendlyName};
}
module.exports = {
+41 -1
View File
@@ -16,6 +16,9 @@ const zigbee = {
}),
};
const publishEntityState = sandbox.stub().callsFake((entityID, payload, cache) => {
});
const cfg = {
default: {
manufSpec: 0,
@@ -28,7 +31,7 @@ describe('DevicePublish', () => {
beforeEach(() => {
utils.stubLogger(sandbox);
devicePublish = new DevicePublish(zigbee, mqtt, null, () => {});
devicePublish = new DevicePublish(zigbee, mqtt, null, publishEntityState);
});
afterEach(() => {
@@ -38,6 +41,7 @@ describe('DevicePublish', () => {
describe('Parse topic', () => {
it('Should publish messages to zigbee devices', () => {
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: '200'}));
chai.assert.isTrue(zigbee.publish.calledOnce);
@@ -49,6 +53,10 @@ describe('DevicePublish', () => {
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);
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 when brightness is in %', () => {
@@ -97,6 +105,7 @@ describe('DevicePublish', () => {
});
it('Should publish messages to zigbee devices with color_temp in %', () => {
publishEntityState.resetHistory();
zigbee.publish.resetHistory();
zigbee.getDevice = sinon.fake.returns({modelId: 'TRADFRI bulb E27 WS opal 980lm'});
devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/set', JSON.stringify({color_temp_percent: '100'}));
@@ -109,6 +118,7 @@ describe('DevicePublish', () => {
chai.assert.deepEqual(zigbee.publish.getCall(0).args[5], {colortemp: '500', 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.notCalled);
});
it('Should publish messages to zigbee devices with non-default ep', () => {
@@ -128,6 +138,7 @@ describe('DevicePublish', () => {
it('Should publish messages to zigbee devices with non-default ep and postfix', () => {
zigbee.publish.resetHistory();
publishEntityState.resetHistory();
zigbee.getDevice = sinon.fake.returns({modelId: 'lumi.ctrl_neutral2'});
devicePublish.onMQTTMessage('zigbee2mqtt/0x12345678/right/set', JSON.stringify({state: 'OFF'}));
chai.assert.isTrue(zigbee.publish.calledOnce);
@@ -139,6 +150,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], 3);
chai.assert.isTrue(publishEntityState.calledOnce);
chai.assert.strictEqual(publishEntityState.getCall(0).args[0], '0x12345678');
chai.assert.deepEqual(publishEntityState.getCall(0).args[1], {state_right: 'OFF'});
chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true);
});
it('Should publish messages to zigbee gledopto with [11,13]', () => {
@@ -234,6 +249,7 @@ describe('DevicePublish', () => {
it('Should publish messages to groups', () => {
sandbox.stub(settings, 'getGroupIDByFriendlyName').callsFake(() => '1');
zigbee.publish.resetHistory();
publishEntityState.resetHistory();
devicePublish.onMQTTMessage('zigbee2mqtt/group/group_1/set', JSON.stringify({state: 'ON'}));
chai.assert.isTrue(zigbee.publish.calledOnce);
chai.assert.strictEqual(zigbee.publish.getCall(0).args[0], 1);
@@ -244,6 +260,30 @@ describe('DevicePublish', () => {
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: 'ON'});
chai.assert.strictEqual(publishEntityState.getCall(0).args[2], true);
});
it('Should publish messages to groups with brightness_percent', () => {
sandbox.stub(settings, 'getGroupIDByFriendlyName').callsFake(() => '1');
zigbee.publish.resetHistory();
publishEntityState.resetHistory();
devicePublish.onMQTTMessage('zigbee2mqtt/group/group_1/set', JSON.stringify({brightness_percent: 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: '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.strictEqual(publishEntityState.getCall(0).args[2], true);
});
});