Skip to content

Instantly share code, notes, and snippets.

@tenderlove
Last active December 30, 2016 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tenderlove/feec609014ebe2fdcdd5141be28ebf1c to your computer and use it in GitHub Desktop.
Save tenderlove/feec609014ebe2fdcdd5141be28ebf1c to your computer and use it in GitHub Desktop.
###
# Find WeMo Insight switches and turn them on or off.
#
# Usage:
#
# WheeMo::Switch.each do |switch|
# p switch.friendly_name
# if switch.on?
# switch.off!
# else
# switch.on!
# end
# end
#
# License: Public Domain
require 'socket'
require 'net/http'
module WheeMo
class Switch < Struct.new :location
class << self; include Enumerable; end
def self.each
UPNP.each("upnp:rootdevice") { |response|
usn = response['usn'].split ':'
if usn.length == 2 && usn.last =~ /Insight/
yield Switch.new URI.parse response['location']
end
}
end
def friendly_name
doc = Net::HTTP.get location
/<friendlyName>([^<]*)<\/friendlyName>/.match(doc)[1]
end
def on?; !off?; end
def off?
res = do_req make_binary_state_req 'Get', '1'
/<BinaryState>([^<]*)<\/BinaryState>/.match(res.body)[1] == '0'
end
def on!
do_req make_binary_state_req 'Set', '1'
self
end
def off!
do_req make_binary_state_req 'Set', '0'
self
end
private
def do_req req
http = Net::HTTP.new location.host, location.port
http.request req
end
def make_binary_state_req get_or_set, state
req = Net::HTTP::Post.new '/upnp/control/basicevent1'
req['SOAPACTION'] = "\"urn:Belkin:service:basicevent:1##{get_or_set}BinaryState\""
req['Content-Type'] = "text/xml; charset=\"utf-8\""
req.body = <<-eoxml
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:#{get_or_set}BinaryState xmlns:u="urn:Belkin:service:basicevent:1">
<BinaryState>#{state}</BinaryState>
</u:#{get_or_set}BinaryState>
</s:Body>
</s:Envelope>
eoxml
req
end
end
class UPNP
BROADCAST_ADDR = '239.255.255.250'
BROADCAST_PORT = 1900
SEARCH = [
"M-SEARCH * HTTP/1.1",
"HOST: #{BROADCAST_ADDR}:#{BROADCAST_PORT}",
"MAN: \"ssdp:discover\"",
"MX: 5",
"ST: ssdp:all",
]
def self.each type = nil, timeout = 2
sock = UDPSocket.new
query = SEARCH
query += ["ST: #{type}"] if type
sock.send(query.join("\r\n") + "\r\n\r\n", 0, BROADCAST_ADDR, BROADCAST_PORT)
buf = Net::BufferedIO.new sock
loop do
rd, = IO.select [sock], nil, nil, timeout
break unless rd
yield Net::HTTPResponse.read_new buf
end
ensure
sock.close if sock
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment