oeuillot / upnpserver

Fast and light upnp server for node
GNU General Public License v2.0
181 stars 47 forks source link

Support for multiple devices per server #37

Open dearchap opened 9 years ago

dearchap commented 9 years ago

Right now we can add multiple services to root device. However some emulated devices actually consist of services as well as multiple sub-devices(each with its own services). Can the upnpserver code be moved into a device class in device.js ? That way upnpserver can be given just the root device node and internally it can create services and sub-devices etc.

s-leger commented 9 years ago

Hi, This is what the device branch is about. Allow multiple devices on a single host. (sharing one single ssdp server) But at this time every device is a root device with his own services. I’ll take a look at the possibility of having a single root device with sub devices.

Le 1 sept. 2015 à 21:16, dearchap notifications@github.com a écrit :

Right now we can add multiple services to root device. However some emulated devices actually consist of services as well as multiple sub-devices(each with its own services). Can the upnpserver code be moved into a device class in device.js ? That way upnpserver can be given just the root device node and internally it can create services and sub-devices etc.

— Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37.

dearchap commented 9 years ago

Where is the "device" branch ? I dont see it in the list of branches.

s-leger commented 9 years ago

Sorry, at this time i'ts on https://github.com/s-leger/upnpserver/tree/device Still work in progress

s-leger commented 9 years ago

pushed branch here https://github.com/oeuillot/upnpserver/tree/device

Olivier realy do a great work with his api, and it's a strong starting point to implement other devices / services. This branch focus on device abstraction, taking MediaServer out from upnpServer. MediaRender is far from being working as it, but is a good test case for the Device abstraction. AVTransport still need some care. RenderingControl still missing.

Configuration sample

The server start automagically no need to call any method

  var UpnpAPI = require("upnpserver").UpnpAPI;

  var server = new UpnpAPI({
    ssdp:{
      Log:0,
      LogLevel:"error"
    },
    // Rootdevices are objects in root array
    root:[{     // this device has one main MediaRender and a embed MediaServer device
       lang:'fr',
       httpPort:8080,
       dlna:true,
       devices:{
        MediaRender:{
          uuid:"A00C5515-23AA-43EC-BA7F-659603492942",
          player:"Player",
          services:{
            connectionManager:"",
            avTransport:"",
            renderingControl:""
          }
        },
        MediaServer:{
          dlna:1 ,
          microsoft:0,
          uuid:"A00C5515-23AA-43EC-BA7F-659603492941",
          enableIntelToolkitSupport:false,
          services:{
            connectionManager:"",
            mediaReceiverRegistrar:"",
            contentDirectory:{
              paths:[
                { path: '/Users/stephen/Documents', mountPoint: '/Documents' }
              , { mountPoint:'/IceCast', type:'icecast'}
              //, { mountPoint: '/Audio', type:'music', path:'/Users/stephen/Music'}
              //, { mountPoint: '/Video', type:'path', path:'/Users/stephen/Movies'}
              //, { mountPoint: '/Images', type:'path', path:'/Users/stephen/Pictures'}
              ]
            }
          }
        }
        }
      },{           // this device has one main MediaServer device
       devices:{
        MediaServer:{
          name:"MS",
          dlna:1 ,
          microsoft:0,
          uuid:"A00C5515-23AA-43EC-BA7F-659603492946",
          enableIntelToolkitSupport:false,
          services:{
            connectionManager:"",
            mediaReceiverRegistrar:"",
            contentDirectory:{
              paths:[
                { path: '/Users/stephen/Documents', mountPoint: '/Documents' }
              , { mountPoint:'/IceCast', type:'icecast'}
              //, { mountPoint: '/Audio', type:'music', path:'/Users/stephen/Music'}
              //, { mountPoint: '/Video', type:'path', path:'/Users/stephen/Movies'}
              //, { mountPoint: '/Images', type:'path', path:'/Users/stephen/Pictures'}
              ]
            }
          }
        }
        }
      },{         // this device has one main MediaRender device
       devices:{
        MediaRender:{
          name:"MR",
          uuid:"A00C5515-23AA-43EC-BA7F-659603492948",
          player:"Player",
          services:{
            connectionManager:"",
            avTransport:"",
            renderingControl:""
          }
        }
       }
    }]
  });
  server.on("error", console.log);
