soffes/sonos

View on GitHub
lib/sonos/endpoint/upnp.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'httpclient'

module Sonos::Endpoint::Upnp
  UPNP_TIMEOUT = 600

  ENDPOINT = {
    content_directory: '/MediaServer/ContentDirectory/Event',
    av_transport: '/MediaRenderer/AVTransport/Event',
    zone_group_topology: '/ZoneGroupTopology/Event',
    device_properties: '/DeviceProperties/Event',
    group_management: '/GroupManagement/Event',
    group_rendering_control: '/MediaRenderer/GroupRenderingControl/Event',
    rendering_control: '/MediaRenderer/RenderingControl/Event',
    connection_manager: '/MediaRenderer/ConnectionManager/Event',
    queue: '/MediaRenderer/Queue/Event',
    alarm_clock: '/AlarmClock/Event',
    music_services: '/MusicServices/Event',
    system_properties: '/SystemProperties/Event'
  }.freeze

  # Subscribes to UPNP events, with callbacks being sent to the provided URL
  # @param [String] The URL to receive http callbacks from the device
  # @param [Symbol] The [Sonos::Endpoint::Upnp::ENDPOINT] to subscribe to
  # @return [Hash] Returns the timeout for the HTTP listener as well as the device SID
  def subscribe_to_upnp_events(callback_url, event)
    client = HTTPClient.new

    request_headers = {
      # The URL to be requested when a callback happens
      'CALLBACK' => "<#{callback_url}>",
      # Apparently required in order to appease the UPNP gods
      'NT' => 'upnp:event',
      # The timeout for the subscription - set to 10 minutes
      'TIMEOUT' => "Second-#{UPNP_TIMEOUT}"
    }

    response = client.request(:subscribe, event_url(event), header: request_headers)

    # Convert the resulting headers into something less shouty
    result_headers = response.header.all.inject({}) do |result, item|
      result[item[0].downcase.to_sym] = item[1]
      result
    end

    # Return all the information you'd need in order to do an unsusbscribe
    Subscription.new({
      timeout: result_headers[:timeout][/(\d+)/].to_i,
      sid: result_headers[:sid],
      event: event
    })
  end

  # Unsubscribes an existing UPNP event
  # @param [String] The subscription ID that you wish to cancel
  # @param [Symbol] The [Sonos::Endpoint::Upnp::ENDPOINT] to which it belongs
  def unsubscribe_from_upnp_events(subscription)
    HTTPClient.new.request(:unsubscribe, event_url(subscription.event), header: {'SID' => subscription.sid })
  end

  # Simple class structure for describing an active subscription
  class Subscription
    attr_accessor :timeout
    attr_accessor :sid
    attr_accessor :event

    def initialize(hash)
      hash.each do |k, v|
        self.send("#{k}=", v) if respond_to?(k)
      end
    end
  end

  private

  def event_url(event)
    "http://#{self.ip}:#{Sonos::PORT}#{ENDPOINT[event]}"
  end

end