class UPnPServer
Attributes
action_handler[RW]
devices[RW]
Public Class Methods
new(host='0.0.0.0', port=0)
click to toggle source
# File lib/upnp_server.rb, line 58 def initialize(host='0.0.0.0', port=0) @finishing = false @devices = {} @ssdp_listener = SSDP::SsdpListener.new @ssdp_listener.handler = self @http_server = WEBrick::HTTPServer.new :BindAddress => host, :Port => port @http_server.mount '/', UPnPServerServlet, self @action_handler = nil @subscriptions = {} @interval_timer = 10 end
Public Instance Methods
get_device_description(path)
click to toggle source
# File lib/upnp_server.rb, line 93 def get_device_description(path) hash = path.split('/').reject(&:empty?).first @devices.each do |udn, device| if get_hash(udn) == hash return UPnPDevice.to_xml_doc device end end end
get_device_location(device)
click to toggle source
# File lib/upnp_server.rb, line 256 def get_device_location(device) host = get_ip_address port = @http_server.config[:Port] return "http://#{host}:#{port}/#{get_hash(device.udn)}/device.xml" end
get_hash(udn)
click to toggle source
# File lib/upnp_server.rb, line 279 def get_hash(udn) Digest::MD5.hexdigest udn end
get_ip_address()
click to toggle source
# File lib/upnp_server.rb, line 283 def get_ip_address Socket::getaddrinfo(Socket.gethostname, 'echo', Socket::AF_INET)[0][3] end
get_scpd(path)
click to toggle source
# File lib/upnp_server.rb, line 103 def get_scpd(path) hash = path.split('/').reject(&:empty?).first @devices.values.each do |device| device.all_services.each do |service| if get_hash(service.service_type) == hash return UPnPScpd.to_xml_doc service.scpd end end end end
msearch_response(st, usn, location, ext_header = nil)
click to toggle source
# File lib/upnp_server.rb, line 262 def msearch_response(st, usn, location, ext_header = nil) header = HttpHeader.new FirstLine.new ['HTTP/1.1', '200', 'OK'] fields = { 'Cache-Control' => 'max-age=1800', 'Location' => location, 'ST' => st, 'USN' => usn, 'Ext' => '', 'Date' => Time.now.httpdate, } header.update! fields if ext_header != nil header.update! ext_header end return header end
notify_alive_all()
click to toggle source
# File lib/upnp_server.rb, line 89 def notify_alive_all end
on_action_request(path, soap_req)
click to toggle source
# File lib/upnp_server.rb, line 115 def on_action_request(path, soap_req) hash = path.split('/').reject(&:empty?).first @devices.values.each do |device| device.all_services.each do |service| if get_hash(service.service_type) == hash if @action_handler return @action_handler.call service, soap_req end end end end end
on_msearch(st)
click to toggle source
# File lib/upnp_server.rb, line 182 def on_msearch(st) # ST can be one of # - ssdp:all # - upnp:rootdevice # - udn # - device type # - service type # HTTP/1.1 200 OK # Cache-Control: max-age=1800 # HOST: 239.255.255.250:1900 # Location: http://172.17.0.2:9001/device.xml?udn=e399855c-7ecb-1fff-8000-000000000000 # ST: e399855c-7ecb-1fff-8000-000000000000 # Server: Cross-Platform/0.0 UPnP/1.0 App/0.0 # USN: e399855c-7ecb-1fff-8000-000000000000 # Ext: # Date: Sat, 08 Sep 2018 13:47:14 GMT response_list = [] if st == 'ssdp:all' devices = @devices.values devices.each do |root_device| location = get_device_location root_device response_list << msearch_response('upnp:rootdevice', root_device.udn + '::upnp:rootdevice', location) root_device.all_devices.each do |device| response_list << msearch_response(device.device_type, device.usn, location) end root_device.all_services.each do |service| response_list << msearch_response(service.service_type, root_device.udn + '::' + service.service_type, location) end end elsif st == 'upnp:rootdevice' devices = @devices.values devices.each do |root_device| location = get_device_location root_device response_list << msearch_response('upnp:rootdevice', root_device.udn + '::upnp:rootdevice', location) end else @devices.values.each do |root_device| location = get_device_location root_device root_device.all_devices.each do |device| if device.device_type == st response_list << msearch_response(st, device.usn, location) end end root_device.all_services.each do |service| if service.service_type == st response_list << msearch_response(st, root_device.udn + '::' + service.service_type, location) end end end end return response_list end
on_renew_subscription(sid)
click to toggle source
# File lib/upnp_server.rb, line 164 def on_renew_subscription(sid) subscription = @subscriptions[sid] subscription.renew_timeout end
on_ssdp_header(ssdp_header)
click to toggle source
# File lib/upnp_server.rb, line 175 def on_ssdp_header(ssdp_header) if ssdp_header.msearch? ret = on_msearch ssdp_header['st'] end end
on_subscribe(path, timeout, callback_urls)
click to toggle source
# File lib/upnp_server.rb, line 148 def on_subscribe(path, timeout, callback_urls) hash = path.split('/').reject(&:empty?).first @devices.values.each do |device| device.all_services.each do |service| if get_hash(service.service_type) == hash sid = 'uuid:' + SecureRandom.uuid subscription = UPnPEventSubscription.new device, service, sid, timeout, callback_urls @subscriptions[sid] = subscription return sid end end end nil end
on_timer()
click to toggle source
# File lib/upnp_server.rb, line 287 def on_timer @subscriptions.reject! {|key, value| value.expired?} notify_alive_all end
on_unsubscribe(sid)
click to toggle source
# File lib/upnp_server.rb, line 170 def on_unsubscribe(sid) @subscriptions.remove sid end
register_device(device, scpd_table)
click to toggle source
# File lib/upnp_server.rb, line 72 def register_device(device, scpd_table) device.all_services.each do |service| hash = get_hash service.service_type service.scpdurl = "/#{hash}/scpd.xml" service.control_url = "/#{hash}/control.xml" service.event_sub_url = "/#{hash}/event.xml" service.scpd = scpd_table[service.service_type] end @devices[device.udn] = device end
set_property(device, service, props)
click to toggle source
# File lib/upnp_server.rb, line 129 def set_property(device, service, props) @subscriptions.values.each do |subscription| if subscription.device.udn == device.udn and subscription.service.service_type == service.service_type subscription.callback_urls.each do |url| url = URI.parse(url) header = { 'SID' => subscription.sid } Net::HTTP.start(url.host, url.port) do |http| req = NotifyRequest.new url, initheader = header req.body = UPnPEventProperty.to_xml_doc props res = http.request req end end end end end
start()
click to toggle source
# File lib/upnp_server.rb, line 303 def start @finishing = false @ssdp_listener_thread = Thread.new { @ssdp_listener.run } @http_server_thread = Thread.new { @http_server.start } @timer_thread = Thread.new { timer_loop } end
stop()
click to toggle source
# File lib/upnp_server.rb, line 310 def stop @finishing = true @http_server.shutdown @http_server_thread.join @timer_thread.join end
timer_loop()
click to toggle source
# File lib/upnp_server.rb, line 292 def timer_loop start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) while not @finishing dur = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) - start if dur >= @interval_timer on_timer start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) end end end
unregister_device(device)
click to toggle source
# File lib/upnp_server.rb, line 84 def unregister_device(device) @devices.remove device.udn end