dearchap commented 9 years ago

I was thinking that we can move devices into a separate lib/devices dir and services into lib/services. Shall I make that change ?

s-leger commented 9 years ago

Good idea, note: take care of contentDirectory dependency they are a lot (mostly auto-required). Commited my last changes so you can do it.

oeuillot commented 9 years ago

Yes it can be interesting :-)

However, I propose to wait the merge of "device" branch into the master, and fix some problems if any. And after, we (you :-) ? ) can work on the separation of devices and services in different folders !?

@S-Leger : an opinion ?

2015-09-02 17:00 GMT+02:00 dearchap notifications@github.com:

I was thinking that we can move devices into a separate lib/devices dir and services into lib/services. Shall I make that change ?

— Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37#issuecomment-137114621.

s-leger commented 9 years ago

It's a bit early to merge. MediaServer still working, but i'll check service / device implementation to ensure we are on the right path. Working on MediaRender is a good test case. There is also some breaking changes (configuration and API) requiring some care in the command line extension. We should take some time to think about a good structure for the whole, as there are many deps.

s-leger commented 9 years ago

@oeuillot

dearchap commented 9 years ago

I would think that the lib directory should have just device.js, service.js, ssdp.js, rest should go into utils or other dirs. I've just submitted a pull request for basic dir change

s-leger commented 9 years ago

There is a clear need for a bit more structure. (as we are talking about many devices and services now) Services deps may reside into folders under lib as upnp classes, repositories ... Maybe we do have to change contentdirectory related deps found in lib into a sub dir. Always keep in mind that repo depth could be a nightmare when including files. A good looking structure worth nothing if at the end of the day you end up with require ../../../serviceXX/serviceXX Keeping all services and devices implementation under lib seem a good idea, to prevent the require hell. Even if we end up with say 5 devices and 10 services this is far from being not manageable.

About your first post : the device class may be splitted into Rootdevice handling httpserver and routing requests, and Subdevice handling device configuration and init, in a way we can implement any valid upnp device architecture on a single host.

s-leger commented 9 years ago

For services, file naming convention may allow to put all deps into a single folder ensuring auto required files always work. Also a naming convention for those folders should be a good starting point (service filename prefix).

dearchap commented 9 years ago

My main motivation for using upnpserver is to model a Sonos Zone Player. From its device description I see that it has around 10 services(not including ContentManager, etc) and 2 sub-devices. My initial thought was that upnpserver can have configured with a different "personality" for such use cases using plugins and such. Thats what led me to propose the devices,services directory. However I am open to any mechanism that makes development and structure easy to understand.

s-leger commented 9 years ago

Look realy promising ! I'm planning to achieve an airtunes zone player (allready have one running here, but based on less stronger implementation)

Started a basic implementation of a Player abstraction class to extend and plug into MediaRender, automagically getting events like play, volume.. from services.

Should take a look at a common way to handle multiple instances of services creation on the fly, or via configuration (basic instance creation, destroy and routing allready here).

Currently, i'm thinking about a far stronger abstraction on soap requests/response (using generic actions in/out params handling for any action) making implementation mutch easyer for all soap actions.

dearchap commented 9 years ago

So shall I revert back my services, devices dir changes ? I can just use the current mechanism. Instead of thinking of abstracting out everything right away perhaps I can make my changes in my forked version and then we can figure out how to abstract over time. The challenge then would be the merge if the forks are very far apart.

s-leger commented 9 years ago

