Last active
November 24, 2017 12:50
-
-
Save arjunmenon/8ad89f7348442834efe6271e731221f4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Client = require('./upnp_client.js') | |
var client = new Client('http://192.168.1.6:49152/description.xml'); | |
console.log(client); | |
// Get the device description | |
client.getDeviceDescription(function(err, description) { | |
if(err) throw err; | |
console.log("starting device description module") | |
console.log(description); | |
console.log("ending device description module") | |
}); | |
client.callAction('AVTransport', 'GetMediaInfo', { InstanceID: 0 }, function(err, result) { | |
if(err) throw err; | |
console.log(result); // => { NrTracks: '1', MediaDuration: ... } | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require("tabris-js-node"); | |
// var util = require("util"); | |
var et = require('elementtree'); // remove the utils | |
function DeviceClient(url) { | |
this.url = url; | |
this.deviceDescription = null; | |
this.serviceDescriptions = {}; | |
this.server = null; | |
this.listening = false; | |
this.subscriptions = {}; | |
} | |
DeviceClient.prototype.getDeviceDescription = function(callback) { | |
var self = this; | |
// Use cache if available | |
if(this.deviceDescription) { | |
process.nextTick(function() { | |
callback(null, self.deviceDescription); | |
}); | |
return; | |
} | |
// console.log("this.url "+this.url) | |
// console.log("self.url "+self.url) | |
fetch(this.url) | |
.then(response => response.text()) | |
.then((bodyText) => { | |
var desc = parseDeviceDescription(bodyText, self.url); | |
self.deviceDescription = desc // Store in cache for next call | |
callback(null, desc); // returns the result | |
}).catch((err) => { | |
console.log("OKAY.THERE IS AN ERROR"); | |
console.log(err); | |
}); | |
}; | |
function parseDeviceDescription(xml, url) { | |
var doc = et.parse(xml); | |
var desc = extractFields(doc.find('./device'), [ | |
'deviceType', | |
'friendlyName', | |
'manufacturer', | |
'manufacturerURL', | |
'modelName', | |
'modelNumber', | |
'modelDescription', | |
'UDN' | |
]); | |
var nodes = doc.findall('./device/serviceList/service'); | |
desc.services = {}; | |
nodes.forEach(function(service) { | |
var tmp = extractFields(service, [ | |
'serviceType', | |
'serviceId', | |
'SCPDURL', | |
'controlURL', | |
'eventSubURL' | |
]); | |
var id = tmp.serviceId; | |
delete tmp.serviceId; | |
desc.services[id] = tmp; | |
}); | |
// Make URLs absolute | |
var baseUrl = extractBaseUrl(url); | |
// Reformat URLs from '/ctl/service' to 'http://host:port/ctl/service' | |
// It was fucking there job to give it pretty | |
Object.keys(desc.services).forEach(function(id) { | |
var service = desc.services[id]; | |
service.SCPDURL = buildAbsoluteUrl(baseUrl, service.SCPDURL); | |
service.controlURL = buildAbsoluteUrl(baseUrl, service.controlURL); | |
service.eventSubURL = buildAbsoluteUrl(baseUrl, service.eventSubURL); | |
}); | |
return desc; | |
}; | |
function extractFields(node, fields) { | |
var data = {}; | |
fields.forEach(function(field) { | |
var value = node.findtext('./' + field); | |
if(typeof value !== 'undefined') { | |
data[field] = value; | |
} | |
}); | |
return data; | |
} | |
function buildAbsoluteUrl(base, url) { | |
if(url === '') return ''; | |
if(url.substring(0, 4) === 'http') return url; | |
if(url[0] === '/') { | |
var root = base.split('/').slice(0, 3).join('/'); // http://host:port | |
return root + url; | |
} else { | |
return base + '/' + url; | |
} | |
} | |
function extractBaseUrl(url) { | |
return url.split('/').slice(0, -1).join('/'); // removes the last slash | |
} | |
DeviceClient.prototype.getServiceDescription = function(serviceId, callback) { | |
var self = this; | |
serviceId = resolveService(serviceId); | |
this.getDeviceDescription(function(err, desc) { | |
if(err) return callback(err); | |
var service = desc.services[serviceId]; | |
if(!service) { // check if they are out of there mind | |
var err = new Error('Service ' + serviceId + ' not provided by device'); | |
err.code = 'ENOSERVICE'; | |
return callback(err); | |
} | |
// Use cache if available | |
if(self.serviceDescriptions[serviceId]) { | |
return callback(null, self.serviceDescriptions[serviceId]); | |
} | |
fetch(service.SCPDURL) | |
.then(response => response.text()) | |
.then((bodyText) => { | |
var desc = parseServiceDescription(bodyText); | |
self.serviceDescriptions[serviceId] = desc; // Store in cache for next call | |
callback(null, desc); // returns the result | |
}).catch((err) => { | |
console.log("OKAY.THERE IS AN ERROR"); | |
console.log(err); | |
}); // end fetch | |
}); // end getDeviceDescrioption | |
}; | |
function resolveService(serviceId) { | |
return (serviceId.indexOf(':') === -1) | |
? 'urn:upnp-org:serviceId:' + serviceId | |
: serviceId; | |
} | |
function parseServiceDescription(xml) { | |
var doc = et.parse(xml); | |
var desc = {}; | |
desc.actions = {}; | |
var nodes = doc.findall('./actionList/action'); | |
nodes.forEach(function(action) { | |
var name = action.findtext('./name'); | |
var inputs = []; | |
var outputs = []; | |
var nodes = action.findall('./argumentList/argument'); | |
nodes.forEach(function(argument) { | |
var arg = extractFields(argument, [ | |
'name', | |
'direction', | |
'relatedStateVariable' | |
]); | |
var direction = arg.direction; | |
delete arg.direction; | |
if(direction === 'in') inputs.push(arg); | |
else outputs.push(arg); | |
}); | |
desc.actions[name] = { | |
inputs: inputs, | |
outputs: outputs | |
}; | |
}); | |
desc.stateVariables = {}; | |
var nodes = doc.findall('./serviceStateTable/stateVariable'); | |
nodes.forEach(function(stateVariable) { | |
var name = stateVariable.findtext('./name'); | |
var nodes = stateVariable.findall('./allowedValueList/allowedValue'); | |
var allowedValues = nodes.map(function(allowedValue) { | |
return allowedValue.text; | |
}); | |
desc.stateVariables[name] = { | |
dataType: stateVariable.findtext('./dataType'), | |
sendEvents: stateVariable.get('sendEvents'), | |
allowedValues: allowedValues, | |
defaultValue: stateVariable.findtext('./defaultValue') | |
}; | |
}); | |
return desc; | |
} | |
DeviceClient.prototype.callAction = function(serviceId, actionName, params, callback) { | |
var self = this; | |
serviceId = resolveService(serviceId); | |
this.getServiceDescription(serviceId, function(err, desc) { | |
if(err) return callback(err); | |
if(!desc.actions[actionName]) { | |
var err = new Error('Action ' + actionName + ' not implemented by service'); | |
err.code = 'ENOACTION'; | |
return callback(err); | |
} | |
var service = self.deviceDescription.services[serviceId]; | |
var UA = "UPnP/1.0, UPNPClient/0.0.1"; | |
var soapAction = service.serviceType + "#" + actionName; | |
var body = "<u:" + actionName + " xmlns:u=\"" + service.serviceType + "\">"; | |
for(var x in params) { | |
body += "<" + x + ">" + params[x] + "</" + x + ">"; | |
} | |
body += "</u:" + actionName + ">"; | |
var payload = `<?xml version="1.0"?> | |
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> | |
<s:Body>` + | |
body + | |
`</s:Body> | |
</s:Envelope>`; | |
var xmlHTTP = new tabris.XMLHttpRequest(); | |
xmlHTTP.open('POST', service.controlURL, true); | |
xmlHTTP.setRequestHeader('SOAPAction', '"' + soapAction + '"'); | |
xmlHTTP.setRequestHeader('Content-Type', 'text/xml; charset="utf-8"'); | |
xmlHTTP.setRequestHeader('User-Agent', UA); | |
xmlHTTP.onreadystatechange = function() { | |
if(xmlHTTP.readyState === xmlHTTP.DONE) { | |
var doc = et.parse(xmlHTTP.responseText); | |
// Extract response outputs | |
var serviceDesc = self.serviceDescriptions[serviceId]; | |
var actionDesc = serviceDesc.actions[actionName]; | |
var outputs = actionDesc.outputs.map(function(desc) { | |
return desc.name; | |
}); | |
var result = {}; | |
outputs.forEach(function(name) { | |
result[name] = doc.findtext('.//' + name); | |
}); | |
callback(null, result) | |
// console.log("THIS IS MINE ----"+xmlHTTP.responseText) | |
} else { | |
console.log("not OK: " + xmlHTTP.status + ": "+JSON.stringify(xmlHTTP)) | |
} | |
}; // end xmlHTTP | |
xmlHTTP.send(payload); | |
}); // end this.getServiceDescription | |
}; | |
module.exports = DeviceClient; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment