Coder Social home page Coder Social logo

peer-upnp's Introduction

peer-upnp

peer-upnp is a Node.js module implementing the UPnP protocol as described in the UPnP Device Architecture specification

Setup

  • use npm install peer-upnp to install the module.
  • run binary light example:
    1. use node node_modules/peer-upnp/test/binary-light-device.js to create and advertise a UPnP BinaryLight Device with SwitchPower service
    2. use node node_modules/peer-upnp/test/binary-light-client.js that discovers BinaryLight devices and uses SwitchPower Service to control the light.
  • or run the other example using node node_modules/peer-upnp/test/upnp-test.js to discover UPnP services on the network.

Usage

The following example shows the discovery and binding process of UPnP devices and services.

var upnp = require("peer-upnp");
var http = require("http");
var server = http.createServer();
var PORT = 8080;
server.listen(PORT);
// Peer is an event emitter
var peer = upnp.createPeer({
	prefix: "/upnp",
	server: server
}).on("ready",function(peer){
	console.log("ready");
	// listen to urn:schemas-upnp-org:service:SwitchPower:1 services
	peer.on("urn:schemas-upnp-org:service:SwitchPower:1",function(service){
		console.log("service "+service.serviceType+" found");
		service.on("disappear",function(service){
			console.log("service "+service.serviceType+" disappeared");
		});
		// Bind to service to be able to call service actions
		service.bind(function(service){
			// Call UPnP action SetTarget with parameter NewTargetValue
			service.SetTarget({
				NewTargetValue: 1
			},function(res){
				console.log("Result",res);
			});
		}).on("event",function(data){
			console.log((data.Status == "1" || data.Status == "true")? "Light is ON": "Light is OFF" );
		});
		// unsubscribe from the service after 10 seconds 
		setTimeout(function(){
			service.removeAllListeners("event");
		},10000);
	}).on("upnp:rootdevice",function(device){ // listen to root devices
		console.log("rootdevice "+device.deviceType+" found");
		device.on("disappear",function(device){
			console.log("rootdevice "+device.UDN+" disappeared");
		});
	});
	// close peer after 30 seconds
	setTimeout(function(){
		peer.close();
	},30000);
}).on("close",function(){
	console.log("closed");
});

The following example shows how to create and advertise a BinaryLight device and with a SwitchPower service as specified in UPnP Lighting Controls V 1.0. Please refer to the documentation in the code.

var upnp = require("peer-upnp");
var http = require("http");
var server = http.createServer();
var PORT = 8080;
// start server on port 8080. please do this step before you create a peer
server.listen(PORT);

// Create a UPnP Peer. 
var peer = upnp.createPeer({
	prefix: "/upnp",
	server: server
}).on("ready",function(peer){
	console.log("ready");
	// advertise device after peer is ready
	device.advertise();
}).on("close",function(peer){
	console.log("closed");
}).start();

// Create a BinaryLight device as specified in UPnP http://upnp.org/specs/ha/UPnP-ha-BinaryLight-v1-Device.pdf.  
// Please refer for device configuration parameters to the UPnP device architecture.
var device = peer.createDevice({
	autoAdvertise: false,
	uuid: "6bd5eabd-b7c8-4f7b-ae6c-a30ccdeb5988",
	productName: "Coltram",
	productVersion: "0.0.1",
	domain: "schemas-upnp-org",
	type: "BinaryLight",
	version: "1",
	friendlyName: "BinaryLight",
	manufacturer: "Fraunhofer FOKUS",
	manufacturerURL: "http://www.fokus.fraunhofer.de",
	modelName: "BinaryLight",
	modelDescription: "BinaryLight",
	modelNumber: "0.0.1",
	modelURL: "http://www.famium.org",
	serialNumber: "1234-1234-1234-1234",
	UPC: "123456789012"
});

// create a SwitchPower service in the BinaryLight device as specified here http://upnp.org/specs/ha/UPnP-ha-SwitchPower-v1-Service.pdf
var service = device.createService({
	domain: "schemas-upnp-org",
	type: "SwitchPower",
	version: "1",
	// Service Implementation
	implementation: {
		GetTarget: function(inputs){
			// the result is the value of the state variable Target
			return {RetTargetValue: this.get("Target")}
		},
		SetTarget: function(inputs){
			// set the new value of the state variable Target
			this.set("Target", inputs.NewTargetValue); 
			// notify state change of the state variable to all subscribers
			this.notify("Target");
			this.get("Target") == "1"? console.log("Light is ON"):console.log("Light is OFF");
		},
		GetStatus: function(inputs){
			// the result is the value of the state variable Target
			return {ResultStatus: this.get("Target")}
		},
	},
	// Service Description. this will be converted to XML 
	description: {
		actions: {
			GetTarget: {
				outputs: {
					RetTargetValue: "Target", // Target is the name of the state variable
				}
			},
			SetTarget: {
				inputs: {
					NewTargetValue: "Target"
				}
			},
			GetStatus: {
				outputs: {
					ResultStatus: "Status",
				}
			}
		},
		// declare all state variables: key is the name of the variable and value is the type of the variable. 
		// type can be JSON object in this form {type: "boolean"}. 
		variables: {
			Target: "boolean", 
			Status: "boolean"
		}
	}
});
// initialize the Target State Variable with 0
service.set("Target",0);

License

Released under the GNU Lesser General Public License v3.0, See LICENSE file.

Copyright (c) 2013 Fraunhofer Gesellschaft zur Foerderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer Institut FOKUS.

All rights reserved

Contact: [email protected]

peer-upnp's People

Contributors

louaybassbouss avatar mlasak avatar aldafu avatar

Stargazers

Sixia "Leask" Huang avatar Oleg Akinin avatar Jakub Łabor avatar Joël CANCELA VAZ avatar Capitan Nemo avatar Ross Tyler avatar Nadeen Udantha avatar Alexander Borovsky avatar Sylvain MATHIEU avatar joeherwig avatar K.O. avatar  avatar Stephan Hesse avatar Leiyin Lin avatar Georg Perhofer avatar monz avatar Hunter Beast avatar Teo Maragakis avatar Andrew Kemp avatar Mrwan Ashraf avatar  avatar  avatar Antoninko avatar Andreas Niedermair avatar Zarathon Maia avatar  avatar Stephan Steglich avatar Adrian Thomas-Prestemon avatar Rodrigo Fernandez avatar Igor Muzyka avatar  avatar  avatar

Watchers

 avatar James Cloos avatar  avatar  avatar  avatar Stephan Steglich avatar  avatar Stefan Kaiser avatar  avatar

peer-upnp's Issues

Can't sendEvents/multicast

I am using the example of the binary light. I tried to put Target as an evented variable :

variables: { Target : { type : "boolean", sendEvents : "yes", multicast: "yes" }, Status : { type : "boolean"} }
Here is the xml produced :

    <stateVariable sendEvents="no" multicast="no">
        <name>Target</name>
        <dataType>boolean</dataType>

    </stateVariable>

    <stateVariable sendEvents="no" multicast="no">
        <name>Status</name>
        <dataType>boolean</dataType>

    </stateVariable>

Critical and high vulnerabilities in dependencies libs

Running npm audit reports 2 vulnerabilities in peer-upnp dependency libs:

Some vulnerabilities require your attention to resolve
         Visit https://go.npm.me/audit-guide for additional guidance

- Critical        ejs template injection vulnerability
 Package         ejs
 Patched in      >=3.1.7
 Dependency of   peer-upnp
 Path            peer-upnp > ejs
 More info       https://github.com/advisories/GHSA-phwq-j96m-2c2q

- High            Insecure Entropy Source - Math.random() in node-uuid
 Package         node-uuid
 Patched in      >=1.4.4
 Dependency of   peer-upnp
 Path            peer-upnp > node-uuid
 More info       https://github.com/advisories/GHSA-265q-28rp-chq5

can these dependencies be updated?

Un-handled exception in unsubscribe() remote service.

Thank you for sharing this library.

The http.request() made in unsubscribe() function, line 596 does not have a handler for error function. this means that if the remote device shuts down before the peer, there will be an un-handled exception when the local peer tries to shut down, and calls removeAllListeners().

It would be great if line 615 would have the following code:
req.on("error",function(){});

Regards,
Reza

Peer-UPnP is not reliable

I have used the Peer-UPnP binary-light-device.js example on a Raspberry Pi CM3+.
It works at the beginning fine than(after a few days) it starts blinking then it disappears completely from windows(10) explorer networking.

I had this issue with two raspberries.

Any hint or help is appreciated.

Renewing a subscription causes notify to fail

Running a subscribing & resubscribing (non peer-upnp) UPnP client against your binary-light-device causes it to fail to notify after the first resubscription.

http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf

4.1.3 Renewing a subscription with SUBSCRIBE with SID
...
CALLBACK
(No CALLBACK header field is used to renew an event subscription.)

Each such resubscription is handled by peer-upnp as a new subscription, generating a new SID with no callbacks (as "no CALLBACK header field is used to renew"). Eventually the service is flooded with subscriptions - none of which would work because there is no callback. Actually, when notifying the first such one, the code throws an exception because it doesn't expect no callbacks.

Cannot read property '*serviceType*' of undefined

err: failed to get device description
/home/pi/upnp-scrobble/upnp-scrobble/node_modules/peer-upnp/lib/peer-upnp.js:754
var service = device && device.services[serviceType];
^

TypeError: Cannot read property 'urn:schemas-upnp-org:service:AVTransport:1' of undefined
at Server.handlePostEvent (/home/pi/upnp-scrobble/upnp-scrobble/node_modules/peer-upnp/lib/peer-upnp.js:754:42)
at IncomingMessage. (/home/pi/upnp-scrobble/upnp-scrobble/node_modules/peer-upnp/lib/peer-upnp.js:634:38)
at emitNone (events.js:67:13)
at IncomingMessage.emit (events.js:166:7)
at endReadableNT (_stream_readable.js:903:12)
at doNTCallback2 (node.js:439:9)
at process._tickCallback (node.js:353:17)

Missing null-check in bind of RemoteService

In line 480 there is the following parsing:

