2018-11-16 12:23:11 -07:00
|
|
|
const settings = require('../util/settings');
|
|
|
|
const logger = require('../util/logger');
|
2019-03-02 08:47:36 -07:00
|
|
|
const utils = require('../util/utils');
|
2018-11-16 12:23:11 -07:00
|
|
|
|
2019-02-19 11:31:58 -07:00
|
|
|
const dontCacheProperties = [
|
|
|
|
'action', 'button', 'button_left', 'button_right', 'click', 'forgotten', 'keyerror',
|
2019-02-26 11:29:53 -07:00
|
|
|
'step_size', 'transition_time', 'action_color_temperature', 'action_color',
|
2019-02-23 11:13:01 -07:00
|
|
|
'action_group',
|
2019-02-19 11:31:58 -07:00
|
|
|
];
|
2018-11-16 12:23:11 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This extensions handles messages received from devices.
|
|
|
|
*/
|
|
|
|
class DeviceReceive {
|
2019-02-04 10:36:49 -07:00
|
|
|
constructor(zigbee, mqtt, state, publishEntityState) {
|
2018-11-16 12:23:11 -07:00
|
|
|
this.zigbee = zigbee;
|
|
|
|
this.mqtt = mqtt;
|
|
|
|
this.state = state;
|
2019-02-04 10:36:49 -07:00
|
|
|
this.publishEntityState = publishEntityState;
|
2019-01-18 14:23:47 -07:00
|
|
|
this.coordinator = null;
|
2019-01-22 12:02:34 -07:00
|
|
|
this.elapsed = {};
|
2019-01-18 14:23:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
onZigbeeStarted() {
|
|
|
|
this.coordinator = this.zigbee.getCoordinator().device.ieeeAddr;
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
onZigbeeMessage(message, device, mappedDevice) {
|
2019-01-08 11:22:09 -07:00
|
|
|
if (message.type == 'devInterview') {
|
|
|
|
if (!settings.getDevice(message.data)) {
|
|
|
|
logger.info('Connecting with device...');
|
|
|
|
this.mqtt.log('pairing', 'connecting with device');
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (message.type == 'devIncoming') {
|
|
|
|
logger.info('Device incoming...');
|
|
|
|
this.mqtt.log('pairing', 'device incoming');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!device) {
|
|
|
|
logger.warn('Message without device!');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-18 14:23:47 -07:00
|
|
|
if (device.ieeeAddr === this.coordinator) {
|
|
|
|
logger.debug('Ignoring message from coordinator');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
// Check if this is a new device.
|
|
|
|
if (!settings.getDevice(device.ieeeAddr)) {
|
2019-02-21 10:45:22 -07:00
|
|
|
logger.info(`New device '${device.modelId}' with address ${device.ieeeAddr} connected!`);
|
2018-11-16 12:23:11 -07:00
|
|
|
settings.addDevice(device.ieeeAddr);
|
2019-02-21 10:45:22 -07:00
|
|
|
this.mqtt.log('device_connected', device.ieeeAddr, {modelID: device.modelId});
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!mappedDevice) {
|
|
|
|
logger.warn(`Device with modelID '${device.modelId}' is not supported.`);
|
2019-02-12 13:39:37 -07:00
|
|
|
logger.warn(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`);
|
2018-11-16 12:23:11 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// After this point we cant handle message withoud cid or cmdId anymore.
|
|
|
|
if (!message.data || (!message.data.cid && !message.data.cmdId)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find a conveter for this message.
|
|
|
|
const cid = message.data.cid;
|
|
|
|
const cmdId = message.data.cmdId;
|
|
|
|
const converters = mappedDevice.fromZigbee.filter((c) => {
|
|
|
|
if (cid) {
|
2019-03-05 11:41:59 -07:00
|
|
|
// readRsp messages have the same structure as attReport messages.
|
|
|
|
// search for attReport converters on readRsp.
|
2019-03-13 11:41:58 -07:00
|
|
|
if (c.cid === cid) {
|
|
|
|
if (c.type instanceof Array) {
|
|
|
|
return c.type.includes(message.type);
|
|
|
|
} else {
|
|
|
|
return c.type === message.type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2018-11-16 12:23:11 -07:00
|
|
|
} else if (cmdId) {
|
|
|
|
return c.cmd === cmdId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check if there is an available converter
|
|
|
|
if (!converters.length) {
|
|
|
|
if (cid) {
|
2019-03-05 11:41:59 -07:00
|
|
|
// Don't log readRsp messages, they are not interesting most of the time.
|
|
|
|
if (message.type === 'readRsp') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
logger.warn(
|
|
|
|
`No converter available for '${mappedDevice.model}' with cid '${cid}', ` +
|
|
|
|
`type '${message.type}' and data '${JSON.stringify(message.data)}'`
|
|
|
|
);
|
|
|
|
} else if (cmdId) {
|
|
|
|
logger.warn(
|
|
|
|
`No converter available for '${mappedDevice.model}' with cmd '${cmdId}' ` +
|
|
|
|
`and data '${JSON.stringify(message.data)}'`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-02-12 13:39:37 -07:00
|
|
|
logger.warn(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html.`);
|
2018-11-16 12:23:11 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert this Zigbee message to a MQTT message.
|
|
|
|
// Get payload for the message.
|
|
|
|
// - If a payload is returned publish it to the MQTT broker
|
|
|
|
// - If NO payload is returned do nothing. This is for non-standard behaviour
|
|
|
|
// for e.g. click switches where we need to count number of clicks and detect long presses.
|
2019-01-11 14:38:16 -07:00
|
|
|
const publish = (payload) => {
|
2019-02-08 16:29:40 -07:00
|
|
|
// Don't cache messages with following properties:
|
2019-01-11 14:38:16 -07:00
|
|
|
let cache = true;
|
2019-02-08 16:29:40 -07:00
|
|
|
dontCacheProperties.forEach((property) => {
|
2019-01-11 14:38:16 -07:00
|
|
|
if (payload.hasOwnProperty(property)) {
|
|
|
|
cache = false;
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
2019-01-11 14:38:16 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// Add device linkquality.
|
|
|
|
if (message.hasOwnProperty('linkquality')) {
|
|
|
|
payload.linkquality = message.linkquality;
|
|
|
|
}
|
2018-11-16 12:23:11 -07:00
|
|
|
|
2019-01-11 14:38:16 -07:00
|
|
|
// Add last seen timestamp
|
2019-01-22 12:02:34 -07:00
|
|
|
const now = Date.now();
|
2019-01-18 12:31:55 -07:00
|
|
|
switch (settings.get().advanced.last_seen) {
|
|
|
|
case 'ISO_8601':
|
2019-01-22 12:02:34 -07:00
|
|
|
payload.last_seen = new Date(now).toISOString();
|
2019-01-18 12:31:55 -07:00
|
|
|
break;
|
2019-03-02 08:47:36 -07:00
|
|
|
case 'ISO_8601_local':
|
|
|
|
payload.last_seen = utils.toLocalISOString(new Date(now));
|
|
|
|
break;
|
2019-01-18 12:31:55 -07:00
|
|
|
case 'epoch':
|
2019-01-22 12:02:34 -07:00
|
|
|
payload.last_seen = now;
|
2019-01-18 12:31:55 -07:00
|
|
|
break;
|
|
|
|
}
|
2018-12-26 09:33:39 -07:00
|
|
|
|
2019-01-22 12:02:34 -07:00
|
|
|
if (settings.get().advanced.elapsed) {
|
|
|
|
if (this.elapsed[device.ieeeAddr]) {
|
|
|
|
payload.elapsed = now - this.elapsed[device.ieeeAddr];
|
|
|
|
}
|
2019-01-22 12:22:16 -07:00
|
|
|
|
2019-01-22 12:02:34 -07:00
|
|
|
this.elapsed[device.ieeeAddr] = now;
|
|
|
|
}
|
|
|
|
|
2019-02-04 10:36:49 -07:00
|
|
|
this.publishEntityState(device.ieeeAddr, payload, cache);
|
2019-01-11 14:38:16 -07:00
|
|
|
};
|
2018-11-16 12:23:11 -07:00
|
|
|
|
2019-01-11 14:38:16 -07:00
|
|
|
let payload = {};
|
|
|
|
converters.forEach((converter) => {
|
2018-12-19 09:33:02 -07:00
|
|
|
const options = {...settings.get().device_options, ...settings.getDevice(device.ieeeAddr)};
|
2019-01-11 14:38:16 -07:00
|
|
|
const converted = converter.convert(mappedDevice, message, publish, options);
|
2018-11-16 12:23:11 -07:00
|
|
|
|
2019-01-11 14:38:16 -07:00
|
|
|
if (converted) {
|
|
|
|
payload = {...payload, ...converted};
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
|
|
|
});
|
2019-01-11 14:38:16 -07:00
|
|
|
|
|
|
|
if (Object.keys(payload).length) {
|
|
|
|
publish(payload);
|
|
|
|
}
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = DeviceReceive;
|