- Based on "winston" logging library - Removed no longer needed lodash - Locked npm dependencies to most recent major versions to avoid installs breaking due to node module updates
190 lines
No EOL
4.9 KiB
JavaScript
190 lines
No EOL
4.9 KiB
JavaScript
const EventEmitter = require('events');
|
|
const mqtt = require('mqtt');
|
|
const Logger = require('./Logger');
|
|
|
|
const startTopic = 'hass/status';
|
|
|
|
const logger = Logger.getLogger("plejd-mqtt");
|
|
|
|
// #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`;
|
|
const getSceneEventTopic = () => `plejd/event/scene`;
|
|
|
|
const getDiscoveryPayload = device => ({
|
|
schema: 'json',
|
|
name: device.name,
|
|
unique_id: `light.plejd.${device.name.toLowerCase().replace(/ /g, '')}`,
|
|
state_topic: getStateTopic(device),
|
|
command_topic: getCommandTopic(device),
|
|
optimistic: false,
|
|
brightness: `${device.dimmable}`,
|
|
device: {
|
|
identifiers: device.serialNumber + '_' + device.id,
|
|
manufacturer: 'Plejd',
|
|
model: device.typeName,
|
|
name: device.name,
|
|
sw_version: device.version
|
|
}
|
|
});
|
|
|
|
const getSwitchPayload = device => ({
|
|
name: device.name,
|
|
state_topic: getStateTopic(device),
|
|
command_topic: getCommandTopic(device),
|
|
optimistic: false,
|
|
device: {
|
|
identifiers: device.serialNumber + '_' + device.id,
|
|
manufacturer: 'Plejd',
|
|
model: device.typeName,
|
|
name: device.name,
|
|
sw_version: device.version
|
|
}
|
|
});
|
|
|
|
// #endregion
|
|
|
|
class MqttClient extends EventEmitter {
|
|
constructor(mqttBroker, username, password) {
|
|
super();
|
|
|
|
this.mqttBroker = mqttBroker;
|
|
this.username = username;
|
|
this.password = password;
|
|
this.deviceMap = {};
|
|
this.devices = [];
|
|
}
|
|
|
|
init() {
|
|
logger.info("Initializing MQTT connection for Plejd addon");
|
|
const self = this;
|
|
|
|
this.client = mqtt.connect(this.mqttBroker, {
|
|
username: this.username,
|
|
password: this.password
|
|
});
|
|
|
|
this.client.on('connect', () => {
|
|
logger.info('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');
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
this.client.on('close', () => {
|
|
logger.verbose('Warning: mqtt channel closed event, reconnecting...');
|
|
self.reconnect();
|
|
});
|
|
|
|
this.client.on('message', (topic, message) => {
|
|
//const command = message.toString();
|
|
const command = message.toString().substring(0, 1) === '{'
|
|
? JSON.parse(message.toString())
|
|
: message.toString();
|
|
|
|
if (topic === startTopic) {
|
|
logger.info('Home Assistant has started. lets do discovery.');
|
|
self.emit('connected');
|
|
}
|
|
else if (topic.includes('set')) {
|
|
logger.verbose(`Got mqtt command on ${topic} - ${message}`);
|
|
const device = self.devices.find(x => getCommandTopic(x) === topic);
|
|
self.emit('stateChanged', device, command);
|
|
}
|
|
else {
|
|
logger.verbose(`Warning: Got unrecognized mqtt command on ${topic} - ${message}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
reconnect() {
|
|
this.client.reconnect();
|
|
}
|
|
|
|
discover(devices) {
|
|
this.devices = devices;
|
|
|
|
const self = this;
|
|
logger.debug(`Sending discovery of ${devices.length} device(s).`);
|
|
|
|
devices.forEach((device) => {
|
|
logger.debug(`Sending discovery for ${device.name}`);
|
|
|
|
let payload = device.type === 'switch' ? getSwitchPayload(device) : getDiscoveryPayload(device);
|
|
logger.info(`Discovered ${device.type} (${device.typeName}) named ${device.name} with PID ${device.id}.`);
|
|
|
|
self.deviceMap[device.id] = payload.unique_id;
|
|
|
|
self.client.publish(
|
|
getConfigPath(device),
|
|
JSON.stringify(payload)
|
|
);
|
|
});
|
|
}
|
|
|
|
updateState(deviceId, data) {
|
|
const device = this.devices.find(x => x.id === deviceId);
|
|
|
|
if (!device) {
|
|
logger.warn(`Unknown device id ${deviceId} - not handled by us.`);
|
|
return;
|
|
}
|
|
|
|
logger.verbose(`Updating state for ${device.name}: ${data.state}`);
|
|
let payload = null;
|
|
|
|
if (device.type === 'switch') {
|
|
payload = data.state === 1 ? 'ON' : 'OFF';
|
|
}
|
|
else {
|
|
if (device.dimmable) {
|
|
payload = {
|
|
state: data.state === 1 ? 'ON' : 'OFF',
|
|
brightness: data.brightness
|
|
}
|
|
}
|
|
else {
|
|
payload = {
|
|
state: data.state === 1 ? 'ON' : 'OFF'
|
|
}
|
|
}
|
|
|
|
payload = JSON.stringify(payload);
|
|
}
|
|
|
|
this.client.publish(
|
|
getStateTopic(device),
|
|
payload
|
|
);
|
|
}
|
|
|
|
sceneTriggered(scene) {
|
|
logger.verbose(`Scene triggered: ${scene}`);
|
|
this.client.publish(
|
|
getSceneEventTopic(),
|
|
JSON.stringify({ scene: scene })
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = { MqttClient }; |