xml2js.parseString(data, {explicitArray: false, ignoreXmlns: true, mergeAttrs: true}, function (err, json) {

This call does not check the argument err

httpRequest(this.SCPDURL, function(err, data) {

Which is in my case true and data is null, which results in an unhandled exception.

How does peer-upnp handle respawning of devices, which did not say BYEBYE

According to the spec, a UPnP-device SHOULD notify that it's going to shutdown. What if the device does not emit a BYEBYE, and comes back after eg 5 minutes.

The handling of found devices/services is done in line 118ff:

.on("found",function(headers, address){

Which does a check against the UDN of the device, to determine if the device has been found before and cancel its internal advertisement if it has already been discovered:

if (**!self.remoteDevices[udn]** && (self.listeners(SSDP_ALL).length>0 || self.listeners(st).length>0)) {

So my question is: If the device, which did not say BYEBYE, comes back again, still having the same UDN, does peer-upnp handle potential changes in the device's address or port?

http server & httpHandlers Object Question

First, please forgive me as I am fairly new to the subject and am still learning about upnp and ssdp.

With that said, I am trying to "tap" into the http server request system so that I can handle additional requests however my method seems to give me

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

I suspect because of the way your http server is implemented. It looks like you define httpHandlers to handle requests, but obviously it does not appear that I can tap into that from the peer.

I will provide to you what I have that is giving me the above error in case its simpler then what I am seeing:

function onRequest(req, res) {

  switch (req.url) {
    case '/discover.json':
      if (req.method === 'GET') res.end(JSON.stringify(getDiscoverJson()));
      break;
    case '/lineup.json':
      if (req.method === 'GET') res.end(JSON.stringify(getLineupJson()));
      break;
    default:
      if (req.method === 'GET') res.end(JSON.stringify(getDiscoverJson()));
  }

}

const httpServer = http.createServer(onRequest).listen(PORT);
console.log(`Listening on ${PORT}`);

// ... methods that return an object for the json responses

// peer
const peer = upnp.createPeer({
  prefix: "/upnp",
  server: httpServer
}).on("ready", async (peer) => {
  console.log("Device Ready");
  device.advertise();
}).on("close", (peer) => {
  console.log("closed");
}).start();

const device = peer.createDevice({
  // all my options
});

Please advise A.S.A.P

Much appreciated in advance

Timeout in httpRequest

I came across a minor bug when using this on a relatively low powered RPi. When discovering a service of which there were many instances, the http.request was throwing a timeout and ultimately crashing as the server (which was hosting all these services) failed to respond quickly enough.

To overcome it I increased the timeout in req.setTimeout by a factor of 10, but i'm sure there is a more elegant way of handling this situation.

Service Bind

Hi,

I appreciate your work. Right now I'm beginning to understand the internals.
Sadly I can not follow the snippet (in binary-light-client.js):

service.bind(function(service){
            // Call UPnP interface SetTarget of SwitchPower Service
            console.log("Turn light ON using SetTarget with input parameter NewTargetValue=1");
			service.SetTarget({
				NewTargetValue: 1
			},function(res){
				console.log("SetTarget done");
			});
		}).on("event",function(data){
			console.log("Receive update from SwitchPower Service 1: ",data);
		});

Particularly I am having problems with .bind.

I understand bind in toy examples, like:

var sum = function(a, b) {
  return a + b;
};

var add5 = sum.bind(null, 5);
console.log(add5(10));

As a way of binding a functions context.

Even after reading your comments:

 // bind to the service in order to call its methods
 // bind will generate a JavaScript function for each service method.
 // Inputs and outputs are JSON objects where the keys are the name of the
 // inputs or outputs.

there are many question marks left in my head.

I would very much appreciate any help as this seems to be a fun project to work on!

Cheers

handlePostControl actionName missing last character

On line 709 of peer-upnp.js where actionName is retrieved from the request headers, it seems like the last character is cut off the string.

originally the line was
actionName = actionName && actionName.substring(actionName.lastIndexOf("#")+1, actionName.length-1);

When soap action was "urn:schemas-upnp-org:services:WANIPConnection:1#GetStatusInfo", actionName would be set to "GetStatusInf".

Changing line 709 to
actionName = actionName && actionName.substring(actionName.lastIndexOf("#")+1);

then actionName would be set to "GetStatusInfo"

This is very odd that something this critical would be overlooked, so I'm open to a potential issue in how javascript and node JS are configured and running on my system. Ubuntu 14.04.5 LTS with NodeJS v8.11.4. It could be that I have formatted the soap action incorrectly.

my test code that reproduced the same results:
var aString="urn:schemas-upnp-org:services:WANIPConnection:1#GetStatusInfo"; var subStringResult = aString && aString.substring(aString.lastIndexOf("#")+1, aString.length-1); console.log("Test: ", subStringResult)

Device icon

Hi

As part of test-upnp.js a device icon is used but I can't figure out where the icon file needs to be placed - or do I need to implement serving the file?

Thanks
Marcus

Minor bug in registerHTTPHandler() function

I discovered a minor bug in registerHTTPHandler(). The case where url.pathname does not start with peer.prefix is not handled, i.e. no response is generated for this case. I noticed it when using "Get Device XML" as part of Intel UPnP Device Spy, which opens a web browser to show the device XML. As most web browsers try to load a favicon from the same site, this causes the browser to keep on loading as there is no response to the favicon GET request. I'll file a PR with a suggested fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.