mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-28 05:24:29 +00:00
Publish group state. #764
This commit is contained in:
+1
-2
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user