huemote 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: