Skip to content

Instantly share code, notes, and snippets.

@CloCkWeRX
Created January 5, 2014 04:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CloCkWeRX/8264408 to your computer and use it in GitHub Desktop.
Save CloCkWeRX/8264408 to your computer and use it in GitHub Desktop.
var http = require('http')
, os = require('os')
, url = require('url')
// , SSDP = require('../steward/node_modules/node-ssdp')
, netmask = require('../steward/node_modules/netmask')
, xml2json = require('../steward/node_modules/xml2json')
;
'use strict';
var dgram = require('dgram')
, EE = require('events').EventEmitter
, util = require('util')
;
var SSDP_SIG = 'node.js/0.0.8 UPnP/1.1 node-ssdp/0.0.1'
, SSDP_IP = '239.255.255.250'
, SSDP_PORT = 1900
, SSDP_IPPORT = SSDP_IP + ':' + SSDP_PORT
, TTL = 1800
, SSDP_LOGGER = { error : function(msg, props) { console.log(msg); if (!!props) console.trace(props.exception); }
, warning : function(msg, props) { console.log(msg); if (!!props) console.log(props); }
, notice : function(msg, props) { console.log(msg); if (!!props) console.log(props); }
, info : function(msg, props) { console.log(msg); if (!!props) console.log(props); }
, debug : function(msg, props) { console.log(msg); if (!!props) console.log(props); }
}
;
function SSDP() {
var self = this;
if (!(this instanceof SSDP)) return new SSDP();
EE.call(self);
self.logger = SSDP_LOGGER;
self.description = 'upnp/desc.php';
self.usns = {};
self.udn = 'uuid:e3f28962-f694-471f-8f74-c6abd507594b';
// Configure socket for either client or server.
self.listening = false;
self.responses = {};
self.sock = dgram.createSocket('udp4');
self.sock.on('error', function (err) {
(self.logger.fatal || self.logger.error)('ssdp', { event: 'socket', diagnostic: 'error', exception: err });
});
self.sock.on('message', function onMessage(msg, rinfo) {
self.parseMessage(msg, rinfo);
});
self.sock.on('listening', function onListening() {
var addr = self.sock.address();
self.listening = 'http://' + addr.address + ':' + addr.port;
self.logger.info('SSDP listening on ' + self.listening);
// self.sock.addMembership(SSDP_IP);
self.sock.setMulticastLoopback(false);
// self.sock.setMulticastTTL(2);
});
process.on('exit', function () {
self.close();
});
}
util.inherits(SSDP, EE);
SSDP.prototype.inMSearch = function (st, rinfo) {
var self = this
, peer = rinfo['address']
, port = rinfo['port'];
if (st[0] == '"') st = st.slice(1, -1);
Object.keys(self.usns).forEach(function (usn) {
var udn = self.usns[usn];
if (st == 'ssdp:all' || usn == st) {
var pkt = self.getSSDPHeader('200 OK', {
ST: usn,
USN: udn,
LOCATION: self.httphost + '/' + self.description,
'CACHE-CONTROL': 'max-age=' + TTL,
DATE: new Date().toUTCString(),
SERVER: SSDP_SIG,
EXT: ''
}, true);
self.logger.debug('Sending a 200 OK for an M-SEARCH to ' + peer + ':' + port);
pkt = new Buffer(pkt);
self.sock.send(pkt, 0, pkt.length, port, peer);
}
});
};
SSDP.prototype.addUSN = function (device) {
this.usns[device] = this.udn + '::' + device;
};
SSDP.prototype.parseMessage = function (msg, rinfo) {
var type = msg.toString().split('\r\n').shift();
// HTTP/#.# ### Response
if (true || type.match(/HTTP\/(\d{1})\.(\d{1}) (\d+) (.*)/)) {
this.parseResponse(msg, rinfo);
} else {
this.parseCommand(msg, rinfo);
}
};
SSDP.prototype.parseCommand = function parseCommand(msg, rinfo) {
var lines = msg.toString().split("\r\n")
, type = lines.shift().split(' ')
, method = type[0]
, self = this;
var heads = {};
lines.forEach(function (line) {
if (line.length) {
var vv = line.match(/^([^:]+):\s*(.*)$/);
heads[vv[1].toUpperCase()] = vv[2];
}
});
switch (method) {
case 'NOTIFY':
// Device coming to life.
if (heads['NTS'] == 'ssdp:alive') {
self.emit('advertise-alive', heads);
}
// Device shutting down.
else if (heads['NTS'] == 'ssdp:byebye') {
self.emit('advertise-bye', heads);
} else {
self.logger.warning('NOTIFY unhandled', { msg: msg, rinfo: rinfo });
}
break;
case 'M-SEARCH':
self.logger.debug('SSDP M-SEARCH: for (' + heads['ST'] + ') from (' + rinfo['address'] + ':' + rinfo['port'] + ')');
if (!heads['MAN']) return;
if (!heads['MX']) return;
if (!heads['ST']) return;
self.inMSearch(heads['ST'], rinfo);
break;
default:
self.logger.warning('NOTIFY unhandled', { msg: msg, rinfo: rinfo });
}
};
SSDP.prototype.parseResponse = function parseResponse(msg, rinfo) {
if (!this.responses[rinfo.address]) {
this.responses[rinfo.address] = true;
this.logger.debug('SSDP response', { rinfo: rinfo });
}
this.emit('response', msg, rinfo);
/*console.log('Parsing a response!');
console.log(msg.toString());*/
};
SSDP.prototype.search = function search(st, ipaddr) {
var self = this;
require('dns').lookup(require('os').hostname(), function (err, add) {/* jshint unused: false */
//self.sock.bind(0, add);
var pkt = self.getSSDPHeader('M-SEARCH', {
'HOST': SSDP_IPPORT,
'ST': st,
'MAN': '"ssdp:discover"',
'MX': 3
});
pkt = new Buffer(pkt);
// console.log('@138', pkt.toString());
self.sock.send(pkt, 0, pkt.length, SSDP_PORT, (!!ipaddr) ? ipaddr : SSDP_IP);
});
};
SSDP.prototype.server = function (ip, portH, portS) {
var self = this;
if (!portH) portH = 10293;
if (!portS) portS = 1900;
this.httphost = 'http://'+ip+':'+portH;
this.usns[this.udn] = this.udn;
if (!this.listening) this.sock.bind(portS, ip);
};
SSDP.prototype.close = function () {
this.advertise(false);
this.advertise(false);
this.sock.close();
};
SSDP.prototype.advertise = function (alive) {
var self = this;
if (!this.sock) return;
if (alive === undefined) alive = true;
Object.keys(self.usns).forEach(function (usn) {
var udn = self.usns[usn]
, out = 'NOTIFY * HTTP/1.1\r\n';
var heads = {
HOST: SSDP_IPPORT,
NT: usn,
NTS: (alive ? 'ssdp:alive' : 'ssdp:byebye'),
USN: udn
};
if (alive) {
heads['LOCATION'] = self.httphost + '/' + self.description;
heads['CACHE-CONTROL'] = 'max-age=1800';
heads['SERVER'] = SSDP_SIG;
}
out = new Buffer(self.getSSDPHeader('NOTIFY', heads));
self.sock.send(out, 0, out.length, SSDP_PORT, SSDP_IP);
});
};
SSDP.prototype.getSSDPHeader = function (head, vars, res) {
var ret = '';
if (res === null) res = false;
if (res) {
ret = "HTTP/1.1 " + head + "\r\n";
} else {
ret = head + " * HTTP/1.1\r\n";
}
Object.keys(vars).forEach(function (n) {
ret += n + ": " + vars[n] + "\r\n";
});
return ret + "\r\n";
};
SSDP.prototype.notify = function(ipaddr, portno) {/* jshint unused: false */
var out;
var self = this;
if (!this.sock) return;
Object.keys(self.usns).forEach(function (usn) {
out = 'NOTIFY * HTTP/1.1\rnHOST: 239.255.255.250:10293\r\nCACHE-CONTROL: max-age=20\r\nSERVER: AIR CONDITIONER\r\n\r\nSPEC_VER: MSpec-1.00\r\nSERVICE_NAME: ControlServer-MLib\r\nMESSAGE_TYPE: CONTROLLER_START\r\n';
bcast = '192.168.1.15'; //
console.log('multicasting to ' + bcast + ':1900 from ' + ipaddr + ':' + portno);
out = new Buffer(out);
self.sock.send(out, 0, out.length, 1900, bcast);
});
};
SSDP.prototype.server = function (ip, portH, portS) {
var self = this;
portH = 1900;
portS = 1900;
this.httphost = 'http://'+ip+':'+portH;
this.usns[this.udn] = this.udn;
this.sock.bind(portS, ip);
};
var listen = function(ipaddr, portno) {
var ssdp;
ssdp = new SSDP().on('response', function(msg, rinfo) {
var f, i, info, j, lines;
lines = msg.toString().split("\r\n");
info = {};
for (i = 1; i < lines.length; i++) {
j = lines[i].indexOf(':');
if (j <= 0) break;
info[lines[i].substring(0, j)] = lines[i].substring(j + 1).trim();
}
f = function() {
console.log('');
console.log(rinfo);
console.log(info);
};
http.request(url.parse(info.LOCATION || info.Location), function(response) {
var data = '';
response.on('data', function(chunk) {
data += chunk.toString();
}).on('end', function() {
f();
console.log(xml2json.toJson(data));
}).on('close', function() {
f();
console.log('socket premature eof');
}).setEncoding('utf8');
}).on('error', function(err) {
f();
console.log('socket error: ' + err.message);
}).end();
});
ssdp.server('0.0.0.0', null, portno);
setTimeout(function() {
ssdp.notify('192.168.1.3', portno);
}, 1000);
};
var ifa, ifaces, ifaddrs, ifname;
ifaces = os.networkInterfaces();
for (ifname in ifaces) {
if ((!ifaces.hasOwnProperty(ifname))
|| (ifname.indexOf('vmnet') === 0)
|| (ifname.indexOf('vnic') === 0)
|| (ifname.indexOf('tun') !== -1)) continue;
ifaddrs = ifaces[ifname];
if (ifaddrs.length === 0) continue;
for (ifa = 0; ifa < ifaddrs.length; ifa++) {
if ((ifaddrs[ifa].internal) || (ifaddrs[ifa].family !== 'IPv4')) continue;
console.log('listening ' + ifname + ' on ' + ifaddrs[ifa].address + ' udp port');
listen(ifaddrs[ifa].address, 1900);
}
}
@mrose17
Copy link

mrose17 commented Jan 5, 2014

hi. two things:

  1. on line 275, there is a backslash missing before 'nHOST:`
  2. on line 277, you are hard-coding the address of the air conditioner(!!), instead of using the broadcast address.

i have a new version of list-notify.js i will be uploading momentarily...

@cicciovo
Copy link

do you resolve it? In my mac it's not work! Cannot discover

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment