node-hue-api
Version:
Philips Hue API Library for Node.js
103 lines (102 loc) • 3.61 kB
JavaScript
import * as dgram from 'dgram';
import * as events from 'events';
export class SSDPSearch {
constructor() {
const self = this;
this.responseEmitter = new events.EventEmitter();
this.socket = dgram.createSocket('udp4');
this.socket.on('error', function (err) {
self.responseEmitter.emit('error', err);
});
this.socket.on('message', function onMessage(msg, rinfo) {
const msgStrings = msg.toString().split('\r\n');
// HTTP/#.# ### Response
if (msgStrings[0].match(/HTTP\/(\d{1})\.(\d{1}) (\d+) (.*)/)) {
self.responseEmitter.emit('response', _parseSearchResponse(msgStrings.slice(1)));
}
});
// Ensure the socket is released on exit of the process (in case of errors)
process.on('exit', this.finished.bind(this));
}
search(timeout) {
const self = this;
return new Promise((resolve, reject) => {
const results = [];
self.responseEmitter.on('error', err => {
self.finished();
reject(err);
});
self.responseEmitter.on('response', value => {
results.push(value);
});
// Await our timeout before returning any results
setTimeout(() => {
self.finished();
resolve(_processResults(results));
}, timeout || 5000);
self._start();
});
}
_start() {
if (this.socket) {
const ip = '239.255.255.250', port = 1900;
const pkt = Buffer.from(_buildSearchPacket({
'HOST': ip + ':' + port,
'MAN': 'ssdp:discover',
'MX': 10,
// "ST": "SsdpSearch:all"
'ST': 'urn:schemas-upnp-org:device:Basic:1'
}));
this.socket.send(pkt, 0, pkt.length, port, ip);
}
}
finished() {
if (this.socket) {
this.socket.close();
this.socket = undefined;
}
}
}
function _buildSearchPacket(vars) {
let packet = 'M-SEARCH * HTTP/1.1\r\n';
Object.keys(vars).forEach(function (n) {
packet += `${n}: ${vars[n]}\r\n`;
});
return `${packet}\r\n`;
}
function _parseSearchResponse(lines) {
const result = {};
lines.forEach(line => {
const separatorIndex = line.indexOf(':');
if (separatorIndex > 0 && separatorIndex < line.length) {
const key = line.substring(0, separatorIndex).toLowerCase();
const value = line.substring(separatorIndex + 1, line.length).trim();
result[key] = value;
}
});
return result;
}
function _processResults(responses) {
const results = {};
responses.forEach(response => {
const location = response.location;
// Older versions used to use hue-bridgeId, now newer software versions use hue-bridgeid, remove the older fallback
// once they are outdated.
let hueBridgeId = response['hue-bridgeid'];
if (hueBridgeId == undefined) {
hueBridgeId = response['hue-bridgeId'];
}
if (location && hueBridgeId) {
if (!results[location]) {
const ipAddress = /\/\/(.*):/.exec(location);
if (ipAddress) {
results[location] = {
id: hueBridgeId,
internalipaddress: ipAddress[1]
};
}
}
}
});
return Object.values(results);
}