huemote 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9be51c602e6af5ead50c6971f39506a6a5032421
4
+ data.tar.gz: 114b210e64560c8a666030a6098e37767da0bc2e
5
+ SHA512:
6
+ metadata.gz: 4b2a0ff35970f5de67a3dc2997370f0cc758200ea12e01224abd329872365555b17ed6350e60af22481d00416efaaa82f0fb6c2097ece58f3c76da56154cfd5a
7
+ data.tar.gz: 63f14a307fce18f9cbeaeb9552de0b8612a57547c576ee702f8025a571f6803f192521786af8cb647652b588530782201ea2d2b7c6ead929afe559e44f24bd39
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
19
+ *.swo
20
+ *~
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - rbx
7
+ - ruby-head
8
+ - jruby-head
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'httparty'
5
+ gem 'rspec'
6
+ gem 'rake'
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Kevin Gisi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ # Huemote [![Build Status](https://travis-ci.org/gisikw/huemote.png?branch=master)](https://travis-ci.org/gisikw/huemote) [![Gem Version](https://badge.fury.io/rb/huemote.png)](http://badge.fury.io/rb/huemote) [![Code Climate](https://codeclimate.com/github/gisikw/huemote.png)](https://codeclimate.com/github/gisikw/huemote)
2
+
3
+ Huemote is an interface for controlling Philips Hue lights. Unlike other implementations, it does not rely on Philips backend servers for upnp discovery.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'huemote'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install huemote
18
+
19
+ ## Usage
20
+
21
+ You can fetch all lights on your network via:
22
+
23
+ ```ruby
24
+ lights = Huemote::Light.all #=> [#<Huemote::Light:0x511ee8dd @name="Hallway", @id="1">, #<Huemote::Light:0x444a2ec6 @name="Main Room", @id="2">, #<Huemote::Light:0x6244ec30 @name="Bathroom 1", @id="3">, #<Huemote::Light:0x1aee75b7 @name="Bathroom 2", @id="4">, #<Huemote::Light:0x1d724f31 @name="Bathroom 3", @id="5">]
25
+ ```
26
+
27
+ Or select a light by its friendly name:
28
+
29
+ ```ruby
30
+ switch = Huemote::Light.find('Hallway') #=> #<Huemote::Light:0x511ee8dd @name="Hallway", @id="1">
31
+ ```
32
+
33
+ Given a Light instance, you can call the following methods:
34
+ ```ruby
35
+ light.off? #=> [true,false]
36
+ light.on? #=> [true,false]
37
+ light.on!
38
+ light.off!
39
+ light.toggle!
40
+ light.brightness(250)
41
+ light.saturation(50)
42
+ light.effect('colorloop')
43
+ light.alert('select')
44
+ light.hue(5)
45
+ light.xy([0.123,0.425])
46
+ light.ct(300)
47
+ ```
48
+
49
+ ## Performance
50
+
51
+ Huemote is designed to be performant - and as such, it will leverage the best HTTP library available for making requests. Currently, Wemote will use (in order of preference): `manticore`, `typhoeus`, `httparty`, and finally (miserably) `net/http`. Because you probably like things fast too, we recommend you `gem install manticore` on JRuby, or `gem install typhoeus` on another engine. In order to keep the gem as flexible as possible, none of these are direct dependencies. They just make Wemote happy and fast.
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec) do |spec|
4
+ spec.pattern = 'spec/**/*_spec.rb'
5
+ spec.rspec_opts = ['--tty --color --format documentation']
6
+ end
7
+
8
+ task :default => :spec
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'huemote/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "huemote"
8
+ spec.version = Huemote::VERSION
9
+ spec.authors = ["Kevin Gisi"]
10
+ spec.email = ["kevin@kevingisi.com"]
11
+ spec.description = %q{Huemote is a Ruby gem for managing Philips Hue lights}
12
+ spec.summary = %q{Huemote is a Ruby gem for managing Philips Hue lights}
13
+ spec.homepage = "https://github.com/gisikw/huemote"
14
+ spec.license = "MIT"
15
+ spec.platform = 'ruby'
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ require_relative './huemote/version'
4
+
5
+ module Huemote
6
+ require_relative './huemote/client'
7
+ require_relative './huemote/light'
8
+ require_relative './huemote/bridge'
9
+
10
+ class << self
11
+ def discover(socket=nil)
12
+ Huemote::Bridge.discover
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,136 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+
4
+ module Huemote
5
+ class Bridge
6
+ DEVICE_TYPE = "Huemote"
7
+ USERNAME = "HuemoteRubyGem"
8
+ MULTICAST_ADDR = '239.255.255.250'
9
+ BIND_ADDR = '0.0.0.0'
10
+ PORT = 1900
11
+ DISCOVERY= <<-EOF
12
+ M-SEARCH * HTTP/1.1\r
13
+ HOST: 239.255.255.250:1900\r
14
+ MAN: "ssdp:discover"\r
15
+ MX: 10\r
16
+ ST: urn:schemas-upnp-org:device:Basic:1\r
17
+ \r
18
+ EOF
19
+
20
+ class << self
21
+ def get
22
+ @bridge ||= begin
23
+ client = Huemote::Client.new
24
+ body = nil
25
+ device = fetch_upnp.detect{|host,port|body = client.get("http://#{host}:#{port}/description.xml").body; body.match('<modelURL>http://www.meethue.com</modelURL>')}
26
+ self.new(*device,body)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def discover(socket = nil)
33
+ @bridge = nil
34
+ client = Huemote::Client.new
35
+ body = nil
36
+
37
+ devices, socket = fetch_upnp(true,socket)
38
+ device = devices.detect{|host,port|body = client.get("http://#{host}:#{port}/description.xml").body; body.match('<modelURL>http://www.meethue.com</modelURL>')}
39
+ @bridge = self.new(*device,body)
40
+
41
+ socket
42
+ end
43
+
44
+ def ssdp_socket
45
+ socket = UDPSocket.new
46
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(BIND_ADDR).hton)
47
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 1)
48
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, 1)
49
+ socket.bind(BIND_ADDR,PORT)
50
+ socket
51
+ end
52
+
53
+ def listen(socket,devices)
54
+ sleep 1
55
+ Thread.start do
56
+ loop do
57
+ message, _ = socket.recvfrom(1024)
58
+ match = message.match(/LOCATION:\s+http:\/\/([^\/]+)/)
59
+ devices << match[1].split(':') if match
60
+ end
61
+ end
62
+ end
63
+
64
+ def fetch_upnp(return_socket=false,socket=nil)
65
+ socket ||= ssdp_socket
66
+ devices = []
67
+
68
+ 3.times { socket.send(DISCOVERY, 0, MULTICAST_ADDR, PORT) }
69
+
70
+ # The following is a bit silly, but is necessary for JRuby support,
71
+ # which seems to have some issues with socket interruption. If you have
72
+ # a working JRuby solution that doesn't require this kind of hackery,
73
+ # by all means, submit a pull request!
74
+
75
+ listen(socket,devices).tap{|l|sleep 1; l.kill}
76
+ devices.uniq!
77
+
78
+ if return_socket
79
+ [devices,socket]
80
+ else
81
+ socket.close
82
+ devices
83
+ end
84
+ end
85
+ end
86
+
87
+ attr_accessor :name
88
+
89
+ def initialize(host,port,body)
90
+ @host, @port = host, port
91
+ @name = body.match(/<friendlyName>([^<]+)<\/friendlyName>/)[1]
92
+ end
93
+
94
+ def _get(path,params={})
95
+ request(:get,path,params)
96
+ end
97
+
98
+ def _post(path,params={})
99
+ request(:post,path,params)
100
+ end
101
+
102
+ def _put(path,params={})
103
+ request(:put,path,params)
104
+ end
105
+
106
+ private
107
+
108
+ def request(method,path,params)
109
+ auth! unless authed?
110
+ JSON.parse(client.send(method,"#{base_url}/api/#{USERNAME}/#{path}",params.to_json).body)
111
+ end
112
+
113
+ def base_url
114
+ @base_url ||= "http://#{@host}:#{@port}"
115
+ end
116
+
117
+ def authed?
118
+ @authed ||= !client.get("#{base_url}/api/#{USERNAME}/lights").body.match('unauthorized user')
119
+ end
120
+
121
+ def auth!
122
+ unless authed?
123
+ body = client.post("#{base_url}/api",{devicetype:DEVICE_TYPE,username:USERNAME}.to_json).body
124
+ if body.match('link button not pressed')
125
+ puts "In order for the Hue Bridge to interact, please press the button just this once and try again."
126
+ false
127
+ end
128
+ end
129
+ end
130
+
131
+ def client
132
+ @client ||= Huemote::Client.new
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,83 @@
1
+ module Huemote
2
+ class Client
3
+
4
+ def self.technique
5
+ @technique ||= begin
6
+ constants.collect {|const_name| const_get(const_name)}.select {|const| const.class == Module}.detect do |mod|
7
+ fulfilled = false
8
+ begin
9
+ next unless mod.const_defined?(:DEPENDENCIES)
10
+ mod.const_get(:DEPENDENCIES).map{|d|require d}
11
+ fulfilled = true
12
+ rescue LoadError
13
+ end
14
+ fulfilled
15
+ end
16
+ end
17
+ end
18
+
19
+ module SmartLib
20
+ %w{get post put}.each do |name|
21
+ define_method name do |*args|
22
+ _req(@lib,name,*args).tap{|r|r.call if @call}
23
+ end
24
+ end
25
+ end
26
+
27
+ module Manticore
28
+ DEPENDENCIES = ['manticore']
29
+ def self.extended(base)
30
+ base.instance_variable_set(:@lib,::Manticore)
31
+ base.instance_variable_set(:@call,true)
32
+ base.extend(SmartLib)
33
+ end
34
+ end
35
+
36
+ module Typhoeus
37
+ DEPENDENCIES = ['typhoeus']
38
+ def self.extended(base)
39
+ base.instance_variable_set(:@lib,::Typhoeus)
40
+ base.extend(SmartLib)
41
+ end
42
+ end
43
+
44
+ module HTTParty
45
+ DEPENDENCIES = ['httparty']
46
+ def self.extended(base)
47
+ base.instance_variable_set(:@lib,::HTTParty)
48
+ base.extend(SmartLib)
49
+ end
50
+ end
51
+
52
+ module NetHTTP
53
+ DEPENDENCIES = ['net/http','uri']
54
+
55
+ def request(klass,url,body=nil,headers=nil)
56
+ uri = URI.parse(url)
57
+ http = Net::HTTP.new(uri.host, uri.port)
58
+ request = klass.new(uri.request_uri)
59
+ headers.map{|k,v|request[k]=v} if headers
60
+ (request.body = body) if body
61
+ response = http.request(request)
62
+ end
63
+
64
+ %w{get post put}.each do |name|
65
+ define_method name do |*args|
66
+ request(Net::HTTP.const_get(name.capitalize),*args)
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ def initialize
73
+ extend Huemote::Client.technique
74
+ end
75
+
76
+ private
77
+
78
+ def _req(lib,method,url,body=nil,headers=nil)
79
+ lib.send(method,url,{body:body,headers:headers})
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,76 @@
1
+ module Huemote
2
+ class Light
3
+
4
+ STATES = {
5
+ brightness: 'bri',
6
+ saturation: 'sat',
7
+ effect: 'effect',
8
+ alert: 'alert',
9
+ hue: 'hue',
10
+ xy: 'xy',
11
+ ct: 'ct'
12
+ }
13
+
14
+ class << self
15
+
16
+ def all(refresh=false)
17
+ @lights = nil if refresh
18
+ @lights ||= bridge._get('lights').map{|id,h|self.new(id,h['name'])}
19
+ end
20
+
21
+ def find(name)
22
+ all.detect{|l|l.name == name}
23
+ end
24
+
25
+ private
26
+
27
+ def bridge
28
+ @bridge ||= Huemote::Bridge.get
29
+ end
30
+
31
+ end
32
+
33
+ attr_accessor :name
34
+
35
+ def initialize(id,name)
36
+ @id, @name = id, name
37
+ end
38
+
39
+ def on!
40
+ set!(on:true)
41
+ end
42
+
43
+ def off!
44
+ set!(on:false)
45
+ end
46
+
47
+ def on?
48
+ bridge._get("lights/#{@id}")['state']['on']
49
+ end
50
+
51
+ def off?
52
+ !on?
53
+ end
54
+
55
+ def toggle!
56
+ on? ? off! : on!
57
+ end
58
+
59
+ STATES.each do |name,state|
60
+ define_method name do |arg|
61
+ set!(state => arg)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def set!(params)
68
+ bridge._put("lights/#{@id}/state",params)
69
+ end
70
+
71
+ def bridge
72
+ @bridge ||= Huemote::Bridge.get
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module Huemote
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Huemote::Bridge do
4
+ describe 'initialize' do
5
+ it 'should set the host, port and friendly name' do
6
+ bridge = Huemote::Bridge.new('fakehost',1234,'<friendlyName>Bob</friendlyName>')
7
+ bridge.instance_variable_get(:@host).should == 'fakehost'
8
+ bridge.instance_variable_get(:@port).should == 1234
9
+ bridge.instance_variable_get(:@name).should == 'Bob'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1 @@
1
+ require 'huemote'
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: huemote
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Gisi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Huemote is a Ruby gem for managing Philips Hue lights
14
+ email:
15
+ - kevin@kevingisi.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .gitignore
21
+ - .travis.yml
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - huemote.gemspec
27
+ - lib/huemote.rb
28
+ - lib/huemote/bridge.rb
29
+ - lib/huemote/client.rb
30
+ - lib/huemote/light.rb
31
+ - lib/huemote/version.rb
32
+ - spec/huemote/bridge_spec.rb
33
+ - spec/spec_helper.rb
34
+ homepage: https://github.com/gisikw/huemote
35
+ licenses:
36
+ - MIT
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.2.2
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Huemote is a Ruby gem for managing Philips Hue lights
58
+ test_files:
59
+ - spec/huemote/bridge_spec.rb
60
+ - spec/spec_helper.rb
61
+ has_rdoc: