easy_upnp
A super simple UPnP control point client for Ruby
Installing
easy_upnp is available on Rubygems. You can install it with:
$ gem install easy_upnp
You can also add it to your Gemfile:
gem 'easy_upnp'
Example usage
Find devices with SSDP
Simple Service Discovery Protocol (SSDP) is a simple UDP protocol used to discover services on a network. It's the entry point to create control points in easy_upnp.
The search
method takes one argument -- the "search target". This controls a header sent in the SSDP packet which affects the devices that respond to the search query. You can use 'ssdp:all'
to specify that all devices should respond.
require 'easy_upnp/ssdp_searcher'
searcher = EasyUpnp::SsdpSearcher.new
devices = searcher.search 'ssdp:all'
This will return a list of EasyUpnp::UpnpDevice
objects. You'll use these to interact with devices on your network.
Interacting with a specific device
Once you have a EasyUpnp::UpnpDevice
, you can start interacting with the services it advertizes. To get a list of all services a device supports:
device = devices.first
device.all_services
# => ["urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ConnectionManager:1", "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"]
You can then create a service client and make calls to the service:
service = device.service 'urn:schemas-upnp-org:service:ContentDirectory:1'
service.service_methods
# => ["GetSearchCapabilities", "GetSortCapabilities", "GetSystemUpdateID", "Browse", "Search"]
service.GetSystemUpdateID
# => {:Id=>"207"}
Static client construction
After you've constructed a client (DeviceControlPoint
), you probably don't want to have to use SSDP to construct it again the next time you use it. DeviceControlPoint
is equipped with #to_params
and #from_params
methods to make this easy.
Say you have a client called client
. To dump it into a hash, do the following:
params = client.to_params
#=> {:urn=>"urn:schemas-upnp-org:service:ContentDirectory:1", :service_endpoint=>"http://10.133.8.11:8200/ctl/ContentDir", :definition=>"<?xml version=\"1.0\"?>\r\n<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">( ... clipped ... )</scpd>", :options=>{}}
We can then reconstruct a client from these params and use it normally:
client = EasyUpnp::DeviceControlPoint.from_params(params)
client.GetSystemUpdateID
=> {:Id=>"258"}
Logging
By default, logs will be printed to $stdout
at the :error
level. To change this behavior, you can use the following options when constructing a control point:
service = client.service(
'urn:schemas-upnp-org:service:ContentDirectory:1',
log_enabled: true,
log_level: :info
)
service = client.service('urn:schemas-upnp-org:service:ContentDirectory:1') do |s|
s.log_enabled = true
s.log_level = :debug
end
Validation
Clients can validate the arguments passed to its methods. By default, this behavior is disabled. You can enable it when initializing a client:
client = device.service('urn:schemas-upnp-org:service:ContentDirectory:1') do |o|
o.validate_arguments = true
end
This enables type checking in addition to whatever validation information is available in the UPnP service's definition. For example:
client.GetVolume(InstanceID: '0', Channel: 'Master')
#: ArgumentError: Invalid value for argument InstanceID: 0 is the wrong type. Should be one of: [Integer]
client.GetVolume(InstanceID: 0, Channel: 'Master2')
#: ArgumentError: Invalid value for argument Channel: Master2 is not in list of allowed values: ["Master"]
client.GetVolume(InstanceID: 0, Channel: 'Master')
#=> {:CurrentVolume=>"32"}
It's also possible to retrieve information about arguments:
client.method_args(:SetVolume)
#=> [:InstanceID, :Channel, :DesiredVolume]
validator = client.arg_validator(:SetVolume, :DesiredVolume)
validator.required_class
#=> Integer
validator.valid_range
#=> #<Enumerator: 0..100:step(1)>
validator.valid_range.max
#=> 100
validator.validate(32)
#=> true
validator.validate(101)
#: ArgumentError: 101 is not in allowed range of values: #<Enumerator: 0..100:step(1)>
validator = client.arg_validator(:SetVolume, :Channel)
validator.allowed_values
#=> ["Master"]