2019-12-04 11:17:06 +01:00
|
|
|
const EventEmitter = require('events');
|
|
|
|
|
const mqtt = require('mqtt');
|
|
|
|
|
const _ = require('lodash');
|
|
|
|
|
|
|
|
|
|
const startTopic = 'hass/status';
|
|
|
|
|
|
|
|
|
|
// #region logging
|
2019-12-22 18:20:20 +00:00
|
|
|
let debug = '';
|
2019-12-04 11:17:06 +01:00
|
|
|
|
|
|
|
|
const getLogger = () => {
|
|
|
|
|
const consoleLogger = msg => console.log('plejd-mqtt', msg);
|
|
|
|
|
if (debug === 'console') {
|
|
|
|
|
return consoleLogger;
|
|
|
|
|
}
|
|
|
|
|
return _.noop;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const logger = getLogger();
|
|
|
|
|
// #endregion
|
|
|
|
|
|
|
|
|
|
// #region discovery
|
|
|
|
|
|
|
|
|
|
const discoveryPrefix = 'homeassistant';
|
|
|
|
|
const nodeId = 'plejd';
|
|
|
|
|
|
|
|
|
|
const getSubscribePath = () => `${discoveryPrefix}/+/${nodeId}/#`;
|
|
|
|
|
const getPath = ({ id, type }) =>
|
|
|
|
|
`${discoveryPrefix}/${type}/${nodeId}/${id}`;
|
|
|
|
|
const getConfigPath = plug => `${getPath(plug)}/config`;
|
|
|
|
|
const getStateTopic = plug => `${getPath(plug)}/state`;
|
|
|
|
|
const getCommandTopic = plug => `${getPath(plug)}/set`;
|
2019-12-22 17:48:16 +00:00
|
|
|
const getSceneEventTopic = () => `plejd/event/scene`;
|
2019-12-22 18:20:20 +00:00
|
|
|
const getSettingsTopic = () => `plejd/settings`;
|
2019-12-04 11:17:06 +01:00
|
|
|
|
2019-12-10 22:01:12 +01:00
|
|
|
const getDiscoveryPayload = device => ({
|
2019-12-21 15:01:15 +00:00
|
|
|
schema: 'json',
|
2019-12-10 22:01:12 +01:00
|
|
|
name: device.name,
|
2020-01-21 14:24:02 +00:00
|
|
|
unique_id: device.serialNumber,
|
2019-12-10 22:01:12 +01:00
|
|
|
state_topic: getStateTopic(device),
|
|
|
|
|
command_topic: getCommandTopic(device),
|
2019-12-21 15:01:15 +00:00
|
|
|
optimistic: false,
|
2020-01-21 14:24:02 +00:00
|
|
|
brightness: `${device.dimmable}`,
|
|
|
|
|
device: {
|
|
|
|
|
identifiers: device.serialNumber,
|
|
|
|
|
manufacturer: 'Plejd',
|
|
|
|
|
model: device.typeName,
|
|
|
|
|
name: device.name,
|
|
|
|
|
sw_version: device.version
|
|
|
|
|
}
|
2019-12-10 22:01:12 +01:00
|
|
|
});
|
|
|
|
|
|
2019-12-04 11:17:06 +01:00
|
|
|
// #endregion
|
|
|
|
|
|
|
|
|
|
class MqttClient extends EventEmitter {
|
|
|
|
|
constructor(mqttBroker, username, password) {
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
this.mqttBroker = mqttBroker;
|
|
|
|
|
this.username = username;
|
|
|
|
|
this.password = password;
|
|
|
|
|
this.deviceMap = {};
|
|
|
|
|
this.devices = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
const self = this;
|
|
|
|
|
|
|
|
|
|
this.client = mqtt.connect(this.mqttBroker, {
|
|
|
|
|
username: this.username,
|
|
|
|
|
password: this.password
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.client.on('connect', () => {
|
|
|
|
|
logger('connected to MQTT.');
|
|
|
|
|
|
|
|
|
|
this.client.subscribe(startTopic, (err) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
logger('error: unable to subscribe to ' + startTopic);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.emit('connected');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.client.subscribe(getSubscribePath(), (err) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
logger('error: unable to subscribe to control topics');
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-12-22 18:20:20 +00:00
|
|
|
|
|
|
|
|
this.client.subscribe(getSettingsTopic(), (err) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.log('error: could not subscribe to settings topic');
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-12-04 11:17:06 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.client.on('close', () => {
|
|
|
|
|
self.reconnect();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.client.on('message', (topic, message) => {
|
2019-12-21 15:01:15 +00:00
|
|
|
//const command = message.toString();
|
|
|
|
|
const command = JSON.parse(message.toString());
|
2019-12-04 11:17:06 +01:00
|
|
|
|
|
|
|
|
if (topic === startTopic) {
|
|
|
|
|
logger('home assistant has started. lets do discovery.');
|
|
|
|
|
self.emit('connected');
|
|
|
|
|
}
|
2019-12-22 18:20:20 +00:00
|
|
|
else if (topic === getSettingsTopic()) {
|
|
|
|
|
self.emit('settingsChanged', command);
|
|
|
|
|
}
|
2019-12-04 11:17:06 +01:00
|
|
|
|
2019-12-21 15:01:15 +00:00
|
|
|
if (_.includes(topic, 'set')) {
|
2019-12-04 11:17:06 +01:00
|
|
|
const device = self.devices.find(x => getCommandTopic(x) === topic);
|
2019-12-21 15:01:15 +00:00
|
|
|
self.emit('stateChanged', device.id, command);
|
2019-12-04 11:17:06 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-22 18:20:20 +00:00
|
|
|
updateSettings(settings) {
|
|
|
|
|
if (settings.debug) {
|
|
|
|
|
debug = 'console';
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
debug = '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-04 11:17:06 +01:00
|
|
|
reconnect() {
|
|
|
|
|
this.client.reconnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
discover(devices) {
|
|
|
|
|
this.devices = devices;
|
|
|
|
|
|
|
|
|
|
const self = this;
|
|
|
|
|
logger('sending discovery of ' + devices.length + ' device(s).');
|
|
|
|
|
|
|
|
|
|
devices.forEach((device) => {
|
|
|
|
|
logger(`sending discovery for ${device.name}`);
|
|
|
|
|
|
2019-12-21 15:01:15 +00:00
|
|
|
let payload = getDiscoveryPayload(device);
|
2020-01-18 16:00:31 +00:00
|
|
|
console.log(`plejd-mqtt: discovered ${device.type} named ${device.name} with PID ${device.id}.`);
|
2019-12-13 14:13:00 +01:00
|
|
|
|
2019-12-04 11:17:06 +01:00
|
|
|
self.deviceMap[device.id] = payload.unique_id;
|
|
|
|
|
|
|
|
|
|
self.client.publish(
|
|
|
|
|
getConfigPath(device),
|
|
|
|
|
JSON.stringify(payload)
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-21 15:01:15 +00:00
|
|
|
updateState(deviceId, data) {
|
2019-12-04 11:17:06 +01:00
|
|
|
const device = this.devices.find(x => x.id === deviceId);
|
|
|
|
|
|
|
|
|
|
if (!device) {
|
|
|
|
|
logger('error: ' + deviceId + ' is not handled by us.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-21 15:01:15 +00:00
|
|
|
logger('updating state for ' + device.name + ': ' + data.state);
|
|
|
|
|
let payload = null;
|
2019-12-04 11:17:06 +01:00
|
|
|
|
2019-12-21 15:01:15 +00:00
|
|
|
if (device.dimmable) {
|
|
|
|
|
payload = {
|
|
|
|
|
state: data.state === 1 ? 'ON' : 'OFF',
|
|
|
|
|
brightness: data.brightness
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
payload = {
|
|
|
|
|
state: data.state === 1 ? 'ON' : 'OFF'
|
|
|
|
|
}
|
2019-12-04 11:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.client.publish(
|
2019-12-21 15:01:15 +00:00
|
|
|
getStateTopic(device),
|
|
|
|
|
JSON.stringify(payload)
|
2019-12-04 11:17:06 +01:00
|
|
|
);
|
|
|
|
|
}
|
2019-12-22 17:48:16 +00:00
|
|
|
|
|
|
|
|
sceneTriggered(scene) {
|
|
|
|
|
this.client.publish(
|
|
|
|
|
getSceneEventTopic(),
|
|
|
|
|
JSON.stringify({ scene: scene })
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-12-04 11:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { MqttClient };
|