homebridge-broadlink-rm-pro
Version:
Broadlink RM plugin (including the mini and pro) for homebridge with AC Pro and TV features
156 lines (124 loc) • 5.12 kB
JavaScript
const ping = require('ping');
const broadlink = require('./broadlink');
const delayForDuration = require('./delayForDuration');
const dgram = require('dgram');
const Mutex = require('await-semaphore').Mutex;
const pingFrequency = 5000;
const keepAliveFrequency = 90000;
const pingTimeout = 5;
const startKeepAlive = (device, log) => {
if(!device.host.port) {return;}
setInterval(() => {
if(broadlink.debug) {log('\x1b[33m[DEBUG]\x1b[0m Sending keepalive to', device.host.address,':',device.host.port)}
const socket = dgram.createSocket({ type:'udp4', reuseAddr:true });
let packet = Buffer.alloc(0x30, 0);
packet[0x26] = 0x1;
socket.send(packet, 0, packet.length, device.host.port, device.host.address, (err, bytes) => {
if (err) {log('\x1b[33m[DEBUG]\x1b[0m send keepalive packet error', err)}
});
socket.close();
}, keepAliveFrequency);
}
const startPing = (device, log) => {
device.state = 'unknown';
device.retryCount = 1;
setInterval(() => {
try {
ping.sys.probe(device.host.address, (active, err) => {
if(err){
log(`Error pinging Broadlink RM device at ${device.host.address} (${device.host.macAddress || ''}): ${err}`);
throw err;
}
if (!active && device.state === 'active' && device.retryCount === 2) {
log(`Broadlink RM device at ${device.host.address} (${device.host.macAddress || ''}) is no longer reachable after three attempts.`);
device.state = 'inactive';
device.retryCount = 0;
} else if (!active && device.state === 'active') {
if(broadlink.debug) {log(`Broadlink RM device at ${device.host.address} (${device.host.macAddress || ''}) is no longer reachable. (attempt ${device.retryCount})`);}
device.retryCount += 1;
} else if (active && device.state !== 'active') {
if (device.state === 'inactive') {log(`Broadlink RM device at ${device.host.address} (${device.host.macAddress || ''}) has been re-discovered.`);}
device.state = 'active';
device.retryCount = 0;
} else if (active && device.retryCount !== 0 ) {
//Acive - reset retry counter
device.retryCount = 0;
}
}, {timeout: pingTimeout})
} catch (err) {
log(`Error pinging Broadlink RM device at ${device.host.address} (${device.host.macAddress || ''}): ${err}`);
}
}, pingFrequency);
}
const discoveredDevices = {};
const manualDevices = {};
let discoverDevicesInterval;
const discoverDevices = (automatic = true, log, logLevel, deviceDiscoveryTimeout = 60) => {
broadlink.log = log;
broadlink.debug = logLevel <=1;
//broadlink.logLevel = logLevel;
if (automatic) {
this.discoverDevicesInterval = setInterval(() => {
broadlink.discover();
}, 2000);
delayForDuration(deviceDiscoveryTimeout).then(() => {
clearInterval(this.discoverDevicesInterval);
});
broadlink.discover();
}
broadlink.on('deviceReady', (device) => {
let macAddressParts, macAddress;
if (device.mac.includes(":")) {
macAddress = device.mac;
}else{
macAddressParts = device.mac.toString('hex').match(/[\s\S]{1,2}/g) || [];
macAddress = macAddressParts.join(':');
}
device.host.macAddress = macAddress;
log(`\x1b[35m[INFO]\x1b[0m Discovered ${device.model} (${device.type.toString(16)}) at ${device.host.address} (${device.host.macAddress})`);
addDevice(device);
startPing(device, log);
startKeepAlive(device, log);
})
}
const addDevice = (device) => {
if (!device.isUnitTestDevice && (discoveredDevices[device.host.address] || discoveredDevices[device.host.macAddress])) {return;}
device.mutex = new Mutex();
discoveredDevices[device.host.address] = device;
discoveredDevices[device.host.macAddress] = device;
}
const getDevice = ({ host, log, learnOnly }) => {
let device;
if (host) {
device = discoveredDevices[host];
// Create manual device
if (!device && !manualDevices[host]) {
const device = { host: { address: host } };
manualDevices[host] = device;
startPing(device, log);
startKeepAlive(device, log);
}
} else { // use the first one of no host is provided
const hosts = Object.keys(discoveredDevices);
if (hosts.length === 0) {
// log(`Send data (no devices found)`);
return;
}
// Only return device that can Learn Code codes
if (learnOnly) {
for (let i = 0; i < hosts.length; i++) {
let currentDevice = discoveredDevices[hosts[i]];
if (currentDevice.enterLearning) {
device = currentDevice
break;
}
}
if (!device) {log(`Learn Code (no device found at ${host})`);}
} else {
device = discoveredDevices[hosts[0]];
if (!device) {log(`Send data (no device found at ${host})`);}
}
}
return device;
}
module.exports = { getDevice, discoverDevices, addDevice };