For the dirs, i think it's better to stay in sync with the main structure.

Soap action handlers planned changes: A generic common handler get the request, push all in params into stateVars, then call your handler if any, with only one argument : a callback. You process whatever the action has to do, using the in stateVars, pushing result to the out stateVars. When you are done, you callback with either a soap error code and a user friendly error message, or null if action success. If the action only require to send out statevars without any processing, you simply let the generic handler do the job for you. The generic common handler will then build the soap response/error using the out params.

For services using multiple instances and A_ARG_TYPE_instanceID, the generic handler knows the route of each instance to pass requests to, so you don't have to care about this at all.

User defined Soap actions handlers must follow this naming convention rule prefix : processSoap_ + the exact action name case sensitive. This way the generic handler know there is something to call to process the request.

StateVars access are done via this.stateVars["varname"].get() and .set()

s-leger commented 9 years ago

As a side note about stateVars access. The stateVars accessors trigger events on set (if any). So if you don't want to send an event, you can use the .value property of the statevar instead of .set()

dearchap commented 9 years ago

I just looked at the latest changes on device branch and they are very much what I needed. I was expecting RootDevice to subclass Device so that most of the features of Device will be available. For example my rootDevice will have services as well as subdevices. Also it appears that some servers add additional fields in headers for NOTIFY etc. Can we have a callback from ssdp into say service or device object to fill in additional headers(as well as modify existing ones) ?

s-leger commented 9 years ago

Well, device class is now split into rootdevice and subdevice. (i just left the file as it, but no more in use)

Whatever device architecture you are planning to achieve (with or without embed devices) require the use of the two classes in the same way.

RootDevice use a single httpserver for any number of subdevices and route requests according. The first device set in configuration is the root one, if there is more than one device, the others are embed ones. You may have any rootDevices, each with or without embed devices.

Any device class (MediaServer...) should inherit from SubDevice (may it be embed or not - this is configuration dependent). See (updated) configuration in this post.

For ssdp, callback already exist, but only at service creation time. see SubDevice.prototype.addService

-> self.root.ssdp.addService( self.uuid, service.type); So « vendor » defined headers could be a property of the service. updateId is a ssdp 1.1 header, reflecting updateid in description.xml

see RootDevice creator ->self.ssdp[addmethod](sub.uuid, sub.type, location); So « vendor » defined headers could be a property of the subdevice.

The standard ssdp headers are defined by the spec, so not expected to be modified and thus are not exposed. Also be aware of the ssdp version (configurable to 1.0 - default or 1.1)

Ssdp is exposed into rootdevice.

from service it’s this.device.root.ssdp from subdevice it’s this.root.ssdp from rootdevice i’ts this.ssdp

Le 13 sept. 2015 à 23:31, dearchap notifications@github.com a écrit :

I just looked at the latest changes on device branch and they are very much what I needed. I was expecting RootDevice to subclass Device so that most of the features of Device will be available. For example my rootDevice will have services as well as subdevices. Also it appears that some servers add additional fields in headers for NOTIFY etc. Can we have a callback from ssdp into say service or device object to fill in additional headers(as well as modify existing ones) ?

— Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37#issuecomment-139919187.

dearchap commented 9 years ago

Okay I missed the subdevice.js portion. Yeah now it makes sense. I am able to create devices/subdevices properly now. As for headers it is more for custom fields in the header rather than the UDN/USN itself. For example Sonos uses some X-RINCON- fields for NOTIFY and such.

s-leger commented 9 years ago

Are those headers changing over time ? I have already something working for per device based headers. A bit harder to handle this for every service.

Le 14 sept. 2015 à 03:38, dearchap notifications@github.com a écrit :

Okay I missed the subdevice.js portion. Yeah now it makes sense. I am able to create devices/subdevices properly now. As for headers it is more for custom fields in the header rather than the UDN/USN itself. For example Sonos uses some X-RINCON- fields for NOTIFY and such.

— Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37#issuecomment-139937802.

s-leger commented 9 years ago

All your devices should inherit from subdevice ! Whenever they are root or embed one depends only on the order they appear in configuration.

Pushed changes

Added device and service skeleton as sample.

Le 14 sept. 2015 à 03:41, stephen leger stephen@3dservices.ch a écrit :

Are those headers changing over time ? I have already something working for per device based headers. A bit harder to handle this for every service.

Le 14 sept. 2015 à 03:38, dearchap <notifications@github.com mailto:notifications@github.com> a écrit :

Okay I missed the subdevice.js portion. Yeah now it makes sense. I am able to create devices/subdevices properly now. As for headers it is more for custom fields in the header rather than the UDN/USN itself. For example Sonos uses some X-RINCON- fields for NOTIFY and such.

— Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37#issuecomment-139937802.

s-leger commented 9 years ago

Note : addType now include a default soap error code just after the type of var. Since type is the basis for simple input validation and parsing. Could be 600 by default.

Range values are supported : replace allowedvaluelist [] by an object {minimum:required , maximum:required , step:optionnal}

Le 14 sept. 2015 à 03:50, stephen leger stephen@3dservices.ch a écrit :

All your devices should inherit from subdevice ! Whenever they are root or embed one depends only on the order they appear in configuration.

Pushed changes

Added device and service skeleton as sample.

Le 14 sept. 2015 à 03:41, stephen leger <stephen@3dservices.ch mailto:stephen@3dservices.ch> a écrit :

Are those headers changing over time ? I have already something working for per device based headers. A bit harder to handle this for every service.

Le 14 sept. 2015 à 03:38, dearchap <notifications@github.com mailto:notifications@github.com> a écrit :

Okay I missed the subdevice.js portion. Yeah now it makes sense. I am able to create devices/subdevices properly now. As for headers it is more for custom fields in the header rather than the UDN/USN itself. For example Sonos uses some X-RINCON- fields for NOTIFY and such.

— Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37#issuecomment-139937802.

dearchap commented 9 years ago

The extended header fields by themselves won't change over time. Some of the field values will change though. For eg the Sonos household ID (HHID) is sent in every notify , while the field name itself remains the same the value will change depending on the network. The easiest way I can think of it to pass a callback arg to ssdp.addService(udn, usn, callback) . When the headers are being constructed for this udn,usn the service is given a chance to fill in additional headers. Or possibly the ssdp.addDevice could have it since those field are usually constant for all the services. 

On Sun, Sep 13, 2015 at 7:20 PM -0700, "s-leger" notifications@github.com wrote:

Note :

addType now include a default soap error code just after the type of var.

Since type is the basis for simple input validation and parsing. Could be 600 by default.

Range values are supported : replace allowedvaluelist [] by an object {minimum:required , maximum:required , step:optionnal}

Le 14 sept. 2015 à 03:50, stephen leger stephen@3dservices.ch a écrit :

All your devices should inherit from subdevice !

Whenever they are root or embed one depends only on the order they appear in configuration.

Pushed changes

Added device and service skeleton as sample.

Le 14 sept. 2015 à 03:41, stephen leger <stephen@3dservices.ch mailto:stephen@3dservices.ch> a écrit :

Are those headers changing over time ?

I have already something working for per device based headers.

A bit harder to handle this for every service.

Le 14 sept. 2015 à 03:38, dearchap <notifications@github.com mailto:notifications@github.com> a écrit :

Okay I missed the subdevice.js portion. Yeah now it makes sense. I am able to create devices/subdevices properly now. As for headers it is more for custom fields in the header rather than the UDN/USN itself. For example Sonos uses some X-RINCON- fields for NOTIFY and such.

Reply to this email directly or view it on GitHub https://github.com/oeuillot/upnpserver/issues/37#issuecomment-139937802.

— Reply to this email directly or view it on GitHub.